Skip to content

Commit 8fbf75e

Browse files
committed
mut_range_bound to check for immediate break from loop
1 parent 290fb8d commit 8fbf75e

File tree

1 file changed

+77
-21
lines changed

1 file changed

+77
-21
lines changed

clippy_lints/src/loops/mut_range_bound.rs

Lines changed: 77 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,27 @@
11
use super::MUT_RANGE_BOUND;
2-
use clippy_utils::diagnostics::span_lint;
3-
use clippy_utils::{higher, path_to_local};
2+
use clippy_utils::diagnostics::span_lint_and_note;
3+
use clippy_utils::{get_enclosing_block, higher, path_to_local};
44
use if_chain::if_chain;
5-
use rustc_hir::{BindingAnnotation, Expr, HirId, Node, PatKind};
5+
use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
6+
use rustc_hir::{BindingAnnotation, Expr, ExprKind, HirId, Node, PatKind};
67
use rustc_infer::infer::TyCtxtInferExt;
78
use rustc_lint::LateContext;
9+
use rustc_middle::hir::map::Map;
810
use rustc_middle::{mir::FakeReadCause, ty};
911
use rustc_span::source_map::Span;
1012
use rustc_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId};
1113

1214
pub(super) fn check(cx: &LateContext<'_>, arg: &Expr<'_>, body: &Expr<'_>) {
13-
if let Some(higher::Range {
14-
start: Some(start),
15-
end: Some(end),
16-
..
17-
}) = higher::Range::hir(arg)
18-
{
19-
let mut_ids = vec![check_for_mutability(cx, start), check_for_mutability(cx, end)];
20-
if mut_ids[0].is_some() || mut_ids[1].is_some() {
21-
let (span_low, span_high) = check_for_mutation(cx, body, &mut_ids);
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);
2225
mut_warn_with_span(cx, span_low);
2326
mut_warn_with_span(cx, span_high);
2427
}
@@ -27,11 +30,13 @@ pub(super) fn check(cx: &LateContext<'_>, arg: &Expr<'_>, body: &Expr<'_>) {
2730

2831
fn mut_warn_with_span(cx: &LateContext<'_>, span: Option<Span>) {
2932
if let Some(sp) = span {
30-
span_lint(
33+
span_lint_and_note(
3134
cx,
3235
MUT_RANGE_BOUND,
3336
sp,
34-
"attempt to mutate range bound within loop; note that the range of the loop is unchanged",
37+
"attempt to mutate range bound within loop",
38+
None,
39+
"the range of the loop is unchanged",
3540
);
3641
}
3742
}
@@ -51,12 +56,13 @@ fn check_for_mutability(cx: &LateContext<'_>, bound: &Expr<'_>) -> Option<HirId>
5156
fn check_for_mutation<'tcx>(
5257
cx: &LateContext<'tcx>,
5358
body: &Expr<'_>,
54-
bound_ids: &[Option<HirId>],
59+
bound_id_start: Option<HirId>,
60+
bound_id_end: Option<HirId>,
5561
) -> (Option<Span>, Option<Span>) {
5662
let mut delegate = MutatePairDelegate {
5763
cx,
58-
hir_id_low: bound_ids[0],
59-
hir_id_high: bound_ids[1],
64+
hir_id_low: bound_id_start,
65+
hir_id_high: bound_id_end,
6066
span_low: None,
6167
span_high: None,
6268
};
@@ -70,6 +76,7 @@ fn check_for_mutation<'tcx>(
7076
)
7177
.walk_expr(body);
7278
});
79+
7380
delegate.mutation_span()
7481
}
7582

@@ -87,10 +94,10 @@ impl<'tcx> Delegate<'tcx> for MutatePairDelegate<'_, 'tcx> {
8794
fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, diag_expr_id: HirId, bk: ty::BorrowKind) {
8895
if let ty::BorrowKind::MutBorrow = bk {
8996
if let PlaceBase::Local(id) = cmt.place.base {
90-
if Some(id) == self.hir_id_low {
97+
if Some(id) == self.hir_id_low && !BreakAfterExprVisitor::is_found(self.cx, diag_expr_id) {
9198
self.span_low = Some(self.cx.tcx.hir().span(diag_expr_id));
9299
}
93-
if Some(id) == self.hir_id_high {
100+
if Some(id) == self.hir_id_high && !BreakAfterExprVisitor::is_found(self.cx, diag_expr_id) {
94101
self.span_high = Some(self.cx.tcx.hir().span(diag_expr_id));
95102
}
96103
}
@@ -99,10 +106,10 @@ impl<'tcx> Delegate<'tcx> for MutatePairDelegate<'_, 'tcx> {
99106

100107
fn mutate(&mut self, cmt: &PlaceWithHirId<'tcx>, diag_expr_id: HirId) {
101108
if let PlaceBase::Local(id) = cmt.place.base {
102-
if Some(id) == self.hir_id_low {
109+
if Some(id) == self.hir_id_low && !BreakAfterExprVisitor::is_found(self.cx, diag_expr_id) {
103110
self.span_low = Some(self.cx.tcx.hir().span(diag_expr_id));
104111
}
105-
if Some(id) == self.hir_id_high {
112+
if Some(id) == self.hir_id_high && !BreakAfterExprVisitor::is_found(self.cx, diag_expr_id) {
106113
self.span_high = Some(self.cx.tcx.hir().span(diag_expr_id));
107114
}
108115
}
@@ -116,3 +123,52 @@ impl MutatePairDelegate<'_, '_> {
116123
(self.span_low, self.span_high)
117124
}
118125
}
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+
}

0 commit comments

Comments
 (0)