Skip to content

Commit

Permalink
Detect match arm body without braces
Browse files Browse the repository at this point in the history
Fix #82524.
  • Loading branch information
estebank committed Mar 3, 2021
1 parent 795a934 commit ae494d1
Show file tree
Hide file tree
Showing 4 changed files with 334 additions and 1 deletion.
111 changes: 111 additions & 0 deletions compiler/rustc_parse/src/parser/expr.rs
Expand Up @@ -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<Expr>,
arrow_span: Span,
) -> Option<P<Expr>> {
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<ast::Stmt>| {
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| {
Expand Down Expand Up @@ -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)) {
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_parse/src/parser/stmt.rs
Expand Up @@ -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,
Expand Down
87 changes: 87 additions & 0 deletions src/test/ui/parser/match-arm-without-braces.rs
@@ -0,0 +1,87 @@
struct S;

impl S {
fn get<K, V: Default>(_: K) -> Option<V> {
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,
}
}
135 changes: 135 additions & 0 deletions 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

0 comments on commit ae494d1

Please sign in to comment.