Skip to content

Commit f994797

Browse files
committed
Add support for different orders of expression
1 parent 6ecb48f commit f994797

File tree

4 files changed

+141
-51
lines changed

4 files changed

+141
-51
lines changed
Lines changed: 104 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use clippy_utils::diagnostics::span_lint_and_sugg;
22
use clippy_utils::source::snippet_with_applicability;
3+
use clippy_utils::SpanlessEq;
34
use rustc_ast::LitKind;
45
use rustc_data_structures::packed::Pu128;
56
use rustc_errors::Applicability;
@@ -10,19 +11,19 @@ use rustc_session::declare_lint_pass;
1011

1112
declare_clippy_lint! {
1213
/// ### What it does
13-
/// Checks for expressions like `x.count_ones() == 1` or `x & (x - 1) == 0`, which are manual
14+
/// Checks for expressions like `x.count_ones() == 1` or `x & (x - 1) == 0`, with x and unsigned integer, which are manual
1415
/// reimplementations of `x.is_power_of_two()`.
1516
/// ### Why is this bad?
1617
/// Manual reimplementations of `is_power_of_two` increase code complexity for little benefit.
1718
/// ### Example
1819
/// ```no_run
19-
/// let x: u32 = 1;
20-
/// let result = x.count_ones() == 1;
20+
/// let a: u32 = 4;
21+
/// let result = a.count_ones() == 1;
2122
/// ```
2223
/// Use instead:
2324
/// ```no_run
24-
/// let x: u32 = 1;
25-
/// let result = x.is_power_of_two();
25+
/// let a: u32 = 4;
26+
/// let result = a.is_power_of_two();
2627
/// ```
2728
#[clippy::version = "1.82.0"]
2829
pub MANUAL_IS_POWER_OF_TWO,
@@ -36,53 +37,106 @@ impl LateLintPass<'_> for ManualIsPowerOfTwo {
3637
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
3738
let mut applicability = Applicability::MachineApplicable;
3839

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()
40+
if let ExprKind::Binary(bin_op, left, right) = expr.kind
41+
&& bin_op.node == BinOpKind::Eq
4742
{
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-
}
43+
// a.count_ones() == 1
44+
if let ExprKind::MethodCall(method_name, reciever, _, _) = left.kind
45+
&& method_name.ident.as_str() == "count_ones"
46+
&& let &Uint(_) = cx.typeck_results().expr_ty(reciever).kind()
47+
&& check_lit(right, 1)
48+
{
49+
build_sugg(cx, expr, reciever, &mut applicability);
50+
}
6051

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-
);
52+
// 1 == a.count_ones()
53+
if let ExprKind::MethodCall(method_name, reciever, _, _) = right.kind
54+
&& method_name.ident.as_str() == "count_ones"
55+
&& let &Uint(_) = cx.typeck_results().expr_ty(reciever).kind()
56+
&& check_lit(left, 1)
57+
{
58+
build_sugg(cx, expr, reciever, &mut applicability);
59+
}
60+
61+
// a & (a - 1) == 0
62+
if let ExprKind::Binary(op1, left1, right1) = left.kind
63+
&& op1.node == BinOpKind::BitAnd
64+
&& let ExprKind::Binary(op2, left2, right2) = right1.kind
65+
&& op2.node == BinOpKind::Sub
66+
&& check_eq_expr(cx, left1, left2)
67+
&& let &Uint(_) = cx.typeck_results().expr_ty(left1).kind()
68+
&& check_lit(right2, 1)
69+
&& check_lit(right, 0)
70+
{
71+
build_sugg(cx, expr, left1, &mut applicability);
72+
}
73+
74+
// (a - 1) & a == 0;
75+
if let ExprKind::Binary(op1, left1, right1) = left.kind
76+
&& op1.node == BinOpKind::BitAnd
77+
&& let ExprKind::Binary(op2, left2, right2) = left1.kind
78+
&& op2.node == BinOpKind::Sub
79+
&& check_eq_expr(cx, right1, left2)
80+
&& let &Uint(_) = cx.typeck_results().expr_ty(right1).kind()
81+
&& check_lit(right2, 1)
82+
&& check_lit(right, 0)
83+
{
84+
build_sugg(cx, expr, right1, &mut applicability);
85+
}
86+
87+
// 0 == a & (a - 1);
88+
if let ExprKind::Binary(op1, left1, right1) = right.kind
89+
&& op1.node == BinOpKind::BitAnd
90+
&& let ExprKind::Binary(op2, left2, right2) = right1.kind
91+
&& op2.node == BinOpKind::Sub
92+
&& check_eq_expr(cx, left1, left2)
93+
&& let &Uint(_) = cx.typeck_results().expr_ty(left1).kind()
94+
&& check_lit(right2, 1)
95+
&& check_lit(left, 0)
96+
{
97+
build_sugg(cx, expr, left1, &mut applicability);
98+
}
99+
100+
// 0 == (a - 1) & a
101+
if let ExprKind::Binary(op1, left1, right1) = right.kind
102+
&& op1.node == BinOpKind::BitAnd
103+
&& let ExprKind::Binary(op2, left2, right2) = left1.kind
104+
&& op2.node == BinOpKind::Sub
105+
&& check_eq_expr(cx, right1, left2)
106+
&& let &Uint(_) = cx.typeck_results().expr_ty(right1).kind()
107+
&& check_lit(right2, 1)
108+
&& check_lit(left, 0)
109+
{
110+
build_sugg(cx, expr, right1, &mut applicability);
111+
}
86112
}
87113
}
88114
}
115+
116+
fn build_sugg(cx: &LateContext<'_>, expr: &Expr<'_>, reciever: &Expr<'_>, applicability: &mut Applicability) {
117+
let snippet = snippet_with_applicability(cx, reciever.span, "..", applicability);
118+
119+
span_lint_and_sugg(
120+
cx,
121+
MANUAL_IS_POWER_OF_TWO,
122+
expr.span,
123+
"manually reimplementing `is_power_of_two`",
124+
"consider using `.is_power_of_two()`",
125+
format!("{snippet}.is_power_of_two()"),
126+
*applicability,
127+
);
128+
}
129+
130+
fn check_lit(expr: &Expr<'_>, expected_num: u128) -> bool {
131+
if let ExprKind::Lit(lit) = expr.kind
132+
&& let LitKind::Int(Pu128(num), _) = lit.node
133+
&& num == expected_num
134+
{
135+
return true;
136+
}
137+
false
138+
}
139+
140+
fn check_eq_expr(cx: &LateContext<'_>, lhs: &Expr<'_>, rhs: &Expr<'_>) -> bool {
141+
SpanlessEq::new(cx).eq_expr(lhs, rhs)
142+
}

