Skip to content

Commit d7996da

Browse files
committed
Add manual_is_power_of_two
1 parent 9e9042a commit d7996da

7 files changed

+137
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5626,6 +5626,7 @@ Released 2018-09-13
56265626
[`manual_is_ascii_check`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_ascii_check
56275627
[`manual_is_finite`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_finite
56285628
[`manual_is_infinite`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_infinite
5629+
[`manual_is_power_of_two`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_power_of_two
56295630
[`manual_is_variant_and`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_variant_and
56305631
[`manual_let_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_let_else
56315632
[`manual_main_separator_str`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_main_separator_str

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
306306
crate::manual_float_methods::MANUAL_IS_INFINITE_INFO,
307307
crate::manual_hash_one::MANUAL_HASH_ONE_INFO,
308308
crate::manual_is_ascii_check::MANUAL_IS_ASCII_CHECK_INFO,
309+
crate::manual_is_power_of_two::MANUAL_IS_POWER_OF_TWO_INFO,
309310
crate::manual_let_else::MANUAL_LET_ELSE_INFO,
310311
crate::manual_main_separator_str::MANUAL_MAIN_SEPARATOR_STR_INFO,
311312
crate::manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE_INFO,

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ mod manual_div_ceil;
207207
mod manual_float_methods;
208208
mod manual_hash_one;
209209
mod manual_is_ascii_check;
210+
mod manual_is_power_of_two;
210211
mod manual_let_else;
211212
mod manual_main_separator_str;
212213
mod manual_non_exhaustive;
@@ -938,5 +939,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
938939
store.register_late_pass(|_| Box::new(zombie_processes::ZombieProcesses));
939940
store.register_late_pass(|_| Box::new(pointers_in_nomem_asm_block::PointersInNomemAsmBlock));
940941
store.register_late_pass(move |_| Box::new(manual_div_ceil::ManualDivCeil::new(conf)));
942+
store.register_late_pass(|_| Box::new(manual_is_power_of_two::ManualIsPowerOfTwo));
941943
// add lints here, do not remove this comment, it's used in `new_lint`
942944
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
use clippy_utils::diagnostics::span_lint_and_sugg;
2+
use clippy_utils::source::snippet_with_applicability;
3+
use rustc_ast::LitKind;
4+
use rustc_data_structures::packed::Pu128;
5+
use rustc_errors::Applicability;
6+
use rustc_hir::{BinOpKind, Expr, ExprKind};
7+
use rustc_lint::{LateContext, LateLintPass};
8+
use rustc_middle::ty::Uint;
9+
use rustc_session::declare_lint_pass;
10+
11+
declare_clippy_lint! {
12+
/// ### What it does
13+
/// Checks for expressions like `x.count_ones() == 1` or `x & (x - 1) == 0` which are manual
14+
/// reimplementations of `x.is_power_of_two()``
15+
/// ### Why is this bad?
16+
/// It's simpler and clearer
17+
/// ### Example
18+
/// ```no_run
19+
/// let x: u32 = 1;
20+
/// let result = x.count_ones() == 1;
21+
/// ```
22+
/// Use instead:
23+
/// ```no_run
24+
/// let x: u32 = 1;
25+
/// let result = x.is_power_of_two();
26+
/// ```
27+
#[clippy::version = "1.82.0"]
28+
pub MANUAL_IS_POWER_OF_TWO,
29+
complexity,
30+
"manually reimplementing `is_power_of_two`"
31+
}
32+
33+
declare_lint_pass!(ManualIsPowerOfTwo => [MANUAL_IS_POWER_OF_TWO]);
34+
35+
impl LateLintPass<'_> for ManualIsPowerOfTwo {
36+
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
37+
let mut applicability = Applicability::MachineApplicable;
38+
39+
// x.count_ones() == 1
40+
if let ExprKind::Binary(op, left, right) = expr.kind
41+
&& BinOpKind::Eq == op.node
42+
&& let ExprKind::MethodCall(method_name, reciever, _, _) = left.kind
43+
&& method_name.ident.as_str() == "count_ones"
44+
&& let ExprKind::Lit(lit) = right.kind
45+
&& let LitKind::Int(Pu128(1), _) = lit.node
46+
&& let &Uint(_) = cx.typeck_results().expr_ty(reciever).kind()
47+
{
48+
let snippet = snippet_with_applicability(cx, reciever.span, "..", &mut applicability);
49+
let sugg = format!("{snippet}.is_power_of_two()");
50+
span_lint_and_sugg(
51+
cx,
52+
MANUAL_IS_POWER_OF_TWO,
53+
expr.span,
54+
"manually reimplementing `is_power_of_two`",
55+
"consider using `.is_power_of_two()`",
56+
sugg,
57+
applicability,
58+
);
59+
}
60+
61+
// x & (x - 1) == 0
62+
if let ExprKind::Binary(op, left, right) = expr.kind
63+
&& BinOpKind::Eq == op.node
64+
&& let ExprKind::Binary(op1, left1, right1) = left.kind
65+
&& BinOpKind::BitAnd == op1.node
66+
&& let ExprKind::Binary(op2, left2, right2) = right1.kind
67+
&& BinOpKind::Sub == op2.node
68+
&& left1.span.eq_ctxt(left2.span)
69+
&& let &Uint(_) = cx.typeck_results().expr_ty(left1).kind()
70+
&& let ExprKind::Lit(lit) = right2.kind
71+
&& let LitKind::Int(Pu128(1), _) = lit.node
72+
&& let ExprKind::Lit(lit1) = right.kind
73+
&& let LitKind::Int(Pu128(0), _) = lit1.node
74+
{
75+
let snippet = snippet_with_applicability(cx, left1.span, "..", &mut applicability);
76+
let sugg = format!("{snippet}.is_power_of_two()");
77+
span_lint_and_sugg(
78+
cx,
79+
MANUAL_IS_POWER_OF_TWO,
80+
expr.span,
81+
"manually reimplementing `is_power_of_two`",
82+
"consider using `.is_power_of_two()`",
83+
sugg,
84+
applicability,
85+
);
86+
}
87+
}
88+
}

tests/ui/manual_is_power_of_two.fixed

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#![warn(clippy::manual_is_power_of_two)]
2+
3+
fn main() {
4+
let a = 16_u64;
5+
6+
let _ = a.is_power_of_two();
7+
let _ = a.is_power_of_two();
8+
9+
let b = 4_i64;
10+
11+
// is_power_of_two only works for unsigned integers
12+
let _ = b.count_ones() == 1;
13+
let _ = b & (b - 1) == 0;
14+
}

tests/ui/manual_is_power_of_two.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#![warn(clippy::manual_is_power_of_two)]
2+
3+
fn main() {
4+
let a = 16_u64;
5+
6+
let _ = a.count_ones() == 1;
7+
let _ = a & (a - 1) == 0;
8+
9+
let b = 4_i64;
10+
11+
// is_power_of_two only works for unsigned integers
12+
let _ = b.count_ones() == 1;
13+
let _ = b & (b - 1) == 0;
14+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
error: manually reimplementing `is_power_of_two`
2+
--> tests/ui/manual_is_power_of_two.rs:6:13
3+
|
4+
LL | let _ = a.count_ones() == 1;
5+
| ^^^^^^^^^^^^^^^^^^^ help: consider using `.is_power_of_two()`: `a.is_power_of_two()`
6+
|
7+
= note: `-D clippy::manual-is-power-of-two` implied by `-D warnings`
8+
= help: to override `-D warnings` add `#[allow(clippy::manual_is_power_of_two)]`
9+
10+
error: manually reimplementing `is_power_of_two`
11+
--> tests/ui/manual_is_power_of_two.rs:7:13
12+
|
13+
LL | let _ = a & (a - 1) == 0;
14+
| ^^^^^^^^^^^^^^^^ help: consider using `.is_power_of_two()`: `a.is_power_of_two()`
15+
16+
error: aborting due to 2 previous errors
17+

0 commit comments

Comments
 (0)