about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--clippy_lints/src/operators/identity_op.rs211
-rw-r--r--tests/ui/identity_op.fixed42
-rw-r--r--tests/ui/identity_op.rs40
-rw-r--r--tests/ui/identity_op.stderr64
4 files changed, 252 insertions, 105 deletions
diff --git a/clippy_lints/src/operators/identity_op.rs b/clippy_lints/src/operators/identity_op.rs
index 830be50c8ba..d8430f6492d 100644
--- a/clippy_lints/src/operators/identity_op.rs
+++ b/clippy_lints/src/operators/identity_op.rs
@@ -42,83 +42,53 @@ pub(crate) fn check<'tcx>(
 
     match op {
         BinOpKind::Add | BinOpKind::BitOr | BinOpKind::BitXor => {
-            let _ = check_op(
-                cx,
-                left,
-                0,
-                expr.span,
-                peeled_right_span,
-                needs_parenthesis(cx, expr, right),
-                right_is_coerced_to_value,
-            ) || check_op(
-                cx,
-                right,
-                0,
-                expr.span,
-                peeled_left_span,
-                Parens::Unneeded,
-                left_is_coerced_to_value,
-            );
+            if is_redundant_op(cx, left, 0) {
+                let paren = needs_parenthesis(cx, expr, right);
+                span_ineffective_operation(cx, expr.span, peeled_right_span, paren, right_is_coerced_to_value);
+            } else if is_redundant_op(cx, right, 0) {
+                let paren = needs_parenthesis(cx, expr, left);
+                span_ineffective_operation(cx, expr.span, peeled_left_span, paren, left_is_coerced_to_value);
+            }
         },
         BinOpKind::Shl | BinOpKind::Shr | BinOpKind::Sub => {
-            let _ = check_op(
-                cx,
-                right,
-                0,
-                expr.span,
-                peeled_left_span,
-                Parens::Unneeded,
-                left_is_coerced_to_value,
-            );
+            if is_redundant_op(cx, right, 0) {
+                span_ineffective_operation(
+                    cx,
+                    expr.span,
+                    peeled_left_span,
+                    Parens::Unneeded,
+                    left_is_coerced_to_value,
+                );
+            }
         },
         BinOpKind::Mul => {
-            let _ = check_op(
-                cx,
-                left,
-                1,
-                expr.span,
-                peeled_right_span,
-                needs_parenthesis(cx, expr, right),
-                right_is_coerced_to_value,
-            ) || check_op(
-                cx,
-                right,
-                1,
-                expr.span,
-                peeled_left_span,
-                Parens::Unneeded,
-                left_is_coerced_to_value,
-            );
+            if is_redundant_op(cx, left, 1) {
+                let paren = needs_parenthesis(cx, expr, right);
+                span_ineffective_operation(cx, expr.span, peeled_right_span, paren, right_is_coerced_to_value);
+            } else if is_redundant_op(cx, right, 1) {
+                let paren = needs_parenthesis(cx, expr, left);
+                span_ineffective_operation(cx, expr.span, peeled_left_span, paren, left_is_coerced_to_value);
+            }
         },
         BinOpKind::Div => {
-            let _ = check_op(
-                cx,
-                right,
-                1,
-                expr.span,
-                peeled_left_span,
-                Parens::Unneeded,
-                left_is_coerced_to_value,
-            );
+            if is_redundant_op(cx, right, 1) {
+                span_ineffective_operation(
+                    cx,
+                    expr.span,
+                    peeled_left_span,
+                    Parens::Unneeded,
+                    left_is_coerced_to_value,
+                );
+            }
         },
         BinOpKind::BitAnd => {
-            let _ = check_op(
-                cx,
-                left,
-                -1,
-                expr.span,
-                peeled_right_span,
-                needs_parenthesis(cx, expr, right),
-                right_is_coerced_to_value,
-            ) || check_op(
-                cx,
-                right,
-                -1,
-                expr.span,
-                peeled_left_span,
-                Parens::Unneeded,
-                left_is_coerced_to_value,
-            );
+            if is_redundant_op(cx, left, -1) {
+                let paren = needs_parenthesis(cx, expr, right);
+                span_ineffective_operation(cx, expr.span, peeled_right_span, paren, right_is_coerced_to_value);
+            } else if is_redundant_op(cx, right, -1) {
+                let paren = needs_parenthesis(cx, expr, left);
+                span_ineffective_operation(cx, expr.span, peeled_left_span, paren, left_is_coerced_to_value);
+            }
         },
         BinOpKind::Rem => check_remainder(cx, left, right, expr.span, left.span),
         _ => (),
@@ -138,43 +108,75 @@ enum Parens {
     Unneeded,
 }
 
-/// Checks if `left op right` needs parenthesis when reduced to `right`
+/// Checks if a binary expression needs parenthesis when reduced to just its
+/// right or left child.
+///
+/// e.g. `-(x + y + 0)` cannot be reduced to `-x + y`, as the behavior changes silently.
+/// e.g. `1u64 + ((x + y + 0i32) as u64)` cannot be reduced to `1u64 + x + y as u64`, since
+/// the the cast expression will not apply to the same expression.
 /// 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 }` where the `if` could be
-/// interpreted as a statement
+/// interpreted as a statement. The same behavior happens for `match`, `loop`,
+/// and blocks.
+/// e.g.  `2 * (0 + { a })` can be reduced to `2 * { a }` without the need for parenthesis,
+/// but `1 * ({ a } + 4)` cannot be reduced to `{ a } + 4`, as a block at the start of a line
+/// will be interpreted as a statement instead of an expression.
 ///
-/// See #8724
-fn needs_parenthesis(cx: &LateContext<'_>, binary: &Expr<'_>, right: &Expr<'_>) -> Parens {
-    match right.kind {
+/// See #8724, #13470
+fn needs_parenthesis(cx: &LateContext<'_>, binary: &Expr<'_>, child: &Expr<'_>) -> Parens {
+    match child.kind {
         ExprKind::Binary(_, lhs, _) | ExprKind::Cast(lhs, _) => {
-            // ensure we're checking against the leftmost expression of `right`
-            //
-            //     ~~~ `lhs`
-            // 0 + {4} * 2
-            //     ~~~~~~~ `right`
-            return needs_parenthesis(cx, binary, lhs);
+            // For casts and binary expressions, we want to add parenthesis if
+            // the parent HIR node is an expression with a different precedence,
+            // or if the parent HIR node is a Block or Stmt, and the new left hand side
+            // would be treated as a statement rather than an expression.
+            if let Some((_, parent)) = cx.tcx.hir().parent_iter(binary.hir_id).next() {
+                if let Node::Expr(expr) = parent {
+                    if child.precedence().order() != expr.precedence().order() {
+                        return Parens::Needed;
+                    }
+                    return Parens::Unneeded;
+                }
+                match parent {
+                    Node::Block(_) | Node::Stmt(_) => {
+                        // ensure we're checking against the leftmost expression of `child`
+                        //
+                        // ~~~~~~~~~~~ `binary`
+                        //     ~~~ `lhs`
+                        // 0 + {4} * 2
+                        //     ~~~~~~~ `child`
+                        return needs_parenthesis(cx, binary, lhs);
+                    },
+                    _ => return Parens::Unneeded,
+                }
+            }
         },
-        ExprKind::If(..) | ExprKind::Match(..) | ExprKind::Block(..) | ExprKind::Loop(..) => {},
-        _ => return Parens::Unneeded,
-    }
+        ExprKind::If(..) | ExprKind::Match(..) | ExprKind::Block(..) | ExprKind::Loop(..) => {
+            // For if, match, block, and loop expressions, we want to add parenthesis if
+            // the closest ancestor node that is not an expression is a block or statement.
+            // This would mean that the rustfix suggestion will appear at the start of a line, which causes
+            // these expressions to be interpreted as statements if they do not have parenthesis.
+            let mut prev_id = binary.hir_id;
+            for (_, parent) in cx.tcx.hir().parent_iter(binary.hir_id) {
+                if let Node::Expr(expr) = parent
+                    && let ExprKind::Binary(_, lhs, _) | ExprKind::Cast(lhs, _) | ExprKind::Unary(_, lhs) = expr.kind
+                    && lhs.hir_id == prev_id
+                {
+                    // keep going until we find a node that encompasses left of `binary`
+                    prev_id = expr.hir_id;
+                    continue;
+                }
 
-    let mut prev_id = binary.hir_id;
-    for (_, node) in cx.tcx.hir().parent_iter(binary.hir_id) {
-        if let Node::Expr(expr) = node
-            && let ExprKind::Binary(_, lhs, _) | ExprKind::Cast(lhs, _) = expr.kind
-            && lhs.hir_id == prev_id
-        {
-            // keep going until we find a node that encompasses left of `binary`
-            prev_id = expr.hir_id;
-            continue;
-        }
-
-        match node {
-            Node::Block(_) | Node::Stmt(_) => break,
-            _ => return Parens::Unneeded,
-        };
+                match parent {
+                    Node::Block(_) | Node::Stmt(_) => return Parens::Needed,
+                    _ => return Parens::Unneeded,
+                };
+            }
+        },
+        _ => {
+            return Parens::Unneeded;
+        },
     }
-
     Parens::Needed
 }
 
@@ -199,7 +201,7 @@ fn check_remainder(cx: &LateContext<'_>, left: &Expr<'_>, right: &Expr<'_>, span
     }
 }
 
-fn check_op(cx: &LateContext<'_>, e: &Expr<'_>, m: i8, span: Span, arg: Span, parens: Parens, is_erased: bool) -> bool {
+fn is_redundant_op(cx: &LateContext<'_>, e: &Expr<'_>, m: i8) -> bool {
     if let Some(Constant::Int(v)) = ConstEvalCtxt::new(cx).eval_simple(e).map(Constant::peel_refs) {
         let check = match *cx.typeck_results().expr_ty(e).peel_refs().kind() {
             ty::Int(ity) => unsext(cx.tcx, -1_i128, ity),
@@ -212,7 +214,6 @@ fn check_op(cx: &LateContext<'_>, e: &Expr<'_>, m: i8, span: Span, arg: Span, pa
             1 => v == 1,
             _ => unreachable!(),
         } {
-            span_ineffective_operation(cx, span, arg, parens, is_erased);
             return true;
         }
     }
@@ -234,7 +235,13 @@ fn span_ineffective_operation(
         expr_snippet.into_owned()
     };
     let suggestion = match parens {
-        Parens::Needed => format!("({expr_snippet})"),
+        Parens::Needed => {
+            if !expr_snippet.starts_with('(') && !expr_snippet.ends_with(')') {
+                format!("({expr_snippet})")
+            } else {
+                expr_snippet
+            }
+        },
         Parens::Unneeded => expr_snippet,
     };
 
diff --git a/tests/ui/identity_op.fixed b/tests/ui/identity_op.fixed
index b18d8560f6f..18b7401768d 100644
--- a/tests/ui/identity_op.fixed
+++ b/tests/ui/identity_op.fixed
@@ -149,7 +149,7 @@ fn main() {
 
     2 * { a };
     //~^ ERROR: this operation has no effect
-    (({ a } + 4));
+    ({ a } + 4);
     //~^ ERROR: this operation has no effect
     1;
     //~^ ERROR: this operation has no effect
@@ -212,3 +212,43 @@ fn issue_12050() {
         //~^ ERROR: this operation has no effect
     }
 }
+
+fn issue_13470() {
+    let x = 1i32;
+    let y = 1i32;
+    // Removes the + 0i32 while keeping the parentheses around x + y so the cast operation works
+    let _: u64 = (x + y) as u64;
+    //~^ ERROR: this operation has no effect
+    // both of the next two lines should look the same after rustfix
+    let _: u64 = 1u64 & (x + y) as u64;
+    //~^ ERROR: this operation has no effect
+    // Same as above, but with extra redundant parenthesis
+    let _: u64 = 1u64 & (x + y) as u64;
+    //~^ ERROR: this operation has no effect
+    // Should maintain parenthesis even if the surrounding expr has the same precedence
+    let _: u64 = 5u64 + (x + y) as u64;
+    //~^ ERROR: this operation has no effect
+
+    // If we don't maintain the parens here, the behavior changes
+    let _ = -(x + y);
+    //~^ ERROR: this operation has no effect
+    // Maintain parenthesis if the parent expr is of higher precedence
+    let _ = 2i32 * (x + y);
+    //~^ ERROR: this operation has no effect
+    // No need for parenthesis if the parent expr is of equal precedence
+    let _ = 2i32 + x + y;
+    //~^ ERROR: this operation has no effect
+    // But make sure that inner parens still exist
+    let z = 1i32;
+    let _ = 2 + x + (y * z);
+    //~^ ERROR: this operation has no effect
+    // Maintain parenthesis if the parent expr is of lower precedence
+    // This is for clarity, and clippy will not warn on these being unnecessary
+    let _ = 2i32 + (x * y);
+    //~^ ERROR: this operation has no effect
+
+    let x = 1i16;
+    let y = 1i16;
+    let _: u64 = 1u64 + (x as i32 + y as i32) as u64;
+    //~^ ERROR: this operation has no effect
+}
diff --git a/tests/ui/identity_op.rs b/tests/ui/identity_op.rs
index f1f01b42447..55483f4e5fb 100644
--- a/tests/ui/identity_op.rs
+++ b/tests/ui/identity_op.rs
@@ -212,3 +212,43 @@ fn issue_12050() {
         //~^ ERROR: this operation has no effect
     }
 }
+
+fn issue_13470() {
+    let x = 1i32;
+    let y = 1i32;
+    // Removes the + 0i32 while keeping the parentheses around x + y so the cast operation works
+    let _: u64 = (x + y + 0i32) as u64;
+    //~^ ERROR: this operation has no effect
+    // both of the next two lines should look the same after rustfix
+    let _: u64 = 1u64 & (x + y + 0i32) as u64;
+    //~^ ERROR: this operation has no effect
+    // Same as above, but with extra redundant parenthesis
+    let _: u64 = 1u64 & ((x + y) + 0i32) as u64;
+    //~^ ERROR: this operation has no effect
+    // Should maintain parenthesis even if the surrounding expr has the same precedence
+    let _: u64 = 5u64 + ((x + y) + 0i32) as u64;
+    //~^ ERROR: this operation has no effect
+
+    // If we don't maintain the parens here, the behavior changes
+    let _ = -(x + y + 0i32);
+    //~^ ERROR: this operation has no effect
+    // Maintain parenthesis if the parent expr is of higher precedence
+    let _ = 2i32 * (x + y + 0i32);
+    //~^ ERROR: this operation has no effect
+    // No need for parenthesis if the parent expr is of equal precedence
+    let _ = 2i32 + (x + y + 0i32);
+    //~^ ERROR: this operation has no effect
+    // But make sure that inner parens still exist
+    let z = 1i32;
+    let _ = 2 + (x + (y * z) + 0);
+    //~^ ERROR: this operation has no effect
+    // Maintain parenthesis if the parent expr is of lower precedence
+    // This is for clarity, and clippy will not warn on these being unnecessary
+    let _ = 2i32 + (x * y * 1i32);
+    //~^ ERROR: this operation has no effect
+
+    let x = 1i16;
+    let y = 1i16;
+    let _: u64 = 1u64 + ((x as i32 + y as i32) as u64 + 0u64);
+    //~^ ERROR: this operation has no effect
+}
diff --git a/tests/ui/identity_op.stderr b/tests/ui/identity_op.stderr
index 9fff86b86f9..856bebfa1ff 100644
--- a/tests/ui/identity_op.stderr
+++ b/tests/ui/identity_op.stderr
@@ -221,7 +221,7 @@ error: this operation has no effect
   --> tests/ui/identity_op.rs:152:5
    |
 LL |     1 * ({ a } + 4);
-   |     ^^^^^^^^^^^^^^^ help: consider reducing it to: `(({ a } + 4))`
+   |     ^^^^^^^^^^^^^^^ help: consider reducing it to: `({ a } + 4)`
 
 error: this operation has no effect
   --> tests/ui/identity_op.rs:154:5
@@ -313,5 +313,65 @@ error: this operation has no effect
 LL |         let _: i32 = **&&*&x + 0;
    |                      ^^^^^^^^^^^ help: consider reducing it to: `***&&*&x`
 
-error: aborting due to 52 previous errors
+error: this operation has no effect
+  --> tests/ui/identity_op.rs:220:18
+   |
+LL |     let _: u64 = (x + y + 0i32) as u64;
+   |                  ^^^^^^^^^^^^^^ help: consider reducing it to: `(x + y)`
+
+error: this operation has no effect
+  --> tests/ui/identity_op.rs:223:25
+   |
+LL |     let _: u64 = 1u64 & (x + y + 0i32) as u64;
+   |                         ^^^^^^^^^^^^^^ help: consider reducing it to: `(x + y)`
+
+error: this operation has no effect
+  --> tests/ui/identity_op.rs:226:25
+   |
+LL |     let _: u64 = 1u64 & ((x + y) + 0i32) as u64;
+   |                         ^^^^^^^^^^^^^^^^ help: consider reducing it to: `(x + y)`
+
+error: this operation has no effect
+  --> tests/ui/identity_op.rs:229:25
+   |
+LL |     let _: u64 = 5u64 + ((x + y) + 0i32) as u64;
+   |                         ^^^^^^^^^^^^^^^^ help: consider reducing it to: `(x + y)`
+
+error: this operation has no effect
+  --> tests/ui/identity_op.rs:233:14
+   |
+LL |     let _ = -(x + y + 0i32);
+   |              ^^^^^^^^^^^^^^ help: consider reducing it to: `(x + y)`
+
+error: this operation has no effect
+  --> tests/ui/identity_op.rs:236:20
+   |
+LL |     let _ = 2i32 * (x + y + 0i32);
+   |                    ^^^^^^^^^^^^^^ help: consider reducing it to: `(x + y)`
+
+error: this operation has no effect
+  --> tests/ui/identity_op.rs:239:20
+   |
+LL |     let _ = 2i32 + (x + y + 0i32);
+   |                    ^^^^^^^^^^^^^^ help: consider reducing it to: `x + y`
+
+error: this operation has no effect
+  --> tests/ui/identity_op.rs:243:17
+   |
+LL |     let _ = 2 + (x + (y * z) + 0);
+   |                 ^^^^^^^^^^^^^^^^^ help: consider reducing it to: `x + (y * z)`
+
+error: this operation has no effect
+  --> tests/ui/identity_op.rs:247:20
+   |
+LL |     let _ = 2i32 + (x * y * 1i32);
+   |                    ^^^^^^^^^^^^^^ help: consider reducing it to: `(x * y)`
+
+error: this operation has no effect
+  --> tests/ui/identity_op.rs:252:25
+   |
+LL |     let _: u64 = 1u64 + ((x as i32 + y as i32) as u64 + 0u64);
+   |                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider reducing it to: `(x as i32 + y as i32) as u64`
+
+error: aborting due to 62 previous errors