tests/ui/manual_is_power_of_two.fixed

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ fn main() {
66
let _ = a.is_power_of_two();
77
let _ = a.is_power_of_two();
88

9+
// Test different orders of expression
10+
let _ = a.is_power_of_two();
11+
let _ = a.is_power_of_two();
12+
let _ = a.is_power_of_two();
13+
let _ = a.is_power_of_two();
14+
915
let b = 4_i64;
1016

1117
// is_power_of_two only works for unsigned integers

tests/ui/manual_is_power_of_two.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ fn main() {
66
let _ = a.count_ones() == 1;
77
let _ = a & (a - 1) == 0;
88

9+
// Test different orders of expression
10+
let _ = 1 == a.count_ones();
11+
let _ = (a - 1) & a == 0;
12+
let _ = 0 == a & (a - 1);
13+
let _ = 0 == (a - 1) & a;
14+
915
let b = 4_i64;
1016

1117
// is_power_of_two only works for unsigned integers

tests/ui/manual_is_power_of_two.stderr

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,29 @@ error: manually reimplementing `is_power_of_two`
1313
LL | let _ = a & (a - 1) == 0;
1414
| ^^^^^^^^^^^^^^^^ help: consider using `.is_power_of_two()`: `a.is_power_of_two()`
1515

16-
error: aborting due to 2 previous errors
16+
error: manually reimplementing `is_power_of_two`
17+
--> tests/ui/manual_is_power_of_two.rs:10:13
18+
|
19+
LL | let _ = 1 == a.count_ones();
20+
| ^^^^^^^^^^^^^^^^^^^ help: consider using `.is_power_of_two()`: `a.is_power_of_two()`
21+
22+
error: manually reimplementing `is_power_of_two`
23+
--> tests/ui/manual_is_power_of_two.rs:11:13
24+
|
25+
LL | let _ = (a - 1) & a == 0;
26+
| ^^^^^^^^^^^^^^^^ help: consider using `.is_power_of_two()`: `a.is_power_of_two()`
27+
28+
error: manually reimplementing `is_power_of_two`
29+
--> tests/ui/manual_is_power_of_two.rs:12:13
30+
|
31+
LL | let _ = 0 == a & (a - 1);
32+
| ^^^^^^^^^^^^^^^^ help: consider using `.is_power_of_two()`: `a.is_power_of_two()`
33+
34+
error: manually reimplementing `is_power_of_two`
35+
--> tests/ui/manual_is_power_of_two.rs:13:13
36+
|
37+
LL | let _ = 0 == (a - 1) & a;
38+
| ^^^^^^^^^^^^^^^^ help: consider using `.is_power_of_two()`: `a.is_power_of_two()`
39+
40+
error: aborting due to 6 previous errors
1741

0 commit comments

Comments
 (0)