]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/loops/mut_range_bound.rs
Rollup merge of #90741 - mbartlett21:patch-4, r=dtolnay
[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, NestedVisitorMap, Visitor};
6 use rustc_hir::{BindingAnnotation, Expr, ExprKind, HirId, Node, PatKind};
7 use rustc_infer::infer::TyCtxtInferExt;
8 use rustc_lint::LateContext;
9 use rustc_middle::hir::map::Map;
10 use rustc_middle::{mir::FakeReadCause, ty};
11 use rustc_span::source_map::Span;
12 use rustc_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId};
13
14 pub(super) fn check(cx: &LateContext<'_>, arg: &Expr<'_>, body: &Expr<'_>) {
15     if_chain! {
16         if let Some(higher::Range {
17             start: Some(start),
18             end: Some(end),
19             ..
20         }) = higher::Range::hir(arg);
21         let (mut_id_start, mut_id_end) = (check_for_mutability(cx, start), check_for_mutability(cx, end));
22         if mut_id_start.is_some() || mut_id_end.is_some();
23         then {
24             let (span_low, span_high) = check_for_mutation(cx, body, mut_id_start, mut_id_end);
25             mut_warn_with_span(cx, span_low);
26             mut_warn_with_span(cx, span_high);
27         }
28     }
29 }
30
31 fn mut_warn_with_span(cx: &LateContext<'_>, span: Option<Span>) {
32     if let Some(sp) = span {
33         span_lint_and_note(
34             cx,
35             MUT_RANGE_BOUND,
36             sp,
37             "attempt to mutate range bound within loop",
38             None,
39             "the range of the loop is unchanged",
40         );
41     }
42 }
43
44 fn check_for_mutability(cx: &LateContext<'_>, bound: &Expr<'_>) -> Option<HirId> {
45     if_chain! {
46         if let Some(hir_id) = path_to_local(bound);
47         if let Node::Binding(pat) = cx.tcx.hir().get(hir_id);
48         if let PatKind::Binding(BindingAnnotation::Mutable, ..) = pat.kind;
49         then {
50             return Some(hir_id);
51         }
52     }
53     None
54 }
55
56 fn check_for_mutation<'tcx>(
57     cx: &LateContext<'tcx>,
58     body: &Expr<'_>,
59     bound_id_start: Option<HirId>,
60     bound_id_end: Option<HirId>,
61 ) -> (Option<Span>, Option<Span>) {
62     let mut delegate = MutatePairDelegate {
63         cx,
64         hir_id_low: bound_id_start,
65         hir_id_high: bound_id_end,
66         span_low: None,
67         span_high: None,
68     };
69     cx.tcx.infer_ctxt().enter(|infcx| {
70         ExprUseVisitor::new(
71             &mut delegate,
72             &infcx,
73             body.hir_id.owner,
74             cx.param_env,
75             cx.typeck_results(),
76         )
77         .walk_expr(body);
78     });
79
80     delegate.mutation_span()
81 }
82
83 struct MutatePairDelegate<'a, 'tcx> {
84     cx: &'a LateContext<'tcx>,
85     hir_id_low: Option<HirId>,
86     hir_id_high: Option<HirId>,
87     span_low: Option<Span>,
88     span_high: Option<Span>,
89 }
90
91 impl<'tcx> Delegate<'tcx> for MutatePairDelegate<'_, 'tcx> {
92     fn consume(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {}
93
94     fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, diag_expr_id: HirId, bk: ty::BorrowKind) {
95         if bk == ty::BorrowKind::MutBorrow {
96             if let PlaceBase::Local(id) = cmt.place.base {
97                 if Some(id) == self.hir_id_low && !BreakAfterExprVisitor::is_found(self.cx, diag_expr_id) {
98                     self.span_low = Some(self.cx.tcx.hir().span(diag_expr_id));
99                 }
100                 if Some(id) == self.hir_id_high && !BreakAfterExprVisitor::is_found(self.cx, diag_expr_id) {
101                     self.span_high = Some(self.cx.tcx.hir().span(diag_expr_id));
102                 }
103             }
104         }
105     }
106
107     fn mutate(&mut self, cmt: &PlaceWithHirId<'tcx>, diag_expr_id: HirId) {
108         if let PlaceBase::Local(id) = cmt.place.base {
109             if Some(id) == self.hir_id_low && !BreakAfterExprVisitor::is_found(self.cx, diag_expr_id) {
110                 self.span_low = Some(self.cx.tcx.hir().span(diag_expr_id));
111             }
112             if Some(id) == self.hir_id_high && !BreakAfterExprVisitor::is_found(self.cx, diag_expr_id) {
113                 self.span_high = Some(self.cx.tcx.hir().span(diag_expr_id));
114             }
115         }
116     }
117
118     fn fake_read(&mut self, _: rustc_typeck::expr_use_visitor::Place<'tcx>, _: FakeReadCause, _: HirId) {}
119 }
120
121 impl MutatePairDelegate<'_, '_> {
122     fn mutation_span(&self) -> (Option<Span>, Option<Span>) {
123         (self.span_low, self.span_high)
124     }
125 }
126
127 struct BreakAfterExprVisitor {
128     hir_id: HirId,
129     past_expr: bool,
130     past_candidate: bool,
131     break_after_expr: bool,
132 }
133
134 impl BreakAfterExprVisitor {
135     pub fn is_found(cx: &LateContext<'_>, hir_id: HirId) -> bool {
136         let mut visitor = BreakAfterExprVisitor {
137             hir_id,
138             past_expr: false,
139             past_candidate: false,
140             break_after_expr: false,
141         };
142
143         get_enclosing_block(cx, hir_id).map_or(false, |block| {
144             visitor.visit_block(block);
145             visitor.break_after_expr
146         })
147     }
148 }
149
150 impl intravisit::Visitor<'tcx> for BreakAfterExprVisitor {
151     type Map = Map<'tcx>;
152
153     fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
154         NestedVisitorMap::None
155     }
156
157     fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
158         if self.past_candidate {
159             return;
160         }
161
162         if expr.hir_id == self.hir_id {
163             self.past_expr = true;
164         } else if self.past_expr {
165             if matches!(&expr.kind, ExprKind::Break(..)) {
166                 self.break_after_expr = true;
167             }
168
169             self.past_candidate = true;
170         } else {
171             intravisit::walk_expr(self, expr);
172         }
173     }
174 }