1
1
use hir:: Semantics ;
2
2
use ide_db:: {
3
- helpers :: pick_best_token , syntax_helpers :: insert_whitespace_into_node :: insert_ws_into ,
4
- RootDatabase ,
3
+ base_db :: FileId , helpers :: pick_best_token ,
4
+ syntax_helpers :: insert_whitespace_into_node :: insert_ws_into , RootDatabase ,
5
5
} ;
6
6
use syntax:: { ast, ted, AstNode , NodeOrToken , SyntaxKind , SyntaxNode , T } ;
7
7
@@ -58,10 +58,9 @@ pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<
58
58
. take_while ( |it| it != & token)
59
59
. filter ( |it| it. kind ( ) == T ! [ , ] )
60
60
. count ( ) ;
61
- Some ( ExpandedMacro {
62
- name,
63
- expansion : expansions. get ( idx) . cloned ( ) . map ( insert_ws_into) ?. to_string ( ) ,
64
- } )
61
+ let expansion =
62
+ format ( db, SyntaxKind :: MACRO_ITEMS , position. file_id , expansions. get ( idx) . cloned ( ) ?) ;
63
+ Some ( ExpandedMacro { name, expansion } )
65
64
} ) ;
66
65
67
66
if derive. is_some ( ) {
@@ -72,25 +71,29 @@ pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<
72
71
// currently we only recursively expand one of the two types
73
72
let mut expanded = None ;
74
73
let mut name = None ;
74
+ let mut kind = SyntaxKind :: ERROR ;
75
75
for node in tok. ancestors ( ) {
76
76
if let Some ( item) = ast:: Item :: cast ( node. clone ( ) ) {
77
77
if let Some ( def) = sema. resolve_attr_macro_call ( & item) {
78
78
name = Some ( def. name ( db) . to_string ( ) ) ;
79
79
expanded = expand_attr_macro_recur ( & sema, & item) ;
80
+ kind = SyntaxKind :: MACRO_ITEMS ;
80
81
break ;
81
82
}
82
83
}
83
84
if let Some ( mac) = ast:: MacroCall :: cast ( node) {
84
85
name = Some ( mac. path ( ) ?. segment ( ) ?. name_ref ( ) ?. to_string ( ) ) ;
85
86
expanded = expand_macro_recur ( & sema, & mac) ;
87
+ kind = mac. syntax ( ) . parent ( ) . map ( |it| it. kind ( ) ) . unwrap_or ( SyntaxKind :: MACRO_ITEMS ) ;
86
88
break ;
87
89
}
88
90
}
89
91
90
92
// FIXME:
91
93
// macro expansion may lose all white space information
92
94
// But we hope someday we can use ra_fmt for that
93
- let expansion = insert_ws_into ( expanded?) . to_string ( ) ;
95
+ let expansion = format ( db, kind, position. file_id , expanded?) ;
96
+
94
97
Some ( ExpandedMacro { name : name. unwrap_or_else ( || "???" . to_owned ( ) ) , expansion } )
95
98
}
96
99
@@ -130,6 +133,70 @@ fn expand<T: AstNode>(
130
133
Some ( expanded)
131
134
}
132
135
136
+ fn format ( db : & RootDatabase , kind : SyntaxKind , file_id : FileId , expanded : SyntaxNode ) -> String {
137
+ let expansion = insert_ws_into ( expanded) . to_string ( ) ;
138
+
139
+ _format ( db, kind, file_id, & expansion) . unwrap_or ( expansion)
140
+ }
141
+
142
+ #[ cfg( test) ]
143
+ fn _format (
144
+ _db : & RootDatabase ,
145
+ _kind : SyntaxKind ,
146
+ _file_id : FileId ,
147
+ _expansion : & str ,
148
+ ) -> Option < String > {
149
+ None
150
+ }
151
+
152
+ #[ cfg( not( test) ) ]
153
+ fn _format (
154
+ db : & RootDatabase ,
155
+ kind : SyntaxKind ,
156
+ file_id : FileId ,
157
+ expansion : & str ,
158
+ ) -> Option < String > {
159
+ use ide_db:: base_db:: { FileLoader , SourceDatabase } ;
160
+ // hack until we get hygiene working (same character amount to preserve formatting as much as possible)
161
+ const DOLLAR_CRATE_REPLACE : & str = & "__r_a_" ;
162
+ let expansion = expansion. replace ( "$crate" , DOLLAR_CRATE_REPLACE ) ;
163
+ let ( prefix, suffix) = match kind {
164
+ SyntaxKind :: MACRO_PAT => ( "fn __(" , ": u32);" ) ,
165
+ SyntaxKind :: MACRO_EXPR | SyntaxKind :: MACRO_STMTS => ( "fn __() {" , "}" ) ,
166
+ SyntaxKind :: MACRO_TYPE => ( "type __ =" , ";" ) ,
167
+ _ => ( "" , "" ) ,
168
+ } ;
169
+ let expansion = format ! ( "{prefix}{expansion}{suffix}" ) ;
170
+
171
+ let & crate_id = db. relevant_crates ( file_id) . iter ( ) . next ( ) ?;
172
+ let edition = db. crate_graph ( ) [ crate_id] . edition ;
173
+
174
+ let mut cmd = std:: process:: Command :: new ( toolchain:: rustfmt ( ) ) ;
175
+ cmd. arg ( "--edition" ) ;
176
+ cmd. arg ( edition. to_string ( ) ) ;
177
+
178
+ let mut rustfmt = cmd
179
+ . stdin ( std:: process:: Stdio :: piped ( ) )
180
+ . stdout ( std:: process:: Stdio :: piped ( ) )
181
+ . stderr ( std:: process:: Stdio :: piped ( ) )
182
+ . spawn ( )
183
+ . ok ( ) ?;
184
+
185
+ std:: io:: Write :: write_all ( & mut rustfmt. stdin . as_mut ( ) ?, expansion. as_bytes ( ) ) . ok ( ) ?;
186
+
187
+ let output = rustfmt. wait_with_output ( ) . ok ( ) ?;
188
+ let captured_stdout = String :: from_utf8 ( output. stdout ) . ok ( ) ?;
189
+
190
+ if output. status . success ( ) && !captured_stdout. trim ( ) . is_empty ( ) {
191
+ let foo = captured_stdout. replace ( DOLLAR_CRATE_REPLACE , "$crate" ) ;
192
+ let trim_indent = stdx:: trim_indent ( foo. trim ( ) . strip_prefix ( prefix) ?. strip_suffix ( suffix) ?) ;
193
+ tracing:: debug!( "expand_macro: formatting succeeded" ) ;
194
+ Some ( trim_indent)
195
+ } else {
196
+ None
197
+ }
198
+ }
199
+
133
200
#[ cfg( test) ]
134
201
mod tests {
135
202
use expect_test:: { expect, Expect } ;
0 commit comments