Skip to content

Commit 8c4ef89

Browse files
committed
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. Move parser recovery tests to subdirectory.
1 parent 722b3ee commit 8c4ef89

File tree

81 files changed

+391
-217
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

81 files changed

+391
-217
lines changed

compiler/rustc_parse/messages.ftl

+3
Original file line numberDiff line numberDiff line change
@@ -764,6 +764,9 @@ parse_unexpected_lifetime_in_pattern = unexpected lifetime `{$symbol}` in patter
764764
parse_unexpected_parentheses_in_for_head = unexpected parentheses surrounding `for` loop head
765765
.suggestion = remove parentheses in `for` loop
766766
767+
parse_unexpected_parentheses_in_match_arm_pattern = unexpected parentheses surrounding `match` arm pattern
768+
.suggestion = remove parentheses surrounding the pattern
769+
767770
parse_unexpected_self_in_generic_parameters = unexpected keyword `Self` in generic parameters
768771
.note = you cannot use `Self` as a generic parameter because it is reserved for associated items
769772

compiler/rustc_parse/src/errors.rs

+21-6
Original file line numberDiff line numberDiff line change
@@ -1241,12 +1241,28 @@ pub(crate) struct ParenthesesInForHead {
12411241
#[derive(Subdiagnostic)]
12421242
#[multipart_suggestion(parse_suggestion, applicability = "machine-applicable")]
12431243
pub(crate) struct ParenthesesInForHeadSugg {
1244-
#[suggestion_part(code = "{left_snippet}")]
1244+
#[suggestion_part(code = " ")]
1245+
pub left: Span,
1246+
#[suggestion_part(code = " ")]
1247+
pub right: Span,
1248+
}
1249+
1250+
#[derive(Diagnostic)]
1251+
#[diag(parse_unexpected_parentheses_in_match_arm_pattern)]
1252+
pub(crate) struct ParenthesesInMatchPat {
1253+
#[primary_span]
1254+
pub span: Vec<Span>,
1255+
#[subdiagnostic]
1256+
pub sugg: ParenthesesInMatchPatSugg,
1257+
}
1258+
1259+
#[derive(Subdiagnostic)]
1260+
#[multipart_suggestion(parse_suggestion, applicability = "machine-applicable")]
1261+
pub(crate) struct ParenthesesInMatchPatSugg {
1262+
#[suggestion_part(code = "")]
12451263
pub left: Span,
1246-
pub left_snippet: String,
1247-
#[suggestion_part(code = "{right_snippet}")]
1264+
#[suggestion_part(code = "")]
12481265
pub right: Span,
1249-
pub right_snippet: String,
12501266
}
12511267

12521268
#[derive(Diagnostic)]
@@ -2250,9 +2266,8 @@ pub(crate) enum InvalidMutInPattern {
22502266
#[note(parse_note_mut_pattern_usage)]
22512267
NonIdent {
22522268
#[primary_span]
2253-
#[suggestion(code = "{pat}", applicability = "machine-applicable")]
2269+
#[suggestion(code = "", applicability = "machine-applicable")]
22542270
span: Span,
2255-
pat: String,
22562271
},
22572272
}
22582273

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;
@@ -1895,56 +1895,6 @@ impl<'a> Parser<'a> {
18951895
}
18961896
}
18971897

