Skip to content

Commit cfdc3c6

Browse files
committed
Account for (pat if expr) => {}
When encountering match arm (pat if expr) => {}, recover and suggest removing parentheses. Fix #100825.
1 parent 5bb717c commit cfdc3c6

File tree

5 files changed

+129
-47
lines changed

5 files changed

+129
-47
lines changed

compiler/rustc_parse/messages.ftl

Lines changed: 3 additions & 0 deletions
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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1247,6 +1247,24 @@ pub(crate) struct ParenthesesInForHeadSugg {
12471247
pub right: Span,
12481248
}
12491249

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 = "")]
1263+
pub left: Span,
1264+
#[suggestion_part(code = "")]
1265+
pub right: Span,
1266+
}
1267+
12501268
#[derive(Diagnostic)]
12511269
#[diag(parse_doc_comment_on_param_type)]
12521270
pub(crate) struct DocCommentOnParamType {

compiler/rustc_parse/src/parser/expr.rs

Lines changed: 83 additions & 39 deletions
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};
@@ -2837,47 +2837,10 @@ impl<'a> Parser<'a> {
28372837
}
28382838

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

2972+
fn parse_match_arm_guard(&mut self) -> PResult<'a, Option<P<Expr>>> {
2973+
// Used to check the `let_chains` and `if_let_guard` features mostly by scanning
2974+
// `&&` tokens.
2975+
fn check_let_expr(expr: &Expr) -> (bool, bool) {
2976+
match &expr.kind {
2977+
ExprKind::Binary(BinOp { node: BinOpKind::And, .. }, lhs, rhs) => {
2978+
let lhs_rslt = check_let_expr(lhs);
2979+
let rhs_rslt = check_let_expr(rhs);
2980+
(lhs_rslt.0 || rhs_rslt.0, false)
2981+
}
2982+
ExprKind::Let(..) => (true, true),
2983+
_ => (false, true),
2984+
}
2985+
}
2986+
if !self.eat_keyword(kw::If) {
2987+
// No match arm guard present.
2988+
return Ok(None);
2989+
}
2990+
2991+
let if_span = self.prev_token.span;
2992+
let mut cond = self.parse_match_guard_condition()?;
2993+
2994+
CondChecker { parser: self, forbid_let_reason: None }.visit_expr(&mut cond);
2995+
2996+
let (has_let_expr, does_not_have_bin_op) = check_let_expr(&cond);
2997+
if has_let_expr {
2998+
if does_not_have_bin_op {
2999+
// Remove the last feature gating of a `let` expression since it's stable.
3000+
self.sess.gated_spans.ungate_last(sym::let_chains, cond.span);
3001+
}
3002+
let span = if_span.to(cond.span);
3003+
self.sess.gated_spans.gate(sym::if_let_guard, span);
3004+
}
3005+
Ok(Some(cond))
3006+
}
3007+
3008+
fn parse_match_arm_pat_and_guard(&mut self) -> PResult<'a, (P<Pat>, Option<P<Expr>>)> {
3009+
if self.token.kind == token::OpenDelim(Delimiter::Parenthesis) {
3010+
// Detect and recover from `($pat if $cond) => $arm`.
3011+
let left = self.token.span;
3012+
match self.parse_pat_allow_top_alt(
3013+
None,
3014+
RecoverComma::Yes,
3015+
RecoverColon::Yes,
3016+
CommaRecoveryMode::EitherTupleOrPipe,
3017+
) {
3018+
Ok(pat) => Ok((pat, self.parse_match_arm_guard()?)),
3019+
Err(err) if let prev_sp = self.prev_token.span && let true = self.eat_keyword(kw::If) => {
3020+
// We know for certain we've found `($pat if` so far.
3021+
let mut cond = match self.parse_match_guard_condition() {
3022+
Ok(cond) => cond,
3023+
Err(cond_err) => {
3024+
cond_err.cancel();
3025+
return Err(err);
3026+
}
3027+
};
3028+
err.cancel();
3029+
CondChecker { parser: self, forbid_let_reason: None }.visit_expr(&mut cond);
3030+
self.eat_to_tokens(&[&token::CloseDelim(Delimiter::Parenthesis)]);
3031+
self.expect(&token::CloseDelim(Delimiter::Parenthesis))?;
3032+
let right = self.prev_token.span;
3033+
self.sess.emit_err(errors::ParenthesesInMatchPat {
3034+
span: vec![left, right],
3035+
sugg: errors::ParenthesesInMatchPatSugg { left, right },
3036+
});
3037+
Ok((self.mk_pat(left.to(prev_sp), ast::PatKind::Wild), Some(cond)))
3038+
}
3039+
Err(err) => Err(err),
3040+
}
3041+
} else {
3042+
// Regular parser flow:
3043+
let pat = self.parse_pat_allow_top_alt(
3044+
None,
3045+
RecoverComma::Yes,
3046+
RecoverColon::Yes,
3047+
CommaRecoveryMode::EitherTupleOrPipe,
3048+
)?;
3049+
Ok((pat, self.parse_match_arm_guard()?))
3050+
}
3051+
}
3052+
30093053
fn parse_match_guard_condition(&mut self) -> PResult<'a, P<Expr>> {
30103054
self.parse_expr_res(Restrictions::ALLOW_LET | Restrictions::IN_IF_GUARD, None).map_err(
30113055
|mut err| {

tests/ui/parser/recover/recover-parens-around-match-arm-head.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@ fn main() {
22
let val = 42;
33
let x = match val {
44
(0 if true) => {
5-
//~^ ERROR expected identifier, found keyword `if`
6-
//~| ERROR expected one of `)`, `,`, `...`, `..=`, `..`, or `|`, found keyword `if`
7-
//~| ERROR expected one of `)`, `,`, `@`, or `|`, found keyword `true`
8-
//~| ERROR mismatched types
5+
//~^ ERROR unexpected parentheses surrounding `match` arm pattern
96
42u8
107
}
118
_ => 0u8,
Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,28 @@
1-
error: expected one of `)`, `,`, `...`, `..=`, `..`, or `|`, found keyword `if`
2-
--> $DIR/recover-parens-around-match-arm-head.rs:4:12
1+
error: unexpected parentheses surrounding `match` arm pattern
2+
--> $DIR/recover-parens-around-match-arm-head.rs:4:9
33
|
44
LL | (0 if true) => {
5-
| ^^ expected one of `)`, `,`, `...`, `..=`, `..`, or `|`
5+
| ^ ^
6+
|
7+
help: remove parentheses surrounding the pattern
8+
|
9+
LL - (0 if true) => {
10+
LL + 0 if true => {
11+
|
12+
13+
error[E0308]: mismatched types
14+
--> $DIR/recover-parens-around-match-arm-head.rs:10:19
15+
|
16+
LL | let _y: u32 = x;
17+
| --- ^ expected `u32`, found `u8`
18+
| |
19+
| expected due to this
20+
|
21+
help: you can convert a `u8` to a `u32`
22+
|
23+
LL | let _y: u32 = x.into();
24+
| +++++++
625

7-
error: aborting due to previous error
26+
error: aborting due to 2 previous errors
827

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

0 commit comments

Comments
 (0)