99//! pretty-printer output, since it likely has nicer formatting, comments, etc. So there is some
1010//! logic in this module for "recovering" from needing to use this strategy by splicing old AST
1111//! text back into the new AST's pretty printer output.
12- use log:: { info, warn} ;
12+ use log:: { debug , info, warn} ;
1313use rustc_ast:: attr;
1414use rustc_ast:: ptr:: P ;
1515use rustc_ast:: token:: { BinOpToken , CommentKind , Delimiter , Nonterminal , Token , TokenKind } ;
@@ -95,6 +95,45 @@ impl PrintParse for Ty {
9595 }
9696}
9797
98+ /// Parse a snippet that contains only outer attributes (e.g. a relocated `#[derive]` line).
99+ ///
100+ /// Transforms such as `fold_let_assign` or `remove_redundant_let_types` may move attributes around
101+ /// on their own, which means `driver::parse_stmts` sees no statement tokens at all and would panic.
102+ /// By parsing the snippet as `attrs + dummy item` we can synthesize an empty statement that still
103+ /// carries the attribute span for recovery.
104+ fn parse_attr_only_stmt ( sess : & Session , src : & str ) -> Option < Stmt > {
105+ let trimmed = src. trim_start ( ) ;
106+ if !( trimmed. starts_with ( "#[" ) || trimmed. starts_with ( "#!" ) ) {
107+ return None ;
108+ }
109+
110+ // Provide a dummy item so Rust's parser accepts the attributes. We only use the attribute spans,
111+ // so the fabricated item never shows up in the recorded rewrite.
112+ let wrapped_src = format ! (
113+ "{src}\n struct __c2rust_dummy_for_attr_only_reparse;" ,
114+ src = src
115+ ) ;
116+ let mut items = driver:: parse_items ( sess, & wrapped_src) ;
117+ if items. len ( ) != 1 {
118+ return None ;
119+ }
120+ let item = items. pop ( ) . unwrap ( ) ;
121+ if item. attrs . is_empty ( ) {
122+ return None ;
123+ }
124+ let span = item
125+ . attrs
126+ . iter ( )
127+ . map ( |attr| attr. span )
128+ . reduce ( |acc, s| acc. to ( s) )
129+ . unwrap_or ( item. span ) ;
130+ Some ( Stmt {
131+ id : DUMMY_NODE_ID ,
132+ span,
133+ kind : StmtKind :: Empty ,
134+ } )
135+ }
136+
98137impl PrintParse for Stmt {
99138 fn to_string ( & self ) -> String {
100139 // pprust::stmt_to_string appends a semicolon to Expr kind statements,
@@ -110,7 +149,51 @@ impl PrintParse for Stmt {
110149
111150 type Parsed = Stmt ;
112151 fn parse ( sess : & Session , src : & str ) -> Self :: Parsed {
113- driver:: parse_stmts ( sess, src) . lone ( )
152+ let mut stmts = driver:: parse_stmts ( sess, src) ;
153+ match stmts. len ( ) {
154+ 1 => stmts. pop ( ) . unwrap ( ) ,
155+ 0 => {
156+ if src. trim ( ) . is_empty ( ) {
157+ // ASTBuilder assigns DUMMY_SP to freshly synthesized statements (e.g. from
158+ // `mk().local_stmt(...)`), so pretty-printing them can legitimately produce an
159+ // empty buffer even though the inner `Local`/`Expr` still carries the original
160+ // span. Preserve that slot explicitly so Recover still sees the expected single
161+ // statement node and doesn’t panic on len()==0.
162+ return Stmt {
163+ id : DUMMY_NODE_ID ,
164+ span : DUMMY_SP ,
165+ kind : StmtKind :: Empty ,
166+ } ;
167+ }
168+ if let Some ( stmt) = parse_attr_only_stmt ( sess, src) {
169+ return stmt;
170+ }
171+ let mut items = driver:: parse_items ( sess, src) ;
172+ if items. len ( ) != 1 {
173+ panic ! (
174+ "PrintParse<Stmt> expected 1 statement or item but parsed {} items from:\n {}" ,
175+ items. len( ) ,
176+ src
177+ ) ;
178+ }
179+ let item = items. pop ( ) . unwrap ( ) ;
180+ // Rust’s parser treats outer attributes as part of the following item. If recover
181+ // reused the old struct body text but the attrs need reprinting, the snippet that
182+ // reaches us can literally be just `#[derive(...)]`. Wrap the parsed item so the
183+ // enclosing `Block` continues to hold a `StmtKind::Item`, matching rustc’s AST.
184+ Stmt {
185+ id : DUMMY_NODE_ID ,
186+ span : item. span ,
187+ kind : StmtKind :: Item ( item) ,
188+ }
189+ }
190+ n => {
191+ panic ! (
192+ "PrintParse<Stmt> expected 1 statement but parsed {} from:\n {}" ,
193+ n, src
194+ ) ;
195+ }
196+ }
114197 }
115198}
116199
@@ -252,7 +335,40 @@ impl Splice for Ty {
252335
253336impl Splice for Stmt {
254337 fn splice_span ( & self ) -> Span {
255- self . span
338+ match & self . kind {
339+ StmtKind :: Local ( local) => {
340+ // Newly inserted locals often have DUMMY_SP; fall back to the local span so we can
341+ // recover text/attrs from the original source.
342+ let base = if self . span == DUMMY_SP {
343+ local. span
344+ } else {
345+ self . span
346+ } ;
347+ extend_span_attrs ( base, & local. attrs )
348+ }
349+ StmtKind :: Item ( item) => {
350+ // Same fallback for items whose wrapper stmt lost its span.
351+ let base = if self . span == DUMMY_SP {
352+ item. span
353+ } else {
354+ self . span
355+ } ;
356+ extend_span_attrs ( base, & item. attrs )
357+ }
358+ StmtKind :: Expr ( expr) | StmtKind :: Semi ( expr) => {
359+ // Expression statements carry attrs/comments on the child `Expr`. Use that span when
360+ // the wrapper is dummy so `extend_span_attrs` still captures outer attributes and any
361+ // trailing comments during rewrites.
362+ let base = if self . span == DUMMY_SP {
363+ expr. span
364+ } else {
365+ self . span
366+ } ;
367+ extend_span_attrs ( base, & expr. attrs )
368+ }
369+ StmtKind :: MacCall ( mac) => extend_span_attrs ( self . span , & mac. attrs ) ,
370+ StmtKind :: Empty => self . span ,
371+ }
256372 }
257373}
258374
@@ -675,6 +791,27 @@ where
675791 T : PrintParse + RecoverChildren + Splice + MaybeGetNodeId ,
676792{
677793 let printed = add_comments ( new. to_string ( ) , new, & rcx) ;
794+ let mut printed = printed;
795+ if printed. trim ( ) . is_empty ( ) {
796+ // When the statement wrapper has DUMMY_SP the pretty printer outputs nothing even though the
797+ // original source had a full `let`. Pull the old snippet (which still contains the attrs/body)
798+ // so `parse()` sees valid syntax and the reparsed AST matches what we’re trying to insert.
799+ if let Ok ( snippet) = rcx
800+ . session ( )
801+ . source_map ( )
802+ . span_to_snippet ( new. splice_span ( ) )
803+ {
804+ if !snippet. trim ( ) . is_empty ( ) {
805+ printed = snippet;
806+ }
807+ }
808+ if printed. trim ( ) . is_empty ( ) {
809+ debug ! (
810+ "rewrite_at_impl: empty printed text {:?} for {:?}" ,
811+ printed, new
812+ ) ;
813+ }
814+ }
678815 let reparsed = T :: parse ( rcx. session ( ) , & printed) ;
679816 let reparsed = reparsed. ast_deref ( ) ;
680817
0 commit comments