Skip to content

Commit 44a8a8d

Browse files
committed
Better messages for next in a iterator inside for loops
1 parent de22388 commit 44a8a8d

File tree

4 files changed

+132
-2
lines changed

4 files changed

+132
-2
lines changed

compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
use std::iter;
2-
31
use either::Either;
2+
use hir::PatField;
43
use rustc_data_structures::captures::Captures;
54
use rustc_data_structures::fx::FxIndexSet;
65
use rustc_errors::{
@@ -28,6 +27,8 @@ use rustc_span::symbol::{kw, sym, Ident};
2827
use rustc_span::{BytePos, Span, Symbol};
2928
use rustc_trait_selection::infer::InferCtxtExt;
3029
use rustc_trait_selection::traits::ObligationCtxt;
30+
use std::iter;
31+
use std::marker::PhantomData;
3132

3233
use crate::borrow_set::TwoPhaseActivation;
3334
use crate::borrowck_errors;
@@ -992,6 +993,11 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
992993
issued_borrow.borrowed_place,
993994
&issued_spans,
994995
);
996+
self.explain_iterator_advancement_in_for_loop_if_applicable(
997+
&mut err,
998+
span,
999+
&issued_spans,
1000+
);
9951001
err
9961002
}
9971003

@@ -1278,6 +1284,75 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
12781284
}
12791285
}
12801286

1287+
/// Suggest using `while let` for call `next` on an iterator in a for loop.
1288+
///
1289+
/// For example:
1290+
/// ```ignore (illustrative)
1291+
///
1292+
/// for x in iter {
1293+
/// ...
1294+
/// iter.next()
1295+
/// }
1296+
/// ```
1297+
pub(crate) fn explain_iterator_advancement_in_for_loop_if_applicable(
1298+
&self,
1299+
err: &mut Diagnostic,
1300+
span: Span,
1301+
issued_spans: &UseSpans<'tcx>,
1302+
) {
1303+
let issue_span = issued_spans.args_or_use();
1304+
let tcx = self.infcx.tcx;
1305+
let hir = tcx.hir();
1306+
1307+
let Some(body_id) = hir.get(self.mir_hir_id()).body_id() else { return };
1308+
1309+
struct ExprFinder<'hir> {
1310+
phantom: PhantomData<&'hir hir::Expr<'hir>>,
1311+
issue_span: Span,
1312+
expr_span: Span,
1313+
found_body_expr: bool,
1314+
loop_bind: Option<Symbol>,
1315+
}
1316+
impl<'hir> Visitor<'hir> for ExprFinder<'hir> {
1317+
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);
1327+
}
1328+
1329+
if let hir::ExprKind::MethodCall(body_call, ..) = ex.kind &&
1330+
body_call.ident.name == sym::next &&
1331+
ex.span.source_equal(self.expr_span) {
1332+
self.found_body_expr = true;
1333+
}
1334+
1335+
hir::intravisit::walk_expr(self, ex);
1336+
}
1337+
}
1338+
let mut finder = ExprFinder {
1339+
phantom: PhantomData,
1340+
expr_span: span,
1341+
issue_span,
1342+
loop_bind: None,
1343+
found_body_expr: false,
1344+
};
1345+
finder.visit_expr(hir.body(body_id).value);
1346+
if let Some(loop_bind) = finder.loop_bind &&
1347+
finder.found_body_expr {
1348+
err.note(format!(
1349+
"a for loop advances the iterator for you, the result is stored in `{}`.",
1350+
loop_bind
1351+
));
1352+
err.help("if you want to call `next` on a iterator within the loop, consider using `while let`.");
1353+
}
1354+
}
1355+
12811356
/// Suggest using closure argument instead of capture.
12821357
///
12831358
/// For example:

compiler/rustc_borrowck/src/diagnostics/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1146,6 +1146,12 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
11461146
// Avoid pointing to the same function in multiple different
11471147
// error messages.
11481148
if span != DUMMY_SP && self.fn_self_span_reported.insert(self_arg.span) {
1149+
self.explain_iterator_advancement_in_for_loop_if_applicable(
1150+
err,
1151+
span,
1152+
&move_spans,
1153+
);
1154+
11491155
let func = tcx.def_path_str(method_did);
11501156
err.subdiagnostic(CaptureReasonNote::FuncTakeSelf {
11511157
func,

tests/ui/suggestions/issue-102972.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
fn test1() {
2+
let mut chars = "Hello".chars();
3+
for _c in chars.by_ref() {
4+
chars.next(); //~ ERROR cannot borrow `chars` as mutable more than once at a time
5+
}
6+
}
7+
8+
fn test2() {
9+
let v = vec![1, 2, 3];
10+
let mut iter = v.iter();
11+
for _i in iter {
12+
iter.next(); //~ ERROR borrow of moved value: `iter`
13+
}
14+
}
15+
16+
fn main() { }
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
error[E0499]: cannot borrow `chars` as mutable more than once at a time
2+
--> $DIR/issue-102972.rs:4:9
3+
|
4+
LL | for _c in chars.by_ref() {
5+
| --------------
6+
| |
7+
| first mutable borrow occurs here
8+
| first borrow later used here
9+
LL | chars.next();
10+
| ^^^^^^^^^^^^ second mutable borrow occurs here
11+
|
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+
error[E0382]: borrow of moved value: `iter`
16+
--> $DIR/issue-102972.rs:12:9
17+
|
18+
LL | let mut iter = v.iter();
19+
| -------- move occurs because `iter` has type `std::slice::Iter<'_, i32>`, which does not implement the `Copy` trait
20+
LL | for _i in iter {
21+
| ---- `iter` moved due to this implicit call to `.into_iter()`
22+
LL | iter.next();
23+
| ^^^^^^^^^^^ value borrowed here after move
24+
|
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`.
27+
note: `into_iter` takes ownership of the receiver `self`, which moves `iter`
28+
--> $SRC_DIR/core/src/iter/traits/collect.rs:LL:COL
29+
30+
error: aborting due to 2 previous errors
31+
32+
Some errors have detailed explanations: E0382, E0499.
33+
For more information about an error, try `rustc --explain E0382`.

0 commit comments

Comments
 (0)