ide_assists/handlers/
inline_macro.rs

1use hir::db::ExpandDatabase;
2use ide_db::syntax_helpers::prettify_macro_expansion;
3use syntax::ast::{self, AstNode};
4
5use crate::{AssistContext, AssistId, Assists};
6
7// Assist: inline_macro
8//
9// Takes a macro and inlines it one step.
10//
11// ```
12// macro_rules! num {
13//     (+$($t:tt)+) => (1 + num!($($t )+));
14//     (-$($t:tt)+) => (-1 + num!($($t )+));
15//     (+) => (1);
16//     (-) => (-1);
17// }
18//
19// fn main() {
20//     let number = num$0!(+ + + - + +);
21//     println!("{number}");
22// }
23// ```
24// ->
25// ```
26// macro_rules! num {
27//     (+$($t:tt)+) => (1 + num!($($t )+));
28//     (-$($t:tt)+) => (-1 + num!($($t )+));
29//     (+) => (1);
30//     (-) => (-1);
31// }
32//
33// fn main() {
34//     let number = 1+num!(+ + - + +);
35//     println!("{number}");
36// }
37// ```
38pub(crate) fn inline_macro(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
39    let unexpanded = ctx.find_node_at_offset::<ast::MacroCall>()?;
40    let macro_call = ctx.sema.to_def(&unexpanded)?;
41    let target_crate_id = ctx.sema.file_to_module_def(ctx.vfs_file_id())?.krate().into();
42    let text_range = unexpanded.syntax().text_range();
43
44    acc.add(
45        AssistId::refactor_inline("inline_macro"),
46        "Inline macro".to_owned(),
47        text_range,
48        |builder| {
49            let expanded = ctx.sema.parse_or_expand(macro_call.into());
50            let span_map = ctx.sema.db.expansion_span_map(macro_call);
51            // Don't call `prettify_macro_expansion()` outside the actual assist action; it does some heavy rowan tree manipulation,
52            // which can be very costly for big macros when it is done *even without the assist being invoked*.
53            let expanded = prettify_macro_expansion(ctx.db(), expanded, &span_map, target_crate_id);
54            builder.replace(text_range, expanded.to_string())
55        },
56    )
57}
58
59#[cfg(test)]
60mod tests {
61    use super::*;
62
63    use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
64
65    macro_rules! simple_macro {
66        () => {
67            r#"
68macro_rules! foo {
69    (foo) => (true);
70    () => (false);
71}
72"#
73        };
74    }
75    macro_rules! double_macro {
76        () => {
77            r#"
78macro_rules! bar {
79    (bar) => (true);
80    ($($tt:tt)?) => (false);
81}
82macro_rules! foo {
83    (foo) => (true);
84    (bar) => (bar!(bar));
85    ($($tt:tt)?) => (bar!($($tt)?));
86}
87"#
88        };
89    }
90
91    macro_rules! complex_macro {
92        () => {
93            r#"
94macro_rules! num {
95    (+$($t:tt)+) => (1 + num!($($t )+));
96    (-$($t:tt)+) => (-1 + num!($($t )+));
97    (+) => (1);
98    (-) => (-1);
99}
100"#
101        };
102    }
103    #[test]
104    fn inline_macro_target() {
105        check_assist_target(
106            inline_macro,
107            concat!(simple_macro!(), r#"fn f() { let a = foo$0!(foo); }"#),
108            "foo!(foo)",
109        );
110    }
111
112    #[test]
113    fn inline_macro_target_start() {
114        check_assist_target(
115            inline_macro,
116            concat!(simple_macro!(), r#"fn f() { let a = $0foo!(foo); }"#),
117            "foo!(foo)",
118        );
119    }
120
121    #[test]
122    fn inline_macro_target_end() {
123        check_assist_target(
124            inline_macro,
125            concat!(simple_macro!(), r#"fn f() { let a = foo!(foo$0); }"#),
126            "foo!(foo)",
127        );
128    }
129
130    #[test]
131    fn inline_macro_simple_case1() {
132        check_assist(
133            inline_macro,
134            concat!(simple_macro!(), r#"fn f() { let result = foo$0!(foo); }"#),
135            concat!(simple_macro!(), r#"fn f() { let result = true; }"#),
136        );
137    }
138
139    #[test]
140    fn inline_macro_simple_case2() {
141        check_assist(
142            inline_macro,
143            concat!(simple_macro!(), r#"fn f() { let result = foo$0!(); }"#),
144            concat!(simple_macro!(), r#"fn f() { let result = false; }"#),
145        );
146    }
147
148    #[test]
149    fn inline_macro_simple_not_applicable() {
150        check_assist_not_applicable(
151            inline_macro,
152            concat!(simple_macro!(), r#"fn f() { let result$0 = foo!(foo); }"#),
153        );
154    }
155
156    #[test]
157    fn inline_macro_simple_not_applicable_broken_macro() {
158        // FIXME: This is a bug. The macro should not expand, but it's
159        // the same behaviour as the "Expand Macro Recursively" command
160        // so it's presumably OK for the time being.
161        check_assist(
162            inline_macro,
163            concat!(simple_macro!(), r#"fn f() { let result = foo$0!(asdfasdf); }"#),
164            concat!(simple_macro!(), r#"fn f() { let result = true; }"#),
165        );
166    }
167
168    #[test]
169    fn inline_macro_double_case1() {
170        check_assist(
171            inline_macro,
172            concat!(double_macro!(), r#"fn f() { let result = foo$0!(bar); }"#),
173            concat!(double_macro!(), r#"fn f() { let result = bar!(bar); }"#),
174        );
175    }
176
177    #[test]
178    fn inline_macro_double_case2() {
179        check_assist(
180            inline_macro,
181            concat!(double_macro!(), r#"fn f() { let result = foo$0!(asdf); }"#),
182            concat!(double_macro!(), r#"fn f() { let result = bar!(asdf); }"#),
183        );
184    }
185
186    #[test]
187    fn inline_macro_complex_case1() {
188        check_assist(
189            inline_macro,
190            concat!(complex_macro!(), r#"fn f() { let result = num!(+ +$0 + - +); }"#),
191            concat!(complex_macro!(), r#"fn f() { let result = 1+num!(+ + - +); }"#),
192        );
193    }
194
195    #[test]
196    fn inline_macro_complex_case2() {
197        check_assist(
198            inline_macro,
199            concat!(complex_macro!(), r#"fn f() { let result = n$0um!(- + + - +); }"#),
200            concat!(complex_macro!(), r#"fn f() { let result = -1+num!(+ + - +); }"#),
201        );
202    }
203
204    #[test]
205    fn inline_macro_recursive_macro() {
206        check_assist(
207            inline_macro,
208            r#"
209macro_rules! foo {
210  () => {foo!()}
211}
212fn f() { let result = foo$0!(); }
213"#,
214            r#"
215macro_rules! foo {
216  () => {foo!()}
217}
218fn f() { let result = foo!(); }
219"#,
220        );
221    }
222
223    #[test]
224    fn inline_macro_unknown_macro() {
225        check_assist_not_applicable(
226            inline_macro,
227            r#"
228fn f() { let result = foo$0!(); }
229"#,
230        );
231    }
232
233    #[test]
234    fn inline_macro_function_call_not_applicable() {
235        check_assist_not_applicable(
236            inline_macro,
237            r#"
238fn f() { let result = foo$0(); }
239"#,
240        );
241    }
242
243    #[test]
244    fn inline_macro_with_whitespace() {
245        check_assist(
246            inline_macro,
247            r#"
248macro_rules! whitespace {
249    () => {
250        if true {}
251    };
252}
253fn f() { whitespace$0!(); }
254"#,
255            r#"
256macro_rules! whitespace {
257    () => {
258        if true {}
259    };
260}
261fn f() { if true{}; }
262"#,
263        )
264    }
265
266    #[test]
267    fn whitespace_between_text_and_pound() {
268        check_assist(
269            inline_macro,
270            r#"
271macro_rules! foo {
272    () => {
273        cfg_if! {
274            if #[cfg(test)] {
275                1;
276            } else {
277                1;
278            }
279        }
280    }
281}
282fn main() {
283    $0foo!();
284}
285"#,
286            r#"
287macro_rules! foo {
288    () => {
289        cfg_if! {
290            if #[cfg(test)] {
291                1;
292            } else {
293                1;
294            }
295        }
296    }
297}
298fn main() {
299    cfg_if!{
300    if #[cfg(test)]{
301        1;
302    }else {
303        1;
304    }
305};
306}
307"#,
308        );
309    }
310
311    #[test]
312    fn dollar_crate() {
313        check_assist(
314            inline_macro,
315            r#"
316pub struct Foo;
317#[macro_export]
318macro_rules! m {
319    () => { $crate::Foo };
320}
321fn bar() {
322    m$0!();
323}
324"#,
325            r#"
326pub struct Foo;
327#[macro_export]
328macro_rules! m {
329    () => { $crate::Foo };
330}
331fn bar() {
332    crate::Foo;
333}
334"#,
335        );
336        check_assist(
337            inline_macro,
338            r#"
339//- /a.rs crate:a
340pub struct Foo;
341#[macro_export]
342macro_rules! m {
343    () => { $crate::Foo };
344}
345//- /b.rs crate:b deps:a
346fn bar() {
347    a::m$0!();
348}
349"#,
350            r#"
351fn bar() {
352    a::Foo;
353}
354"#,
355        );
356        check_assist(
357            inline_macro,
358            r#"
359//- /a.rs crate:a
360pub struct Foo;
361#[macro_export]
362macro_rules! m {
363    () => { $crate::Foo };
364}
365//- /b.rs crate:b deps:a
366pub use a::m;
367//- /c.rs crate:c deps:b
368fn bar() {
369    b::m$0!();
370}
371"#,
372            r#"
373fn bar() {
374    a::Foo;
375}
376"#,
377        );
378    }
379}