Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion compiler/rustc_parse/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -689,7 +689,7 @@ pub(crate) struct MissingInInForLoop {
#[primary_span]
pub span: Span,
#[subdiagnostic]
pub sub: MissingInInForLoopSub,
pub sub: Option<MissingInInForLoopSub>,
}

#[derive(Subdiagnostic)]
Expand Down
33 changes: 28 additions & 5 deletions compiler/rustc_parse/src/parser/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3086,18 +3086,41 @@ impl<'a> Parser<'a> {
}

fn error_missing_in_for_loop(&mut self) {
let (span, sub): (_, fn(_) -> _) = if self.token.is_ident_named(sym::of) {
let (span, sub) = if self.token.is_ident_named(sym::of) {
// Possibly using JS syntax (#75311).
let span = self.token.span;
self.bump();
(span, errors::MissingInInForLoopSub::InNotOf)
(span, Some(errors::MissingInInForLoopSub::InNotOf(span)))
} else if self.eat(exp!(Eq)) {
(self.prev_token.span, errors::MissingInInForLoopSub::InNotEq)
let span = self.prev_token.span;
(span, Some(errors::MissingInInForLoopSub::InNotEq(span)))
} else {
(self.prev_token.span.between(self.token.span), errors::MissingInInForLoopSub::AddIn)
let span = self.prev_token.span.between(self.token.span);
let sub = (!self.for_loop_head_has_in())
.then_some(errors::MissingInInForLoopSub::AddIn(span));
(span, sub)
};

self.dcx().emit_err(errors::MissingInInForLoop { span, sub: sub(span) });
self.dcx().emit_err(errors::MissingInInForLoop { span, sub });
}

/// Whether the `for` loop header already contains an `in` before its body.
/// If it does, the binding is malformed (e.g. `for i i in 0..10`) rather
/// than missing `in`, so suggesting another `in` would just be invalid too.
fn for_loop_head_has_in(&self) -> bool {
let mut dist = 0;
loop {
let (is_in, is_end) = self.look_ahead(dist, |t| {
(t.is_keyword(kw::In), matches!(t.kind, token::OpenBrace | token::Eof))
});
if is_in {
return true;
}
if is_end {
return false;
}
dist += 1;
}
}

/// Parses a `while` or `while let` expression (`while` token already eaten).
Expand Down
9 changes: 9 additions & 0 deletions tests/ui/suggestions/for-loop-no-spurious-in-suggestion.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Regression test for https://github.com/rust-lang/rust/issues/103561: when the
// `for` loop header already contains an `in`, a missing `in` is not the problem,
// so we must not suggest inserting another one (which would not compile).

fn main() {
for i i in 0..10 {}
//~^ ERROR missing `in` in `for` loop
//~| ERROR expected `{`, found keyword `in`
}
14 changes: 14 additions & 0 deletions tests/ui/suggestions/for-loop-no-spurious-in-suggestion.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
error: missing `in` in `for` loop
--> $DIR/for-loop-no-spurious-in-suggestion.rs:6:10
|
LL | for i i in 0..10 {}
| ^

error: expected `{`, found keyword `in`
--> $DIR/for-loop-no-spurious-in-suggestion.rs:6:13
|
LL | for i i in 0..10 {}
| ^^ expected `{`

error: aborting due to 2 previous errors

Loading