]>
Commit | Line | Data |
---|---|---|
064997fb FG |
1 | use hir::Semantics; |
2 | use ide_db::{ | |
3 | base_db::FileId, helpers::pick_best_token, | |
4 | syntax_helpers::insert_whitespace_into_node::insert_ws_into, RootDatabase, | |
5 | }; | |
6 | use syntax::{ast, ted, AstNode, NodeOrToken, SyntaxKind, SyntaxNode, T}; | |
7 | ||
8 | use crate::FilePosition; | |
9 | ||
10 | pub struct ExpandedMacro { | |
11 | pub name: String, | |
12 | pub expansion: String, | |
13 | } | |
14 | ||
15 | // Feature: Expand Macro Recursively | |
16 | // | |
17 | // Shows the full macro expansion of the macro at current cursor. | |
18 | // | |
19 | // |=== | |
20 | // | Editor | Action Name | |
21 | // | |
f2b60f7d | 22 | // | VS Code | **rust-analyzer: Expand macro recursively** |
064997fb FG |
23 | // |=== |
24 | // | |
25 | // image::https://user-images.githubusercontent.com/48062697/113020648-b3973180-917a-11eb-84a9-ecb921293dc5.gif[] | |
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 | ||
30 | let tok = pick_best_token(file.syntax().token_at_offset(position.offset), |kind| match kind { | |
31 | SyntaxKind::IDENT => 1, | |
32 | _ => 0, | |
33 | })?; | |
34 | ||
f2b60f7d | 35 | // due to how rust-analyzer works internally, we need to special case derive attributes, |
064997fb FG |
36 | // otherwise they might not get found, e.g. here with the cursor at $0 `#[attr]` would expand: |
37 | // ``` | |
38 | // #[attr] | |
39 | // #[derive($0Foo)] | |
40 | // struct Bar; | |
41 | // ``` | |
42 | ||
43 | let derive = sema.descend_into_macros(tok.clone()).into_iter().find_map(|descended| { | |
44 | let hir_file = sema.hir_file_for(&descended.parent()?); | |
45 | if !hir_file.is_derive_attr_pseudo_expansion(db) { | |
46 | return None; | |
47 | } | |
48 | ||
49 | let name = descended.parent_ancestors().filter_map(ast::Path::cast).last()?.to_string(); | |
50 | // up map out of the #[derive] expansion | |
51 | let token = hir::InFile::new(hir_file, descended).upmap(db)?.value; | |
52 | let attr = token.parent_ancestors().find_map(ast::Attr::cast)?; | |
53 | let expansions = sema.expand_derive_macro(&attr)?; | |
54 | let idx = attr | |
55 | .token_tree()? | |
56 | .token_trees_and_tokens() | |
57 | .filter_map(NodeOrToken::into_token) | |
58 | .take_while(|it| it != &token) | |
59 | .filter(|it| it.kind() == T![,]) | |
60 | .count(); | |
61 | let expansion = | |
62 | format(db, SyntaxKind::MACRO_ITEMS, position.file_id, expansions.get(idx).cloned()?); | |
63 | Some(ExpandedMacro { name, expansion }) | |
64 | }); | |
65 | ||
66 | if derive.is_some() { | |
67 | return derive; | |
68 | } | |
69 | ||
70 | // FIXME: Intermix attribute and bang! expansions | |
71 | // currently we only recursively expand one of the two types | |
72 | let mut anc = tok.parent_ancestors(); | |
73 | let (name, expanded, kind) = loop { | |
74 | let node = anc.next()?; | |
75 | ||
76 | if let Some(item) = ast::Item::cast(node.clone()) { | |
77 | if let Some(def) = sema.resolve_attr_macro_call(&item) { | |
78 | break ( | |
fe692bf9 | 79 | def.name(db).display(db).to_string(), |
064997fb FG |
80 | expand_attr_macro_recur(&sema, &item)?, |
81 | SyntaxKind::MACRO_ITEMS, | |
82 | ); | |
83 | } | |
84 | } | |
85 | if let Some(mac) = ast::MacroCall::cast(node) { | |
fe692bf9 FG |
86 | let mut name = mac.path()?.segment()?.name_ref()?.to_string(); |
87 | name.push('!'); | |
064997fb | 88 | break ( |
fe692bf9 | 89 | name, |
064997fb FG |
90 | expand_macro_recur(&sema, &mac)?, |
91 | mac.syntax().parent().map(|it| it.kind()).unwrap_or(SyntaxKind::MACRO_ITEMS), | |
92 | ); | |
93 | } | |
94 | }; | |
95 | ||
96 | // FIXME: | |
97 | // macro expansion may lose all white space information | |
98 | // But we hope someday we can use ra_fmt for that | |
99 | let expansion = format(db, kind, position.file_id, expanded); | |
100 | ||
101 | Some(ExpandedMacro { name, expansion }) | |
102 | } | |
103 | ||
104 | fn expand_macro_recur( | |
105 | sema: &Semantics<'_, RootDatabase>, | |
106 | macro_call: &ast::MacroCall, | |
107 | ) -> Option<SyntaxNode> { | |
108 | let expanded = sema.expand(macro_call)?.clone_for_update(); | |
109 | expand(sema, expanded, ast::MacroCall::cast, expand_macro_recur) | |
110 | } | |
111 | ||
112 | fn expand_attr_macro_recur( | |
113 | sema: &Semantics<'_, RootDatabase>, | |
114 | item: &ast::Item, | |
115 | ) -> Option<SyntaxNode> { | |
116 | let expanded = sema.expand_attr_macro(item)?.clone_for_update(); | |
117 | expand(sema, expanded, ast::Item::cast, expand_attr_macro_recur) | |
118 | } | |
119 | ||
120 | fn expand<T: AstNode>( | |
121 | sema: &Semantics<'_, RootDatabase>, | |
122 | expanded: SyntaxNode, | |
123 | f: impl FnMut(SyntaxNode) -> Option<T>, | |
124 | exp: impl Fn(&Semantics<'_, RootDatabase>, &T) -> Option<SyntaxNode>, | |
125 | ) -> Option<SyntaxNode> { | |
126 | let children = expanded.descendants().filter_map(f); | |
127 | let mut replacements = Vec::new(); | |
128 | ||
129 | for child in children { | |
130 | if let Some(new_node) = exp(sema, &child) { | |
131 | // check if the whole original syntax is replaced | |
132 | if expanded == *child.syntax() { | |
133 | return Some(new_node); | |
134 | } | |
135 | replacements.push((child, new_node)); | |
136 | } | |
137 | } | |
138 | ||
139 | replacements.into_iter().rev().for_each(|(old, new)| ted::replace(old.syntax(), new)); | |
140 | Some(expanded) | |
141 | } | |
142 | ||
143 | fn format(db: &RootDatabase, kind: SyntaxKind, file_id: FileId, expanded: SyntaxNode) -> String { | |
144 | let expansion = insert_ws_into(expanded).to_string(); | |
145 | ||
146 | _format(db, kind, file_id, &expansion).unwrap_or(expansion) | |
147 | } | |
148 | ||
149 | #[cfg(any(test, target_arch = "wasm32", target_os = "emscripten"))] | |
150 | fn _format( | |
151 | _db: &RootDatabase, | |
152 | _kind: SyntaxKind, | |
153 | _file_id: FileId, | |
fe692bf9 | 154 | expansion: &str, |
064997fb | 155 | ) -> Option<String> { |
fe692bf9 FG |
156 | // remove trailing spaces for test |
157 | use itertools::Itertools; | |
158 | Some(expansion.lines().map(|x| x.trim_end()).join("\n")) | |
064997fb FG |
159 | } |
160 | ||
161 | #[cfg(not(any(test, target_arch = "wasm32", target_os = "emscripten")))] | |
162 | fn _format( | |
163 | db: &RootDatabase, | |
164 | kind: SyntaxKind, | |
165 | file_id: FileId, | |
166 | expansion: &str, | |
167 | ) -> Option<String> { | |
168 | use ide_db::base_db::{FileLoader, SourceDatabase}; | |
169 | // hack until we get hygiene working (same character amount to preserve formatting as much as possible) | |
9c376795 | 170 | const DOLLAR_CRATE_REPLACE: &str = "__r_a_"; |
064997fb FG |
171 | let expansion = expansion.replace("$crate", DOLLAR_CRATE_REPLACE); |
172 | let (prefix, suffix) = match kind { | |
173 | SyntaxKind::MACRO_PAT => ("fn __(", ": u32);"), | |
174 | SyntaxKind::MACRO_EXPR | SyntaxKind::MACRO_STMTS => ("fn __() {", "}"), | |
175 | SyntaxKind::MACRO_TYPE => ("type __ =", ";"), | |
176 | _ => ("", ""), | |
177 | }; | |
178 | let expansion = format!("{prefix}{expansion}{suffix}"); | |
179 | ||
180 | let &crate_id = db.relevant_crates(file_id).iter().next()?; | |
181 | let edition = db.crate_graph()[crate_id].edition; | |
182 | ||
183 | let mut cmd = std::process::Command::new(toolchain::rustfmt()); | |
184 | cmd.arg("--edition"); | |
185 | cmd.arg(edition.to_string()); | |
186 | ||
187 | let mut rustfmt = cmd | |
188 | .stdin(std::process::Stdio::piped()) | |
189 | .stdout(std::process::Stdio::piped()) | |
190 | .stderr(std::process::Stdio::piped()) | |
191 | .spawn() | |
192 | .ok()?; | |
193 | ||
194 | std::io::Write::write_all(&mut rustfmt.stdin.as_mut()?, expansion.as_bytes()).ok()?; | |
195 | ||
196 | let output = rustfmt.wait_with_output().ok()?; | |
197 | let captured_stdout = String::from_utf8(output.stdout).ok()?; | |
198 | ||
199 | if output.status.success() && !captured_stdout.trim().is_empty() { | |
200 | let output = captured_stdout.replace(DOLLAR_CRATE_REPLACE, "$crate"); | |
201 | let output = output.trim().strip_prefix(prefix)?; | |
202 | let output = match kind { | |
203 | SyntaxKind::MACRO_PAT => { | |
204 | output.strip_suffix(suffix).or_else(|| output.strip_suffix(": u32,\n);"))? | |
205 | } | |
206 | _ => output.strip_suffix(suffix)?, | |
207 | }; | |
208 | let trim_indent = stdx::trim_indent(output); | |
209 | tracing::debug!("expand_macro: formatting succeeded"); | |
210 | Some(trim_indent) | |
211 | } else { | |
212 | None | |
213 | } | |
214 | } | |
215 | ||
216 | #[cfg(test)] | |
217 | mod tests { | |
218 | use expect_test::{expect, Expect}; | |
219 | ||
220 | use crate::fixture; | |
221 | ||
222 | #[track_caller] | |
223 | fn check(ra_fixture: &str, expect: Expect) { | |
224 | let (analysis, pos) = fixture::position(ra_fixture); | |
225 | let expansion = analysis.expand_macro(pos).unwrap().unwrap(); | |
226 | let actual = format!("{}\n{}", expansion.name, expansion.expansion); | |
227 | expect.assert_eq(&actual); | |
228 | } | |
229 | ||
230 | #[test] | |
231 | fn macro_expand_as_keyword() { | |
232 | check( | |
233 | r#" | |
234 | macro_rules! bar { | |
235 | ($i:tt) => { $i as _ } | |
236 | } | |
237 | fn main() { | |
238 | let x: u64 = ba$0r!(5i64); | |
239 | } | |
240 | "#, | |
241 | expect![[r#" | |
fe692bf9 | 242 | bar! |
064997fb FG |
243 | 5i64 as _"#]], |
244 | ); | |
245 | } | |
246 | ||
247 | #[test] | |
248 | fn macro_expand_underscore() { | |
249 | check( | |
250 | r#" | |
251 | macro_rules! bar { | |
252 | ($i:tt) => { for _ in 0..$i {} } | |
253 | } | |
254 | fn main() { | |
255 | ba$0r!(42); | |
256 | } | |
257 | "#, | |
258 | expect![[r#" | |
fe692bf9 | 259 | bar! |
064997fb FG |
260 | for _ in 0..42{}"#]], |
261 | ); | |
262 | } | |
263 | ||
264 | #[test] | |
265 | fn macro_expand_recursive_expansion() { | |
266 | check( | |
267 | r#" | |
268 | macro_rules! bar { | |
269 | () => { fn b() {} } | |
270 | } | |
271 | macro_rules! foo { | |
272 | () => { bar!(); } | |
273 | } | |
274 | macro_rules! baz { | |
275 | () => { foo!(); } | |
276 | } | |
277 | f$0oo!(); | |
278 | "#, | |
279 | expect![[r#" | |
fe692bf9 FG |
280 | foo! |
281 | fn b(){}"#]], | |
064997fb FG |
282 | ); |
283 | } | |
284 | ||
285 | #[test] | |
286 | fn macro_expand_multiple_lines() { | |
287 | check( | |
288 | r#" | |
289 | macro_rules! foo { | |
290 | () => { | |
291 | fn some_thing() -> u32 { | |
292 | let a = 0; | |
293 | a + 10 | |
294 | } | |
295 | } | |
296 | } | |
297 | f$0oo!(); | |
298 | "#, | |
299 | expect![[r#" | |
fe692bf9 | 300 | foo! |
064997fb FG |
301 | fn some_thing() -> u32 { |
302 | let a = 0; | |
303 | a+10 | |
304 | }"#]], | |
305 | ); | |
306 | } | |
307 | ||
308 | #[test] | |
309 | fn macro_expand_match_ast() { | |
310 | check( | |
311 | r#" | |
312 | macro_rules! match_ast { | |
313 | (match $node:ident { $($tt:tt)* }) => { match_ast!(match ($node) { $($tt)* }) }; | |
314 | (match ($node:expr) { | |
315 | $( ast::$ast:ident($it:ident) => $res:block, )* | |
316 | _ => $catch_all:expr $(,)? | |
317 | }) => {{ | |
318 | $( if let Some($it) = ast::$ast::cast($node.clone()) $res else )* | |
319 | { $catch_all } | |
320 | }}; | |
321 | } | |
322 | ||
323 | fn main() { | |
324 | mat$0ch_ast! { | |
325 | match container { | |
326 | ast::TraitDef(it) => {}, | |
327 | ast::ImplDef(it) => {}, | |
328 | _ => { continue }, | |
329 | } | |
330 | } | |
331 | } | |
332 | "#, | |
333 | expect![[r#" | |
fe692bf9 FG |
334 | match_ast! |
335 | { | |
336 | if let Some(it) = ast::TraitDef::cast(container.clone()){} | |
337 | else if let Some(it) = ast::ImplDef::cast(container.clone()){} | |
338 | else { | |
339 | { | |
340 | continue | |
341 | } | |
342 | } | |
343 | }"#]], | |
064997fb FG |
344 | ); |
345 | } | |
346 | ||
347 | #[test] | |
348 | fn macro_expand_match_ast_inside_let_statement() { | |
349 | check( | |
350 | r#" | |
351 | macro_rules! match_ast { | |
352 | (match $node:ident { $($tt:tt)* }) => { match_ast!(match ($node) { $($tt)* }) }; | |
353 | (match ($node:expr) {}) => {{}}; | |
354 | } | |
355 | ||
356 | fn main() { | |
357 | let p = f(|it| { | |
358 | let res = mat$0ch_ast! { match c {}}; | |
359 | Some(res) | |
360 | })?; | |
361 | } | |
362 | "#, | |
363 | expect![[r#" | |
fe692bf9 | 364 | match_ast! |
064997fb FG |
365 | {}"#]], |
366 | ); | |
367 | } | |
368 | ||
369 | #[test] | |
370 | fn macro_expand_inner_macro_rules() { | |
371 | check( | |
372 | r#" | |
373 | macro_rules! foo { | |
374 | ($t:tt) => {{ | |
375 | macro_rules! bar { | |
376 | () => { | |
377 | $t | |
378 | } | |
379 | } | |
380 | bar!() | |
381 | }}; | |
382 | } | |
383 | ||
384 | fn main() { | |
385 | foo$0!(42); | |
386 | } | |
387 | "#, | |
388 | expect![[r#" | |
fe692bf9 | 389 | foo! |
064997fb FG |
390 | { |
391 | macro_rules! bar { | |
392 | () => { | |
393 | 42 | |
394 | } | |
395 | } | |
396 | 42 | |
397 | }"#]], | |
398 | ); | |
399 | } | |
400 | ||
401 | #[test] | |
402 | fn macro_expand_inner_macro_fail_to_expand() { | |
403 | check( | |
404 | r#" | |
405 | macro_rules! bar { | |
406 | (BAD) => {}; | |
407 | } | |
408 | macro_rules! foo { | |
409 | () => {bar!()}; | |
410 | } | |
411 | ||
412 | fn main() { | |
413 | let res = fo$0o!(); | |
414 | } | |
415 | "#, | |
416 | expect![[r#" | |
fe692bf9 | 417 | foo! |
064997fb FG |
418 | "#]], |
419 | ); | |
420 | } | |
421 | ||
422 | #[test] | |
423 | fn macro_expand_with_dollar_crate() { | |
424 | check( | |
425 | r#" | |
426 | #[macro_export] | |
427 | macro_rules! bar { | |
428 | () => {0}; | |
429 | } | |
430 | macro_rules! foo { | |
431 | () => {$crate::bar!()}; | |
432 | } | |
433 | ||
434 | fn main() { | |
435 | let res = fo$0o!(); | |
436 | } | |
437 | "#, | |
438 | expect![[r#" | |
fe692bf9 | 439 | foo! |
064997fb FG |
440 | 0"#]], |
441 | ); | |
442 | } | |
443 | ||
444 | #[test] | |
445 | fn macro_expand_with_dyn_absolute_path() { | |
446 | check( | |
447 | r#" | |
448 | macro_rules! foo { | |
449 | () => {fn f<T>(_: &dyn ::std::marker::Copy) {}}; | |
450 | } | |
451 | ||
452 | fn main() { | |
453 | let res = fo$0o!(); | |
454 | } | |
455 | "#, | |
456 | expect![[r#" | |
fe692bf9 | 457 | foo! |
064997fb FG |
458 | fn f<T>(_: &dyn ::std::marker::Copy){}"#]], |
459 | ); | |
460 | } | |
461 | ||
462 | #[test] | |
463 | fn macro_expand_derive() { | |
464 | check( | |
465 | r#" | |
466 | //- proc_macros: identity | |
467 | //- minicore: clone, derive | |
468 | ||
469 | #[proc_macros::identity] | |
470 | #[derive(C$0lone)] | |
471 | struct Foo {} | |
472 | "#, | |
473 | expect![[r#" | |
474 | Clone | |
fe692bf9 FG |
475 | impl < >core::clone::Clone for Foo< >where { |
476 | fn clone(&self) -> Self { | |
477 | match self { | |
478 | Foo{} | |
479 | => Foo{} | |
480 | , | |
481 | ||
482 | } | |
483 | } | |
484 | ||
485 | }"#]], | |
064997fb FG |
486 | ); |
487 | } | |
488 | ||
489 | #[test] | |
490 | fn macro_expand_derive2() { | |
491 | check( | |
492 | r#" | |
493 | //- minicore: copy, clone, derive | |
494 | ||
495 | #[derive(Cop$0y)] | |
496 | #[derive(Clone)] | |
497 | struct Foo {} | |
498 | "#, | |
499 | expect![[r#" | |
500 | Copy | |
fe692bf9 | 501 | impl < >core::marker::Copy for Foo< >where{}"#]], |
064997fb FG |
502 | ); |
503 | } | |
504 | ||
505 | #[test] | |
506 | fn macro_expand_derive_multi() { | |
507 | check( | |
508 | r#" | |
509 | //- minicore: copy, clone, derive | |
510 | ||
511 | #[derive(Cop$0y, Clone)] | |
512 | struct Foo {} | |
513 | "#, | |
514 | expect![[r#" | |
515 | Copy | |
fe692bf9 | 516 | impl < >core::marker::Copy for Foo< >where{}"#]], |
064997fb FG |
517 | ); |
518 | check( | |
519 | r#" | |
520 | //- minicore: copy, clone, derive | |
521 | ||
522 | #[derive(Copy, Cl$0one)] | |
523 | struct Foo {} | |
524 | "#, | |
525 | expect![[r#" | |
526 | Clone | |
fe692bf9 FG |
527 | impl < >core::clone::Clone for Foo< >where { |
528 | fn clone(&self) -> Self { | |
529 | match self { | |
530 | Foo{} | |
531 | => Foo{} | |
532 | , | |
533 | ||
534 | } | |
535 | } | |
536 | ||
537 | }"#]], | |
064997fb FG |
538 | ); |
539 | } | |
540 | } |