Skip to main content

ide/
expand_macro.rs

1use hir::{ExpandResult, InFile, Semantics};
2use ide_db::{
3    FileId, RootDatabase, base_db::Crate, helpers::pick_best_token,
4    syntax_helpers::prettify_macro_expansion,
5};
6use span::{SpanMap, TextRange, TextSize};
7use stdx::format_to;
8use syntax::syntax_editor::SyntaxEditor;
9use syntax::{AstNode, NodeOrToken, SyntaxKind, SyntaxNode, T, ast};
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 = expansion_file_id.expansion_span_map(db);
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    if let Some(err) = err {
157        format_to!(error, "\n{}", err.render_to_string(sema.db));
158    }
159    let file_id =
160        sema.hir_file_for(&expanded).macro_file().expect("expansion must produce a macro file");
161    let expansion_span_map = file_id.expansion_span_map(sema.db);
162    result_span_map.merge(
163        TextRange::at(offset_in_original_node, macro_call.syntax().text_range().len()),
164        expanded.text_range().len(),
165        expansion_span_map,
166    );
167    Some(expand(sema, expanded, error, result_span_map, u32::from(offset_in_original_node) as i32))
168}
169
170fn expand(
171    sema: &Semantics<'_, RootDatabase>,
172    expanded: SyntaxNode,
173    error: &mut String,
174    result_span_map: &mut SpanMap,
175    mut offset_in_original_node: i32,
176) -> SyntaxNode {
177    let (editor, expanded) = SyntaxEditor::new(expanded);
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)| editor.replace(old.syntax(), new));
204    editor.finish().new_root().clone()
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#"
451//- minicore: try
452macro_rules! match_ast {
453    (match $node:ident { $($tt:tt)* }) => { match_ast!(match ($node) { $($tt)* }) };
454    (match ($node:expr) {}) => {{}};
455}
456
457fn main() {
458    let p = f(|it| {
459        let res = mat$0ch_ast! { match c {}};
460        Some(res)
461    })?;
462}
463"#,
464            expect![[r#"
465                match_ast!
466                {}"#]],
467        );
468    }
469
470    #[test]
471    fn macro_expand_inner_macro_rules() {
472        check(
473            r#"
474macro_rules! foo {
475    ($t:tt) => {{
476        macro_rules! bar {
477            () => {
478                $t
479            }
480        }
481        bar!()
482    }};
483}
484
485fn main() {
486    foo$0!(42);
487}
488            "#,
489            expect![[r#"
490                foo!
491                {
492                    macro_rules! bar {
493                        () => {
494                            42
495                        }
496                    }
497                    42
498                }"#]],
499        );
500    }
501
502    #[test]
503    fn macro_expand_inner_macro_fail_to_expand() {
504        check(
505            r#"
506macro_rules! bar {
507    (BAD) => {};
508}
509macro_rules! foo {
510    () => {bar!()};
511}
512
513fn main() {
514    let res = fo$0o!();
515}
516"#,
517            expect![[r#"
518                foo!
519                Expansion had errors:
520                expected ident: `BAD`
521
522            "#]],
523        );
524    }
525
526    #[test]
527    fn macro_expand_with_dollar_crate() {
528        check(
529            r#"
530#[macro_export]
531macro_rules! bar {
532    () => {0};
533}
534macro_rules! foo {
535    () => {$crate::bar!()};
536}
537
538fn main() {
539    let res = fo$0o!();
540}
541"#,
542            expect![[r#"
543                foo!
544                0"#]],
545        );
546    }
547
548    #[test]
549    fn macro_expand_with_dyn_absolute_path() {
550        check(
551            r#"
552macro_rules! foo {
553    () => {fn f<T>(_: &dyn ::std::marker::Copy) {}};
554}
555
556fn main() {
557    fo$0o!()
558}
559"#,
560            expect![[r#"
561                foo!
562                fn f<T>(_: &dyn ::std::marker::Copy){}"#]],
563        );
564    }
565
566    #[test]
567    fn macro_expand_item_expansion_in_expression_call() {
568        check(
569            r#"
570macro_rules! foo {
571    () => {fn f<T>() {}};
572}
573
574fn main() {
575    let res = fo$0o!();
576}
577"#,
578            expect![[r#"
579                foo!
580                fn f<T>(){}"#]],
581        );
582    }
583
584    #[test]
585    fn macro_expand_derive() {
586        check(
587            r#"
588//- proc_macros: identity, derive_identity
589//- minicore: derive
590
591#[proc_macros::identity]
592#[derive(proc_macros::DeriveIde$0ntity)]
593struct Foo {}
594"#,
595            expect![[r#"
596                proc_macros::DeriveIdentity
597                struct Foo {}"#]],
598        );
599    }
600
601    #[test]
602    fn macro_expand_derive2() {
603        check(
604            r#"
605//- proc_macros: derive_identity
606//- minicore: derive
607
608#[derive(proc_macros::$0DeriveIdentity)]
609#[derive(proc_macros::DeriveIdentity)]
610struct Foo {}
611"#,
612            expect![[r#"
613                proc_macros::DeriveIdentity
614                #[derive(proc_macros::DeriveIdentity)]
615                struct Foo {}"#]],
616        );
617    }
618
619    #[test]
620    fn macro_expand_derive_multi() {
621        check(
622            r#"
623//- proc_macros: derive_identity
624//- minicore: derive
625
626#[derive(proc_macros::DeriveIdent$0ity, proc_macros::DeriveIdentity)]
627struct Foo {}
628"#,
629            expect![[r#"
630                proc_macros::DeriveIdentity
631                struct Foo {}"#]],
632        );
633        check(
634            r#"
635//- proc_macros: derive_identity
636//- minicore: derive
637
638#[derive(proc_macros::DeriveIdentity, proc_macros::De$0riveIdentity)]
639struct Foo {}
640"#,
641            expect![[r#"
642                proc_macros::DeriveIdentity
643                struct Foo {}"#]],
644        );
645    }
646
647    #[test]
648    fn dollar_crate() {
649        check(
650            r#"
651//- /a.rs crate:a
652pub struct Foo;
653#[macro_export]
654macro_rules! m {
655    ( $i:ident ) => { $crate::Foo; $crate::Foo; $i::Foo; };
656}
657//- /b.rs crate:b deps:a
658pub struct Foo;
659#[macro_export]
660macro_rules! m {
661    () => { a::m!($crate); $crate::Foo; $crate::Foo; };
662}
663//- /c.rs crate:c deps:b,a
664pub struct Foo;
665#[macro_export]
666macro_rules! m {
667    () => { b::m!(); $crate::Foo; $crate::Foo; };
668}
669fn bar() {
670    m$0!();
671}
672"#,
673            expect![[r#"
674m!
675a::Foo;
676a::Foo;
677b::Foo;
678;
679b::Foo;
680b::Foo;
681;
682crate::Foo;
683crate::Foo;"#]],
684        );
685    }
686
687    #[test]
688    fn semi_glueing() {
689        check(
690            r#"
691macro_rules! __log_value {
692    ($key:ident :$capture:tt =) => {};
693}
694
695macro_rules! __log {
696    ($key:tt $(:$capture:tt)? $(= $value:expr)?; $($arg:tt)+) => {
697        __log_value!($key $(:$capture)* = $($value)*);
698    };
699}
700
701__log!(written:%; "Test"$0);
702    "#,
703            expect![[r#"
704                __log!
705            "#]],
706        );
707    }
708
709    #[test]
710    fn assoc_call() {
711        check(
712            r#"
713macro_rules! mac {
714    () => { fn assoc() {} }
715}
716impl () {
717    mac$0!();
718}
719    "#,
720            expect![[r#"
721                mac!
722                fn assoc(){}"#]],
723        );
724    }
725
726    #[test]
727    fn eager() {
728        check(
729            r#"
730//- minicore: concat
731macro_rules! my_concat {
732    ($head:expr, $($tail:tt)*) => { concat!($head, $($tail)*) };
733}
734
735
736fn test() {
737    _ = my_concat!(
738        conc$0at!("<", ">"),
739        "hi",
740    );
741}
742    "#,
743            expect![[r#"
744                concat!
745                "<>""#]],
746        );
747    }
748
749    #[test]
750    fn in_included() {
751        check(
752            r#"
753//- minicore: include
754//- /main.rs crate:main
755include!("./included.rs");
756//- /included.rs
757macro_rules! foo {
758    () => { fn item() {} };
759}
760foo$0!();
761"#,
762            expect![[r#"
763                foo!
764                fn item(){}"#]],
765        );
766    }
767
768    #[test]
769    fn include() {
770        check(
771            r#"
772//- minicore: include
773//- /main.rs crate:main
774include$0!("./included.rs");
775//- /included.rs
776macro_rules! foo {
777    () => { fn item() {} };
778}
779foo();
780"#,
781            expect![[r#"
782                include!
783                macro_rules! foo {
784                    () => {
785                        fn item(){}
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}