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