about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2024-09-18 20:17:21 +0000
committerbors <bors@rust-lang.org>2024-09-18 20:17:21 +0000
commit4ed7f4b5c970d7501f15a147d4cb6d940a8769e2 (patch)
tree438839f2379cb5141b6860403515d7d4695e0eab
parent627ccda3e56fad5e10138d3fad9fbbe0d504e870 (diff)
parentd878b537bd6ffd3321200bf6e43e3e6dc575d356 (diff)
downloadrust-4ed7f4b5c970d7501f15a147d4cb6d940a8769e2.tar.gz
rust-4ed7f4b5c970d7501f15a147d4cb6d940a8769e2.zip
Auto merge of #18131 - ChayimFriedman2:macro-expand-dollar-crate, r=Veykril
fix: Get rid of `$crate` in expansions shown to the user

Be it "Expand Macro Recursively", "Inline macro" or few other things.

We replace it with the crate name, as should've always been.

Probably fixes some issues, but I don't know what they are.
-rw-r--r--src/tools/rust-analyzer/crates/hir-expand/src/lib.rs7
-rw-r--r--src/tools/rust-analyzer/crates/hir-expand/src/prettify_macro_expansion_.rs60
-rw-r--r--src/tools/rust-analyzer/crates/hir/src/lib.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_call.rs43
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_macro.rs82
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/utils.rs20
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs32
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/lib.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/expand_macro.rs126
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/hover/render.rs18
-rw-r--r--src/tools/rust-analyzer/crates/mbe/src/tests.rs11
-rw-r--r--src/tools/rust-analyzer/crates/span/src/map.rs25
-rw-r--r--src/tools/rust-analyzer/crates/syntax-bridge/src/lib.rs2
-rw-r--r--src/tools/rust-analyzer/crates/syntax-bridge/src/prettify_macro_expansion.rs (renamed from src/tools/rust-analyzer/crates/syntax-bridge/src/insert_whitespace_into_node.rs)17
-rw-r--r--src/tools/rust-analyzer/crates/syntax/src/ast/make.rs13
15 files changed, 396 insertions, 64 deletions
diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs b/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs
index 7ae2fa1e39c..7cf4fb5cef2 100644
--- a/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs
@@ -21,6 +21,7 @@ pub mod span_map;
 
 mod cfg_process;
 mod fixup;
+mod prettify_macro_expansion_;
 
 use attrs::collect_attrs;
 use rustc_hash::FxHashMap;
@@ -51,11 +52,13 @@ use crate::{
     span_map::{ExpansionSpanMap, SpanMap},
 };
 
-pub use crate::files::{AstId, ErasedAstId, FileRange, InFile, InMacroFile, InRealFile};
+pub use crate::{
+    files::{AstId, ErasedAstId, FileRange, InFile, InMacroFile, InRealFile},
+    prettify_macro_expansion_::prettify_macro_expansion,
+};
 
 pub use mbe::{DeclarativeMacro, ValueResult};
 pub use span::{HirFileId, MacroCallId, MacroFileId};
