diff --git a/compiler/rustc_parse/locales/en-US.ftl b/compiler/rustc_parse/locales/en-US.ftl
index e76e91fc1b135..72c30662fbdab 100644
--- a/compiler/rustc_parse/locales/en-US.ftl
+++ b/compiler/rustc_parse/locales/en-US.ftl
@@ -439,6 +439,13 @@ parse_visibility_not_followed_by_item = visibility `{$vis}` is not followed by a
     .label = the visibility
     .help = you likely meant to define an item, e.g., `{$vis} fn foo() {"{}"}`
 
+parse_visibility_followed_by_let = visibility does not apply to `let` statement
+    .suggestion = remove pub
+
+parse_invalid_let_outside_function = `let` is invalid outside of a function
+    .suggestion_static = consider using `static` instead of `let`
+    .suggestion_const = consider using `const` instead of `let`
+
 parse_default_not_followed_by_item = `default` is not followed by an item
     .label = the `default` qualifier
     .note = only `fn`, `const`, `type`, or `impl` items may be prefixed by `default`
diff --git a/compiler/rustc_parse/src/errors.rs b/compiler/rustc_parse/src/errors.rs
index 1662db36d10f9..35ecfc220a1de 100644
--- a/compiler/rustc_parse/src/errors.rs
+++ b/compiler/rustc_parse/src/errors.rs
@@ -1403,6 +1403,33 @@ pub(crate) struct VisibilityNotFollowedByItem {
     pub vis: Visibility,
 }
 
+#[derive(Diagnostic)]
+#[diag(parse_visibility_followed_by_let)]
+pub(crate) struct VisibilityFollowedByLet {
+    #[primary_span]
+    #[suggestion(code = "let", applicability = "machine-applicable")]
+    pub span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(parse_invalid_let_outside_function)]
+pub(crate) struct InvalidLetOutsideFunction {
+    #[primary_span]
+    #[suggestion(
+        parse_suggestion_static,
+        style = "verbose",
+        code = "static",
+        applicability = "machine-applicable"
+    )]
+    #[suggestion(
+        parse_suggestion_const,
+        style = "verbose",
+        code = "const",
+        applicability = "machine-applicable"
+    )]
+    pub span: Span,
+}
+
 #[derive(Diagnostic)]
 #[diag(parse_default_not_followed_by_item)]
 #[note]
diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs
index 9d9ae154ad42e..0b33ff49c1ff4 100644
--- a/compiler/rustc_parse/src/parser/item.rs
+++ b/compiler/rustc_parse/src/parser/item.rs
@@ -68,16 +68,17 @@ impl<'a> Parser<'a> {
         }
 
         if !self.eat(term) {
-            let token_str = super::token_descr(&self.token);
             if !self.maybe_consume_incorrect_semicolon(&items) {
-                let msg = &format!("expected item, found {token_str}");
-                let mut err = self.struct_span_err(self.token.span, msg);
-                let label = if self.is_kw_followed_by_ident(kw::Let) {
-                    "consider using `const` or `static` instead of `let` for global variables"
-                } else {
-                    "expected item"
-                };
-                err.span_label(self.token.span, label);
+                if self.is_kw_followed_by_ident(kw::Let) {
+                    let err = self
+                        .sess
+                        .create_err(errors::InvalidLetOutsideFunction { span: self.token.span });
+                    return Err(err);
+                }
+                let token_str = super::token_descr(&self.token);
+                let mut err = self
+                    .struct_span_err(self.token.span, &format!("expected item, found {token_str}"));
+                err.span_label(self.token.span, "expected item");
                 return Err(err);
             }
         }
