Skip to content

Commit

Permalink
Change how for (x in foo) {} is handled
Browse files Browse the repository at this point in the history
Use the same approach used for match arm patterns.
  • Loading branch information
estebank committed Nov 6, 2023
1 parent cfdc3c6 commit 0ef0cc4
Show file tree
Hide file tree
Showing 10 changed files with 138 additions and 81 deletions.
43 changes: 6 additions & 37 deletions compiler/rustc_parse/src/parser/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ use crate::errors::{
DoubleColonInBound, ExpectedIdentifier, ExpectedSemi, ExpectedSemiSugg,
GenericParamsWithoutAngleBrackets, GenericParamsWithoutAngleBracketsSugg,
HelpIdentifierStartsWithNumber, InInTypo, IncorrectAwait, IncorrectSemicolon,
IncorrectUseOfAwait, ParenthesesInForHead, ParenthesesInForHeadSugg,
PatternMethodParamWithoutBody, QuestionMarkInType, QuestionMarkInTypeSugg, SelfParamNotFirst,
StructLiteralBodyWithoutPath, StructLiteralBodyWithoutPathSugg, StructLiteralNeedingParens,
StructLiteralNeedingParensSugg, SuggAddMissingLetStmt, SuggEscapeIdentifier, SuggRemoveComma,
TernaryOperator, UnexpectedConstInGenericParam, UnexpectedConstParamDeclaration,
UnexpectedConstParamDeclarationSugg, UnmatchedAngleBrackets, UseEqInstead, WrapType,
IncorrectUseOfAwait, PatternMethodParamWithoutBody, QuestionMarkInType, QuestionMarkInTypeSugg,
SelfParamNotFirst, StructLiteralBodyWithoutPath, StructLiteralBodyWithoutPathSugg,
StructLiteralNeedingParens, StructLiteralNeedingParensSugg, SuggAddMissingLetStmt,
SuggEscapeIdentifier, SuggRemoveComma, TernaryOperator, UnexpectedConstInGenericParam,
UnexpectedConstParamDeclaration, UnexpectedConstParamDeclarationSugg, UnmatchedAngleBrackets,
UseEqInstead, WrapType,
};

use crate::fluent_generated as fluent;
Expand Down Expand Up @@ -1895,37 +1895,6 @@ impl<'a> Parser<'a> {
}
}

/// Recovers a situation like `for ( $pat in $expr )`
/// and suggest writing `for $pat in $expr` instead.
///
/// This should be called before parsing the `$block`.
pub(super) fn recover_parens_around_for_head(
&mut self,
pat: P<Pat>,
begin_paren: Option<(Span, Span)>,
) -> P<Pat> {
match (&self.token.kind, begin_paren) {
(token::CloseDelim(Delimiter::Parenthesis), Some((begin_par_sp, left))) => {
let right = self.prev_token.span.between(self.look_ahead(1, |t| t.span));
self.bump();
self.sess.emit_err(ParenthesesInForHead {
span: vec![begin_par_sp, self.prev_token.span],
// With e.g. `for (x) in y)` this would replace `(x) in y)`
// with `x) in y)` which is syntactically invalid.
// However, this is prevented before we get here.
sugg: ParenthesesInForHeadSugg { left, right },
});

// Unwrap `(pat)` into `pat` to avoid the `unused_parens` lint.
pat.and_then(|pat| match pat.kind {
PatKind::Paren(pat) => pat,
_ => P(pat),
})
}
_ => pat,
}
}

pub(super) fn recover_seq_parse_error(
&mut self,
delim: Delimiter,
Expand Down
73 changes: 53 additions & 20 deletions compiler/rustc_parse/src/parser/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2590,33 +2590,66 @@ impl<'a> Parser<'a> {
}
}

