Skip to main content

ide/
expand_macro.rs

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