diff options
author | Aleksey Kladov <[email protected]> | 2019-11-27 18:32:33 +0000 |
---|---|---|
committer | Aleksey Kladov <[email protected]> | 2019-11-27 18:35:06 +0000 |
commit | 757e593b253b4df7e6fc8bf15a4d4f34c9d484c5 (patch) | |
tree | d972d3a7e6457efdb5e0c558a8350db1818d07ae /crates/ra_ide_api/src/expand_macro.rs | |
parent | d9a36a736bfb91578a36505e7237212959bb55fe (diff) |
rename ra_ide_api -> ra_ide
Diffstat (limited to 'crates/ra_ide_api/src/expand_macro.rs')
-rw-r--r-- | crates/ra_ide_api/src/expand_macro.rs | 295 |
1 files changed, 0 insertions, 295 deletions
diff --git a/crates/ra_ide_api/src/expand_macro.rs b/crates/ra_ide_api/src/expand_macro.rs deleted file mode 100644 index abc602244..000000000 --- a/crates/ra_ide_api/src/expand_macro.rs +++ /dev/null | |||
@@ -1,295 +0,0 @@ | |||
1 | //! This modules implements "expand macro" functionality in the IDE | ||
2 | |||
3 | use crate::{db::RootDatabase, FilePosition}; | ||
4 | use hir::db::AstDatabase; | ||
5 | use ra_db::SourceDatabase; | ||
6 | use rustc_hash::FxHashMap; | ||
7 | |||
8 | use ra_syntax::{ | ||
9 | algo::{find_node_at_offset, replace_descendants}, | ||
10 | ast::{self}, | ||
11 | AstNode, NodeOrToken, SyntaxKind, SyntaxNode, WalkEvent, T, | ||
12 | }; | ||
13 | |||
14 | pub struct ExpandedMacro { | ||
15 | pub name: String, | ||
16 | pub expansion: String, | ||
17 | } | ||
18 | |||
19 | pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<ExpandedMacro> { | ||
20 | let parse = db.parse(position.file_id); | ||
21 | let file = parse.tree(); | ||
22 | let name_ref = find_node_at_offset::<ast::NameRef>(file.syntax(), position.offset)?; | ||
23 | let mac = name_ref.syntax().ancestors().find_map(ast::MacroCall::cast)?; | ||
24 | |||
25 | let source = hir::Source::new(position.file_id.into(), mac.syntax()); | ||
26 | let expanded = expand_macro_recur(db, source, source.with_value(&mac))?; | ||
27 | |||
28 | // FIXME: | ||
29 | // macro expansion may lose all white space information | ||
30 | // But we hope someday we can use ra_fmt for that | ||
31 | let expansion = insert_whitespaces(expanded); | ||
32 | Some(ExpandedMacro { name: name_ref.text().to_string(), expansion }) | ||
33 | } | ||
34 | |||
35 | fn expand_macro_recur( | ||
36 | db: &RootDatabase, | ||
37 | source: hir::Source<&SyntaxNode>, | ||
38 | macro_call: hir::Source<&ast::MacroCall>, | ||
39 | ) -> Option<SyntaxNode> { | ||
40 | let analyzer = hir::SourceAnalyzer::new(db, source, None); | ||
41 | let expansion = analyzer.expand(db, macro_call)?; | ||
42 | let macro_file_id = expansion.file_id(); | ||
43 | let mut expanded: SyntaxNode = db.parse_or_expand(macro_file_id)?; | ||
44 | |||
45 | let children = expanded.descendants().filter_map(ast::MacroCall::cast); | ||
46 | let mut replaces = FxHashMap::default(); | ||
47 | |||
48 | for child in children.into_iter() { | ||
49 | let node = hir::Source::new(macro_file_id, &child); | ||
50 | if let Some(new_node) = expand_macro_recur(db, source, node) { | ||
51 | // Replace the whole node if it is root | ||
52 | // `replace_descendants` will not replace the parent node | ||
53 | // but `SyntaxNode::descendants include itself | ||
54 | if expanded == *child.syntax() { | ||
55 | expanded = new_node; | ||
56 | } else { | ||
57 | replaces.insert(child.syntax().clone().into(), new_node.into()); | ||
58 | } | ||
59 | } | ||
60 | } | ||
61 | |||
62 | Some(replace_descendants(&expanded, &replaces)) | ||
63 | } | ||
64 | |||
65 | // FIXME: It would also be cool to share logic here and in the mbe tests, | ||
66 | // which are pretty unreadable at the moment. | ||
67 | fn insert_whitespaces(syn: SyntaxNode) -> String { | ||
68 | use SyntaxKind::*; | ||
69 | |||
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 = |f: fn(SyntaxKind) -> bool, default| -> bool { | ||
90 | last.map(|it| f(it)).unwrap_or(default) | ||
91 | }; | ||
92 | |||
93 | res += &match token.kind() { | ||
94 | k @ _ if is_text(k) && is_next(|it| !it.is_punct(), true) => { | ||
95 | token.text().to_string() + " " | ||
96 | } | ||
97 | L_CURLY if is_next(|it| it != R_CURLY, true) => { | ||
98 | indent += 1; | ||
99 | let leading_space = if is_last(|it| is_text(it), false) { " " } else { "" }; | ||
100 | format!("{}{{\n{}", leading_space, " ".repeat(indent)) | ||
101 | } | ||
102 | R_CURLY if is_last(|it| it != L_CURLY, true) => { | ||
103 | indent = indent.checked_sub(1).unwrap_or(0); | ||
104 | format!("\n{}}}", " ".repeat(indent)) | ||
105 | } | ||
106 | R_CURLY => format!("}}\n{}", " ".repeat(indent)), | ||
107 | T![;] => format!(";\n{}", " ".repeat(indent)), | ||
108 | T![->] => " -> ".to_string(), | ||
109 | T![=] => " = ".to_string(), | ||
110 | T![=>] => " => ".to_string(), | ||
111 | _ => token.text().to_string(), | ||
112 | }; | ||
113 | |||
114 | last = Some(token.kind()); | ||
115 | } | ||
116 | |||
117 | return res; | ||
118 | |||
119 | fn is_text(k: SyntaxKind) -> bool { | ||
120 | k.is_keyword() || k.is_literal() || k == IDENT | ||
121 | } | ||
122 | } | ||
123 | |||
124 | #[cfg(test)] | ||
125 | mod tests { | ||
126 | use super::*; | ||
127 | use crate::mock_analysis::analysis_and_position; | ||
128 | use insta::assert_snapshot; | ||
129 | |||
130 | fn check_expand_macro(fixture: &str) -> ExpandedMacro { | ||
131 | let (analysis, pos) = analysis_and_position(fixture); | ||
132 | analysis.expand_macro(pos).unwrap().unwrap() | ||
133 | } | ||
134 | |||
135 | #[test] | ||
136 | fn macro_expand_recursive_expansion() { | ||
137 | let res = check_expand_macro( | ||
138 | r#" | ||
139 | //- /lib.rs | ||
140 | macro_rules! bar { | ||
141 | () => { fn b() {} } | ||
142 | } | ||
143 | macro_rules! foo { | ||
144 | () => { bar!(); } | ||
145 | } | ||
146 | macro_rules! baz { | ||
147 | () => { foo!(); } | ||
148 | } | ||
149 | f<|>oo!(); | ||
150 | "#, | ||
151 | ); | ||
152 | |||
153 | assert_eq!(res.name, "foo"); | ||
154 | assert_snapshot!(res.expansion, @r###" | ||
155 | fn b(){} | ||
156 | "###); | ||
157 | } | ||
158 | |||
159 | #[test] | ||
160 | fn macro_expand_multiple_lines() { | ||
161 | let res = check_expand_macro( | ||
162 | r#" | ||
163 | //- /lib.rs | ||
164 | macro_rules! foo { | ||
165 | () => { | ||
166 | fn some_thing() -> u32 { | ||
167 | let a = 0; | ||
168 | a + 10 | ||
169 | } | ||
170 | } | ||
171 | } | ||
172 | f<|>oo!(); | ||
173 | "#, | ||
174 | ); | ||
175 | |||
176 | assert_eq!(res.name, "foo"); | ||
177 | assert_snapshot!(res.expansion, @r###" | ||
178 | fn some_thing() -> u32 { | ||
179 | let a = 0; | ||
180 | a+10 | ||
181 | } | ||
182 | "###); | ||
183 | } | ||
184 | |||
185 | #[test] | ||
186 | fn macro_expand_match_ast() { | ||
187 | let res = check_expand_macro( | ||
188 | r#" | ||
189 | //- /lib.rs | ||
190 | macro_rules! match_ast { | ||
191 | (match $node:ident { $($tt:tt)* }) => { match_ast!(match ($node) { $($tt)* }) }; | ||
192 | |||
193 | (match ($node:expr) { | ||
194 | $( ast::$ast:ident($it:ident) => $res:block, )* | ||
195 | _ => $catch_all:expr $(,)? | ||
196 | }) => {{ | ||
197 | $( if let Some($it) = ast::$ast::cast($node.clone()) $res else )* | ||
198 | { $catch_all } | ||
199 | }}; | ||
200 | } | ||
201 | |||
202 | fn main() { | ||
203 | mat<|>ch_ast! { | ||
204 | match container { | ||
205 | ast::TraitDef(it) => {}, | ||
206 | ast::ImplBlock(it) => {}, | ||
207 | _ => { continue }, | ||
208 | } | ||
209 | } | ||
210 | } | ||
211 | "#, | ||
212 | ); | ||
213 | |||
214 | assert_eq!(res.name, "match_ast"); | ||
215 | assert_snapshot!(res.expansion, @r###" | ||
216 | { | ||
217 | if let Some(it) = ast::TraitDef::cast(container.clone()){} | ||
218 | else if let Some(it) = ast::ImplBlock::cast(container.clone()){} | ||
219 | else { | ||
220 | { | ||
221 | continue | ||
222 | } | ||
223 | } | ||
224 | } | ||
225 | "###); | ||
226 | } | ||
227 | |||
228 | #[test] | ||
229 | fn macro_expand_match_ast_inside_let_statement() { | ||
230 | let res = check_expand_macro( | ||
231 | r#" | ||
232 | //- /lib.rs | ||
233 | macro_rules! match_ast { | ||
234 | (match $node:ident { $($tt:tt)* }) => { match_ast!(match ($node) { $($tt)* }) }; | ||
235 | (match ($node:expr) {}) => {{}}; | ||
236 | } | ||
237 | |||
238 | fn main() { | ||
239 | let p = f(|it| { | ||
240 | let res = mat<|>ch_ast! { match c {}}; | ||
241 | Some(res) | ||
242 | })?; | ||
243 | } | ||
244 | "#, | ||
245 | ); | ||
246 | |||
247 | assert_eq!(res.name, "match_ast"); | ||
248 | assert_snapshot!(res.expansion, @r###"{}"###); | ||
249 | } | ||
250 | |||
251 | #[test] | ||
252 | fn macro_expand_inner_macro_fail_to_expand() { | ||
253 | let res = check_expand_macro( | ||
254 | r#" | ||
255 | //- /lib.rs | ||
256 | macro_rules! bar { | ||
257 | (BAD) => {}; | ||
258 | } | ||
259 | macro_rules! foo { | ||
260 | () => {bar!()}; | ||
261 | } | ||
262 | |||
263 | fn main() { | ||
264 | let res = fo<|>o!(); | ||
265 | } | ||
266 | "#, | ||
267 | ); | ||
268 | |||
269 | assert_eq!(res.name, "foo"); | ||
270 | assert_snapshot!(res.expansion, @r###"bar!()"###); | ||
271 | } | ||
272 | |||
273 | #[test] | ||
274 | fn macro_expand_with_dollar_crate() { | ||
275 | let res = check_expand_macro( | ||
276 | r#" | ||
277 | //- /lib.rs | ||
278 | #[macro_export] | ||
279 | macro_rules! bar { | ||
280 | () => {0}; | ||
281 | } | ||
282 | macro_rules! foo { | ||
283 | () => {$crate::bar!()}; | ||
284 | } | ||
285 | |||
286 | fn main() { | ||
287 | let res = fo<|>o!(); | ||
288 | } | ||
289 | "#, | ||
290 | ); | ||
291 | |||
292 | assert_eq!(res.name, "foo"); | ||
293 | assert_snapshot!(res.expansion, @r###"0"###); | ||
294 | } | ||
295 | } | ||