]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/loops/mut_range_bound.rs
Auto merge of #9497 - kraktus:needless_return2, 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_infer::infer::TyCtxtInferExt;
8 use rustc_lint::LateContext;
9 use rustc_middle::{mir::FakeReadCause, ty};
10 use rustc_span::source_map::Span;
11 use rustc_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId};
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,
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(&mut self, _: &rustc_typeck::expr_use_visitor::PlaceWithHirId<'tcx>, _: FakeReadCause, _: HirId) {}
118 }
119
120 impl MutatePairDelegate<'_, '_> {
121     fn mutation_span(&self) -> (Option<Span>, Option<Span>) {
122         (self.span_low, self.span_high)
123     }
124 }
125
126 struct BreakAfterExprVisitor {
127     hir_id: HirId,
128     past_expr: bool,
129     past_candidate: bool,
130     break_after_expr: bool,
131 }
132
133 impl BreakAfterExprVisitor {
134     pub fn is_found(cx: &LateContext<'_>, hir_id: HirId) -> bool {
135         let mut visitor = BreakAfterExprVisitor {
136             hir_id,
137             past_expr: false,
138             past_candidate: false,
139             break_after_expr: false,
140         };
141
142         get_enclosing_block(cx, hir_id).map_or(false, |block| {
143             visitor.visit_block(block);
144             visitor.break_after_expr
145         })
146     }
147 }
148
149 impl<'tcx> intravisit::Visitor<'tcx> for BreakAfterExprVisitor {
150     fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
151         if self.past_candidate {
152             return;
153         }
154
155         if expr.hir_id == self.hir_id {
156             self.past_expr = true;
157         } else if self.past_expr {
158             if matches!(&expr.kind, ExprKind::Break(..)) {
159                 self.break_after_expr = true;
160             }
161
162             self.past_candidate = true;
163         } else {
164             intravisit::walk_expr(self, expr);
165         }
166     }
167 }