/// Parses `for <src_pat> in <src_expr> <src_loop_block>` (`for` token already eaten).
fn parse_expr_for(&mut self, opt_label: Option<Label>, lo: Span) -> PResult<'a, P<Expr>> {
// Record whether we are about to parse `for (`.
// This is used below for recovery in case of `for ( $stuff ) $block`
// in which case we will suggest `for $stuff $block`.
let begin_paren = match self.token.kind {
token::OpenDelim(Delimiter::Parenthesis) => Some((
self.token.span,
self.prev_token.span.between(self.look_ahead(1, |t| t.span)),
)),
_ => None,
fn parse_for_head(&mut self) -> PResult<'a, (P<Pat>, P<Expr>)> {
let pat = if self.token.kind == token::OpenDelim(Delimiter::Parenthesis) {
// Record whether we are about to parse `for (`.
// This is used below for recovery in case of `for ( $stuff ) $block`
// in which case we will suggest `for $stuff $block`.
let start_span = self.token.span;
let left = self.prev_token.span.between(self.look_ahead(1, |t| t.span));
match self.parse_pat_allow_top_alt(
None,
RecoverComma::Yes,
RecoverColon::Yes,
CommaRecoveryMode::LikelyTuple,
) {
Ok(pat) => pat,
Err(err) if self.eat_keyword(kw::In) => {
let expr = match self.parse_expr_res(Restrictions::NO_STRUCT_LITERAL, None) {
Ok(expr) => expr,
Err(expr_err) => {
expr_err.cancel();
return Err(err);
}
};
return if self.token.kind == token::CloseDelim(Delimiter::Parenthesis) {
let span = vec![start_span, self.token.span];
let right = self.prev_token.span.between(self.look_ahead(1, |t| t.span));
self.bump(); // )
err.cancel();
self.sess.emit_err(errors::ParenthesesInForHead {
span,
// With e.g. `for (x) in y)` this would replace `(x) in y)`
// with `x) in y)` which is syntactically invalid.
// However, this is prevented before we get here.
sugg: errors::ParenthesesInForHeadSugg { left, right },
});
Ok((self.mk_pat(start_span.to(right), ast::PatKind::Wild), expr))
} else {
Err(err)
};
}
Err(err) => return Err(err),
}
} else {
self.parse_pat_allow_top_alt(
None,
RecoverComma::Yes,
RecoverColon::Yes,
CommaRecoveryMode::LikelyTuple,
)?
};

let pat = self.parse_pat_allow_top_alt(
None,
RecoverComma::Yes,
RecoverColon::Yes,
CommaRecoveryMode::LikelyTuple,
)?;
if !self.eat_keyword(kw::In) {
self.error_missing_in_for_loop();
}
self.check_for_for_in_in_typo(self.prev_token.span);
let expr = self.parse_expr_res(Restrictions::NO_STRUCT_LITERAL, None)?;
Ok((pat, expr))
}

let pat = self.recover_parens_around_for_head(pat, begin_paren);

/// Parses `for <src_pat> in <src_expr> <src_loop_block>` (`for` token already eaten).
fn parse_expr_for(&mut self, opt_label: Option<Label>, lo: Span) -> PResult<'a, P<Expr>> {
let (pat, expr) = self.parse_for_head()?;
// Recover from missing expression in `for` loop
if matches!(expr.kind, ExprKind::Block(..))
&& !matches!(self.token.kind, token::OpenDelim(Delimiter::Brace))
Expand Down
5 changes: 2 additions & 3 deletions tests/ui/lint/issue-103435-extra-parentheses.fixed
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ fn main() {
//~^ ERROR unnecessary parentheses around `if` condition

// reported by parser
for(_x in 1..10){}
//~^ ERROR expected one of
//~| ERROR unexpected parentheses surrounding
for _x in 1..10 {}
//~^ ERROR unexpected parentheses surrounding
}
7 changes: 3 additions & 4 deletions tests/ui/lint/issue-103435-extra-parentheses.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,10 @@ fn main() {
for(_x)in 1..10 {}
//~^ ERROR unnecessary parentheses around pattern

if(2 == 1){}
if(2 == 1) {}
//~^ ERROR unnecessary parentheses around `if` condition

// reported by parser
for(_x in 1..10){}
//~^ ERROR expected one of
//~| ERROR unexpected parentheses surrounding
for(_x in 1..10) {}
//~^ ERROR unexpected parentheses surrounding
}
18 changes: 12 additions & 6 deletions tests/ui/lint/issue-103435-extra-parentheses.stderr
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
error: expected one of `)`, `,`, `@`, or `|`, found keyword `in`
--> $DIR/issue-103435-extra-parentheses.rs:15:12
error: unexpected parentheses surrounding `for` loop head
--> $DIR/issue-103435-extra-parentheses.rs:15:8
|
LL | for(_x in 1..10) {}
| ^ ^
|
help: remove parentheses in `for` loop
|
LL - for(_x in 1..10) {}
LL + for _x in 1..10 {}
|
LL | for(_x in 1..10){}
| ^^ expected one of `)`, `,`, `@`, or `|`

