ide/
expand_macro.rs

1use hir::db::ExpandDatabase;
2use hir::{ExpandResult, InFile, InRealFile, Semantics};
3use ide_db::{
4    FileId, RootDatabase, base_db::Crate, helpers::pick_best_token,
5    syntax_helpers::prettify_macro_expansion,
6};
7use span::{SpanMap, SyntaxContext, TextRange, TextSize};
8use stdx::format_to;
9use syntax::{AstNode, NodeOrToken, SyntaxKind, SyntaxNode, T, ast, ted};
10
11use crate::FilePosition;
12
13pub struct ExpandedMacro {
14    pub name: String,
15    pub expansion: String,
16}
17
18// Feature: Expand Macro Recursively
19//
20// Shows the full macro expansion of the macro at the current caret position.
21//
22// | Editor  | Action Name |
23// |---------|-------------|
24// | VS Code | **rust-analyzer: Expand macro recursively at caret** |
25//
26// ![Expand Macro Recursively](https://user-images.githubusercontent.com/48062697/113020648-b3973180-917a-11eb-84a9-ecb921293dc5.gif)
27pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<ExpandedMacro> {
28    let sema = Semantics::new(db);
29    let file_id = sema.attach_first_edition(position.file_id)?;
30    let file = sema.parse(file_id);
31    let krate = sema.file_to_module_def(file_id.file_id(db))?.krate().into();
32
33    let tok = pick_best_token(file.syntax().token_at_offset(position.offset), |kind| match kind {
34        SyntaxKind::IDENT => 1,
35        _ => 0,
36    })?;
37
38    // due to how rust-analyzer works internally, we need to special case derive attributes,
39    // otherwise they might not get found, e.g. here with the cursor at $0 `#[attr]` would expand:
40    // ```
41    // #[attr]
42    // #[derive($0Foo)]
43    // struct Bar;
44    // ```
45
46    let derive = sema.descend_into_macros_exact(tok.clone()).into_iter().find_map(|descended| {
47        let macro_file = sema.hir_file_for(&descended.parent()?).macro_file()?;
48        if !macro_file.is_derive_attr_pseudo_expansion(db) {
49            return None;
50        }
51
52        let name = descended.parent_ancestors().filter_map(ast::Path::cast).last()?.to_string();
53        // up map out of the #[derive] expansion
54        let InFile { file_id, value: tokens } =
55            hir::InMacroFile::new(macro_file, descended).upmap_once(db);
56        let token = sema.parse_or_expand(file_id).covering_element(tokens[0]).into_token()?;
57        let attr = token.parent_ancestors().find_map(ast::Attr::cast)?;
58        let expansions = sema.expand_derive_macro(&attr)?;
59        let idx = attr
60            .token_tree()?
61            .token_trees_and_tokens()
62            .filter_map(NodeOrToken::into_token)
63            .take_while(|it| it != &token)
64            .filter(|it| it.kind() == T![,])
65            .count();
66        let ExpandResult { err, value: expansion } = expansions.get(idx)?.clone();
67        let expansion_file_id = sema.hir_file_for(&expansion).macro_file()?;
68        let expansion_span_map = db.expansion_span_map(expansion_file_id);
69        let mut expansion = format(
70            db,
71            SyntaxKind::MACRO_ITEMS,
72            position.file_id,
73            expansion,
74            &expansion_span_map,
75            krate,
76        );
77        if let Some(err) = err {
78            expansion.insert_str(
79                0,
80                &format!("Expansion had errors: {}\n\n", err.render_to_string(sema.db)),
81            );
82        }
83        Some(ExpandedMacro { name, expansion })
84    });
85
86    if derive.is_some() {
87        return derive;
88    }
89
90    let mut anc = sema
91        .descend_token_into_include_expansion(InRealFile::new(file_id, tok))
92        .value
93        .parent_ancestors();
94    let mut span_map = SpanMap::empty();
95    let mut error = String::new();
96    let (name, expanded, kind) = loop {
97        let node = anc.next()?;
98
99        if let Some(item) = ast::Item::cast(node.clone())
100            && let Some(def) = sema.resolve_attr_macro_call(&item)
101        {
102            break (
103                def.name(db).display(db, file_id.edition(db)).to_string(),
104                expand_macro_recur(&sema, &item, &mut error, &mut span_map, TextSize::new(0))?,
105                SyntaxKind::MACRO_ITEMS,
106            );
107        }
108        if let Some(mac) = ast::MacroCall::cast(node) {
109            let mut name = mac.path()?.segment()?.name_ref()?.to_string();
110            name.push('!');
111            let syntax_kind =
112                mac.syntax().parent().map(|it| it.kind()).unwrap_or(SyntaxKind::MACRO_ITEMS);
113            break (
114                name,
115                expand_macro_recur(
116                    &sema,
117                    &ast::Item::MacroCall(mac),
118                    &mut error,
119                    &mut span_map,
120                    TextSize::new(0),
121                )?,
122                syntax_kind,
123            );
124        }
125    };
126
127    // FIXME:
128    // macro expansion may lose all white space information
129    // But we hope someday we can use ra_fmt for that
130    let mut expansion = format(db, kind, position.file_id, expanded, &span_map, krate);
131
132    if !error.is_empty() {
133        expansion.insert_str(0, &format!("Expansion had errors:{error}\n\n"));
134    }
135    Some(ExpandedMacro { name, expansion })
136}
137
138fn expand_macro_recur(
139    sema: &Semantics<'_, RootDatabase>,
140    macro_call: &ast::Item,
141    error: &mut String,
142    result_span_map: &mut SpanMap<SyntaxContext>,
143    offset_in_original_node: TextSize,
144) -> Option<SyntaxNode> {
145    let ExpandResult { value: expanded, err } = match macro_call {
146        item @ ast::Item::MacroCall(macro_call) => sema
147            .expand_attr_macro(item)
148            .map(|it| it.map(|it| it.value))
149            .or_else(|| sema.expand_allowed_builtins(macro_call))?,
150        item => sema.expand_attr_macro(item)?.map(|it| it.value),
151    };
152    let expanded = expanded.clone_for_update();
153    if let Some(err) = err {
154        format_to!(error, "\n{}", err.render_to_string(sema.db));
155    }
156    let file_id =
157        sema.hir_file_for(&expanded).macro_file().expect("expansion must produce a macro file");
158    let expansion_span_map = sema.db.expansion_span_map(file_id);
159    result_span_map.merge(
160        TextRange::at(offset_in_original_node, macro_call.syntax().text_range().len()),
161        expanded.text_range().len(),
162        &expansion_span_map,
163    );
164    Some(expand(sema, expanded, error, result_span_map, u32::from(offset_in_original_node) as i32))
165}
166
167fn expand(
168    sema: &Semantics<'_, RootDatabase>,
169    expanded: SyntaxNode,
170    error: &mut String,
171    result_span_map: &mut SpanMap<SyntaxContext>,
172    mut offset_in_original_node: i32,
173) -> SyntaxNode {
174    let children = expanded.descendants().filter_map(ast::Item::cast);
175    let mut replacements = Vec::new();
176
177    for child in children {
178        if let Some(new_node) = expand_macro_recur(
179            sema,
180            &child,
181            error,
182            result_span_map,
183            TextSize::new(
184                (offset_in_original_node + (u32::from(child.syntax().text_range().start()) as i32))
185                    as u32,
186            ),
187        ) {
188            offset_in_original_node = offset_in_original_node
189                + (u32::from(new_node.text_range().len()) as i32)
190                - (u32::from(child.syntax().text_range().len()) as i32);
191            // check if the whole original syntax is replaced
192            if expanded == *child.syntax() {
193                return new_node;
194            }
195            replacements.push((child, new_node));
196        }
197    }
198
199    replacements.into_iter().rev().for_each(|(old, new)| ted::replace(old.syntax(), new));
200    expanded
201}
202
203fn format(
204    db: &RootDatabase,
205    kind: SyntaxKind,
206    file_id: FileId,
207    expanded: SyntaxNode,
208    span_map: &SpanMap<SyntaxContext>,
209    krate: Crate,
210) -> String {
211    let expansion = prettify_macro_expansion(db, expanded, span_map, krate).to_string();
212
213    _format(db, kind, file_id, &expansion).unwrap_or(expansion)
214}
215
216#[cfg(any(test, target_arch = "wasm32", target_os = "emscripten"))]
217fn _format(
218    _db: &RootDatabase,
219    _kind: SyntaxKind,
220    _file_id: FileId,
221    expansion: &str,
222) -> Option<String> {
223    // remove trailing spaces for test
224    use itertools::Itertools;
225    Some(expansion.lines().map(|x| x.trim_end()).join("\n"))
226}
227
228#[cfg(not(any(test, target_arch = "wasm32", target_os = "emscripten")))]
229fn _format(
230    db: &RootDatabase,
231    kind: SyntaxKind,
232    file_id: FileId,
233    expansion: &str,
234) -> Option<String> {
235    use ide_db::base_db::RootQueryDb;
236
237    // hack until we get hygiene working (same character amount to preserve formatting as much as possible)
238    const DOLLAR_CRATE_REPLACE: &str = "__r_a_";
239    const BUILTIN_REPLACE: &str = "builtin__POUND";
240    let expansion =
241        expansion.replace("$crate", DOLLAR_CRATE_REPLACE).replace("builtin #", BUILTIN_REPLACE);
242    let (prefix, suffix) = match kind {
243        SyntaxKind::MACRO_PAT => ("fn __(", ": u32);"),
244        SyntaxKind::MACRO_EXPR | SyntaxKind::MACRO_STMTS => ("fn __() {", "}"),
245        SyntaxKind::MACRO_TYPE => ("type __ =", ";"),
246        _ => ("", ""),
247    };
248    let expansion = format!("{prefix}{expansion}{suffix}");
249
250    let &crate_id = db.relevant_crates(file_id).iter().next()?;
251    let edition = crate_id.data(db).edition;
252
253    #[allow(clippy::disallowed_methods)]
254    let mut cmd = std::process::Command::new(toolchain::Tool::Rustfmt.path());
255    cmd.arg("--edition");
256    cmd.arg(edition.to_string());
257
258    let mut rustfmt = cmd
259        .stdin(std::process::Stdio::piped())
260        .stdout(std::process::Stdio::piped())
261        .stderr(std::process::Stdio::piped())
262        .spawn()
263        .ok()?;
264
265    std::io::Write::write_all(&mut rustfmt.stdin.as_mut()?, expansion.as_bytes()).ok()?;
266
267    let output = rustfmt.wait_with_output().ok()?;
268    let captured_stdout = String::from_utf8(output.stdout).ok()?;
269
270    if output.status.success() && !captured_stdout.trim().is_empty() {
271        let output = captured_stdout
272            .replace(DOLLAR_CRATE_REPLACE, "$crate")
273            .replace(BUILTIN_REPLACE, "builtin #");
274        let output = output.trim().strip_prefix(prefix)?;
275        let output = match kind {
276            SyntaxKind::MACRO_PAT => {
277                output.strip_suffix(suffix).or_else(|| output.strip_suffix(": u32,\n);"))?
278            }
279            _ => output.strip_suffix(suffix)?,
280        };
281        let trim_indent = stdx::trim_indent(output);
282        tracing::debug!("expand_macro: formatting succeeded");
283        Some(trim_indent)
284    } else {
285        None
286    }
287}
288
289#[cfg(test)]
290mod tests {
291    use expect_test::{Expect, expect};
292
293    use crate::fixture;
294
295    #[track_caller]
296    fn check(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) {
297        let (analysis, pos) = fixture::position(ra_fixture);
298        let expansion = analysis.expand_macro(pos).unwrap().unwrap();
299        let actual = format!("{}\n{}", expansion.name, expansion.expansion);
300        expect.assert_eq(&actual);
301    }
302
303    #[test]
304    fn expand_allowed_builtin_macro() {
305        check(
306            r#"
307//- minicore: concat
308$0concat!("test", 10, 'b', true);"#,
309            expect![[r#"
310                concat!
311                "test10btrue""#]],
312        );
313    }
314
315    #[test]
316    fn do_not_expand_disallowed_macro() {
317        let (analysis, pos) = fixture::position(
318            r#"
319//- minicore: asm
320$0asm!("0x300, x0");"#,
321        );
322        let expansion = analysis.expand_macro(pos).unwrap();
323        assert!(expansion.is_none());
324    }
325
326    #[test]
327    fn macro_expand_as_keyword() {
328        check(
329            r#"
330macro_rules! bar {
331    ($i:tt) => { $i as _ }
332}
333fn main() {
334    let x: u64 = ba$0r!(5i64);
335}
336"#,
337            expect![[r#"
338                bar!
339                5i64 as _"#]],
340        );
341    }
342
343    #[test]
344    fn macro_expand_underscore() {
345        check(
346            r#"
347macro_rules! bar {
348    ($i:tt) => { for _ in 0..$i {} }
349}
350fn main() {
351    ba$0r!(42);
352}
353"#,
354            expect![[r#"
355                bar!
356                for _ in 0..42{}"#]],
357        );
358    }
359
360    #[test]
361    fn macro_expand_recursive_expansion() {
362        check(
363            r#"
364macro_rules! bar {
365    () => { fn  b() {} }
366}
367macro_rules! foo {
368    () => { bar!(); }
369}
370macro_rules! baz {
371    () => { foo!(); }
372}
373f$0oo!();
374"#,
375            expect![[r#"
376                foo!
377                fn b(){}"#]],
378        );
379    }
380
381    #[test]
382    fn macro_expand_multiple_lines() {
383        check(
384            r#"
385macro_rules! foo {
386    () => {
387        fn some_thing() -> u32 {
388            let a = 0;
389            a + 10
390        }
391    }
392}
393f$0oo!();
394        "#,
395            expect![[r#"
396                foo!
397                fn some_thing() -> u32 {
398                    let a = 0;
399                    a+10
400                }"#]],
401        );
402    }
403
404    #[test]
405    fn macro_expand_match_ast() {
406        check(
407            r#"
408macro_rules! match_ast {
409    (match $node:ident { $($tt:tt)* }) => { match_ast!(match ($node) { $($tt)* }) };
410    (match ($node:expr) {
411        $( ast::$ast:ident($it:ident) => $res:block, )*
412        _ => $catch_all:expr $(,)?
413    }) => {{
414        $( if let Some($it) = ast::$ast::cast($node.clone()) $res else )*
415        { $catch_all }
416    }};
417}
418
419fn main() {
420    mat$0ch_ast! {
421        match container {
422            ast::TraitDef(it) => {},
423            ast::ImplDef(it) => {},
424            _ => { continue },
425        }
426    }
427}
428"#,
429            expect![[r#"
430                match_ast!
431                {
432                    if let Some(it) = ast::TraitDef::cast(container.clone()){}
433                    else if let Some(it) = ast::ImplDef::cast(container.clone()){}
434                    else {
435                        {
436                            continue
437                        }
438                    }
439                }"#]],
440        );
441    }
442
443    #[test]
444    fn macro_expand_match_ast_inside_let_statement() {
445        check(
446            r#"
447macro_rules! match_ast {
448    (match $node:ident { $($tt:tt)* }) => { match_ast!(match ($node) { $($tt)* }) };
449    (match ($node:expr) {}) => {{}};
450}
451
452fn main() {
453    let p = f(|it| {
454        let res = mat$0ch_ast! { match c {}};
455        Some(res)
456    })?;
457}
458"#,
459            expect![[r#"
460                match_ast!
461                {}"#]],
462        );
463    }
464
465    #[test]
466    fn macro_expand_inner_macro_rules() {
467        check(
468            r#"
469macro_rules! foo {
470    ($t:tt) => {{
471        macro_rules! bar {
472            () => {
473                $t
474            }
475        }
476        bar!()
477    }};
478}
479
480fn main() {
481    foo$0!(42);
482}
483            "#,
484            expect![[r#"
485                foo!
486                {
487                    macro_rules! bar {
488                        () => {
489                            42
490                        }
491                    }
492                    42
493                }"#]],
494        );
495    }
496
497    #[test]
498    fn macro_expand_inner_macro_fail_to_expand() {
499        check(
500            r#"
501macro_rules! bar {
502    (BAD) => {};
503}
504macro_rules! foo {
505    () => {bar!()};
506}
507
508fn main() {
509    let res = fo$0o!();
510}
511"#,
512            expect![[r#"
513                foo!
514                Expansion had errors:
515                expected ident: `BAD`
516
517            "#]],
518        );
519    }
520
521    #[test]
522    fn macro_expand_with_dollar_crate() {
523        check(
524            r#"
525#[macro_export]
526macro_rules! bar {
527    () => {0};
528}
529macro_rules! foo {
530    () => {$crate::bar!()};
531}
532
533fn main() {
534    let res = fo$0o!();
535}
536"#,
537            expect![[r#"
538                foo!
539                0"#]],
540        );
541    }
542
543    #[test]
544    fn macro_expand_with_dyn_absolute_path() {
545        check(
546            r#"
547macro_rules! foo {
548    () => {fn f<T>(_: &dyn ::std::marker::Copy) {}};
549}
550
551fn main() {
552    fo$0o!()
553}
554"#,
555            expect![[r#"
556                foo!
557                fn f<T>(_: &dyn ::std::marker::Copy){}"#]],
558        );
559    }
560
561    #[test]
562    fn macro_expand_item_expansion_in_expression_call() {
563        check(
564            r#"
565macro_rules! foo {
566    () => {fn f<T>() {}};
567}
568
569fn main() {
570    let res = fo$0o!();
571}
572"#,
573            expect![[r#"
574                foo!
575                fn f<T>(){}"#]],
576        );
577    }
578
579    #[test]
580    fn macro_expand_derive() {
581        check(
582            r#"
583//- proc_macros: identity
584//- minicore: clone, derive
585
586#[proc_macros::identity]
587#[derive(C$0lone)]
588struct Foo {}
589"#,
590            expect![[r#"
591                Clone
592                impl <>core::clone::Clone for Foo< >where {
593                    fn clone(&self) -> Self {
594                        match self {
595                            Foo{}
596                             => Foo{}
597                            ,
598
599                            }
600                    }
601
602                    }"#]],
603        );
604    }
605
606    #[test]
607    fn macro_expand_derive2() {
608        check(
609            r#"
610//- minicore: copy, clone, derive
611
612#[derive(Cop$0y)]
613#[derive(Clone)]
614struct Foo {}
615"#,
616            expect![[r#"
617                Copy
618                impl <>core::marker::Copy for Foo< >where{}"#]],
619        );
620    }
621
622    #[test]
623    fn macro_expand_derive_multi() {
624        check(
625            r#"
626//- minicore: copy, clone, derive
627
628#[derive(Cop$0y, Clone)]
629struct Foo {}
630"#,
631            expect![[r#"
632                Copy
633                impl <>core::marker::Copy for Foo< >where{}"#]],
634        );
635        check(
636            r#"
637//- minicore: copy, clone, derive
638
639#[derive(Copy, Cl$0one)]
640struct Foo {}
641"#,
642            expect![[r#"
643                Clone
644                impl <>core::clone::Clone for Foo< >where {
645                    fn clone(&self) -> Self {
646                        match self {
647                            Foo{}
648                             => Foo{}
649                            ,
650
651                            }
652                    }
653
654                    }"#]],
655        );
656    }
657
658    #[test]
659    fn dollar_crate() {
660        check(
661            r#"
662//- /a.rs crate:a
663pub struct Foo;
664#[macro_export]
665macro_rules! m {
666    ( $i:ident ) => { $crate::Foo; $crate::Foo; $i::Foo; };
667}
668//- /b.rs crate:b deps:a
669pub struct Foo;
670#[macro_export]
671macro_rules! m {
672    () => { a::m!($crate); $crate::Foo; $crate::Foo; };
673}
674//- /c.rs crate:c deps:b,a
675pub struct Foo;
676#[macro_export]
677macro_rules! m {
678    () => { b::m!(); $crate::Foo; $crate::Foo; };
679}
680fn bar() {
681    m$0!();
682}
683"#,
684            expect![[r#"
685m!
686a::Foo;
687a::Foo;
688b::Foo;
689;
690b::Foo;
691b::Foo;
692;
693crate::Foo;
694crate::Foo;"#]],
695        );
696    }
697
698    #[test]
699    fn semi_glueing() {
700        check(
701            r#"
702macro_rules! __log_value {
703    ($key:ident :$capture:tt =) => {};
704}
705
706macro_rules! __log {
707    ($key:tt $(:$capture:tt)? $(= $value:expr)?; $($arg:tt)+) => {
708        __log_value!($key $(:$capture)* = $($value)*);
709    };
710}
711
712__log!(written:%; "Test"$0);
713    "#,
714            expect![[r#"
715                __log!
716            "#]],
717        );
718    }
719
720    #[test]
721    fn assoc_call() {
722        check(
723            r#"
724macro_rules! mac {
725    () => { fn assoc() {} }
726}
727impl () {
728    mac$0!();
729}
730    "#,
731            expect![[r#"
732                mac!
733                fn assoc(){}"#]],
734        );
735    }
736
737    #[test]
738    fn eager() {
739        check(
740            r#"
741//- minicore: concat
742macro_rules! my_concat {
743    ($head:expr, $($tail:tt)*) => { concat!($head, $($tail)*) };
744}
745
746
747fn test() {
748    _ = my_concat!(
749        conc$0at!("<", ">"),
750        "hi",
751    );
752}
753    "#,
754            expect![[r#"
755                my_concat!
756                "<>hi""#]],
757        );
758    }
759
760    #[test]
761    fn in_included() {
762        check(
763            r#"
764//- minicore: include
765//- /main.rs crate:main
766include!("./included.rs");
767//- /included.rs
768macro_rules! foo {
769    () => { fn item() {} };
770}
771foo$0!();
772"#,
773            expect![[r#"
774                foo!
775                fn item(){}"#]],
776        );
777    }
778
779    #[test]
780    fn include() {
781        check(
782            r#"
783//- minicore: include
784//- /main.rs crate:main
785include$0!("./included.rs");
786//- /included.rs
787macro_rules! foo {
788    () => { fn item() {} };
789}
790foo();
791"#,
792            expect![[r#"
793                include!
794                macro_rules! foo {
795                    () => {
796                        fn item(){}
797
798                    };
799                }
800                foo();"#]],
801        );
802    }
803
804    #[test]
805    fn works_in_sig() {
806        check(
807            r#"
808macro_rules! foo {
809    () => { u32 };
810}
811fn foo() -> foo$0!() {
812    42
813}
814"#,
815            expect![[r#"
816                foo!
817                u32"#]],
818        );
819        check(
820            r#"
821macro_rules! foo {
822    () => { u32 };
823}
824fn foo(_: foo$0!() ) {}
825"#,
826            expect![[r#"
827                foo!
828                u32"#]],
829        );
830    }
831
832    #[test]
833    fn works_in_generics() {
834        check(
835            r#"
836trait Trait {}
837macro_rules! foo {
838    () => { Trait };
839}
840impl<const C: foo$0!()> Trait for () {}
841"#,
842            expect![[r#"
843                foo!
844                Trait"#]],
845        );
846    }
847
848    #[test]
849    fn works_in_fields() {
850        check(
851            r#"
852macro_rules! foo {
853    () => { u32 };
854}
855struct S {
856    field: foo$0!(),
857}
858"#,
859            expect![[r#"
860                foo!
861                u32"#]],
862        );
863    }
864}