| 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) | |
| } | |
| } | |
| } |