about summary refs log tree commit diff
diff options
context:
space:
mode:
authorEsteban Küber <esteban@kuber.com.ar>2019-10-20 14:35:46 -0700
committerEsteban Küber <esteban@kuber.com.ar>2019-10-28 10:53:13 -0700
commitd673d0ac8462bd30612b0cce719ac0bf15dfaf86 (patch)
tree0822ed1a284b2761a7b2a61b33a5b5dcaf0a123d
parent03a50ae9b87021d4a166c70d2c932f1cb0aa8f28 (diff)
downloadrust-d673d0ac8462bd30612b0cce719ac0bf15dfaf86.tar.gz
rust-d673d0ac8462bd30612b0cce719ac0bf15dfaf86.zip
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.
-rw-r--r--src/libsyntax/parse/parser/diagnostics.rs91
-rw-r--r--src/libsyntax/parse/parser/item.rs24
-rw-r--r--src/libsyntax/parse/parser/stmt.rs6
-rw-r--r--src/libsyntax/parse/token.rs51
-rw-r--r--src/test/ui/parser/import-from-path.stderr2
-rw-r--r--src/test/ui/parser/import-from-rename.stderr4
-rw-r--r--src/test/ui/parser/import-glob-path.stderr2
-rw-r--r--src/test/ui/parser/import-glob-rename.stderr4
-rw-r--r--src/test/ui/parser/issue-3036.rs4
-rw-r--r--src/test/ui/parser/issue-3036.stderr8
-rw-r--r--src/test/ui/parser/recover-missing-semi.rs4
-rw-r--r--src/test/ui/parser/recover-missing-semi.stderr20
12 files changed, 120 insertions, 100 deletions
diff --git a/src/libsyntax/parse/parser/diagnostics.rs b/src/libsyntax/parse/parser/diagnostics.rs
index 06982c789db..677d16a40d9 100644
--- a/src/libsyntax/parse/parser/diagnostics.rs
+++ b/src/libsyntax/parse/parser/diagnostics.rs
@@ -6,7 +6,7 @@ use crate::ast::{
     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 @@ impl<'a> Parser<'a> {
             }
         }
 
-        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,18 +876,61 @@ impl<'a> Parser<'a> {
             }
         }
         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);
+        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(())
             }
-            _ => {
-                err.span_label(prev_sp, label_exp);
-                err.span_label(sp, "unexpected token");
+        } 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(())
             }
         }
