]> git.lizzy.rs Git - rust.git/blob - crates/ide/src/expand_macro.rs
Merge #9170
[rust.git] / crates / ide / src / expand_macro.rs
1 use std::iter;
2
3 use hir::Semantics;
4 use ide_db::RootDatabase;
5 use syntax::{
6     ast, match_ast, ted, AstNode, NodeOrToken, SyntaxKind, SyntaxKind::*, SyntaxNode, WalkEvent, T,
7 };
8
9 use crate::FilePosition;
10
11 pub struct ExpandedMacro {
12     pub name: String,
13     pub expansion: String,
14 }
15
16 // Feature: Expand Macro Recursively
17 //
18 // Shows the full macro expansion of the macro at current cursor.
19 //
20 // |===
21 // | Editor  | Action Name
22 //
23 // | VS Code | **Rust Analyzer: Expand macro recursively**
24 // |===
25 //
26 // image::https://user-images.githubusercontent.com/48062697/113020648-b3973180-917a-11eb-84a9-ecb921293dc5.gif[]
27 pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<ExpandedMacro> {
28     let sema = Semantics::new(db);
29     let file = sema.parse(position.file_id);
30
31     let tok = file.syntax().token_at_offset(position.offset).left_biased()?;
32     let mut expanded = None;
33     let mut name = None;
34     for node in tok.ancestors() {
35         match_ast! {
36             match node {
37                 ast::MacroCall(mac) => {
38                     name = Some(mac.path()?.segment()?.name_ref()?.to_string());
39                     expanded = expand_macro_recur(&sema, &mac);
40                     break;
41                 },
42                 ast::Item(item) => {
43                     // FIXME: add the macro name
44                     // FIXME: make this recursive too
45                     name = Some("?".to_string());
46                     expanded = sema.expand_attr_macro(&item);
47                     if expanded.is_some() {
48                         break;
49                     }
50                 },
51                 _ => {}
52             }
53         }
54     }
55
56     // FIXME:
57     // macro expansion may lose all white space information
58     // But we hope someday we can use ra_fmt for that
59     let expansion = insert_whitespaces(expanded?);
60     Some(ExpandedMacro { name: name?, expansion })
61 }
62
63 fn expand_macro_recur(
64     sema: &Semantics<RootDatabase>,
65     macro_call: &ast::MacroCall,
66 ) -> Option<SyntaxNode> {
67     let expanded = sema.expand(macro_call)?.clone_for_update();
68
69     let children = expanded.descendants().filter_map(ast::MacroCall::cast);
70     let mut replacements = Vec::new();
71
72     for child in children {
73         if let Some(new_node) = expand_macro_recur(sema, &child) {
74             // check if the whole original syntax is replaced
75             if expanded == *child.syntax() {
76                 return Some(new_node);
77             }
78             replacements.push((child, new_node));
79         }
80     }
81
82     replacements.into_iter().rev().for_each(|(old, new)| ted::replace(old.syntax(), new));
83     Some(expanded)
84 }
85
86 // FIXME: It would also be cool to share logic here and in the mbe tests,
87 // which are pretty unreadable at the moment.
88 fn insert_whitespaces(syn: SyntaxNode) -> String {
89     let mut res = String::new();
90     let mut token_iter = syn
91         .preorder_with_tokens()
92         .filter_map(|event| {
93             if let WalkEvent::Enter(NodeOrToken::Token(token)) = event {
94                 Some(token)
95             } else {
96                 None
97             }
98         })
99         .peekable();
100
101     let mut indent = 0;
102     let mut last: Option<SyntaxKind> = None;
103
104     while let Some(token) = token_iter.next() {
105         let mut is_next = |f: fn(SyntaxKind) -> bool, default| -> bool {
106             token_iter.peek().map(|it| f(it.kind())).unwrap_or(default)
107         };
108         let is_last =
109             |f: fn(SyntaxKind) -> bool, default| -> bool { last.map(f).unwrap_or(default) };
110
111         match token.kind() {
112             k if is_text(k) && is_next(|it| !it.is_punct(), true) => {
113                 res.push_str(token.text());
114                 res.push(' ');
115             }
116             L_CURLY if is_next(|it| it != R_CURLY, true) => {
117                 indent += 1;
118                 if is_last(is_text, false) {
119                     res.push(' ');
120                 }
121                 res.push_str("{\n");
122                 res.extend(iter::repeat(" ").take(2 * indent));
123             }
124             R_CURLY if is_last(|it| it != L_CURLY, true) => {
125                 indent = indent.saturating_sub(1);
126                 res.push('\n');
127                 res.extend(iter::repeat(" ").take(2 * indent));
128                 res.push_str("}");
129             }
130             R_CURLY => {
131                 res.push_str("}\n");
132                 res.extend(iter::repeat(" ").take(2 * indent));
133             }
134             LIFETIME_IDENT if is_next(|it| it == IDENT, true) => {
135                 res.push_str(token.text());
136                 res.push(' ');
137             }
138             T![;] => {
139                 res.push_str(";\n");
140                 res.extend(iter::repeat(" ").take(2 * indent));
141             }
142             T![->] => res.push_str(" -> "),
143             T![=] => res.push_str(" = "),
144             T![=>] => res.push_str(" => "),
145             _ => res.push_str(token.text()),
146         }
147
148         last = Some(token.kind());
149     }
150
151     return res;
152
153     fn is_text(k: SyntaxKind) -> bool {
154         k.is_keyword() || k.is_literal() || k == IDENT
155     }
156 }
157
158 #[cfg(test)]
159 mod tests {
160     use expect_test::{expect, Expect};
161
162     use crate::fixture;
163
164     fn check(ra_fixture: &str, expect: Expect) {
165         let (analysis, pos) = fixture::position(ra_fixture);
166         let expansion = analysis.expand_macro(pos).unwrap().unwrap();
167         let actual = format!("{}\n{}", expansion.name, expansion.expansion);
168         expect.assert_eq(&actual);
169     }
170
171     #[test]
172     fn macro_expand_recursive_expansion() {
173         check(
174             r#"
175 macro_rules! bar {
176     () => { fn  b() {} }
177 }
178 macro_rules! foo {
179     () => { bar!(); }
180 }
181 macro_rules! baz {
182     () => { foo!(); }
183 }
184 f$0oo!();
185 "#,
186             expect![[r#"
187                 foo
188                 fn b(){}
189             "#]],
190         );
191     }
192
193     #[test]
194     fn macro_expand_multiple_lines() {
195         check(
196             r#"
197 macro_rules! foo {
198     () => {
199         fn some_thing() -> u32 {
200             let a = 0;
201             a + 10
202         }
203     }
204 }
205 f$0oo!();
206         "#,
207             expect![[r#"
208             foo
209             fn some_thing() -> u32 {
210               let a = 0;
211               a+10
212             }"#]],
213         );
214     }
215
216     #[test]
217     fn macro_expand_match_ast() {
218         check(
219             r#"
220 macro_rules! match_ast {
221     (match $node:ident { $($tt:tt)* }) => { match_ast!(match ($node) { $($tt)* }) };
222     (match ($node:expr) {
223         $( ast::$ast:ident($it:ident) => $res:block, )*
224         _ => $catch_all:expr $(,)?
225     }) => {{
226         $( if let Some($it) = ast::$ast::cast($node.clone()) $res else )*
227         { $catch_all }
228     }};
229 }
230
231 fn main() {
232     mat$0ch_ast! {
233         match container {
234             ast::TraitDef(it) => {},
235             ast::ImplDef(it) => {},
236             _ => { continue },
237         }
238     }
239 }
240 "#,
241             expect![[r#"
242        match_ast
243        {
244          if let Some(it) = ast::TraitDef::cast(container.clone()){}
245          else if let Some(it) = ast::ImplDef::cast(container.clone()){}
246          else {
247            {
248              continue
249            }
250          }
251        }"#]],
252         );
253     }
254
255     #[test]
256     fn macro_expand_match_ast_inside_let_statement() {
257         check(
258             r#"
259 macro_rules! match_ast {
260     (match $node:ident { $($tt:tt)* }) => { match_ast!(match ($node) { $($tt)* }) };
261     (match ($node:expr) {}) => {{}};
262 }
263
264 fn main() {
265     let p = f(|it| {
266         let res = mat$0ch_ast! { match c {}};
267         Some(res)
268     })?;
269 }
270 "#,
271             expect![[r#"
272                 match_ast
273                 {}
274             "#]],
275         );
276     }
277
278     #[test]
279     fn macro_expand_inner_macro_fail_to_expand() {
280         check(
281             r#"
282 macro_rules! bar {
283     (BAD) => {};
284 }
285 macro_rules! foo {
286     () => {bar!()};
287 }
288
289 fn main() {
290     let res = fo$0o!();
291 }
292 "#,
293             expect![[r#"
294                 foo
295             "#]],
296         );
297     }
298
299     #[test]
300     fn macro_expand_with_dollar_crate() {
301         check(
302             r#"
303 #[macro_export]
304 macro_rules! bar {
305     () => {0};
306 }
307 macro_rules! foo {
308     () => {$crate::bar!()};
309 }
310
311 fn main() {
312     let res = fo$0o!();
313 }
314 "#,
315             expect![[r#"
316                 foo
317                 0 "#]],
318         );
319     }
320 }