improve identity_op

fix

Update identity_op.rs

ok

update without_loop_counters test

chore

fix

chore

remove visitor and leftmost

fix

add document
This commit is contained in:
tamaron 2022-04-22 10:39:38 +09:00
parent a9d31e71be
commit 6ad810f94e
3 changed files with 180 additions and 24 deletions

View File

@ -1,3 +1,4 @@
use clippy_utils::get_parent_expr;
use clippy_utils::source::snippet;
use rustc_hir::{BinOp, BinOpKind, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
@ -22,6 +23,11 @@ declare_clippy_lint! {
/// # let x = 1;
/// x / 1 + 0 * 1 - 0 | 0;
/// ```
///
/// ### Known problems
/// False negatives: `f(0 + if b { 1 } else { 2 } + 3);` is reducible to
/// `f(if b { 1 } else { 2 } + 3);`. But the lint doesn't trigger for the code.
/// See [#8724](https://github.com/rust-lang/rust-clippy/issues/8724)
#[clippy::version = "pre 1.29.0"]
pub IDENTITY_OP,
complexity,
@ -31,36 +37,66 @@ declare_clippy_lint! {
declare_lint_pass!(IdentityOp => [IDENTITY_OP]);
impl<'tcx> LateLintPass<'tcx> for IdentityOp {
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
if e.span.from_expansion() {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if expr.span.from_expansion() {
return;
}
if let ExprKind::Binary(cmp, left, right) = e.kind {
if is_allowed(cx, cmp, left, right) {
return;
}
match cmp.node {
BinOpKind::Add | BinOpKind::BitOr | BinOpKind::BitXor => {
check(cx, left, 0, e.span, right.span);
check(cx, right, 0, e.span, left.span);
},
BinOpKind::Shl | BinOpKind::Shr | BinOpKind::Sub => check(cx, right, 0, e.span, left.span),
BinOpKind::Mul => {
check(cx, left, 1, e.span, right.span);
check(cx, right, 1, e.span, left.span);
},
BinOpKind::Div => check(cx, right, 1, e.span, left.span),
BinOpKind::BitAnd => {
check(cx, left, -1, e.span, right.span);
check(cx, right, -1, e.span, left.span);
},
BinOpKind::Rem => check_remainder(cx, left, right, e.span, left.span),
_ => (),
if let ExprKind::Binary(cmp, left, right) = &expr.kind {
if !is_allowed(cx, *cmp, left, right) {
match cmp.node {
BinOpKind::Add | BinOpKind::BitOr | BinOpKind::BitXor => {
if reducible_to_right(cx, expr, right) {
check(cx, left, 0, expr.span, right.span);
}
check(cx, right, 0, expr.span, left.span);
},
BinOpKind::Shl | BinOpKind::Shr | BinOpKind::Sub => {
check(cx, right, 0, expr.span, left.span);
},
BinOpKind::Mul => {
if reducible_to_right(cx, expr, right) {
check(cx, left, 1, expr.span, right.span);
}
check(cx, right, 1, expr.span, left.span);
},
BinOpKind::Div => check(cx, right, 1, expr.span, left.span),
BinOpKind::BitAnd => {
if reducible_to_right(cx, expr, right) {
check(cx, left, -1, expr.span, right.span);
}
check(cx, right, -1, expr.span, left.span);
},
BinOpKind::Rem => {
// Don't call reducible_to_right because N % N is always reducible to 1
check_remainder(cx, left, right, expr.span, left.span);
},
_ => (),
}
}
}
}
}
/// Checks if `left op ..right` can be actually reduced to `right`
/// e.g. `0 + if b { 1 } else { 2 } + if b { 3 } else { 4 }`
/// cannot be reduced to `if b { 1 } else { 2 } + if b { 3 } else { 4 }`
/// See #8724
fn reducible_to_right(cx: &LateContext<'_>, binary: &Expr<'_>, right: &Expr<'_>) -> bool {
if let ExprKind::If(..) | ExprKind::Match(..) | ExprKind::Block(..) | ExprKind::Loop(..) = right.kind {
is_toplevel_binary(cx, binary)
} else {
true
}
}
fn is_toplevel_binary(cx: &LateContext<'_>, must_be_binary: &Expr<'_>) -> bool {
if let Some(parent) = get_parent_expr(cx, must_be_binary) && let ExprKind::Binary(..) = &parent.kind {
false
} else {
true
}
}
fn is_allowed(cx: &LateContext<'_>, cmp: BinOp, left: &Expr<'_>, right: &Expr<'_>) -> bool {
// This lint applies to integers
!cx.typeck_results().expr_ty(left).peel_refs().is_integral()

View File

@ -77,4 +77,34 @@ fn main() {
(x + 1) % 3; // no error
4 % 3; // no error
4 % -3; // no error
// See #8724
let a = 0;
let b = true;
0 + if b { 1 } else { 2 };
0 + if b { 1 } else { 2 } + if b { 3 } else { 4 }; // no error
0 + match a { 0 => 10, _ => 20 };
0 + match a { 0 => 10, _ => 20 } + match a { 0 => 30, _ => 40 }; // no error
0 + if b { 1 } else { 2 } + match a { 0 => 30, _ => 40 }; // no error
0 + match a { 0 => 10, _ => 20 } + if b { 3 } else { 4 }; // no error
0 + if b { 0 + 1 } else { 2 };
0 + match a { 0 => 0 + 10, _ => 20 };
0 + if b { 0 + 1 } else { 2 } + match a { 0 => 0 + 30, _ => 40 };
let _ = 0 + if 0 + 1 > 0 { 1 } else { 2 } + if 0 + 1 > 0 { 3 } else { 4 };
let _ = 0 + match 0 + 1 { 0 => 10, _ => 20 } + match 0 + 1 { 0 => 30, _ => 40 };
0 + if b { 1 } else { 2 } + if b { 3 } else { 4 } + 0;
0 + { a } + 3; // no error
0 + loop { let mut c = 0; if c == 10 { break c; } c += 1; } + { a * 2 }; // no error
fn f(_: i32) {
todo!();
}
f(1 * a + { 8 * 5 });
f(0 + if b { 1 } else { 2 } + 3); // no error
const _: i32 = { 2 * 4 } + 0 + 3;
const _: i32 = 0 + { 1 + 2 * 3 } + 3; // no error
}

View File

@ -108,5 +108,95 @@ error: the operation is ineffective. Consider reducing it to `1`
LL | x + 1 % 3;
| ^^^^^
error: aborting due to 18 previous errors
error: the operation is ineffective. Consider reducing it to `if b { 1 } else { 2 }`
--> $DIR/identity_op.rs:84:5
|
LL | 0 + if b { 1 } else { 2 };
| ^^^^^^^^^^^^^^^^^^^^^^^^^
error: the operation is ineffective. Consider reducing it to `match a { 0 => 10, _ => 20 }`
--> $DIR/identity_op.rs:86:5
|
LL | 0 + match a { 0 => 10, _ => 20 };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: the operation is ineffective. Consider reducing it to `if b { 0 + 1 } else { 2 }`
--> $DIR/identity_op.rs:91:5
|
LL | 0 + if b { 0 + 1 } else { 2 };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: the operation is ineffective. Consider reducing it to `1`
--> $DIR/identity_op.rs:91:16
|
LL | 0 + if b { 0 + 1 } else { 2 };
| ^^^^^
error: the operation is ineffective. Consider reducing it to `match a { 0 => 0 + 10, _ => 20 }`
--> $DIR/identity_op.rs:92:5
|
LL | 0 + match a { 0 => 0 + 10, _ => 20 };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: the operation is ineffective. Consider reducing it to `10`
--> $DIR/identity_op.rs:92:25
|
LL | 0 + match a { 0 => 0 + 10, _ => 20 };
| ^^^^^^
error: the operation is ineffective. Consider reducing it to `1`
--> $DIR/identity_op.rs:93:16
|
LL | 0 + if b { 0 + 1 } else { 2 } + match a { 0 => 0 + 30, _ => 40 };
| ^^^^^
error: the operation is ineffective. Consider reducing it to `30`
--> $DIR/identity_op.rs:93:52
|
LL | 0 + if b { 0 + 1 } else { 2 } + match a { 0 => 0 + 30, _ => 40 };
| ^^^^^^
error: the operation is ineffective. Consider reducing it to `1`
--> $DIR/identity_op.rs:95:20
|
LL | let _ = 0 + if 0 + 1 > 0 { 1 } else { 2 } + if 0 + 1 > 0 { 3 } else { 4 };
| ^^^^^
error: the operation is ineffective. Consider reducing it to `1`
--> $DIR/identity_op.rs:95:52
|
LL | let _ = 0 + if 0 + 1 > 0 { 1 } else { 2 } + if 0 + 1 > 0 { 3 } else { 4 };
| ^^^^^
error: the operation is ineffective. Consider reducing it to `1`
--> $DIR/identity_op.rs:96:23
|
LL | let _ = 0 + match 0 + 1 { 0 => 10, _ => 20 } + match 0 + 1 { 0 => 30, _ => 40 };
| ^^^^^
error: the operation is ineffective. Consider reducing it to `1`
--> $DIR/identity_op.rs:96:58
|
LL | let _ = 0 + match 0 + 1 { 0 => 10, _ => 20 } + match 0 + 1 { 0 => 30, _ => 40 };
| ^^^^^
error: the operation is ineffective. Consider reducing it to `0 + if b { 1 } else { 2 } + if b { 3 } else { 4 }`
--> $DIR/identity_op.rs:98:5
|
LL | 0 + if b { 1 } else { 2 } + if b { 3 } else { 4 } + 0;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: the operation is ineffective. Consider reducing it to `a`
--> $DIR/identity_op.rs:106:7
|
LL | f(1 * a + { 8 * 5 });
| ^^^^^
error: the operation is ineffective. Consider reducing it to `{ 2 * 4 }`
--> $DIR/identity_op.rs:108:20
|
LL | const _: i32 = { 2 * 4 } + 0 + 3;
| ^^^^^^^^^^^^^
error: aborting due to 33 previous errors