From 40dcf89a26bde41734f6c3eb71d9eb6420e45ef8 Mon Sep 17 00:00:00 2001
From: Takayuki Maeda <takoyaki0316@gmail.com>
Date: Mon, 15 Aug 2022 16:10:31 +0900
Subject: [PATCH] suggest adding a missing semicolon before an item

---
 compiler/rustc_ast/src/token.rs               | 24 ++++++
 .../rustc_parse/src/parser/diagnostics.rs     |  6 +-
 .../recover-missing-semi-before-item.fixed    | 61 ++++++++++++++
 .../recover-missing-semi-before-item.rs       | 61 ++++++++++++++
 .../recover-missing-semi-before-item.stderr   | 83 +++++++++++++++++++
 5 files changed, 233 insertions(+), 2 deletions(-)
 create mode 100644 src/test/ui/parser/recover-missing-semi-before-item.fixed
 create mode 100644 src/test/ui/parser/recover-missing-semi-before-item.rs
 create mode 100644 src/test/ui/parser/recover-missing-semi-before-item.stderr

diff --git a/compiler/rustc_ast/src/token.rs b/compiler/rustc_ast/src/token.rs
index 85d9687c600dc..dd98946b4cc5e 100644
--- a/compiler/rustc_ast/src/token.rs
+++ b/compiler/rustc_ast/src/token.rs
@@ -436,6 +436,30 @@ impl Token {
             || self == &OpenDelim(Delimiter::Parenthesis)
     }
 
