Skip to content

Commit b40be9d

Browse files
author
Orion Gonzalez
committed
improve parse item fallback
1 parent 44722bd commit b40be9d

File tree

8 files changed

+108
-18
lines changed

8 files changed

+108
-18
lines changed

compiler/rustc_ast_pretty/src/pprust/mod.rs

+4
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ pub fn path_segment_to_string(p: &ast::PathSegment) -> String {
6363
State::new().path_segment_to_string(p)
6464
}
6565

66+
pub fn stmt_to_string(p: &ast::Stmt) -> String {
67+
State::new().stmt_to_string(p)
68+
}
69+
6670
pub fn vis_to_string(v: &ast::Visibility) -> String {
6771
State::new().vis_to_string(v)
6872
}

compiler/rustc_parse/src/parser/item.rs

+64-14
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ use tracing::debug;
2121
use super::diagnostics::{ConsumeClosingDelim, dummy_arg};
2222
use super::ty::{AllowPlus, RecoverQPath, RecoverReturnSign};
2323
use super::{
24-
AttrWrapper, FollowedByType, ForceCollect, Parser, PathStyle, Trailing, UsePreAttrPos,
24+
AttemptLocalParseRecovery, AttrWrapper, FollowedByType, ForceCollect, Parser, PathStyle,
25+
Trailing, UsePreAttrPos,
2526
};
2627
use crate::errors::{self, MacroExpandsToAdtField};
2728
use crate::{fluent_generated as fluent, maybe_whole};
@@ -74,21 +75,11 @@ impl<'a> Parser<'a> {
7475
items.push(item);
7576
}
7677

78+
// The last token should be `term`: either EOF or `}`. If it's not that means that we've had an error
79+
// parsing an item
7780
if !self.eat(term) {
78-
let token_str = super::token_descr(&self.token);
7981
if !self.maybe_consume_incorrect_semicolon(items.last().map(|x| &**x)) {
80-
let msg = format!("expected item, found {token_str}");
81-
let mut err = self.dcx().struct_span_err(self.token.span, msg);
82-
let span = self.token.span;
83-
if self.is_kw_followed_by_ident(kw::Let) {
84-
err.span_label(
85-
span,
86-
"consider using `const` or `static` instead of `let` for global variables",
87-
);
88-
} else {
89-
err.span_label(span, "expected item")
90-
.note("for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html>");
91-
};
82+
let err = self.fallback_incorrect_item();
9283
return Err(err);
9384
}
9485
}
@@ -97,6 +88,65 @@ impl<'a> Parser<'a> {
9788
let mod_spans = ModSpans { inner_span: lo.to(self.prev_token.span), inject_use_span };
9889
Ok((attrs, items, mod_spans))
9990
}
91+
92+
/// Tries to parse the item as a statement to provide further diagnostics.
93+
fn fallback_incorrect_item(&mut self) -> rustc_errors::Diag<'a> {
94+
let token_str = super::token_descr(&self.token);
95+
let token_span = self.token.span;
96+
let mut err =
97+
self.dcx().struct_span_err(token_span, format!("expected item, found {token_str}"));
98+
99+
let mut do_default_diag = true;
100+
101+
match self.parse_full_stmt(AttemptLocalParseRecovery::No) {
102+
Ok(Some(stmt)) => {
103+
do_default_diag = false;
104+
let span = stmt.span;
105+
match &stmt.kind {
106+
StmtKind::Let(_) => {
107+
err.span_label(span, "unexpected `let` binding outside of a function")
108+
.help(format!("consider using `const` or `static` instead of `let` for global variables, or put it inside of a function: fn foo() {{ {} }}",
109+
pprust::stmt_to_string(&stmt)));
110+
}
111+
StmtKind::Semi(expr) => {
112+
err.span_label(span, "unexpected expression").help(format!(
113+
"consider putting it inside a function: fn foo() {{ {}; }}",
114+
pprust::expr_to_string(expr)
115+
));
116+
}
117+
StmtKind::Expr(expr) => {
118+
err.span_label(span, "unexpected expression").help(format!(
119+
"consider putting it inside a function: fn foo() {{ {} }}",
120+
pprust::expr_to_string(expr)
121+
));
122+
}
123+
StmtKind::Empty => {
124+
unreachable!(
125+
"Should have been handled by maybe_consume_incorrect_semicolon"
126+
);
127+
}
128+
StmtKind::Item(_) | StmtKind::MacCall(_) => {
129+
// These typically are valid items after an error, use the default message.
130+
do_default_diag = true;
131+
}
132+
};
133+
}
134+
// It's not a statement, we can't do much recovery.
135+
Ok(None) => {}
136+
Err(e) => {
137+
// We don't really care about an error parsing this statement.
138+
e.cancel();
139+
}
140+
}
141+
142+
if do_default_diag {
143+
err.span_label(token_span, "expected item");
144+
}
145+
146+
err.note("for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html>");
147+
148+
err
149+
}
100150
}
101151

