Do not lint result from macro expansion

If parts of the expression comes from macro expansion, it may match an
expression equivalent to `is_power_of_two()` by chance only.
This commit is contained in:
Samuel Tardieu 2025-03-24 19:01:18 +01:00
parent 89385c135d
commit 1cab0b412e
4 changed files with 56 additions and 14 deletions

View file

@ -35,8 +35,8 @@ declare_lint_pass!(ManualIsPowerOfTwo => [MANUAL_IS_POWER_OF_TWO]);
impl<'tcx> LateLintPass<'tcx> for ManualIsPowerOfTwo {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
if let ExprKind::Binary(bin_op, lhs, rhs) = expr.kind
&& bin_op.node == BinOpKind::Eq
if !expr.span.from_expansion()
&& let Some((lhs, rhs)) = unexpanded_binop_operands(expr, BinOpKind::Eq)
{
if let Some(a) = count_ones_receiver(cx, lhs)
&& is_integer_literal(rhs, 1)
@ -92,8 +92,7 @@ fn is_one_less<'tcx>(
greater: &'tcx Expr<'tcx>,
smaller: &Expr<'tcx>,
) -> Option<&'tcx Expr<'tcx>> {
if let ExprKind::Binary(op, lhs, rhs) = smaller.kind
&& op.node == BinOpKind::Sub
if let Some((lhs, rhs)) = unexpanded_binop_operands(smaller, BinOpKind::Sub)
&& SpanlessEq::new(cx).eq_expr(greater, lhs)
&& is_integer_literal(rhs, 1)
&& matches!(cx.typeck_results().expr_ty_adjusted(greater).kind(), ty::Uint(_))
@ -106,10 +105,19 @@ fn is_one_less<'tcx>(
/// Return `v` if `expr` is `v & (v - 1)` or `(v - 1) & v`
fn is_and_minus_one<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
if let ExprKind::Binary(op, lhs, rhs) = expr.kind
&& op.node == BinOpKind::BitAnd
let (lhs, rhs) = unexpanded_binop_operands(expr, BinOpKind::BitAnd)?;
is_one_less(cx, lhs, rhs).or_else(|| is_one_less(cx, rhs, lhs))
}
/// Return the operands of the `expr` binary operation if the operator is `op` and none of the
/// operands come from expansion.
fn unexpanded_binop_operands<'hir>(expr: &Expr<'hir>, op: BinOpKind) -> Option<(&'hir Expr<'hir>, &'hir Expr<'hir>)> {
if let ExprKind::Binary(binop, lhs, rhs) = expr.kind
&& binop.node == op
&& !lhs.span.from_expansion()
&& !rhs.span.from_expansion()
{
is_one_less(cx, lhs, rhs).or_else(|| is_one_less(cx, rhs, lhs))
Some((lhs, rhs))
} else {
None
}

View file

@ -1,4 +1,17 @@
#![warn(clippy::manual_is_power_of_two)]
#![allow(clippy::precedence)]
macro_rules! binop {
($a: expr, equal, $b: expr) => {
$a == $b
};
($a: expr, and, $b: expr) => {
$a & $b
};
($a: expr, minus, $b: expr) => {
$a - $b
};
}
fn main() {
let a = 16_u64;
@ -27,4 +40,8 @@ fn main() {
let i: i32 = 3;
let _ = (i as u32).is_power_of_two();
//~^ manual_is_power_of_two
let _ = binop!(a.count_ones(), equal, 1);
let _ = binop!(a, and, a - 1) == 0;
let _ = a & binop!(a, minus, 1) == 0;
}

View file

@ -1,4 +1,17 @@
#![warn(clippy::manual_is_power_of_two)]
#![allow(clippy::precedence)]
macro_rules! binop {
($a: expr, equal, $b: expr) => {
$a == $b
};
($a: expr, and, $b: expr) => {
$a & $b
};
($a: expr, minus, $b: expr) => {
$a - $b
};
}
fn main() {
let a = 16_u64;
@ -27,4 +40,8 @@ fn main() {
let i: i32 = 3;
let _ = i as u32 & (i as u32 - 1) == 0;
//~^ manual_is_power_of_two
let _ = binop!(a.count_ones(), equal, 1);
let _ = binop!(a, and, a - 1) == 0;
let _ = a & binop!(a, minus, 1) == 0;
}

View file

@ -1,5 +1,5 @@
error: manually reimplementing `is_power_of_two`
--> tests/ui/manual_is_power_of_two.rs:6:13
--> tests/ui/manual_is_power_of_two.rs:19:13
|
LL | let _ = a.count_ones() == 1;
| ^^^^^^^^^^^^^^^^^^^ help: consider using `.is_power_of_two()`: `a.is_power_of_two()`
@ -8,37 +8,37 @@ LL | let _ = a.count_ones() == 1;
= help: to override `-D warnings` add `#[allow(clippy::manual_is_power_of_two)]`
error: manually reimplementing `is_power_of_two`
--> tests/ui/manual_is_power_of_two.rs:8:13
--> tests/ui/manual_is_power_of_two.rs:21:13
|
LL | let _ = a & (a - 1) == 0;
| ^^^^^^^^^^^^^^^^ help: consider using `.is_power_of_two()`: `a.is_power_of_two()`
error: manually reimplementing `is_power_of_two`
--> tests/ui/manual_is_power_of_two.rs:12:13
--> tests/ui/manual_is_power_of_two.rs:25:13
|
LL | let _ = 1 == a.count_ones();
| ^^^^^^^^^^^^^^^^^^^ help: consider using `.is_power_of_two()`: `a.is_power_of_two()`
error: manually reimplementing `is_power_of_two`
--> tests/ui/manual_is_power_of_two.rs:14:13
--> tests/ui/manual_is_power_of_two.rs:27:13
|
LL | let _ = (a - 1) & a == 0;
| ^^^^^^^^^^^^^^^^ help: consider using `.is_power_of_two()`: `a.is_power_of_two()`
error: manually reimplementing `is_power_of_two`
--> tests/ui/manual_is_power_of_two.rs:16:13
--> tests/ui/manual_is_power_of_two.rs:29:13
|
LL | let _ = 0 == a & (a - 1);
| ^^^^^^^^^^^^^^^^ help: consider using `.is_power_of_two()`: `a.is_power_of_two()`
error: manually reimplementing `is_power_of_two`
--> tests/ui/manual_is_power_of_two.rs:18:13
--> tests/ui/manual_is_power_of_two.rs:31:13
|
LL | let _ = 0 == (a - 1) & a;
| ^^^^^^^^^^^^^^^^ help: consider using `.is_power_of_two()`: `a.is_power_of_two()`
error: manually reimplementing `is_power_of_two`
--> tests/ui/manual_is_power_of_two.rs:28:13
--> tests/ui/manual_is_power_of_two.rs:41:13
|
LL | let _ = i as u32 & (i as u32 - 1) == 0;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.is_power_of_two()`: `(i as u32).is_power_of_two()`