Skip to content

Detect match arm body without braces #82714

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions compiler/rustc_parse/src/parser/expr.rs
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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