Skip to content

Commit d9cc76a

Browse files
committed
feat(manual_ilog2): new lint
1 parent e567e30 commit d9cc76a

File tree

9 files changed

+168
-0
lines changed

9 files changed

+168
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6541,6 +6541,7 @@ Released 2018-09-13
65416541
[`manual_flatten`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_flatten
65426542
[`manual_hash_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_hash_one
65436543
[`manual_ignore_case_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_ignore_case_cmp
6544+
[`manual_ilog2`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_ilog2
65446545
[`manual_inspect`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_inspect
65456546
[`manual_instant_elapsed`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_instant_elapsed
65466547
[`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
@@ -300,6 +300,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
300300
crate::manual_float_methods::MANUAL_IS_INFINITE_INFO,
301301
crate::manual_hash_one::MANUAL_HASH_ONE_INFO,
302302
crate::manual_ignore_case_cmp::MANUAL_IGNORE_CASE_CMP_INFO,
303+
crate::manual_ilog2::MANUAL_ILOG2_INFO,
303304
crate::manual_is_ascii_check::MANUAL_IS_ASCII_CHECK_INFO,
304305
crate::manual_is_power_of_two::MANUAL_IS_POWER_OF_TWO_INFO,
305306
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
@@ -201,6 +201,7 @@ mod manual_clamp;
201201
mod manual_float_methods;
202202
mod manual_hash_one;
203203
mod manual_ignore_case_cmp;
204+
mod manual_ilog2;
204205
mod manual_is_ascii_check;
205206
mod manual_is_power_of_two;
206207
mod manual_let_else;
@@ -848,6 +849,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
848849
Box::new(|_| Box::new(toplevel_ref_arg::ToplevelRefArg)),
849850
Box::new(|_| Box::new(volatile_composites::VolatileComposites)),
850851
Box::new(|_| Box::<replace_box::ReplaceBox>::default()),
852+
Box::new(move |_| Box::new(manual_ilog2::ManualIlog2::new(conf))),
851853
// add late passes here, used by `cargo dev new_lint`
852854
];
853855
store.late_passes.extend(late_lints);

clippy_lints/src/manual_ilog2.rs

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
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::source::snippet_with_applicability;
5+
use clippy_utils::{is_from_proc_macro, sym};
6+
use rustc_ast::LitKind;
7+
use rustc_data_structures::packed::Pu128;
8+
use rustc_errors::Applicability;
9+
use rustc_hir::{BinOpKind, Expr, ExprKind};
10+
use rustc_lint::{LateContext, LateLintPass, LintContext};
11+
use rustc_middle::ty;
12+
use rustc_session::impl_lint_pass;
13+
14+
declare_clippy_lint! {
15+
/// ### What it does
16+
/// Checks for expressions like `31 - x.leading_zeros()` which are manual
17+
/// reimplementations of `x.ilog2()`
18+
///
19+
/// ### Why is this bad?
20+
/// Manual reimplementations of `ilog2` increase code complexity for little benefit.
21+
///
22+
/// ### Example
23+
/// ```no_run
24+
/// let x: u32 = 5;
25+
/// let log = 31 - x.leading_zeros();
26+
/// ```
27+
/// Use instead:
28+
/// ```no_run
29+
/// let x: u32 = 5;
30+
/// let log = x.ilog2();
31+
/// ```
32+
#[clippy::version = "1.93.0"]
33+
pub MANUAL_ILOG2,
34+
pedantic,
35+
"manually reimplementing `ilog2`"
36+
}
37+
38+
pub struct ManualIlog2 {
39+
msrv: Msrv,
40+
}
41+
42+
impl ManualIlog2 {
43+
pub fn new(conf: &Conf) -> Self {
44+
Self { msrv: conf.msrv }
45+
}
46+
}
47+
48+
impl_lint_pass!(ManualIlog2 => [MANUAL_ILOG2]);
49+
50+
impl LateLintPass<'_> for ManualIlog2 {
51+
fn check_expr<'tcx>(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
52+
if expr.span.in_external_macro(cx.sess().source_map()) {
53+
return;
54+
}
55+
56+
match expr.kind {
57+
// `BIT_WIDTH - 1 - n.leading_zeros()`
58+
ExprKind::Binary(op, left, right)
59+
if left.span.eq_ctxt(right.span)
60+
&& op.node == BinOpKind::Sub
61+
&& let ExprKind::Lit(lit) = left.kind
62+
&& let LitKind::Int(Pu128(val), _) = lit.node
63+
&& let ExprKind::MethodCall(leading_zeros, recv, [], _) = right.kind
64+
&& leading_zeros.ident.name == sym::leading_zeros
65+
&& let ty = cx.typeck_results().expr_ty(recv)
66+
&& let Some(bit_width) = match ty.kind() {
67+
ty::Uint(uint_ty) => uint_ty.bit_width(),
68+
ty::Int(_) => {
69+
// On non-positive integers, `ilog2` would panic, which might be a sign that the author does
70+
// in fact want to calculate something different, so stay on the safer side and don't
71+
// suggest anything.
72+
return;
73+
},
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+
// NOTE: we don't check for `n.ilog(2)`, as the compiler will specialize that to `ilog2` by itself
84+
_ => {},
85+
}
86+
}
87+
}
88+
89+
fn emit(cx: &LateContext<'_>, recv: &Expr<'_>, full_expr: &Expr<'_>) {
90+
let mut app = Applicability::MachineApplicable;
91+
let recv = snippet_with_applicability(cx, recv.span, "_", &mut app);
92+
span_lint_and_sugg(
93+
cx,
94+
MANUAL_ILOG2,
95+
full_expr.span,
96+
"manually reimplementing `ilog2`",
97+
"try",
98+
format!("{recv}.ilog2()"),
99+
app,
100+
);
101+
}

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: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ generate! {
207207
join,
208208
kw,
209209
lazy_static,
210+
leading_zeros,
210211
lint_vec,
211212
ln,
212213
lock,

tests/ui/manual_ilog2.fixed

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
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+
b.ilog2(); //~ manual_ilog2
10+
64 - b.leading_zeros(); // No lint because manual ilog2 is `BIT_WIDTH - 1 - x.leading_zeros()`
11+
12+
// don't lint when macros are involved
13+
macro_rules! thirty_one {
14+
() => {
15+
31
16+
};
17+
};
18+
thirty_one!() - a.leading_zeros();
19+
20+
external!(31 - $a.leading_zeros());
21+
with_span!(span; 31 - a.leading_zeros());
22+
}

tests/ui/manual_ilog2.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
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+
63 - b.leading_zeros(); //~ manual_ilog2
10+
64 - b.leading_zeros(); // No lint because manual ilog2 is `BIT_WIDTH - 1 - x.leading_zeros()`
11+
12+
// don't lint when macros are involved
13+
macro_rules! thirty_one {
14+
() => {
15+
31
16+
};
17+
};
18+
thirty_one!() - a.leading_zeros();
19+
20+
external!(31 - $a.leading_zeros());
21+
with_span!(span; 31 - a.leading_zeros());
22+
}

tests/ui/manual_ilog2.stderr

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
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 | 63 - b.leading_zeros();
14+
| ^^^^^^^^^^^^^^^^^^^^^^ help: try: `b.ilog2()`
15+
16+
error: aborting due to 2 previous errors
17+

0 commit comments

Comments
 (0)