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