Skip to content

Commit 3d8877b

Browse files
committed
Auto merge of #69445 - Centril:soyuz, r=<try>
EXPERIMENT: Recover on stmts/exprs at module level, suggesting to wrap in function TODO: write description r? @petrochenkov @estebank
2 parents 834bc56 + 0748f60 commit 3d8877b

28 files changed

+596
-203
lines changed

src/librustc_expand/expand.rs

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

src/librustc_parse/parser/item.rs

+45-12
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ impl<'a> Parser<'a> {
3030

3131
fn parse_item_(&mut self, req_name: ReqName) -> PResult<'a, Option<Item>> {
3232
let attrs = self.parse_outer_attributes()?;
33-
self.parse_item_common(attrs, true, false, req_name)
33+
self.parse_item_common(attrs, true, false, req_name, true)
3434
}
3535

3636
pub(super) fn parse_item_common(
@@ -39,6 +39,7 @@ impl<'a> Parser<'a> {
3939
mac_allowed: bool,
4040
attrs_allowed: bool,
4141
req_name: ReqName,
42+
mod_stmt: bool,
4243
) -> PResult<'a, Option<Item>> {
4344
maybe_whole!(self, NtItem, |item| {
4445
let mut item = item;
@@ -49,9 +50,9 @@ impl<'a> Parser<'a> {
4950

5051
let mut unclosed_delims = vec![];
5152
let (mut item, tokens) = self.collect_tokens(|this| {
52-
let item = this.parse_item_common_(attrs, mac_allowed, attrs_allowed, req_name);
53+
let i = this.parse_item_common_(attrs, mac_allowed, attrs_allowed, req_name, mod_stmt);
5354
unclosed_delims.append(&mut this.unclosed_delims);
54-
item
55+
i
5556
})?;
5657
self.unclosed_delims.append(&mut unclosed_delims);
5758

@@ -83,11 +84,13 @@ impl<'a> Parser<'a> {
8384
mac_allowed: bool,
8485
attrs_allowed: bool,
8586
req_name: ReqName,
87+
mod_stmt: bool,
8688
) -> PResult<'a, Option<Item>> {
8789
let lo = self.token.span;
8890
let vis = self.parse_visibility(FollowedByType::No)?;
8991
let mut def = self.parse_defaultness();
90-
let kind = self.parse_item_kind(&mut attrs, mac_allowed, lo, &vis, &mut def, req_name)?;
92+
let kind =
93+
self.parse_item_kind(&mut attrs, mac_allowed, lo, &vis, &mut def, req_name, mod_stmt)?;
9194
if let Some((ident, kind)) = kind {
9295
self.error_on_unconsumed_default(def, &kind);
9396
let span = lo.to(self.prev_span);
@@ -148,6 +151,7 @@ impl<'a> Parser<'a> {
148151
vis: &Visibility,
149152
def: &mut Defaultness,
150153
req_name: ReqName,
154+
mod_stmt: bool,
151155
) -> PResult<'a, Option<ItemInfo>> {
152156
let mut def = || mem::replace(def, Defaultness::Final);
153157

@@ -212,9 +216,13 @@ impl<'a> Parser<'a> {
212216
} else if vis.node.is_pub() && self.isnt_macro_invocation() {
213217
self.recover_missing_kw_before_item()?;
214218
return Ok(None);
215-
} else if macros_allowed && self.token.is_path_start() {
219+
} else if let Some(kind) = if macros_allowed && self.token.is_path_start() {
220+
self.parse_item_macro(vis, mod_stmt)?
221+
} else {
222+
None
223+
} {
216224
// MACRO INVOCATION ITEM
217-
(Ident::invalid(), ItemKind::Mac(self.parse_item_macro(vis)?))
225+
(Ident::invalid(), ItemKind::Mac(kind))
218226
} else {
219227
return Ok(None);
220228
};
@@ -333,13 +341,36 @@ impl<'a> Parser<'a> {
333341
}
334342

335343
/// Parses an item macro, e.g., `item!();`.
336-
fn parse_item_macro(&mut self, vis: &Visibility) -> PResult<'a, Mac> {
337-
let path = self.parse_path(PathStyle::Mod)?; // `foo::bar`
338-
self.expect(&token::Not)?; // `!`
344+
fn parse_item_macro(&mut self, vis: &Visibility, mod_stmt: bool) -> PResult<'a, Option<Mac>> {
345+
let parse_prefix = |p: &mut Self| -> PResult<'a, ast::Path> {
346+
let path = p.parse_path(PathStyle::Mod)?; // `foo::bar`
347+
p.expect(&token::Not)?; // `!`
348+
Ok(path)
349+
};
350+
let path = if mod_stmt {
351+
// We're in statement-as-module-item recovery mode.
352+
// To avoid "stealing" syntax from e.g. `x.f()` as a module-level statement,
353+
// we backtrack if we failed to parse `$path!`; after we have, we commit firmly.
354+
// This is only done when `mod_stmt` holds to avoid backtracking inside functions.
355+
let snapshot = self.clone();
356+
match parse_prefix(self) {
357+
Ok(path) => path,
358+
Err(mut err) => {
359+
// Assert that this is only for diagnostics!
360+
// This is a safeguard against breaking LL(k) accidentally in the spec,
361+
// assuming no one has gated the syntax with something like `#[cfg(FALSE)]`.
362+
err.delay_as_bug();
363+
*self = snapshot;
364+
return Ok(None);
365+
}
366+
}
367+
} else {
368+
parse_prefix(self)?
369+
};
339370
let args = self.parse_mac_args()?; // `( .. )` or `[ .. ]` (followed by `;`), or `{ .. }`.
340371
self.eat_semi_for_macro_if_needed(&args);
341372
self.complain_if_pub_macro(vis, false);
342-
Ok(Mac { path, args, prior_type_ascription: self.last_type_ascription })
373+
Ok(Some(Mac { path, args, prior_type_ascription: self.last_type_ascription }))
343374
}
344375

345376
/// Recover if we parsed attributes and expected an item but there was none.
@@ -647,7 +678,8 @@ impl<'a> Parser<'a> {
647678

648679
/// Parses associated items.
649680
fn parse_assoc_item(&mut self, req_name: ReqName) -> PResult<'a, Option<Option<P<AssocItem>>>> {
650-
Ok(self.parse_item_(req_name)?.map(|Item { attrs, id, span, vis, ident, kind, tokens }| {
681+
let item = self.parse_item_(req_name)?;
682+
Ok(item.map(|Item { attrs, id, span, vis, ident, kind, tokens }| {
651683
let kind = match kind {
652684
ItemKind::Mac(a) => AssocItemKind::Macro(a),
653685
ItemKind::Fn(a, b, c, d) => AssocItemKind::Fn(a, b, c, d),
@@ -836,7 +868,8 @@ impl<'a> Parser<'a> {
836868
pub fn parse_foreign_item(&mut self) -> PResult<'a, Option<Option<P<ForeignItem>>>> {
837869
maybe_whole!(self, NtForeignItem, |item| Some(Some(item)));
838870

839-
Ok(self.parse_item_(|_| true)?.map(|Item { attrs, id, span, vis, ident, kind, tokens }| {
871+
let item = self.parse_item_(|_| true)?;
872+
Ok(item.map(|Item { attrs, id, span, vis, ident, kind, tokens }| {
840873
let kind = match kind {
841874
ItemKind::Mac(a) => ForeignItemKind::Macro(a),
842875
ItemKind::Fn(a, b, c, d) => ForeignItemKind::Fn(a, b, c, d),

src/librustc_parse/parser/module.rs

+143-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,138 @@ 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+
// `-> ()`; We either had `return;`, so return type is unit, or nothing was returned.
199+
(true, _, _) | (false, false, false) => FnRetTy::Default(span),
200+
// `-> Result<_, _>`; We had `?` somewhere so `-> Result<_, _>` is a good bet.
201+
(_, _, true) => {
202+
let arg = GenericArg::Type(self.mk_ty(span, TyKind::Infer));
203+
let args = [arg.clone(), arg].to_vec();
204+
let args = AngleBracketedArgs { span, constraints: vec![], args };
205+
let mut path = Path::from_ident(Ident::from_str_and_span("Result", span));
206+
path.segments[0].args = Some(P(GenericArgs::AngleBracketed(args)));
207+
FnRetTy::Ty(self.mk_ty(span, TyKind::Path(None, path)))
208+
}
209+
// `-> _`; We had `return $expr;` so it's probably not `()` as return type.
210+
(_, true, _) => FnRetTy::Ty(self.mk_ty(span, TyKind::Infer)),
211+
};
212+
213+
// Finalize the AST for the function item: `fn $ident() $output { $stmts }`.
214+
let sig = FnSig { header: FnHeader::default(), decl: P(FnDecl { inputs: vec![], output }) };
215+
let body = self.mk_block(stmts, BlockCheckMode::Default, span);
216+
let kind = ItemKind::Fn(Defaultness::Final, sig, Generics::default(), Some(body));
217+
let vis = respan(span, VisibilityKind::Inherited);
218+
let item = Item { span, ident, vis, kind, attrs: vec![], id: DUMMY_NODE_ID, tokens: None };
219+
220+
// Emit the error with a suggestion to wrap the statements in the function.
221+
let mut err = self.struct_span_err(spans, "statements cannot reside in modules");
222+
err.span_suggestion_verbose(
223+
span,
224+
"consider moving the statements into a function",
225+
pprust::item_to_string(&item),
226+
Applicability::HasPlaceholders,
227+
);
228+
err.note("the program entry point starts in `fn main() { ... }`, defined in `main.rs`");
229+
err.note(
230+
"for more on functions and how to structure your program, \
231+
see https://doc.rust-lang.org/book/ch03-03-how-functions-work.html",
232+
);
233+
err.emit();
234+
}
235+
98236
fn submod_path(
99237
&mut self,
100238
id: ast::Ident,

0 commit comments

Comments
 (0)