@@ -4,12 +4,15 @@ use super::Parser;
44
55use 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 } ;
910use rustc_span:: symbol:: sym;
1011use syntax:: ast:: { self , Attribute , Crate , Ident , ItemKind , Mod } ;
1112use syntax:: attr;
13+ use syntax:: ptr:: P ;
1214use syntax:: token:: { self , TokenKind } ;
15+ use syntax:: visit:: Visitor ;
1316
1417use 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