Skip to content

Commit f356784

Browse files
committed
Auto merge of rust-lang#7607 - dswij:mut-range-bound-break, r=flip1995
`mut_range_bound` check for immediate break after mutation closes rust-lang#7532 `mut_range_bound` ignores mutation on range bounds that is placed immediately before break. Still warns if the break is not always reachable. changelog: [`mut_range_bound`] ignore range bound mutations before immediate break
2 parents b7c25e1 + dc6f7dc commit f356784

File tree

4 files changed

+158
-41
lines changed

4 files changed

+158
-41
lines changed

clippy_lints/src/loops/mod.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,21 @@ declare_clippy_lint! {
397397
/// ### Why is this bad?
398398
/// One might think that modifying the mutable variable changes the loop bounds
399399
///
400+
/// ### Known problems
401+
/// False positive when mutation is followed by a `break`, but the `break` is not immediately
402+
/// after the mutation:
403+
///
404+
/// ```rust
405+
/// let mut x = 5;
406+
/// for _ in 0..x {
407+
/// x += 1; // x is a range bound that is mutated
408+
/// ..; // some other expression
409+
/// break; // leaves the loop, so mutation is not an issue
410+
/// }
411+
/// ```
412+
///
413+
/// False positive on nested loops ([#6072](https://github.com/rust-lang/rust-clippy/issues/6072))
414+
///
400415
/// ### Example
401416
/// ```rust
402417
/// let mut foo = 42;

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+
}

tests/ui/mut_range_bound.rs

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,6 @@
11
#![allow(unused)]
22

3-
fn main() {
4-
mut_range_bound_upper();
5-
mut_range_bound_lower();
6-
mut_range_bound_both();
7-
mut_range_bound_no_mutation();
8-
immut_range_bound();
9-
mut_borrow_range_bound();
10-
immut_borrow_range_bound();
11-
}
3+
fn main() {}
124

135
fn mut_range_bound_upper() {
146
let mut m = 4;
@@ -61,3 +53,32 @@ fn immut_range_bound() {
6153
continue;
6254
} // no warning
6355
}
56+
57+
fn mut_range_bound_break() {
58+
let mut m = 4;
59+
for i in 0..m {
60+
if m == 4 {
61+
m = 5; // no warning because of immediate break
62+
break;
63+
}
64+
}
65+
}
66+
67+
fn mut_range_bound_no_immediate_break() {
68+
let mut m = 4;
69+
for i in 0..m {
70+
m = 2; // warning because it is not immediately followed by break
71+
if m == 4 {
72+
break;
73+
}
74+
}
75+
76+
let mut n = 3;
77+
for i in n..10 {
78+
if n == 4 {
79+
n = 1; // FIXME: warning because is is not immediately followed by break
80+
let _ = 2;
81+
break;
82+
}
83+
}
84+
}

tests/ui/mut_range_bound.stderr

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,59 @@
1-
error: attempt to mutate range bound within loop; note that the range of the loop is unchanged
2-
--> $DIR/mut_range_bound.rs:16:9
1+
error: attempt to mutate range bound within loop
2+
--> $DIR/mut_range_bound.rs:8:9
33
|
44
LL | m = 5;
55
| ^
66
|
77
= note: `-D clippy::mut-range-bound` implied by `-D warnings`
8+
= note: the range of the loop is unchanged
89

9-
error: attempt to mutate range bound within loop; note that the range of the loop is unchanged
10-
--> $DIR/mut_range_bound.rs:23:9
10+
error: attempt to mutate range bound within loop
11+
--> $DIR/mut_range_bound.rs:15:9
1112
|
1213
LL | m *= 2;
1314
| ^
15+
|
16+
= note: the range of the loop is unchanged
1417

15-
error: attempt to mutate range bound within loop; note that the range of the loop is unchanged
16-
--> $DIR/mut_range_bound.rs:31:9
18+
error: attempt to mutate range bound within loop
19+
--> $DIR/mut_range_bound.rs:23:9
1720
|
1821
LL | m = 5;
1922
| ^
23+
|
24+
= note: the range of the loop is unchanged
2025

21-
error: attempt to mutate range bound within loop; note that the range of the loop is unchanged
22-
--> $DIR/mut_range_bound.rs:32:9
26+
error: attempt to mutate range bound within loop
27+
--> $DIR/mut_range_bound.rs:24:9
2328
|
2429
LL | n = 7;
2530
| ^
31+
|
32+
= note: the range of the loop is unchanged
2633

27-
error: attempt to mutate range bound within loop; note that the range of the loop is unchanged
28-
--> $DIR/mut_range_bound.rs:46:22
34+
error: attempt to mutate range bound within loop
35+
--> $DIR/mut_range_bound.rs:38:22
2936
|
3037
LL | let n = &mut m; // warning
3138
| ^
39+
|
40+
= note: the range of the loop is unchanged
41+
42+
error: attempt to mutate range bound within loop
43+
--> $DIR/mut_range_bound.rs:70:9
44+
|
45+
LL | m = 2; // warning because it is not immediately followed by break
46+
| ^
47+
|
48+
= note: the range of the loop is unchanged
49+
50+
error: attempt to mutate range bound within loop
51+
--> $DIR/mut_range_bound.rs:79:13
52+
|
53+
LL | n = 1; // FIXME: warning because is is not immediately followed by break
54+
| ^
55+
|
56+
= note: the range of the loop is unchanged
3257

33-
error: aborting due to 5 previous errors
58+
error: aborting due to 7 previous errors
3459

0 commit comments

Comments
 (0)