Skip to content

Commit f9a9c9e

Browse files
committed
feat(manual_ilog2): new lint
1 parent 7c7bd6e commit f9a9c9e

File tree

9 files changed

+209
-0
lines changed

9 files changed

+209
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6456,6 +6456,7 @@ Released 2018-09-13
64566456
[`manual_flatten`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_flatten
64576457
[`manual_hash_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_hash_one
64586458
[`manual_ignore_case_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_ignore_case_cmp
6459+
[`manual_ilog2`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_ilog2
64596460
[`manual_inspect`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_inspect
64606461
[`manual_instant_elapsed`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_instant_elapsed
64616462
[`manual_is_ascii_check`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_ascii_check

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
303303
crate::manual_float_methods::MANUAL_IS_INFINITE_INFO,
304304
crate::manual_hash_one::MANUAL_HASH_ONE_INFO,
305305
crate::manual_ignore_case_cmp::MANUAL_IGNORE_CASE_CMP_INFO,
306+
crate::manual_ilog2::MANUAL_ILOG2_INFO,
306307
crate::manual_is_ascii_check::MANUAL_IS_ASCII_CHECK_INFO,
307308
crate::manual_is_power_of_two::MANUAL_IS_POWER_OF_TWO_INFO,
308309
crate::manual_let_else::MANUAL_LET_ELSE_INFO,

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ mod manual_div_ceil;
212212
mod manual_float_methods;
213213
mod manual_hash_one;
214214
mod manual_ignore_case_cmp;
215+
mod manual_ilog2;
215216
mod manual_is_ascii_check;
216217
mod manual_is_power_of_two;
217218
mod manual_let_else;
@@ -834,5 +835,6 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
834835
store.register_late_pass(|_| Box::new(toplevel_ref_arg::ToplevelRefArg));
835836
store.register_late_pass(|_| Box::new(volatile_composites::VolatileComposites));
836837
store.register_late_pass(|_| Box::new(replace_box::ReplaceBox));
838+
store.register_late_pass(move |_| Box::new(manual_ilog2::ManualIlog2::new(conf)));
837839
// add lints here, do not remove this comment, it's used in `new_lint`
838840
}

clippy_lints/src/manual_ilog2.rs

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
use clippy_config::Conf;
2+
use clippy_utils::diagnostics::span_lint_and_sugg;
3+
use clippy_utils::msrvs::{self, Msrv};
4+
use clippy_utils::res::{MaybeDef, MaybeTypeckRes};
5+
use clippy_utils::source::snippet_with_applicability;
6+
use clippy_utils::{is_from_proc_macro, sym};
7+
use rustc_ast::LitKind;
8+
use rustc_data_structures::packed::Pu128;
9+
use rustc_errors::Applicability;
10+
use rustc_hir::{BinOpKind, Expr, ExprKind};
11+
use rustc_lint::{LateContext, LateLintPass, LintContext};
12+
use rustc_middle::ty;
13+
use rustc_session::impl_lint_pass;
14+
15+
declare_clippy_lint! {
16+
/// ### What it does
17+
/// Checks for expressions like `31 - x.leading_zeros()` or `x.ilog(2)` which are manual
18+
/// reimplementations of `x.ilog2()`
19+
///
20+
/// ### Why is this bad?
21+
/// Manual reimplementations of `ilog2` increase code complexity for little benefit.
22+
///
23+
/// ### Example
24+
/// ```no_run
25+
/// let x: u32 = 5;
26+
/// let log = 31 - x.leading_zeros();
27+
/// let log = x.ilog(2);
28+
/// ```
29+
/// Use instead:
30+
/// ```no_run
31+
/// let x: u32 = 5;
32+
/// let log = x.ilog2();
33+
/// let log = x.ilog2();
34+
/// ```
35+
#[clippy::version = "1.92.0"]
36+
pub MANUAL_ILOG2,
37+
complexity,
38+
"manually reimplementing `ilog2`"
39+
}
40+
41+
pub struct ManualIlog2 {
42+
msrv: Msrv,
43+
}
44+
45+
impl ManualIlog2 {
46+
pub fn new(conf: &Conf) -> Self {
47+
Self { msrv: conf.msrv }
48+
}
49+
}
50+
51+
impl_lint_pass!(ManualIlog2 => [MANUAL_ILOG2]);
52+
53+
impl LateLintPass<'_> for ManualIlog2 {
54+
fn check_expr<'tcx>(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
55+
if expr.span.in_external_macro(cx.sess().source_map()) {
56+
return;
57+
}
58+
59+
match expr.kind {
60+
// `BIT_WIDTH - 1 - n.leading_zeros()`
61+
ExprKind::Binary(op, left, right)
62+
if left.span.eq_ctxt(right.span)
63+
&& op.node == BinOpKind::Sub
64+
&& let ExprKind::Lit(lit) = left.kind
65+
&& let LitKind::Int(Pu128(val), _) = lit.node
66+
&& let ExprKind::MethodCall(leading_zeros, recv, [], _) = right.kind
67+
&& leading_zeros.ident.name == sym::leading_zeros
68+
// Whether `leading_zeros` is an inherent method, i.e. doesn't come from some unrelated trait
69+
&& cx.ty_based_def(right).opt_parent(cx).is_impl(cx)
70+
&& let ty = cx.typeck_results().expr_ty(recv)
71+
&& let Some(bit_width) = match ty.kind() {
72+
ty::Int(int_ty) => int_ty.bit_width(),
73+
ty::Uint(uint_ty) => uint_ty.bit_width(),
74+
_ => return,
75+
}
76+
&& val == u128::from(bit_width) - 1
77+
&& self.msrv.meets(cx, msrvs::ILOG2)
78+
&& !is_from_proc_macro(cx, expr) =>
79+
{
80+
emit(cx, recv, expr);
81+
},
82+
83+
// `n.ilog(2)`
84+
ExprKind::MethodCall(ilog, recv, [two], _)
85+
if expr.span.eq_ctxt(two.span)
86+
&& ilog.ident.name == sym::ilog
87+
// Whether `ilog` is an inherent method, i.e. doesn't come from some unrelated trait
88+
&& cx.ty_based_def(expr).opt_parent(cx).is_impl(cx)
89+
&& let ExprKind::Lit(lit) = two.kind
90+
&& let LitKind::Int(Pu128(2), _) = lit.node
91+
&& cx.typeck_results().expr_ty(recv).is_integral()
92+
&& self.msrv.meets(cx, msrvs::ILOG2)
93+
&& !is_from_proc_macro(cx, expr) =>
94+
{
95+
emit(cx, recv, expr);
96+
},
97+
98+
_ => {},
99+
}
100+
}
101+
}
102+
103+
fn emit(cx: &LateContext<'_>, recv: &Expr<'_>, full_expr: &Expr<'_>) {
104+
let mut app = Applicability::MachineApplicable;
105+
let recv = snippet_with_applicability(cx, recv.span, "_", &mut app);
106+
span_lint_and_sugg(
107+
cx,
108+
MANUAL_ILOG2,
109+
full_expr.span,
110+
"manually reimplementing `ilog2`",
111+
"try",
112+
format!("{recv}.ilog2()"),
113+
app,
114+
);
115+
}

clippy_utils/src/msrvs.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ msrv_aliases! {
4040
1,71,0 { TUPLE_ARRAY_CONVERSIONS, BUILD_HASHER_HASH_ONE }
4141
1,70,0 { OPTION_RESULT_IS_VARIANT_AND, BINARY_HEAP_RETAIN }
4242
1,68,0 { PATH_MAIN_SEPARATOR_STR }
43+
1,67,0 { ILOG2 }
4344
1,65,0 { LET_ELSE, POINTER_CAST_CONSTNESS }
4445
1,63,0 { CLONE_INTO, CONST_SLICE_FROM_REF }
4546
1,62,0 { BOOL_THEN_SOME, DEFAULT_ENUM_ATTRIBUTE, CONST_EXTERN_C_FN }

clippy_utils/src/sym.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ generate! {
175175
has_significant_drop,
176176
hidden_glob_reexports,
177177
hygiene,
178+
ilog,
178179
insert,
179180
insert_str,
180181
inspect,
@@ -202,6 +203,7 @@ generate! {
202203
join,
203204
kw,
204205
lazy_static,
206+
leading_zeros,
205207
lint_vec,
206208
ln,
207209
lock,

tests/ui/manual_ilog2.fixed

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//@aux-build:proc_macros.rs
2+
#![warn(clippy::manual_ilog2)]
3+
#![allow(clippy::unnecessary_operation)]
4+
5+
use proc_macros::{external, with_span};
6+
7+
fn foo(a: u32, b: u64) {
8+
a.ilog2(); //~ manual_ilog2
9+
a.ilog2(); //~ manual_ilog2
10+
11+
b.ilog2(); //~ manual_ilog2
12+
64 - b.leading_zeros(); // No lint because manual ilog2 is `BIT_WIDTH - 1 - x.leading_zeros()`
13+
14+
// don't lint when macros are involved
15+
macro_rules! two {
16+
() => {
17+
2
18+
};
19+
};
20+
21+
macro_rules! thirty_one {
22+
() => {
23+
31
24+
};
25+
};
26+
27+
a.ilog(two!());
28+
thirty_one!() - a.leading_zeros();
29+
30+
external!($a.ilog(2));
31+
with_span!(span; a.ilog(2));
32+
}

tests/ui/manual_ilog2.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//@aux-build:proc_macros.rs
2+
#![warn(clippy::manual_ilog2)]
3+
#![allow(clippy::unnecessary_operation)]
4+
5+
use proc_macros::{external, with_span};
6+
7+
fn foo(a: u32, b: u64) {
8+
31 - a.leading_zeros(); //~ manual_ilog2
9+
a.ilog(2); //~ manual_ilog2
10+
11+
63 - b.leading_zeros(); //~ manual_ilog2
12+
64 - b.leading_zeros(); // No lint because manual ilog2 is `BIT_WIDTH - 1 - x.leading_zeros()`
13+
14+
// don't lint when macros are involved
15+
macro_rules! two {
16+
() => {
17+
2
18+
};
19+
};
20+
21+
macro_rules! thirty_one {
22+
() => {
23+
31
24+
};
25+
};
26+
27+
a.ilog(two!());
28+
thirty_one!() - a.leading_zeros();
29+
30+
external!($a.ilog(2));
31+
with_span!(span; a.ilog(2));
32+
}

tests/ui/manual_ilog2.stderr

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
error: manually reimplementing `ilog2`
2+
--> tests/ui/manual_ilog2.rs:8:5
3+
|
4+
LL | 31 - a.leading_zeros();
5+
| ^^^^^^^^^^^^^^^^^^^^^^ help: try: `a.ilog2()`
6+
|
7+
= note: `-D clippy::manual-ilog2` implied by `-D warnings`
8+
= help: to override `-D warnings` add `#[allow(clippy::manual_ilog2)]`
9+
10+
error: manually reimplementing `ilog2`
11+
--> tests/ui/manual_ilog2.rs:9:5
12+
|
13+
LL | a.ilog(2);
14+
| ^^^^^^^^^ help: try: `a.ilog2()`
15+
16+
error: manually reimplementing `ilog2`
17+
--> tests/ui/manual_ilog2.rs:11:5
18+
|
19+
LL | 63 - b.leading_zeros();
20+
| ^^^^^^^^^^^^^^^^^^^^^^ help: try: `b.ilog2()`
21+
22+
error: aborting due to 3 previous errors
23+

0 commit comments

Comments
 (0)