-pub use syntax_bridge::insert_whitespace_into_node;
 
 pub mod tt {
     pub use span::Span;
diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/prettify_macro_expansion_.rs b/src/tools/rust-analyzer/crates/hir-expand/src/prettify_macro_expansion_.rs
new file mode 100644
index 00000000000..d928cafdefc
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/hir-expand/src/prettify_macro_expansion_.rs
@@ -0,0 +1,60 @@
+//! Pretty printing of macros output.
+
+use base_db::CrateId;
+use rustc_hash::FxHashMap;
+use syntax::NodeOrToken;
+use syntax::{ast::make, SyntaxNode};
+
+use crate::{db::ExpandDatabase, span_map::ExpansionSpanMap};
+
+/// Inserts whitespace and replaces `$crate` in macro expansions.
+#[expect(deprecated)]
+pub fn prettify_macro_expansion(
+    db: &dyn ExpandDatabase,
+    syn: SyntaxNode,
+    span_map: &ExpansionSpanMap,
+    target_crate_id: CrateId,
+) -> SyntaxNode {
+    let crate_graph = db.crate_graph();
+    let target_crate = &crate_graph[target_crate_id];
+    let mut syntax_ctx_id_to_dollar_crate_replacement = FxHashMap::default();
+    syntax_bridge::prettify_macro_expansion::prettify_macro_expansion(syn, &mut |dollar_crate| {
+        let ctx = span_map.span_at(dollar_crate.text_range().start()).ctx;
+        let replacement =
+            syntax_ctx_id_to_dollar_crate_replacement.entry(ctx).or_insert_with(|| {
+                let ctx_data = db.lookup_intern_syntax_context(ctx);
+                let macro_call_id =
+                    ctx_data.outer_expn.expect("`$crate` cannot come from `SyntaxContextId::ROOT`");
+                let macro_call = db.lookup_intern_macro_call(macro_call_id);
+                let macro_def_crate = macro_call.def.krate;
+                // First, if this is the same crate as the macro, nothing will work but `crate`.
+                // If not, if the target trait has the macro's crate as a dependency, using the dependency name
+                // will work in inserted code and match the user's expectation.
+                // If not, the crate's display name is what the dependency name is likely to be once such dependency
+                // is inserted, and also understandable to the user.
+                // Lastly, if nothing else found, resort to leaving `$crate`.
+                if target_crate_id == macro_def_crate {
+                    make::tokens::crate_kw()
+                } else if let Some(dep) =
+                    target_crate.dependencies.iter().find(|dep| dep.crate_id == macro_def_crate)
+                {
+                    make::tokens::ident(&dep.name)
+                } else if let Some(crate_name) = &crate_graph[macro_def_crate].display_name {
+                    make::tokens::ident(crate_name.crate_name())
+                } else {
+                    return dollar_crate.clone();
+                }
+            });
+        if replacement.text() == "$crate" {
+            // The parent may have many children, and looking for the token may yield incorrect results.
+            return dollar_crate.clone();
+        }
+        // We need to `clone_subtree()` but rowan doesn't provide such operation for tokens.
+        let parent = replacement.parent().unwrap().clone_subtree().clone_for_update();
+        parent
+            .children_with_tokens()
+            .filter_map(NodeOrToken::into_token)
+            .find(|it| it.kind() == replacement.kind())
+            .unwrap()
+    })
+}
diff --git a/src/tools/rust-analyzer/crates/hir/src/lib.rs b/src/tools/rust-analyzer/crates/hir/src/lib.rs
index d18a36bdbd1..8f5db32f957 100644
--- a/src/tools/rust-analyzer/crates/hir/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/hir/src/lib.rs
@@ -136,8 +136,8 @@ pub use {
         },
         hygiene::{marks_rev, SyntaxContextExt},
         inert_attr_macro::AttributeTemplate,
-        insert_whitespace_into_node,
         name::Name,
+        prettify_macro_expansion,
         proc_macro::{ProcMacros, ProcMacrosBuilder},
         tt, ExpandResult, HirFileId, HirFileIdExt, MacroFileId, MacroFileIdExt,
     },
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_call.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_call.rs
index 906e9a9891a..9e09f198feb 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_call.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_call.rs
@@ -2,14 +2,18 @@ use std::collections::BTreeSet;
 
 use ast::make;
 use either::Either;
-use hir::{db::HirDatabase, sym, FileRange, PathResolution, Semantics, TypeInfo};
+use hir::{
+    db::{ExpandDatabase, HirDatabase},
+    sym, FileRange, PathResolution, Semantics, TypeInfo,
+};
 use ide_db::{
+    base_db::CrateId,
     defs::Definition,
     imports::insert_use::remove_path_if_in_use_stmt,
     path_transform::PathTransform,
     search::{FileReference, FileReferenceNode, SearchScope},
     source_change::SourceChangeBuilder,
-    syntax_helpers::{insert_whitespace_into_node::insert_ws_into, node_ext::expr_as_name_ref},
+    syntax_helpers::{node_ext::expr_as_name_ref, prettify_macro_expansion},
     EditionedFileId, RootDatabase,
 };
 use itertools::{izip, Itertools};
