aboutsummaryrefslogtreecommitdiff
path: root/crates/ide/src/expand_macro.rs
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2020-08-13 16:42:52 +0100
committerAleksey Kladov <[email protected]>2020-08-13 16:58:27 +0100
commit1b0c7701cc97cd7bef8bb9729011d4cf291a60c5 (patch)
treeb69f0c9947d9cec522ce835d7213b21075fe6dcf /crates/ide/src/expand_macro.rs
parentfc34403018079ea053f26d0a31b7517053c7dd8c (diff)
Rename ra_ide -> ide
Diffstat (limited to 'crates/ide/src/expand_macro.rs')
-rw-r--r--crates/ide/src/expand_macro.rs283
1 files changed, 283 insertions, 0 deletions
diff --git a/crates/ide/src/expand_macro.rs b/crates/ide/src/expand_macro.rs
new file mode 100644
index 000000000..31455709d
--- /dev/null
+++ b/crates/ide/src/expand_macro.rs
@@ -0,0 +1,283 @@
1use hir::Semantics;
2use ide_db::RootDatabase;
3use syntax::{
4 algo::{find_node_at_offset, SyntaxRewriter},
5 ast, AstNode, NodeOrToken, SyntaxKind,
6 SyntaxKind::*,
7 SyntaxNode, WalkEvent, T,
8};
9
10use crate::FilePosition;
11
12pub 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// |===
26pub(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 let name_ref = find_node_at_offset::<ast::NameRef>(file.syntax(), position.offset)?;
30 let mac = name_ref.syntax().ancestors().find_map(ast::MacroCall::cast)?;
31
32 let expanded = expand_macro_recur(&sema, &mac)?;
33
34 // FIXME:
35 // macro expansion may lose all white space information
36 // But we hope someday we can use ra_fmt for that
37 let expansion = insert_whitespaces(expanded);
38 Some(ExpandedMacro { name: name_ref.text().to_string(), expansion })
39}
40
41fn expand_macro_recur(
42 sema: &Semantics<RootDatabase>,
43 macro_call: &ast::MacroCall,
44) -> Option<SyntaxNode> {
45 let mut expanded = sema.expand(macro_call)?;
46
47 let children = expanded.descendants().filter_map(ast::MacroCall::cast);
48 let mut rewriter = SyntaxRewriter::default();
49
50 for child in children.into_iter() {
51 if let Some(new_node) = expand_macro_recur(sema, &child) {
52 // Replace the whole node if it is root
53 // `replace_descendants` will not replace the parent node
54 // but `SyntaxNode::descendants include itself
55 if expanded == *child.syntax() {
56 expanded = new_node;
57 } else {
58 rewriter.replace(child.syntax(), &new_node)
59 }
60 }
61 }
62
63 let res = rewriter.rewrite(&expanded);
64 Some(res)
65}
66
67// FIXME: It would also be cool to share logic here and in the mbe tests,
68// which are pretty unreadable at the moment.
69fn insert_whitespaces(syn: SyntaxNode) -> String {
70 let mut res = String::new();
71 let mut token_iter = syn
72 .preorder_with_tokens()
73 .filter_map(|event| {
74 if let WalkEvent::Enter(NodeOrToken::Token(token)) = event {
75 Some(token)
76 } else {
77 None
78 }
79 })
80 .peekable();
81
82 let mut indent = 0;
83 let mut last: Option<SyntaxKind> = None;
84
85 while let Some(token) = token_iter.next() {
86 let mut is_next = |f: fn(SyntaxKind) -> bool, default| -> bool {
87 token_iter.peek().map(|it| f(it.kind())).unwrap_or(default)
88 };
89 let is_last =
90 |f: fn(SyntaxKind) -> bool, default| -> bool { last.map(f).unwrap_or(default) };
91
92 res += &match token.kind() {
93 k if is_text(k) && is_next(|it| !it.is_punct(), true) => token.text().to_string() + " ",
94 L_CURLY if is_next(|it| it != R_CURLY, true) => {
95 indent += 1;
96 let leading_space = if is_last(is_text, false) { " " } else { "" };
97 format!("{}{{\n{}", leading_space, " ".repeat(indent))
98 }
99 R_CURLY if is_last(|it| it != L_CURLY, true) => {
100 indent = indent.saturating_sub(1);
101 format!("\n{}}}", " ".repeat(indent))
102 }
103 R_CURLY => format!("}}\n{}", " ".repeat(indent)),
104 T![;] => format!(";\n{}", " ".repeat(indent)),
105 T![->] => " -> ".to_string(),
106 T![=] => " = ".to_string(),
107 T![=>] => " => ".to_string(),
108 _ => token.text().to_string(),
109 };
110
111 last = Some(token.kind());
112 }
113
114 return res;
115
116 fn is_text(k: SyntaxKind) -> bool {
117 k.is_keyword() || k.is_literal() || k == IDENT
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use expect::{expect, Expect};
124
125 use crate::mock_analysis::analysis_and_position;
126
127 fn check(ra_fixture: &str, expect: Expect) {
128 let (analysis, pos) = analysis_and_position(ra_fixture);
129 let expansion = analysis.expand_macro(pos).unwrap().unwrap();
130 let actual = format!("{}\n{}", expansion.name, expansion.expansion);
131 expect.assert_eq(&actual);
132 }
133
134 #[test]
135 fn macro_expand_recursive_expansion() {
136 check(
137 r#"
138macro_rules! bar {
139 () => { fn b() {} }
140}
141macro_rules! foo {
142 () => { bar!(); }
143}
144macro_rules! baz {
145 () => { foo!(); }
146}
147f<|>oo!();
148"#,
149 expect![[r#"
150 foo
151 fn b(){}
152 "#]],
153 );
154 }
155
156 #[test]
157 fn macro_expand_multiple_lines() {
158 check(
159 r#"
160macro_rules! foo {
161 () => {
162 fn some_thing() -> u32 {
163 let a = 0;
164 a + 10
165 }
166 }
167}
168f<|>oo!();
169 "#,
170 expect![[r#"
171 foo
172 fn some_thing() -> u32 {
173 let a = 0;
174 a+10
175 }"#]],
176 );
177 }
178
179 #[test]
180 fn macro_expand_match_ast() {
181 check(
182 r#"
183macro_rules! match_ast {
184 (match $node:ident { $($tt:tt)* }) => { match_ast!(match ($node) { $($tt)* }) };
185 (match ($node:expr) {
186 $( ast::$ast:ident($it:ident) => $res:block, )*
187 _ => $catch_all:expr $(,)?
188 }) => {{
189 $( if let Some($it) = ast::$ast::cast($node.clone()) $res else )*
190 { $catch_all }
191 }};
192}
193
194fn main() {
195 mat<|>ch_ast! {
196 match container {
197 ast::TraitDef(it) => {},
198 ast::ImplDef(it) => {},
199 _ => { continue },
200 }
201 }
202}
203"#,
204 expect![[r#"
205 match_ast
206 {
207 if let Some(it) = ast::TraitDef::cast(container.clone()){}
208 else if let Some(it) = ast::ImplDef::cast(container.clone()){}
209 else {
210 {
211 continue
212 }
213 }
214 }"#]],
215 );
216 }
217
218 #[test]
219 fn macro_expand_match_ast_inside_let_statement() {
220 check(
221 r#"
222macro_rules! match_ast {
223 (match $node:ident { $($tt:tt)* }) => { match_ast!(match ($node) { $($tt)* }) };
224 (match ($node:expr) {}) => {{}};
225}
226
227fn main() {
228 let p = f(|it| {
229 let res = mat<|>ch_ast! { match c {}};
230 Some(res)
231 })?;
232}
233"#,
234 expect![[r#"
235 match_ast
236 {}
237 "#]],
238 );
239 }
240
241 #[test]
242 fn macro_expand_inner_macro_fail_to_expand() {
243 check(
244 r#"
245macro_rules! bar {
246 (BAD) => {};
247}
248macro_rules! foo {
249 () => {bar!()};
250}
251
252fn main() {
253 let res = fo<|>o!();
254}
255"#,
256 expect![[r#"
257 foo
258 "#]],
259 );
260 }
261
262 #[test]
263 fn macro_expand_with_dollar_crate() {
264 check(
265 r#"
266#[macro_export]
267macro_rules! bar {
268 () => {0};
269}
270macro_rules! foo {
271 () => {$crate::bar!()};
272}
273
274fn main() {
275 let res = fo<|>o!();
276}
277"#,
278 expect![[r#"
279 foo
280 0 "#]],
281 );
282 }
283}