Skip to content

Commit

Permalink
Rollup merge of #113174 - chenyukang:yukang-fix-102972-loop-next, r=c…
Browse files Browse the repository at this point in the history
…ompiler-errors

Better messages for next on a iterator inside for loops

Fixes #102972
  • Loading branch information
matthiaskrgr committed Jul 1, 2023
2 parents b63bc06 + 4189faa commit 9082287
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 2 deletions.
76 changes: 74 additions & 2 deletions compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use std::iter;

use either::Either;
use hir::PatField;
use rustc_data_structures::captures::Captures;
use rustc_data_structures::fx::FxIndexSet;
use rustc_errors::{
Expand Down Expand Up @@ -28,6 +27,7 @@ use rustc_span::symbol::{kw, sym, Ident};
use rustc_span::{BytePos, Span, Symbol};
use rustc_trait_selection::infer::InferCtxtExt;
use rustc_trait_selection::traits::ObligationCtxt;
use std::iter;

use crate::borrow_set::TwoPhaseActivation;
use crate::borrowck_errors;
Expand Down Expand Up @@ -992,6 +992,11 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
issued_borrow.borrowed_place,
&issued_spans,
);
self.explain_iterator_advancement_in_for_loop_if_applicable(
&mut err,
span,
&issued_spans,
);
err
}

Expand Down Expand Up @@ -1279,6 +1284,73 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
}
}

/// Suggest using `while let` for call `next` on an iterator in a for loop.
///
/// For example:
/// ```ignore (illustrative)
///
/// for x in iter {
/// ...
/// iter.next()
/// }
/// ```
pub(crate) fn explain_iterator_advancement_in_for_loop_if_applicable(
&self,
err: &mut Diagnostic,
span: Span,
issued_spans: &UseSpans<'tcx>,
) {
let issue_span = issued_spans.args_or_use();
let tcx = self.infcx.tcx;
let hir = tcx.hir();

let Some(body_id) = hir.get(self.mir_hir_id()).body_id() else { return };
let typeck_results = tcx.typeck(self.mir_def_id());

struct ExprFinder<'hir> {
issue_span: Span,
expr_span: Span,
body_expr: Option<&'hir hir::Expr<'hir>>,
loop_bind: Option<Symbol>,
}
impl<'hir> Visitor<'hir> for ExprFinder<'hir> {
fn visit_expr(&mut self, ex: &'hir hir::Expr<'hir>) {
if let hir::ExprKind::Loop(hir::Block{ stmts: [stmt, ..], ..}, _, hir::LoopSource::ForLoop, _) = ex.kind &&
let hir::StmtKind::Expr(hir::Expr{ kind: hir::ExprKind::Match(call, [_, bind, ..], _), ..}) = stmt.kind &&
let hir::ExprKind::Call(path, _args) = call.kind &&
let hir::ExprKind::Path(hir::QPath::LangItem(LangItem::IteratorNext, _, _, )) = path.kind &&
let hir::PatKind::Struct(path, [field, ..], _) = bind.pat.kind &&
let hir::QPath::LangItem(LangItem::OptionSome, _, _) = path &&
let PatField { pat: hir::Pat{ kind: hir::PatKind::Binding(_, _, ident, ..), .. }, ..} = field &&
self.issue_span.source_equal(call.span) {
self.loop_bind = Some(ident.name);
}

if let hir::ExprKind::MethodCall(body_call, _recv, ..) = ex.kind &&
body_call.ident.name == sym::next && ex.span.source_equal(self.expr_span) {
self.body_expr = Some(ex);
}

hir::intravisit::walk_expr(self, ex);
}
}
let mut finder =
ExprFinder { expr_span: span, issue_span, loop_bind: None, body_expr: None };
finder.visit_expr(hir.body(body_id).value);

if let Some(loop_bind) = finder.loop_bind &&
let Some(body_expr) = finder.body_expr &&
let Some(def_id) = typeck_results.type_dependent_def_id(body_expr.hir_id) &&
let Some(trait_did) = tcx.trait_of_item(def_id) &&
tcx.is_diagnostic_item(sym::Iterator, trait_did) {
err.note(format!(
"a for loop advances the iterator for you, the result is stored in `{}`.",
loop_bind
));
err.help("if you want to call `next` on a iterator within the loop, consider using `while let`.");
}
}

/// Suggest using closure argument instead of capture.
///
/// For example:
Expand Down
6 changes: 6 additions & 0 deletions compiler/rustc_borrowck/src/diagnostics/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1146,6 +1146,12 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
// Avoid pointing to the same function in multiple different
// error messages.
if span != DUMMY_SP && self.fn_self_span_reported.insert(self_arg.span) {
self.explain_iterator_advancement_in_for_loop_if_applicable(
err,
span,
&move_spans,
);

let func = tcx.def_path_str(method_did);
err.subdiagnostic(CaptureReasonNote::FuncTakeSelf {
func,
Expand Down
16 changes: 16 additions & 0 deletions tests/ui/suggestions/issue-102972.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
fn test1() {
let mut chars = "Hello".chars();
for _c in chars.by_ref() {
chars.next(); //~ ERROR cannot borrow `chars` as mutable more than once at a time
}
}

fn test2() {
let v = vec![1, 2, 3];
let mut iter = v.iter();
for _i in iter {
iter.next(); //~ ERROR borrow of moved value: `iter`
}
}

fn main() { }
33 changes: 33 additions & 0 deletions tests/ui/suggestions/issue-102972.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
error[E0499]: cannot borrow `chars` as mutable more than once at a time
--> $DIR/issue-102972.rs:4:9
|
LL | for _c in chars.by_ref() {
| --------------
| |
| first mutable borrow occurs here
| first borrow later used here
LL | chars.next();
| ^^^^^^^^^^^^ second mutable borrow occurs here
|
= note: a for loop advances the iterator for you, the result is stored in `_c`.
= help: if you want to call `next` on a iterator within the loop, consider using `while let`.

error[E0382]: borrow of moved value: `iter`
--> $DIR/issue-102972.rs:12:9
|
LL | let mut iter = v.iter();
| -------- move occurs because `iter` has type `std::slice::Iter<'_, i32>`, which does not implement the `Copy` trait
LL | for _i in iter {
| ---- `iter` moved due to this implicit call to `.into_iter()`
LL | iter.next();
| ^^^^^^^^^^^ value borrowed here after move
|
= note: a for loop advances the iterator for you, the result is stored in `_i`.
= help: if you want to call `next` on a iterator within the loop, consider using `while let`.
note: `into_iter` takes ownership of the receiver `self`, which moves `iter`
--> $SRC_DIR/core/src/iter/traits/collect.rs:LL:COL

error: aborting due to 2 previous errors

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

0 comments on commit 9082287

Please sign in to comment.