@@ -102,12 +106,13 @@ pub(crate) fn inline_into_callers(acc: &mut Assists, ctx: &AssistContext<'_>) ->
             let mut remove_def = true;
             let mut inline_refs_for_file = |file_id, refs: Vec<FileReference>| {
                 builder.edit_file(file_id);
+                let call_krate = ctx.sema.file_to_module_def(file_id).map(|it| it.krate());
                 let count = refs.len();
                 // The collects are required as we are otherwise iterating while mutating 🙅‍♀️🙅‍♂️
                 let (name_refs, name_refs_use) = split_refs_and_uses(builder, refs, Some);
                 let call_infos: Vec<_> = name_refs
                     .into_iter()
-                    .filter_map(CallInfo::from_name_ref)
+                    .filter_map(|it| CallInfo::from_name_ref(it, call_krate?.into()))
                     // FIXME: do not handle callsites in macros' parameters, because
                     // directly inlining into macros may cause errors.
                     .filter(|call_info| !ctx.sema.hir_file_for(call_info.node.syntax()).is_macro())
@@ -185,7 +190,10 @@ pub(super) fn split_refs_and_uses<T: ast::AstNode>(
 // ```
 pub(crate) fn inline_call(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
     let name_ref: ast::NameRef = ctx.find_node_at_offset()?;
-    let call_info = CallInfo::from_name_ref(name_ref.clone())?;
+    let call_info = CallInfo::from_name_ref(
+        name_ref.clone(),
+        ctx.sema.file_to_module_def(ctx.file_id())?.krate().into(),
+    )?;
     let (function, label) = match &call_info.node {
         ast::CallableExpr::Call(call) => {
             let path = match call.expr()? {
@@ -243,10 +251,11 @@ struct CallInfo {
     node: ast::CallableExpr,
     arguments: Vec<ast::Expr>,
     generic_arg_list: Option<ast::GenericArgList>,
+    krate: CrateId,
 }
 
 impl CallInfo {
-    fn from_name_ref(name_ref: ast::NameRef) -> Option<CallInfo> {
+    fn from_name_ref(name_ref: ast::NameRef, krate: CrateId) -> Option<CallInfo> {
         let parent = name_ref.syntax().parent()?;
         if let Some(call) = ast::MethodCallExpr::cast(parent.clone()) {
             let receiver = call.receiver()?;
@@ -256,6 +265,7 @@ impl CallInfo {
                 generic_arg_list: call.generic_arg_list(),
                 node: ast::CallableExpr::MethodCall(call),
                 arguments,
+                krate,
             })
         } else if let Some(segment) = ast::PathSegment::cast(parent) {
             let path = segment.syntax().parent().and_then(ast::Path::cast)?;
@@ -266,6 +276,7 @@ impl CallInfo {
                 arguments: call.arg_list()?.args().collect(),
                 node: ast::CallableExpr::Call(call),
                 generic_arg_list: segment.generic_arg_list(),
+                krate,
             })
         } else {
             None
@@ -307,11 +318,15 @@ fn inline(
     function: hir::Function,
     fn_body: &ast::BlockExpr,
     params: &[(ast::Pat, Option<ast::Type>, hir::Param)],
-    CallInfo { node, arguments, generic_arg_list }: &CallInfo,
+    CallInfo { node, arguments, generic_arg_list, krate }: &CallInfo,
 ) -> ast::Expr {
-    let mut body = if sema.hir_file_for(fn_body.syntax()).is_macro() {
+    let file_id = sema.hir_file_for(fn_body.syntax());
+    let mut body = if let Some(macro_file) = file_id.macro_file() {
         cov_mark::hit!(inline_call_defined_in_macro);
-        if let Some(body) = ast::BlockExpr::cast(insert_ws_into(fn_body.syntax().clone())) {
+        let span_map = sema.db.expansion_span_map(macro_file);
+        let body_prettified =
+            prettify_macro_expansion(sema.db, fn_body.syntax().clone(), &span_map, *krate);
+        if let Some(body) = ast::BlockExpr::cast(body_prettified) {
             body
         } else {
             fn_body.clone_for_update()
@@ -420,8 +435,16 @@ fn inline(
 
         let mut insert_let_stmt = || {
             let param_ty = param_ty.clone().map(|param_ty| {
-                if sema.hir_file_for(param_ty.syntax()).is_macro() {
-                    ast::Type::cast(insert_ws_into(param_ty.syntax().clone())).unwrap_or(param_ty)
+                let file_id = sema.hir_file_for(param_ty.syntax());
+                if let Some(macro_file) = file_id.macro_file() {
+                    let span_map = sema.db.expansion_span_map(macro_file);
+                    let param_ty_prettified = prettify_macro_expansion(
+                        sema.db,
+                        param_ty.syntax().clone(),
+                        &span_map,
+                        *krate,
+                    );
+                    ast::Type::cast(param_ty_prettified).unwrap_or(param_ty)
                 } else {
                     param_ty
                 }
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_macro.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_macro.rs
index 4708be61696..d558ec3bec7 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_macro.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_macro.rs
@@ -1,4 +1,5 @@
-use ide_db::syntax_helpers::insert_whitespace_into_node::insert_ws_into;
+use hir::db::ExpandDatabase;
+use ide_db::syntax_helpers::prettify_macro_expansion;
 use syntax::ast::{self, AstNode};
 
 use crate::{AssistContext, AssistId, AssistKind, Assists};
@@ -36,7 +37,15 @@ use crate::{AssistContext, AssistId, AssistKind, Assists};
 // ```
 pub(crate) fn inline_macro(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
     let unexpanded = ctx.find_node_at_offset::<ast::MacroCall>()?;
-    let expanded = insert_ws_into(ctx.sema.expand(&unexpanded)?.clone_for_update());
+    let macro_call = ctx.sema.to_def(&unexpanded)?;
+    let expanded = ctx.sema.parse_or_expand(macro_call.as_file());
+    let span_map = ctx.sema.db.expansion_span_map(macro_call.as_macro_file());
+    let expanded = prettify_macro_expansion(
+        ctx.db(),
+        expanded,
+        &span_map,
+        ctx.sema.file_to_module_def(ctx.file_id())?.krate().into(),
+    );
     let text_range = unexpanded.syntax().text_range();
 
     acc.add(
@@ -298,4 +307,73 @@ fn main() {
 "#,
         );
     }
+
+    #[test]
+    fn dollar_crate() {
+        check_assist(
+            inline_macro,
+            r#"
+pub struct Foo;
+#[macro_export]
+macro_rules! m {
+    () => { $crate::Foo };
+}
+fn bar() {
+    m$0!();
+}
+"#,
+            r#"
+pub struct Foo;
+#[macro_export]
+macro_rules! m {
+    () => { $crate::Foo };
+}
+fn bar() {
+    crate::Foo;
+}
+"#,
+        );
+        check_assist(
+            inline_macro,
+            r#"
+//- /a.rs crate:a
+pub struct Foo;
+#[macro_export]
+macro_rules! m {
+    () => { $crate::Foo };
+}
+//- /b.rs crate:b deps:a
+fn bar() {
+    a::m$0!();
+}
+"#,
+            r#"
+fn bar() {
+    a::Foo;
+}
+"#,
+        );
+        check_assist(
+            inline_macro,
+            r#"
+//- /a.rs crate:a
+pub struct Foo;
+#[macro_export]
+macro_rules! m {
+    () => { $crate::Foo };
+}
+//- /b.rs crate:b deps:a
+pub use a::m;
+//- /c.rs crate:c deps:b
+fn bar() {
+    b::m$0!();
+}
+"#,
+            r#"
+fn bar() {
+    a::Foo;
+}
+"#,
+        );
+    }
 }
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs b/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs
index 19d1ef3157d..0830017bd0f 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs
@@ -1,10 +1,13 @@
 //! Assorted functions shared by several assists.
 
 pub(crate) use gen_trait_fn_body::gen_trait_fn_body;
-use hir::{db::HirDatabase, HasAttrs as HirHasAttrs, HirDisplay, InFile, Semantics};
+use hir::{
+    db::{ExpandDatabase, HirDatabase},
+    HasAttrs as HirHasAttrs, HirDisplay, InFile, Semantics,
+};
 use ide_db::{
     famous_defs::FamousDefs, path_transform::PathTransform,
-    syntax_helpers::insert_whitespace_into_node::insert_ws_into, RootDatabase,
+    syntax_helpers::prettify_macro_expansion, RootDatabase,
 };
 use stdx::format_to;
 use syntax::{
@@ -178,10 +181,15 @@ pub fn add_trait_assoc_items_to_impl(
     let new_indent_level = IndentLevel::from_node(impl_.syntax()) + 1;
     let items = original_items.iter().map(|InFile { file_id, value: original_item }| {
         let cloned_item = {
-            if file_id.is_macro() {
-                if let Some(formatted) =
-                    ast::AssocItem::cast(insert_ws_into(original_item.syntax().clone()))
-                {
+            if let Some(macro_file) = file_id.macro_file() {
+                let span_map = sema.db.expansion_span_map(macro_file);
+                let item_prettified = prettify_macro_expansion(
+                    sema.db,
+                    original_item.syntax().clone(),
+                    &span_map,
+                    target_scope.krate().into(),
+                );
+                if let Some(formatted) = ast::AssocItem::cast(item_prettified) {
                     return formatted;
                 } else {
                     stdx::never!("formatted `AssocItem` could not be cast back to `AssocItem`");
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs
index b5bc5533c79..672e1796d1e 100644
--- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs
@@ -31,10 +31,10 @@
 //! }
 //! ```
 
-use hir::{HasAttrs, Name};
+use hir::{db::ExpandDatabase, HasAttrs, MacroFileId, Name};
 use ide_db::{
     documentation::HasDocs, path_transform::PathTransform,
-    syntax_helpers::insert_whitespace_into_node, traits::get_missing_assoc_items, SymbolKind,
+    syntax_helpers::prettify_macro_expansion, traits::get_missing_assoc_items, SymbolKind,
 };
 use syntax::{
     ast::{self, edit_in_place::AttrsOwnerEdit, make, HasGenericArgs, HasTypeBounds},
@@ -227,7 +227,8 @@ fn add_function_impl_(
         if let Some(transformed_fn) =
             get_transformed_fn(ctx, source.value, impl_def, async_sugaring)
         {
-            let function_decl = function_declaration(&transformed_fn, source.file_id.is_macro());
+            let function_decl =
+                function_declaration(ctx, &transformed_fn, source.file_id.macro_file());
             match ctx.config.snippet_cap {
                 Some(cap) => {
                     let snippet = format!("{function_decl} {{\n    $0\n}}");
@@ -432,7 +433,8 @@ fn add_const_impl(
                     _ => unreachable!(),
                 };
 
-                let label = make_const_compl_syntax(&transformed_const, source.file_id.is_macro());
+                let label =
+                    make_const_compl_syntax(ctx, &transformed_const, source.file_id.macro_file());
                 let replacement = format!("{label} ");
 
                 let mut item =
@@ -456,9 +458,14 @@ fn add_const_impl(
     }
 }
 
-fn make_const_compl_syntax(const_: &ast::Const, needs_whitespace: bool) -> SmolStr {
-    let const_ = if needs_whitespace {
-        insert_whitespace_into_node::insert_ws_into(const_.syntax().clone())
+fn make_const_compl_syntax(
+    ctx: &CompletionContext<'_>,
+    const_: &ast::Const,
+    macro_file: Option<MacroFileId>,
+) -> SmolStr {
+    let const_ = if let Some(macro_file) = macro_file {
+        let span_map = ctx.db.expansion_span_map(macro_file);
+        prettify_macro_expansion(ctx.db, const_.syntax().clone(), &span_map, ctx.krate.into())
     } else {
         const_.syntax().clone()
     };
@@ -479,9 +486,14 @@ fn make_const_compl_syntax(const_: &ast::Const, needs_whitespace: bool) -> SmolS
     format_smolstr!("{} =", syntax.trim_end())
 }
 
-fn function_declaration(node: &ast::Fn, needs_whitespace: bool) -> String {
-    let node = if needs_whitespace {
-        insert_whitespace_into_node::insert_ws_into(node.syntax().clone())
+fn function_declaration(
+    ctx: &CompletionContext<'_>,
+    node: &ast::Fn,
+    macro_file: Option<MacroFileId>,
+) -> String {
+    let node = if let Some(macro_file) = macro_file {
+        let span_map = ctx.db.expansion_span_map(macro_file);
+        prettify_macro_expansion(ctx.db, node.syntax().clone(), &span_map, ctx.krate.into())
     } else {
         node.syntax().clone()
     };
diff --git a/src/tools/rust-analyzer/crates/ide-db/src/lib.rs b/src/tools/rust-analyzer/crates/ide-db/src/lib.rs
index 3435757ad31..a45ff9a9545 100644
--- a/src/tools/rust-analyzer/crates/ide-db/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/ide-db/src/lib.rs
@@ -36,7 +36,7 @@ pub mod generated {
 pub mod syntax_helpers {
     pub mod format_string;
     pub mod format_string_exprs;
-    pub use hir::insert_whitespace_into_node;
+    pub use hir::prettify_macro_expansion;
     pub mod node_ext;
     pub mod suggest_name;
 
diff --git a/src/tools/rust-analyzer/crates/ide/src/expand_macro.rs b/src/tools/rust-analyzer/crates/ide/src/expand_macro.rs
index a939ed214ad..f55082de674 100644
--- a/src/tools/rust-analyzer/crates/ide/src/expand_macro.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/expand_macro.rs
@@ -1,9 +1,10 @@
+use hir::db::ExpandDatabase;
 use hir::{InFile, MacroFileIdExt, Semantics};
+use ide_db::base_db::CrateId;
 use ide_db::{
-    helpers::pick_best_token, syntax_helpers::insert_whitespace_into_node::insert_ws_into, FileId,
-    RootDatabase,
+    helpers::pick_best_token, syntax_helpers::prettify_macro_expansion, FileId, RootDatabase,
 };
-use span::Edition;
+use span::{Edition, SpanMap, SyntaxContextId, TextRange, TextSize};
 use syntax::{ast, ted, AstNode, NodeOrToken, SyntaxKind, SyntaxNode, T};
 
 use crate::FilePosition;
@@ -27,6 +28,7 @@ pub struct ExpandedMacro {
 pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<ExpandedMacro> {
     let sema = Semantics::new(db);
     let file = sema.parse_guess_edition(position.file_id);
+    let krate = sema.file_to_module_def(position.file_id)?.krate().into();
 
     let tok = pick_best_token(file.syntax().token_at_offset(position.offset), |kind| match kind {
         SyntaxKind::IDENT => 1,
@@ -61,8 +63,17 @@ pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<
             .take_while(|it| it != &token)
             .filter(|it| it.kind() == T![,])
             .count();
-        let expansion =
-            format(db, SyntaxKind::MACRO_ITEMS, position.file_id, expansions.get(idx).cloned()?);
+        let expansion = expansions.get(idx)?.clone();
+        let expansion_file_id = sema.hir_file_for(&expansion).macro_file()?;
+        let expansion_span_map = db.expansion_span_map(expansion_file_id);
+        let expansion = format(
+            db,
+            SyntaxKind::MACRO_ITEMS,
+            position.file_id,
+            expansion,
+            &expansion_span_map,
+            krate,
+        );
         Some(ExpandedMacro { name, expansion })
     });
 
@@ -71,6 +82,7 @@ pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<
     }
 
     let mut anc = tok.parent_ancestors();
+    let mut span_map = SpanMap::empty();
     let (name, expanded, kind) = loop {
         let node = anc.next()?;
 
@@ -85,7 +97,7 @@ pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<
                                 .unwrap_or(Edition::CURRENT),
                         )
                         .to_string(),
-                    expand_macro_recur(&sema, &item)?,
+                    expand_macro_recur(&sema, &item, &mut span_map, TextSize::new(0))?,
                     SyntaxKind::MACRO_ITEMS,
                 );
             }
@@ -95,14 +107,23 @@ pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<
             name.push('!');
             let syntax_kind =
                 mac.syntax().parent().map(|it| it.kind()).unwrap_or(SyntaxKind::MACRO_ITEMS);
-            break (name, expand_macro_recur(&sema, &ast::Item::MacroCall(mac))?, syntax_kind);
+            break (
+                name,
+                expand_macro_recur(
+                    &sema,
+                    &ast::Item::MacroCall(mac),
+                    &mut span_map,
+                    TextSize::new(0),
+                )?,
+                syntax_kind,
+            );
         }
     };
 
     // FIXME:
     // macro expansion may lose all white space information
     // But we hope someday we can use ra_fmt for that
-    let expansion = format(db, kind, position.file_id, expanded);
+    let expansion = format(db, kind, position.file_id, expanded, &span_map, krate);
 
     Some(ExpandedMacro { name, expansion })
 }
@@ -110,6 +131,8 @@ pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<
 fn expand_macro_recur(
     sema: &Semantics<'_, RootDatabase>,
     macro_call: &ast::Item,
+    result_span_map: &mut SpanMap<SyntaxContextId>,
+    offset_in_original_node: TextSize,
 ) -> Option<SyntaxNode> {
     let expanded = match macro_call {
         item @ ast::Item::MacroCall(macro_call) => sema
@@ -118,29 +141,54 @@ fn expand_macro_recur(
             .clone_for_update(),
         item => sema.expand_attr_macro(item)?.clone_for_update(),
     };
-    expand(sema, expanded)
+    let file_id =
+        sema.hir_file_for(&expanded).macro_file().expect("expansion must produce a macro file");
+    let expansion_span_map = sema.db.expansion_span_map(file_id);
+    result_span_map.merge(
+        TextRange::at(offset_in_original_node, macro_call.syntax().text_range().len()),
+        expanded.text_range().len(),
+        &expansion_span_map,
+    );
+    Some(expand(sema, expanded, result_span_map, offset_in_original_node))
 }
 
-fn expand(sema: &Semantics<'_, RootDatabase>, expanded: SyntaxNode) -> Option<SyntaxNode> {
+fn expand(
+    sema: &Semantics<'_, RootDatabase>,
+    expanded: SyntaxNode,
+    result_span_map: &mut SpanMap<SyntaxContextId>,
+    offset_in_original_node: TextSize,
+) -> SyntaxNode {
     let children = expanded.descendants().filter_map(ast::Item::cast);
     let mut replacements = Vec::new();
 
     for child in children {
-        if let Some(new_node) = expand_macro_recur(sema, &child) {
+        if let Some(new_node) = expand_macro_recur(
+            sema,
+            &child,
+            result_span_map,
+            offset_in_original_node + child.syntax().text_range().start(),
+        ) {
             // check if the whole original syntax is replaced
             if expanded == *child.syntax() {
-                return Some(new_node);
+                return new_node;
             }
             replacements.push((child, new_node));
         }
     }
 
     replacements.into_iter().rev().for_each(|(old, new)| ted::replace(old.syntax(), new));
-    Some(expanded)
+    expanded
 }
 
-fn format(db: &RootDatabase, kind: SyntaxKind, file_id: FileId, expanded: SyntaxNode) -> String {
-    let expansion = insert_ws_into(expanded).to_string();
+fn format(
+    db: &RootDatabase,
+    kind: SyntaxKind,
+    file_id: FileId,
+    expanded: SyntaxNode,
+    span_map: &SpanMap<SyntaxContextId>,
+    krate: CrateId,
+) -> String {
+    let expansion = prettify_macro_expansion(db, expanded, span_map, krate).to_string();
 
     _format(db, kind, file_id, &expansion).unwrap_or(expansion)
 }
@@ -498,7 +546,7 @@ struct Foo {}
 "#,
             expect![[r#"
                 Clone
-                impl < >$crate::clone::Clone for Foo< >where {
+                impl < >core::clone::Clone for Foo< >where {
                     fn clone(&self) -> Self {
                         match self {
                             Foo{}
@@ -524,7 +572,7 @@ struct Foo {}
 "#,
             expect![[r#"
                 Copy
-                impl < >$crate::marker::Copy for Foo< >where{}"#]],
+                impl < >core::marker::Copy for Foo< >where{}"#]],
         );
     }
 
@@ -539,7 +587,7 @@ struct Foo {}
 "#,
             expect![[r#"
                 Copy
-                impl < >$crate::marker::Copy for Foo< >where{}"#]],
+                impl < >core::marker::Copy for Foo< >where{}"#]],
         );
         check(
             r#"
@@ -550,7 +598,7 @@ struct Foo {}
 "#,
             expect![[r#"
                 Clone
-                impl < >$crate::clone::Clone for Foo< >where {
+                impl < >core::clone::Clone for Foo< >where {
                     fn clone(&self) -> Self {
                         match self {
                             Foo{}
@@ -563,4 +611,44 @@ struct Foo {}
                     }"#]],
         );
     }
+
+    #[test]
+    fn dollar_crate() {
+        check(
+            r#"
+//- /a.rs crate:a
+pub struct Foo;
+#[macro_export]
+macro_rules! m {
+    ( $i:ident ) => { $crate::Foo; $crate::Foo; $i::Foo; };
+}
+//- /b.rs crate:b deps:a
+pub struct Foo;
+#[macro_export]
+macro_rules! m {
+    () => { a::m!($crate); $crate::Foo; $crate::Foo; };
+}
+//- /c.rs crate:c deps:b,a
+pub struct Foo;
+#[macro_export]
+macro_rules! m {
+    () => { b::m!(); $crate::Foo; $crate::Foo; };
+}
+fn bar() {
+    m$0!();
+}
+"#,
+            expect![[r#"
+m!
+a::Foo;
+a::Foo;
+b::Foo;
+;
+b::Foo;
+b::Foo;
+;
+crate::Foo;
+crate::Foo;"#]],
+        );
+    }
 }
diff --git a/src/tools/rust-analyzer/crates/ide/src/hover/render.rs b/src/tools/rust-analyzer/crates/ide/src/hover/render.rs
index ead2d45847d..b02e9b0f073 100644
--- a/src/tools/rust-analyzer/crates/ide/src/hover/render.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/hover/render.rs
@@ -3,9 +3,9 @@ use std::{mem, ops::Not};
 
 use either::Either;
 use hir::{
-    Adt, AsAssocItem, AsExternAssocItem, CaptureKind, HasCrate, HasSource, HirDisplay, Layout,
-    LayoutError, MethodViolationCode, Name, ObjectSafetyViolation, Semantics, Trait, Type,
-    TypeInfo,
+    db::ExpandDatabase, Adt, AsAssocItem, AsExternAssocItem, CaptureKind, HasCrate, HasSource,
+    HirDisplay, Layout, LayoutError, MethodViolationCode, Name, ObjectSafetyViolation, Semantics,
+    Trait, Type, TypeInfo,
 };
 use ide_db::{
     base_db::SourceDatabase,
@@ -13,7 +13,7 @@ use ide_db::{
     documentation::HasDocs,
     famous_defs::FamousDefs,
     generated::lints::{CLIPPY_LINTS, DEFAULT_LINTS, FEATURES},
-    syntax_helpers::insert_whitespace_into_node,
+    syntax_helpers::prettify_macro_expansion,
     RootDatabase,
 };
 use itertools::Itertools;
@@ -476,8 +476,9 @@ pub(super) fn definition(
                 Err(_) => {
                     let source = it.source(db)?;
                     let mut body = source.value.body()?.syntax().clone();
-                    if source.file_id.is_macro() {
-                        body = insert_whitespace_into_node::insert_ws_into(body);
+                    if let Some(macro_file) = source.file_id.macro_file() {
+                        let span_map = db.expansion_span_map(macro_file);
+                        body = prettify_macro_expansion(db, body, &span_map, it.krate(db).into());
                     }
                     Some(body.to_string())
                 }
@@ -486,8 +487,9 @@ pub(super) fn definition(
         Definition::Static(it) => {
             let source = it.source(db)?;
             let mut body = source.value.body()?.syntax().clone();
-            if source.file_id.is_macro() {
-                body = insert_whitespace_into_node::insert_ws_into(body);
+            if let Some(macro_file) = source.file_id.macro_file() {
+                let span_map = db.expansion_span_map(macro_file);
+                body = prettify_macro_expansion(db, body, &span_map, it.krate(db).into());
             }
             Some(body.to_string())
         }
diff --git a/src/tools/rust-analyzer/crates/mbe/src/tests.rs b/src/tools/rust-analyzer/crates/mbe/src/tests.rs
index f6f12a9f9a0..e63ad113ffd 100644
--- a/src/tools/rust-analyzer/crates/mbe/src/tests.rs
+++ b/src/tools/rust-analyzer/crates/mbe/src/tests.rs
@@ -5,11 +5,11 @@
 use expect_test::expect;
 use span::{Edition, EditionedFileId, ErasedFileAstId, FileId, Span, SpanAnchor, SyntaxContextId};
 use stdx::format_to;
-use syntax_bridge::insert_whitespace_into_node::insert_ws_into;
 use tt::{TextRange, TextSize};
 
 use crate::DeclarativeMacro;
 
+#[expect(deprecated)]
 fn check_(
     def_edition: Edition,
     call_edition: Edition,
@@ -60,7 +60,14 @@ fn check_(
         format_to!(expect_res, "{:#?}\n\n", res.value.0);
     }
     let (node, _) = syntax_bridge::token_tree_to_syntax_node(&res.value.0, parse, def_edition);
-    format_to!(expect_res, "{}", insert_ws_into(node.syntax_node()));
+    format_to!(
+        expect_res,
+        "{}",
+        syntax_bridge::prettify_macro_expansion::prettify_macro_expansion(
+            node.syntax_node(),
+            &mut |it| it.clone()
+        )
+    );
     expect.assert_eq(&expect_res);
 }
 
diff --git a/src/tools/rust-analyzer/crates/span/src/map.rs b/src/tools/rust-analyzer/crates/span/src/map.rs
index c539754979d..2680e5036c4 100644
--- a/src/tools/rust-analyzer/crates/span/src/map.rs
+++ b/src/tools/rust-analyzer/crates/span/src/map.rs
@@ -104,6 +104,31 @@ where
     pub fn iter(&self) -> impl Iterator<Item = (TextSize, SpanData<S>)> + '_ {
         self.spans.iter().copied()
     }
+
+    /// Merges this span map with another span map, where `other` is inserted at (and replaces) `other_range`.
+    ///
+    /// The length of the replacement node needs to be `other_size`.
+    pub fn merge(&mut self, other_range: TextRange, other_size: TextSize, other: &SpanMap<S>) {
+        self.spans.retain_mut(|(offset, _)| {
+            if other_range.contains(*offset) {
+                false
+            } else {
+                if *offset >= other_range.end() {
+                    *offset += other_size;
+                    *offset -= other_range.len();
+                }
+                true
+            }
+        });
+
+        self.spans
+            .extend(other.spans.iter().map(|&(offset, span)| (offset + other_range.start(), span)));
+
+        self.spans.sort_unstable_by_key(|&(offset, _)| offset);
+
+        // Matched arm info is no longer correct once we have multiple macros.
+        self.matched_arm = None;
+    }
 }
 
 #[derive(PartialEq, Eq, Hash, Debug)]
diff --git a/src/tools/rust-analyzer/crates/syntax-bridge/src/lib.rs b/src/tools/rust-analyzer/crates/syntax-bridge/src/lib.rs
index f23b33127fe..0ccd0886760 100644
--- a/src/tools/rust-analyzer/crates/syntax-bridge/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/syntax-bridge/src/lib.rs
@@ -17,7 +17,7 @@ use tt::{
     token_to_literal,
 };
 
-pub mod insert_whitespace_into_node;
+pub mod prettify_macro_expansion;
 mod to_parser_input;
 pub use to_parser_input::to_parser_input;
 // FIXME: we probably should re-think  `token_tree_to_syntax_node` interfaces
diff --git a/src/tools/rust-analyzer/crates/syntax-bridge/src/insert_whitespace_into_node.rs b/src/tools/rust-analyzer/crates/syntax-bridge/src/prettify_macro_expansion.rs
index a61fca9ec52..fc7caaa9886 100644
--- a/src/tools/rust-analyzer/crates/syntax-bridge/src/insert_whitespace_into_node.rs
+++ b/src/tools/rust-analyzer/crates/syntax-bridge/src/prettify_macro_expansion.rs
@@ -8,10 +8,19 @@ use syntax::{
 };
 
 /// Renders a [`SyntaxNode`] with whitespace inserted between tokens that require them.
-pub fn insert_ws_into(syn: SyntaxNode) -> SyntaxNode {
+///
+/// This is an internal API that is only exported because `mbe` needs it for tests and cannot depend
+/// on `hir-expand`. For any purpose other than tests, you are supposed to use the `prettify_macro_expansion`
+/// from `hir-expand` that handles `$crate` for you.
+#[deprecated = "use `hir_expand::prettify_macro_expansion()` instead"]
+pub fn prettify_macro_expansion(
+    syn: SyntaxNode,
+    dollar_crate_replacement: &mut dyn FnMut(&SyntaxToken) -> SyntaxToken,
+) -> SyntaxNode {
     let mut indent = 0;
     let mut last: Option<SyntaxKind> = None;
     let mut mods = Vec::new();
+    let mut dollar_crate_replacements = Vec::new();
     let syn = syn.clone_subtree().clone_for_update();
 
     let before = Position::before;
@@ -49,6 +58,9 @@ pub fn insert_ws_into(syn: SyntaxNode) -> SyntaxNode {
             }
             _ => continue,
         };
+        if token.kind() == SyntaxKind::IDENT && token.text() == "$crate" {
+            dollar_crate_replacements.push((token.clone(), dollar_crate_replacement(&token)));
+        }
         let tok = &token;
 
         let is_next = |f: fn(SyntaxKind) -> bool, default| -> bool {
@@ -120,6 +132,9 @@ pub fn insert_ws_into(syn: SyntaxNode) -> SyntaxNode {
     for (pos, insert) in mods {
         ted::insert(pos, insert);
     }
+    for (old, new) in dollar_crate_replacements {
+        ted::replace(old, new);
+    }
 
     if let Some(it) = syn.last_token().filter(|it| it.kind() == SyntaxKind::WHITESPACE) {
         ted::remove(it);
diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs
index 2eb9c1ec5a5..fcdc97ce327 100644
--- a/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs
+++ b/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs
@@ -1162,7 +1162,7 @@ pub mod tokens {
 
     pub(super) static SOURCE_FILE: LazyLock<Parse<SourceFile>> = LazyLock::new(|| {
         SourceFile::parse(
-            "const C: <()>::Item = ( true && true , true || true , 1 != 1, 2 == 2, 3 < 3, 4 <= 4, 5 > 5, 6 >= 6, !true, *p, &p , &mut p, async { let _ @ [] })\n;\n\nimpl A for B where: {}", Edition::CURRENT,
+            "use crate::foo; const C: <()>::Item = ( true && true , true || true , 1 != 1, 2 == 2, 3 < 3, 4 <= 4, 5 > 5, 6 >= 6, !true, *p, &p , &mut p, async { let _ @ [] })\n;\n\nimpl A for B where: {}", Edition::CURRENT,
         )
     });
 
@@ -1188,6 +1188,17 @@ pub mod tokens {
             .unwrap()
     }
 
+    pub fn crate_kw() -> SyntaxToken {
+        SOURCE_FILE
+            .tree()
+            .syntax()
+            .clone_for_update()
+            .descendants_with_tokens()
+            .filter_map(|it| it.into_token())
+            .find(|it| it.kind() == CRATE_KW)
+            .unwrap()
+    }
+
     pub fn whitespace(text: &str) -> SyntaxToken {
         assert!(text.trim().is_empty());
         let sf = SourceFile::parse(text, Edition::CURRENT).ok().unwrap();