Skip to content

Commit 438fdad

Browse files
committed
Add [manual_ilog2] lint
1 parent a81f1c8 commit 438fdad

File tree

8 files changed

+157
-0
lines changed

8 files changed

+157
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5566,6 +5566,7 @@ Released 2018-09-13
55665566
[`manual_find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_find_map
55675567
[`manual_flatten`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_flatten
55685568
[`manual_hash_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_hash_one
5569+
[`manual_ilog2`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_ilog2
55695570
[`manual_inspect`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_inspect
55705571
[`manual_instant_elapsed`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_instant_elapsed
55715572
[`manual_is_ascii_check`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_ascii_check

clippy_config/src/msrvs.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ msrv_aliases! {
2424
1,71,0 { TUPLE_ARRAY_CONVERSIONS, BUILD_HASHER_HASH_ONE }
2525
1,70,0 { OPTION_RESULT_IS_VARIANT_AND, BINARY_HEAP_RETAIN }
2626
1,68,0 { PATH_MAIN_SEPARATOR_STR }
27+
1,67,0 { MANUAL_ILOG2 }
2728
1,65,0 { LET_ELSE, POINTER_CAST_CONSTNESS }
2829
1,63,0 { CLONE_INTO }
2930
1,62,0 { BOOL_THEN_SOME, DEFAULT_ENUM_ATTRIBUTE, CONST_EXTERN_FN }

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
304304
crate::manual_float_methods::MANUAL_IS_FINITE_INFO,
305305
crate::manual_float_methods::MANUAL_IS_INFINITE_INFO,
306306
crate::manual_hash_one::MANUAL_HASH_ONE_INFO,
307+
crate::manual_ilog2::MANUAL_ILOG2_INFO,
307308
crate::manual_is_ascii_check::MANUAL_IS_ASCII_CHECK_INFO,
308309
crate::manual_let_else::MANUAL_LET_ELSE_INFO,
309310
crate::manual_main_separator_str::MANUAL_MAIN_SEPARATOR_STR_INFO,

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ mod manual_bits;
205205
mod manual_clamp;
206206
mod manual_float_methods;
207207
mod manual_hash_one;
208+
mod manual_ilog2;
208209
mod manual_is_ascii_check;
209210
mod manual_let_else;
210211
mod manual_main_separator_str;
@@ -935,5 +936,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
935936
store.register_early_pass(|| Box::new(byte_char_slices::ByteCharSlice));
936937
store.register_early_pass(|| Box::new(cfg_not_test::CfgNotTest));
937938
store.register_late_pass(|_| Box::new(zombie_processes::ZombieProcesses));
939+
store.register_late_pass(move |_| Box::new(manual_ilog2::ManualIlog2::new(conf)));
938940
// add lints here, do not remove this comment, it's used in `new_lint`
939941
}

clippy_lints/src/manual_ilog2.rs

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
use clippy_config::msrvs::{self, Msrv};
2+
use clippy_config::Conf;
3+
use clippy_utils::diagnostics::span_lint_and_sugg;
4+
use clippy_utils::source::snippet_with_applicability;
5+
use rustc_ast::LitKind;
6+
use rustc_data_structures::packed::Pu128;
7+
use rustc_errors::Applicability;
8+
use rustc_hir::{BinOpKind, Expr, ExprKind};
9+
use rustc_lint::{LateContext, LateLintPass};
10+
use rustc_middle::ty;
11+
use rustc_session::impl_lint_pass;
12+
13+
declare_clippy_lint! {
14+
/// ### What it does
15+
/// Checks for expressions like `31 - x.leading_zeros()` or `x.ilog(2)` which are manual
16+
/// reimplementations of `x.ilog2()`
17+
/// ### Why is this bad?
18+
/// It is easier to read and understand
19+
/// ### Example
20+
/// ```no_run
21+
/// let x: u32 = 5;
22+
/// let log = 31 - x.leading_zeros()
23+
/// ```
24+
/// Use instead:
25+
/// ```no_run
26+
/// let x: u32 = 5;
27+
/// let log = x.ilog2()
28+
/// ```
29+
#[clippy::version = "1.82.0"]
30+
pub MANUAL_ILOG2,
31+
complexity,
32+
"manually reimplementing `ilog2`"
33+
}
34+
35+
pub struct ManualIlog2 {
36+
msrv: Msrv,
37+
}
38+
39+
impl ManualIlog2 {
40+
#[must_use]
41+
pub fn new(conf: &Conf) -> Self {
42+
Self {
43+
msrv: conf.msrv.clone(),
44+
}
45+
}
46+
}
47+
48+
impl_lint_pass!(ManualIlog2 => [MANUAL_ILOG2]);
49+
50+
impl LateLintPass<'_> for ManualIlog2 {
51+
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
52+
if !self.msrv.meets(msrvs::MANUAL_ILOG2) {
53+
return;
54+
}
55+
let mut applicability = Applicability::MachineApplicable;
56+
57+
if let ExprKind::Binary(op, left, right) = expr.kind
58+
&& BinOpKind::Sub == op.node
59+
&& let ExprKind::Lit(lit) = left.kind
60+
&& let LitKind::Int(Pu128(val), _) = lit.node
61+
&& let ExprKind::MethodCall(method_name, reciever, _, _) = right.kind
62+
&& method_name.ident.as_str() == "leading_zeros"
63+
{
64+
let type_ = cx.typeck_results().expr_ty(reciever);
65+
let Some(bit_width) = (match type_.kind() {
66+
ty::Int(itype) => itype.bit_width(),
67+
ty::Uint(itype) => itype.bit_width(),
68+
_ => return,
69+
}) else {
70+
return;
71+
};
72+
if val == u128::from(bit_width) - 1 {
73+
let sugg = snippet_with_applicability(cx, reciever.span, "..", &mut applicability);
74+
span_lint_and_sugg(
75+
cx,
76+
MANUAL_ILOG2,
77+
expr.span,
78+
"manually reimplementing `ilog2`",
79+
"consider using .ilog2()",
80+
format!("{sugg}.ilog2()"),
81+
applicability,
82+
);
83+
}
84+
}
85+
86+
if let ExprKind::MethodCall(method_name, reciever, args, _) = expr.kind
87+
&& method_name.ident.as_str() == "ilog"
88+
&& args.len() == 1
89+
&& let ExprKind::Lit(lit) = args[0].kind
90+
&& let LitKind::Int(Pu128(2), _) = lit.node
91+
&& cx.typeck_results().expr_ty(reciever).is_integral()
92+
{
93+
let sugg = snippet_with_applicability(cx, reciever.span, "..", &mut applicability);
94+
span_lint_and_sugg(
95+
cx,
96+
MANUAL_ILOG2,
97+
expr.span,
98+
"manually reimplementing `ilog2`",
99+
"consider using .ilog2()",
100+
format!("{sugg}.ilog2()"),
101+
applicability,
102+
);
103+
}
104+
}
105+
}

tests/ui/manual_ilog2.fixed

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#![warn(clippy::manual_ilog2)]
2+
3+
fn main() {
4+
let a: u32 = 5;
5+
let _ = a.ilog2();
6+
let _ = a.ilog2();
7+
8+
let b: u64 = 543534;
9+
let _ = b.ilog2();
10+
11+
let _ = 64 - b.leading_zeros(); // No lint
12+
}

tests/ui/manual_ilog2.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#![warn(clippy::manual_ilog2)]
2+
3+
fn main() {
4+
let a: u32 = 5;
5+
let _ = 31 - a.leading_zeros();
6+
let _ = a.ilog(2);
7+
8+
let b: u64 = 543534;
9+
let _ = 63 - b.leading_zeros();
10+
11+
let _ = 64 - b.leading_zeros(); // No lint
12+
}

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:5:13
3+
|
4+
LL | let _ = 31 - a.leading_zeros();
5+
| ^^^^^^^^^^^^^^^^^^^^^^ help: consider using .ilog2(): `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:6:13
12+
|
13+
LL | let _ = a.ilog(2);
14+
| ^^^^^^^^^ help: consider using .ilog2(): `a.ilog2()`
15+
16+
error: manually reimplementing `ilog2`
17+
--> tests/ui/manual_ilog2.rs:9:13
18+
|
19+
LL | let _ = 63 - b.leading_zeros();
20+
| ^^^^^^^^^^^^^^^^^^^^^^ help: consider using .ilog2(): `b.ilog2()`
21+
22+
error: aborting due to 3 previous errors
23+

0 commit comments

Comments
 (0)