Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve parse item fallback #125388

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions compiler/rustc_ast_pretty/src/pprust/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
78 changes: 64 additions & 14 deletions compiler/rustc_parse/src/parser/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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 <https://doc.rust-lang.org/reference/items.html>");
};
let err = self.fallback_incorrect_item();
return Err(err);
}
}
Expand All @@ -97,6 +88,65 @@ 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(_) => {
// These typically are valid items after an error, use the default message.
do_default_diag = true;
}
};
}
// 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 <https://doc.rust-lang.org/reference/items.html>");

err
}
}

pub(super) type ItemInfo = (Ident, ItemKind);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 <https://doc.rust-lang.org/reference/items.html>

error: aborting due to 1 previous error
Expand Down
3 changes: 2 additions & 1 deletion tests/ui/parser/issues/issue-62913.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -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 <https://doc.rust-lang.org/reference/items.html>

error: aborting due to 3 previous errors
Expand Down
5 changes: 5 additions & 0 deletions tests/ui/parser/recover/pub-let-outside-fn.rs
Original file line number Diff line number Diff line change
@@ -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`
}
21 changes: 21 additions & 0 deletions tests/ui/parser/recover/pub-let-outside-fn.stderr
Original file line number Diff line number Diff line change
@@ -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 <https://doc.rust-lang.org/reference/items.html>

error: aborting due to 2 previous errors

5 changes: 4 additions & 1 deletion tests/ui/parser/shebang/shebang-doc-comment.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -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 <https://doc.rust-lang.org/reference/items.html>

error: aborting due to 1 previous error
Expand Down
7 changes: 6 additions & 1 deletion tests/ui/parser/suggest-const-for-global-var.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -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 <https://doc.rust-lang.org/reference/items.html>

error: aborting due to 1 previous error

Loading