]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/loops/mut_range_bound.rs
Rollup merge of #103140 - chenyukang:yukang/fix-103112, r=estebank
[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<'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     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(
117         &mut self,
118         _: &rustc_hir_typeck::expr_use_visitor::PlaceWithHirId<'tcx>,
119         _: FakeReadCause,
120         _: HirId,
121     ) {
122     }
123 }
124
125 impl MutatePairDelegate<'_, '_> {
126     fn mutation_span(&self) -> (Option<Span>, Option<Span>) {
127         (self.span_low, self.span_high)
128     }
129 }
130
131 struct BreakAfterExprVisitor {
132     hir_id: HirId,
133     past_expr: bool,
134     past_candidate: bool,
135     break_after_expr: bool,
136 }
137
138 impl BreakAfterExprVisitor {
139     pub fn is_found(cx: &LateContext<'_>, hir_id: HirId) -> bool {
140         let mut visitor = BreakAfterExprVisitor {
141             hir_id,
142             past_expr: false,
143             past_candidate: false,
144             break_after_expr: false,
145         };
146
147         get_enclosing_block(cx, hir_id).map_or(false, |block| {
148             visitor.visit_block(block);
149             visitor.break_after_expr
150         })
151     }
152 }
153
154 impl<'tcx> intravisit::Visitor<'tcx> for BreakAfterExprVisitor {
155     fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
156         if self.past_candidate {
157             return;
158         }
159
160         if expr.hir_id == self.hir_id {
161             self.past_expr = true;
162         } else if self.past_expr {
163             if matches!(&expr.kind, ExprKind::Break(..)) {
164                 self.break_after_expr = true;
165             }
166
167             self.past_candidate = true;
168         } else {
169             intravisit::walk_expr(self, expr);
170         }
171     }
172 }