about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2022-05-31 16:17:12 +0000
committerbors <bors@rust-lang.org>2022-05-31 16:17:12 +0000
commit5b1a4c0d763f51ba6ecd7e378e18a94d8b55c2bd (patch)
tree4005824c214b484fb18af07ae8508e52e07f3ff2
parent7000e758c19472bb1e642e5b4bd463a3b5c8ca68 (diff)
parent257f09776a9c68ac903d1f23b80c414b70de1185 (diff)
downloadrust-5b1a4c0d763f51ba6ecd7e378e18a94d8b55c2bd.tar.gz
rust-5b1a4c0d763f51ba6ecd7e378e18a94d8b55c2bd.zip
Auto merge of #8884 - evantypanski:manual_range_contains_multiple, r=Manishearth
Fix `manual_range_contains` false negative with chains of `&&` and `||`

Fixes #8745

Since the precedence for `&&` is the same as itself the HIR for a chain of `&&` ends up with a right skewed tree like:

```
     &&
    /  \
  &&   c2
 /  \
... c1
```

So only the leftmost `&&` was actually "fully" checked, the top level was just `c2` and `&&` so the `manual_range_contains` lint won't apply. This change makes it also check `c2` with `c1`.

There's a bit of a hacky solution in the [second commit](https://github.com/rust-lang/rust-clippy/commit/257f09776a9c68ac903d1f23b80c414b70de1185) to check if the number of open/closing parens in the snippet match. This is to prevent a case like `((x % 2 == 0) || (x < 0)) || (x >= 10)` from offering a suggestion like `((x % 2 == 0) || !(0..10).contains(&x)` which now won't compile.

Any suggestions for that paren hack welcome, kinda new to working on this so not too sure about possible solutions :) it's weird because I don't know how else to check for parens in HIR considering they're removed when lowering AST.

changelog: Fix [`manual_range_contains`] false negative with chains of `&&` and `||`
-rw-r--r--clippy_lints/src/ranges.rs18
-rw-r--r--tests/ui/range_contains.fixed7
-rw-r--r--tests/ui/range_contains.rs7
-rw-r--r--tests/ui/range_contains.stderr26
4 files changed, 55 insertions, 3 deletions
diff --git a/clippy_lints/src/ranges.rs b/clippy_lints/src/ranges.rs
index a47dc26f603..584b561dcf0 100644
--- a/clippy_lints/src/ranges.rs
+++ b/clippy_lints/src/ranges.rs
@@ -194,7 +194,7 @@ impl<'tcx> LateLintPass<'tcx> for Ranges {
             },
             ExprKind::Binary(ref op, l, r) => {
                 if meets_msrv(self.msrv, msrvs::RANGE_CONTAINS) {
-                    check_possible_range_contains(cx, op.node, l, r, expr);
+                    check_possible_range_contains(cx, op.node, l, r, expr, expr.span);
                 }
             },
             _ => {},
@@ -213,12 +213,12 @@ fn check_possible_range_contains(
     left: &Expr<'_>,
     right: &Expr<'_>,
     expr: &Expr<'_>,
+    span: Span,
 ) {
     if in_constant(cx, expr.hir_id) {
         return;
     }
 
-    let span = expr.span;
     let combine_and = match op {
         BinOpKind::And | BinOpKind::BitAnd => true,
         BinOpKind::Or | BinOpKind::BitOr => false,
@@ -294,6 +294,20 @@ fn check_possible_range_contains(
             );
         }
     }
+
+    // If the LHS is the same operator, we have to recurse to get the "real" RHS, since they have
+    // the same operator precedence
+    if_chain! {
+        if let ExprKind::Binary(ref lhs_op, _left, new_lhs) = left.kind;
+        if op == lhs_op.node;
+        let new_span = Span::new(new_lhs.span.lo(), right.span.hi(), expr.span.ctxt(), expr.span.parent());
+        if let Some(snip) = &snippet_opt(cx, new_span);
+        // Do not continue if we have mismatched number of parens, otherwise the suggestion is wrong
+        if snip.matches('(').count() == snip.matches(')').count();
+        then {
+            check_possible_range_contains(cx, op, new_lhs, right, expr, new_span);
+        }
+    }
 }
 
 struct RangeBounds<'a> {
diff --git a/tests/ui/range_contains.fixed b/tests/ui/range_contains.fixed
index f4977199711..85d021b2f25 100644
--- a/tests/ui/range_contains.fixed
+++ b/tests/ui/range_contains.fixed
@@ -49,6 +49,13 @@ fn main() {
     x >= 10 && x <= -10;
     (-3. ..=3.).contains(&y);
     y >= 3. && y <= -3.;
+
+    // Fix #8745
+    let z = 42;
+    (0..=10).contains(&x) && (0..=10).contains(&z);
+    !(0..10).contains(&x) || !(0..10).contains(&z);
+    // Make sure operators in parens don't give a breaking suggestion
+    ((x % 2 == 0) || (x < 0)) || (x >= 10);
 }
 
 // Fix #6373
diff --git a/tests/ui/range_contains.rs b/tests/ui/range_contains.rs
index 9e2180b0c99..9a7a75dc132 100644
--- a/tests/ui/range_contains.rs
+++ b/tests/ui/range_contains.rs
@@ -49,6 +49,13 @@ fn main() {
     x >= 10 && x <= -10;
     y >= -3. && y <= 3.;
     y >= 3. && y <= -3.;
+
+    // Fix #8745
+    let z = 42;
+    (x >= 0) && (x <= 10) && (z >= 0) && (z <= 10);
+    (x < 0) || (x >= 10) || (z < 0) || (z >= 10);
+    // Make sure operators in parens don't give a breaking suggestion
+    ((x % 2 == 0) || (x < 0)) || (x >= 10);
 }
 
 // Fix #6373
diff --git a/tests/ui/range_contains.stderr b/tests/ui/range_contains.stderr
index 1817ee1715d..936859db5a1 100644
--- a/tests/ui/range_contains.stderr
+++ b/tests/ui/range_contains.stderr
@@ -96,5 +96,29 @@ error: manual `RangeInclusive::contains` implementation
 LL |     y >= -3. && y <= 3.;
    |     ^^^^^^^^^^^^^^^^^^^ help: use: `(-3. ..=3.).contains(&y)`
 
-error: aborting due to 16 previous errors
+error: manual `RangeInclusive::contains` implementation
+  --> $DIR/range_contains.rs:55:30
+   |
+LL |     (x >= 0) && (x <= 10) && (z >= 0) && (z <= 10);
+   |                              ^^^^^^^^^^^^^^^^^^^^^ help: use: `(0..=10).contains(&z)`
+
+error: manual `RangeInclusive::contains` implementation
+  --> $DIR/range_contains.rs:55:5
+   |
+LL |     (x >= 0) && (x <= 10) && (z >= 0) && (z <= 10);
+   |     ^^^^^^^^^^^^^^^^^^^^^ help: use: `(0..=10).contains(&x)`
+
+error: manual `!Range::contains` implementation
+  --> $DIR/range_contains.rs:56:29
+   |
+LL |     (x < 0) || (x >= 10) || (z < 0) || (z >= 10);
+   |                             ^^^^^^^^^^^^^^^^^^^^ help: use: `!(0..10).contains(&z)`
+
+error: manual `!Range::contains` implementation
+  --> $DIR/range_contains.rs:56:5
+   |
+LL |     (x < 0) || (x >= 10) || (z < 0) || (z >= 10);
+   |     ^^^^^^^^^^^^^^^^^^^^ help: use: `!(0..10).contains(&x)`
+
+error: aborting due to 20 previous errors