]> git.lizzy.rs Git - rust.git/blob - src/tools/rust-analyzer/crates/ide/src/expand_macro.rs
Rollup merge of #100228 - luqmana:suggestion-ice, r=estebank
[rust.git] / src / tools / rust-analyzer / crates / ide / src / expand_macro.rs
1 use hir::Semantics;
2 use ide_db::{
3     base_db::FileId, helpers::pick_best_token,
4     syntax_helpers::insert_whitespace_into_node::insert_ws_into, RootDatabase,
5 };
6 use syntax::{ast, ted, AstNode, NodeOrToken, SyntaxKind, SyntaxNode, T};
7
8 use crate::FilePosition;
9
10 pub struct ExpandedMacro {
11     pub name: String,
12     pub expansion: String,
13 }
14
15 // Feature: Expand Macro Recursively
16 //
17 // Shows the full macro expansion of the macro at current cursor.
18 //
19 // |===
20 // | Editor  | Action Name
21 //
22 // | VS Code | **rust-analyzer: Expand macro recursively**
23 // |===
24 //
25 // image::https://user-images.githubusercontent.com/48062697/113020648-b3973180-917a-11eb-84a9-ecb921293dc5.gif[]
26 pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<ExpandedMacro> {
27     let sema = Semantics::new(db);
28     let file = sema.parse(position.file_id);
29
30     let tok = pick_best_token(file.syntax().token_at_offset(position.offset), |kind| match kind {
31         SyntaxKind::IDENT => 1,
32         _ => 0,
33     })?;
34
35     // due to how rust-analyzer works internally, we need to special case derive attributes,
36     // otherwise they might not get found, e.g. here with the cursor at $0 `#[attr]` would expand:
37     // ```
38     // #[attr]
39     // #[derive($0Foo)]
40     // struct Bar;
41     // ```
42
43     let derive = sema.descend_into_macros(tok.clone()).into_iter().find_map(|descended| {
44         let hir_file = sema.hir_file_for(&descended.parent()?);
45         if !hir_file.is_derive_attr_pseudo_expansion(db) {
46             return None;
47         }
48
49         let name = descended.parent_ancestors().filter_map(ast::Path::cast).last()?.to_string();
50         // up map out of the #[derive] expansion
51         let token = hir::InFile::new(hir_file, descended).upmap(db)?.value;
52         let attr = token.parent_ancestors().find_map(ast::Attr::cast)?;
53         let expansions = sema.expand_derive_macro(&attr)?;
54         let idx = attr
55             .token_tree()?
56             .token_trees_and_tokens()
57             .filter_map(NodeOrToken::into_token)
58             .take_while(|it| it != &token)
59             .filter(|it| it.kind() == T![,])
60             .count();
61         let expansion =
62             format(db, SyntaxKind::MACRO_ITEMS, position.file_id, expansions.get(idx).cloned()?);
63         Some(ExpandedMacro { name, expansion })
64     });
65
66     if derive.is_some() {
67         return derive;
68     }
69
70     // FIXME: Intermix attribute and bang! expansions
71     // currently we only recursively expand one of the two types
72     let mut anc = tok.parent_ancestors();
73     let (name, expanded, kind) = loop {
74         let node = anc.next()?;
75
76         if let Some(item) = ast::Item::cast(node.clone()) {
77             if let Some(def) = sema.resolve_attr_macro_call(&item) {
78                 break (
79                     def.name(db).to_string(),
80                     expand_attr_macro_recur(&sema, &item)?,
81                     SyntaxKind::MACRO_ITEMS,
82                 );
83             }
84         }
85         if let Some(mac) = ast::MacroCall::cast(node) {
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             );
91         }
92     };
93
94     // FIXME:
95     // macro expansion may lose all white space information
96     // But we hope someday we can use ra_fmt for that
97     let expansion = format(db, kind, position.file_id, expanded);
98
99     Some(ExpandedMacro { name, expansion })
100 }
101
102 fn expand_macro_recur(
103     sema: &Semantics<'_, RootDatabase>,
104     macro_call: &ast::MacroCall,
105 ) -> Option<SyntaxNode> {
106     let expanded = sema.expand(macro_call)?.clone_for_update();
107     expand(sema, expanded, ast::MacroCall::cast, expand_macro_recur)
108 }
109
110 fn expand_attr_macro_recur(
111     sema: &Semantics<'_, RootDatabase>,
112     item: &ast::Item,
113 ) -> Option<SyntaxNode> {
114     let expanded = sema.expand_attr_macro(item)?.clone_for_update();
115     expand(sema, expanded, ast::Item::cast, expand_attr_macro_recur)
116 }
117
118 fn expand<T: AstNode>(
119     sema: &Semantics<'_, RootDatabase>,
120     expanded: SyntaxNode,
121     f: impl FnMut(SyntaxNode) -> Option<T>,
122     exp: impl Fn(&Semantics<'_, RootDatabase>, &T) -> Option<SyntaxNode>,
123 ) -> Option<SyntaxNode> {
124     let children = expanded.descendants().filter_map(f);
125     let mut replacements = Vec::new();
126
127     for child in children {
128         if let Some(new_node) = exp(sema, &child) {
129             // check if the whole original syntax is replaced
130             if expanded == *child.syntax() {
131                 return Some(new_node);
132             }
133             replacements.push((child, new_node));
134         }
135     }
136
137     replacements.into_iter().rev().for_each(|(old, new)| ted::replace(old.syntax(), new));
138     Some(expanded)
139 }
140
141 fn format(db: &RootDatabase, kind: SyntaxKind, file_id: FileId, expanded: SyntaxNode) -> String {
142     let expansion = insert_ws_into(expanded).to_string();
143
144     _format(db, kind, file_id, &expansion).unwrap_or(expansion)
145 }
146
147 #[cfg(any(test, target_arch = "wasm32", target_os = "emscripten"))]
148 fn _format(
149     _db: &RootDatabase,
150     _kind: SyntaxKind,
151     _file_id: FileId,
152     _expansion: &str,
153 ) -> Option<String> {
154     None
155 }
156
157 #[cfg(not(any(test, target_arch = "wasm32", target_os = "emscripten")))]
158 fn _format(
159     db: &RootDatabase,
160     kind: SyntaxKind,
161     file_id: FileId,
162     expansion: &str,
163 ) -> Option<String> {
164     use ide_db::base_db::{FileLoader, SourceDatabase};
165     // hack until we get hygiene working (same character amount to preserve formatting as much as possible)
166     const DOLLAR_CRATE_REPLACE: &str = &"__r_a_";
167     let expansion = expansion.replace("$crate", DOLLAR_CRATE_REPLACE);
168     let (prefix, suffix) = match kind {
169         SyntaxKind::MACRO_PAT => ("fn __(", ": u32);"),
170         SyntaxKind::MACRO_EXPR | SyntaxKind::MACRO_STMTS => ("fn __() {", "}"),
171         SyntaxKind::MACRO_TYPE => ("type __ =", ";"),
172         _ => ("", ""),
173     };
174     let expansion = format!("{prefix}{expansion}{suffix}");
175
176     let &crate_id = db.relevant_crates(file_id).iter().next()?;
177     let edition = db.crate_graph()[crate_id].edition;
178
179     let mut cmd = std::process::Command::new(toolchain::rustfmt());
180     cmd.arg("--edition");
181     cmd.arg(edition.to_string());
182
183     let mut rustfmt = cmd
184         .stdin(std::process::Stdio::piped())
185         .stdout(std::process::Stdio::piped())
186         .stderr(std::process::Stdio::piped())
187         .spawn()
188         .ok()?;
189
190     std::io::Write::write_all(&mut rustfmt.stdin.as_mut()?, expansion.as_bytes()).ok()?;
191
192     let output = rustfmt.wait_with_output().ok()?;
193     let captured_stdout = String::from_utf8(output.stdout).ok()?;
194
195     if output.status.success() && !captured_stdout.trim().is_empty() {
196         let output = captured_stdout.replace(DOLLAR_CRATE_REPLACE, "$crate");
197         let output = output.trim().strip_prefix(prefix)?;
198         let output = match kind {
199             SyntaxKind::MACRO_PAT => {
200                 output.strip_suffix(suffix).or_else(|| output.strip_suffix(": u32,\n);"))?
201             }
202             _ => output.strip_suffix(suffix)?,
203         };
204         let trim_indent = stdx::trim_indent(output);
205         tracing::debug!("expand_macro: formatting succeeded");
206         Some(trim_indent)
207     } else {
208         None
209     }
210 }
211
212 #[cfg(test)]
213 mod tests {
214     use expect_test::{expect, Expect};
215
216     use crate::fixture;
217
218     #[track_caller]
219     fn check(ra_fixture: &str, expect: Expect) {
220         let (analysis, pos) = fixture::position(ra_fixture);
221         let expansion = analysis.expand_macro(pos).unwrap().unwrap();
222         let actual = format!("{}\n{}", expansion.name, expansion.expansion);
223         expect.assert_eq(&actual);
224     }
225
226     #[test]
227     fn macro_expand_as_keyword() {
228         check(
229             r#"
230 macro_rules! bar {
231     ($i:tt) => { $i as _ }
232 }
233 fn main() {
234     let x: u64 = ba$0r!(5i64);
235 }
236 "#,
237             expect![[r#"
238                 bar
239                 5i64 as _"#]],
240         );
241     }
242
243     #[test]
244     fn macro_expand_underscore() {
245         check(
246             r#"
247 macro_rules! bar {
248     ($i:tt) => { for _ in 0..$i {} }
249 }
250 fn main() {
251     ba$0r!(42);
252 }
253 "#,
254             expect![[r#"
255                 bar
256                 for _ in 0..42{}"#]],
257         );
258     }
259
260     #[test]
261     fn macro_expand_recursive_expansion() {
262         check(
263             r#"
264 macro_rules! bar {
265     () => { fn  b() {} }
266 }
267 macro_rules! foo {
268     () => { bar!(); }
269 }
270 macro_rules! baz {
271     () => { foo!(); }
272 }
273 f$0oo!();
274 "#,
275             expect![[r#"
276                 foo
277                 fn b(){}
278             "#]],
279         );
280     }
281
282     #[test]
283     fn macro_expand_multiple_lines() {
284         check(
285             r#"
286 macro_rules! foo {
287     () => {
288         fn some_thing() -> u32 {
289             let a = 0;
290             a + 10
291         }
292     }
293 }
294 f$0oo!();
295         "#,
296             expect![[r#"
297                 foo
298                 fn some_thing() -> u32 {
299                   let a = 0;
300                   a+10
301                 }"#]],
302         );
303     }
304
305     #[test]
306     fn macro_expand_match_ast() {
307         check(
308             r#"
309 macro_rules! match_ast {
310     (match $node:ident { $($tt:tt)* }) => { match_ast!(match ($node) { $($tt)* }) };
311     (match ($node:expr) {
312         $( ast::$ast:ident($it:ident) => $res:block, )*
313         _ => $catch_all:expr $(,)?
314     }) => {{
315         $( if let Some($it) = ast::$ast::cast($node.clone()) $res else )*
316         { $catch_all }
317     }};
318 }
319
320 fn main() {
321     mat$0ch_ast! {
322         match container {
323             ast::TraitDef(it) => {},
324             ast::ImplDef(it) => {},
325             _ => { continue },
326         }
327     }
328 }
329 "#,
330             expect![[r#"
331        match_ast
332        {
333          if let Some(it) = ast::TraitDef::cast(container.clone()){}
334          else if let Some(it) = ast::ImplDef::cast(container.clone()){}
335          else {
336            {
337              continue
338            }
339          }
340        }"#]],
341         );
342     }
343
344     #[test]
345     fn macro_expand_match_ast_inside_let_statement() {
346         check(
347             r#"
348 macro_rules! match_ast {
349     (match $node:ident { $($tt:tt)* }) => { match_ast!(match ($node) { $($tt)* }) };
350     (match ($node:expr) {}) => {{}};
351 }
352
353 fn main() {
354     let p = f(|it| {
355         let res = mat$0ch_ast! { match c {}};
356         Some(res)
357     })?;
358 }
359 "#,
360             expect![[r#"
361                 match_ast
362                 {}"#]],
363         );
364     }
365
366     #[test]
367     fn macro_expand_inner_macro_rules() {
368         check(
369             r#"
370 macro_rules! foo {
371     ($t:tt) => {{
372         macro_rules! bar {
373             () => {
374                 $t
375             }
376         }
377         bar!()
378     }};
379 }
380
381 fn main() {
382     foo$0!(42);
383 }
384             "#,
385             expect![[r#"
386                 foo
387                 {
388                   macro_rules! bar {
389                     () => {
390                       42
391                     }
392                   }
393                   42
394                 }"#]],
395         );
396     }
397
398     #[test]
399     fn macro_expand_inner_macro_fail_to_expand() {
400         check(
401             r#"
402 macro_rules! bar {
403     (BAD) => {};
404 }
405 macro_rules! foo {
406     () => {bar!()};
407 }
408
409 fn main() {
410     let res = fo$0o!();
411 }
412 "#,
413             expect![[r#"
414                 foo
415             "#]],
416         );
417     }
418
419     #[test]
420     fn macro_expand_with_dollar_crate() {
421         check(
422             r#"
423 #[macro_export]
424 macro_rules! bar {
425     () => {0};
426 }
427 macro_rules! foo {
428     () => {$crate::bar!()};
429 }
430
431 fn main() {
432     let res = fo$0o!();
433 }
434 "#,
435             expect![[r#"
436                 foo
437                 0"#]],
438         );
439     }
440
441     #[test]
442     fn macro_expand_with_dyn_absolute_path() {
443         check(
444             r#"
445 macro_rules! foo {
446     () => {fn f<T>(_: &dyn ::std::marker::Copy) {}};
447 }
448
449 fn main() {
450     let res = fo$0o!();
451 }
452 "#,
453             expect![[r#"
454                 foo
455                 fn f<T>(_: &dyn ::std::marker::Copy){}"#]],
456         );
457     }
458
459     #[test]
460     fn macro_expand_derive() {
461         check(
462             r#"
463 //- proc_macros: identity
464 //- minicore: clone, derive
465
466 #[proc_macros::identity]
467 #[derive(C$0lone)]
468 struct Foo {}
469 "#,
470             expect![[r#"
471                 Clone
472                 impl < >core::clone::Clone for Foo< >{}
473             "#]],
474         );
475     }
476
477     #[test]
478     fn macro_expand_derive2() {
479         check(
480             r#"
481 //- minicore: copy, clone, derive
482
483 #[derive(Cop$0y)]
484 #[derive(Clone)]
485 struct Foo {}
486 "#,
487             expect![[r#"
488                 Copy
489                 impl < >core::marker::Copy for Foo< >{}
490             "#]],
491         );
492     }
493
494     #[test]
495     fn macro_expand_derive_multi() {
496         check(
497             r#"
498 //- minicore: copy, clone, derive
499
500 #[derive(Cop$0y, Clone)]
501 struct Foo {}
502 "#,
503             expect![[r#"
504                 Copy
505                 impl < >core::marker::Copy for Foo< >{}
506             "#]],
507         );
508         check(
509             r#"
510 //- minicore: copy, clone, derive
511
512 #[derive(Copy, Cl$0one)]
513 struct Foo {}
514 "#,
515             expect![[r#"
516                 Clone
517                 impl < >core::clone::Clone for Foo< >{}
518             "#]],
519         );
520     }
521 }