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:
parent
03a50ae9b8
commit
d673d0ac84
@ -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 {
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -2,5 +2,5 @@
|
||||
|
||||
fn main()
|
||||
{
|
||||
let x = 3
|
||||
} //~ ERROR: expected one of `.`, `;`, `?`, or an operator, found `}`
|
||||
let x = 3 //~ ERROR: expected `;`
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user