error: unnecessary parentheses around pattern
--> $DIR/issue-103435-extra-parentheses.rs:5:11
Expand Down Expand Up @@ -36,12 +42,12 @@ LL + for _x in 1..10 {}
error: unnecessary parentheses around `if` condition
--> $DIR/issue-103435-extra-parentheses.rs:11:7
|
LL | if(2 == 1){}
LL | if(2 == 1) {}
| ^ ^
|
help: remove these parentheses
|
LL - if(2 == 1){}
LL - if(2 == 1) {}
LL + if 2 == 1 {}
|

Expand Down
3 changes: 2 additions & 1 deletion tests/ui/parser/recover/recover-enum2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ fn main() {
}

// recover...
let a = 1;
let () = 1; //~ ERROR mismatched types
enum Test2 {
Fine,
}
Expand All @@ -24,5 +24,6 @@ fn main() {
enum Test4 {
Nope(i32 {}) //~ ERROR: found `{`
}
let () = 1; //~ ERROR mismatched types
}
}
19 changes: 18 additions & 1 deletion tests/ui/parser/recover/recover-enum2.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,22 @@ LL | enum Test4 {
LL | Nope(i32 {})
| ^ expected one of 7 possible tokens

error: aborting due to 2 previous errors
error[E0308]: mismatched types
--> $DIR/recover-enum2.rs:11:9
|
LL | let () = 1;
| ^^ - this expression has type `{integer}`
| |
| expected integer, found `()`

error[E0308]: mismatched types
--> $DIR/recover-enum2.rs:27:13
|
LL | let () = 1;
| ^^ - this expression has type `{integer}`
| |
| expected integer, found `()`

error: aborting due to 4 previous errors

For more information about this error, try `rustc --explain E0308`.
15 changes: 15 additions & 0 deletions tests/ui/parser/recover/recover-for-loop-parens-around-head.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// run-rustfix
// Here we test that the parser is able to recover in a situation like
// `for ( $pat in $expr )` since that is familiar syntax in other languages.
// Instead we suggest that the user writes `for $pat in $expr`.

#![deny(unused)] // Make sure we don't trigger `unused_parens`.

fn main() {
let vec = vec![1, 2, 3];

for _elem in vec {
//~^ ERROR unexpected parentheses surrounding `for` loop head
const _RECOVERY_WITNESS: u32 = 0u32; //~ ERROR mismatched types
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// run-rustfix
// Here we test that the parser is able to recover in a situation like
// `for ( $pat in $expr )` since that is familiar syntax in other languages.
// Instead we suggest that the user writes `for $pat in $expr`.
Expand All @@ -7,9 +8,8 @@
fn main() {
let vec = vec![1, 2, 3];

for ( elem in vec ) {
//~^ ERROR expected one of `)`, `,`, `@`, or `|`, found keyword `in`
//~| ERROR unexpected parentheses surrounding `for` loop head
const RECOVERY_WITNESS: () = 0; //~ ERROR mismatched types
for ( _elem in vec ) {
//~^ ERROR unexpected parentheses surrounding `for` loop head
const _RECOVERY_WITNESS: u32 = 0u8; //~ ERROR mismatched types
}
}
28 changes: 23 additions & 5 deletions tests/ui/parser/recover/recover-for-loop-parens-around-head.stderr
Original file line number Diff line number Diff line change
@@ -1,8 +1,26 @@
error: expected one of `)`, `,`, `@`, or `|`, found keyword `in`
--> $DIR/recover-for-loop-parens-around-head.rs:10:16
error: unexpected parentheses surrounding `for` loop head
--> $DIR/recover-for-loop-parens-around-head.rs:11:9
|
LL | for ( elem in vec ) {
| ^^ expected one of `)`, `,`, `@`, or `|`
LL | for ( _elem in vec ) {
| ^ ^
|
help: remove parentheses in `for` loop
|
LL - for ( _elem in vec ) {
LL + for _elem in vec {
|

error[E0308]: mismatched types
--> $DIR/recover-for-loop-parens-around-head.rs:13:40
|
LL | const _RECOVERY_WITNESS: u32 = 0u8;
| ^^^ expected `u32`, found `u8`
|
help: change the type of the numeric literal from `u8` to `u32`
|
LL | const _RECOVERY_WITNESS: u32 = 0u32;
| ~~~

error: aborting due to previous error
error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0308`.

0 comments on commit 0ef0cc4

Please sign in to comment.