Skip to content

Commit cc74eaa

Browse files
committed
Auto merge of #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 #100825. When encountering malformed enums, try more localized per-variant parse recovery. Move parser recovery tests to subdirectory.
2 parents b10cfcd + 88453aa commit cc74eaa

24 files changed

+348
-218
lines changed

compiler/rustc_parse/messages.ftl

+3
Original file line numberDiff line numberDiff line change
@@ -777,6 +777,9 @@ parse_unexpected_lifetime_in_pattern = unexpected lifetime `{$symbol}` in patter
777777
parse_unexpected_parentheses_in_for_head = unexpected parentheses surrounding `for` loop head
778778
.suggestion = remove parentheses in `for` loop
779779
780+
parse_unexpected_parentheses_in_match_arm_pattern = unexpected parentheses surrounding `match` arm pattern
781+
.suggestion = remove parentheses surrounding the pattern
782+
780783
parse_unexpected_self_in_generic_parameters = unexpected keyword `Self` in generic parameters
781784
.note = you cannot use `Self` as a generic parameter because it is reserved for associated items
782785

compiler/rustc_parse/src/errors.rs

+20-4
Original file line numberDiff line numberDiff line change
@@ -1275,12 +1275,28 @@ pub(crate) struct ParenthesesInForHead {
12751275
#[derive(Subdiagnostic)]
12761276
#[multipart_suggestion(parse_suggestion, applicability = "machine-applicable")]
12771277
pub(crate) struct ParenthesesInForHeadSugg {
1278-
#[suggestion_part(code = "{left_snippet}")]
1278+
#[suggestion_part(code = " ")]
1279+
pub left: Span,
1280+
#[suggestion_part(code = " ")]
1281+
pub right: Span,
1282+
}
1283+
1284+
#[derive(Diagnostic)]
1285+
#[diag(parse_unexpected_parentheses_in_match_arm_pattern)]
1286+
pub(crate) struct ParenthesesInMatchPat {
1287+
#[primary_span]
1288+
pub span: Vec<Span>,
1289+
#[subdiagnostic]
1290+
pub sugg: ParenthesesInMatchPatSugg,
1291+
}
1292+
1293+
#[derive(Subdiagnostic)]
1294+
#[multipart_suggestion(parse_suggestion, applicability = "machine-applicable")]
1295+
pub(crate) struct ParenthesesInMatchPatSugg {
1296+
#[suggestion_part(code = "")]
12791297
pub left: Span,
1280-
pub left_snippet: String,
1281-
#[suggestion_part(code = "{right_snippet}")]
1298+
#[suggestion_part(code = "")]
12821299
pub right: Span,
1283-
pub right_snippet: String,
12841300
}
12851301

12861302
#[derive(Diagnostic)]

compiler/rustc_parse/src/parser/diagnostics.rs

+6-56
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

+145-56
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use super::{
1010
use crate::errors;
1111
use crate::maybe_recover_from_interpolated_ty_qpath;
1212
use ast::mut_visit::{noop_visit_expr, MutVisitor};
13-
use ast::{GenBlockKind, Path, PathSegment};
13+
use ast::{GenBlockKind, Pat, Path, PathSegment};
1414
use core::mem;
1515
use rustc_ast::ptr::P;
1616
use rustc_ast::token::{self, Delimiter, Token, TokenKind};
@@ -2609,30 +2609,72 @@ impl<'a> Parser<'a> {
26092609
}
26102610
}
26112611

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

2634-
let pat = self.recover_parens_around_for_head(pat, begin_paren);
2635-
2675+
/// Parses `for <src_pat> in <src_expr> <src_loop_block>` (`for` token already eaten).
2676+
fn parse_expr_for(&mut self, opt_label: Option<Label>, lo: Span) -> PResult<'a, P<Expr>> {
2677+
let (pat, expr) = self.parse_for_head()?;
26362678
// Recover from missing expression in `for` loop
26372679
if matches!(expr.kind, ExprKind::Block(..))
26382680
&& !matches!(self.token.kind, token::OpenDelim(Delimiter::Brace))
@@ -2853,47 +2895,10 @@ impl<'a> Parser<'a> {
28532895
}
28542896

28552897
pub(super) fn parse_arm(&mut self) -> PResult<'a, Arm> {
2856-
// Used to check the `let_chains` and `if_let_guard` features mostly by scanning
2857-
// `&&` tokens.
2858-
fn check_let_expr(expr: &Expr) -> (bool, bool) {
2859-
match &expr.kind {
2860-
ExprKind::Binary(BinOp { node: BinOpKind::And, .. }, lhs, rhs) => {
2861-
let lhs_rslt = check_let_expr(lhs);
2862-
let rhs_rslt = check_let_expr(rhs);
2863-
(lhs_rslt.0 || rhs_rslt.0, false)
2864-
}
2865-
ExprKind::Let(..) => (true, true),
2866-
_ => (false, true),
2867-
}
2868-
}
28692898
let attrs = self.parse_outer_attributes()?;
28702899
self.collect_tokens_trailing_token(attrs, ForceCollect::No, |this, attrs| {
28712900
let lo = this.token.span;
2872-
let pat = this.parse_pat_allow_top_alt(
2873-
None,
2874-
RecoverComma::Yes,
2875-
RecoverColon::Yes,
2876-
CommaRecoveryMode::EitherTupleOrPipe,
2877-
)?;
2878-
let guard = if this.eat_keyword(kw::If) {
2879-
let if_span = this.prev_token.span;
2880-
let mut cond = this.parse_match_guard_condition()?;
2881-
2882-
CondChecker::new(this).visit_expr(&mut cond);
2883-
2884-
let (has_let_expr, does_not_have_bin_op) = check_let_expr(&cond);
2885-
if has_let_expr {
2886-
if does_not_have_bin_op {
2887-
// Remove the last feature gating of a `let` expression since it's stable.
2888-
this.sess.gated_spans.ungate_last(sym::let_chains, cond.span);
2889-
}
2890-
let span = if_span.to(cond.span);
2891-
this.sess.gated_spans.gate(sym::if_let_guard, span);
2892-
}
2893-
Some(cond)
2894-
} else {
2895-
None
2896-
};
2901+
let (pat, guard) = this.parse_match_arm_pat_and_guard()?;
28972902
let arrow_span = this.token.span;
28982903
if let Err(mut err) = this.expect(&token::FatArrow) {
28992904
// We might have a `=>` -> `=` or `->` typo (issue #89396).
@@ -3023,6 +3028,90 @@ impl<'a> Parser<'a> {
30233028
})
30243029
}
30253030

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

0 commit comments

Comments
 (0)