Skip to main content

ide_assists/handlers/
inline_macro.rs

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