Skip to content

Commit a76582e

Browse files
authored
Rollup merge of rust-lang#117565 - estebank:issue-100825, r=Nilstrieb
Tweak parsing recovery of enums, for exprs and match arm patterns Tweak recovery of `for (pat in expr) {}` for more accurate spans. When encountering `match` arm `(pat if expr) => {}`, recover and suggest removing parentheses. Fix rust-lang#100825. When encountering malformed enums, try more localized per-variant parse recovery. Move parser recovery tests to subdirectory.
2 parents b1e56de + b4b5981 commit a76582e

24 files changed

+346
-215
lines changed

compiler/rustc_parse/messages.ftl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -773,6 +773,9 @@ parse_unexpected_lifetime_in_pattern = unexpected lifetime `{$symbol}` in patter
773773
parse_unexpected_parentheses_in_for_head = unexpected parentheses surrounding `for` loop head
774774
.suggestion = remove parentheses in `for` loop
775775
776+
parse_unexpected_parentheses_in_match_arm_pattern = unexpected parentheses surrounding `match` arm pattern
777+
.suggestion = remove parentheses surrounding the pattern
778+
776779
parse_unexpected_self_in_generic_parameters = unexpected keyword `Self` in generic parameters
777780
.note = you cannot use `Self` as a generic parameter because it is reserved for associated items
778781

compiler/rustc_parse/src/errors.rs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1249,12 +1249,28 @@ pub(crate) struct ParenthesesInForHead {
12491249
#[derive(Subdiagnostic)]
12501250
#[multipart_suggestion(parse_suggestion, applicability = "machine-applicable")]
12511251
pub(crate) struct ParenthesesInForHeadSugg {
1252-
#[suggestion_part(code = "{left_snippet}")]
1252+
#[suggestion_part(code = " ")]
1253+
pub left: Span,
1254+
#[suggestion_part(code = " ")]
1255+
pub right: Span,
1256+
}
1257+
1258+
#[derive(Diagnostic)]
1259+
#[diag(parse_unexpected_parentheses_in_match_arm_pattern)]
1260+
pub(crate) struct ParenthesesInMatchPat {
1261+
#[primary_span]
1262+
pub span: Vec<Span>,
1263+
#[subdiagnostic]
1264+
pub sugg: ParenthesesInMatchPatSugg,
1265+
}
1266+
1267+
#[derive(Subdiagnostic)]
1268+
#[multipart_suggestion(parse_suggestion, applicability = "machine-applicable")]
1269+
pub(crate) struct ParenthesesInMatchPatSugg {
1270+
#[suggestion_part(code = "")]
12531271
pub left: Span,
1254-
pub left_snippet: String,
1255-
#[suggestion_part(code = "{right_snippet}")]
1272+
#[suggestion_part(code = "")]
12561273
pub right: Span,
1257-
pub right_snippet: String,
12581274
}
12591275

12601276
#[derive(Diagnostic)]

compiler/rustc_parse/src/parser/diagnostics.rs

Lines changed: 6 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ use crate::errors::{
1111
DoubleColonInBound, ExpectedIdentifier, ExpectedSemi, ExpectedSemiSugg,
1212
GenericParamsWithoutAngleBrackets, GenericParamsWithoutAngleBracketsSugg,
1313
HelpIdentifierStartsWithNumber, InInTypo, IncorrectAwait, IncorrectSemicolon,
14-
IncorrectUseOfAwait, ParenthesesInForHead, ParenthesesInForHeadSugg,
15-
PatternMethodParamWithoutBody, QuestionMarkInType, QuestionMarkInTypeSugg, SelfParamNotFirst,
16-
StructLiteralBodyWithoutPath, StructLiteralBodyWithoutPathSugg, StructLiteralNeedingParens,
17-
StructLiteralNeedingParensSugg, SuggAddMissingLetStmt, SuggEscapeIdentifier, SuggRemoveComma,
18-
TernaryOperator, UnexpectedConstInGenericParam, UnexpectedConstParamDeclaration,
19-
UnexpectedConstParamDeclarationSugg, UnmatchedAngleBrackets, UseEqInstead, WrapType,
14+
IncorrectUseOfAwait, PatternMethodParamWithoutBody, QuestionMarkInType, QuestionMarkInTypeSugg,
15+
SelfParamNotFirst, StructLiteralBodyWithoutPath, StructLiteralBodyWithoutPathSugg,
16+
StructLiteralNeedingParens, StructLiteralNeedingParensSugg, SuggAddMissingLetStmt,
17+
SuggEscapeIdentifier, SuggRemoveComma, TernaryOperator, UnexpectedConstInGenericParam,
18+
UnexpectedConstParamDeclaration, UnexpectedConstParamDeclarationSugg, UnmatchedAngleBrackets,
19+
UseEqInstead, WrapType,
2020
};
2121

2222
use crate::fluent_generated as fluent;
@@ -1994,56 +1994,6 @@ impl<'a> Parser<'a> {
19941994
}
19951995
}
19961996

