Rollup merge of #62928 - Centril:recover-parens-around-for-head, r=estebank

Syntax: Recover on `for ( $pat in $expr ) $block`

Fixes #62724 by adding some recovery:

```
error: unexpected closing `)`
  --> $DIR/recover-for-loop-parens-around-head.rs:10:23
   |
LL |     for ( elem in vec ) {
   |         --------------^
   |         |
   |         opening `(`
   |         help: remove parenthesis in `for` loop: `elem in vec`
```

The last 2 commits are drive-by cleanups.

r? @estebank
This commit is contained in:
Mazdak Farrokhzad 2019-07-30 05:37:32 +02:00 committed by GitHub
commit 36029878e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 259 additions and 162 deletions

View File

@ -14,7 +14,7 @@ use crate::ThinVec;
use crate::util::parser::AssocOp;
use errors::{Applicability, DiagnosticBuilder, DiagnosticId};
use rustc_data_structures::fx::FxHashSet;
use syntax_pos::{Span, DUMMY_SP, MultiSpan};
use syntax_pos::{Span, DUMMY_SP, MultiSpan, SpanSnippetError};
use log::{debug, trace};
use std::mem;
@ -199,6 +199,10 @@ impl<'a> Parser<'a> {
&self.sess.span_diagnostic
}
crate fn span_to_snippet(&self, span: Span) -> Result<String, SpanSnippetError> {
self.sess.source_map().span_to_snippet(span)
}
crate fn expected_ident_found(&self) -> DiagnosticBuilder<'a> {
let mut err = self.struct_span_err(
self.token.span,
@ -549,8 +553,10 @@ impl<'a> Parser<'a> {
ExprKind::Binary(op, _, _) if op.node.is_comparison() => {
// respan to include both operators
let op_span = op.span.to(self.token.span);
let mut err = self.diagnostic().struct_span_err(op_span,
"chained comparison operators require parentheses");
let mut err = self.struct_span_err(
op_span,
"chained comparison operators require parentheses",
);
if op.node == BinOpKind::Lt &&
*outer_op == AssocOp::Less || // Include `<` to provide this recommendation
*outer_op == AssocOp::Greater // even in a case like the following:
@ -717,8 +723,6 @@ impl<'a> Parser<'a> {
path.span = ty_span.to(self.prev_span);
let ty_str = self
.sess
.source_map()
.span_to_snippet(ty_span)
.unwrap_or_else(|_| pprust::ty_to_string(&ty));
self.diagnostic()
@ -889,7 +893,7 @@ impl<'a> Parser<'a> {
err.span_label(await_sp, "while parsing this incorrect await expression");
err
})?;
let expr_str = self.sess.source_map().span_to_snippet(expr.span)
let expr_str = self.span_to_snippet(expr.span)
.unwrap_or_else(|_| pprust::expr_to_string(&expr));
let suggestion = format!("{}.await{}", expr_str, if is_question { "?" } else { "" });
let sp = lo.to(expr.span);
@ -923,6 +927,48 @@ impl<'a> Parser<'a> {
}
}
/// Recover a situation like `for ( $pat in $expr )`
/// and suggest writing `for $pat in $expr` instead.
///
/// This should be called before parsing the `$block`.
crate fn recover_parens_around_for_head(
&mut self,
pat: P<Pat>,
expr: &Expr,
begin_paren: Option<Span>,
) -> P<Pat> {
match (&self.token.kind, begin_paren) {
(token::CloseDelim(token::Paren), Some(begin_par_sp)) => {
self.bump();
let pat_str = self
// Remove the `(` from the span of the pattern:
.span_to_snippet(pat.span.trim_start(begin_par_sp).unwrap())
.unwrap_or_else(|_| pprust::pat_to_string(&pat));
self.struct_span_err(self.prev_span, "unexpected closing `)`")
.span_label(begin_par_sp, "opening `(`")
.span_suggestion(
begin_par_sp.to(self.prev_span),
"remove parenthesis in `for` loop",
format!("{} in {}", pat_str, pprust::expr_to_string(&expr)),
// With e.g. `for (x) in y)` this would replace `(x) in y)`
// with `x) in y)` which is syntactically invalid.
// However, this is prevented before we get here.
Applicability::MachineApplicable,
)
.emit();
// Unwrap `(pat)` into `pat` to avoid the `unused_parens` lint.
pat.and_then(|pat| match pat.node {
PatKind::Paren(pat) => pat,
_ => P(pat),
})
}
_ => pat,
}
}
crate fn could_ascription_be_path(&self, node: &ast::ExprKind) -> bool {
self.token.is_ident() &&
if let ast::ExprKind::Path(..) = node { true } else { false } &&
@ -1105,17 +1151,14 @@ impl<'a> Parser<'a> {
crate fn check_for_for_in_in_typo(&mut self, in_span: Span) {
if self.eat_keyword(kw::In) {
// a common typo: `for _ in in bar {}`
let mut err = self.sess.span_diagnostic.struct_span_err(
self.prev_span,
"expected iterable, found keyword `in`",
);
err.span_suggestion_short(
in_span.until(self.prev_span),
"remove the duplicated `in`",
String::new(),
Applicability::MachineApplicable,
);
err.emit();
self.struct_span_err(self.prev_span, "expected iterable, found keyword `in`")
.span_suggestion_short(
in_span.until(self.prev_span),
"remove the duplicated `in`",
String::new(),
Applicability::MachineApplicable,
)
.emit();
}
}
@ -1128,12 +1171,12 @@ impl<'a> Parser<'a> {
crate fn eat_incorrect_doc_comment_for_arg_type(&mut self) {
if let token::DocComment(_) = self.token.kind {
let mut err = self.diagnostic().struct_span_err(
self.struct_span_err(
self.token.span,
"documentation comments cannot be applied to a function parameter's type",
);
err.span_label(self.token.span, "doc comments are not allowed here");
err.emit();
)
.span_label(self.token.span, "doc comments are not allowed here")
.emit();
self.bump();
} else if self.token == token::Pound && self.look_ahead(1, |t| {
*t == token::OpenDelim(token::Bracket)
@ -1145,12 +1188,12 @@ impl<'a> Parser<'a> {
}
let sp = lo.to(self.token.span);
self.bump();
let mut err = self.diagnostic().struct_span_err(
self.struct_span_err(
sp,
"attributes cannot be applied to a function parameter's type",
);
err.span_label(sp, "attributes are not allowed here");
err.emit();
)
.span_label(sp, "attributes are not allowed here")
.emit();
}
}
@ -1206,18 +1249,19 @@ impl<'a> Parser<'a> {
self.expect(&token::Colon)?;
let ty = self.parse_ty()?;
let mut err = self.diagnostic().struct_span_err_with_code(
pat.span,
"patterns aren't allowed in methods without bodies",
DiagnosticId::Error("E0642".into()),
);
err.span_suggestion_short(
pat.span,
"give this argument a name or use an underscore to ignore it",
"_".to_owned(),
Applicability::MachineApplicable,
);
err.emit();
self.diagnostic()
.struct_span_err_with_code(
pat.span,
"patterns aren't allowed in methods without bodies",
DiagnosticId::Error("E0642".into()),
)
.span_suggestion_short(
pat.span,
"give this argument a name or use an underscore to ignore it",
"_".to_owned(),
Applicability::MachineApplicable,
)
.emit();
// Pretend the pattern is `_`, to avoid duplicate errors from AST validation.
let pat = P(Pat {

View File

@ -2329,19 +2329,19 @@ impl<'a> Parser<'a> {
// This is a struct literal, but we don't can't accept them here
let expr = self.parse_struct_expr(lo, path.clone(), attrs.clone());
if let (Ok(expr), false) = (&expr, struct_allowed) {
let mut err = self.diagnostic().struct_span_err(
self.struct_span_err(
expr.span,
"struct literals are not allowed here",
);
err.multipart_suggestion(
)
.multipart_suggestion(
"surround the struct literal with parentheses",
vec![
(lo.shrink_to_lo(), "(".to_string()),
(expr.span.shrink_to_hi(), ")".to_string()),
],
Applicability::MachineApplicable,
);
err.emit();
)
.emit();
}
return Some(expr);
}
@ -2370,18 +2370,18 @@ impl<'a> Parser<'a> {
}
}
if self.token == token::Comma {
let mut err = self.sess.span_diagnostic.mut_span_err(
self.struct_span_err(
exp_span.to(self.prev_span),
"cannot use a comma after the base struct",
);
err.span_suggestion_short(
)
.span_suggestion_short(
self.token.span,
"remove this comma",
String::new(),
Applicability::MachineApplicable
);
err.note("the base struct must always be the last field");
err.emit();
)
.note("the base struct must always be the last field")
.emit();
self.recover_stmt();
}
break;
@ -2736,15 +2736,14 @@ impl<'a> Parser<'a> {
let e = self.parse_prefix_expr(None);
let (span, e) = self.interpolated_or_expr_span(e)?;
let span_of_tilde = lo;
let mut err = self.diagnostic()
.struct_span_err(span_of_tilde, "`~` cannot be used as a unary operator");
err.span_suggestion_short(
span_of_tilde,
"use `!` to perform bitwise negation",
"!".to_owned(),
Applicability::MachineApplicable
);
err.emit();
self.struct_span_err(span_of_tilde, "`~` cannot be used as a unary operator")
.span_suggestion_short(
span_of_tilde,
"use `!` to perform bitwise negation",
"!".to_owned(),
Applicability::MachineApplicable
)
.emit();
(lo.to(span), self.mk_unary(UnOp::Not, e))
}
token::BinOp(token::Minus) => {
@ -2792,21 +2791,20 @@ impl<'a> Parser<'a> {
if cannot_continue_expr {
self.bump();
// Emit the error ...
let mut err = self.diagnostic()
.struct_span_err(self.token.span,
&format!("unexpected {} after identifier",
self.this_token_descr()));
// span the `not` plus trailing whitespace to avoid
// trailing whitespace after the `!` in our suggestion
let to_replace = self.sess.source_map()
.span_until_non_whitespace(lo.to(self.token.span));
err.span_suggestion_short(
to_replace,
self.struct_span_err(
self.token.span,
&format!("unexpected {} after identifier",self.this_token_descr())
)
.span_suggestion_short(
// Span the `not` plus trailing whitespace to avoid
// trailing whitespace after the `!` in our suggestion
self.sess.source_map()
.span_until_non_whitespace(lo.to(self.token.span)),
"use `!` to perform logical negation",
"!".to_owned(),
Applicability::MachineApplicable
);
err.emit();
)
.emit();
// —and recover! (just as if we were in the block
// for the `token::Not` arm)
let e = self.parse_prefix_expr(None);
@ -2884,7 +2882,7 @@ impl<'a> Parser<'a> {
// 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.token.span, &format!(
let mut err = self.struct_span_err(self.token.span, &format!(
"expected expression, found `{}`",
pprust::token_to_string(&self.token),
));
@ -3072,28 +3070,29 @@ impl<'a> Parser<'a> {
// in AST and continue parsing.
let msg = format!("`<` is interpreted as a start of generic \
arguments for `{}`, not a {}", path, op_noun);
let mut err =
self.sess.span_diagnostic.struct_span_err(self.token.span, &msg);
let span_after_type = parser_snapshot_after_type.token.span;
err.span_label(self.look_ahead(1, |t| t.span).to(span_after_type),
"interpreted as generic arguments");
err.span_label(self.token.span, format!("not interpreted as {}", op_noun));
let expr = mk_expr(self, P(Ty {
span: path.span,
node: TyKind::Path(None, path),
id: ast::DUMMY_NODE_ID
}));
let expr_str = self.sess.source_map().span_to_snippet(expr.span)
.unwrap_or_else(|_| pprust::expr_to_string(&expr));
err.span_suggestion(
expr.span,
&format!("try {} the cast value", op_verb),
format!("({})", expr_str),
Applicability::MachineApplicable
);
err.emit();
let expr_str = self.span_to_snippet(expr.span)
.unwrap_or_else(|_| pprust::expr_to_string(&expr));
self.struct_span_err(self.token.span, &msg)
.span_label(
self.look_ahead(1, |t| t.span).to(span_after_type),
"interpreted as generic arguments"
)
.span_label(self.token.span, format!("not interpreted as {}", op_noun))
.span_suggestion(
expr.span,
&format!("try {} the cast value", op_verb),
format!("({})", expr_str),
Applicability::MachineApplicable
)
.emit();
Ok(expr)
}
@ -3276,26 +3275,40 @@ impl<'a> Parser<'a> {
}
/// Parse a 'for' .. 'in' expression ('for' token already eaten)
fn parse_for_expr(&mut self, opt_label: Option<Label>,
span_lo: Span,
mut attrs: ThinVec<Attribute>) -> PResult<'a, P<Expr>> {
fn parse_for_expr(
&mut self,
opt_label: Option<Label>,
span_lo: Span,
mut attrs: ThinVec<Attribute>
) -> PResult<'a, P<Expr>> {
// Parse: `for <src_pat> in <src_expr> <src_loop_block>`
// Record whether we are about to parse `for (`.
// This is used below for recovery in case of `for ( $stuff ) $block`
// in which case we will suggest `for $stuff $block`.
let begin_paren = match self.token.kind {
token::OpenDelim(token::Paren) => Some(self.token.span),
_ => None,
};
let pat = self.parse_top_level_pat()?;
if !self.eat_keyword(kw::In) {
let in_span = self.prev_span.between(self.token.span);
let mut err = self.sess.span_diagnostic
.struct_span_err(in_span, "missing `in` in `for` loop");
err.span_suggestion_short(
in_span, "try adding `in` here", " in ".into(),
// has been misleading, at least in the past (closed Issue #48492)
Applicability::MaybeIncorrect
);
err.emit();
self.struct_span_err(in_span, "missing `in` in `for` loop")
.span_suggestion_short(
in_span,
"try adding `in` here", " in ".into(),
// has been misleading, at least in the past (closed Issue #48492)
Applicability::MaybeIncorrect
)
.emit();
}
let in_span = self.prev_span;
self.check_for_for_in_in_typo(in_span);
let expr = self.parse_expr_res(Restrictions::NO_STRUCT_LITERAL, None)?;
let pat = self.recover_parens_around_for_head(pat, &expr, begin_paren);
let (iattrs, loop_block) = self.parse_inner_attrs_and_block()?;
attrs.extend(iattrs);
@ -3522,15 +3535,14 @@ impl<'a> Parser<'a> {
pats.push(self.parse_top_level_pat()?);
if self.token == token::OrOr {
let mut err = self.struct_span_err(self.token.span,
"unexpected token `||` after pattern");
err.span_suggestion(
self.token.span,
"use a single `|` to specify multiple patterns",
"|".to_owned(),
Applicability::MachineApplicable
);
err.emit();
self.struct_span_err(self.token.span, "unexpected token `||` after pattern")
.span_suggestion(
self.token.span,
"use a single `|` to specify multiple patterns",
"|".to_owned(),
Applicability::MachineApplicable
)
.emit();
self.bump();
} else if self.eat(&token::BinOp(token::Or)) {
// This is a No-op. Continue the loop to parse the next
@ -3627,15 +3639,14 @@ impl<'a> Parser<'a> {
if self.token == token::DotDotDot { // Issue #46718
// Accept `...` as if it were `..` to avoid further errors
let mut err = self.struct_span_err(self.token.span,
"expected field pattern, found `...`");
err.span_suggestion(
self.token.span,
"to omit remaining fields, use one fewer `.`",
"..".to_owned(),
Applicability::MachineApplicable
);
err.emit();
self.struct_span_err(self.token.span, "expected field pattern, found `...`")
.span_suggestion(
self.token.span,
"to omit remaining fields, use one fewer `.`",
"..".to_owned(),
Applicability::MachineApplicable
)
.emit();
}
self.bump(); // `..` || `...`
@ -3788,7 +3799,7 @@ impl<'a> Parser<'a> {
let seq_span = pat.span.to(self.prev_span);
let mut err = self.struct_span_err(comma_span,
"unexpected `,` in pattern");
if let Ok(seq_snippet) = self.sess.source_map().span_to_snippet(seq_span) {
if let Ok(seq_snippet) = self.span_to_snippet(seq_span) {
err.span_suggestion(
seq_span,
"try adding parentheses to match on a tuple..",
@ -4137,7 +4148,7 @@ impl<'a> Parser<'a> {
let parser_snapshot_after_type = self.clone();
mem::replace(self, parser_snapshot_before_type);
let snippet = self.sess.source_map().span_to_snippet(pat.span).unwrap();
let snippet = self.span_to_snippet(pat.span).unwrap();
err.span_label(pat.span, format!("while parsing the type for `{}`", snippet));
(Some((parser_snapshot_after_type, colon_sp, err)), None)
}
@ -4557,7 +4568,7 @@ impl<'a> Parser<'a> {
if self.eat(&token::Semi) {
stmt_span = stmt_span.with_hi(self.prev_span.hi());
}
if let Ok(snippet) = self.sess.source_map().span_to_snippet(stmt_span) {
if let Ok(snippet) = self.span_to_snippet(stmt_span) {
e.span_suggestion(
stmt_span,
"try placing this code inside a block",
@ -4730,7 +4741,7 @@ impl<'a> Parser<'a> {
lo.to(self.prev_span),
"parenthesized lifetime bounds are not supported"
);
if let Ok(snippet) = self.sess.source_map().span_to_snippet(inner_span) {
if let Ok(snippet) = self.span_to_snippet(inner_span) {
err.span_suggestion_short(
lo.to(self.prev_span),
"remove the parentheses",
@ -4788,7 +4799,7 @@ impl<'a> Parser<'a> {
let mut new_bound_list = String::new();
if !bounds.is_empty() {
let mut snippets = bounds.iter().map(|bound| bound.span())
.map(|span| self.sess.source_map().span_to_snippet(span));
.map(|span| self.span_to_snippet(span));
while let Some(Ok(snippet)) = snippets.next() {
new_bound_list.push_str(" + ");
new_bound_list.push_str(&snippet);
@ -5853,15 +5864,16 @@ impl<'a> Parser<'a> {
if let token::DocComment(_) = self.token.kind {
if self.look_ahead(1,
|tok| tok == &token::CloseDelim(token::Brace)) {
let mut err = self.diagnostic().struct_span_err_with_code(
self.diagnostic().struct_span_err_with_code(
self.token.span,
"found a documentation comment that doesn't document anything",
DiagnosticId::Error("E0584".into()),
);
err.help("doc comments must come before what they document, maybe a \
)
.help(
"doc comments must come before what they document, maybe a \
comment was intended with `//`?",
);
err.emit();
)
.emit();
self.bump();
continue;
}
@ -6305,12 +6317,15 @@ impl<'a> Parser<'a> {
let sp = path.span;
let help_msg = format!("make this visible only to module `{}` with `in`", path);
self.expect(&token::CloseDelim(token::Paren))?; // `)`
let mut err = struct_span_err!(self.sess.span_diagnostic, sp, E0704, "{}", msg);
err.help(suggestion);
err.span_suggestion(
sp, &help_msg, format!("in {}", path), Applicability::MachineApplicable
);
err.emit(); // emit diagnostic, but continue with public visibility
struct_span_err!(self.sess.span_diagnostic, sp, E0704, "{}", msg)
.help(suggestion)
.span_suggestion(
sp,
&help_msg,
format!("in {}", path),
Applicability::MachineApplicable,
)
.emit(); // emit diagnostic, but continue with public visibility
}
}
@ -6744,14 +6759,10 @@ impl<'a> Parser<'a> {
}
ident = Ident::from_str(&fixed_name).with_span_pos(fixed_name_sp);
let mut err = self.struct_span_err(fixed_name_sp, error_msg);
err.span_label(fixed_name_sp, "dash-separated idents are not valid");
err.multipart_suggestion(
suggestion_msg,
replacement,
Applicability::MachineApplicable,
);
err.emit();
self.struct_span_err(fixed_name_sp, error_msg)
.span_label(fixed_name_sp, "dash-separated idents are not valid")
.multipart_suggestion(suggestion_msg, replacement, Applicability::MachineApplicable)
.emit();
}
Ok(ident)
}
@ -6906,14 +6917,14 @@ impl<'a> Parser<'a> {
if !self.eat(&token::Comma) {
if self.token.is_ident() && !self.token.is_reserved_ident() {
let sp = self.sess.source_map().next_point(self.prev_span);
let mut err = self.struct_span_err(sp, "missing comma");
err.span_suggestion_short(
sp,
"missing comma",
",".to_owned(),
Applicability::MaybeIncorrect,
);
err.emit();
self.struct_span_err(sp, "missing comma")
.span_suggestion_short(
sp,
"missing comma",
",".to_owned(),
Applicability::MaybeIncorrect,
)
.emit();
} else {
break;
}
@ -6952,15 +6963,16 @@ impl<'a> Parser<'a> {
Some(abi) => Ok(Some(abi)),
None => {
let prev_span = self.prev_span;
let mut err = struct_span_err!(
struct_span_err!(
self.sess.span_diagnostic,
prev_span,
E0703,
"invalid ABI: found `{}`",
symbol);
err.span_label(prev_span, "invalid ABI");
err.help(&format!("valid ABIs: {}", abi::all_names().join(", ")));
err.emit();
symbol
)
.span_label(prev_span, "invalid ABI")
.help(&format!("valid ABIs: {}", abi::all_names().join(", ")))
.emit();
Ok(None)
}
}
@ -7130,16 +7142,15 @@ impl<'a> Parser<'a> {
// CONST ITEM
if self.eat_keyword(kw::Mut) {
let prev_span = self.prev_span;
let mut err = self.diagnostic()
.struct_span_err(prev_span, "const globals cannot be mutable");
err.span_label(prev_span, "cannot be mutable");
err.span_suggestion(
const_span,
"you might want to declare a static instead",
"static".to_owned(),
Applicability::MaybeIncorrect,
);
err.emit();
self.struct_span_err(prev_span, "const globals cannot be mutable")
.span_label(prev_span, "cannot be mutable")
.span_suggestion(
const_span,
"you might want to declare a static instead",
"static".to_owned(),
Applicability::MaybeIncorrect,
)
.emit();
}
let (ident, item_, extra_attrs) = self.parse_item_const(None)?;
let prev_span = self.prev_span;
@ -7407,7 +7418,7 @@ impl<'a> Parser<'a> {
sp, &suggestion, format!(" {} ", kw), Applicability::MachineApplicable
);
} else {
if let Ok(snippet) = self.sess.source_map().span_to_snippet(ident_sp) {
if let Ok(snippet) = self.span_to_snippet(ident_sp) {
err.span_suggestion(
full_sp,
"if you meant to call a macro, try",

View File

@ -0,0 +1,15 @@
// Here we test that the parser is able to recover in a situation like
// `for ( $pat in $expr )` since that is familiar syntax in other languages.
// Instead we suggest that the user writes `for $pat in $expr`.
#![deny(unused)] // Make sure we don't trigger `unused_parens`.
fn main() {
let vec = vec![1, 2, 3];
for ( elem in vec ) {
//~^ ERROR expected one of `)`, `,`, or `@`, found `in`
//~| ERROR unexpected closing `)`
const RECOVERY_WITNESS: () = 0; //~ ERROR mismatched types
}
}

View File

@ -0,0 +1,27 @@
error: expected one of `)`, `,`, or `@`, found `in`
--> $DIR/recover-for-loop-parens-around-head.rs:10:16
|
LL | for ( elem in vec ) {
| ^^ expected one of `)`, `,`, or `@` here
error: unexpected closing `)`
--> $DIR/recover-for-loop-parens-around-head.rs:10:23
|
LL | for ( elem in vec ) {
| --------------^
| |
| opening `(`
| help: remove parenthesis in `for` loop: `elem in vec`
error[E0308]: mismatched types
--> $DIR/recover-for-loop-parens-around-head.rs:13:38
|
LL | const RECOVERY_WITNESS: () = 0;
| ^ expected (), found integer
|
= note: expected type `()`
found type `{integer}`
error: aborting due to 3 previous errors
For more information about this error, try `rustc --explain E0308`.