]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/loops/mut_range_bound.rs
Auto merge of #7962 - Jarcho:fix_match_type_on_diagnostic_items, r=llogiq
[rust.git] / 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_analysis::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<'tcx>(
56     cx: &LateContext<'tcx>,
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     cx.tcx.infer_ctxt().enter(|infcx| {
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
79     delegate.mutation_span()
80 }
81
82 struct MutatePairDelegate<'a, 'tcx> {
83     cx: &'a LateContext<'tcx>,
84     hir_id_low: Option<HirId>,
85     hir_id_high: Option<HirId>,
86     span_low: Option<Span>,
87     span_high: Option<Span>,
88 }
89
90 impl<'tcx> Delegate<'tcx> for MutatePairDelegate<'_, 'tcx> {
91     fn consume(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {}
92
93     fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, diag_expr_id: HirId, bk: ty::BorrowKind) {
94         if bk == ty::BorrowKind::MutBorrow {
95             if let PlaceBase::Local(id) = cmt.place.base {
96                 if Some(id) == self.hir_id_low && !BreakAfterExprVisitor::is_found(self.cx, diag_expr_id) {
97                     self.span_low = Some(self.cx.tcx.hir().span(diag_expr_id));
98                 }
99                 if Some(id) == self.hir_id_high && !BreakAfterExprVisitor::is_found(self.cx, diag_expr_id) {
100                     self.span_high = Some(self.cx.tcx.hir().span(diag_expr_id));
101                 }
102             }
103         }
104     }
105
106     fn mutate(&mut self, cmt: &PlaceWithHirId<'tcx>, diag_expr_id: HirId) {
107         if let PlaceBase::Local(id) = cmt.place.base {
108             if Some(id) == self.hir_id_low && !BreakAfterExprVisitor::is_found(self.cx, diag_expr_id) {
109                 self.span_low = Some(self.cx.tcx.hir().span(diag_expr_id));
110             }
111             if Some(id) == self.hir_id_high && !BreakAfterExprVisitor::is_found(self.cx, diag_expr_id) {
112                 self.span_high = Some(self.cx.tcx.hir().span(diag_expr_id));
113             }
114         }
115     }
116
117     fn fake_read(
118         &mut self,
119         _: &rustc_hir_analysis::expr_use_visitor::PlaceWithHirId<'tcx>,
120         _: FakeReadCause,
121         _: HirId,
122     ) {
123     }
124 }
125
126 impl MutatePairDelegate<'_, '_> {
127     fn mutation_span(&self) -> (Option<Span>, Option<Span>) {
128         (self.span_low, self.span_high)
129     }
130 }
131
132 struct BreakAfterExprVisitor {
133     hir_id: HirId,
134     past_expr: bool,
135     past_candidate: bool,
136     break_after_expr: bool,
137 }
138
139 impl BreakAfterExprVisitor {
140     pub fn is_found(cx: &LateContext<'_>, hir_id: HirId) -> bool {
141         let mut visitor = BreakAfterExprVisitor {
142             hir_id,
143             past_expr: false,
144             past_candidate: false,
145             break_after_expr: false,
146         };
147
148         get_enclosing_block(cx, hir_id).map_or(false, |block| {
149             visitor.visit_block(block);
150             visitor.break_after_expr
151         })
152     }
153 }
154
155 impl<'tcx> intravisit::Visitor<'tcx> for BreakAfterExprVisitor {
156     fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
157         if self.past_candidate {
158             return;
159         }
160
161         if expr.hir_id == self.hir_id {
162             self.past_expr = true;
163         } else if self.past_expr {
164             if matches!(&expr.kind, ExprKind::Break(..)) {
165                 self.break_after_expr = true;
166             }
167
168             self.past_candidate = true;
169         } else {
170             intravisit::walk_expr(self, expr);
171         }
172     }
173 }