diff options
author | Igor Aleksanov <[email protected]> | 2020-08-14 05:34:07 +0100 |
---|---|---|
committer | Igor Aleksanov <[email protected]> | 2020-08-14 05:34:07 +0100 |
commit | c26c911ec1e6c2ad1dcb7d155a6a1d528839ad1a (patch) | |
tree | 7cff36c38234be0afb65273146d8247083a5cfeb /crates/ide/src/expand_macro.rs | |
parent | 3c018bf84de5c693b5ee1c6bec0fed3b201c2060 (diff) | |
parent | f1f73649a686dc6e6449afc35e0fa6fed00e225d (diff) |
Merge branch 'master' into add-disable-diagnostics
Diffstat (limited to 'crates/ide/src/expand_macro.rs')
-rw-r--r-- | crates/ide/src/expand_macro.rs | 283 |
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 @@ | |||
1 | use hir::Semantics; | ||
2 | use ide_db::RootDatabase; | ||
3 | use syntax::{ | ||
4 | algo::{find_node_at_offset, SyntaxRewriter}, | ||
5 | ast, AstNode, NodeOrToken, SyntaxKind, | ||
6 | SyntaxKind::*, | ||
7 | SyntaxNode, 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 | 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 | 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 | |||
41 | fn 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. | ||
69 | fn 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)] | ||
122 | mod 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#" | ||
138 | macro_rules! bar { | ||
139 | () => { fn b() {} } | ||
140 | } | ||
141 | macro_rules! foo { | ||
142 | () => { bar!(); } | ||
143 | } | ||
144 | macro_rules! baz { | ||
145 | () => { foo!(); } | ||
146 | } | ||
147 | f<|>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#" | ||
160 | macro_rules! foo { | ||
161 | () => { | ||
162 | fn some_thing() -> u32 { | ||
163 | let a = 0; | ||
164 | a + 10 | ||
165 | } | ||
166 | } | ||
167 | } | ||
168 | f<|>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#" | ||
183 | macro_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 | |||
194 | fn 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#" | ||
222 | macro_rules! match_ast { | ||
223 | (match $node:ident { $($tt:tt)* }) => { match_ast!(match ($node) { $($tt)* }) }; | ||
224 | (match ($node:expr) {}) => {{}}; | ||
225 | } | ||
226 | |||
227 | fn 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#" | ||
245 | macro_rules! bar { | ||
246 | (BAD) => {}; | ||
247 | } | ||
248 | macro_rules! foo { | ||
249 | () => {bar!()}; | ||
250 | } | ||
251 | |||
252 | fn 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] | ||
267 | macro_rules! bar { | ||
268 | () => {0}; | ||
269 | } | ||
270 | macro_rules! foo { | ||
271 | () => {$crate::bar!()}; | ||
272 | } | ||
273 | |||
274 | fn main() { | ||
275 | let res = fo<|>o!(); | ||
276 | } | ||
277 | "#, | ||
278 | expect![[r#" | ||
279 | foo | ||
280 | 0 "#]], | ||
281 | ); | ||
282 | } | ||
283 | } | ||