| use clippy_config::Conf; | |
| use clippy_utils::consts::{ConstEvalCtxt, Constant}; | |
| use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then}; | |
| use clippy_utils::msrvs::{self, Msrv}; | |
| use clippy_utils::res::MaybeResPath; | |
| use clippy_utils::source::{SpanRangeExt, snippet, snippet_with_applicability}; | |
| use clippy_utils::sugg::Sugg; | |
| use clippy_utils::ty::implements_trait; | |
| use clippy_utils::{expr_use_ctxt, fn_def_id, get_parent_expr, higher, is_in_const_context, is_integer_const, sym}; | |
| use rustc_ast::Mutability; | |
| use rustc_ast::ast::RangeLimits; | |
| use rustc_errors::Applicability; | |
| use rustc_hir::{BinOpKind, Expr, ExprKind, HirId, LangItem, Node}; | |
| use rustc_lint::{LateContext, LateLintPass, Lint}; | |
| use rustc_middle::ty::{self, ClauseKind, GenericArgKind, PredicatePolarity, Ty}; | |
| use rustc_session::impl_lint_pass; | |
| use rustc_span::{DesugaringKind, Span, Spanned}; | |
| use std::cmp::Ordering; | |
| declare_clippy_lint! { | |
| /// ### What it does | |
| /// Checks for expressions like `x >= 3 && x < 8` that could | |
| /// be more readably expressed as `(3..8).contains(x)`. | |
| /// | |
| /// ### Why is this bad? | |
| /// `contains` expresses the intent better and has less | |
| /// failure modes (such as fencepost errors or using `||` instead of `&&`). | |
| /// | |
| /// ### Example | |
| /// ```no_run | |
| /// // given | |
| /// let x = 6; | |
| /// | |
| /// assert!(x >= 3 && x < 8); | |
| /// ``` | |
| /// Use instead: | |
| /// ```no_run | |
| ///# let x = 6; | |
| /// assert!((3..8).contains(&x)); | |
| /// ``` | |
| #[clippy::version = "1.49.0"] | |
| pub MANUAL_RANGE_CONTAINS, | |
| style, | |
| "manually reimplementing {`Range`, `RangeInclusive`}`::contains`" | |
| } | |
| declare_clippy_lint! { | |
| /// ### What it does | |
| /// Checks for inclusive ranges where 1 is subtracted from | |
| /// the upper bound, e.g., `x..=(y-1)`. | |
| /// | |
| /// ### Why is this bad? | |
| /// The code is more readable with an exclusive range | |
| /// like `x..y`. | |
| /// | |
| /// ### Limitations | |
| /// The lint is conservative and will trigger only when switching | |
| /// from an inclusive to an exclusive range is provably safe from | |
| /// a typing point of view. This corresponds to situations where | |
| /// the range is used as an iterator, or for indexing. | |
| /// | |
| /// ### Example | |
| /// ```no_run | |
| /// # let x = 0; | |
| /// # let y = 1; | |
| /// for i in x..=(y-1) { | |
| /// // .. | |
| /// } | |
| /// ``` | |
| /// | |
| /// Use instead: | |
| /// ```no_run | |
| /// # let x = 0; | |
| /// # let y = 1; | |
| /// for i in x..y { | |
| /// // .. | |
| /// } | |
| /// ``` | |
| #[clippy::version = "pre 1.29.0"] | |
| pub RANGE_MINUS_ONE, | |
| pedantic, | |
| "`x..=(y-1)` reads better as `x..y`" | |
| } | |
| declare_clippy_lint! { | |
| /// ### What it does | |
| /// Checks for exclusive ranges where 1 is added to the | |
| /// upper bound, e.g., `x..(y+1)`. | |
| /// | |
| /// ### Why is this bad? | |
| /// The code is more readable with an inclusive range | |
| /// like `x..=y`. | |
| /// | |
| /// ### Limitations | |
| /// The lint is conservative and will trigger only when switching | |
| /// from an exclusive to an inclusive range is provably safe from | |
| /// a typing point of view. This corresponds to situations where | |
| /// the range is used as an iterator, or for indexing. | |
| /// | |
| /// ### Known problems | |
| /// Will add unnecessary pair of parentheses when the | |
| /// expression is not wrapped in a pair but starts with an opening parenthesis | |
| /// and ends with a closing one. | |
| /// I.e., `let _ = (f()+1)..(f()+1)` results in `let _ = ((f()+1)..=f())`. | |
| /// | |
| /// Also in many cases, inclusive ranges are still slower to run than | |
| /// exclusive ranges, because they essentially add an extra branch that | |
| /// LLVM may fail to hoist out of the loop. | |
| /// | |
| /// ### Example | |
| /// ```no_run | |
| /// # let x = 0; | |
| /// # let y = 1; | |
| /// for i in x..(y+1) { | |
| /// // .. | |
| /// } | |
| /// ``` | |
| /// | |
| /// Use instead: | |
| /// ```no_run | |
| /// # let x = 0; | |
| /// # let y = 1; | |
| /// for i in x..=y { | |
| /// // .. | |
| /// } | |
| /// ``` | |
| #[clippy::version = "pre 1.29.0"] | |
| pub RANGE_PLUS_ONE, | |
| pedantic, | |
| "`x..(y+1)` reads better as `x..=y`" | |
| } | |
| declare_clippy_lint! { | |
| /// ### What it does | |
| /// Checks for range expressions `x..y` where both `x` and `y` | |
| /// are constant and `x` is greater to `y`. Also triggers if `x` is equal to `y` when they are conditions to a `for` loop. | |
| /// | |
| /// ### Why is this bad? | |
| /// Empty ranges yield no values so iterating them is a no-op. | |
| /// Moreover, trying to use a reversed range to index a slice will panic at run-time. | |
| /// | |
| /// ### Example | |
| /// ```rust,no_run | |
| /// fn main() { | |
| /// (10..=0).for_each(|x| println!("{}", x)); | |
| /// | |
| /// let arr = [1, 2, 3, 4, 5]; | |
| /// let sub = &arr[3..1]; | |
| /// } | |
| /// ``` | |
| /// Use instead: | |
| /// ```no_run | |
| /// fn main() { | |
| /// (0..=10).rev().for_each(|x| println!("{}", x)); | |
| /// | |
| /// let arr = [1, 2, 3, 4, 5]; | |
| /// let sub = &arr[1..3]; | |
| /// } | |
| /// ``` | |
| #[clippy::version = "1.45.0"] | |
| pub REVERSED_EMPTY_RANGES, | |
| correctness, | |
| "reversing the limits of range expressions, resulting in empty ranges" | |
| } | |
| impl_lint_pass!(Ranges => [ | |
| MANUAL_RANGE_CONTAINS, | |
| RANGE_MINUS_ONE, | |
| RANGE_PLUS_ONE, | |
| REVERSED_EMPTY_RANGES, | |
| ]); | |
| pub struct Ranges { | |
| msrv: Msrv, | |
| } | |
| impl Ranges { | |
| pub fn new(conf: &'static Conf) -> Self { | |
| Self { msrv: conf.msrv } | |
| } | |
| } | |
| impl<'tcx> LateLintPass<'tcx> for Ranges { | |
| fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { | |
| if let ExprKind::Binary(ref op, l, r) = expr.kind | |
| && self.msrv.meets(cx, msrvs::RANGE_CONTAINS) | |
| { | |
| check_possible_range_contains(cx, op.node, l, r, expr, expr.span); | |
| } | |
| check_exclusive_range_plus_one(cx, expr); | |
| check_inclusive_range_minus_one(cx, expr); | |
| check_reversed_empty_range(cx, expr); | |
| } | |
| } | |
| fn check_possible_range_contains( | |
| cx: &LateContext<'_>, | |
| op: BinOpKind, | |
| left: &Expr<'_>, | |
| right: &Expr<'_>, | |
| expr: &Expr<'_>, | |
| span: Span, | |
| ) { | |
| if is_in_const_context(cx) { | |
| return; | |
| } | |
| let combine_and = match op { | |
| BinOpKind::And | BinOpKind::BitAnd => true, | |
| BinOpKind::Or | BinOpKind::BitOr => false, | |
| _ => return, | |
| }; | |
| // value, name, order (higher/lower), inclusiveness | |
| 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 l.id != r.id || l.ord == r.ord { | |
| return; | |
| } | |
| 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 r.ord == Ordering::Less { | |
| (l.val_span, r.val_span, l.inc, r.inc) | |
| } else { | |
| (r.val_span, l.val_span, r.inc, l.inc) | |
| }; | |
| // we only lint inclusive lower bounds | |
| if !l_inc { | |
| return; | |
| } | |
| let (range_type, range_op) = if u_inc { | |
| ("RangeInclusive", "..=") | |
| } else { | |
| ("Range", "..") | |
| }; | |
| let mut applicability = Applicability::MachineApplicable; | |
| 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 { "" }; | |
| span_lint_and_sugg( | |
| cx, | |
| MANUAL_RANGE_CONTAINS, | |
| span, | |
| format!("manual `{range_type}::contains` implementation"), | |
| "use", | |
| format!("({lo}{space}{range_op}{hi}).contains(&{name})"), | |
| applicability, | |
| ); | |
| } 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 l.ord == Ordering::Less { | |
| (l.val_span, r.val_span, l.inc, r.inc) | |
| } else { | |
| (r.val_span, l.val_span, r.inc, l.inc) | |
| }; | |
| if l_inc { | |
| return; | |
| } | |
| let (range_type, range_op) = if u_inc { | |
| ("Range", "..") | |
| } else { | |
| ("RangeInclusive", "..=") | |
| }; | |
| let mut applicability = Applicability::MachineApplicable; | |
| 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 { "" }; | |
| span_lint_and_sugg( | |
| cx, | |
| MANUAL_RANGE_CONTAINS, | |
| span, | |
| format!("manual `!{range_type}::contains` implementation"), | |
| "use", | |
| format!("!({lo}{space}{range_op}{hi}).contains(&{name})"), | |
| applicability, | |
| ); | |
| } | |
| } | |
| // If the LHS is the same operator, we have to recurse to get the "real" RHS, since they have | |
| // the same operator precedence | |
| if let ExprKind::Binary(ref lhs_op, _left, new_lhs) = left.kind | |
| && op == lhs_op.node | |
| && let new_span = Span::new(new_lhs.span.lo(), right.span.hi(), expr.span.ctxt(), expr.span.parent()) | |
| && new_span.check_source_text(cx, |src| { | |
| // Do not continue if we have mismatched number of parens, otherwise the suggestion is wrong | |
| src.matches('(').count() == src.matches(')').count() | |
| }) | |
| { | |
| check_possible_range_contains(cx, op, new_lhs, right, expr, new_span); | |
| } | |
| } | |
| 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<RangeBounds<'a>> { | |
| if let ExprKind::Binary(ref op, l, r) = ex.kind { | |
| let (inclusive, ordering) = match op.node { | |
| BinOpKind::Gt => (false, Ordering::Greater), | |
| BinOpKind::Ge => (true, Ordering::Greater), | |
| BinOpKind::Lt => (false, Ordering::Less), | |
| BinOpKind::Le => (true, Ordering::Less), | |
| _ => return None, | |
| }; | |
| if let Some(id) = l.res_local_id() { | |
| if let Some(c) = ConstEvalCtxt::new(cx).eval(r) { | |
| 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) = r.res_local_id() | |
| && let Some(c) = ConstEvalCtxt::new(cx).eval(l) | |
| { | |
| return Some(RangeBounds { | |
| val: c, | |
| expr: l, | |
| id, | |
| name_span: r.span, | |
| val_span: l.span, | |
| ord: ordering.reverse(), | |
| inc: inclusive, | |
| }); | |
| } | |
| } | |
| None | |
| } | |
| /// Check whether `expr` could switch range types without breaking the typing requirements. This is | |
| /// generally the case when `expr` is used as an iterator for example, or as a slice or `&str` | |
| /// index. | |
| /// | |
| /// FIXME: Note that the current implementation may still return false positives. A proper fix would | |
| /// check that the obligations are still satisfied after switching the range type. | |
| fn can_switch_ranges<'tcx>( | |
| cx: &LateContext<'tcx>, | |
| expr: &'tcx Expr<'_>, | |
| original: RangeLimits, | |
| inner_ty: Ty<'tcx>, | |
| ) -> bool { | |
| let use_ctxt = expr_use_ctxt(cx, expr); | |
| let (Node::Expr(parent_expr), false) = (use_ctxt.node, use_ctxt.is_ty_unified) else { | |
| return false; | |
| }; | |
| // Check if `expr` is the argument of a compiler-generated `IntoIter::into_iter(expr)` | |
| if let ExprKind::Call(func, [arg]) = parent_expr.kind | |
| && arg.hir_id == use_ctxt.child_id | |
| && let ExprKind::Path(qpath) = func.kind | |
| && cx.tcx.qpath_is_lang_item(qpath, LangItem::IntoIterIntoIter) | |
| && parent_expr.span.is_desugaring(DesugaringKind::ForLoop) | |
| { | |
| return true; | |
| } | |
| // Check if `expr` is used as the receiver of a method of the `Iterator`, `IntoIterator`, | |
| // or `RangeBounds` traits. | |
| if let ExprKind::MethodCall(_, receiver, _, _) = parent_expr.kind | |
| && receiver.hir_id == use_ctxt.child_id | |
| && let Some(method_did) = cx.typeck_results().type_dependent_def_id(parent_expr.hir_id) | |
| && let Some(trait_did) = cx.tcx.trait_of_assoc(method_did) | |
| && matches!( | |
| cx.tcx.get_diagnostic_name(trait_did), | |
| Some(sym::Iterator | sym::IntoIterator | sym::RangeBounds) | |
| ) | |
| { | |
| return true; | |
| } | |
| // Check if `expr` is an argument of a call which requires an `Iterator`, `IntoIterator`, | |
| // or `RangeBounds` trait. | |
| if let ExprKind::Call(_, args) | ExprKind::MethodCall(_, _, args, _) = parent_expr.kind | |
| && let Some(id) = fn_def_id(cx, parent_expr) | |
| && let Some(arg_idx) = args.iter().position(|e| e.hir_id == use_ctxt.child_id) | |
| { | |
| let input_idx = if matches!(parent_expr.kind, ExprKind::MethodCall(..)) { | |
| arg_idx + 1 | |
| } else { | |
| arg_idx | |
| }; | |
| let inputs = cx | |
| .tcx | |
| .liberate_late_bound_regions(id, cx.tcx.fn_sig(id).instantiate_identity()) | |
| .inputs(); | |
| let expr_ty = inputs[input_idx]; | |
| // Check that the `expr` type is present only once, otherwise modifying just one of them might be | |
| // risky if they are referenced using the same generic type for example. | |
| if inputs.iter().enumerate().all(|(n, ty)| | |
| n == input_idx | |
| || !ty.walk().any(|arg| matches!(arg.kind(), | |
| GenericArgKind::Type(ty) if ty == expr_ty))) | |
| // Look for a clause requiring `Iterator`, `IntoIterator`, or `RangeBounds`, and resolving to `expr_type`. | |
| && cx | |
| .tcx | |
| .param_env(id) | |
| .caller_bounds() | |
| .into_iter() | |
| .any(|p| { | |
| if let ClauseKind::Trait(t) = p.kind().skip_binder() | |
| && t.polarity == PredicatePolarity::Positive | |
| && matches!( | |
| cx.tcx.get_diagnostic_name(t.trait_ref.def_id), | |
| Some(sym::Iterator | sym::IntoIterator | sym::RangeBounds) | |
| ) | |
| { | |
| t.self_ty() == expr_ty | |
| } else { | |
| false | |
| } | |
| }) | |
| { | |
| return true; | |
| } | |
| } | |
| // Check if `expr` is used for indexing, and if the switched range type could be used | |
| // as well. | |
| if let ExprKind::Index(outer_expr, index, _) = parent_expr.kind | |
| && index.hir_id == expr.hir_id | |
| // Build the switched range type (for example `RangeInclusive<usize>`). | |
| && let Some(switched_range_def_id) = match original { | |
| RangeLimits::HalfOpen => cx.tcx.lang_items().range_inclusive_struct(), | |
| RangeLimits::Closed => cx.tcx.lang_items().range_struct(), | |
| } | |
| && let switched_range_ty = cx | |
| .tcx | |
| .type_of(switched_range_def_id) | |
| .instantiate(cx.tcx, &[inner_ty.into()]) | |
| // Check that the switched range type can be used for indexing the original expression | |
| // through the `Index` or `IndexMut` trait. | |
| && let ty::Ref(_, outer_ty, mutability) = cx.typeck_results().expr_ty_adjusted(outer_expr).kind() | |
| && let Some(index_def_id) = match mutability { | |
| Mutability::Not => cx.tcx.lang_items().index_trait(), | |
| Mutability::Mut => cx.tcx.lang_items().index_mut_trait(), | |
| } | |
| && implements_trait(cx, *outer_ty, index_def_id, &[switched_range_ty.into()]) | |
| // We could also check that the associated item of the `index_def_id` trait with the switched range type | |
| // return the same type, but it is reasonable to expect so. We can't check that the result is identical | |
| // in both `Index<Range<…>>` and `Index<RangeInclusive<…>>` anyway. | |
| { | |
| return true; | |
| } | |
| false | |
| } | |
| // exclusive range plus one: `x..(y+1)` | |
| fn check_exclusive_range_plus_one<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { | |
| check_range_switch( | |
| cx, | |
| expr, | |
| RangeLimits::HalfOpen, | |
| y_plus_one, | |
| RANGE_PLUS_ONE, | |
| "an inclusive range would be more readable", | |
| "..=", | |
| ); | |
| } | |
| // inclusive range minus one: `x..=(y-1)` | |
| fn check_inclusive_range_minus_one<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { | |
| check_range_switch( | |
| cx, | |
| expr, | |
| RangeLimits::Closed, | |
| y_minus_one, | |
| RANGE_MINUS_ONE, | |
| "an exclusive range would be more readable", | |
| "..", | |
| ); | |
| } | |
| /// Check for a `kind` of range in `expr`, check for `predicate` on the end, | |
| /// and emit the `lint` with `msg` and the `operator`. | |
| fn check_range_switch<'tcx>( | |
| cx: &LateContext<'tcx>, | |
| expr: &'tcx Expr<'_>, | |
| kind: RangeLimits, | |
| predicate: impl for<'hir> FnOnce(&LateContext<'_>, &Expr<'hir>) -> Option<&'hir Expr<'hir>>, | |
| lint: &'static Lint, | |
| msg: &'static str, | |
| operator: &str, | |
| ) { | |
| if let Some(range) = higher::Range::hir(cx, expr) | |
| && let higher::Range { | |
| start, | |
| end: Some(end), | |
| limits, | |
| span, | |
| } = range | |
| && span.can_be_used_for_suggestions() | |
| && limits == kind | |
| && let Some(y) = predicate(cx, end) | |
| && can_switch_ranges(cx, expr, kind, cx.typeck_results().expr_ty(y)) | |
| { | |
| span_lint_and_then(cx, lint, span, msg, |diag| { | |
| let mut app = Applicability::MachineApplicable; | |
| let start = start.map_or(String::new(), |x| { | |
| Sugg::hir_with_applicability(cx, x, "<x>", &mut app) | |
| .maybe_paren() | |
| .to_string() | |
| }); | |
| let end = Sugg::hir_with_applicability(cx, y, "<y>", &mut app).maybe_paren(); | |
| match span.with_source_text(cx, |src| src.starts_with('(') && src.ends_with(')')) { | |
| Some(true) => { | |
| diag.span_suggestion(span, "use", format!("({start}{operator}{end})"), app); | |
| }, | |
| Some(false) => { | |
| diag.span_suggestion(span, "use", format!("{start}{operator}{end}"), app); | |
| }, | |
| None => {}, | |
| } | |
| }); | |
| } | |
| } | |
| fn check_reversed_empty_range(cx: &LateContext<'_>, expr: &Expr<'_>) { | |
| fn inside_indexing_expr(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { | |
| matches!( | |
| get_parent_expr(cx, expr), | |
| Some(Expr { | |
| kind: ExprKind::Index(..), | |
| .. | |
| }) | |
| ) | |
| } | |
| fn is_for_loop_arg(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { | |
| let mut cur_expr = expr; | |
| while let Some(parent_expr) = get_parent_expr(cx, cur_expr) { | |
| match higher::ForLoop::hir(parent_expr) { | |
| Some(higher::ForLoop { arg, .. }) if arg.hir_id == expr.hir_id => return true, | |
| _ => cur_expr = parent_expr, | |
| } | |
| } | |
| false | |
| } | |
| fn is_empty_range(limits: RangeLimits, ordering: Ordering) -> bool { | |
| match limits { | |
| RangeLimits::HalfOpen => ordering != Ordering::Less, | |
| RangeLimits::Closed => ordering == Ordering::Greater, | |
| } | |
| } | |
| if let Some(higher::Range { | |
| start: Some(start), | |
| end: Some(end), | |
| limits, | |
| span, | |
| }) = higher::Range::hir(cx, expr) | |
| && let ty = cx.typeck_results().expr_ty(start) | |
| && let ty::Int(_) | ty::Uint(_) = ty.kind() | |
| && let ecx = ConstEvalCtxt::new(cx) | |
| && let Some(start_idx) = ecx.eval(start) | |
| && let Some(end_idx) = ecx.eval(end) | |
| && let Some(ordering) = Constant::partial_cmp(cx.tcx, ty, &start_idx, &end_idx) | |
| && is_empty_range(limits, ordering) | |
| { | |
| if inside_indexing_expr(cx, expr) { | |
| // Avoid linting `N..N` as it has proven to be useful, see #5689 and #5628 ... | |
| if ordering != Ordering::Equal { | |
| span_lint( | |
| cx, | |
| REVERSED_EMPTY_RANGES, | |
| span, | |
| "this range is reversed and using it to index a slice will panic at run-time", | |
| ); | |
| } | |
| // ... except in for loop arguments for backwards compatibility with `reverse_range_loop` | |
| } else if ordering != Ordering::Equal || is_for_loop_arg(cx, expr) { | |
| span_lint_and_then( | |
| cx, | |
| REVERSED_EMPTY_RANGES, | |
| span, | |
| "this range is empty so it will yield no values", | |
| |diag| { | |
| if ordering != Ordering::Equal { | |
| let start_snippet = snippet(cx, start.span, "_"); | |
| let end_snippet = snippet(cx, end.span, "_"); | |
| let dots = match limits { | |
| RangeLimits::HalfOpen => "..", | |
| RangeLimits::Closed => "..=", | |
| }; | |
| diag.span_suggestion( | |
| span, | |
| "consider using the following if you are attempting to iterate over this \ | |
| range in reverse", | |
| format!("({end_snippet}{dots}{start_snippet}).rev()"), | |
| Applicability::MaybeIncorrect, | |
| ); | |
| } | |
| }, | |
| ); | |
| } | |
| } | |
| } | |
| fn y_plus_one<'tcx>(cx: &LateContext<'_>, expr: &Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> { | |
| match expr.kind { | |
| ExprKind::Binary( | |
| Spanned { | |
| node: BinOpKind::Add, .. | |
| }, | |
| lhs, | |
| rhs, | |
| ) => { | |
| if is_integer_const(cx, lhs, 1) { | |
| Some(rhs) | |
| } else if is_integer_const(cx, rhs, 1) { | |
| Some(lhs) | |
| } else { | |
| None | |
| } | |
| }, | |
| _ => None, | |
| } | |
| } | |
| fn y_minus_one<'tcx>(cx: &LateContext<'_>, expr: &Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> { | |
| match expr.kind { | |
| ExprKind::Binary( | |
| Spanned { | |
| node: BinOpKind::Sub, .. | |
| }, | |
| lhs, | |
| rhs, | |
| ) if is_integer_const(cx, rhs, 1) => Some(lhs), | |
| _ => None, | |
| } | |
| } |