]> git.proxmox.com Git - rustc.git/blame - src/tools/rust-analyzer/crates/ide/src/expand_macro.rs
New upstream version 1.72.1+dfsg1
[rustc.git] / src / tools / rust-analyzer / crates / ide / src / expand_macro.rs
CommitLineData
064997fb
FG
1use hir::Semantics;
2use ide_db::{
3 base_db::FileId, helpers::pick_best_token,
4 syntax_helpers::insert_whitespace_into_node::insert_ws_into, RootDatabase,
5};
6use syntax::{ast, ted, AstNode, NodeOrToken, SyntaxKind, SyntaxNode, T};
7
8use crate::FilePosition;
9
10pub 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[]
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
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
104fn 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
112fn 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
120fn 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
143fn 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"))]
150fn _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")))]
162fn _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)]
217mod 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#"
234macro_rules! bar {
235 ($i:tt) => { $i as _ }
236}
237fn 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#"
251macro_rules! bar {
252 ($i:tt) => { for _ in 0..$i {} }
253}
254fn 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#"
268macro_rules! bar {
269 () => { fn b() {} }
270}
271macro_rules! foo {
272 () => { bar!(); }
273}
274macro_rules! baz {
275 () => { foo!(); }
276}
277f$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#"
289macro_rules! foo {
290 () => {
291 fn some_thing() -> u32 {
292 let a = 0;
293 a + 10
294 }
295 }
296}
297f$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#"
312macro_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
323fn 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#"
351macro_rules! match_ast {
352 (match $node:ident { $($tt:tt)* }) => { match_ast!(match ($node) { $($tt)* }) };
353 (match ($node:expr) {}) => {{}};
354}
355
356fn 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#"
373macro_rules! foo {
374 ($t:tt) => {{
375 macro_rules! bar {
376 () => {
377 $t
378 }
379 }
380 bar!()
381 }};
382}
383
384fn 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#"
405macro_rules! bar {
406 (BAD) => {};
407}
408macro_rules! foo {
409 () => {bar!()};
410}
411
412fn 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]
427macro_rules! bar {
428 () => {0};
429}
430macro_rules! foo {
431 () => {$crate::bar!()};
432}
433
434fn 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#"
448macro_rules! foo {
449 () => {fn f<T>(_: &dyn ::std::marker::Copy) {}};
450}
451
452fn 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)]
471struct 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)]
497struct 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)]
512struct 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)]
523struct 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}