102152
pub(super) type ItemInfo = (Ident, ItemKind);

tests/ui/parser/issues/issue-113110-non-item-at-module-root.stderr

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ error: expected item, found `5`
22
--> $DIR/issue-113110-non-item-at-module-root.rs:1:2
33
|
44
LL | 5
5-
| ^ expected item
5+
| ^ unexpected expression
66
|
7+
= help: consider putting it inside a function: fn foo() { 5 }
78
= note: for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html>
89

910
error: aborting due to 1 previous error

tests/ui/parser/issues/issue-62913.stderr

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@ error: expected item, found `"\u\"`
1616
--> $DIR/issue-62913.rs:1:1
1717
|
1818
LL | "\u\"
19-
| ^^^^^^ expected item
19+
| ^^^^^^ unexpected expression
2020
|
21+
= help: consider putting it inside a function: fn foo() { "\u\" }
2122
= note: for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html>
2223

2324
error: aborting due to 3 previous errors
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
mod m {
2+
pub let answer = 42;
3+
//~^ ERROR visibility `pub` is not followed by an item
4+
//~| ERROR expected item, found keyword `let`
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
error: visibility `pub` is not followed by an item
2+
--> $DIR/pub-let-outside-fn.rs:2:5
3+
|
4+
LL | pub let answer = 42;
5+
| ^^^ the visibility
6+
|
7+
= help: you likely meant to define an item, e.g., `pub fn foo() {}`
8+
9+
error: expected item, found keyword `let`
10+
--> $DIR/pub-let-outside-fn.rs:2:9
11+
|
12+
LL | pub let answer = 42;
13+
| ^^^-------------
14+
| |
15+
| unexpected `let` binding outside of a function
16+
|
17+
= help: consider using `const` or `static` instead of `let` for global variables, or put it inside of a function: fn foo() { let answer = 42; }
18+
= note: for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html>
19+
20+
error: aborting due to 2 previous errors
21+

tests/ui/parser/shebang/shebang-doc-comment.stderr

+4-1
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@ error: expected item, found `[`
22
--> $DIR/shebang-doc-comment.rs:2:1
33
|
44
LL | [allow(unused_variables)]
5-
| ^ expected item
5+
| ^------------------------
6+
| |
7+
| unexpected expression
68
|
9+
= help: consider putting it inside a function: fn foo() { [allow(unused_variables)] }
710
= note: for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html>
811

912
error: aborting due to 1 previous error

tests/ui/parser/suggest-const-for-global-var.stderr

+6-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@ error: expected item, found keyword `let`
22
--> $DIR/suggest-const-for-global-var.rs:1:1
33
|
44
LL | let X: i32 = 12;
5-
| ^^^ consider using `const` or `static` instead of `let` for global variables
5+
| ^^^-------------
6+
| |
7+
| unexpected `let` binding outside of a function
8+
|
9+
= 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; }
10+
= note: for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html>
611

712
error: aborting due to 1 previous error
813

0 commit comments

Comments
 (0)