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