1997-
/// Recovers a situation like `for ( $pat in $expr )`
1998-
/// and suggest writing `for $pat in $expr` instead.
1999-
///
2000-
/// This should be called before parsing the `$block`.
2001-
pub(super) fn recover_parens_around_for_head(
2002-
&mut self,
2003-
pat: P<Pat>,
2004-
begin_paren: Option<Span>,
2005-
) -> P<Pat> {
2006-
match (&self.token.kind, begin_paren) {
2007-
(token::CloseDelim(Delimiter::Parenthesis), Some(begin_par_sp)) => {
2008-
self.bump();
2009-
2010-
let sm = self.sess.source_map();
2011-
let left = begin_par_sp;
2012-
let right = self.prev_token.span;
2013-
let left_snippet = if let Ok(snip) = sm.span_to_prev_source(left)
2014-
&& !snip.ends_with(' ')
2015-
{
2016-
" ".to_string()
2017-
} else {
2018-
"".to_string()
2019-
};
2020-
2021-
let right_snippet = if let Ok(snip) = sm.span_to_next_source(right)
2022-
&& !snip.starts_with(' ')
2023-
{
2024-
" ".to_string()
2025-
} else {
2026-
"".to_string()
2027-
};
2028-
2029-
self.sess.emit_err(ParenthesesInForHead {
2030-
span: vec![left, right],
2031-
// With e.g. `for (x) in y)` this would replace `(x) in y)`
2032-
// with `x) in y)` which is syntactically invalid.
2033-
// However, this is prevented before we get here.
2034-
sugg: ParenthesesInForHeadSugg { left, right, left_snippet, right_snippet },
2035-
});
2036-
2037-
// Unwrap `(pat)` into `pat` to avoid the `unused_parens` lint.
2038-
pat.and_then(|pat| match pat.kind {
2039-
PatKind::Paren(pat) => pat,
2040-
_ => P(pat),
2041-
})
2042-
}
2043-
_ => pat,
2044-
}
2045-
}
2046-
20471997
pub(super) fn recover_seq_parse_error(
20481998
&mut self,
20491999
delim: Delimiter,

compiler/rustc_parse/src/parser/expr.rs

Lines changed: 146 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// ignore-tidy-filelength
12
use super::diagnostics::SnapshotParser;
23
use super::pat::{CommaRecoveryMode, Expected, RecoverColon, RecoverComma};
34
use super::ty::{AllowPlus, RecoverQPath, RecoverReturnSign};
@@ -9,7 +10,7 @@ use super::{
910
use crate::errors;
1011
use crate::maybe_recover_from_interpolated_ty_qpath;
1112
use ast::mut_visit::{noop_visit_expr, MutVisitor};
12-
use ast::{GenBlockKind, Path, PathSegment};
13+
use ast::{GenBlockKind, Pat, Path, PathSegment};
1314
use core::mem;
1415
use rustc_ast::ptr::P;
1516
use rustc_ast::token::{self, Delimiter, Token, TokenKind};
@@ -2606,30 +2607,72 @@ impl<'a> Parser<'a> {
26062607
}
26072608
}
26082609

2609-
/// Parses `for <src_pat> in <src_expr> <src_loop_block>` (`for` token already eaten).
2610-
fn parse_expr_for(&mut self, opt_label: Option<Label>, lo: Span) -> PResult<'a, P<Expr>> {
2611-
// Record whether we are about to parse `for (`.
2612-
// This is used below for recovery in case of `for ( $stuff ) $block`
2613-
// in which case we will suggest `for $stuff $block`.
2614-
let begin_paren = match self.token.kind {
2615-
token::OpenDelim(Delimiter::Parenthesis) => Some(self.token.span),
2616-
_ => None,
2610+
fn parse_for_head(&mut self) -> PResult<'a, (P<Pat>, P<Expr>)> {
2611+
let begin_paren = if self.token.kind == token::OpenDelim(Delimiter::Parenthesis) {
2612+
// Record whether we are about to parse `for (`.
2613+
// This is used below for recovery in case of `for ( $stuff ) $block`
2614+
// in which case we will suggest `for $stuff $block`.
2615+
let start_span = self.token.span;
2616+
let left = self.prev_token.span.between(self.look_ahead(1, |t| t.span));
2617+
Some((start_span, left))
2618+
} else {
2619+
None
2620+
};
2621+
// Try to parse the pattern `for ($PAT) in $EXPR`.
2622+
let pat = match (
2623+
self.parse_pat_allow_top_alt(
2624+
None,
2625+
RecoverComma::Yes,
2626+
RecoverColon::Yes,
2627+
CommaRecoveryMode::LikelyTuple,
2628+
),
2629+
begin_paren,
2630+
) {
2631+
(Ok(pat), _) => pat, // Happy path.
2632+
(Err(err), Some((start_span, left))) if self.eat_keyword(kw::In) => {
2633+
// We know for sure we have seen `for ($SOMETHING in`. In the happy path this would
2634+
// happen right before the return of this method.
2635+
let expr = match self.parse_expr_res(Restrictions::NO_STRUCT_LITERAL, None) {
2636+
Ok(expr) => expr,
2637+
Err(expr_err) => {
2638+
// We don't know what followed the `in`, so cancel and bubble up the
2639+
// original error.
2640+
expr_err.cancel();
2641+
return Err(err);
2642+
}
2643+
};
2644+
return if self.token.kind == token::CloseDelim(Delimiter::Parenthesis) {
2645+
// We know for sure we have seen `for ($SOMETHING in $EXPR)`, so we recover the
2646+
// parser state and emit a targetted suggestion.
2647+
let span = vec![start_span, self.token.span];
2648+
let right = self.prev_token.span.between(self.look_ahead(1, |t| t.span));
2649+
self.bump(); // )
2650+
err.cancel();
2651+
self.sess.emit_err(errors::ParenthesesInForHead {
2652+
span,
2653+
// With e.g. `for (x) in y)` this would replace `(x) in y)`
2654+
// with `x) in y)` which is syntactically invalid.
2655+
// However, this is prevented before we get here.
2656+
sugg: errors::ParenthesesInForHeadSugg { left, right },
2657+
});
2658+
Ok((self.mk_pat(start_span.to(right), ast::PatKind::Wild), expr))
2659+
} else {
2660+
Err(err) // Some other error, bubble up.
2661+
};
2662+
}
2663+
(Err(err), _) => return Err(err), // Some other error, bubble up.
26172664
};
2618-
2619-
let pat = self.parse_pat_allow_top_alt(
2620-
None,
2621-
RecoverComma::Yes,
2622-
RecoverColon::Yes,
2623-
CommaRecoveryMode::LikelyTuple,
2624-
)?;
26252665
if !self.eat_keyword(kw::In) {
26262666
self.error_missing_in_for_loop();
26272667
}
26282668
self.check_for_for_in_in_typo(self.prev_token.span);
26292669
let expr = self.parse_expr_res(Restrictions::NO_STRUCT_LITERAL, None)?;
2670+
Ok((pat, expr))
2671+
}
26302672

2631-
let pat = self.recover_parens_around_for_head(pat, begin_paren);
2632-
2673+
/// Parses `for <src_pat> in <src_expr> <src_loop_block>` (`for` token already eaten).
2674+
fn parse_expr_for(&mut self, opt_label: Option<Label>, lo: Span) -> PResult<'a, P<Expr>> {
2675+
let (pat, expr) = self.parse_for_head()?;
26332676
// Recover from missing expression in `for` loop
26342677
if matches!(expr.kind, ExprKind::Block(..))
26352678
&& !matches!(self.token.kind, token::OpenDelim(Delimiter::Brace))
@@ -2850,47 +2893,10 @@ impl<'a> Parser<'a> {
28502893
}
28512894

28522895
pub(super) fn parse_arm(&mut self) -> PResult<'a, Arm> {
2853-
// Used to check the `let_chains` and `if_let_guard` features mostly by scanning
2854-
// `&&` tokens.
2855-
fn check_let_expr(expr: &Expr) -> (bool, bool) {
2856-
match &expr.kind {
2857-
ExprKind::Binary(BinOp { node: BinOpKind::And, .. }, lhs, rhs) => {
2858-
let lhs_rslt = check_let_expr(lhs);
2859-
let rhs_rslt = check_let_expr(rhs);
2860-
(lhs_rslt.0 || rhs_rslt.0, false)
2861-
}
2862-
ExprKind::Let(..) => (true, true),
2863-
_ => (false, true),
2864-
}
2865-
}
28662896
let attrs = self.parse_outer_attributes()?;
28672897
self.collect_tokens_trailing_token(attrs, ForceCollect::No, |this, attrs| {
28682898
let lo = this.token.span;
2869-
let pat = this.parse_pat_allow_top_alt(
2870-
None,
2871-
RecoverComma::Yes,
2872-
RecoverColon::Yes,
2873-
CommaRecoveryMode::EitherTupleOrPipe,
2874-
)?;
2875-
let guard = if this.eat_keyword(kw::If) {
2876-
let if_span = this.prev_token.span;
2877-
let mut cond = this.parse_match_guard_condition()?;
2878-
2879-
CondChecker { parser: this, forbid_let_reason: None }.visit_expr(&mut cond);
2880-
2881-
let (has_let_expr, does_not_have_bin_op) = check_let_expr(&cond);
2882-
if has_let_expr {
2883-
if does_not_have_bin_op {
2884-
// Remove the last feature gating of a `let` expression since it's stable.
2885-
this.sess.gated_spans.ungate_last(sym::let_chains, cond.span);
2886-
}
2887-
let span = if_span.to(cond.span);
2888-
this.sess.gated_spans.gate(sym::if_let_guard, span);
2889-
}
2890-
Some(cond)
2891-
} else {
2892-
None
2893-
};
2899+
let (pat, guard) = this.parse_match_arm_pat_and_guard()?;
28942900
let arrow_span = this.token.span;
28952901
if let Err(mut err) = this.expect(&token::FatArrow) {
28962902
// We might have a `=>` -> `=` or `->` typo (issue #89396).
@@ -3020,6 +3026,90 @@ impl<'a> Parser<'a> {
30203026
})
30213027
}
30223028

3029+
fn parse_match_arm_guard(&mut self) -> PResult<'a, Option<P<Expr>>> {
3030+
// Used to check the `let_chains` and `if_let_guard` features mostly by scanning
3031+
// `&&` tokens.
3032+
fn check_let_expr(expr: &Expr) -> (bool, bool) {
3033+
match &expr.kind {
3034+
ExprKind::Binary(BinOp { node: BinOpKind::And, .. }, lhs, rhs) => {
3035+
let lhs_rslt = check_let_expr(lhs);
3036+
let rhs_rslt = check_let_expr(rhs);
3037+
(lhs_rslt.0 || rhs_rslt.0, false)
3038+
}
3039+
ExprKind::Let(..) => (true, true),
3040+
_ => (false, true),
3041+
}
3042+
}
3043+
if !self.eat_keyword(kw::If) {
3044+
// No match arm guard present.
3045+
return Ok(None);
3046+
}
3047+
3048+
let if_span = self.prev_token.span;
3049+
let mut cond = self.parse_match_guard_condition()?;
3050+
3051+
CondChecker { parser: self, forbid_let_reason: None }.visit_expr(&mut cond);
3052+
3053+
let (has_let_expr, does_not_have_bin_op) = check_let_expr(&cond);
3054+
if has_let_expr {
3055+
if does_not_have_bin_op {
3056+
// Remove the last feature gating of a `let` expression since it's stable.
3057+
self.sess.gated_spans.ungate_last(sym::let_chains, cond.span);
3058+
}
3059+
let span = if_span.to(cond.span);
3060+
self.sess.gated_spans.gate(sym::if_let_guard, span);
3061+
}
3062+
Ok(Some(cond))
3063+
}
3064+
3065+
fn parse_match_arm_pat_and_guard(&mut self) -> PResult<'a, (P<Pat>, Option<P<Expr>>)> {
3066+
if self.token.kind == token::OpenDelim(Delimiter::Parenthesis) {
3067+
// Detect and recover from `($pat if $cond) => $arm`.
3068+
let left = self.token.span;
3069+
match self.parse_pat_allow_top_alt(
3070+
None,
3071+
RecoverComma::Yes,
3072+
RecoverColon::Yes,
3073+
CommaRecoveryMode::EitherTupleOrPipe,
3074+
) {
3075+
Ok(pat) => Ok((pat, self.parse_match_arm_guard()?)),
3076+
Err(err)
3077+
if let prev_sp = self.prev_token.span
3078+
&& let true = self.eat_keyword(kw::If) =>
3079+
{
3080+
// We know for certain we've found `($pat if` so far.
3081+
let mut cond = match self.parse_match_guard_condition() {
3082+
Ok(cond) => cond,
3083+
Err(cond_err) => {
3084+
cond_err.cancel();
3085+
return Err(err);
3086+
}
3087+
};
3088+
err.cancel();
3089+
CondChecker { parser: self, forbid_let_reason: None }.visit_expr(&mut cond);
3090+
self.eat_to_tokens(&[&token::CloseDelim(Delimiter::Parenthesis)]);
3091+
self.expect(&token::CloseDelim(Delimiter::Parenthesis))?;
3092+
let right = self.prev_token.span;
3093+
self.sess.emit_err(errors::ParenthesesInMatchPat {
3094+
span: vec![left, right],
3095+
sugg: errors::ParenthesesInMatchPatSugg { left, right },
3096+
});
3097+
Ok((self.mk_pat(left.to(prev_sp), ast::PatKind::Wild), Some(cond)))
3098+
}
3099+
Err(err) => Err(err),
3100+
}
3101+
} else {
3102+
// Regular parser flow:
3103+
let pat = self.parse_pat_allow_top_alt(
3104+
None,
3105+
RecoverComma::Yes,
3106+
RecoverColon::Yes,
3107+
CommaRecoveryMode::EitherTupleOrPipe,
3108+
)?;
3109+
Ok((pat, self.parse_match_arm_guard()?))
3110+
}
3111+
}
3112+
30233113
fn parse_match_guard_condition(&mut self) -> PResult<'a, P<Expr>> {
30243114
self.parse_expr_res(Restrictions::ALLOW_LET | Restrictions::IN_IF_GUARD, None).map_err(
30253115
|mut err| {

0 commit comments

Comments
 (0)