From ae494d147af627537097f10f21cf961e4c1c6f38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Thu, 25 Feb 2021 19:29:50 -0800 Subject: [PATCH] Detect match arm body without braces Fix #82524. --- compiler/rustc_parse/src/parser/expr.rs | 111 ++++++++++++++ compiler/rustc_parse/src/parser/stmt.rs | 2 +- .../ui/parser/match-arm-without-braces.rs | 87 +++++++++++ .../ui/parser/match-arm-without-braces.stderr | 135 ++++++++++++++++++ 4 files changed, 334 insertions(+), 1 deletion(-) create mode 100644 src/test/ui/parser/match-arm-without-braces.rs create mode 100644 src/test/ui/parser/match-arm-without-braces.stderr diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 28bfaea4555..608b0248274 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -1973,6 +1973,102 @@ impl<'a> Parser<'a> { Ok(self.mk_expr(lo.to(hi), ExprKind::Match(scrutinee, arms), attrs)) } + /// Attempt to recover from match arm body with statements and no surrounding braces. + fn parse_arm_body_missing_braces( + &mut self, + first_expr: &P, + arrow_span: Span, + ) -> Option> { + if self.token.kind != token::Semi { + return None; + } + let start_snapshot = self.clone(); + let semi_sp = self.token.span; + self.bump(); // `;` + let mut stmts = + vec![self.mk_stmt(first_expr.span, ast::StmtKind::Expr(first_expr.clone()))]; + let err = |this: &mut Parser<'_>, stmts: Vec| { + let span = stmts[0].span.to(stmts[stmts.len() - 1].span); + let mut err = this.struct_span_err(span, "`match` arm body without braces"); + let (these, s, are) = + if stmts.len() > 1 { ("these", "s", "are") } else { ("this", "", "is") }; + err.span_label( + span, + &format!( + "{these} statement{s} {are} not surrounded by a body", + these = these, + s = s, + are = are + ), + ); + err.span_label(arrow_span, "while parsing the `match` arm starting here"); + if stmts.len() > 1 { + err.multipart_suggestion( + &format!("surround the statement{} with a body", s), + vec![ + (span.shrink_to_lo(), "{ ".to_string()), + (span.shrink_to_hi(), " }".to_string()), + ], + Applicability::MachineApplicable, + ); + } else { + err.span_suggestion( + semi_sp, + "use a comma to end a `match` arm expression", + ",".to_string(), + Applicability::MachineApplicable, + ); + } + err.emit(); + this.mk_expr_err(span) + }; + // We might have either a `,` -> `;` typo, or a block without braces. We need + // a more subtle parsing strategy. + loop { + if self.token.kind == token::CloseDelim(token::Brace) { + // We have reached the closing brace of the `match` expression. + return Some(err(self, stmts)); + } + if self.token.kind == token::Comma { + *self = start_snapshot; + return None; + } + let pre_pat_snapshot = self.clone(); + match self.parse_pat_no_top_alt(None) { + Ok(_pat) => { + if self.token.kind == token::FatArrow { + // Reached arm end. + *self = pre_pat_snapshot; + return Some(err(self, stmts)); + } + } + Err(mut err) => { + err.cancel(); + } + } + + *self = pre_pat_snapshot; + match self.parse_stmt_without_recovery(true, ForceCollect::No) { + // Consume statements for as long as possible. + Ok(Some(stmt)) => { + stmts.push(stmt); + } + Ok(None) => { + *self = start_snapshot; + break; + } + // We couldn't parse either yet another statement missing it's + // enclosing block nor the next arm's pattern or closing brace. + Err(mut stmt_err) => { + stmt_err.cancel(); + *self = start_snapshot; + break; + } + } + } + None + } + pub(super) fn parse_arm(&mut self) -> PResult<'a, Arm> { let attrs = self.parse_outer_attributes()?; self.collect_tokens_trailing_token(attrs, ForceCollect::No, |this, attrs| { @@ -2007,6 +2103,21 @@ impl<'a> Parser<'a> { if require_comma { let sm = this.sess.source_map(); + if let Some(body) = this.parse_arm_body_missing_braces(&expr, arrow_span) { + let span = body.span; + return Ok(( + ast::Arm { + attrs, + pat, + guard, + body, + span, + id: DUMMY_NODE_ID, + is_placeholder: false, + }, + TrailingToken::None, + )); + } this.expect_one_of(&[token::Comma], &[token::CloseDelim(token::Brace)]).map_err( |mut err| { match (sm.span_to_lines(expr.span), sm.span_to_lines(arm_start_span)) { diff --git a/compiler/rustc_parse/src/parser/stmt.rs b/compiler/rustc_parse/src/parser/stmt.rs index 07746f2390d..a0f9616f72a 100644 --- a/compiler/rustc_parse/src/parser/stmt.rs +++ b/compiler/rustc_parse/src/parser/stmt.rs @@ -34,7 +34,7 @@ impl<'a> Parser<'a> { /// If `force_capture` is true, forces collection of tokens regardless of whether /// or not we have attributes - fn parse_stmt_without_recovery( + crate fn parse_stmt_without_recovery( &mut self, capture_semi: bool, force_collect: ForceCollect, diff --git a/src/test/ui/parser/match-arm-without-braces.rs b/src/test/ui/parser/match-arm-without-braces.rs new file mode 100644 index 00000000000..55a88742769 --- /dev/null +++ b/src/test/ui/parser/match-arm-without-braces.rs @@ -0,0 +1,87 @@ +struct S; + +impl S { + fn get(_: K) -> Option { + Default::default() + } +} + +enum Val { + Foo, + Bar, +} + +impl Default for Val { + fn default() -> Self { + Val::Foo + } +} + +fn main() { + match S::get(1) { + Some(Val::Foo) => {} + _ => {} + } + match S::get(2) { + Some(Val::Foo) => 3; //~ ERROR `match` arm body without braces + _ => 4, + } + match S::get(5) { + Some(Val::Foo) => + 7; //~ ERROR `match` arm body without braces + 8; + _ => 9, + } + match S::get(10) { + Some(Val::Foo) => + 11; //~ ERROR `match` arm body without braces + 12; + _ => (), + } + match S::get(13) { + None => {} + Some(Val::Foo) => + 14; //~ ERROR `match` arm body without braces + 15; + } + match S::get(16) { + Some(Val::Foo) => 17 + _ => 18, //~ ERROR expected one of + } + match S::get(19) { + Some(Val::Foo) => + 20; //~ ERROR `match` arm body without braces + 21 + _ => 22, + } + match S::get(23) { + Some(Val::Foo) => + 24; //~ ERROR `match` arm body without braces + 25 + _ => (), + } + match S::get(26) { + None => {} + Some(Val::Foo) => + 27; //~ ERROR `match` arm body without braces + 28 + } + match S::get(29) { + Some(Val::Foo) => + 30; //~ ERROR expected one of + 31, + _ => 32, + } + match S::get(33) { + Some(Val::Foo) => + 34; //~ ERROR expected one of + 35, + _ => (), + } + match S::get(36) { + None => {} + Some(Val::Foo) => + 37; //~ ERROR expected one of + 38, + } +} diff --git a/src/test/ui/parser/match-arm-without-braces.stderr b/src/test/ui/parser/match-arm-without-braces.stderr new file mode 100644 index 00000000000..03ae351bf79 --- /dev/null +++ b/src/test/ui/parser/match-arm-without-braces.stderr @@ -0,0 +1,135 @@ +error: `match` arm body without braces + --> $DIR/match-arm-without-braces.rs:26:27 + | +LL | Some(Val::Foo) => 3; + | -- ^- help: use a comma to end a `match` arm expression: `,` + | | | + | | this statement is not surrounded by a body + | while parsing the `match` arm starting here + +error: `match` arm body without braces + --> $DIR/match-arm-without-braces.rs:31:11 + | +LL | Some(Val::Foo) => + | -- while parsing the `match` arm starting here +LL | / 7; +LL | | 8; + | |____________^ these statements are not surrounded by a body + | +help: surround the statements with a body + | +LL | { 7; +LL | 8; } + | + +error: `match` arm body without braces + --> $DIR/match-arm-without-braces.rs:37:11 + | +LL | Some(Val::Foo) => + | -- while parsing the `match` arm starting here +LL | / 11; +LL | | 12; + | |_____________^ these statements are not surrounded by a body + | +help: surround the statements with a body + | +LL | { 11; +LL | 12; } + | + +error: `match` arm body without braces + --> $DIR/match-arm-without-braces.rs:44:11 + | +LL | Some(Val::Foo) => + | -- while parsing the `match` arm starting here +LL | / 14; +LL | | 15; + | |_____________^ these statements are not surrounded by a body + | +help: surround the statements with a body + | +LL | { 14; +LL | 15; } + | + +error: expected one of `,`, `.`, `?`, `}`, or an operator, found reserved identifier `_` + --> $DIR/match-arm-without-braces.rs:49:9 + | +LL | Some(Val::Foo) => 17 + | -- - expected one of `,`, `.`, `?`, `}`, or an operator + | | + | while parsing the `match` arm starting here +LL | _ => 18, + | ^ unexpected token + +error: `match` arm body without braces + --> $DIR/match-arm-without-braces.rs:53:11 + | +LL | Some(Val::Foo) => + | -- while parsing the `match` arm starting here +LL | / 20; +LL | | 21 + | |____________^ these statements are not surrounded by a body + | +help: surround the statements with a body + | +LL | { 20; +LL | 21 } + | + +error: `match` arm body without braces + --> $DIR/match-arm-without-braces.rs:59:11 + | +LL | Some(Val::Foo) => + | -- while parsing the `match` arm starting here +LL | / 24; +LL | | 25 + | |____________^ these statements are not surrounded by a body + | +help: surround the statements with a body + | +LL | { 24; +LL | 25 } + | + +error: `match` arm body without braces + --> $DIR/match-arm-without-braces.rs:66:11 + | +LL | Some(Val::Foo) => + | -- while parsing the `match` arm starting here +LL | / 27; +LL | | 28 + | |____________^ these statements are not surrounded by a body + | +help: surround the statements with a body + | +LL | { 27; +LL | 28 } + | + +error: expected one of `,`, `.`, `?`, `}`, or an operator, found `;` + --> $DIR/match-arm-without-braces.rs:71:13 + | +LL | Some(Val::Foo) => + | -- while parsing the `match` arm starting here +LL | 30; + | ^ expected one of `,`, `.`, `?`, `}`, or an operator + +error: expected one of `,`, `.`, `?`, `}`, or an operator, found `;` + --> $DIR/match-arm-without-braces.rs:77:13 + | +LL | Some(Val::Foo) => + | -- while parsing the `match` arm starting here +LL | 34; + | ^ expected one of `,`, `.`, `?`, `}`, or an operator + +error: expected one of `,`, `.`, `?`, `}`, or an operator, found `;` + --> $DIR/match-arm-without-braces.rs:84:13 + | +LL | Some(Val::Foo) => + | -- while parsing the `match` arm starting here +LL | 37; + | ^ expected one of `,`, `.`, `?`, `}`, or an operator + +error: aborting due to 11 previous errors +