diff --git a/clippy_lints/src/ranges.rs b/clippy_lints/src/ranges.rs index e213c208794..f26f3650cb3 100644 --- a/clippy_lints/src/ranges.rs +++ b/clippy_lints/src/ranges.rs @@ -207,7 +207,13 @@ impl<'tcx> LateLintPass<'tcx> for Ranges { extract_msrv_attr!(LateContext); } -fn check_possible_range_contains(cx: &LateContext<'_>, op: BinOpKind, l: &Expr<'_>, r: &Expr<'_>, expr: &Expr<'_>) { +fn check_possible_range_contains( + cx: &LateContext<'_>, + op: BinOpKind, + left: &Expr<'_>, + right: &Expr<'_>, + expr: &Expr<'_>, +) { if in_constant(cx, expr.hir_id) { return; } @@ -219,21 +225,19 @@ fn check_possible_range_contains(cx: &LateContext<'_>, op: BinOpKind, l: &Expr<' _ => return, }; // value, name, order (higher/lower), inclusiveness - if let (Some((lval, lid, name_span, lval_span, lord, linc)), Some((rval, rid, _, rval_span, rord, rinc))) = - (check_range_bounds(cx, l), check_range_bounds(cx, r)) - { + if let (Some(l), Some(r)) = (check_range_bounds(cx, left), check_range_bounds(cx, right)) { // we only lint comparisons on the same name and with different // direction - if lid != rid || lord == rord { + if l.id != r.id || l.ord == r.ord { return; } - let ord = Constant::partial_cmp(cx.tcx, cx.typeck_results().expr_ty(l), &lval, &rval); - if combine_and && ord == Some(rord) { + let ord = Constant::partial_cmp(cx.tcx, cx.typeck_results().expr_ty(l.expr), &l.val, &r.val); + if combine_and && ord == Some(r.ord) { // order lower bound and upper bound - let (l_span, u_span, l_inc, u_inc) = if rord == Ordering::Less { - (lval_span, rval_span, linc, rinc) + let (l_span, u_span, l_inc, u_inc) = if r.ord == Ordering::Less { + (l.val_span, r.val_span, l.inc, r.inc) } else { - (rval_span, lval_span, rinc, linc) + (r.val_span, l.val_span, r.inc, l.inc) }; // we only lint inclusive lower bounds if !l_inc { @@ -245,7 +249,7 @@ fn check_possible_range_contains(cx: &LateContext<'_>, op: BinOpKind, l: &Expr<' ("Range", "..") }; let mut applicability = Applicability::MachineApplicable; - let name = snippet_with_applicability(cx, name_span, "_", &mut applicability); + let name = snippet_with_applicability(cx, l.name_span, "_", &mut applicability); let lo = snippet_with_applicability(cx, l_span, "_", &mut applicability); let hi = snippet_with_applicability(cx, u_span, "_", &mut applicability); let space = if lo.ends_with('.') { " " } else { "" }; @@ -258,13 +262,13 @@ fn check_possible_range_contains(cx: &LateContext<'_>, op: BinOpKind, l: &Expr<' format!("({}{}{}{}).contains(&{})", lo, space, range_op, hi, name), applicability, ); - } else if !combine_and && ord == Some(lord) { + } else if !combine_and && ord == Some(l.ord) { // `!_.contains(_)` // order lower bound and upper bound - let (l_span, u_span, l_inc, u_inc) = if lord == Ordering::Less { - (lval_span, rval_span, linc, rinc) + let (l_span, u_span, l_inc, u_inc) = if l.ord == Ordering::Less { + (l.val_span, r.val_span, l.inc, r.inc) } else { - (rval_span, lval_span, rinc, linc) + (r.val_span, l.val_span, r.inc, l.inc) }; if l_inc { return; @@ -275,7 +279,7 @@ fn check_possible_range_contains(cx: &LateContext<'_>, op: BinOpKind, l: &Expr<' ("RangeInclusive", "..=") }; let mut applicability = Applicability::MachineApplicable; - let name = snippet_with_applicability(cx, name_span, "_", &mut applicability); + let name = snippet_with_applicability(cx, l.name_span, "_", &mut applicability); let lo = snippet_with_applicability(cx, l_span, "_", &mut applicability); let hi = snippet_with_applicability(cx, u_span, "_", &mut applicability); let space = if lo.ends_with('.') { " " } else { "" }; @@ -292,7 +296,20 @@ fn check_possible_range_contains(cx: &LateContext<'_>, op: BinOpKind, l: &Expr<' } } -fn check_range_bounds(cx: &LateContext<'_>, ex: &Expr<'_>) -> Option<(Constant, HirId, Span, Span, Ordering, bool)> { +struct RangeBounds<'a> { + val: Constant, + expr: &'a Expr<'a>, + id: HirId, + name_span: Span, + val_span: Span, + ord: Ordering, + inc: bool, +} + +// Takes a binary expression such as x <= 2 as input +// Breaks apart into various pieces, such as the value of the number, +// hir id of the variable, and direction/inclusiveness of the operator +fn check_range_bounds<'a>(cx: &'a LateContext<'_>, ex: &'a Expr<'_>) -> Option> { if let ExprKind::Binary(ref op, l, r) = ex.kind { let (inclusive, ordering) = match op.node { BinOpKind::Gt => (false, Ordering::Greater), @@ -303,11 +320,27 @@ fn check_range_bounds(cx: &LateContext<'_>, ex: &Expr<'_>) -> Option<(Constant, }; if let Some(id) = path_to_local(l) { if let Some((c, _)) = constant(cx, cx.typeck_results(), r) { - return Some((c, id, l.span, r.span, ordering, inclusive)); + return Some(RangeBounds { + val: c, + expr: r, + id, + name_span: l.span, + val_span: r.span, + ord: ordering, + inc: inclusive, + }); } } else if let Some(id) = path_to_local(r) { if let Some((c, _)) = constant(cx, cx.typeck_results(), l) { - return Some((c, id, r.span, l.span, ordering.reverse(), inclusive)); + return Some(RangeBounds { + val: c, + expr: l, + id, + name_span: r.span, + val_span: l.span, + ord: ordering.reverse(), + inc: inclusive, + }); } } } diff --git a/clippy_utils/src/consts.rs b/clippy_utils/src/consts.rs index fdb822c3e5b..9785c0a75fb 100644 --- a/clippy_utils/src/consts.rs +++ b/clippy_utils/src/consts.rs @@ -130,12 +130,10 @@ impl Constant { match (left, right) { (&Self::Str(ref ls), &Self::Str(ref rs)) => Some(ls.cmp(rs)), (&Self::Char(ref l), &Self::Char(ref r)) => Some(l.cmp(r)), - (&Self::Int(l), &Self::Int(r)) => { - if let ty::Int(int_ty) = *cmp_type.kind() { - Some(sext(tcx, l, int_ty).cmp(&sext(tcx, r, int_ty))) - } else { - Some(l.cmp(&r)) - } + (&Self::Int(l), &Self::Int(r)) => match *cmp_type.kind() { + ty::Int(int_ty) => Some(sext(tcx, l, int_ty).cmp(&sext(tcx, r, int_ty))), + ty::Uint(_) => Some(l.cmp(&r)), + _ => bug!("Not an int type"), }, (&Self::F64(l), &Self::F64(r)) => l.partial_cmp(&r), (&Self::F32(l), &Self::F32(r)) => l.partial_cmp(&r), diff --git a/tests/ui/range_contains.fixed b/tests/ui/range_contains.fixed index 47c974e614b..f4977199711 100644 --- a/tests/ui/range_contains.fixed +++ b/tests/ui/range_contains.fixed @@ -6,7 +6,7 @@ #[allow(clippy::short_circuit_statement)] #[allow(clippy::unnecessary_operation)] fn main() { - let x = 9_u32; + let x = 9_i32; // order shouldn't matter (8..12).contains(&x); @@ -43,6 +43,12 @@ fn main() { let y = 3.; (0. ..1.).contains(&y); !(0. ..=1.).contains(&y); + + // handle negatives #8721 + (-10..=10).contains(&x); + x >= 10 && x <= -10; + (-3. ..=3.).contains(&y); + y >= 3. && y <= -3.; } // Fix #6373 diff --git a/tests/ui/range_contains.rs b/tests/ui/range_contains.rs index 835deced5e4..9e2180b0c99 100644 --- a/tests/ui/range_contains.rs +++ b/tests/ui/range_contains.rs @@ -6,7 +6,7 @@ #[allow(clippy::short_circuit_statement)] #[allow(clippy::unnecessary_operation)] fn main() { - let x = 9_u32; + let x = 9_i32; // order shouldn't matter x >= 8 && x < 12; @@ -43,6 +43,12 @@ fn main() { let y = 3.; y >= 0. && y < 1.; y < 0. || y > 1.; + + // handle negatives #8721 + x >= -10 && x <= 10; + x >= 10 && x <= -10; + y >= -3. && y <= 3.; + y >= 3. && y <= -3.; } // Fix #6373 diff --git a/tests/ui/range_contains.stderr b/tests/ui/range_contains.stderr index bc79f1bca84..1817ee1715d 100644 --- a/tests/ui/range_contains.stderr +++ b/tests/ui/range_contains.stderr @@ -84,5 +84,17 @@ error: manual `!RangeInclusive::contains` implementation LL | y < 0. || y > 1.; | ^^^^^^^^^^^^^^^^ help: use: `!(0. ..=1.).contains(&y)` -error: aborting due to 14 previous errors +error: manual `RangeInclusive::contains` implementation + --> $DIR/range_contains.rs:48:5 + | +LL | x >= -10 && x <= 10; + | ^^^^^^^^^^^^^^^^^^^ help: use: `(-10..=10).contains(&x)` + +error: manual `RangeInclusive::contains` implementation + --> $DIR/range_contains.rs:50:5 + | +LL | y >= -3. && y <= 3.; + | ^^^^^^^^^^^^^^^^^^^ help: use: `(-3. ..=3.).contains(&y)` + +error: aborting due to 16 previous errors