Skip to content

Commit 6c1ba4a

Browse files
committed
Use structured suggestion for #113174
When encountering a for loop that is rejected by the borrow checker because it is being advanced within its body, provide a structured suggestion for `while let Some(pat) = iter.next()`.
1 parent 1a449dc commit 6c1ba4a

File tree

4 files changed

+265
-32
lines changed

4 files changed

+265
-32
lines changed

compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs

+148-24
Original file line numberDiff line numberDiff line change
@@ -1311,43 +1311,167 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
13111311
issue_span: Span,
13121312
expr_span: Span,
13131313
body_expr: Option<&'hir hir::Expr<'hir>>,
1314-
loop_bind: Option<Symbol>,
1314+
loop_bind: Option<&'hir Ident>,
1315+
loop_span: Option<Span>,
1316+
head_span: Option<Span>,
1317+
pat_span: Option<Span>,
1318+
head: Option<&'hir hir::Expr<'hir>>,
13151319
}
13161320
impl<'hir> Visitor<'hir> for ExprFinder<'hir> {
13171321
fn visit_expr(&mut self, ex: &'hir hir::Expr<'hir>) {
1318-
if let hir::ExprKind::Loop(hir::Block{ stmts: [stmt, ..], ..}, _, hir::LoopSource::ForLoop, _) = ex.kind &&
1319-
let hir::StmtKind::Expr(hir::Expr{ kind: hir::ExprKind::Match(call, [_, bind, ..], _), ..}) = stmt.kind &&
1320-
let hir::ExprKind::Call(path, _args) = call.kind &&
1321-
let hir::ExprKind::Path(hir::QPath::LangItem(LangItem::IteratorNext, _, _, )) = path.kind &&
1322-
let hir::PatKind::Struct(path, [field, ..], _) = bind.pat.kind &&
1323-
let hir::QPath::LangItem(LangItem::OptionSome, _, _) = path &&
1324-
let PatField { pat: hir::Pat{ kind: hir::PatKind::Binding(_, _, ident, ..), .. }, ..} = field &&
1325-
self.issue_span.source_equal(call.span) {
1326-
self.loop_bind = Some(ident.name);
1322+
// Try to find
1323+
// let result = match IntoIterator::into_iter(<head>) {
1324+
// mut iter => {
1325+
// [opt_ident]: loop {
1326+
// match Iterator::next(&mut iter) {
1327+
// None => break,
1328+
// Some(<pat>) => <body>,
1329+
// };
1330+
// }
1331+
// }
1332+
// };
1333+
// corresponding to the desugaring of a for loop `for <pat> in <head> { <body> }`.
1334+
if let hir::ExprKind::Call(path, [arg]) = ex.kind
1335+
&& let hir::ExprKind::Path(
1336+
hir::QPath::LangItem(LangItem::IntoIterIntoIter, _, _),
1337+
) = path.kind
1338+
&& arg.span == self.issue_span
1339+
{
1340+
// Find `IntoIterator::into_iter(<head>)`
1341+
self.head = Some(arg);
1342+
}
1343+
if let hir::ExprKind::Loop(
1344+
hir::Block { stmts: [stmt, ..], .. },
1345+
_,
1346+
hir::LoopSource::ForLoop,
1347+
_,
1348+
) = ex.kind
1349+
&& let hir::StmtKind::Expr(hir::Expr {
1350+
kind: hir::ExprKind::Match(call, [_, bind, ..], _),
1351+
span: head_span,
1352+
..
1353+
}) = stmt.kind
1354+
&& let hir::ExprKind::Call(path, _args) = call.kind
1355+
&& let hir::ExprKind::Path(
1356+
hir::QPath::LangItem(LangItem::IteratorNext, _, _),
1357+
) = path.kind
1358+
&& let hir::PatKind::Struct(path, [field, ..], _) = bind.pat.kind
1359+
&& let hir::QPath::LangItem(LangItem::OptionSome, pat_span, _) = path
1360+
&& self.issue_span.source_equal(call.span)
1361+
{
1362+
// Find `<pat>` and the span for the whole `for` loop.
1363+
if let PatField { pat: hir::Pat {
1364+
kind: hir::PatKind::Binding(_, _, ident, ..),
1365+
..
1366+
}, ..} = field {
1367+
self.loop_bind = Some(ident);
13271368
}
1369+
self.head_span = Some(*head_span);
1370+
self.pat_span = Some(pat_span);
1371+
self.loop_span = Some(stmt.span);
1372+
}
13281373

1329-
if let hir::ExprKind::MethodCall(body_call, _recv, ..) = ex.kind &&
1330-
body_call.ident.name == sym::next && ex.span.source_equal(self.expr_span) {
1331-
self.body_expr = Some(ex);
1374+
if let hir::ExprKind::MethodCall(body_call, _recv, ..) = ex.kind
1375+
&& body_call.ident.name == sym::next && ex.span.source_equal(self.expr_span)
1376+
{
1377+
self.body_expr = Some(ex);
13321378
}
13331379

13341380
hir::intravisit::walk_expr(self, ex);
13351381
}
13361382
}
1337-
let mut finder =
1338-
ExprFinder { expr_span: span, issue_span, loop_bind: None, body_expr: None };
1383+
let mut finder = ExprFinder {
1384+
expr_span: span,
1385+
issue_span,
1386+
loop_bind: None,
1387+
body_expr: None,
1388+
head_span: None,
1389+
loop_span: None,
1390+
pat_span: None,
1391+
head: None,
1392+
};
13391393
finder.visit_expr(hir.body(body_id).value);
13401394

1341-
if let Some(loop_bind) = finder.loop_bind &&
1342-
let Some(body_expr) = finder.body_expr &&
1343-
let Some(def_id) = typeck_results.type_dependent_def_id(body_expr.hir_id) &&
1344-
let Some(trait_did) = tcx.trait_of_item(def_id) &&
1345-
tcx.is_diagnostic_item(sym::Iterator, trait_did) {
1346-
err.note(format!(
1347-
"a for loop advances the iterator for you, the result is stored in `{}`.",
1348-
loop_bind
1395+
if let Some(body_expr) = finder.body_expr
1396+
&& let Some(loop_span) = finder.loop_span
1397+
&& let Some(def_id) = typeck_results.type_dependent_def_id(body_expr.hir_id)
1398+
&& let Some(trait_did) = tcx.trait_of_item(def_id)
1399+
&& tcx.is_diagnostic_item(sym::Iterator, trait_did)
1400+
{
1401+
if let Some(loop_bind) = finder.loop_bind {
1402+
err.note(format!(
1403+
"a for loop advances the iterator for you, the result is stored in `{}`",
1404+
loop_bind.name,
1405+
));
1406+
} else {
1407+
err.note(
1408+
"a for loop advances the iterator for you, the result is stored in its pattern",
1409+
);
1410+
}
1411+
let msg = "if you want to call `next` on a iterator within the loop, consider using \
1412+
`while let`";
1413+
if let Some(head) = finder.head
1414+
&& let Some(pat_span) = finder.pat_span
1415+
&& loop_span.contains(body_expr.span)
1416+
&& loop_span.contains(head.span)
1417+
{
1418+
let sm = self.infcx.tcx.sess.source_map();
1419+
1420+
let mut sugg = vec![];
1421+
if let hir::ExprKind::Path(hir::QPath::Resolved(None, _)) = head.kind {
1422+
// A bare path doesn't need a `let` assignment, it's already a simple
1423+
// binding access.
1424+
// As a new binding wasn't added, we don't need to modify the advancing call.
1425+
sugg.push((
1426+
loop_span.with_hi(pat_span.lo()),
1427+
format!("while let Some("),
1428+
));
1429+
sugg.push((
1430+
pat_span.shrink_to_hi().with_hi(head.span.lo()),
1431+
") = ".to_string(),
1432+
));
1433+
sugg.push((
1434+
head.span.shrink_to_hi(),
1435+
".next()".to_string(),
1436+
));
1437+
} else {
1438+
// Needs a new a `let` binding.
1439+
let indent = if let Some(indent) = sm.indentation_before(loop_span) {
1440+
format!("\n{indent}")
1441+
} else {
1442+
" ".to_string()
1443+
};
1444+
let Ok(head_str) = sm.span_to_snippet(head.span) else {
1445+
err.help(msg);
1446+
return;
1447+
};
1448+
sugg.push((
1449+
loop_span.with_hi(pat_span.lo()),
1450+
format!("let iter = {head_str};{indent}while let Some("),
13491451
));
1350-
err.help("if you want to call `next` on a iterator within the loop, consider using `while let`.");
1452+
sugg.push((
1453+
pat_span.shrink_to_hi().with_hi(head.span.hi()),
1454+
") = iter.next()".to_string(),
1455+
));
1456+
// As a new binding was added, we should change how the iterator is advanced to
1457+
// use the newly introduced binding.
1458+
if let hir::ExprKind::MethodCall(_, recv, ..) = body_expr.kind
1459+
&& let hir::ExprKind::Path(hir::QPath::Resolved(None, ..)) = recv.kind
1460+
{
1461+
// As we introduced a `let iter = <head>;`, we need to change where the
1462+
// already borrowed value was accessed from `<recv>.next()` to
1463+
// `iter.next()`.
1464+
sugg.push((recv.span, "iter".to_string()));
1465+
}
1466+
}
1467+
err.multipart_suggestion(
1468+
msg,
1469+
sugg,
1470+
Applicability::MaybeIncorrect,
1471+
);
1472+
} else {
1473+
err.help(msg);
1474+
}
13511475
}
13521476
}
13531477

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// run-rustfix
2+
3+
fn test1() {
4+
let mut chars = "Hello".chars();
5+
let iter = chars.by_ref();
6+
while let Some(_c) = iter.next() {
7+
iter.next(); //~ ERROR cannot borrow `chars` as mutable more than once at a time
8+
}
9+
}
10+
11+
fn test2() {
12+
let v = vec![1, 2, 3];
13+
let mut iter = v.iter();
14+
while let Some(_i) = iter.next() {
15+
iter.next(); //~ ERROR borrow of moved value: `iter`
16+
}
17+
}
18+
19+
fn test3() {
20+
let v = vec![(), (), ()];
21+
let mut i = v.iter();
22+
let iter = i.by_ref();
23+
while let Some(()) = iter.next() {
24+
iter.next(); //~ ERROR cannot borrow `i`
25+
}
26+
}
27+
28+
fn test4() {
29+
let v = vec![(), (), ()];
30+
let mut iter = v.iter();
31+
while let Some(()) = iter.next() {
32+
iter.next(); //~ ERROR borrow of moved value: `iter`
33+
}
34+
}
35+
36+
fn main() {
37+
test1();
38+
test2();
39+
test3();
40+
test4();
41+
}

tests/ui/suggestions/issue-102972.rs

+24-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// run-rustfix
2+
13
fn test1() {
24
let mut chars = "Hello".chars();
35
for _c in chars.by_ref() {
@@ -13,4 +15,25 @@ fn test2() {
1315
}
1416
}
1517

16-
fn main() { }
18+
fn test3() {
19+
let v = vec![(), (), ()];
20+
let mut i = v.iter();
21+
for () in i.by_ref() {
22+
i.next(); //~ ERROR cannot borrow `i`
23+
}
24+
}
25+
26+
fn test4() {
27+
let v = vec![(), (), ()];
28+
let mut iter = v.iter();
29+
for () in iter {
30+
iter.next(); //~ ERROR borrow of moved value: `iter`
31+
}
32+
}
33+
34+
fn main() {
35+
test1();
36+
test2();
37+
test3();
38+
test4();
39+
}
+52-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error[E0499]: cannot borrow `chars` as mutable more than once at a time
2-
--> $DIR/issue-102972.rs:4:9
2+
--> $DIR/issue-102972.rs:6:9
33
|
44
LL | for _c in chars.by_ref() {
55
| --------------
@@ -9,11 +9,16 @@ LL | for _c in chars.by_ref() {
99
LL | chars.next();
1010
| ^^^^^^^^^^^^ second mutable borrow occurs here
1111
|
12-
= note: a for loop advances the iterator for you, the result is stored in `_c`.
13-
= help: if you want to call `next` on a iterator within the loop, consider using `while let`.
12+
= note: a for loop advances the iterator for you, the result is stored in `_c`
13+
help: if you want to call `next` on a iterator within the loop, consider using `while let`
14+
|
15+
LL ~ let iter = chars.by_ref();
16+
LL ~ while let Some(_c) = iter.next() {
17+
LL ~ iter.next();
18+
|
1419

1520
error[E0382]: borrow of moved value: `iter`
16-
--> $DIR/issue-102972.rs:12:9
21+
--> $DIR/issue-102972.rs:14:9
1722
|
1823
LL | let mut iter = v.iter();
1924
| -------- move occurs because `iter` has type `std::slice::Iter<'_, i32>`, which does not implement the `Copy` trait
@@ -22,12 +27,52 @@ LL | for _i in iter {
2227
LL | iter.next();
2328
| ^^^^^^^^^^^ value borrowed here after move
2429
|
25-
= note: a for loop advances the iterator for you, the result is stored in `_i`.
26-
= help: if you want to call `next` on a iterator within the loop, consider using `while let`.
30+
= note: a for loop advances the iterator for you, the result is stored in `_i`
31+
note: `into_iter` takes ownership of the receiver `self`, which moves `iter`
32+
--> $SRC_DIR/core/src/iter/traits/collect.rs:LL:COL
33+
help: if you want to call `next` on a iterator within the loop, consider using `while let`
34+
|
35+
LL | while let Some(_i) = iter.next() {
36+
| ~~~~~~~~~~~~~~~ ~~~ +++++++
37+
38+
error[E0499]: cannot borrow `i` as mutable more than once at a time
39+
--> $DIR/issue-102972.rs:22:9
40+
|
41+
LL | for () in i.by_ref() {
42+
| ----------
43+
| |
44+
| first mutable borrow occurs here
45+
| first borrow later used here
46+
LL | i.next();
47+
| ^^^^^^^^ second mutable borrow occurs here
48+
|
49+
= note: a for loop advances the iterator for you, the result is stored in its pattern
50+
help: if you want to call `next` on a iterator within the loop, consider using `while let`
51+
|
52+
LL ~ let iter = i.by_ref();
53+
LL ~ while let Some(()) = iter.next() {
54+
LL ~ iter.next();
55+
|
56+
57+
error[E0382]: borrow of moved value: `iter`
58+
--> $DIR/issue-102972.rs:30:9
59+
|
60+
LL | let mut iter = v.iter();
61+
| -------- move occurs because `iter` has type `std::slice::Iter<'_, ()>`, which does not implement the `Copy` trait
62+
LL | for () in iter {
63+
| ---- `iter` moved due to this implicit call to `.into_iter()`
64+
LL | iter.next();
65+
| ^^^^^^^^^^^ value borrowed here after move
66+
|
67+
= note: a for loop advances the iterator for you, the result is stored in its pattern
2768
note: `into_iter` takes ownership of the receiver `self`, which moves `iter`
2869
--> $SRC_DIR/core/src/iter/traits/collect.rs:LL:COL
70+
help: if you want to call `next` on a iterator within the loop, consider using `while let`
71+
|
72+
LL | while let Some(()) = iter.next() {
73+
| ~~~~~~~~~~~~~~~ ~~~ +++++++
2974

30-
error: aborting due to 2 previous errors
75+
error: aborting due to 4 previous errors
3176

3277
Some errors have detailed explanations: E0382, E0499.
3378
For more information about an error, try `rustc --explain E0382`.

0 commit comments

Comments
 (0)