Skip to content

Commit 4284524

Browse files
committed
feat(manual_ilog2): new lint
1 parent a2c13c1 commit 4284524

File tree

9 files changed

+204
-0
lines changed

9 files changed

+204
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6540,6 +6540,7 @@ Released 2018-09-13
65406540
[`manual_flatten`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_flatten
65416541
[`manual_hash_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_hash_one
65426542
[`manual_ignore_case_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_ignore_case_cmp
6543+
[`manual_ilog2`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_ilog2
65436544
[`manual_inspect`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_inspect
65446545
[`manual_instant_elapsed`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_instant_elapsed
65456546
[`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
@@ -299,6 +299,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
299299
crate::manual_float_methods::MANUAL_IS_INFINITE_INFO,
300300
crate::manual_hash_one::MANUAL_HASH_ONE_INFO,
301301
crate::manual_ignore_case_cmp::MANUAL_IGNORE_CASE_CMP_INFO,
302+
crate::manual_ilog2::MANUAL_ILOG2_INFO,
302303
crate::manual_is_ascii_check::MANUAL_IS_ASCII_CHECK_INFO,
303304
crate::manual_is_power_of_two::MANUAL_IS_POWER_OF_TWO_INFO,
304305
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;
@@ -819,5 +820,6 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
819820
store.register_late_pass(|_| Box::new(toplevel_ref_arg::ToplevelRefArg));
820821
store.register_late_pass(|_| Box::new(volatile_composites::VolatileComposites));
821822
store.register_late_pass(|_| Box::new(replace_box::ReplaceBox));
823+
store.register_late_pass(move |_| Box::new(manual_ilog2::ManualIlog2::new(conf)));
822824
// add lints here, do not remove this comment, it's used in `new_lint`
823825
}

clippy_lints/src/manual_ilog2.rs

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
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()` or `x.ilog(2)` 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+
/// let log = x.ilog(2);
27+
/// ```
28+
/// Use instead:
29+
/// ```no_run
30+
/// let x: u32 = 5;
31+
/// let log = x.ilog2();
32+
/// let log = x.ilog2();
33+
/// ```
34+
#[clippy::version = "1.92.0"]
35+
pub MANUAL_ILOG2,
36+
complexity,
37+
"manually reimplementing `ilog2`"
38+
}
39+
40+
pub struct ManualIlog2 {
41+
msrv: Msrv,
42+
}
43+
44+
impl ManualIlog2 {
45+
pub fn new(conf: &Conf) -> Self {
46+
Self { msrv: conf.msrv }
47+
}
48+
}
49+
50+
impl_lint_pass!(ManualIlog2 => [MANUAL_ILOG2]);
51+
52+
impl LateLintPass<'_> for ManualIlog2 {
53+
fn check_expr<'tcx>(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
54+
if expr.span.in_external_macro(cx.sess().source_map()) {
55+
return;
56+
}
57+
58+
match expr.kind {
59+
// `BIT_WIDTH - 1 - n.leading_zeros()`
60+
ExprKind::Binary(op, left, right)
61+
if left.span.eq_ctxt(right.span)
62+
&& op.node == BinOpKind::Sub
63+
&& let ExprKind::Lit(lit) = left.kind
64+
&& let LitKind::Int(Pu128(val), _) = lit.node
65+
&& let ExprKind::MethodCall(leading_zeros, recv, [], _) = right.kind
66+
&& leading_zeros.ident.name == sym::leading_zeros
67+
&& let ty = cx.typeck_results().expr_ty(recv)
68+
&& let Some(bit_width) = match ty.kind() {
69+
ty::Int(int_ty) => int_ty.bit_width(),
70+
ty::Uint(uint_ty) => uint_ty.bit_width(),
71+
_ => return,
72+
}
73+
&& val == u128::from(bit_width) - 1
74+
&& self.msrv.meets(cx, msrvs::ILOG2)
75+
&& !is_from_proc_macro(cx, expr) =>
76+
{
77+
emit(cx, recv, expr);
78+
},
79+
80+
// `n.ilog(2)`
81+
ExprKind::MethodCall(ilog, recv, [two], _)
82+
if expr.span.eq_ctxt(two.span)
83+
&& ilog.ident.name == sym::ilog
84+
&& let ExprKind::Lit(lit) = two.kind
85+
&& let LitKind::Int(Pu128(2), _) = lit.node
86+
&& cx.typeck_results().expr_ty_adjusted(recv).is_integral()
87+
&& self.msrv.meets(cx, msrvs::ILOG2)
88+
&& !is_from_proc_macro(cx, expr) =>
89+
{
90+
emit(cx, recv, expr);
91+
},
92+
93+
_ => {},
94+
}
95+
}
96+
}
97+
98+
fn emit(cx: &LateContext<'_>, recv: &Expr<'_>, full_expr: &Expr<'_>) {
99+
let mut app = Applicability::MachineApplicable;
100+
let recv = snippet_with_applicability(cx, recv.span, "_", &mut app);
101+
span_lint_and_sugg(
102+
cx,
103+
MANUAL_ILOG2,
104+
full_expr.span,
105+
"manually reimplementing `ilog2`",
106+
"try",
107+
format!("{recv}.ilog2()"),
108+
app,
109+
);
110+
}

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
@@ -180,6 +180,7 @@ generate! {
180180
has_significant_drop,
181181
hidden_glob_reexports,
182182
hygiene,
183+
ilog,
183184
insert,
184185
insert_str,
185186
inspect,
@@ -207,6 +208,7 @@ generate! {
207208
join,
208209
kw,
209210
lazy_static,
211+
leading_zeros,
210212
lint_vec,
211213
ln,
212214
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)