1898-
/// Recovers a situation like `for ( $pat in $expr )`
1899-
/// and suggest writing `for $pat in $expr` instead.
1900-
///
1901-
/// This should be called before parsing the `$block`.
1902-
pub(super) fn recover_parens_around_for_head(
1903-
&mut self,
1904-
pat: P<Pat>,
1905-
begin_paren: Option<Span>,
1906-
) -> P<Pat> {
1907-
match (&self.token.kind, begin_paren) {
1908-
(token::CloseDelim(Delimiter::Parenthesis), Some(begin_par_sp)) => {
1909-
self.bump();
1910-
1911-
let sm = self.sess.source_map();
1912-
let left = begin_par_sp;
1913-
let right = self.prev_token.span;
1914-
let left_snippet = if let Ok(snip) = sm.span_to_prev_source(left)
1915-
&& !snip.ends_with(' ')
1916-
{
1917-
" ".to_string()
1918-
} else {
1919-
"".to_string()
1920-
};
1921-
1922-
let right_snippet = if let Ok(snip) = sm.span_to_next_source(right)
1923-
&& !snip.starts_with(' ')
1924-
{
1925-
" ".to_string()
1926-
} else {
1927-
"".to_string()
1928-
};
1929-
1930-
self.sess.emit_err(ParenthesesInForHead {
1931-
span: vec![left, right],
1932-
// With e.g. `for (x) in y)` this would replace `(x) in y)`
1933-
// with `x) in y)` which is syntactically invalid.
1934-
// However, this is prevented before we get here.
1935-
sugg: ParenthesesInForHeadSugg { left, right, left_snippet, right_snippet },
1936-
});
1937-
1938-
// Unwrap `(pat)` into `pat` to avoid the `unused_parens` lint.
1939-
pat.and_then(|pat| match pat.kind {
1940-
PatKind::Paren(pat) => pat,
1941-
_ => P(pat),
1942-
})
1943-
}
1944-
_ => pat,
1945-
}
1946-
}
1947-
19481898
pub(super) fn recover_seq_parse_error(
19491899
&mut self,
19501900
delim: Delimiter,

compiler/rustc_parse/src/parser/expr.rs

+136-56
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use super::{
99
use crate::errors;
1010
use crate::maybe_recover_from_interpolated_ty_qpath;
1111
use ast::mut_visit::{noop_visit_expr, MutVisitor};
12-
use ast::{GenBlockKind, Path, PathSegment};
12+
use ast::{GenBlockKind, Pat, Path, PathSegment};
1313
use core::mem;
1414
use rustc_ast::ptr::P;
1515
use rustc_ast::token::{self, Delimiter, Token, TokenKind};
@@ -2589,30 +2589,66 @@ impl<'a> Parser<'a> {
25892589
}
25902590
}
25912591

2592-
/// Parses `for <src_pat> in <src_expr> <src_loop_block>` (`for` token already eaten).
2593-
fn parse_expr_for(&mut self, opt_label: Option<Label>, lo: Span) -> PResult<'a, P<Expr>> {
2594-
// Record whether we are about to parse `for (`.
2595-
// This is used below for recovery in case of `for ( $stuff ) $block`
2596-
// in which case we will suggest `for $stuff $block`.
2597-
let begin_paren = match self.token.kind {
2598-
token::OpenDelim(Delimiter::Parenthesis) => Some(self.token.span),
2599-
_ => None,
2592+
fn parse_for_head(&mut self) -> PResult<'a, (P<Pat>, P<Expr>)> {
2593+
let pat = if self.token.kind == token::OpenDelim(Delimiter::Parenthesis) {
2594+
// Record whether we are about to parse `for (`.
2595+
// This is used below for recovery in case of `for ( $stuff ) $block`
2596+
// in which case we will suggest `for $stuff $block`.
2597+
let start_span = self.token.span;
2598+
let left = self.prev_token.span.between(self.look_ahead(1, |t| t.span));
2599+
match self.parse_pat_allow_top_alt(
2600+
None,
2601+
RecoverComma::Yes,
2602+
RecoverColon::Yes,
2603+
CommaRecoveryMode::LikelyTuple,
2604+
) {
2605+
Ok(pat) => pat,
2606+
Err(err) if self.eat_keyword(kw::In) => {
2607+
let expr = match self.parse_expr_res(Restrictions::NO_STRUCT_LITERAL, None) {
2608+
Ok(expr) => expr,
2609+
Err(expr_err) => {
2610+
expr_err.cancel();
2611+
return Err(err);
2612+
}
2613+
};
2614+
return if self.token.kind == token::CloseDelim(Delimiter::Parenthesis) {
2615+
let span = vec![start_span, self.token.span];
2616+
let right = self.prev_token.span.between(self.look_ahead(1, |t| t.span));
2617+
self.bump(); // )
2618+
err.cancel();
2619+
self.sess.emit_err(errors::ParenthesesInForHead {
2620+
span,
2621+
// With e.g. `for (x) in y)` this would replace `(x) in y)`
2622+
// with `x) in y)` which is syntactically invalid.
2623+
// However, this is prevented before we get here.
2624+
sugg: errors::ParenthesesInForHeadSugg { left, right },
2625+
});
2626+
Ok((self.mk_pat(start_span.to(right), ast::PatKind::Wild), expr))
2627+
} else {
2628+
Err(err)
2629+
};
2630+
}
2631+
Err(err) => return Err(err),
2632+
}
2633+
} else {
2634+
self.parse_pat_allow_top_alt(
2635+
None,
2636+
RecoverComma::Yes,
2637+
RecoverColon::Yes,
2638+
CommaRecoveryMode::LikelyTuple,
2639+
)?
26002640
};
2601-
2602-
let pat = self.parse_pat_allow_top_alt(
2603-
None,
2604-
RecoverComma::Yes,
2605-
RecoverColon::Yes,
2606-
CommaRecoveryMode::LikelyTuple,
2607-
)?;
26082641
if !self.eat_keyword(kw::In) {
26092642
self.error_missing_in_for_loop();
26102643
}
26112644
self.check_for_for_in_in_typo(self.prev_token.span);
26122645
let expr = self.parse_expr_res(Restrictions::NO_STRUCT_LITERAL, None)?;
2646+
Ok((pat, expr))
2647+
}
26132648

2614-
let pat = self.recover_parens_around_for_head(pat, begin_paren);
2615-
2649+
/// Parses `for <src_pat> in <src_expr> <src_loop_block>` (`for` token already eaten).
2650+
fn parse_expr_for(&mut self, opt_label: Option<Label>, lo: Span) -> PResult<'a, P<Expr>> {
2651+
let (pat, expr) = self.parse_for_head()?;
26162652
// Recover from missing expression in `for` loop
26172653
if matches!(expr.kind, ExprKind::Block(..))
26182654
&& !matches!(self.token.kind, token::OpenDelim(Delimiter::Brace))
@@ -2833,47 +2869,10 @@ impl<'a> Parser<'a> {
28332869
}
28342870

28352871
pub(super) fn parse_arm(&mut self) -> PResult<'a, Arm> {
2836-
// Used to check the `let_chains` and `if_let_guard` features mostly by scanning
2837-
// `&&` tokens.
2838-
fn check_let_expr(expr: &Expr) -> (bool, bool) {
2839-
match &expr.kind {
2840-
ExprKind::Binary(BinOp { node: BinOpKind::And, .. }, lhs, rhs) => {
2841-
let lhs_rslt = check_let_expr(lhs);
2842-
let rhs_rslt = check_let_expr(rhs);
2843-
(lhs_rslt.0 || rhs_rslt.0, false)
2844-
}
2845-
ExprKind::Let(..) => (true, true),
2846-
_ => (false, true),
2847-
}
2848-
}
28492872
let attrs = self.parse_outer_attributes()?;
28502873
self.collect_tokens_trailing_token(attrs, ForceCollect::No, |this, attrs| {
28512874
let lo = this.token.span;
2852-
let pat = this.parse_pat_allow_top_alt(
2853-
None,
2854-
RecoverComma::Yes,
2855-
RecoverColon::Yes,
2856-
CommaRecoveryMode::EitherTupleOrPipe,
2857-
)?;
2858-
let guard = if this.eat_keyword(kw::If) {
2859-
let if_span = this.prev_token.span;
2860-
let mut cond = this.parse_match_guard_condition()?;
2861-
2862-
CondChecker { parser: this, forbid_let_reason: None }.visit_expr(&mut cond);
2863-
2864-
let (has_let_expr, does_not_have_bin_op) = check_let_expr(&cond);
2865-
if has_let_expr {
2866-
if does_not_have_bin_op {
2867-
// Remove the last feature gating of a `let` expression since it's stable.
2868-
this.sess.gated_spans.ungate_last(sym::let_chains, cond.span);
2869-
}
2870-
let span = if_span.to(cond.span);
2871-
this.sess.gated_spans.gate(sym::if_let_guard, span);
2872-
}
2873-
Some(cond)
2874-
} else {
2875-
None
2876-
};
2875+
let (pat, guard) = this.parse_match_arm_pat_and_guard()?;
28772876
let arrow_span = this.token.span;
28782877
if let Err(mut err) = this.expect(&token::FatArrow) {
28792878
// We might have a `=>` -> `=` or `->` typo (issue #89396).
@@ -3002,6 +3001,87 @@ impl<'a> Parser<'a> {
30023001
})
30033002
}
30043003

3004+
fn parse_match_arm_guard(&mut self) -> PResult<'a, Option<P<Expr>>> {
3005+
// Used to check the `let_chains` and `if_let_guard` features mostly by scanning
3006+
// `&&` tokens.
3007+
fn check_let_expr(expr: &Expr) -> (bool, bool) {
3008+
match &expr.kind {
3009+
ExprKind::Binary(BinOp { node: BinOpKind::And, .. }, lhs, rhs) => {
3010+
let lhs_rslt = check_let_expr(lhs);
3011+
let rhs_rslt = check_let_expr(rhs);
3012+
(lhs_rslt.0 || rhs_rslt.0, false)
3013+
}
3014+
ExprKind::Let(..) => (true, true),
3015+
_ => (false, true),
3016+
}
3017+
}
3018+
if !self.eat_keyword(kw::If) {
3019+
// No match arm guard present.
3020+
return Ok(None);
3021+
}
3022+
3023+
let if_span = self.prev_token.span;
3024+
let mut cond = self.parse_match_guard_condition()?;
3025+
3026+
CondChecker { parser: self, forbid_let_reason: None }.visit_expr(&mut cond);
3027+
3028+
let (has_let_expr, does_not_have_bin_op) = check_let_expr(&cond);
3029+
if has_let_expr {
3030+
if does_not_have_bin_op {
3031+
// Remove the last feature gating of a `let` expression since it's stable.
3032+
self.sess.gated_spans.ungate_last(sym::let_chains, cond.span);
3033+
}
3034+
let span = if_span.to(cond.span);
3035+
self.sess.gated_spans.gate(sym::if_let_guard, span);
3036+
}
3037+
Ok(Some(cond))
3038+
}
3039+
3040+
fn parse_match_arm_pat_and_guard(&mut self) -> PResult<'a, (P<Pat>, Option<P<Expr>>)> {
3041+
if self.token.kind == token::OpenDelim(Delimiter::Parenthesis) {
3042+
// Detect and recover from `($pat if $cond) => $arm`.
3043+
let left = self.token.span;
3044+
match self.parse_pat_allow_top_alt(
3045+
None,
3046+
RecoverComma::Yes,
3047+
RecoverColon::Yes,
3048+
CommaRecoveryMode::EitherTupleOrPipe,
3049+
) {
3050+
Ok(pat) => Ok((pat, self.parse_match_arm_guard()?)),
3051+
Err(err) if let prev_sp = self.prev_token.span && let true = self.eat_keyword(kw::If) => {
3052+
// We know for certain we've found `($pat if` so far.
3053+
let mut cond = match self.parse_match_guard_condition() {
3054+
Ok(cond) => cond,
3055+
Err(cond_err) => {
3056+
cond_err.cancel();
3057+
return Err(err);
3058+
}
3059+
};
3060+
err.cancel();
3061+
CondChecker { parser: self, forbid_let_reason: None }.visit_expr(&mut cond);
3062+
self.eat_to_tokens(&[&token::CloseDelim(Delimiter::Parenthesis)]);
3063+
self.expect(&token::CloseDelim(Delimiter::Parenthesis))?;
3064+
let right = self.prev_token.span;
3065+
self.sess.emit_err(errors::ParenthesesInMatchPat {
3066+
span: vec![left, right],
3067+
sugg: errors::ParenthesesInMatchPatSugg { left, right },
3068+
});
3069+
Ok((self.mk_pat(left.to(prev_sp), ast::PatKind::Wild), Some(cond)))
3070+
}
3071+
Err(err) => Err(err),
3072+
}
3073+
} else {
3074+
// Regular parser flow:
3075+
let pat = self.parse_pat_allow_top_alt(
3076+
None,
3077+
RecoverComma::Yes,
3078+
RecoverColon::Yes,
3079+
CommaRecoveryMode::EitherTupleOrPipe,
3080+
)?;
3081+
Ok((pat, self.parse_match_arm_guard()?))
3082+
}
3083+
}
3084+
30053085
fn parse_match_guard_condition(&mut self) -> PResult<'a, P<Expr>> {
30063086
self.parse_expr_res(Restrictions::ALLOW_LET | Restrictions::IN_IF_GUARD, None).map_err(
30073087
|mut err| {

0 commit comments

Comments
 (0)