From 0ab14caeaf5f7ef987e047ccbbbdda59876f86de Mon Sep 17 00:00:00 2001 From: Haoran Wang Date: Mon, 15 Jun 2026 19:35:07 +0800 Subject: [PATCH] Don't suggest adding `in` to a `for` loop that already has one When a `for` loop is missing its `in`, the parser suggested inserting one based only on the token after the pattern. A malformed binding such as `for i i in 0..10` was therefore "corrected" to `for i in i in 0..10`, which does not parse. Only suggest inserting `in` when the loop header does not already contain one before the body. --- compiler/rustc_parse/src/errors.rs | 2 +- compiler/rustc_parse/src/parser/expr.rs | 33 ++++++++++++++++--- .../for-loop-no-spurious-in-suggestion.rs | 9 +++++ .../for-loop-no-spurious-in-suggestion.stderr | 14 ++++++++ 4 files changed, 52 insertions(+), 6 deletions(-) create mode 100644 tests/ui/suggestions/for-loop-no-spurious-in-suggestion.rs create mode 100644 tests/ui/suggestions/for-loop-no-spurious-in-suggestion.stderr diff --git a/compiler/rustc_parse/src/errors.rs b/compiler/rustc_parse/src/errors.rs index 3a726cbce8182..0c850b69ca7f1 100644 --- a/compiler/rustc_parse/src/errors.rs +++ b/compiler/rustc_parse/src/errors.rs @@ -689,7 +689,7 @@ pub(crate) struct MissingInInForLoop { #[primary_span] pub span: Span, #[subdiagnostic] - pub sub: MissingInInForLoopSub, + pub sub: Option, } #[derive(Subdiagnostic)] diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index e414e10e84169..c81b3dd6ab5c0 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -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). diff --git a/tests/ui/suggestions/for-loop-no-spurious-in-suggestion.rs b/tests/ui/suggestions/for-loop-no-spurious-in-suggestion.rs new file mode 100644 index 0000000000000..142f925665881 --- /dev/null +++ b/tests/ui/suggestions/for-loop-no-spurious-in-suggestion.rs @@ -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` +} diff --git a/tests/ui/suggestions/for-loop-no-spurious-in-suggestion.stderr b/tests/ui/suggestions/for-loop-no-spurious-in-suggestion.stderr new file mode 100644 index 0000000000000..472ee7d9ddca2 --- /dev/null +++ b/tests/ui/suggestions/for-loop-no-spurious-in-suggestion.stderr @@ -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 +