1 use super::MUT_RANGE_BOUND;
2 use clippy_utils::diagnostics::span_lint_and_note;
3 use clippy_utils::{get_enclosing_block, higher, path_to_local};
4 use if_chain::if_chain;
5 use rustc_hir::intravisit::{self, Visitor};
6 use rustc_hir::{BindingAnnotation, Expr, ExprKind, HirId, Node, PatKind};
7 use rustc_hir_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId};
8 use rustc_infer::infer::TyCtxtInferExt;
9 use rustc_lint::LateContext;
10 use rustc_middle::{mir::FakeReadCause, ty};
11 use rustc_span::source_map::Span;
13 pub(super) fn check(cx: &LateContext<'_>, arg: &Expr<'_>, body: &Expr<'_>) {
15 if let Some(higher::Range {
19 }) = higher::Range::hir(arg);
20 let (mut_id_start, mut_id_end) = (check_for_mutability(cx, start), check_for_mutability(cx, end));
21 if mut_id_start.is_some() || mut_id_end.is_some();
23 let (span_low, span_high) = check_for_mutation(cx, body, mut_id_start, mut_id_end);
24 mut_warn_with_span(cx, span_low);
25 mut_warn_with_span(cx, span_high);
30 fn mut_warn_with_span(cx: &LateContext<'_>, span: Option<Span>) {
31 if let Some(sp) = span {
36 "attempt to mutate range bound within loop",
38 "the range of the loop is unchanged",
43 fn check_for_mutability(cx: &LateContext<'_>, bound: &Expr<'_>) -> Option<HirId> {
45 if let Some(hir_id) = path_to_local(bound);
46 if let Node::Pat(pat) = cx.tcx.hir().get(hir_id);
47 if let PatKind::Binding(BindingAnnotation::MUT, ..) = pat.kind;
55 fn check_for_mutation(
58 bound_id_start: Option<HirId>,
59 bound_id_end: Option<HirId>,
60 ) -> (Option<Span>, Option<Span>) {
61 let mut delegate = MutatePairDelegate {
63 hir_id_low: bound_id_start,
64 hir_id_high: bound_id_end,
68 let infcx = cx.tcx.infer_ctxt().build();
72 body.hir_id.owner.def_id,
78 delegate.mutation_span()
81 struct MutatePairDelegate<'a, 'tcx> {
82 cx: &'a LateContext<'tcx>,
83 hir_id_low: Option<HirId>,
84 hir_id_high: Option<HirId>,
85 span_low: Option<Span>,
86 span_high: Option<Span>,
89 impl<'tcx> Delegate<'tcx> for MutatePairDelegate<'_, 'tcx> {
90 fn consume(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {}
92 fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, diag_expr_id: HirId, bk: ty::BorrowKind) {
93 if bk == ty::BorrowKind::MutBorrow {
94 if let PlaceBase::Local(id) = cmt.place.base {
95 if Some(id) == self.hir_id_low && !BreakAfterExprVisitor::is_found(self.cx, diag_expr_id) {
96 self.span_low = Some(self.cx.tcx.hir().span(diag_expr_id));
98 if Some(id) == self.hir_id_high && !BreakAfterExprVisitor::is_found(self.cx, diag_expr_id) {
99 self.span_high = Some(self.cx.tcx.hir().span(diag_expr_id));
105 fn mutate(&mut self, cmt: &PlaceWithHirId<'tcx>, diag_expr_id: HirId) {
106 if let PlaceBase::Local(id) = cmt.place.base {
107 if Some(id) == self.hir_id_low && !BreakAfterExprVisitor::is_found(self.cx, diag_expr_id) {
108 self.span_low = Some(self.cx.tcx.hir().span(diag_expr_id));
110 if Some(id) == self.hir_id_high && !BreakAfterExprVisitor::is_found(self.cx, diag_expr_id) {
111 self.span_high = Some(self.cx.tcx.hir().span(diag_expr_id));
116 fn fake_read(&mut self, _: &rustc_hir_typeck::expr_use_visitor::PlaceWithHirId<'tcx>, _: FakeReadCause, _: HirId) {}
119 impl MutatePairDelegate<'_, '_> {
120 fn mutation_span(&self) -> (Option<Span>, Option<Span>) {
121 (self.span_low, self.span_high)
125 struct BreakAfterExprVisitor {
128 past_candidate: bool,
129 break_after_expr: bool,
132 impl BreakAfterExprVisitor {
133 pub fn is_found(cx: &LateContext<'_>, hir_id: HirId) -> bool {
134 let mut visitor = BreakAfterExprVisitor {
137 past_candidate: false,
138 break_after_expr: false,
141 get_enclosing_block(cx, hir_id).map_or(false, |block| {
142 visitor.visit_block(block);
143 visitor.break_after_expr
148 impl<'tcx> intravisit::Visitor<'tcx> for BreakAfterExprVisitor {
149 fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
150 if self.past_candidate {
154 if expr.hir_id == self.hir_id {
155 self.past_expr = true;
156 } else if self.past_expr {
157 if matches!(&expr.kind, ExprKind::Break(..)) {
158 self.break_after_expr = true;
161 self.past_candidate = true;
163 intravisit::walk_expr(self, expr);