+    /// Returns `true` if the token can appear at the start of an item.
+    pub fn can_begin_item(&self) -> bool {
+        match self.kind {
+            Ident(name, _) => [
+                kw::Fn,
+                kw::Use,
+                kw::Struct,
+                kw::Enum,
+                kw::Pub,
+                kw::Trait,
+                kw::Extern,
+                kw::Impl,
+                kw::Unsafe,
+                kw::Static,
+                kw::Union,
+                kw::Macro,
+                kw::Mod,
+                kw::Type,
+            ]
+            .contains(&name),
+            _ => false,
+        }
+    }
+
     /// Returns `true` if the token is any literal.
     pub fn is_lit(&self) -> bool {
         matches!(self.kind, Literal(..))
diff --git a/compiler/rustc_parse/src/parser/diagnostics.rs b/compiler/rustc_parse/src/parser/diagnostics.rs
index f4c6b33a52924..d0fb768caa687 100644
--- a/compiler/rustc_parse/src/parser/diagnostics.rs
+++ b/compiler/rustc_parse/src/parser/diagnostics.rs
@@ -555,10 +555,12 @@ impl<'a> Parser<'a> {
                 return Ok(true);
             } else if self.look_ahead(0, |t| {
                 t == &token::CloseDelim(Delimiter::Brace)
-                    || (t.can_begin_expr() && t != &token::Semi && t != &token::Pound)
+                    || ((t.can_begin_expr() || t.can_begin_item())
+                        && t != &token::Semi
+                        && t != &token::Pound)
                     // Avoid triggering with too many trailing `#` in raw string.
                     || (sm.is_multiline(
-                        self.prev_token.span.shrink_to_hi().until(self.token.span.shrink_to_lo())
+                        self.prev_token.span.shrink_to_hi().until(self.token.span.shrink_to_lo()),
                     ) && t == &token::Pound)
             }) && !expected.contains(&TokenType::Token(token::Comma))
             {
diff --git a/src/test/ui/parser/recover-missing-semi-before-item.fixed b/src/test/ui/parser/recover-missing-semi-before-item.fixed
new file mode 100644
index 0000000000000..0be17e69e8ff7
--- /dev/null
+++ b/src/test/ui/parser/recover-missing-semi-before-item.fixed
@@ -0,0 +1,61 @@
+// run-rustfix
+
+#![allow(unused_variables, dead_code)]
+
+fn for_struct() {
+    let foo = 3; //~ ERROR expected `;`, found keyword `struct`
+    struct Foo;
+}
+
+fn for_union() {
+    let foo = 3; //~ ERROR expected `;`, found `union`
+    union Foo {
+        foo: usize,
+    }
+}
+
+fn for_enum() {
+    let foo = 3; //~ ERROR expected `;`, found keyword `enum`
+    enum Foo {
+        Bar,
+    }
+}
+
+fn for_fn() {
+    let foo = 3; //~ ERROR expected `;`, found keyword `fn`
+    fn foo() {}
+}
+
+fn for_extern() {
+    let foo = 3; //~ ERROR expected `;`, found keyword `extern`
+    extern fn foo() {}
+}
+
+fn for_impl() {
+    struct Foo;
+    let foo = 3; //~ ERROR expected `;`, found keyword `impl`
+    impl Foo {}
+}
+
+fn for_use() {
+    let foo = 3; //~ ERROR expected `;`, found keyword `pub`
+    pub use bar::Bar;
+}
+
+fn for_mod() {
+    let foo = 3; //~ ERROR expected `;`, found keyword `mod`
+    mod foo {}
+}
+
+fn for_type() {
+    let foo = 3; //~ ERROR expected `;`, found keyword `type`
+    type Foo = usize;
+}
+
+mod bar {
+    pub struct Bar;
+}
+
+const X: i32 = 123; //~ ERROR expected `;`, found keyword `fn`
+
+fn main() {}
diff --git a/src/test/ui/parser/recover-missing-semi-before-item.rs b/src/test/ui/parser/recover-missing-semi-before-item.rs
new file mode 100644
index 0000000000000..867b7b749bb1f
--- /dev/null
+++ b/src/test/ui/parser/recover-missing-semi-before-item.rs
@@ -0,0 +1,61 @@
+// run-rustfix
+
+#![allow(unused_variables, dead_code)]
+
+fn for_struct() {
+    let foo = 3 //~ ERROR expected `;`, found keyword `struct`
+    struct Foo;
+}
+
+fn for_union() {
+    let foo = 3 //~ ERROR expected `;`, found `union`
+    union Foo {
+        foo: usize,
+    }
+}
+
+fn for_enum() {
+    let foo = 3 //~ ERROR expected `;`, found keyword `enum`
+    enum Foo {
+        Bar,
+    }
+}
+
+fn for_fn() {
+    let foo = 3 //~ ERROR expected `;`, found keyword `fn`
+    fn foo() {}
+}
+
+fn for_extern() {
+    let foo = 3 //~ ERROR expected `;`, found keyword `extern`
+    extern fn foo() {}
+}
+
+fn for_impl() {
+    struct Foo;
+    let foo = 3 //~ ERROR expected `;`, found keyword `impl`
+    impl Foo {}
+}
+
+fn for_use() {
+    let foo = 3 //~ ERROR expected `;`, found keyword `pub`
+    pub use bar::Bar;
+}
+
+fn for_mod() {
+    let foo = 3 //~ ERROR expected `;`, found keyword `mod`
+    mod foo {}
+}
+
+fn for_type() {
+    let foo = 3 //~ ERROR expected `;`, found keyword `type`
+    type Foo = usize;
+}
+
+mod bar {
+    pub struct Bar;
+}
+
+const X: i32 = 123 //~ ERROR expected `;`, found keyword `fn`
+
+fn main() {}
diff --git a/src/test/ui/parser/recover-missing-semi-before-item.stderr b/src/test/ui/parser/recover-missing-semi-before-item.stderr
new file mode 100644
index 0000000000000..61c43f2f18998
--- /dev/null
+++ b/src/test/ui/parser/recover-missing-semi-before-item.stderr
@@ -0,0 +1,83 @@
+error: expected `;`, found keyword `struct`
+  --> $DIR/recover-missing-semi-before-item.rs:6:16
+   |
+LL |     let foo = 3
+   |                ^ help: add `;` here
+LL |     struct Foo;
+   |     ------ unexpected token
+
+error: expected `;`, found `union`
+  --> $DIR/recover-missing-semi-before-item.rs:11:16
+   |
+LL |     let foo = 3
+   |                ^ help: add `;` here
+LL |     union Foo {
+   |     ----- unexpected token
+
+error: expected `;`, found keyword `enum`
+  --> $DIR/recover-missing-semi-before-item.rs:18:16
+   |
+LL |     let foo = 3
+   |                ^ help: add `;` here
+LL |     enum Foo {
+   |     ---- unexpected token
+
+error: expected `;`, found keyword `fn`
+  --> $DIR/recover-missing-semi-before-item.rs:25:16
+   |
+LL |     let foo = 3
+   |                ^ help: add `;` here
+LL |     fn foo() {}
+   |     -- unexpected token
+
+error: expected `;`, found keyword `extern`
+  --> $DIR/recover-missing-semi-before-item.rs:30:16
+   |
+LL |     let foo = 3
+   |                ^ help: add `;` here
+LL |     extern fn foo() {}
+   |     ------ unexpected token
+
+error: expected `;`, found keyword `impl`
+  --> $DIR/recover-missing-semi-before-item.rs:36:16
+   |
+LL |     let foo = 3
+   |                ^ help: add `;` here
+LL |     impl Foo {}
+   |     ---- unexpected token
+
+error: expected `;`, found keyword `pub`
+  --> $DIR/recover-missing-semi-before-item.rs:41:16
+   |
+LL |     let foo = 3
+   |                ^ help: add `;` here
+LL |     pub use bar::Bar;
+   |     --- unexpected token
+
+error: expected `;`, found keyword `mod`
+  --> $DIR/recover-missing-semi-before-item.rs:46:16
+   |
+LL |     let foo = 3
+   |                ^ help: add `;` here
+LL |     mod foo {}
+   |     --- unexpected token
+
+error: expected `;`, found keyword `type`
+  --> $DIR/recover-missing-semi-before-item.rs:51:16
+   |
+LL |     let foo = 3
+   |                ^ help: add `;` here
+LL |     type Foo = usize;
+   |     ---- unexpected token
+
+error: expected `;`, found keyword `fn`
+  --> $DIR/recover-missing-semi-before-item.rs:59:19
+   |
+LL | const X: i32 = 123
+   |                   ^ help: add `;` here
+LL |
+LL | fn main() {}
+   | -- unexpected token
+
+error: aborting due to 10 previous errors
+