Use heuristics to recover parsing of missing ;

- Detect `,` and `:` typos where `;` was intended.
- When the next token could have been the start of a new statement,
  detect a missing semicolon.
This commit is contained in:
Esteban Küber 2019-10-20 14:35:46 -07:00
parent 03a50ae9b8
commit d673d0ac84
12 changed files with 121 additions and 101 deletions

View File

@ -6,7 +6,7 @@
self, Param, BinOpKind, BindingMode, BlockCheckMode, Expr, ExprKind, Ident, Item, ItemKind,
Mutability, Pat, PatKind, PathSegment, QSelf, Ty, TyKind,
};
use crate::parse::token::{self, TokenKind};
use crate::parse::token::{self, TokenKind, token_can_begin_expr};
use crate::print::pprust;
use crate::ptr::P;
use crate::symbol::{kw, sym};
@ -326,34 +326,8 @@ fn tokens_to_string(tokens: &[TokenType]) -> String {
}
}
let is_semi_suggestable = expected.iter().any(|t| match t {
TokenType::Token(token::Semi) => true, // We expect a `;` here.
_ => false,
}) && ( // A `;` would be expected before the current keyword.
self.token.is_keyword(kw::Break) ||
self.token.is_keyword(kw::Continue) ||
self.token.is_keyword(kw::For) ||
self.token.is_keyword(kw::If) ||
self.token.is_keyword(kw::Let) ||
self.token.is_keyword(kw::Loop) ||
self.token.is_keyword(kw::Match) ||
self.token.is_keyword(kw::Return) ||
self.token.is_keyword(kw::While)
);
let sm = self.sess.source_map();
match (sm.lookup_line(self.token.span.lo()), sm.lookup_line(sp.lo())) {
(Ok(ref a), Ok(ref b)) if a.line != b.line && is_semi_suggestable => {
// The spans are in different lines, expected `;` and found `let` or `return`.
// High likelihood that it is only a missing `;`.
err.span_suggestion_short(
label_sp,
"a semicolon may be missing here",
";".to_string(),
Applicability::MaybeIncorrect,
);
err.emit();
return Ok(true);
}
(Ok(ref a), Ok(ref b)) if a.line == b.line => {
// When the spans are in the same line, it means that the only content between
// them is whitespace, point at the found token in that case:
@ -902,20 +876,63 @@ pub(super) fn unexpected_try_recover(
}
}
let sm = self.sess.source_map();
match (sm.lookup_line(prev_sp.lo()), sm.lookup_line(sp.lo())) {
(Ok(ref a), Ok(ref b)) if a.line == b.line => {
// When the spans are in the same line, it means that the only content
// between them is whitespace, point only at the found token.
err.span_label(sp, label_exp);
}
_ => {
err.span_label(prev_sp, label_exp);
err.span_label(sp, "unexpected token");
}
if !sm.is_multiline(prev_sp.until(sp)) {
// When the spans are in the same line, it means that the only content
// between them is whitespace, point only at the found token.
err.span_label(sp, label_exp);
} else {
err.span_label(prev_sp, label_exp);
err.span_label(sp, "unexpected token");
}
Err(err)
}
pub(super) fn expect_semi(&mut self) -> PResult<'a, ()> {
if self.eat(&token::Semi) {
return Ok(());
}
let sm = self.sess.source_map();
let msg = format!("expected `;`, found `{}`", self.this_token_descr());
let appl = Applicability::MachineApplicable;
if self.look_ahead(1, |t| t == &token::CloseDelim(token::Brace)
|| token_can_begin_expr(t) && t.kind != token::Colon
) && [token::Comma, token::Colon].contains(&self.token.kind) {
// Likely typo: `,` → `;` or `:` → `;`. This is triggered if the current token is
// either `,` or `:`, and the next token could either start a new statement or is a
// block close. For example:
//
// let x = 32:
// let y = 42;
if sm.is_multiline(self.prev_span.until(self.token.span)) {
self.bump();
let sp = self.prev_span;
self.struct_span_err(sp, &msg)
.span_suggestion(sp, "change this to `;`", ";".to_string(), appl)
.emit();
return Ok(())
}
} else if self.look_ahead(0, |t| t == &token::CloseDelim(token::Brace) || (
token_can_begin_expr(t)
&& t != &token::Semi
&& t != &token::Pound // Avoid triggering with too many trailing `#` in raw string.
)) {
// Missing semicolon typo. This is triggered if the next token could either start a
// new statement or is a block close. For example:
//
// let x = 32
// let y = 42;
if sm.is_multiline(self.prev_span.until(self.token.span)) {
let sp = self.prev_span.shrink_to_hi();
self.struct_span_err(sp, &msg)
.span_label(self.token.span, "unexpected token")
.span_suggestion_short(sp, "add `;` here", ";".to_string(), appl)
.emit();
return Ok(())
}
}
self.expect(&token::Semi).map(|_| ()) // Error unconditionally
}
pub(super) fn parse_semi_or_incorrect_foreign_fn_body(
&mut self,
ident: &Ident,
@ -943,7 +960,7 @@ pub(super) fn parse_semi_or_incorrect_foreign_fn_body(
Err(mut err) => {
err.cancel();
mem::replace(self, parser_snapshot);
self.expect(&token::Semi)?;
self.expect_semi()?;
}
}
} else {

View File

@ -98,7 +98,7 @@ fn parse_item_implementation(
if self.eat_keyword(kw::Use) {
// USE ITEM
let item_ = ItemKind::Use(P(self.parse_use_tree()?));
self.expect(&token::Semi)?;
self.expect_semi()?;
let span = lo.to(self.prev_span);
let item = self.mk_item(span, Ident::invalid(), item_, vis, attrs);
@ -526,7 +526,7 @@ fn parse_assoc_macro_invoc(&mut self, item_kind: &str, vis: Option<&Visibility>,
// eat a matched-delimiter token tree:
let (delim, tts) = self.expect_delimited_token_tree()?;
if delim != MacDelimiter::Brace {
self.expect(&token::Semi)?;
self.expect_semi()?;
}
Ok(Some(Mac {
@ -776,7 +776,7 @@ fn parse_impl_const(&mut self) -> PResult<'a, (Ident, ImplItemKind, Generics)> {
let typ = self.parse_ty()?;
self.expect(&token::Eq)?;
let expr = self.parse_expr()?;
self.expect(&token::Semi)?;
self.expect_semi()?;
Ok((name, ImplItemKind::Const(typ, expr), Generics::default()))
}
@ -813,7 +813,7 @@ fn parse_item_trait(&mut self, lo: Span, unsafety: Unsafety) -> PResult<'a, Item
let bounds = self.parse_generic_bounds(None)?;
tps.where_clause = self.parse_where_clause()?;
self.expect(&token::Semi)?;
self.expect_semi()?;
let whole_span = lo.to(self.prev_span);
if is_auto == IsAuto::Yes {
@ -927,7 +927,7 @@ fn parse_trait_item_const(&mut self) -> PResult<'a, (Ident, TraitItemKind, Gener
} else {
None
};
self.expect(&token::Semi)?;
self.expect_semi()?;
Ok((ident, TraitItemKind::Const(ty, default), Generics::default()))
}
@ -951,7 +951,7 @@ fn parse_trait_item_assoc_ty(&mut self) -> PResult<'a, (Ident, TraitItemKind, Ge
} else {
None
};
self.expect(&token::Semi)?;
self.expect_semi()?;
Ok((ident, TraitItemKind::Type(bounds, default), generics))
}
@ -1054,7 +1054,7 @@ fn parse_item_extern_crate(
} else {
(orig_name, None)
};
self.expect(&token::Semi)?;
self.expect_semi()?;
let span = lo.to(self.prev_span);
Ok(self.mk_item(span, item_name, ItemKind::ExternCrate(orig_name), visibility, attrs))
@ -1217,7 +1217,7 @@ fn parse_item_foreign_static(&mut self, vis: ast::Visibility, lo: Span, attrs: V
self.expect(&token::Colon)?;
let ty = self.parse_ty()?;
let hi = self.token.span;
self.expect(&token::Semi)?;
self.expect_semi()?;
Ok(ForeignItem {
ident,
attrs,
@ -1235,7 +1235,7 @@ fn parse_item_foreign_type(&mut self, vis: ast::Visibility, lo: Span, attrs: Vec
let ident = self.parse_ident()?;
let hi = self.token.span;
self.expect(&token::Semi)?;
self.expect_semi()?;
Ok(ast::ForeignItem {
ident,
attrs,
@ -1282,7 +1282,7 @@ fn parse_item_const(&mut self, m: Option<Mutability>) -> PResult<'a, ItemInfo> {
self.expect(&token::Eq)?;
let e = self.parse_expr()?;
self.expect(&token::Semi)?;
self.expect_semi()?;
let item = match m {
Some(m) => ItemKind::Static(ty, m, e),
None => ItemKind::Const(ty, e),
@ -1344,7 +1344,7 @@ fn parse_type_alias(&mut self) -> PResult<'a, (Ident, AliasKind, Generics)> {
let ty = self.parse_ty()?;
AliasKind::Weak(ty)
};
self.expect(&token::Semi)?;
self.expect_semi()?;
Ok((ident, alias, tps))
}
@ -1468,7 +1468,7 @@ fn parse_item_struct(&mut self) -> PResult<'a, ItemInfo> {
} else if self.token == token::OpenDelim(token::Paren) {
let body = VariantData::Tuple(self.parse_tuple_struct_body()?, DUMMY_NODE_ID);
generics.where_clause = self.parse_where_clause()?;
self.expect(&token::Semi)?;
self.expect_semi()?;
body
} else {
let token_str = self.this_token_descr();

View File

@ -432,6 +432,7 @@ pub fn parse_full_stmt(&mut self, macro_legacy_warnings: bool) -> PResult<'a, Op
None => return Ok(None),
};
let mut eat_semi = true;
match stmt.kind {
StmtKind::Expr(ref expr) if self.token != token::Eof => {
// expression without semicolon
@ -453,13 +454,14 @@ pub fn parse_full_stmt(&mut self, macro_legacy_warnings: bool) -> PResult<'a, Op
if macro_legacy_warnings && self.token != token::Semi {
self.warn_missing_semicolon();
} else {
self.expect_one_of(&[], &[token::Semi])?;
self.expect_semi()?;
eat_semi = false;
}
}
_ => {}
}
if self.eat(&token::Semi) {
if eat_semi && self.eat(&token::Semi) {
stmt = stmt.add_trailing_semicolon();
}
stmt.span = stmt.span.to(self.prev_span);

View File

@ -143,34 +143,35 @@ pub fn new(kind: LitKind, symbol: Symbol, suffix: Option<Symbol>) -> Lit {
pub(crate) fn ident_can_begin_expr(name: ast::Name, span: Span, is_raw: bool) -> bool {
let ident_token = Token::new(Ident(name, is_raw), span);
token_can_begin_expr(&ident_token)
}
pub(crate) fn token_can_begin_expr(ident_token: &Token) -> bool {
!ident_token.is_reserved_ident() ||
ident_token.is_path_segment_keyword() ||
[
kw::Async,
// FIXME: remove when `await!(..)` syntax is removed
// https://github.com/rust-lang/rust/issues/60610
kw::Await,
kw::Do,
kw::Box,
kw::Break,
kw::Continue,
kw::False,
kw::For,
kw::If,
kw::Let,
kw::Loop,
kw::Match,
kw::Move,
kw::Return,
kw::True,
kw::Unsafe,
kw::While,
kw::Yield,
kw::Static,
].contains(&name)
match ident_token.kind {
TokenKind::Ident(ident, _) => [
kw::Async,
kw::Do,
kw::Box,
kw::Break,
kw::Continue,
kw::False,
kw::For,
kw::If,
kw::Let,
kw::Loop,
kw::Match,
kw::Move,
kw::Return,
kw::True,
kw::Unsafe,
kw::While,
kw::Yield,
kw::Static,
].contains(&ident),
_=> false,
}
}
fn ident_can_begin_type(name: ast::Name, span: Span, is_raw: bool) -> bool {

View File

@ -2,7 +2,7 @@ error: expected `;`, found `::`
--> $DIR/import-from-path.rs:2:15
|
LL | use foo::{bar}::baz
| ^^ expected `;`
| ^^ expected `;` here
error: aborting due to previous error

View File

@ -1,8 +1,8 @@
error: expected `;`, found keyword `as`
error: expected `;`, found `as`
--> $DIR/import-from-rename.rs:3:16
|
LL | use foo::{bar} as baz;
| ^^ expected `;`
| ^^ expected `;` here
error: aborting due to previous error

View File

@ -2,7 +2,7 @@ error: expected `;`, found `::`
--> $DIR/import-glob-path.rs:2:11
|
LL | use foo::*::bar
| ^^ expected `;`
| ^^ expected `;` here
error: aborting due to previous error

View File

@ -1,8 +1,8 @@
error: expected `;`, found keyword `as`
error: expected `;`, found `as`
--> $DIR/import-glob-rename.rs:3:12
|
LL | use foo::* as baz;
| ^^ expected `;`
| ^^ expected `;` here
error: aborting due to previous error

View File

@ -2,5 +2,5 @@
fn main()
{
let x = 3
} //~ ERROR: expected one of `.`, `;`, `?`, or an operator, found `}`
let x = 3 //~ ERROR: expected `;`
}

View File

@ -1,10 +1,10 @@
error: expected one of `.`, `;`, `?`, or an operator, found `}`
--> $DIR/issue-3036.rs:6:1
error: expected `;`, found ``}``
--> $DIR/issue-3036.rs:5:14
|
LL | let x = 3
| - expected one of `.`, `;`, `?`, or an operator here
| ^ help: add `;` here
LL | }
| ^ unexpected token
| - unexpected token
error: aborting due to previous error

View File

@ -1,13 +1,13 @@
fn main() {
let _: usize = ()
//~^ ERROR mismatched types
//~| ERROR expected `;`
let _ = 3;
//~^ ERROR expected one of
}
fn foo() -> usize {
let _: usize = ()
//~^ ERROR mismatched types
//~| ERROR expected `;`
return 3;
//~^ ERROR expected one of
}

View File

@ -1,20 +1,20 @@
error: expected one of `.`, `;`, `?`, or an operator, found `let`
--> $DIR/recover-missing-semi.rs:4:5
error: expected `;`, found `keyword `let``
--> $DIR/recover-missing-semi.rs:2:22
|
LL | let _: usize = ()
| - help: a semicolon may be missing here
LL |
| ^ help: add `;` here
...
LL | let _ = 3;
| ^^^
| --- unexpected token
error: expected one of `.`, `;`, `?`, or an operator, found `return`
--> $DIR/recover-missing-semi.rs:11:5
error: expected `;`, found `keyword `return``
--> $DIR/recover-missing-semi.rs:9:22
|
LL | let _: usize = ()
| - help: a semicolon may be missing here
LL |
| ^ help: add `;` here
...
LL | return 3;
| ^^^^^^
| ------ unexpected token
error[E0308]: mismatched types
--> $DIR/recover-missing-semi.rs:2:20