Skip to content

Commit

Permalink
improve parse item fallback
Browse files Browse the repository at this point in the history
  • Loading branch information
Orion Gonzalez committed Oct 2, 2024
1 parent 44722bd commit faf1c5f
Show file tree
Hide file tree
Showing 8 changed files with 107 additions and 18 deletions.
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
77 changes: 63 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,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 <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

0 comments on commit faf1c5f

Please sign in to comment.