]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/loops/mut_range_bound.rs
Rollup merge of #99244 - gthb:doc-improve-iterator-scan, r=m-ou-se
[rust.git] / src / tools / clippy / clippy_lints / src / loops / mut_range_bound.rs
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;
12
13 pub(super) fn check(cx: &LateContext<'_>, arg: &Expr<'_>, body: &Expr<'_>) {
14     if_chain! {
15         if let Some(higher::Range {
16             start: Some(start),
17             end: Some(end),
18             ..
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();
22         then {
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);
26         }
27     }
28 }
29
30 fn mut_warn_with_span(cx: &LateContext<'_>, span: Option<Span>) {
31     if let Some(sp) = span {
32         span_lint_and_note(
33             cx,
34             MUT_RANGE_BOUND,
35             sp,
36             "attempt to mutate range bound within loop",
37             None,
38             "the range of the loop is unchanged",
39         );
40     }
41 }
42
43 fn check_for_mutability(cx: &LateContext<'_>, bound: &Expr<'_>) -> Option<HirId> {
44     if_chain! {
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;
48         then {
49             return Some(hir_id);
50         }
51     }
52     None
53 }
54
55 fn check_for_mutation(
56     cx: &LateContext<'_>,
57     body: &Expr<'_>,
58     bound_id_start: Option<HirId>,
59     bound_id_end: Option<HirId>,
60 ) -> (Option<Span>, Option<Span>) {
61     let mut delegate = MutatePairDelegate {
62         cx,
63         hir_id_low: bound_id_start,
64         hir_id_high: bound_id_end,
65         span_low: None,
66         span_high: None,
67     };
68     let infcx = cx.tcx.infer_ctxt().build();
69     ExprUseVisitor::new(
70         &mut delegate,
71         &infcx,
72         body.hir_id.owner.def_id,
73         cx.param_env,
74         cx.typeck_results(),
75     )
76     .walk_expr(body);
77
78     delegate.mutation_span()
79 }
80
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>,
87 }
88
89 impl<'tcx> Delegate<'tcx> for MutatePairDelegate<'_, 'tcx> {
90     fn consume(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {}
91
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));
97                 }
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));
100                 }
101             }
102         }
103     }
104
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));
109             }
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));
112             }
113         }
114     }
115
116     fn fake_read(&mut self, _: &rustc_hir_typeck::expr_use_visitor::PlaceWithHirId<'tcx>, _: FakeReadCause, _: HirId) {}
117 }
118
119 impl MutatePairDelegate<'_, '_> {
120     fn mutation_span(&self) -> (Option<Span>, Option<Span>) {
121         (self.span_low, self.span_high)
122     }
123 }
124
125 struct BreakAfterExprVisitor {
126     hir_id: HirId,
127     past_expr: bool,
128     past_candidate: bool,
129     break_after_expr: bool,
130 }
131
132 impl BreakAfterExprVisitor {
133     pub fn is_found(cx: &LateContext<'_>, hir_id: HirId) -> bool {
134         let mut visitor = BreakAfterExprVisitor {
135             hir_id,
136             past_expr: false,
137             past_candidate: false,
138             break_after_expr: false,
139         };
140
141         get_enclosing_block(cx, hir_id).map_or(false, |block| {
142             visitor.visit_block(block);
143             visitor.break_after_expr
144         })
145     }
146 }
147
148 impl<'tcx> intravisit::Visitor<'tcx> for BreakAfterExprVisitor {
149     fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
150         if self.past_candidate {
151             return;
152         }
153
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;
159             }
160
161             self.past_candidate = true;
162         } else {
163             intravisit::walk_expr(self, expr);
164         }
165     }
166 }