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