| use super::MUT_RANGE_BOUND; |
| use clippy_utils::diagnostics::span_lint_and_note; |
| use clippy_utils::res::MaybeResPath; |
| use clippy_utils::{get_enclosing_block, higher}; |
| use rustc_hir::intravisit::{self, Visitor}; |
| use rustc_hir::{BindingMode, Expr, ExprKind, HirId, Node, PatKind}; |
| use rustc_hir_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId}; |
| use rustc_lint::LateContext; |
| use rustc_middle::mir::FakeReadCause; |
| use rustc_middle::ty; |
| use rustc_span::Span; |
| use std::ops::ControlFlow; |
| |
| pub(super) fn check(cx: &LateContext<'_>, arg: &Expr<'_>, body: &Expr<'_>) { |
| if let Some(higher::Range { |
| start: Some(start), |
| end: Some(end), |
| .. |
| }) = higher::Range::hir(cx, arg) |
| && let (mut_id_start, mut_id_end) = (check_for_mutability(cx, start), check_for_mutability(cx, end)) |
| && (mut_id_start.is_some() || mut_id_end.is_some()) |
| { |
| let (span_low, span_high) = check_for_mutation(cx, body, mut_id_start, mut_id_end); |
| mut_warn_with_span(cx, span_low); |
| mut_warn_with_span(cx, span_high); |
| } |
| } |
| |
| fn mut_warn_with_span(cx: &LateContext<'_>, span: Option<Span>) { |
| if let Some(sp) = span { |
| span_lint_and_note( |
| cx, |
| MUT_RANGE_BOUND, |
| sp, |
| "attempt to mutate range bound within loop", |
| None, |
| "the range of the loop is unchanged", |
| ); |
| } |
| } |
| |
| fn check_for_mutability(cx: &LateContext<'_>, bound: &Expr<'_>) -> Option<HirId> { |
| if let Some(hir_id) = bound.res_local_id() |
| && let Node::Pat(pat) = cx.tcx.hir_node(hir_id) |
| && let PatKind::Binding(BindingMode::MUT, ..) = pat.kind |
| { |
| return Some(hir_id); |
| } |
| None |
| } |
| |
| fn check_for_mutation( |
| cx: &LateContext<'_>, |
| body: &Expr<'_>, |
| bound_id_start: Option<HirId>, |
| bound_id_end: Option<HirId>, |
| ) -> (Option<Span>, Option<Span>) { |
| let mut delegate = MutatePairDelegate { |
| cx, |
| hir_id_low: bound_id_start, |
| hir_id_high: bound_id_end, |
| span_low: None, |
| span_high: None, |
| }; |
| ExprUseVisitor::for_clippy(cx, body.hir_id.owner.def_id, &mut delegate) |
| .walk_expr(body) |
| .into_ok(); |
| |
| delegate.mutation_span() |
| } |
| |
| struct MutatePairDelegate<'a, 'tcx> { |
| cx: &'a LateContext<'tcx>, |
| hir_id_low: Option<HirId>, |
| hir_id_high: Option<HirId>, |
| span_low: Option<Span>, |
| span_high: Option<Span>, |
| } |
| |
| impl<'tcx> Delegate<'tcx> for MutatePairDelegate<'_, 'tcx> { |
| fn consume(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {} |
| |
| fn use_cloned(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {} |
| |
| fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, diag_expr_id: HirId, bk: ty::BorrowKind) { |
| if bk == ty::BorrowKind::Mutable |
| && let PlaceBase::Local(id) = cmt.place.base |
| { |
| if Some(id) == self.hir_id_low && !BreakAfterExprVisitor::is_found(self.cx, diag_expr_id) { |
| self.span_low = Some(self.cx.tcx.hir_span(diag_expr_id)); |
| } |
| if Some(id) == self.hir_id_high && !BreakAfterExprVisitor::is_found(self.cx, diag_expr_id) { |
| self.span_high = Some(self.cx.tcx.hir_span(diag_expr_id)); |
| } |
| } |
| } |
| |
| fn mutate(&mut self, cmt: &PlaceWithHirId<'tcx>, diag_expr_id: HirId) { |
| if let PlaceBase::Local(id) = cmt.place.base { |
| if Some(id) == self.hir_id_low && !BreakAfterExprVisitor::is_found(self.cx, diag_expr_id) { |
| self.span_low = Some(self.cx.tcx.hir_span(diag_expr_id)); |
| } |
| if Some(id) == self.hir_id_high && !BreakAfterExprVisitor::is_found(self.cx, diag_expr_id) { |
| self.span_high = Some(self.cx.tcx.hir_span(diag_expr_id)); |
| } |
| } |
| } |
| |
| fn fake_read(&mut self, _: &PlaceWithHirId<'tcx>, _: FakeReadCause, _: HirId) {} |
| } |
| |
| impl MutatePairDelegate<'_, '_> { |
| fn mutation_span(&self) -> (Option<Span>, Option<Span>) { |
| (self.span_low, self.span_high) |
| } |
| } |
| |
| struct BreakAfterExprVisitor { |
| hir_id: HirId, |
| past_expr: bool, |
| break_after_expr: bool, |
| } |
| |
| impl BreakAfterExprVisitor { |
| pub fn is_found(cx: &LateContext<'_>, hir_id: HirId) -> bool { |
| let mut visitor = BreakAfterExprVisitor { |
| hir_id, |
| past_expr: false, |
| break_after_expr: false, |
| }; |
| |
| get_enclosing_block(cx, hir_id).is_some_and(|block| { |
| let _ = visitor.visit_block(block); |
| visitor.break_after_expr |
| }) |
| } |
| } |
| |
| impl<'tcx> Visitor<'tcx> for BreakAfterExprVisitor { |
| type Result = ControlFlow<()>; |
| fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) -> ControlFlow<()> { |
| if expr.hir_id == self.hir_id { |
| self.past_expr = true; |
| ControlFlow::Continue(()) |
| } else if self.past_expr { |
| if matches!(&expr.kind, ExprKind::Break(..)) { |
| self.break_after_expr = true; |
| } |
| |
| ControlFlow::Break(()) |
| } else { |
| intravisit::walk_expr(self, expr) |
| } |
| } |
| } |