3 base_db
::FileId
, helpers
::pick_best_token
,
4 syntax_helpers
::insert_whitespace_into_node
::insert_ws_into
, RootDatabase
,
6 use syntax
::{ast, ted, AstNode, NodeOrToken, SyntaxKind, SyntaxNode, T}
;
8 use crate::FilePosition
;
10 pub struct ExpandedMacro
{
12 pub expansion
: String
,
15 // Feature: Expand Macro Recursively
17 // Shows the full macro expansion of the macro at current cursor.
20 // | Editor | Action Name
22 // | VS Code | **Rust Analyzer: Expand macro recursively**
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
);
30 let tok
= pick_best_token(file
.syntax().token_at_offset(position
.offset
), |kind
| match kind
{
31 SyntaxKind
::IDENT
=> 1,
35 // due to how Rust Analyzer works internally, we need to special case derive attributes,
36 // otherwise they might not get found, e.g. here with the cursor at $0 `#[attr]` would expand:
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
) {
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
)?
;
56 .token_trees_and_tokens()
57 .filter_map(NodeOrToken
::into_token
)
58 .take_while(|it
| it
!= &token
)
59 .filter(|it
| it
.kind() == T
![,])
62 format(db
, SyntaxKind
::MACRO_ITEMS
, position
.file_id
, expansions
.get(idx
).cloned()?
);
63 Some(ExpandedMacro { name, expansion }
)
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()?
;
76 if let Some(item
) = ast
::Item
::cast(node
.clone()) {
77 if let Some(def
) = sema
.resolve_attr_macro_call(&item
) {
79 def
.name(db
).to_string(),
80 expand_attr_macro_recur(&sema
, &item
)?
,
81 SyntaxKind
::MACRO_ITEMS
,
85 if let Some(mac
) = ast
::MacroCall
::cast(node
) {
87 mac
.path()?
.segment()?
.name_ref()?
.to_string(),
88 expand_macro_recur(&sema
, &mac
)?
,
89 mac
.syntax().parent().map(|it
| it
.kind()).unwrap_or(SyntaxKind
::MACRO_ITEMS
),
95 // macro expansion may lose all white space information
96 // But we hope someday we can use ra_fmt for that
97 let expansion
= format(db
, kind
, position
.file_id
, expanded
);
99 Some(ExpandedMacro { name, expansion }
)
102 fn expand_macro_recur(
103 sema
: &Semantics
<'_
, RootDatabase
>,
104 macro_call
: &ast
::MacroCall
,
105 ) -> Option
<SyntaxNode
> {
106 let expanded
= sema
.expand(macro_call
)?
.clone_for_update();
107 expand(sema
, expanded
, ast
::MacroCall
::cast
, expand_macro_recur
)
110 fn expand_attr_macro_recur(
111 sema
: &Semantics
<'_
, RootDatabase
>,
113 ) -> Option
<SyntaxNode
> {
114 let expanded
= sema
.expand_attr_macro(item
)?
.clone_for_update();
115 expand(sema
, expanded
, ast
::Item
::cast
, expand_attr_macro_recur
)
118 fn expand
<T
: AstNode
>(
119 sema
: &Semantics
<'_
, RootDatabase
>,
120 expanded
: SyntaxNode
,
121 f
: impl FnMut(SyntaxNode
) -> Option
<T
>,
122 exp
: impl Fn(&Semantics
<'_
, RootDatabase
>, &T
) -> Option
<SyntaxNode
>,
123 ) -> Option
<SyntaxNode
> {
124 let children
= expanded
.descendants().filter_map(f
);
125 let mut replacements
= Vec
::new();
127 for child
in children
{
128 if let Some(new_node
) = exp(sema
, &child
) {
129 // check if the whole original syntax is replaced
130 if expanded
== *child
.syntax() {
131 return Some(new_node
);
133 replacements
.push((child
, new_node
));
137 replacements
.into_iter().rev().for_each(|(old
, new
)| ted
::replace(old
.syntax(), new
));
141 fn format(db
: &RootDatabase
, kind
: SyntaxKind
, file_id
: FileId
, expanded
: SyntaxNode
) -> String
{
142 let expansion
= insert_ws_into(expanded
).to_string();
144 _format(db
, kind
, file_id
, &expansion
).unwrap_or(expansion
)
147 #[cfg(any(test, target_arch = "wasm32", target_os = "emscripten"))]
153 ) -> Option
<String
> {
157 #[cfg(not(any(test, target_arch = "wasm32", target_os = "emscripten")))]
163 ) -> Option
<String
> {
164 use ide_db
::base_db
::{FileLoader, SourceDatabase}
;
165 // hack until we get hygiene working (same character amount to preserve formatting as much as possible)
166 const DOLLAR_CRATE_REPLACE
: &str = &"__r_a_";
167 let expansion
= expansion
.replace("$crate", DOLLAR_CRATE_REPLACE
);
168 let (prefix
, suffix
) = match kind
{
169 SyntaxKind
::MACRO_PAT
=> ("fn __(", ": u32);"),
170 SyntaxKind
::MACRO_EXPR
| SyntaxKind
::MACRO_STMTS
=> ("fn __() {", "}"),
171 SyntaxKind
::MACRO_TYPE
=> ("type __ =", ";"),
174 let expansion
= format
!("{prefix}{expansion}{suffix}");
176 let &crate_id
= db
.relevant_crates(file_id
).iter().next()?
;
177 let edition
= db
.crate_graph()[crate_id
].edition
;
179 let mut cmd
= std
::process
::Command
::new(toolchain
::rustfmt());
180 cmd
.arg("--edition");
181 cmd
.arg(edition
.to_string());
183 let mut rustfmt
= cmd
184 .stdin(std
::process
::Stdio
::piped())
185 .stdout(std
::process
::Stdio
::piped())
186 .stderr(std
::process
::Stdio
::piped())
190 std
::io
::Write
::write_all(&mut rustfmt
.stdin
.as_mut()?
, expansion
.as_bytes()).ok()?
;
192 let output
= rustfmt
.wait_with_output().ok()?
;
193 let captured_stdout
= String
::from_utf8(output
.stdout
).ok()?
;
195 if output
.status
.success() && !captured_stdout
.trim().is_empty() {
196 let output
= captured_stdout
.replace(DOLLAR_CRATE_REPLACE
, "$crate");
197 let output
= output
.trim().strip_prefix(prefix
)?
;
198 let output
= match kind
{
199 SyntaxKind
::MACRO_PAT
=> {
200 output
.strip_suffix(suffix
).or_else(|| output
.strip_suffix(": u32,\n);"))?
202 _
=> output
.strip_suffix(suffix
)?
,
204 let trim_indent
= stdx
::trim_indent(output
);
205 tracing
::debug
!("expand_macro: formatting succeeded");
214 use expect_test
::{expect, Expect}
;
219 fn check(ra_fixture
: &str, expect
: Expect
) {
220 let (analysis
, pos
) = fixture
::position(ra_fixture
);
221 let expansion
= analysis
.expand_macro(pos
).unwrap().unwrap();
222 let actual
= format
!("{}\n{}", expansion
.name
, expansion
.expansion
);
223 expect
.assert_eq(&actual
);
227 fn macro_expand_as_keyword() {
231 ($i:tt) => { $i as _ }
234 let x: u64 = ba$0r!(5i64);
244 fn macro_expand_underscore() {
248 ($i:tt) => { for _ in 0..$i {} }
256 for _ in 0..42{}"#]],
261 fn macro_expand_recursive_expansion() {
283 fn macro_expand_multiple_lines() {
288 fn some_thing() -> u32 {
298 fn some_thing() -> u32 {
306 fn macro_expand_match_ast() {
309 macro_rules! match_ast {
310 (match $node:ident { $($tt:tt)* }) => { match_ast!(match ($node) { $($tt)* }) };
311 (match ($node:expr) {
312 $( ast::$ast:ident($it:ident) => $res:block, )*
313 _ => $catch_all:expr $(,)?
315 $( if let Some($it) = ast::$ast::cast($node.clone()) $res else )*
323 ast::TraitDef(it) => {},
324 ast::ImplDef(it) => {},
333 if let Some(it) = ast::TraitDef::cast(container.clone()){}
334 else if let Some(it) = ast::ImplDef::cast(container.clone()){}
345 fn macro_expand_match_ast_inside_let_statement() {
348 macro_rules! match_ast {
349 (match $node:ident { $($tt:tt)* }) => { match_ast!(match ($node) { $($tt)* }) };
350 (match ($node:expr) {}) => {{}};
355 let res = mat$0ch_ast! { match c {}};
367 fn macro_expand_inner_macro_rules() {
399 fn macro_expand_inner_macro_fail_to_expand() {
420 fn macro_expand_with_dollar_crate() {
428 () => {$crate::bar!()};
442 fn macro_expand_with_dyn_absolute_path() {
446 () => {fn f<T>(_: &dyn ::std::marker::Copy) {}};
455 fn f<T>(_: &dyn ::std::marker::Copy){}"#]],
460 fn macro_expand_derive() {
463 //- proc_macros: identity
464 //- minicore: clone, derive
466 #[proc_macros::identity]
472 impl < >core::clone::Clone for Foo< >{}
478 fn macro_expand_derive2() {
481 //- minicore: copy, clone, derive
489 impl < >core::marker::Copy for Foo< >{}
495 fn macro_expand_derive_multi() {
498 //- minicore: copy, clone, derive
500 #[derive(Cop$0y, Clone)]
505 impl < >core::marker::Copy for Foo< >{}
510 //- minicore: copy, clone, derive
512 #[derive(Copy, Cl$0one)]
517 impl < >core::clone::Clone for Foo< >{}