Auto merge of #12014 - Veykril:expmacfmt, r=Veykril

feat: Attempt to format expand_macro output with rustfmt if possible

Fixes https://github.com/rust-lang/rust-analyzer/issues/10548
This commit is contained in:
bors 2022-04-17 12:03:27 +00:00
commit 9c675d652f
4 changed files with 98 additions and 19 deletions

1
Cargo.lock generated
View File

@ -611,6 +611,7 @@ dependencies = [
"syntax",
"test_utils",
"text_edit",
"toolchain",
"tracing",
"url",
]

View File

@ -37,6 +37,9 @@ ide_completion = { path = "../ide_completion", version = "0.0.0" }
# something from some `hir_xxx` subpackage, reexport the API via `hir`.
hir = { path = "../hir", version = "0.0.0" }
[target.'cfg(not(any(target_arch = "wasm32", target_os = "emscripten")))'.dependencies]
toolchain = { path = "../toolchain", version = "0.0.0" }
[dev-dependencies]
test_utils = { path = "../test_utils" }
expect-test = "1.2.2"

View File

@ -1,7 +1,7 @@
use hir::Semantics;
use ide_db::{
helpers::pick_best_token, syntax_helpers::insert_whitespace_into_node::insert_ws_into,
RootDatabase,
base_db::FileId, helpers::pick_best_token,
syntax_helpers::insert_whitespace_into_node::insert_ws_into, RootDatabase,
};
use syntax::{ast, ted, AstNode, NodeOrToken, SyntaxKind, SyntaxNode, T};
@ -58,10 +58,9 @@ pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<
.take_while(|it| it != &token)
.filter(|it| it.kind() == T![,])
.count();
Some(ExpandedMacro {
name,
expansion: expansions.get(idx).cloned().map(insert_ws_into)?.to_string(),
})
let expansion =
format(db, SyntaxKind::MACRO_ITEMS, position.file_id, expansions.get(idx).cloned()?);
Some(ExpandedMacro { name, expansion })
});
if derive.is_some() {
@ -70,28 +69,34 @@ pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<
// FIXME: Intermix attribute and bang! expansions
// currently we only recursively expand one of the two types
let mut expanded = None;
let mut name = None;
for node in tok.ancestors() {
let mut anc = tok.ancestors();
let (name, expanded, kind) = loop {
let node = anc.next()?;
if let Some(item) = ast::Item::cast(node.clone()) {
if let Some(def) = sema.resolve_attr_macro_call(&item) {
name = Some(def.name(db).to_string());
expanded = expand_attr_macro_recur(&sema, &item);
break;
break (
def.name(db).to_string(),
expand_attr_macro_recur(&sema, &item)?,
SyntaxKind::MACRO_ITEMS,
);
}
}
if let Some(mac) = ast::MacroCall::cast(node) {
name = Some(mac.path()?.segment()?.name_ref()?.to_string());
expanded = expand_macro_recur(&sema, &mac);
break;
break (
mac.path()?.segment()?.name_ref()?.to_string(),
expand_macro_recur(&sema, &mac)?,
mac.syntax().parent().map(|it| it.kind()).unwrap_or(SyntaxKind::MACRO_ITEMS),
);
}
}
};
// FIXME:
// macro expansion may lose all white space information
// But we hope someday we can use ra_fmt for that
let expansion = insert_ws_into(expanded?).to_string();
Some(ExpandedMacro { name: name.unwrap_or_else(|| "???".to_owned()), expansion })
let expansion = format(db, kind, position.file_id, expanded);
Some(ExpandedMacro { name, expansion })
}
fn expand_macro_recur(
@ -130,6 +135,77 @@ fn expand<T: AstNode>(
Some(expanded)
}
fn format(db: &RootDatabase, kind: SyntaxKind, file_id: FileId, expanded: SyntaxNode) -> String {
let expansion = insert_ws_into(expanded).to_string();
_format(db, kind, file_id, &expansion).unwrap_or(expansion)
}
#[cfg(any(test, target_arch = "wasm32", target_os = "emscripten"))]
fn _format(
_db: &RootDatabase,
_kind: SyntaxKind,
_file_id: FileId,
_expansion: &str,
) -> Option<String> {
None
}
#[cfg(not(any(test, target_arch = "wasm32", target_os = "emscripten")))]
fn _format(
db: &RootDatabase,
kind: SyntaxKind,
file_id: FileId,
expansion: &str,
) -> Option<String> {
use ide_db::base_db::{FileLoader, SourceDatabase};
// hack until we get hygiene working (same character amount to preserve formatting as much as possible)
const DOLLAR_CRATE_REPLACE: &str = &"__r_a_";
let expansion = expansion.replace("$crate", DOLLAR_CRATE_REPLACE);
let (prefix, suffix) = match kind {
SyntaxKind::MACRO_PAT => ("fn __(", ": u32);"),
SyntaxKind::MACRO_EXPR | SyntaxKind::MACRO_STMTS => ("fn __() {", "}"),
SyntaxKind::MACRO_TYPE => ("type __ =", ";"),
_ => ("", ""),
};
let expansion = format!("{prefix}{expansion}{suffix}");
let &crate_id = db.relevant_crates(file_id).iter().next()?;
let edition = db.crate_graph()[crate_id].edition;
let mut cmd = std::process::Command::new(toolchain::rustfmt());
cmd.arg("--edition");
cmd.arg(edition.to_string());
let mut rustfmt = cmd
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.spawn()
.ok()?;
std::io::Write::write_all(&mut rustfmt.stdin.as_mut()?, expansion.as_bytes()).ok()?;
let output = rustfmt.wait_with_output().ok()?;
let captured_stdout = String::from_utf8(output.stdout).ok()?;
if output.status.success() && !captured_stdout.trim().is_empty() {
let output = captured_stdout.replace(DOLLAR_CRATE_REPLACE, "$crate");
let output = output.trim().strip_prefix(prefix)?;
let output = match kind {
SyntaxKind::MACRO_PAT => {
output.strip_suffix(suffix).or_else(|| output.strip_suffix(": u32,\n);"))?
}
_ => output.strip_suffix(suffix)?,
};
let trim_indent = stdx::trim_indent(output);
tracing::debug!("expand_macro: formatting succeeded");
Some(trim_indent)
} else {
None
}
}
#[cfg(test)]
mod tests {
use expect_test::{expect, Expect};

View File

@ -340,7 +340,6 @@ impl TryToNav for hir::Macro {
Either::Left(it) => it,
Either::Right(it) => it,
};
tracing::debug!("nav target {:#?}", name_owner.syntax());
let mut res = NavigationTarget::from_named(
db,
src.as_ref().with_value(name_owner),