Skip to content

Commit 5b1a4c0

Browse files
committed
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](257f097) 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 `||`
2 parents 7000e75 + 257f097 commit 5b1a4c0

File tree

4 files changed

+55
-3
lines changed

4 files changed

+55
-3
lines changed

clippy_lints/src/ranges.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ impl<'tcx> LateLintPass<'tcx> for Ranges {
194194
},
195195
ExprKind::Binary(ref op, l, r) => {
196196
if meets_msrv(self.msrv, msrvs::RANGE_CONTAINS) {
197-
check_possible_range_contains(cx, op.node, l, r, expr);
197+
check_possible_range_contains(cx, op.node, l, r, expr, expr.span);
198198
}
199199
},
200200
_ => {},
@@ -213,12 +213,12 @@ fn check_possible_range_contains(
213213
left: &Expr<'_>,
214214
right: &Expr<'_>,
215215
expr: &Expr<'_>,
216+
span: Span,
216217
) {
217218
if in_constant(cx, expr.hir_id) {
218219
return;
219220
}
220221

221-
let span = expr.span;
222222
let combine_and = match op {
223223
BinOpKind::And | BinOpKind::BitAnd => true,
224224
BinOpKind::Or | BinOpKind::BitOr => false,
@@ -294,6 +294,20 @@ fn check_possible_range_contains(
294294
);
295295
}
296296
}
297+
298+
// If the LHS is the same operator, we have to recurse to get the "real" RHS, since they have
299+
// the same operator precedence
300+
if_chain! {
301+
if let ExprKind::Binary(ref lhs_op, _left, new_lhs) = left.kind;
302+
if op == lhs_op.node;
303+
let new_span = Span::new(new_lhs.span.lo(), right.span.hi(), expr.span.ctxt(), expr.span.parent());
304+
if let Some(snip) = &snippet_opt(cx, new_span);
305+
// Do not continue if we have mismatched number of parens, otherwise the suggestion is wrong
306+
if snip.matches('(').count() == snip.matches(')').count();
307+
then {
308+
check_possible_range_contains(cx, op, new_lhs, right, expr, new_span);
309+
}
310+
}
297311
}
298312

299313
struct RangeBounds<'a> {

tests/ui/range_contains.fixed

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,13 @@ fn main() {
4949
x >= 10 && x <= -10;
5050
(-3. ..=3.).contains(&y);
5151
y >= 3. && y <= -3.;
52+
53+
// Fix #8745
54+
let z = 42;
55+
(0..=10).contains(&x) && (0..=10).contains(&z);
56+
!(0..10).contains(&x) || !(0..10).contains(&z);
57+
// Make sure operators in parens don't give a breaking suggestion
58+
((x % 2 == 0) || (x < 0)) || (x >= 10);
5259
}
5360

5461
// Fix #6373

tests/ui/range_contains.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,13 @@ fn main() {
4949
x >= 10 && x <= -10;
5050
y >= -3. && y <= 3.;
5151
y >= 3. && y <= -3.;
52+
53+
// Fix #8745
54+
let z = 42;
55+
(x >= 0) && (x <= 10) && (z >= 0) && (z <= 10);
56+
(x < 0) || (x >= 10) || (z < 0) || (z >= 10);
57+
// Make sure operators in parens don't give a breaking suggestion
58+
((x % 2 == 0) || (x < 0)) || (x >= 10);
5259
}
5360

5461
// Fix #6373

tests/ui/range_contains.stderr

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,5 +96,29 @@ error: manual `RangeInclusive::contains` implementation
9696
LL | y >= -3. && y <= 3.;
9797
| ^^^^^^^^^^^^^^^^^^^ help: use: `(-3. ..=3.).contains(&y)`
9898

99-
error: aborting due to 16 previous errors
99+
error: manual `RangeInclusive::contains` implementation
100+
--> $DIR/range_contains.rs:55:30
101+
|
102+
LL | (x >= 0) && (x <= 10) && (z >= 0) && (z <= 10);
103+
| ^^^^^^^^^^^^^^^^^^^^^ help: use: `(0..=10).contains(&z)`
104+
105+
error: manual `RangeInclusive::contains` implementation
106+
--> $DIR/range_contains.rs:55:5
107+
|
108+
LL | (x >= 0) && (x <= 10) && (z >= 0) && (z <= 10);
109+
| ^^^^^^^^^^^^^^^^^^^^^ help: use: `(0..=10).contains(&x)`
110+
111+
error: manual `!Range::contains` implementation
112+
--> $DIR/range_contains.rs:56:29
113+
|
114+
LL | (x < 0) || (x >= 10) || (z < 0) || (z >= 10);
115+
| ^^^^^^^^^^^^^^^^^^^^ help: use: `!(0..10).contains(&z)`
116+
117+
error: manual `!Range::contains` implementation
118+
--> $DIR/range_contains.rs:56:5
119+
|
120+
LL | (x < 0) || (x >= 10) || (z < 0) || (z >= 10);
121+
| ^^^^^^^^^^^^^^^^^^^^ help: use: `!(0..10).contains(&x)`
122+
123+
error: aborting due to 20 previous errors
100124

0 commit comments

Comments
 (0)