Rollup merge of #60188 - estebank:recover-block, r=varkor

Identify when a stmt could have been parsed as an expr

There are some expressions that can be parsed as a statement without
a trailing semicolon depending on the context, which can lead to
confusing errors due to the same looking code being accepted in some
places and not others. Identify these cases and suggest enclosing in
parenthesis making the parse non-ambiguous without changing the
accepted grammar.

Fix #54186, cc #54482, fix #59975, fix #47287.
This commit is contained in:
Mazdak Farrokhzad 2019-05-09 23:56:09 +02:00 committed by GitHub
commit 39edc68c69
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 312 additions and 24 deletions

View file

@ -186,6 +186,7 @@ enum PrevTokenKind {
Interpolated,
Eof,
Ident,
BitOr,
Other,
}
@ -1375,6 +1376,7 @@ impl<'a> Parser<'a> {
token::DocComment(..) => PrevTokenKind::DocComment,
token::Comma => PrevTokenKind::Comma,
token::BinOp(token::Plus) => PrevTokenKind::Plus,
token::BinOp(token::Or) => PrevTokenKind::BitOr,
token::Interpolated(..) => PrevTokenKind::Interpolated,
token::Eof => PrevTokenKind::Eof,
token::Ident(..) => PrevTokenKind::Ident,
@ -2806,6 +2808,12 @@ impl<'a> Parser<'a> {
let msg = format!("expected expression, found {}",
self.this_token_descr());
let mut err = self.fatal(&msg);
let sp = self.sess.source_map().start_point(self.span);
if let Some(sp) = self.sess.ambiguous_block_expr_parse.borrow()
.get(&sp)
{
self.sess.expr_parentheses_needed(&mut err, *sp, None);
}
err.span_label(self.span, "expected expression");
return Err(err);
}
@ -2845,7 +2853,7 @@ impl<'a> Parser<'a> {
"struct literals are not allowed here",
);
err.multipart_suggestion(
"surround the struct literal with parenthesis",
"surround the struct literal with parentheses",
vec![
(lo.shrink_to_lo(), "(".to_string()),
(expr.span.shrink_to_hi(), ")".to_string()),
@ -3506,9 +3514,42 @@ impl<'a> Parser<'a> {
}
};
if self.expr_is_complete(&lhs) {
// Semi-statement forms are odd. See https://github.com/rust-lang/rust/issues/29071
return Ok(lhs);
match (self.expr_is_complete(&lhs), AssocOp::from_token(&self.token)) {
(true, None) => {
// Semi-statement forms are odd. See https://github.com/rust-lang/rust/issues/29071
return Ok(lhs);
}
(false, _) => {} // continue parsing the expression
// An exhaustive check is done in the following block, but these are checked first
// because they *are* ambiguous but also reasonable looking incorrect syntax, so we
// want to keep their span info to improve diagnostics in these cases in a later stage.
(true, Some(AssocOp::Multiply)) | // `{ 42 } *foo = bar;` or `{ 42 } * 3`
(true, Some(AssocOp::Subtract)) | // `{ 42 } -5`
(true, Some(AssocOp::Add)) => { // `{ 42 } + 42
// These cases are ambiguous and can't be identified in the parser alone
let sp = self.sess.source_map().start_point(self.span);
self.sess.ambiguous_block_expr_parse.borrow_mut().insert(sp, lhs.span);
return Ok(lhs);
}
(true, Some(ref op)) if !op.can_continue_expr_unambiguously() => {
return Ok(lhs);
}
(true, Some(_)) => {
// We've found an expression that would be parsed as a statement, but the next
// token implies this should be parsed as an expression.
// For example: `if let Some(x) = x { x } else { 0 } / 2`
let mut err = self.sess.span_diagnostic.struct_span_err(self.span, &format!(
"expected expression, found `{}`",
pprust::token_to_string(&self.token),
));
err.span_label(self.span, "expected expression");
self.sess.expr_parentheses_needed(
&mut err,
lhs.span,
Some(pprust::expr_to_string(&lhs),
));
err.emit();
}
}
self.expected_tokens.push(TokenType::Operator);
while let Some(op) = AssocOp::from_token(&self.token) {
@ -4819,6 +4860,10 @@ impl<'a> Parser<'a> {
);
let mut err = self.fatal(&msg);
err.span_label(self.span, format!("expected {}", expected));
let sp = self.sess.source_map().start_point(self.span);
if let Some(sp) = self.sess.ambiguous_block_expr_parse.borrow().get(&sp) {
self.sess.expr_parentheses_needed(&mut err, *sp, None);
}
return Err(err);
}
}