-        Err(err)
+        self.expect(&token::Semi).map(|_| ()) // Error unconditionally
     }
 
     pub(super) fn parse_semi_or_incorrect_foreign_fn_body(
@@ -943,7 +960,7 @@ impl<'a> Parser<'a> {
                 Err(mut err) => {
                     err.cancel();
                     mem::replace(self, parser_snapshot);
-                    self.expect(&token::Semi)?;
+                    self.expect_semi()?;
                 }
             }
         } else {
diff --git a/src/libsyntax/parse/parser/item.rs b/src/libsyntax/parse/parser/item.rs
index 506a1a2a27a..fe125336190 100644
--- a/src/libsyntax/parse/parser/item.rs
+++ b/src/libsyntax/parse/parser/item.rs
@@ -98,7 +98,7 @@ impl<'a> Parser<'a> {
         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 @@ impl<'a> Parser<'a> {
             // 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 @@ impl<'a> Parser<'a> {
         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 @@ impl<'a> Parser<'a> {
 
             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 @@ impl<'a> Parser<'a> {
         } else {
             None
         };
-        self.expect(&token::Semi)?;
+        self.expect_semi()?;
         Ok((ident, TraitItemKind::Const(ty, default), Generics::default()))
     }
 
@@ -951,7 +951,7 @@ impl<'a> Parser<'a> {
         } else {
             None
         };
-        self.expect(&token::Semi)?;
+        self.expect_semi()?;
 
         Ok((ident, TraitItemKind::Type(bounds, default), generics))
     }
@@ -1054,7 +1054,7 @@ impl<'a> Parser<'a> {
         } 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 @@ impl<'a> Parser<'a> {
         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 @@ impl<'a> Parser<'a> {
 
         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 @@ impl<'a> Parser<'a> {
 
         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 @@ impl<'a> Parser<'a> {
             let ty = self.parse_ty()?;
             AliasKind::Weak(ty)
         };
-        self.expect(&token::Semi)?;
+        self.expect_semi()?;
         Ok((ident, alias, tps))
     }
 
@@ -1468,7 +1468,7 @@ impl<'a> Parser<'a> {
         } 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();
diff --git a/src/libsyntax/parse/parser/stmt.rs b/src/libsyntax/parse/parser/stmt.rs
index ea7e4c05ea1..4f51fefe66f 100644
--- a/src/libsyntax/parse/parser/stmt.rs
+++ b/src/libsyntax/parse/parser/stmt.rs
@@ -432,6 +432,7 @@ impl<'a> Parser<'a> {
             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 @@ impl<'a> Parser<'a> {
                 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);
diff --git a/src/libsyntax/parse/token.rs b/src/libsyntax/parse/token.rs
index 4a8b25c6107..03e77b199cc 100644
--- a/src/libsyntax/parse/token.rs
+++ b/src/libsyntax/parse/token.rs
@@ -143,34 +143,35 @@ impl 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 {
diff --git a/src/test/ui/parser/import-from-path.stderr b/src/test/ui/parser/import-from-path.stderr
index 5842037fb80..84c3b31df20 100644
--- a/src/test/ui/parser/import-from-path.stderr
+++ b/src/test/ui/parser/import-from-path.stderr
@@ -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
 
diff --git a/src/test/ui/parser/import-from-rename.stderr b/src/test/ui/parser/import-from-rename.stderr
index a966e993737..53ceb0280f9 100644
--- a/src/test/ui/parser/import-from-rename.stderr
+++ b/src/test/ui/parser/import-from-rename.stderr
@@ -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
 
diff --git a/src/test/ui/parser/import-glob-path.stderr b/src/test/ui/parser/import-glob-path.stderr
index ebca2db8305..44f4fc57a4a 100644
--- a/src/test/ui/parser/import-glob-path.stderr
+++ b/src/test/ui/parser/import-glob-path.stderr
@@ -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
 
diff --git a/src/test/ui/parser/import-glob-rename.stderr b/src/test/ui/parser/import-glob-rename.stderr
index 28538732782..56f021c29d4 100644
--- a/src/test/ui/parser/import-glob-rename.stderr
+++ b/src/test/ui/parser/import-glob-rename.stderr
@@ -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
 
diff --git a/src/test/ui/parser/issue-3036.rs b/src/test/ui/parser/issue-3036.rs
index 00b241b9054..6a8b67fefa7 100644
--- a/src/test/ui/parser/issue-3036.rs
+++ b/src/test/ui/parser/issue-3036.rs
@@ -2,5 +2,5 @@
 
 fn main()
 {
-    let x = 3
-} //~ ERROR: expected one of `.`, `;`, `?`, or an operator, found `}`
+    let x = 3 //~ ERROR: expected `;`
+}
diff --git a/src/test/ui/parser/issue-3036.stderr b/src/test/ui/parser/issue-3036.stderr
index 18947b8fa40..b6557163d45 100644
--- a/src/test/ui/parser/issue-3036.stderr
+++ b/src/test/ui/parser/issue-3036.stderr
@@ -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
 
diff --git a/src/test/ui/parser/recover-missing-semi.rs b/src/test/ui/parser/recover-missing-semi.rs
index 1893dc716be..f47d5e6805f 100644
--- a/src/test/ui/parser/recover-missing-semi.rs
+++ b/src/test/ui/parser/recover-missing-semi.rs
@@ -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
 }
diff --git a/src/test/ui/parser/recover-missing-semi.stderr b/src/test/ui/parser/recover-missing-semi.stderr
index 99339e4dd50..c40918ee2bd 100644
--- a/src/test/ui/parser/recover-missing-semi.stderr
+++ b/src/test/ui/parser/recover-missing-semi.stderr
@@ -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