Skip to content

Commit 1a61ee7

Browse files
committed
Account for (pat if expr) => {}
When encountering match arm (pat if expr) => {}, recover and suggest removing parentheses. Fix rust-lang#100825.
1 parent d6b6f5d commit 1a61ee7

File tree

5 files changed

+129
-47
lines changed

5 files changed

+129
-47
lines changed

compiler/rustc_parse/messages.ftl

+3
Original file line numberDiff line numberDiff line change
@@ -767,6 +767,9 @@ parse_unexpected_lifetime_in_pattern = unexpected lifetime `{$symbol}` in patter
767767
parse_unexpected_parentheses_in_for_head = unexpected parentheses surrounding `for` loop head
768768
.suggestion = remove parentheses in `for` loop
769769
770+
parse_unexpected_parentheses_in_match_arm_pattern = unexpected parentheses surrounding `match` arm pattern
771+
.suggestion = remove parentheses surrounding the pattern
772+
770773
parse_unexpected_self_in_generic_parameters = unexpected keyword `Self` in generic parameters
771774
.note = you cannot use `Self` as a generic parameter because it is reserved for associated items
772775

compiler/rustc_parse/src/errors.rs

+18
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

+83-39
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};
@@ -2853,47 +2853,10 @@ impl<'a> Parser<'a> {
28532853
}
28542854

28552855
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-
}
28692856
let attrs = self.parse_outer_attributes()?;
28702857
self.collect_tokens_trailing_token(attrs, ForceCollect::No, |this, attrs| {
28712858
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 { parser: this, forbid_let_reason: None }.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-
};
2859+
let (pat, guard) = this.parse_match_arm_pat_and_guard()?;
28972860
let arrow_span = this.token.span;
28982861
if let Err(mut err) = this.expect(&token::FatArrow) {
28992862
// We might have a `=>` -> `=` or `->` typo (issue #89396).
@@ -3023,6 +2986,87 @@ impl<'a> Parser<'a> {
30232986
})
30242987
}
30252988

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

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

+1-4
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,
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)