diff --git a/compiler/rustc_parse/src/parser/stmt.rs b/compiler/rustc_parse/src/parser/stmt.rs
index 92a22ffc2b07b..bd025048cff29 100644
--- a/compiler/rustc_parse/src/parser/stmt.rs
+++ b/compiler/rustc_parse/src/parser/stmt.rs
@@ -111,6 +111,11 @@ impl<'a> Parser<'a> {
             // Do not attempt to parse an expression if we're done here.
             self.error_outer_attrs(attrs);
             self.mk_stmt(lo, StmtKind::Empty)
+        } else if self.prev_token.is_keyword(kw::Pub) && self.token.is_keyword(kw::Let) {
+            let err = self.sess.create_err(errors::VisibilityFollowedByLet {
+                span: self.prev_token.span.to(self.token.span),
+            });
+            return Err(err);
         } else if self.token != token::CloseDelim(Delimiter::Brace) {
             // Remainder are line-expr stmts.
             let e = match force_collect {
diff --git a/tests/ui/parser/let-outside-function.rs b/tests/ui/parser/let-outside-function.rs
new file mode 100644
index 0000000000000..ec2f881ea2c33
--- /dev/null
+++ b/tests/ui/parser/let-outside-function.rs
@@ -0,0 +1,5 @@
+let a = 1;
+//~^ ERROR `let` is invalid outside of a function
+
+fn main() {
+}
diff --git a/tests/ui/parser/let-outside-function.stderr b/tests/ui/parser/let-outside-function.stderr
new file mode 100644
index 0000000000000..22bb39eebc78b
--- /dev/null
+++ b/tests/ui/parser/let-outside-function.stderr
@@ -0,0 +1,17 @@
+error: `let` is invalid outside of a function
+  --> $DIR/let-outside-function.rs:1:1
+   |
+LL | let a = 1;
+   | ^^^
+   |
+help: consider using `static` instead of `let`
+   |
+LL | static a = 1;
+   | ~~~~~~
+help: consider using `const` instead of `let`
+   |
+LL | const a = 1;
+   | ~~~~~
+
+error: aborting due to previous error
+
diff --git a/tests/ui/parser/pub-let.fixed b/tests/ui/parser/pub-let.fixed
new file mode 100644
index 0000000000000..a76d84243bee1
--- /dev/null
+++ b/tests/ui/parser/pub-let.fixed
@@ -0,0 +1,9 @@
+// run-rustfix
+
+#![allow(unused_variables)]
+
+fn main() {
+    let x = 1;
+    //~^ ERROR visibility `pub` is not followed by an item
+    //~^^ ERROR visibility does not apply to `let` statement
+}
diff --git a/tests/ui/parser/pub-let.rs b/tests/ui/parser/pub-let.rs
new file mode 100644
index 0000000000000..9de063405e680
--- /dev/null
+++ b/tests/ui/parser/pub-let.rs
@@ -0,0 +1,9 @@
+// run-rustfix
+
+#![allow(unused_variables)]
+
+fn main() {
+    pub let x = 1;
+    //~^ ERROR visibility `pub` is not followed by an item
+    //~^^ ERROR visibility does not apply to `let` statement
+}
diff --git a/tests/ui/parser/pub-let.stderr b/tests/ui/parser/pub-let.stderr
new file mode 100644
index 0000000000000..ae221d4999aca
--- /dev/null
+++ b/tests/ui/parser/pub-let.stderr
@@ -0,0 +1,16 @@
+error: visibility `pub` is not followed by an item
+  --> $DIR/pub-let.rs:6:5
+   |
+LL |     pub let x = 1;
+   |     ^^^ the visibility
+   |
+   = help: you likely meant to define an item, e.g., `pub fn foo() {}`
+
+error: visibility does not apply to `let` statement
+  --> $DIR/pub-let.rs:6:5
+   |
+LL |     pub let x = 1;
+   |     ^^^^^^^ help: remove pub: `let`
+
+error: aborting due to 2 previous errors
+
diff --git a/tests/ui/parser/suggest-const-for-global-var.rs b/tests/ui/parser/suggest-const-for-global-var.rs
index d6216cb7ac275..432546aa303e7 100644
--- a/tests/ui/parser/suggest-const-for-global-var.rs
+++ b/tests/ui/parser/suggest-const-for-global-var.rs
@@ -1,5 +1,5 @@
 let X: i32 = 12;
-//~^ ERROR expected item, found keyword `let`
+//~^ ERROR `let` is invalid outside of a function
 
 fn main() {
     println!("{}", X);
diff --git a/tests/ui/parser/suggest-const-for-global-var.stderr b/tests/ui/parser/suggest-const-for-global-var.stderr
index 94e44ec7f6ce1..dd2a243309ffc 100644
--- a/tests/ui/parser/suggest-const-for-global-var.stderr
+++ b/tests/ui/parser/suggest-const-for-global-var.stderr
@@ -1,8 +1,17 @@
-error: expected item, found keyword `let`
+error: `let` is invalid outside of a function
   --> $DIR/suggest-const-for-global-var.rs:1:1
    |
 LL | let X: i32 = 12;
-   | ^^^ consider using `const` or `static` instead of `let` for global variables
+   | ^^^
+   |
+help: consider using `static` instead of `let`
+   |
+LL | static X: i32 = 12;
+   | ~~~~~~
+help: consider using `const` instead of `let`
+   |
+LL | const X: i32 = 12;
+   | ~~~~~
 
 error: aborting due to previous error