]> git.lizzy.rs Git - rust.git/blob - crates/ide/src/expand_macro.rs
Merge #10421
[rust.git] / crates / ide / src / expand_macro.rs
1 use std::iter;
2
3 use hir::Semantics;
4 use ide_db::{helpers::pick_best_token, RootDatabase};
5 use itertools::Itertools;
6 use syntax::{ast, ted, AstNode, NodeOrToken, SyntaxKind, SyntaxNode, WalkEvent, T};
7
8 use crate::FilePosition;
9
10 pub struct ExpandedMacro {
11     pub name: String,
12     pub expansion: String,
13 }
14
15 // Feature: Expand Macro Recursively
16 //
17 // Shows the full macro expansion of the macro at current cursor.
18 //
19 // |===
20 // | Editor  | Action Name
21 //
22 // | VS Code | **Rust Analyzer: Expand macro recursively**
23 // |===
24 //
25 // image::https://user-images.githubusercontent.com/48062697/113020648-b3973180-917a-11eb-84a9-ecb921293dc5.gif[]
26 pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<ExpandedMacro> {
27     let sema = Semantics::new(db);
28     let file = sema.parse(position.file_id);
29
30     let tok = pick_best_token(file.syntax().token_at_offset(position.offset), |kind| match kind {
31         SyntaxKind::IDENT => 1,
32         _ => 0,
33     })?;
34
35     let descended = sema.descend_into_macros(tok.clone());
36     if let Some(attr) = descended.ancestors().find_map(ast::Attr::cast) {
37         if let Some((path, tt)) = attr.as_simple_call() {
38             if path == "derive" {
39                 let mut tt = tt.syntax().children_with_tokens().skip(1).join("");
40                 tt.pop();
41                 let expansions = sema.expand_derive_macro(&attr)?;
42                 return Some(ExpandedMacro {
43                     name: tt,
44                     expansion: expansions.into_iter().map(insert_whitespaces).join(""),
45                 });
46             }
47         }
48     }
49
50     // FIXME: Intermix attribute and bang! expansions
51     // currently we only recursively expand one of the two types
52     let mut expanded = None;
53     let mut name = None;
54     for node in tok.ancestors() {
55         if let Some(item) = ast::Item::cast(node.clone()) {
56             if let Some(def) = sema.resolve_attr_macro_call(&item) {
57                 name = def.name(db).map(|name| name.to_string());
58                 expanded = expand_attr_macro_recur(&sema, &item);
59                 break;
60             }
61         }
62         if let Some(mac) = ast::MacroCall::cast(node) {
63             name = Some(mac.path()?.segment()?.name_ref()?.to_string());
64             expanded = expand_macro_recur(&sema, &mac);
65             break;
66         }
67     }
68
69     // FIXME:
70     // macro expansion may lose all white space information
71     // But we hope someday we can use ra_fmt for that
72     let expansion = insert_whitespaces(expanded?);
73     Some(ExpandedMacro { name: name.unwrap_or_else(|| "???".to_owned()), expansion })
74 }
75
76 fn expand_macro_recur(
77     sema: &Semantics<RootDatabase>,
78     macro_call: &ast::MacroCall,
79 ) -> Option<SyntaxNode> {
80     let expanded = sema.expand(macro_call)?.clone_for_update();
81     expand(sema, expanded, ast::MacroCall::cast, expand_macro_recur)
82 }
83
84 fn expand_attr_macro_recur(sema: &Semantics<RootDatabase>, item: &ast::Item) -> Option<SyntaxNode> {
85     let expanded = sema.expand_attr_macro(item)?.clone_for_update();
86     expand(sema, expanded, ast::Item::cast, expand_attr_macro_recur)
87 }
88
89 fn expand<T: AstNode>(
90     sema: &Semantics<RootDatabase>,
91     expanded: SyntaxNode,
92     f: impl FnMut(SyntaxNode) -> Option<T>,
93     exp: impl Fn(&Semantics<RootDatabase>, &T) -> Option<SyntaxNode>,
94 ) -> Option<SyntaxNode> {
95     let children = expanded.descendants().filter_map(f);
96     let mut replacements = Vec::new();
97
98     for child in children {
99         if let Some(new_node) = exp(sema, &child) {
100             // check if the whole original syntax is replaced
101             if expanded == *child.syntax() {
102                 return Some(new_node);
103             }
104             replacements.push((child, new_node));
105         }
106     }
107
108     replacements.into_iter().rev().for_each(|(old, new)| ted::replace(old.syntax(), new));
109     Some(expanded)
110 }
111
112 // FIXME: It would also be cool to share logic here and in the mbe tests,
113 // which are pretty unreadable at the moment.
114 fn insert_whitespaces(syn: SyntaxNode) -> String {
115     use SyntaxKind::*;
116     let mut res = String::new();
117
118     let mut indent = 0;
119     let mut last: Option<SyntaxKind> = None;
120
121     for event in syn.preorder_with_tokens() {
122         let token = match event {
123             WalkEvent::Enter(NodeOrToken::Token(token)) => token,
124             WalkEvent::Leave(NodeOrToken::Node(node))
125                 if matches!(node.kind(), ATTR | MATCH_ARM | STRUCT | ENUM | UNION | FN | IMPL) =>
126             {
127                 res.push('\n');
128                 res.extend(iter::repeat(" ").take(2 * indent));
129                 continue;
130             }
131             _ => continue,
132         };
133         let is_next = |f: fn(SyntaxKind) -> bool, default| -> bool {
134             token.next_token().map(|it| f(it.kind())).unwrap_or(default)
135         };
136         let is_last =
137             |f: fn(SyntaxKind) -> bool, default| -> bool { last.map(f).unwrap_or(default) };
138
139         match token.kind() {
140             k if is_text(k) && is_next(|it| !it.is_punct(), true) => {
141                 res.push_str(token.text());
142                 res.push(' ');
143             }
144             L_CURLY if is_next(|it| it != R_CURLY, true) => {
145                 indent += 1;
146                 if is_last(is_text, false) {
147                     res.push(' ');
148                 }
149                 res.push_str("{\n");
150                 res.extend(iter::repeat(" ").take(2 * indent));
151             }
152             R_CURLY if is_last(|it| it != L_CURLY, true) => {
153                 indent = indent.saturating_sub(1);
154                 res.push('\n');
155                 res.extend(iter::repeat(" ").take(2 * indent));
156                 res.push_str("}");
157             }
158             R_CURLY => {
159                 res.push_str("}\n");
160                 res.extend(iter::repeat(" ").take(2 * indent));
161             }
162             LIFETIME_IDENT if is_next(|it| it == IDENT || it == MUT_KW, true) => {
163                 res.push_str(token.text());
164                 res.push(' ');
165             }
166             T![;] => {
167                 res.push_str(";\n");
168                 res.extend(iter::repeat(" ").take(2 * indent));
169             }
170             T![->] => res.push_str(" -> "),
171             T![=] => res.push_str(" = "),
172             T![=>] => res.push_str(" => "),
173             _ => res.push_str(token.text()),
174         }
175
176         last = Some(token.kind());
177     }
178
179     return res;
180
181     fn is_text(k: SyntaxKind) -> bool {
182         k.is_keyword() || k.is_literal() || k == IDENT
183     }
184 }
185
186 #[cfg(test)]
187 mod tests {
188     use expect_test::{expect, Expect};
189
190     use crate::fixture;
191
192     #[track_caller]
193     fn check(ra_fixture: &str, expect: Expect) {
194         let (analysis, pos) = fixture::position(ra_fixture);
195         let expansion = analysis.expand_macro(pos).unwrap().unwrap();
196         let actual = format!("{}\n{}", expansion.name, expansion.expansion);
197         expect.assert_eq(&actual);
198     }
199
200     #[test]
201     fn macro_expand_recursive_expansion() {
202         check(
203             r#"
204 macro_rules! bar {
205     () => { fn  b() {} }
206 }
207 macro_rules! foo {
208     () => { bar!(); }
209 }
210 macro_rules! baz {
211     () => { foo!(); }
212 }
213 f$0oo!();
214 "#,
215             expect![[r#"
216                 foo
217                 fn b(){}
218
219             "#]],
220         );
221     }
222
223     #[test]
224     fn macro_expand_multiple_lines() {
225         check(
226             r#"
227 macro_rules! foo {
228     () => {
229         fn some_thing() -> u32 {
230             let a = 0;
231             a + 10
232         }
233     }
234 }
235 f$0oo!();
236         "#,
237             expect![[r#"
238                 foo
239                 fn some_thing() -> u32 {
240                   let a = 0;
241                   a+10
242                 }
243             "#]],
244         );
245     }
246
247     #[test]
248     fn macro_expand_match_ast() {
249         check(
250             r#"
251 macro_rules! match_ast {
252     (match $node:ident { $($tt:tt)* }) => { match_ast!(match ($node) { $($tt)* }) };
253     (match ($node:expr) {
254         $( ast::$ast:ident($it:ident) => $res:block, )*
255         _ => $catch_all:expr $(,)?
256     }) => {{
257         $( if let Some($it) = ast::$ast::cast($node.clone()) $res else )*
258         { $catch_all }
259     }};
260 }
261
262 fn main() {
263     mat$0ch_ast! {
264         match container {
265             ast::TraitDef(it) => {},
266             ast::ImplDef(it) => {},
267             _ => { continue },
268         }
269     }
270 }
271 "#,
272             expect![[r#"
273        match_ast
274        {
275          if let Some(it) = ast::TraitDef::cast(container.clone()){}
276          else if let Some(it) = ast::ImplDef::cast(container.clone()){}
277          else {
278            {
279              continue
280            }
281          }
282        }"#]],
283         );
284     }
285
286     #[test]
287     fn macro_expand_match_ast_inside_let_statement() {
288         check(
289             r#"
290 macro_rules! match_ast {
291     (match $node:ident { $($tt:tt)* }) => { match_ast!(match ($node) { $($tt)* }) };
292     (match ($node:expr) {}) => {{}};
293 }
294
295 fn main() {
296     let p = f(|it| {
297         let res = mat$0ch_ast! { match c {}};
298         Some(res)
299     })?;
300 }
301 "#,
302             expect![[r#"
303                 match_ast
304                 {}
305             "#]],
306         );
307     }
308
309     #[test]
310     fn macro_expand_inner_macro_fail_to_expand() {
311         check(
312             r#"
313 macro_rules! bar {
314     (BAD) => {};
315 }
316 macro_rules! foo {
317     () => {bar!()};
318 }
319
320 fn main() {
321     let res = fo$0o!();
322 }
323 "#,
324             expect![[r#"
325                 foo
326             "#]],
327         );
328     }
329
330     #[test]
331     fn macro_expand_with_dollar_crate() {
332         check(
333             r#"
334 #[macro_export]
335 macro_rules! bar {
336     () => {0};
337 }
338 macro_rules! foo {
339     () => {$crate::bar!()};
340 }
341
342 fn main() {
343     let res = fo$0o!();
344 }
345 "#,
346             expect![[r#"
347                 foo
348                 0 "#]],
349         );
350     }
351
352     #[test]
353     fn macro_expand_derive() {
354         check(
355             r#"
356 #[rustc_builtin_macro]
357 pub macro Clone {}
358
359 #[derive(C$0lone)]
360 struct Foo {}
361 "#,
362             expect![[r#"
363                 Clone
364                 impl< >crate::clone::Clone for Foo< >{}
365
366             "#]],
367         );
368     }
369
370     #[test]
371     fn macro_expand_derive2() {
372         check(
373             r#"
374 #[rustc_builtin_macro]
375 pub macro Clone {}
376 #[rustc_builtin_macro]
377 pub macro Copy {}
378
379 #[derive(Cop$0y)]
380 #[derive(Clone)]
381 struct Foo {}
382 "#,
383             expect![[r#"
384                 Copy
385                 impl< >crate::marker::Copy for Foo< >{}
386
387             "#]],
388         );
389     }
390
391     #[test]
392     fn macro_expand_derive_multi() {
393         check(
394             r#"
395 #[rustc_builtin_macro]
396 pub macro Clone {}
397 #[rustc_builtin_macro]
398 pub macro Copy {}
399
400 #[derive(Cop$0y, Clone)]
401 struct Foo {}
402 "#,
403             expect![[r#"
404                 Copy, Clone
405                 impl< >crate::marker::Copy for Foo< >{}
406
407                 impl< >crate::clone::Clone for Foo< >{}
408
409             "#]],
410         );
411     }
412 }