Skip to content

Commit 9c675d6

Browse files
committed
Auto merge of #12014 - Veykril:expmacfmt, r=Veykril
feat: Attempt to format expand_macro output with rustfmt if possible Fixes #10548
2 parents 53afd2a + 3de9a42 commit 9c675d6

File tree

4 files changed

+98
-19
lines changed

4 files changed

+98
-19
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/ide/Cargo.toml

+3
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ ide_completion = { path = "../ide_completion", version = "0.0.0" }
3737
# something from some `hir_xxx` subpackage, reexport the API via `hir`.
3838
hir = { path = "../hir", version = "0.0.0" }
3939

40+
[target.'cfg(not(any(target_arch = "wasm32", target_os = "emscripten")))'.dependencies]
41+
toolchain = { path = "../toolchain", version = "0.0.0" }
42+
4043
[dev-dependencies]
4144
test_utils = { path = "../test_utils" }
4245
expect-test = "1.2.2"

crates/ide/src/expand_macro.rs

+94-18
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use hir::Semantics;
22
use ide_db::{
3-
helpers::pick_best_token, syntax_helpers::insert_whitespace_into_node::insert_ws_into,
4-
RootDatabase,
3+
base_db::FileId, helpers::pick_best_token,
4+
syntax_helpers::insert_whitespace_into_node::insert_ws_into, RootDatabase,
55
};
66
use syntax::{ast, ted, AstNode, NodeOrToken, SyntaxKind, SyntaxNode, T};
77

@@ -58,10 +58,9 @@ pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<
5858
.take_while(|it| it != &token)
5959
.filter(|it| it.kind() == T![,])
6060
.count();
61-
Some(ExpandedMacro {
62-
name,
63-
expansion: expansions.get(idx).cloned().map(insert_ws_into)?.to_string(),
64-
})
61+
let expansion =
62+
format(db, SyntaxKind::MACRO_ITEMS, position.file_id, expansions.get(idx).cloned()?);
63+
Some(ExpandedMacro { name, expansion })
6564
});
6665

6766
if derive.is_some() {
@@ -70,28 +69,34 @@ pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<
7069

7170
// FIXME: Intermix attribute and bang! expansions
7271
// currently we only recursively expand one of the two types
73-
let mut expanded = None;
74-
let mut name = None;
75-
for node in tok.ancestors() {
72+
let mut anc = tok.ancestors();
73+
let (name, expanded, kind) = loop {
74+
let node = anc.next()?;
75+
7676
if let Some(item) = ast::Item::cast(node.clone()) {
7777
if let Some(def) = sema.resolve_attr_macro_call(&item) {
78-
name = Some(def.name(db).to_string());
79-
expanded = expand_attr_macro_recur(&sema, &item);
80-
break;
78+
break (
79+
def.name(db).to_string(),
80+
expand_attr_macro_recur(&sema, &item)?,
81+
SyntaxKind::MACRO_ITEMS,
82+
);
8183
}
8284
}
8385
if let Some(mac) = ast::MacroCall::cast(node) {
84-
name = Some(mac.path()?.segment()?.name_ref()?.to_string());
85-
expanded = expand_macro_recur(&sema, &mac);
86-
break;
86+
break (
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),
90+
);
8791
}
88-
}
92+
};
8993

9094
// FIXME:
9195
// macro expansion may lose all white space information
9296
// But we hope someday we can use ra_fmt for that
93-
let expansion = insert_ws_into(expanded?).to_string();
94-
Some(ExpandedMacro { name: name.unwrap_or_else(|| "???".to_owned()), expansion })
97+
let expansion = format(db, kind, position.file_id, expanded);
98+
99+
Some(ExpandedMacro { name, expansion })
95100
}
96101

97102
fn expand_macro_recur(
@@ -130,6 +135,77 @@ fn expand<T: AstNode>(
130135
Some(expanded)
131136
}
132137

138+
fn format(db: &RootDatabase, kind: SyntaxKind, file_id: FileId, expanded: SyntaxNode) -> String {
139+
let expansion = insert_ws_into(expanded).to_string();
140+
141+
_format(db, kind, file_id, &expansion).unwrap_or(expansion)
142+
}
143+
144+
#[cfg(any(test, target_arch = "wasm32", target_os = "emscripten"))]
145+
fn _format(
146+
_db: &RootDatabase,
147+
_kind: SyntaxKind,
148+
_file_id: FileId,
149+
_expansion: &str,
150+
) -> Option<String> {
151+
None
152+
}
153+
154+
#[cfg(not(any(test, target_arch = "wasm32", target_os = "emscripten")))]
155+
fn _format(
156+
db: &RootDatabase,
157+
kind: SyntaxKind,
158+
file_id: FileId,
159+
expansion: &str,
160+
) -> Option<String> {
161+
use ide_db::base_db::{FileLoader, SourceDatabase};
162+
// hack until we get hygiene working (same character amount to preserve formatting as much as possible)
163+
const DOLLAR_CRATE_REPLACE: &str = &"__r_a_";
164+
let expansion = expansion.replace("$crate", DOLLAR_CRATE_REPLACE);
165+
let (prefix, suffix) = match kind {
166+
SyntaxKind::MACRO_PAT => ("fn __(", ": u32);"),
167+
SyntaxKind::MACRO_EXPR | SyntaxKind::MACRO_STMTS => ("fn __() {", "}"),
168+
SyntaxKind::MACRO_TYPE => ("type __ =", ";"),
169+
_ => ("", ""),
170+
};
171+
let expansion = format!("{prefix}{expansion}{suffix}");
172+
173+
let &crate_id = db.relevant_crates(file_id).iter().next()?;
174+
let edition = db.crate_graph()[crate_id].edition;
175+
176+
let mut cmd = std::process::Command::new(toolchain::rustfmt());
177+
cmd.arg("--edition");
178+
cmd.arg(edition.to_string());
179+
180+
let mut rustfmt = cmd
181+
.stdin(std::process::Stdio::piped())
182+
.stdout(std::process::Stdio::piped())
183+
.stderr(std::process::Stdio::piped())
184+
.spawn()
185+
.ok()?;
186+
187+
std::io::Write::write_all(&mut rustfmt.stdin.as_mut()?, expansion.as_bytes()).ok()?;
188+
189+
let output = rustfmt.wait_with_output().ok()?;
190+
let captured_stdout = String::from_utf8(output.stdout).ok()?;
191+
192+
if output.status.success() && !captured_stdout.trim().is_empty() {
193+
let output = captured_stdout.replace(DOLLAR_CRATE_REPLACE, "$crate");
194+
let output = output.trim().strip_prefix(prefix)?;
195+
let output = match kind {
196+
SyntaxKind::MACRO_PAT => {
197+
output.strip_suffix(suffix).or_else(|| output.strip_suffix(": u32,\n);"))?
198+
}
199+
_ => output.strip_suffix(suffix)?,
200+
};
201+
let trim_indent = stdx::trim_indent(output);
202+
tracing::debug!("expand_macro: formatting succeeded");
203+
Some(trim_indent)
204+
} else {
205+
None
206+
}
207+
}
208+
133209
#[cfg(test)]
134210
mod tests {
135211
use expect_test::{expect, Expect};

crates/ide/src/navigation_target.rs

-1
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,6 @@ impl TryToNav for hir::Macro {
340340
Either::Left(it) => it,
341341
Either::Right(it) => it,
342342
};
343-
tracing::debug!("nav target {:#?}", name_owner.syntax());
344343
let mut res = NavigationTarget::from_named(
345344
db,
346345
src.as_ref().with_value(name_owner),

0 commit comments

Comments
 (0)