Skip to content

Commit ccc47fd

Browse files
committed
wip
1 parent abc3073 commit ccc47fd

14 files changed

+325
-34
lines changed

src/librustc_expand/expand.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -892,7 +892,7 @@ pub fn parse_ast_fragment<'a>(
892892
let mut stmts = SmallVec::new();
893893
// Won't make progress on a `}`.
894894
while this.token != token::Eof && this.token != token::CloseDelim(token::Brace) {
895-
if let Some(stmt) = this.parse_full_stmt()? {
895+
if let Some(stmt) = this.parse_full_stmt(false)? {
896896
stmts.push(stmt);
897897
}
898898
}

src/librustc_parse/parser/module.rs

+140-5
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@ use super::Parser;
44

55
use crate::{new_sub_parser_from_file, DirectoryOwnership};
66

7-
use rustc_errors::PResult;
8-
use rustc_span::source_map::{FileName, SourceMap, Span, DUMMY_SP};
7+
use rustc_ast_pretty::pprust;
8+
use rustc_errors::{Applicability, PResult};
9+
use rustc_span::source_map::{respan, FileName, MultiSpan, SourceMap, Span, DUMMY_SP};
910
use rustc_span::symbol::sym;
1011
use syntax::ast::{self, Attribute, Crate, Ident, ItemKind, Mod};
1112
use syntax::attr;
13+
use syntax::ptr::P;
1214
use syntax::token::{self, TokenKind};
15+
use syntax::visit::Visitor;
1316

1417
use std::path::{self, Path, PathBuf};
1518

@@ -75,9 +78,12 @@ impl<'a> Parser<'a> {
7578
/// Given a termination token, parses all of the items in a module.
7679
fn parse_mod_items(&mut self, term: &TokenKind, inner_lo: Span) -> PResult<'a, Mod> {
7780
let mut items = vec![];
78-
while let Some(item) = self.parse_item()? {
79-
items.push(item);
80-
self.maybe_consume_incorrect_semicolon(&items);
81+
let mut stuck = false;
82+
while let Some(res) = self.parse_item_in_mod(term, &mut stuck)? {
83+
if let Some(item) = res {
84+
items.push(item);
85+
self.maybe_consume_incorrect_semicolon(&items);
86+
}
8187
}
8288

8389
if !self.eat(term) {
@@ -95,6 +101,135 @@ impl<'a> Parser<'a> {
95101
Ok(Mod { inner: inner_lo.to(hi), items, inline: true })
96102
}
97103

104+
fn parse_item_in_mod(
105+
&mut self,
106+
term: &TokenKind,
107+
stuck: &mut bool,
108+
) -> PResult<'a, Option<Option<P<ast::Item>>>> {
109+
match self.parse_item()? {
110+
// We just made progress and we might have statements following this item.
111+
i @ Some(_) => {
112+
*stuck = false;
113+
Ok(Some(i))
114+
}
115+
// No progress and the previous attempt at statements failed, so terminate the loop.
116+
None if *stuck => Ok(None),
117+
None => Ok(self.recover_stmts_as_item(term, stuck)?.then_some(None)),
118+
}
119+
}
120+
121+
/// Parse a contiguous list of statements until we reach the terminating token or EOF.
122+
/// When any statements were parsed, perform recovery and suggest wrapping the statements
123+
/// inside a function. If `stuck` becomes `true`, then this method should not be called
124+
/// unless we have advanced the cursor.
125+
fn recover_stmts_as_item(&mut self, term: &TokenKind, stuck: &mut bool) -> PResult<'a, bool> {
126+
let lo = self.token.span;
127+
let mut stmts = vec![];
128+
while ![term, &token::Eof].contains(&&self.token.kind) {
129+
let old_expected = std::mem::take(&mut self.expected_tokens);
130+
let snapshot = self.clone();
131+
let stmt = self.parse_full_stmt(true);
132+
self.expected_tokens = old_expected; // Restore expected tokens to before recovery.
133+
match stmt {
134+
Ok(None) => break,
135+
Ok(Some(stmt)) => stmts.push(stmt),
136+
Err(mut err) => {
137+
// We couldn't parse as a statement. Rewind to the last one we could for.
138+
// Also notify the caller that we made no progress, meaning that the method
139+
// should not be called again to avoid non-termination.
140+
err.cancel();
141+
*self = snapshot;
142+
*stuck = true;
143+
break;
144+
}
145+
}
146+
}
147+
148+
let recovered = !stmts.is_empty();
149+
if recovered {
150+
// We parsed some statements and have recovered, so let's emit an error.
151+
self.error_stmts_as_item_suggest_fn(lo, stmts);
152+
}
153+
Ok(recovered)
154+
}
155+
156+
fn error_stmts_as_item_suggest_fn(&self, lo: Span, stmts: Vec<ast::Stmt>) {
157+
use syntax::ast::*;
158+
159+
let span = lo.to(self.prev_span);
160+
let spans: MultiSpan = match &*stmts {
161+
[] | [_] => span.into(),
162+
[x, .., y] => vec![x.span, y.span].into(),
163+
};
164+
165+
// Perform coarse grained inference about returns.
166+
// We use this to tell whether `main` is an acceptable name
167+
// and if `-> _` or `-> Result<_, _>` should be used instead of defaulting to unit.
168+
#[derive(Default)]
169+
struct RetInfer(bool, bool, bool);
170+
let RetInfer(has_ret_unit, has_ret_expr, has_try_expr) = {
171+
impl Visitor<'_> for RetInfer {
172+
fn visit_expr_post(&mut self, expr: &Expr) {
173+
match expr.kind {
174+
ExprKind::Ret(None) => self.0 = true, // `return`
175+
ExprKind::Ret(Some(_)) => self.1 = true, // `return $expr`
176+
ExprKind::Try(_) => self.2 = true, // `expr?`
177+
_ => {}
178+
}
179+
}
180+
}
181+
let mut visitor = RetInfer::default();
182+
for stmt in &stmts {
183+
visitor.visit_stmt(stmt);
184+
}
185+
if let StmtKind::Expr(_) = &stmts.last().unwrap().kind {
186+
visitor.1 = true; // The tail expression.
187+
}
188+
visitor
189+
};
190+
191+
// For the function name, use `main` if we are in `main.rs`, and `my_function` otherwise.
192+
let use_main = (has_ret_unit || has_try_expr)
193+
&& self.directory.path.file_stem() == Some(std::ffi::OsStr::new("main"));
194+
let ident = Ident::from_str_and_span(if use_main { "main" } else { "my_function" }, span);
195+
196+
// Construct the return type; either default, `-> _`, or `-> Result<_, _>`.
197+
let output = match (has_ret_unit, has_ret_expr, has_try_expr) {
198+
(true, _, _) | (false, false, false) => FnRetTy::Default(span),
199+
(_, _, true) => {
200+
let arg = GenericArg::Type(self.mk_ty(span, TyKind::Infer));
201+
let args = [arg.clone(), arg].to_vec();
202+
let args = AngleBracketedArgs { span, constraints: vec![], args };
203+
let mut path = Path::from_ident(Ident::from_str_and_span("Result", span));
204+
path.segments[0].args = Some(P(GenericArgs::AngleBracketed(args)));
205+
FnRetTy::Ty(self.mk_ty(span, TyKind::Path(None, path)))
206+
}
207+
(_, true, _) => FnRetTy::Ty(self.mk_ty(span, TyKind::Infer)),
208+
};
209+
210+
// Finalize the AST for the function item.
211+
let sig = FnSig { header: FnHeader::default(), decl: P(FnDecl { inputs: vec![], output }) };
212+
let body = self.mk_block(stmts, BlockCheckMode::Default, span);
213+
let kind = ItemKind::Fn(Defaultness::Final, sig, Generics::default(), Some(body));
214+
let vis = respan(span, VisibilityKind::Inherited);
215+
let item = Item { span, ident, vis, kind, attrs: vec![], id: DUMMY_NODE_ID, tokens: None };
216+
217+
// Emit the error with a suggestion to wrap the statements in the function.
218+
let mut err = self.struct_span_err(spans, "statements cannot reside in modules");
219+
err.span_suggestion_verbose(
220+
span,
221+
"consider moving the statements into a function",
222+
pprust::item_to_string(&item),
223+
Applicability::HasPlaceholders,
224+
);
225+
err.note("the program entry point starts in `fn main() { ... }`, defined in `main.rs`");
226+
err.note(
227+
"for more on functions and how to structure your program, \
228+
see https://doc.rust-lang.org/book/ch03-03-how-functions-work.html",
229+
);
230+
err.emit();
231+
}
232+
98233
fn submod_path(
99234
&mut self,
100235
id: ast::Ident,

src/librustc_parse/parser/stmt.rs

+15-9
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,14 @@ impl<'a> Parser<'a> {
2222
/// Parses a statement. This stops just before trailing semicolons on everything but items.
2323
/// e.g., a `StmtKind::Semi` parses to a `StmtKind::Expr`, leaving the trailing `;` unconsumed.
2424
pub fn parse_stmt(&mut self) -> PResult<'a, Option<Stmt>> {
25-
Ok(self.parse_stmt_without_recovery().unwrap_or_else(|mut e| {
25+
Ok(self.parse_stmt_without_recovery(false).unwrap_or_else(|mut e| {
2626
e.emit();
2727
self.recover_stmt_(SemiColonMode::Break, BlockMode::Ignore);
2828
None
2929
}))
3030
}
3131

32-
fn parse_stmt_without_recovery(&mut self) -> PResult<'a, Option<Stmt>> {
32+
fn parse_stmt_without_recovery(&mut self, module_stmt: bool) -> PResult<'a, Option<Stmt>> {
3333
maybe_whole!(self, NtStmt, |x| Some(x));
3434

3535
let attrs = self.parse_outer_attributes()?;
@@ -56,7 +56,10 @@ impl<'a> Parser<'a> {
5656
// that starts like a path (1 token), but it fact not a path.
5757
// Also, we avoid stealing syntax from `parse_item_`.
5858
self.parse_stmt_path_start(lo, attrs)?
59-
} else if let Some(item) = self.parse_stmt_item(attrs.clone())? {
59+
} else if let Some(item) =
60+
// When parsing the statement as a module (recovery), avoid parsing items.
61+
if module_stmt { None } else { self.parse_stmt_item(attrs.clone())? }
62+
{
6063
// FIXME: Bad copy of attrs
6164
self.mk_stmt(lo.to(item.span), StmtKind::Item(P(item)))
6265
} else if self.token == token::Semi {
@@ -275,7 +278,7 @@ impl<'a> Parser<'a> {
275278
// bar;
276279
//
277280
// which is valid in other languages, but not Rust.
278-
match self.parse_stmt_without_recovery() {
281+
match self.parse_stmt_without_recovery(true) {
279282
Ok(Some(stmt)) => {
280283
if self.look_ahead(1, |t| t == &token::OpenDelim(token::Brace))
281284
|| do_not_suggest_help
@@ -334,7 +337,7 @@ impl<'a> Parser<'a> {
334337
if self.token == token::Eof {
335338
break;
336339
}
337-
let stmt = match self.parse_full_stmt() {
340+
let stmt = match self.parse_full_stmt(false) {
338341
Err(mut err) => {
339342
self.maybe_annotate_with_ascription(&mut err, false);
340343
err.emit();
@@ -354,20 +357,23 @@ impl<'a> Parser<'a> {
354357
}
355358

356359
/// Parses a statement, including the trailing semicolon.
357-
pub fn parse_full_stmt(&mut self) -> PResult<'a, Option<Stmt>> {
360+
pub fn parse_full_stmt(&mut self, module_stmt: bool) -> PResult<'a, Option<Stmt>> {
358361
// Skip looking for a trailing semicolon when we have an interpolated statement.
359362
maybe_whole!(self, NtStmt, |x| Some(x));
360363

361-
let mut stmt = match self.parse_stmt_without_recovery()? {
364+
let mut stmt = match self.parse_stmt_without_recovery(module_stmt)? {
362365
Some(stmt) => stmt,
363366
None => return Ok(None),
364367
};
365368

366369
let mut eat_semi = true;
367370
match stmt.kind {
368-
// Expression without semicolon.
369371
StmtKind::Expr(ref expr)
370-
if self.token != token::Eof && classify::expr_requires_semi_to_be_stmt(expr) =>
372+
// Expression without semicolon.
373+
if self.token != token::Eof
374+
&& classify::expr_requires_semi_to_be_stmt(expr)
375+
// Do not error here if in `module_stmt` recovery mode.
376+
&& !module_stmt =>
371377
{
372378
// Just check for errors and recover; do not eat semicolon yet.
373379
if let Err(mut e) =

src/test/ui/issues/issue-49040.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
#![allow(unused_variables)]; //~ ERROR expected item, found `;`
1+
#![allow(unused_variables)]; //~ ERROR statements cannot reside in modules
22
//~^ ERROR `main` function
33
fn foo() {}

src/test/ui/issues/issue-49040.stderr

+10-3
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
1-
error: expected item, found `;`
1+
error: statements cannot reside in modules
22
--> $DIR/issue-49040.rs:1:28
33
|
44
LL | #![allow(unused_variables)];
5-
| ^ help: remove this semicolon
5+
| ^
6+
|
7+
= note: the program entry point starts in `fn main() { ... }`, defined in `main.rs`
8+
= note: for more on functions and how to structure your program, see https://doc.rust-lang.org/book/ch03-03-how-functions-work.html
9+
help: consider moving the statements into a function
10+
|
11+
LL | #![allow(unused_variables)]fn my_function() { }
12+
| ^^^^^^^^^^^^^^^^^^^^
613

714
error[E0601]: `main` function not found in crate `issue_49040`
815
--> $DIR/issue-49040.rs:1:1
916
|
1017
LL | / #![allow(unused_variables)];
1118
LL | |
1219
LL | | fn foo() {}
13-
| |__^ consider adding a `main` function to `$DIR/issue-49040.rs`
20+
| |___________^ consider adding a `main` function to `$DIR/issue-49040.rs`
1421

1522
error: aborting due to 2 previous errors
1623

src/test/ui/issues/issue-62554.stderr

-6
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,6 @@ LL | fn foo(u: u8) { if u8 macro_rules! u8 { (u6) => { fn uuuuuuuuuuu() { use s
6060
| -- ^^^^^^^^^^^ expected `{`
6161
| |
6262
| this `if` expression has a condition, but no block
63-
|
64-
help: try placing this code inside a block
65-
|
66-
LL | fn foo(u: u8) { if u8 { macro_rules! u8 { (u6) => { fn uuuuuuuuuuu() { use s loo mod u8 {
67-
LL | }
68-
|
6963

7064
error: aborting due to 6 previous errors
7165

src/test/ui/parser/issue-62913.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"\u\\"
22
//~^ ERROR incorrect unicode escape sequence
33
//~| ERROR invalid trailing slash in literal
4-
//~| ERROR expected item, found `"\u\\"`
4+
//~| ERROR statements cannot reside in modules
5+
//~| ERROR `main` function not found

src/test/ui/parser/issue-62913.stderr

+17-3
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,25 @@ error: invalid trailing slash in literal
1212
LL | "\u\"
1313
| ^
1414

15-
error: expected item, found `"\u\"`
15+
error: statements cannot reside in modules
1616
--> $DIR/issue-62913.rs:1:1
1717
|
1818
LL | "\u\"
19-
| ^^^^^^ expected item
19+
| ^^^^^^
20+
|
21+
= note: the program entry point starts in `fn main() { ... }`, defined in `main.rs`
22+
= note: for more on functions and how to structure your program, see https://doc.rust-lang.org/book/ch03-03-how-functions-work.html
23+
help: consider moving the statements into a function
24+
|
25+
LL | fn my_function() -> _ { "\u\" }
26+
|
27+
28+
error[E0601]: `main` function not found in crate `issue_62913`
29+
--> $DIR/issue-62913.rs:1:1
30+
|
31+
LL | "\u\"
32+
| ^^^^^^ consider adding a `main` function to `$DIR/issue-62913.rs`
2033

21-
error: aborting due to 3 previous errors
34+
error: aborting due to 4 previous errors
2235

36+
For more information about this error, try `rustc --explain E0601`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
fn main() {}
2+
3+
fn foo() {}
4+
5+
let x = 0; //~ ERROR statements cannot reside in modules
6+
x;
7+
x;
8+
x;
9+
x;
10+
x;
11+
x;
12+
foo()?;
13+
Ok(42u16)
14+
15+
struct X;
16+
17+
if true {} //~ ERROR statements cannot reside in modules
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
error: statements cannot reside in modules
2+
--> $DIR/main.rs:5:1
3+
|
4+
LL | let x = 0;
5+
| ^^^^^^^^^^
6+
...
7+
LL | Ok(42u16)
8+
| ^^^^^^^^^
9+
|
10+
= note: the program entry point starts in `fn main() { ... }`, defined in `main.rs`
11+
= note: for more on functions and how to structure your program, see https://doc.rust-lang.org/book/ch03-03-how-functions-work.html
12+
help: consider moving the statements into a function
13+
|
14+
LL | fn my_function() -> Result<_, _> {
15+
LL | let x = 0;
16+
LL | x;
17+
LL | x;
18+
LL | x;
19+
LL | x;
20+
...
21+
22+
error: statements cannot reside in modules
23+
--> $DIR/main.rs:17:1
24+
|
25+
LL | if true {}
26+
| ^^^^^^^^^^
27+
|
28+
= note: the program entry point starts in `fn main() { ... }`, defined in `main.rs`
29+
= note: for more on functions and how to structure your program, see https://doc.rust-lang.org/book/ch03-03-how-functions-work.html
30+
help: consider moving the statements into a function
31+
|
32+
LL | fn my_function() -> _ { if true { } }
33+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
34+
35+
error: aborting due to 2 previous errors
36+

0 commit comments

Comments
 (0)