about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2022-04-17 12:03:27 +0000
committerbors <bors@rust-lang.org>2022-04-17 12:03:27 +0000
commit9c675d652f2e1e16a5d2ca5fadf019a5afd527a6 (patch)
tree1f696f8075e8ef24fc1421ee738f219c09495d90
parent53afd2a707138230dee582fa1d5c3075cab2b6b7 (diff)
parent3de9a4281018f40934b195f0f8960c10147163ff (diff)
downloadrust-9c675d652f2e1e16a5d2ca5fadf019a5afd527a6.tar.gz
rust-9c675d652f2e1e16a5d2ca5fadf019a5afd527a6.zip
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
-rw-r--r--Cargo.lock1
-rw-r--r--crates/ide/Cargo.toml3
-rw-r--r--crates/ide/src/expand_macro.rs112
-rw-r--r--crates/ide/src/navigation_target.rs1
4 files changed, 98 insertions, 19 deletions
diff --git a/Cargo.lock b/Cargo.lock
index cea85409369..78ad6dc07db 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -611,6 +611,7 @@ dependencies = [
  "syntax",
  "test_utils",
  "text_edit",
+ "toolchain",
  "tracing",
  "url",
 ]
diff --git a/crates/ide/Cargo.toml b/crates/ide/Cargo.toml
index cd11b2801d7..d09b4a0dafe 100644
--- a/crates/ide/Cargo.toml
+++ b/crates/ide/Cargo.toml
@@ -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"
diff --git a/crates/ide/src/expand_macro.rs b/crates/ide/src/expand_macro.rs
index b4608d63c6c..e4061016e60 100644
--- a/crates/ide/src/expand_macro.rs
+++ b/crates/ide/src/expand_macro.rs
@@ -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};
diff --git a/crates/ide/src/navigation_target.rs b/crates/ide/src/navigation_target.rs
index 55bb10d5e86..1fb3b5ec3f3 100644
--- a/crates/ide/src/navigation_target.rs
+++ b/crates/ide/src/navigation_target.rs
@@ -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),