diff --git a/compiler/rustc_ast_pretty/src/pprust/mod.rs b/compiler/rustc_ast_pretty/src/pprust/mod.rs index 726ceebe3c524..c18cbfdd2df29 100644 --- a/compiler/rustc_ast_pretty/src/pprust/mod.rs +++ b/compiler/rustc_ast_pretty/src/pprust/mod.rs @@ -63,6 +63,10 @@ pub fn path_segment_to_string(p: &ast::PathSegment) -> String { State::new().path_segment_to_string(p) } +pub fn stmt_to_string(p: &ast::Stmt) -> String { + State::new().stmt_to_string(p) +} + pub fn vis_to_string(v: &ast::Visibility) -> String { State::new().vis_to_string(v) } diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs index 9fc82d84225cc..43d49d6ef9ed1 100644 --- a/compiler/rustc_parse/src/parser/item.rs +++ b/compiler/rustc_parse/src/parser/item.rs @@ -21,7 +21,8 @@ use tracing::debug; use super::diagnostics::{ConsumeClosingDelim, dummy_arg}; use super::ty::{AllowPlus, RecoverQPath, RecoverReturnSign}; use super::{ - AttrWrapper, FollowedByType, ForceCollect, Parser, PathStyle, Trailing, UsePreAttrPos, + AttemptLocalParseRecovery, AttrWrapper, FollowedByType, ForceCollect, Parser, PathStyle, + Trailing, UsePreAttrPos, }; use crate::errors::{self, MacroExpandsToAdtField}; use crate::{fluent_generated as fluent, maybe_whole}; @@ -74,21 +75,11 @@ impl<'a> Parser<'a> { items.push(item); } + // The last token should be `term`: either EOF or `}`. If it's not that means that we've had an error + // parsing an item if !self.eat(term) { - let token_str = super::token_descr(&self.token); if !self.maybe_consume_incorrect_semicolon(items.last().map(|x| &**x)) { - let msg = format!("expected item, found {token_str}"); - let mut err = self.dcx().struct_span_err(self.token.span, msg); - let span = self.token.span; - if self.is_kw_followed_by_ident(kw::Let) { - err.span_label( - span, - "consider using `const` or `static` instead of `let` for global variables", - ); - } else { - err.span_label(span, "expected item") - .note("for a full list of items that can appear in modules, see "); - }; + let err = self.fallback_incorrect_item(); return Err(err); } } @@ -97,6 +88,64 @@ impl<'a> Parser<'a> { let mod_spans = ModSpans { inner_span: lo.to(self.prev_token.span), inject_use_span }; Ok((attrs, items, mod_spans)) } + + /// Tries to parse the item as a statement to provide further diagnostics. + fn fallback_incorrect_item(&mut self) -> rustc_errors::Diag<'a> { + let token_str = super::token_descr(&self.token); + let token_span = self.token.span; + let mut err = + self.dcx().struct_span_err(token_span, format!("expected item, found {token_str}")); + + let mut do_default_diag = true; + + match self.parse_full_stmt(AttemptLocalParseRecovery::No) { + Ok(Some(stmt)) => { + do_default_diag = false; + let span = stmt.span; + match &stmt.kind { + StmtKind::Let(_) => { + err.span_label(span, "unexpected `let` binding outside of a function") + .help(format!("consider using `const` or `static` instead of `let` for global variables, or put it inside of a function: fn foo() {{ {} }}", + pprust::stmt_to_string(&stmt))); + } + StmtKind::Semi(expr) => { + err.span_label(span, "unexpected expression").help(format!( + "consider putting it inside a function: fn foo() {{ {}; }}", + pprust::expr_to_string(expr) + )); + } + StmtKind::Expr(expr) => { + err.span_label(span, "unexpected expression").help(format!( + "consider putting it inside a function: fn foo() {{ {} }}", + pprust::expr_to_string(expr) + )); + } + StmtKind::Empty => { + unreachable!( + "Should have been handled by maybe_consume_incorrect_semicolon" + ); + } + StmtKind::Item(_) | StmtKind::MacCall(_) => { + unreachable!("These should be valid items!") + } + }; + } + // It's not a statement, we can't do much recovery. + Ok(None) => {} + Err(e) => { + // We don't really care about an error parsing this statement. + e.cancel(); + } + } + + if do_default_diag { + err.span_label(token_span, "expected item"); + } + + err.note("for a full list of items that can appear in modules, see "); + + err + } } pub(super) type ItemInfo = (Ident, ItemKind); diff --git a/tests/ui/parser/issues/issue-113110-non-item-at-module-root.stderr b/tests/ui/parser/issues/issue-113110-non-item-at-module-root.stderr index a47dd41036997..709420524b8f4 100644 --- a/tests/ui/parser/issues/issue-113110-non-item-at-module-root.stderr +++ b/tests/ui/parser/issues/issue-113110-non-item-at-module-root.stderr @@ -2,8 +2,9 @@ error: expected item, found `5` --> $DIR/issue-113110-non-item-at-module-root.rs:1:2 | LL | 5 - | ^ expected item + | ^ unexpected expression | + = help: consider putting it inside a function: fn foo() { 5 } = note: for a full list of items that can appear in modules, see error: aborting due to 1 previous error diff --git a/tests/ui/parser/issues/issue-62913.stderr b/tests/ui/parser/issues/issue-62913.stderr index c33e46837287f..9830e0ffadd55 100644 --- a/tests/ui/parser/issues/issue-62913.stderr +++ b/tests/ui/parser/issues/issue-62913.stderr @@ -16,8 +16,9 @@ error: expected item, found `"\u\"` --> $DIR/issue-62913.rs:1:1 | LL | "\u\" - | ^^^^^^ expected item + | ^^^^^^ unexpected expression | + = help: consider putting it inside a function: fn foo() { "\u\" } = note: for a full list of items that can appear in modules, see error: aborting due to 3 previous errors diff --git a/tests/ui/parser/recover/pub-let-outside-fn.rs b/tests/ui/parser/recover/pub-let-outside-fn.rs new file mode 100644 index 0000000000000..1c1d7227b8d84 --- /dev/null +++ b/tests/ui/parser/recover/pub-let-outside-fn.rs @@ -0,0 +1,5 @@ +mod m { + pub let answer = 42; + //~^ ERROR visibility `pub` is not followed by an item + //~| ERROR expected item, found keyword `let` +} diff --git a/tests/ui/parser/recover/pub-let-outside-fn.stderr b/tests/ui/parser/recover/pub-let-outside-fn.stderr new file mode 100644 index 0000000000000..ea96ed57a5f8d --- /dev/null +++ b/tests/ui/parser/recover/pub-let-outside-fn.stderr @@ -0,0 +1,21 @@ +error: visibility `pub` is not followed by an item + --> $DIR/pub-let-outside-fn.rs:2:5 + | +LL | pub let answer = 42; + | ^^^ the visibility + | + = help: you likely meant to define an item, e.g., `pub fn foo() {}` + +error: expected item, found keyword `let` + --> $DIR/pub-let-outside-fn.rs:2:9 + | +LL | pub let answer = 42; + | ^^^------------- + | | + | unexpected `let` binding outside of a function + | + = help: consider using `const` or `static` instead of `let` for global variables, or put it inside of a function: fn foo() { let answer = 42; } + = note: for a full list of items that can appear in modules, see + +error: aborting due to 2 previous errors + diff --git a/tests/ui/parser/shebang/shebang-doc-comment.stderr b/tests/ui/parser/shebang/shebang-doc-comment.stderr index 92fefded55a50..8c6ef5e13483a 100644 --- a/tests/ui/parser/shebang/shebang-doc-comment.stderr +++ b/tests/ui/parser/shebang/shebang-doc-comment.stderr @@ -2,8 +2,11 @@ error: expected item, found `[` --> $DIR/shebang-doc-comment.rs:2:1 | LL | [allow(unused_variables)] - | ^ expected item + | ^------------------------ + | | + | unexpected expression | + = help: consider putting it inside a function: fn foo() { [allow(unused_variables)] } = note: for a full list of items that can appear in modules, see error: aborting due to 1 previous error diff --git a/tests/ui/parser/suggest-const-for-global-var.stderr b/tests/ui/parser/suggest-const-for-global-var.stderr index 235e621d8827b..265087331e39a 100644 --- a/tests/ui/parser/suggest-const-for-global-var.stderr +++ b/tests/ui/parser/suggest-const-for-global-var.stderr @@ -2,7 +2,12 @@ error: expected item, found keyword `let` --> $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 + | ^^^------------- + | | + | unexpected `let` binding outside of a function + | + = help: consider using `const` or `static` instead of `let` for global variables, or put it inside of a function: fn foo() { let X: i32 = 12; } + = note: for a full list of items that can appear in modules, see error: aborting due to 1 previous error