Skip to content

Commit 02764f6

Browse files
authored
Various fixes for manual_is_power_of_two (#14463)
Fix #14461: - insert parentheses as required in suggestion - check MSRV before suggesting fix in `const` context - do not lint macro expansion result Commits have been logically separated to facilitate review, and start with a refactoring (and simplification) of the existing code. changelog: [`manual_is_power_of_two`]: insert parentheses as required in suggestion, check MSRV before suggesting fix in `const` context, do not lint macro expansion results
2 parents 69ade77 + c43c87d commit 02764f6

File tree

8 files changed

+193
-104
lines changed

8 files changed

+193
-104
lines changed

book/src/lint_configuration.md

+1
Original file line numberDiff line numberDiff line change
@@ -805,6 +805,7 @@ The minimum rust version that the project supports. Defaults to the `rust-versio
805805
* [`manual_flatten`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_flatten)
806806
* [`manual_hash_one`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_hash_one)
807807
* [`manual_is_ascii_check`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_ascii_check)
808+
* [`manual_is_power_of_two`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_power_of_two)
808809
* [`manual_let_else`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_let_else)
809810
* [`manual_midpoint`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_midpoint)
810811
* [`manual_non_exhaustive`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_non_exhaustive)

clippy_config/src/conf.rs

+1
Original file line numberDiff line numberDiff line change
@@ -722,6 +722,7 @@ define_Conf! {
722722
manual_flatten,
723723
manual_hash_one,
724724
manual_is_ascii_check,
725+
manual_is_power_of_two,
725726
manual_let_else,
726727
manual_midpoint,
727728
manual_non_exhaustive,

clippy_lints/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -971,7 +971,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
971971
store.register_late_pass(|_| Box::new(zombie_processes::ZombieProcesses));
972972
store.register_late_pass(|_| Box::new(pointers_in_nomem_asm_block::PointersInNomemAsmBlock));
973973
store.register_late_pass(move |_| Box::new(manual_div_ceil::ManualDivCeil::new(conf)));
974-
store.register_late_pass(|_| Box::new(manual_is_power_of_two::ManualIsPowerOfTwo));
974+
store.register_late_pass(move |_| Box::new(manual_is_power_of_two::ManualIsPowerOfTwo::new(conf)));
975975
store.register_late_pass(|_| Box::new(non_zero_suggestions::NonZeroSuggestions));
976976
store.register_late_pass(|_| Box::new(literal_string_with_formatting_args::LiteralStringWithFormattingArg));
977977
store.register_late_pass(move |_| Box::new(unused_trait_names::UnusedTraitNames::new(conf)));
+96-96
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1-
use clippy_utils::SpanlessEq;
1+
use clippy_config::Conf;
22
use clippy_utils::diagnostics::span_lint_and_sugg;
3-
use clippy_utils::source::snippet_with_applicability;
4-
use rustc_ast::LitKind;
5-
use rustc_data_structures::packed::Pu128;
3+
use clippy_utils::msrvs::{self, Msrv};
4+
use clippy_utils::sugg::Sugg;
5+
use clippy_utils::ty::ty_from_hir_ty;
6+
use clippy_utils::{SpanlessEq, is_in_const_context, is_integer_literal};
67
use rustc_errors::Applicability;
7-
use rustc_hir::{BinOpKind, Expr, ExprKind};
8+
use rustc_hir::{BinOpKind, Expr, ExprKind, QPath};
89
use rustc_lint::{LateContext, LateLintPass};
9-
use rustc_middle::ty::Uint;
10-
use rustc_session::declare_lint_pass;
10+
use rustc_middle::ty;
11+
use rustc_session::impl_lint_pass;
1112

1213
declare_clippy_lint! {
1314
/// ### What it does
@@ -33,112 +34,111 @@ declare_clippy_lint! {
3334
"manually reimplementing `is_power_of_two`"
3435
}
3536

36-
declare_lint_pass!(ManualIsPowerOfTwo => [MANUAL_IS_POWER_OF_TWO]);
37+
pub struct ManualIsPowerOfTwo {
38+
msrv: Msrv,
39+
}
3740

38-
impl LateLintPass<'_> for ManualIsPowerOfTwo {
39-
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
40-
let mut applicability = Applicability::MachineApplicable;
41+
impl_lint_pass!(ManualIsPowerOfTwo => [MANUAL_IS_POWER_OF_TWO]);
4142

42-
if let ExprKind::Binary(bin_op, left, right) = expr.kind
43-
&& bin_op.node == BinOpKind::Eq
44-
{
45-
// a.count_ones() == 1
46-
if let ExprKind::MethodCall(method_name, receiver, [], _) = left.kind
47-
&& method_name.ident.as_str() == "count_ones"
48-
&& let &Uint(_) = cx.typeck_results().expr_ty(receiver).kind()
49-
&& check_lit(right, 1)
50-
{
51-
build_sugg(cx, expr, receiver, &mut applicability);
52-
}
43+
impl ManualIsPowerOfTwo {
44+
pub fn new(conf: &'static Conf) -> Self {
45+
Self { msrv: conf.msrv }
46+
}
5347

54-
// 1 == a.count_ones()
55-
if let ExprKind::MethodCall(method_name, receiver, [], _) = right.kind
56-
&& method_name.ident.as_str() == "count_ones"
57-
&& let &Uint(_) = cx.typeck_results().expr_ty(receiver).kind()
58-
&& check_lit(left, 1)
59-
{
60-
build_sugg(cx, expr, receiver, &mut applicability);
61-
}
48+
fn build_sugg(&self, cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>) {
49+
if is_in_const_context(cx) && !self.msrv.meets(cx, msrvs::CONST_IS_POWER_OF_TWO) {
50+
return;
51+
}
6252

63-
// a & (a - 1) == 0
64-
if let ExprKind::Binary(op1, left1, right1) = left.kind
65-
&& op1.node == BinOpKind::BitAnd
66-
&& let ExprKind::Binary(op2, left2, right2) = right1.kind
67-
&& op2.node == BinOpKind::Sub
68-
&& check_eq_expr(cx, left1, left2)
69-
&& let &Uint(_) = cx.typeck_results().expr_ty(left1).kind()
70-
&& check_lit(right2, 1)
71-
&& check_lit(right, 0)
72-
{
73-
build_sugg(cx, expr, left1, &mut applicability);
74-
}
53+
let mut applicability = Applicability::MachineApplicable;
54+
let snippet = Sugg::hir_with_applicability(cx, receiver, "_", &mut applicability);
7555

76-
// (a - 1) & a == 0;
77-
if let ExprKind::Binary(op1, left1, right1) = left.kind
78-
&& op1.node == BinOpKind::BitAnd
79-
&& let ExprKind::Binary(op2, left2, right2) = left1.kind
80-
&& op2.node == BinOpKind::Sub
81-
&& check_eq_expr(cx, right1, left2)
82-
&& let &Uint(_) = cx.typeck_results().expr_ty(right1).kind()
83-
&& check_lit(right2, 1)
84-
&& check_lit(right, 0)
85-
{
86-
build_sugg(cx, expr, right1, &mut applicability);
87-
}
56+
span_lint_and_sugg(
57+
cx,
58+
MANUAL_IS_POWER_OF_TWO,
59+
expr.span,
60+
"manually reimplementing `is_power_of_two`",
61+
"consider using `.is_power_of_two()`",
62+
format!("{}.is_power_of_two()", snippet.maybe_paren()),
63+
applicability,
64+
);
65+
}
66+
}
8867

89-
// 0 == a & (a - 1);
90-
if let ExprKind::Binary(op1, left1, right1) = right.kind
91-
&& op1.node == BinOpKind::BitAnd
92-
&& let ExprKind::Binary(op2, left2, right2) = right1.kind
93-
&& op2.node == BinOpKind::Sub
94-
&& check_eq_expr(cx, left1, left2)
95-
&& let &Uint(_) = cx.typeck_results().expr_ty(left1).kind()
96-
&& check_lit(right2, 1)
97-
&& check_lit(left, 0)
68+
impl<'tcx> LateLintPass<'tcx> for ManualIsPowerOfTwo {
69+
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
70+
if !expr.span.from_expansion()
71+
&& let Some((lhs, rhs)) = unexpanded_binop_operands(expr, BinOpKind::Eq)
72+
{
73+
if let Some(a) = count_ones_receiver(cx, lhs)
74+
&& is_integer_literal(rhs, 1)
9875
{
99-
build_sugg(cx, expr, left1, &mut applicability);
100-
}
101-
102-
// 0 == (a - 1) & a
103-
if let ExprKind::Binary(op1, left1, right1) = right.kind
104-
&& op1.node == BinOpKind::BitAnd
105-
&& let ExprKind::Binary(op2, left2, right2) = left1.kind
106-
&& op2.node == BinOpKind::Sub
107-
&& check_eq_expr(cx, right1, left2)
108-
&& let &Uint(_) = cx.typeck_results().expr_ty(right1).kind()
109-
&& check_lit(right2, 1)
110-
&& check_lit(left, 0)
76+
self.build_sugg(cx, expr, a);
77+
} else if let Some(a) = count_ones_receiver(cx, rhs)
78+
&& is_integer_literal(lhs, 1)
79+
{
80+
self.build_sugg(cx, expr, a);
81+
} else if is_integer_literal(rhs, 0)
82+
&& let Some(a) = is_and_minus_one(cx, lhs)
83+
{
84+
self.build_sugg(cx, expr, a);
85+
} else if is_integer_literal(lhs, 0)
86+
&& let Some(a) = is_and_minus_one(cx, rhs)
11187
{
112-
build_sugg(cx, expr, right1, &mut applicability);
88+
self.build_sugg(cx, expr, a);
11389
}
11490
}
11591
}
11692
}
11793

118-
fn build_sugg(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, applicability: &mut Applicability) {
119-
let snippet = snippet_with_applicability(cx, receiver.span, "..", applicability);
120-
121-
span_lint_and_sugg(
122-
cx,
123-
MANUAL_IS_POWER_OF_TWO,
124-
expr.span,
125-
"manually reimplementing `is_power_of_two`",
126-
"consider using `.is_power_of_two()`",
127-
format!("{snippet}.is_power_of_two()"),
128-
*applicability,
129-
);
94+
/// Return the unsigned integer receiver of `.count_ones()` or the argument of
95+
/// `<int-type>::count_ones(…)`.
96+
fn count_ones_receiver<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
97+
let (method, ty, receiver) = if let ExprKind::MethodCall(method_name, receiver, [], _) = expr.kind {
98+
(method_name, cx.typeck_results().expr_ty_adjusted(receiver), receiver)
99+
} else if let ExprKind::Call(func, [arg]) = expr.kind
100+
&& let ExprKind::Path(QPath::TypeRelative(ty, func_name)) = func.kind
101+
{
102+
(func_name, ty_from_hir_ty(cx, ty), arg)
103+
} else {
104+
return None;
105+
};
106+
(method.ident.as_str() == "count_ones" && matches!(ty.kind(), ty::Uint(_))).then_some(receiver)
130107
}
131108

132-
fn check_lit(expr: &Expr<'_>, expected_num: u128) -> bool {
133-
if let ExprKind::Lit(lit) = expr.kind
134-
&& let LitKind::Int(Pu128(num), _) = lit.node
135-
&& num == expected_num
109+
/// Return `greater` if `smaller == greater - 1`
110+
fn is_one_less<'tcx>(
111+
cx: &LateContext<'tcx>,
112+
greater: &'tcx Expr<'tcx>,
113+
smaller: &Expr<'tcx>,
114+
) -> Option<&'tcx Expr<'tcx>> {
115+
if let Some((lhs, rhs)) = unexpanded_binop_operands(smaller, BinOpKind::Sub)
116+
&& SpanlessEq::new(cx).eq_expr(greater, lhs)
117+
&& is_integer_literal(rhs, 1)
118+
&& matches!(cx.typeck_results().expr_ty_adjusted(greater).kind(), ty::Uint(_))
136119
{
137-
return true;
120+
Some(greater)
121+
} else {
122+
None
138123
}
139-
false
140124
}
141125

142-
fn check_eq_expr(cx: &LateContext<'_>, lhs: &Expr<'_>, rhs: &Expr<'_>) -> bool {
143-
SpanlessEq::new(cx).eq_expr(lhs, rhs)
126+
/// Return `v` if `expr` is `v & (v - 1)` or `(v - 1) & v`
127+
fn is_and_minus_one<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
128+
let (lhs, rhs) = unexpanded_binop_operands(expr, BinOpKind::BitAnd)?;
129+
is_one_less(cx, lhs, rhs).or_else(|| is_one_less(cx, rhs, lhs))
130+
}
131+
132+
/// Return the operands of the `expr` binary operation if the operator is `op` and none of the
133+
/// operands come from expansion.
134+
fn unexpanded_binop_operands<'hir>(expr: &Expr<'hir>, op: BinOpKind) -> Option<(&'hir Expr<'hir>, &'hir Expr<'hir>)> {
135+
if let ExprKind::Binary(binop, lhs, rhs) = expr.kind
136+
&& binop.node == op
137+
&& !lhs.span.from_expansion()
138+
&& !rhs.span.from_expansion()
139+
{
140+
Some((lhs, rhs))
141+
} else {
142+
None
143+
}
144144
}

clippy_utils/src/msrvs.rs

+1
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ msrv_aliases! {
6464
1,35,0 { OPTION_COPIED, RANGE_CONTAINS }
6565
1,34,0 { TRY_FROM }
6666
1,33,0 { UNDERSCORE_IMPORTS }
67+
1,32,0 { CONST_IS_POWER_OF_TWO }
6768
1,31,0 { OPTION_REPLACE }
6869
1,30,0 { ITERATOR_FIND_MAP, TOOL_ATTRIBUTES }
6970
1,29,0 { ITER_FLATTEN }

tests/ui/manual_is_power_of_two.fixed

+34
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,17 @@
11
#![warn(clippy::manual_is_power_of_two)]
2+
#![allow(clippy::precedence)]
3+
4+
macro_rules! binop {
5+
($a: expr, equal, $b: expr) => {
6+
$a == $b
7+
};
8+
($a: expr, and, $b: expr) => {
9+
$a & $b
10+
};
11+
($a: expr, minus, $b: expr) => {
12+
$a - $b
13+
};
14+
}
215

316
fn main() {
417
let a = 16_u64;
@@ -7,6 +20,8 @@ fn main() {
720
//~^ manual_is_power_of_two
821
let _ = a.is_power_of_two();
922
//~^ manual_is_power_of_two
23+
let _ = a.is_power_of_two();
24+
//~^ manual_is_power_of_two
1025

1126
// Test different orders of expression
1227
let _ = a.is_power_of_two();
@@ -23,4 +38,23 @@ fn main() {
2338
// is_power_of_two only works for unsigned integers
2439
let _ = b.count_ones() == 1;
2540
let _ = b & (b - 1) == 0;
41+
42+
let i: i32 = 3;
43+
let _ = (i as u32).is_power_of_two();
44+
//~^ manual_is_power_of_two
45+
46+
let _ = binop!(a.count_ones(), equal, 1);
47+
let _ = binop!(a, and, a - 1) == 0;
48+
let _ = a & binop!(a, minus, 1) == 0;
49+
}
50+
51+
#[clippy::msrv = "1.31"]
52+
const fn low_msrv(a: u32) -> bool {
53+
a & (a - 1) == 0
54+
}
55+
56+
#[clippy::msrv = "1.32"]
57+
const fn high_msrv(a: u32) -> bool {
58+
a.is_power_of_two()
59+
//~^ manual_is_power_of_two
2660
}

tests/ui/manual_is_power_of_two.rs

+34
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,25 @@
11
#![warn(clippy::manual_is_power_of_two)]
2+
#![allow(clippy::precedence)]
3+
4+
macro_rules! binop {
5+
($a: expr, equal, $b: expr) => {
6+
$a == $b
7+
};
8+
($a: expr, and, $b: expr) => {
9+
$a & $b
10+
};
11+
($a: expr, minus, $b: expr) => {
12+
$a - $b
13+
};
14+
}
215

316
fn main() {
417
let a = 16_u64;
518

619
let _ = a.count_ones() == 1;
720
//~^ manual_is_power_of_two
21+
let _ = u64::count_ones(a) == 1;
22+
//~^ manual_is_power_of_two
823
let _ = a & (a - 1) == 0;
924
//~^ manual_is_power_of_two
1025

@@ -23,4 +38,23 @@ fn main() {
2338
// is_power_of_two only works for unsigned integers
2439
let _ = b.count_ones() == 1;
2540
let _ = b & (b - 1) == 0;
41+
42+
let i: i32 = 3;
43+
let _ = i as u32 & (i as u32 - 1) == 0;
44+
//~^ manual_is_power_of_two
45+
46+
let _ = binop!(a.count_ones(), equal, 1);
47+
let _ = binop!(a, and, a - 1) == 0;
48+
let _ = a & binop!(a, minus, 1) == 0;
49+
}
50+
51+
#[clippy::msrv = "1.31"]
52+
const fn low_msrv(a: u32) -> bool {
53+
a & (a - 1) == 0
54+
}
55+
56+
#[clippy::msrv = "1.32"]
57+
const fn high_msrv(a: u32) -> bool {
58+
a & (a - 1) == 0
59+
//~^ manual_is_power_of_two
2660
}

0 commit comments

Comments
 (0)