about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2023-03-25 17:22:45 +0000
committerbors <bors@rust-lang.org>2023-03-25 17:22:45 +0000
commit90340b71ad0b640c344a0c6ef6d7da8a6673d762 (patch)
treee59745012662a9121d025e49b7c6b6a7dd4b8468
parent71b23360e781a00e51ed702a96e181261e92c6c5 (diff)
parente5f24a6d7c6e91e590e72b9ffd107a398c2bdebd (diff)
downloadrust-90340b71ad0b640c344a0c6ef6d7da8a6673d762.tar.gz
rust-90340b71ad0b640c344a0c6ef6d7da8a6673d762.zip
Auto merge of #14405 - Veykril:proc-macro-loading, r=Veykril
feat: Load proc-macros asynchronously

Closes https://github.com/rust-lang/rust-analyzer/issues/8646
-rw-r--r--crates/base-db/src/change.rs10
-rw-r--r--crates/base-db/src/fixture.rs17
-rw-r--r--crates/base-db/src/input.rs29
-rw-r--r--crates/base-db/src/lib.rs8
-rw-r--r--crates/hir-def/src/nameres/collector.rs44
-rw-r--r--crates/hir-expand/src/proc_macro.rs9
-rw-r--r--crates/ide-db/src/apply_change.rs1
-rw-r--r--crates/ide-db/src/lib.rs1
-rw-r--r--crates/ide/src/lib.rs1
-rw-r--r--crates/ide/src/shuffle_crate_graph.rs7
-rw-r--r--crates/project-model/src/tests.rs104
-rw-r--r--crates/project-model/src/workspace.rs77
-rw-r--r--crates/rust-analyzer/src/cli/load_cargo.rs40
-rw-r--r--crates/rust-analyzer/src/global_state.rs10
-rw-r--r--crates/rust-analyzer/src/handlers.rs11
-rw-r--r--crates/rust-analyzer/src/lsp_ext.rs8
-rw-r--r--crates/rust-analyzer/src/main_loop.rs19
-rw-r--r--crates/rust-analyzer/src/reload.rs106
-rw-r--r--docs/dev/lsp-extensions.md2
-rw-r--r--editors/code/src/commands.ts4
-rw-r--r--editors/code/src/ctx.ts5
-rw-r--r--editors/code/src/lsp_ext.ts1
-rw-r--r--editors/code/src/main.ts1
23 files changed, 288 insertions, 227 deletions
diff --git a/crates/base-db/src/change.rs b/crates/base-db/src/change.rs
index b57f2345767..b906511dbcf 100644
--- a/crates/base-db/src/change.rs
+++ b/crates/base-db/src/change.rs
@@ -6,7 +6,7 @@ use std::{fmt, sync::Arc};
 use salsa::Durability;
 use vfs::FileId;
 
-use crate::{CrateGraph, SourceDatabaseExt, SourceRoot, SourceRootId};
+use crate::{CrateGraph, ProcMacros, SourceDatabaseExt, SourceRoot, SourceRootId};
 
 /// Encapsulate a bunch of raw `.set` calls on the database.
 #[derive(Default)]
@@ -14,6 +14,7 @@ pub struct Change {
     pub roots: Option<Vec<SourceRoot>>,
     pub files_changed: Vec<(FileId, Option<Arc<String>>)>,
     pub crate_graph: Option<CrateGraph>,
+    pub proc_macros: Option<ProcMacros>,
 }
 
 impl fmt::Debug for Change {
@@ -49,6 +50,10 @@ impl Change {
         self.crate_graph = Some(graph);
     }
 
+    pub fn set_proc_macros(&mut self, proc_macros: ProcMacros) {
+        self.proc_macros = Some(proc_macros);
+    }
+
     pub fn apply(self, db: &mut dyn SourceDatabaseExt) {
         let _p = profile::span("RootDatabase::apply_change");
         if let Some(roots) = self.roots {
@@ -73,6 +78,9 @@ impl Change {
         if let Some(crate_graph) = self.crate_graph {
             db.set_crate_graph_with_durability(Arc::new(crate_graph), Durability::HIGH)
         }
+        if let Some(proc_macros) = self.proc_macros {
+            db.set_proc_macros_with_durability(Arc::new(proc_macros), Durability::HIGH)
+        }
     }
 }
 
diff --git a/crates/base-db/src/fixture.rs b/crates/base-db/src/fixture.rs
index 8a7e9dfadfe..7269180a5d6 100644
--- a/crates/base-db/src/fixture.rs
+++ b/crates/base-db/src/fixture.rs
@@ -12,8 +12,8 @@ use vfs::{file_set::FileSet, VfsPath};
 use crate::{
     input::{CrateName, CrateOrigin, LangCrateOrigin},
     Change, CrateDisplayName, CrateGraph, CrateId, Dependency, Edition, Env, FileId, FilePosition,
-    FileRange, ProcMacro, ProcMacroExpander, ProcMacroExpansionError, SourceDatabaseExt,
-    SourceRoot, SourceRootId,
+    FileRange, ProcMacro, ProcMacroExpander, ProcMacroExpansionError, ProcMacros,
+    SourceDatabaseExt, SourceRoot, SourceRootId,
 };
 
 pub const WORKSPACE: SourceRootId = SourceRootId(0);
@@ -100,7 +100,7 @@ impl ChangeFixture {
 
     pub fn parse_with_proc_macros(
         ra_fixture: &str,
-        mut proc_macros: Vec<(String, ProcMacro)>,
+        mut proc_macro_defs: Vec<(String, ProcMacro)>,
     ) -> ChangeFixture {
         let (mini_core, proc_macro_names, fixture) = Fixture::parse(ra_fixture);
         let mut change = Change::new();
@@ -160,7 +160,6 @@ impl ChangeFixture {
                     meta.cfg.clone(),
                     meta.cfg,
                     meta.env,
-                    Ok(Vec::new()),
                     false,
                     origin,
                     meta.target_data_layout
@@ -200,7 +199,6 @@ impl ChangeFixture {
                 default_cfg.clone(),
                 default_cfg,
                 Env::default(),
-                Ok(Vec::new()),
                 false,
                 CrateOrigin::CratesIo { repo: None, name: None },
                 default_target_data_layout
@@ -244,7 +242,6 @@ impl ChangeFixture {
                 CfgOptions::default(),
                 CfgOptions::default(),
                 Env::default(),
-                Ok(Vec::new()),
                 false,
                 CrateOrigin::Lang(LangCrateOrigin::Core),
                 target_layout.clone(),
@@ -257,12 +254,13 @@ impl ChangeFixture {
             }
         }
 
+        let mut proc_macros = ProcMacros::default();
         if !proc_macro_names.is_empty() {
             let proc_lib_file = file_id;
             file_id.0 += 1;
 
-            proc_macros.extend(default_test_proc_macros());
-            let (proc_macro, source) = filter_test_proc_macros(&proc_macro_names, proc_macros);
+            proc_macro_defs.extend(default_test_proc_macros());
+            let (proc_macro, source) = filter_test_proc_macros(&proc_macro_names, proc_macro_defs);
             let mut fs = FileSet::default();
             fs.insert(
                 proc_lib_file,
@@ -282,11 +280,11 @@ impl ChangeFixture {
                 CfgOptions::default(),
                 CfgOptions::default(),
                 Env::default(),
-                Ok(proc_macro),
                 true,
                 CrateOrigin::CratesIo { repo: None, name: None },
                 target_layout,
             );
+            proc_macros.insert(proc_macros_crate, Ok(proc_macro));
 
             for krate in all_crates {
                 crate_graph
@@ -305,6 +303,7 @@ impl ChangeFixture {
         roots.push(root);
         change.set_roots(roots);
         change.set_crate_graph(crate_graph);
+        change.set_proc_macros(proc_macros);
 
         ChangeFixture { file_position, files, change }
     }
diff --git a/crates/base-db/src/input.rs b/crates/base-db/src/input.rs
index 43388e915b5..9580b76faa2 100644
--- a/crates/base-db/src/input.rs
+++ b/crates/base-db/src/input.rs
@@ -6,14 +6,17 @@
 //! actual IO. See `vfs` and `project_model` in the `rust-analyzer` crate for how
 //! actual IO is done and lowered to input.
 
-use std::{fmt, ops, panic::RefUnwindSafe, str::FromStr, sync::Arc};
+use std::{fmt, mem, ops, panic::RefUnwindSafe, str::FromStr, sync::Arc};
 
 use cfg::CfgOptions;
 use rustc_hash::FxHashMap;
 use stdx::hash::{NoHashHashMap, NoHashHashSet};
 use syntax::SmolStr;
 use tt::token_id::Subtree;
-use vfs::{file_set::FileSet, AnchoredPath, FileId, VfsPath};
+use vfs::{file_set::FileSet, AbsPathBuf, AnchoredPath, FileId, VfsPath};
+
+pub type ProcMacroPaths = FxHashMap<CrateId, Result<(Option<String>, AbsPathBuf), String>>;
+pub type ProcMacros = FxHashMap<CrateId, ProcMacroLoadResult>;
 
 /// Files are grouped into source roots. A source root is a directory on the
 /// file systems which is watched for changes. Typically it corresponds to a
@@ -269,7 +272,6 @@ pub struct CrateData {
     pub target_layout: TargetLayoutLoadResult,
     pub env: Env,
     pub dependencies: Vec<Dependency>,
-    pub proc_macro: ProcMacroLoadResult,
     pub origin: CrateOrigin,
     pub is_proc_macro: bool,
 }
@@ -322,7 +324,6 @@ impl CrateGraph {
         cfg_options: CfgOptions,
         potential_cfg_options: CfgOptions,
         env: Env,
-        proc_macro: ProcMacroLoadResult,
         is_proc_macro: bool,
         origin: CrateOrigin,
         target_layout: Result<Arc<str>, Arc<str>>,
@@ -335,7 +336,6 @@ impl CrateGraph {
             cfg_options,
             potential_cfg_options,
             env,
-            proc_macro,
             dependencies: Vec::new(),
             origin,
             target_layout,
@@ -456,11 +456,11 @@ impl CrateGraph {
     }
 
     /// Extends this crate graph by adding a complete disjoint second crate
-    /// graph.
+    /// graph and adjust the ids in the [`ProcMacroPaths`] accordingly.
     ///
     /// The ids of the crates in the `other` graph are shifted by the return
     /// amount.
-    pub fn extend(&mut self, other: CrateGraph) -> u32 {
+    pub fn extend(&mut self, other: CrateGraph, proc_macros: &mut ProcMacroPaths) -> u32 {
         let start = self.arena.len() as u32;
         self.arena.extend(other.arena.into_iter().map(|(id, mut data)| {
             let new_id = id.shift(start);
@@ -469,6 +469,11 @@ impl CrateGraph {
             }
             (new_id, data)
         }));
+
+        *proc_macros = mem::take(proc_macros)
+            .into_iter()
+            .map(|(id, macros)| (id.shift(start), macros))
+            .collect();
         start
     }
 
@@ -645,7 +650,6 @@ mod tests {
             CfgOptions::default(),
             CfgOptions::default(),
             Env::default(),
-            Ok(Vec::new()),
             false,
             CrateOrigin::CratesIo { repo: None, name: None },
             Err("".into()),
@@ -658,7 +662,6 @@ mod tests {
             CfgOptions::default(),
             CfgOptions::default(),
             Env::default(),
-            Ok(Vec::new()),
             false,
             CrateOrigin::CratesIo { repo: None, name: None },
             Err("".into()),
@@ -671,7 +674,6 @@ mod tests {
             CfgOptions::default(),
             CfgOptions::default(),
             Env::default(),
-            Ok(Vec::new()),
             false,
             CrateOrigin::CratesIo { repo: None, name: None },
             Err("".into()),
@@ -698,7 +700,6 @@ mod tests {
             CfgOptions::default(),
             CfgOptions::default(),
             Env::default(),
-            Ok(Vec::new()),
             false,
             CrateOrigin::CratesIo { repo: None, name: None },
             Err("".into()),
@@ -711,7 +712,6 @@ mod tests {
             CfgOptions::default(),
             CfgOptions::default(),
             Env::default(),
-            Ok(Vec::new()),
             false,
             CrateOrigin::CratesIo { repo: None, name: None },
             Err("".into()),
@@ -735,7 +735,6 @@ mod tests {
             CfgOptions::default(),
             CfgOptions::default(),
             Env::default(),
-            Ok(Vec::new()),
             false,
             CrateOrigin::CratesIo { repo: None, name: None },
             Err("".into()),
@@ -748,7 +747,6 @@ mod tests {
             CfgOptions::default(),
             CfgOptions::default(),
             Env::default(),
-            Ok(Vec::new()),
             false,
             CrateOrigin::CratesIo { repo: None, name: None },
             Err("".into()),
@@ -761,7 +759,6 @@ mod tests {
             CfgOptions::default(),
             CfgOptions::default(),
             Env::default(),
-            Ok(Vec::new()),
             false,
             CrateOrigin::CratesIo { repo: None, name: None },
             Err("".into()),
@@ -785,7 +782,6 @@ mod tests {
             CfgOptions::default(),
             CfgOptions::default(),
             Env::default(),
-            Ok(Vec::new()),
             false,
             CrateOrigin::CratesIo { repo: None, name: None },
             Err("".into()),
@@ -798,7 +794,6 @@ mod tests {
             CfgOptions::default(),
             CfgOptions::default(),
             Env::default(),
-            Ok(Vec::new()),
             false,
             CrateOrigin::CratesIo { repo: None, name: None },
             Err("".into()),
diff --git a/crates/base-db/src/lib.rs b/crates/base-db/src/lib.rs
index 9720db9d8ac..f6975f2fbd7 100644
--- a/crates/base-db/src/lib.rs
+++ b/crates/base-db/src/lib.rs
@@ -16,8 +16,8 @@ pub use crate::{
     input::{
         CrateData, CrateDisplayName, CrateGraph, CrateId, CrateName, CrateOrigin, Dependency,
         Edition, Env, LangCrateOrigin, ProcMacro, ProcMacroExpander, ProcMacroExpansionError,
-        ProcMacroId, ProcMacroKind, ProcMacroLoadResult, SourceRoot, SourceRootId,
-        TargetLayoutLoadResult,
+        ProcMacroId, ProcMacroKind, ProcMacroLoadResult, ProcMacroPaths, ProcMacros, SourceRoot,
+        SourceRootId, TargetLayoutLoadResult,
     },
 };
 pub use salsa::{self, Cancelled};
@@ -73,6 +73,10 @@ pub trait SourceDatabase: FileLoader + std::fmt::Debug {
     /// The crate graph.
     #[salsa::input]
     fn crate_graph(&self) -> Arc<CrateGraph>;
+
+    /// The crate graph.
+    #[salsa::input]
+    fn proc_macros(&self) -> Arc<ProcMacros>;
 }
 
 fn parse_query(db: &dyn SourceDatabase, file_id: FileId) -> Parse<ast::SourceFile> {
diff --git a/crates/hir-def/src/nameres/collector.rs b/crates/hir-def/src/nameres/collector.rs
index ddcee77ec4c..b3ce913d9ab 100644
--- a/crates/hir-def/src/nameres/collector.rs
+++ b/crates/hir-def/src/nameres/collector.rs
@@ -78,25 +78,35 @@ pub(super) fn collect_defs(db: &dyn DefDatabase, mut def_map: DefMap, tree_id: T
     }
 
     let cfg_options = &krate.cfg_options;
-    let proc_macros = match &krate.proc_macro {
-        Ok(proc_macros) => {
-            proc_macros
-                .iter()
-                .enumerate()
-                .map(|(idx, it)| {
-                    // FIXME: a hacky way to create a Name from string.
-                    let name =
-                        tt::Ident { text: it.name.clone(), span: tt::TokenId::unspecified() };
-                    (name.as_name(), ProcMacroExpander::new(base_db::ProcMacroId(idx as u32)))
-                })
-                .collect()
-        }
-        Err(e) => {
-            def_map.proc_macro_loading_error = Some(e.clone().into_boxed_str());
-            Vec::new()
+
+    let is_proc_macro = krate.is_proc_macro;
+    let proc_macros = if is_proc_macro {
+        match db.proc_macros().get(&def_map.krate) {
+            Some(Ok(proc_macros)) => {
+                proc_macros
+                    .iter()
+                    .enumerate()
+                    .map(|(idx, it)| {
+                        // FIXME: a hacky way to create a Name from string.
+                        let name =
+                            tt::Ident { text: it.name.clone(), span: tt::TokenId::unspecified() };
+                        (name.as_name(), ProcMacroExpander::new(base_db::ProcMacroId(idx as u32)))
+                    })
+                    .collect()
+            }
+            Some(Err(e)) => {
+                def_map.proc_macro_loading_error = Some(e.clone().into_boxed_str());
+                Vec::new()
+            }
+            None => {
+                def_map.proc_macro_loading_error =
+                    Some("No proc-macros present for crate".to_owned().into_boxed_str());
+                Vec::new()
+            }
         }
+    } else {
+        vec![]
     };
-    let is_proc_macro = krate.is_proc_macro;
 
     let mut collector = DefCollector {
         db,
diff --git a/crates/hir-expand/src/proc_macro.rs b/crates/hir-expand/src/proc_macro.rs
index d758e9302cd..ad989358740 100644
--- a/crates/hir-expand/src/proc_macro.rs
+++ b/crates/hir-expand/src/proc_macro.rs
@@ -33,10 +33,10 @@ impl ProcMacroExpander {
     ) -> ExpandResult<tt::Subtree> {
         match self.proc_macro_id {
             Some(id) => {
-                let krate_graph = db.crate_graph();
-                let proc_macros = match &krate_graph[def_crate].proc_macro {
-                    Ok(proc_macros) => proc_macros,
-                    Err(_) => {
+                let proc_macros = db.proc_macros();
+                let proc_macros = match proc_macros.get(&def_crate) {
+                    Some(Ok(proc_macros)) => proc_macros,
+                    Some(Err(_)) | None => {
                         never!("Non-dummy expander even though there are no proc macros");
                         return ExpandResult::with_err(
                             tt::Subtree::empty(),
@@ -59,6 +59,7 @@ impl ProcMacroExpander {
                     }
                 };
 
+                let krate_graph = db.crate_graph();
                 // Proc macros have access to the environment variables of the invoking crate.
                 let env = &krate_graph[calling_crate].env;
                 match proc_macro.expander.expand(tt, attr_arg, env) {
diff --git a/crates/ide-db/src/apply_change.rs b/crates/ide-db/src/apply_change.rs
index ea1d9cc4919..3b8458980c6 100644
--- a/crates/ide-db/src/apply_change.rs
+++ b/crates/ide-db/src/apply_change.rs
@@ -64,6 +64,7 @@ impl RootDatabase {
             // SourceDatabase
             base_db::ParseQuery
             base_db::CrateGraphQuery
+            base_db::ProcMacrosQuery
 
             // SourceDatabaseExt
             base_db::FileTextQuery
diff --git a/crates/ide-db/src/lib.rs b/crates/ide-db/src/lib.rs
index b1df11bf911..f9b8a502d99 100644
--- a/crates/ide-db/src/lib.rs
+++ b/crates/ide-db/src/lib.rs
@@ -137,6 +137,7 @@ impl RootDatabase {
     pub fn new(lru_capacity: Option<usize>) -> RootDatabase {
         let mut db = RootDatabase { storage: ManuallyDrop::new(salsa::Storage::default()) };
         db.set_crate_graph_with_durability(Default::default(), Durability::HIGH);
+        db.set_proc_macros_with_durability(Default::default(), Durability::HIGH);
         db.set_local_roots_with_durability(Default::default(), Durability::HIGH);
         db.set_library_roots_with_durability(Default::default(), Durability::HIGH);
         db.set_enable_proc_attr_macros(false);
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index 078b66dd395..8477a8e6228 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -235,7 +235,6 @@ impl Analysis {
             cfg_options.clone(),
             cfg_options,
             Env::default(),
-            Ok(Vec::new()),
             false,
             CrateOrigin::CratesIo { repo: None, name: None },
             Err("Analysis::from_single_file has no target layout".into()),
diff --git a/crates/ide/src/shuffle_crate_graph.rs b/crates/ide/src/shuffle_crate_graph.rs
index e606072a823..471c36dfecf 100644
--- a/crates/ide/src/shuffle_crate_graph.rs
+++ b/crates/ide/src/shuffle_crate_graph.rs
@@ -1,7 +1,7 @@
 use std::sync::Arc;
 
 use ide_db::{
-    base_db::{salsa::Durability, CrateGraph, SourceDatabase},
+    base_db::{salsa::Durability, CrateGraph, ProcMacros, SourceDatabase},
     FxHashMap, RootDatabase,
 };
 
@@ -16,6 +16,7 @@ use ide_db::{
 // |===
 pub(crate) fn shuffle_crate_graph(db: &mut RootDatabase) {
     let crate_graph = db.crate_graph();
+    let proc_macros = db.proc_macros();
 
     let mut shuffled_ids = crate_graph.iter().collect::<Vec<_>>();
 
@@ -23,6 +24,7 @@ pub(crate) fn shuffle_crate_graph(db: &mut RootDatabase) {
     stdx::rand::shuffle(&mut shuffled_ids, |i| rng.rand_range(0..i as u32) as usize);
 
     let mut new_graph = CrateGraph::default();
+    let mut new_proc_macros = ProcMacros::default();
 
     let mut map = FxHashMap::default();
     for old_id in shuffled_ids.iter().copied() {
@@ -35,11 +37,11 @@ pub(crate) fn shuffle_crate_graph(db: &mut RootDatabase) {
             data.cfg_options.clone(),
             data.potential_cfg_options.clone(),
             data.env.clone(),
-            data.proc_macro.clone(),
             data.is_proc_macro,
             data.origin.clone(),
             data.target_layout.clone(),
         );
+        new_proc_macros.insert(new_id, proc_macros[&old_id].clone());
         map.insert(old_id, new_id);
     }
 
@@ -53,4 +55,5 @@ pub(crate) fn shuffle_crate_graph(db: &mut RootDatabase) {
     }
 
     db.set_crate_graph_with_durability(Arc::new(new_graph), Durability::HIGH);
+    db.set_proc_macros_with_durability(Arc::new(new_proc_macros), Durability::HIGH);
 }
diff --git a/crates/project-model/src/tests.rs b/crates/project-model/src/tests.rs
index 3754accbb03..26c4c89f764 100644
--- a/crates/project-model/src/tests.rs
+++ b/crates/project-model/src/tests.rs
@@ -3,7 +3,7 @@ use std::{
     path::{Path, PathBuf},
 };
 
-use base_db::{CrateGraph, FileId};
+use base_db::{CrateGraph, FileId, ProcMacroPaths};
 use cfg::{CfgAtom, CfgDiff};
 use expect_test::{expect, Expect};
 use paths::{AbsPath, AbsPathBuf};
@@ -14,11 +14,14 @@ use crate::{
     WorkspaceBuildScripts,
 };
 
-fn load_cargo(file: &str) -> CrateGraph {
+fn load_cargo(file: &str) -> (CrateGraph, ProcMacroPaths) {
     load_cargo_with_overrides(file, CfgOverrides::default())
 }
 
-fn load_cargo_with_overrides(file: &str, cfg_overrides: CfgOverrides) -> CrateGraph {
+fn load_cargo_with_overrides(
+    file: &str,
+    cfg_overrides: CfgOverrides,
+) -> (CrateGraph, ProcMacroPaths) {
     let meta = get_test_json_file(file);
     let cargo_workspace = CargoWorkspace::new(meta);
     let project_workspace = ProjectWorkspace::Cargo {
@@ -34,7 +37,7 @@ fn load_cargo_with_overrides(file: &str, cfg_overrides: CfgOverrides) -> CrateGr
     to_crate_graph(project_workspace)
 }
 
-fn load_rust_project(file: &str) -> CrateGraph {
+fn load_rust_project(file: &str) -> (CrateGraph, ProcMacroPaths) {
     let data = get_test_json_file(file);
     let project = rooted_project_json(data);
     let sysroot = Ok(get_fake_sysroot());
@@ -92,9 +95,8 @@ fn rooted_project_json(data: ProjectJsonData) -> ProjectJson {
     ProjectJson::new(base, data)
 }
 
-fn to_crate_graph(project_workspace: ProjectWorkspace) -> CrateGraph {
+fn to_crate_graph(project_workspace: ProjectWorkspace) -> (CrateGraph, ProcMacroPaths) {
     project_workspace.to_crate_graph(
-        &mut |_, _| Ok(Vec::new()),
         &mut {
             let mut counter = 0;
             move |_path| {
@@ -117,7 +119,8 @@ fn cargo_hello_world_project_model_with_wildcard_overrides() {
     let cfg_overrides = CfgOverrides::Wildcard(
         CfgDiff::new(Vec::new(), vec![CfgAtom::Flag("test".into())]).unwrap(),
     );
-    let crate_graph = load_cargo_with_overrides("hello-world-metadata.json", cfg_overrides);
+    let (crate_graph, _proc_macros) =
+        load_cargo_with_overrides("hello-world-metadata.json", cfg_overrides);
     check_crate_graph(
         crate_graph,
         expect![[r#"
@@ -184,9 +187,6 @@ fn cargo_hello_world_project_model_with_wildcard_overrides() {
                                 prelude: true,
                             },
                         ],
-                        proc_macro: Err(
-                            "crate has not (yet) been built",
-                        ),
                         origin: CratesIo {
                             repo: None,
                             name: Some(
@@ -265,9 +265,6 @@ fn cargo_hello_world_project_model_with_wildcard_overrides() {
                                 prelude: true,
                             },
                         ],
-                        proc_macro: Err(
-                            "crate has not (yet) been built",
-                        ),
                         origin: CratesIo {
                             repo: None,
                             name: Some(
@@ -346,9 +343,6 @@ fn cargo_hello_world_project_model_with_wildcard_overrides() {
                                 prelude: true,
                             },
                         ],
-                        proc_macro: Err(
-                            "crate has not (yet) been built",
-                        ),
                         origin: CratesIo {
                             repo: None,
                             name: Some(
@@ -427,9 +421,6 @@ fn cargo_hello_world_project_model_with_wildcard_overrides() {
                                 prelude: true,
                             },
                         ],
-                        proc_macro: Err(
-                            "crate has not (yet) been built",
-                        ),
                         origin: CratesIo {
                             repo: None,
                             name: Some(
@@ -498,9 +489,6 @@ fn cargo_hello_world_project_model_with_wildcard_overrides() {
                             },
                         },
                         dependencies: [],
-                        proc_macro: Err(
-                            "crate has not (yet) been built",
-                        ),
                         origin: CratesIo {
                             repo: Some(
                                 "https://github.com/rust-lang/libc",
@@ -527,7 +515,8 @@ fn cargo_hello_world_project_model_with_selective_overrides() {
             .collect(),
         )
     };
-    let crate_graph = load_cargo_with_overrides("hello-world-metadata.json", cfg_overrides);
+    let (crate_graph, _proc_macros) =
+        load_cargo_with_overrides("hello-world-metadata.json", cfg_overrides);
     check_crate_graph(
         crate_graph,
         expect![[r#"
@@ -596,9 +585,6 @@ fn cargo_hello_world_project_model_with_selective_overrides() {
                                 prelude: true,
                             },
                         ],
-                        proc_macro: Err(
-                            "crate has not (yet) been built",
-                        ),
                         origin: CratesIo {
                             repo: None,
                             name: Some(
@@ -679,9 +665,6 @@ fn cargo_hello_world_project_model_with_selective_overrides() {
                                 prelude: true,
                             },
                         ],
-                        proc_macro: Err(
-                            "crate has not (yet) been built",
-                        ),
                         origin: CratesIo {
                             repo: None,
                             name: Some(
@@ -762,9 +745,6 @@ fn cargo_hello_world_project_model_with_selective_overrides() {
                                 prelude: true,
                             },
                         ],
-                        proc_macro: Err(
-                            "crate has not (yet) been built",
-                        ),
                         origin: CratesIo {
                             repo: None,
                             name: Some(
@@ -845,9 +825,6 @@ fn cargo_hello_world_project_model_with_selective_overrides() {
                                 prelude: true,
                             },
                         ],
-                        proc_macro: Err(
-                            "crate has not (yet) been built",
-                        ),
                         origin: CratesIo {
                             repo: None,
                             name: Some(
@@ -916,9 +893,6 @@ fn cargo_hello_world_project_model_with_selective_overrides() {
                             },
                         },
                         dependencies: [],
-                        proc_macro: Err(
-                            "crate has not (yet) been built",
-                        ),
                         origin: CratesIo {
                             repo: Some(
                                 "https://github.com/rust-lang/libc",
@@ -936,7 +910,7 @@ fn cargo_hello_world_project_model_with_selective_overrides() {
 
 #[test]
 fn cargo_hello_world_project_model() {
-    let crate_graph = load_cargo("hello-world-metadata.json");
+    let (crate_graph, _proc_macros) = load_cargo("hello-world-metadata.json");
     check_crate_graph(
         crate_graph,
         expect![[r#"
@@ -1005,9 +979,6 @@ fn cargo_hello_world_project_model() {
                                 prelude: true,
                             },
                         ],
-                        proc_macro: Err(
-                            "crate has not (yet) been built",
-                        ),
                         origin: CratesIo {
                             repo: None,
                             name: Some(
@@ -1088,9 +1059,6 @@ fn cargo_hello_world_project_model() {
                                 prelude: true,
                             },
                         ],
-                        proc_macro: Err(
-                            "crate has not (yet) been built",
-                        ),
                         origin: CratesIo {
                             repo: None,
                             name: Some(
@@ -1171,9 +1139,6 @@ fn cargo_hello_world_project_model() {
                                 prelude: true,
                             },
                         ],
-                        proc_macro: Err(
-                            "crate has not (yet) been built",
-                        ),
                         origin: CratesIo {
                             repo: None,
                             name: Some(
@@ -1254,9 +1219,6 @@ fn cargo_hello_world_project_model() {
                                 prelude: true,
                             },
                         ],
-                        proc_macro: Err(
-                            "crate has not (yet) been built",
-                        ),
                         origin: CratesIo {
                             repo: None,
                             name: Some(
@@ -1325,9 +1287,6 @@ fn cargo_hello_world_project_model() {
                             },
                         },
                         dependencies: [],
-                        proc_macro: Err(
-                            "crate has not (yet) been built",
-                        ),
                         origin: CratesIo {
                             repo: Some(
                                 "https://github.com/rust-lang/libc",
@@ -1345,7 +1304,7 @@ fn cargo_hello_world_project_model() {
 
 #[test]
 fn rust_project_hello_world_project_model() {
-    let crate_graph = load_rust_project("hello-world-project.json");
+    let (crate_graph, _proc_macros) = load_rust_project("hello-world-project.json");
     check_crate_graph(
         crate_graph,
         expect![[r#"
@@ -1390,9 +1349,6 @@ fn rust_project_hello_world_project_model() {
                                 prelude: true,
                             },
                         ],
-                        proc_macro: Err(
-                            "no proc macro loaded for sysroot crate",
-                        ),
                         origin: Lang(
                             Alloc,
                         ),
@@ -1427,9 +1383,6 @@ fn rust_project_hello_world_project_model() {
                             entries: {},
                         },
                         dependencies: [],
-                        proc_macro: Err(
-                            "no proc macro loaded for sysroot crate",
-                        ),
                         origin: Lang(
                             Core,
                         ),
@@ -1464,9 +1417,6 @@ fn rust_project_hello_world_project_model() {
                             entries: {},
                         },
                         dependencies: [],
-                        proc_macro: Err(
-                            "no proc macro loaded for sysroot crate",
-                        ),
                         origin: Lang(
                             Other,
                         ),
@@ -1501,9 +1451,6 @@ fn rust_project_hello_world_project_model() {
                             entries: {},
                         },
                         dependencies: [],
-                        proc_macro: Err(
-                            "no proc macro loaded for sysroot crate",
-                        ),
                         origin: Lang(
                             Other,
                         ),
@@ -1557,9 +1504,6 @@ fn rust_project_hello_world_project_model() {
                                 prelude: true,
                             },
                         ],
-                        proc_macro: Err(
-                            "no proc macro loaded for sysroot crate",
-                        ),
                         origin: Lang(
                             Other,
                         ),
@@ -1594,9 +1538,6 @@ fn rust_project_hello_world_project_model() {
                             entries: {},
                         },
                         dependencies: [],
-                        proc_macro: Err(
-                            "no proc macro loaded for sysroot crate",
-                        ),
                         origin: Lang(
                             Other,
                         ),
@@ -1704,9 +1645,6 @@ fn rust_project_hello_world_project_model() {
                                 prelude: true,
                             },
                         ],
-                        proc_macro: Err(
-                            "no proc macro loaded for sysroot crate",
-                        ),
                         origin: Lang(
                             Std,
                         ),
@@ -1741,9 +1679,6 @@ fn rust_project_hello_world_project_model() {
                             entries: {},
                         },
                         dependencies: [],
-                        proc_macro: Err(
-                            "no proc macro loaded for sysroot crate",
-                        ),
                         origin: Lang(
                             Other,
                         ),
@@ -1778,9 +1713,6 @@ fn rust_project_hello_world_project_model() {
                             entries: {},
                         },
                         dependencies: [],
-                        proc_macro: Err(
-                            "no proc macro loaded for sysroot crate",
-                        ),
                         origin: Lang(
                             Test,
                         ),
@@ -1815,9 +1747,6 @@ fn rust_project_hello_world_project_model() {
                             entries: {},
                         },
                         dependencies: [],
-                        proc_macro: Err(
-                            "no proc macro loaded for sysroot crate",
-                        ),
                         origin: Lang(
                             Other,
                         ),
@@ -1889,9 +1818,6 @@ fn rust_project_hello_world_project_model() {
                                 prelude: false,
                             },
                         ],
-                        proc_macro: Err(
-                            "no proc macro dylib present",
-                        ),
                         origin: CratesIo {
                             repo: None,
                             name: Some(
@@ -1907,7 +1833,7 @@ fn rust_project_hello_world_project_model() {
 
 #[test]
 fn rust_project_is_proc_macro_has_proc_macro_dep() {
-    let crate_graph = load_rust_project("is-proc-macro-project.json");
+    let (crate_graph, _proc_macros) = load_rust_project("is-proc-macro-project.json");
     // Since the project only defines one crate (outside the sysroot crates),
     // it should be the one with the biggest Id.
     let crate_id = crate_graph.iter().max().unwrap();
diff --git a/crates/project-model/src/workspace.rs b/crates/project-model/src/workspace.rs
index 2158485a330..1fd7c681938 100644
--- a/crates/project-model/src/workspace.rs
+++ b/crates/project-model/src/workspace.rs
@@ -7,7 +7,7 @@ use std::{collections::VecDeque, fmt, fs, process::Command, sync::Arc};
 use anyhow::{bail, format_err, Context, Result};
 use base_db::{
     CrateDisplayName, CrateGraph, CrateId, CrateName, CrateOrigin, Dependency, Edition, Env,
-    FileId, LangCrateOrigin, ProcMacroLoadResult, TargetLayoutLoadResult,
+    FileId, LangCrateOrigin, ProcMacroPaths, TargetLayoutLoadResult,
 };
 use cfg::{CfgDiff, CfgOptions};
 use paths::{AbsPath, AbsPathBuf};
@@ -576,16 +576,14 @@ impl ProjectWorkspace {
 
     pub fn to_crate_graph(
         &self,
-        load_proc_macro: &mut dyn FnMut(&str, &AbsPath) -> ProcMacroLoadResult,
         load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
         extra_env: &FxHashMap<String, String>,
-    ) -> CrateGraph {
+    ) -> (CrateGraph, ProcMacroPaths) {
         let _p = profile::span("ProjectWorkspace::to_crate_graph");
 
-        let mut crate_graph = match self {
+        let (mut crate_graph, proc_macros) = match self {
             ProjectWorkspace::Json { project, sysroot, rustc_cfg } => project_json_to_crate_graph(
                 rustc_cfg.clone(),
-                load_proc_macro,
                 load,
                 project,
                 sysroot.as_ref().ok(),
@@ -602,7 +600,6 @@ impl ProjectWorkspace {
                 toolchain: _,
                 target_layout,
             } => cargo_to_crate_graph(
-                load_proc_macro,
                 load,
                 rustc.as_ref().ok(),
                 cargo,
@@ -630,7 +627,7 @@ impl ProjectWorkspace {
         } else {
             tracing::debug!("Did not patch std to depend on cfg-if")
         }
-        crate_graph
+        (crate_graph, proc_macros)
     }
 
     pub fn eq_ignore_build_data(&self, other: &Self) -> bool {
@@ -679,14 +676,14 @@ impl ProjectWorkspace {
 
 fn project_json_to_crate_graph(
     rustc_cfg: Vec<CfgFlag>,
-    load_proc_macro: &mut dyn FnMut(&str, &AbsPath) -> ProcMacroLoadResult,
     load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
     project: &ProjectJson,
     sysroot: Option<&Sysroot>,
     extra_env: &FxHashMap<String, String>,
     target_layout: TargetLayoutLoadResult,
-) -> CrateGraph {
+) -> (CrateGraph, ProcMacroPaths) {
     let mut crate_graph = CrateGraph::default();
+    let mut proc_macros = FxHashMap::default();
     let sysroot_deps = sysroot.as_ref().map(|sysroot| {
         sysroot_to_crate_graph(
             &mut crate_graph,
@@ -707,14 +704,15 @@ fn project_json_to_crate_graph(
         })
         .map(|(crate_id, krate, file_id)| {
             let env = krate.env.clone().into_iter().collect();
-            let proc_macro = match krate.proc_macro_dylib_path.clone() {
-                Some(it) => load_proc_macro(
-                    krate.display_name.as_ref().map(|it| it.canonical_name()).unwrap_or(""),
-                    &it,
-                ),
-                None => Err("no proc macro dylib present".into()),
-            };
-
+            if let Some(path) = krate.proc_macro_dylib_path.clone() {
+                proc_macros.insert(
+                    crate_id,
+                    Ok((
+                        krate.display_name.as_ref().map(|it| it.canonical_name().to_owned()),
+                        path,
+                    )),
+                );
+            }
             let target_cfgs = match krate.target.as_deref() {
                 Some(target) => cfg_cache
                     .entry(target)
@@ -734,7 +732,6 @@ fn project_json_to_crate_graph(
                     cfg_options.clone(),
                     cfg_options,
                     env,
-                    proc_macro,
                     krate.is_proc_macro,
                     if krate.display_name.is_some() {
                         CrateOrigin::CratesIo {
@@ -776,11 +773,10 @@ fn project_json_to_crate_graph(
             }
         }
     }
-    crate_graph
+    (crate_graph, proc_macros)
 }
 
 fn cargo_to_crate_graph(
-    load_proc_macro: &mut dyn FnMut(&str, &AbsPath) -> ProcMacroLoadResult,
     load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
     rustc: Option<&(CargoWorkspace, WorkspaceBuildScripts)>,
     cargo: &CargoWorkspace,
@@ -789,9 +785,10 @@ fn cargo_to_crate_graph(
     override_cfg: &CfgOverrides,
     build_scripts: &WorkspaceBuildScripts,
     target_layout: TargetLayoutLoadResult,
-) -> CrateGraph {
+) -> (CrateGraph, ProcMacroPaths) {
     let _p = profile::span("cargo_to_crate_graph");
     let mut crate_graph = CrateGraph::default();
+    let mut proc_macros = FxHashMap::default();
     let (public_deps, libproc_macro) = match sysroot {
         Some(sysroot) => sysroot_to_crate_graph(
             &mut crate_graph,
@@ -855,10 +852,10 @@ fn cargo_to_crate_graph(
             if let Some(file_id) = load(&cargo[tgt].root) {
                 let crate_id = add_target_crate_root(
                     &mut crate_graph,
+                    &mut proc_macros,
                     &cargo[pkg],
                     build_scripts.get_output(pkg),
                     cfg_options.clone(),
-                    &mut |path| load_proc_macro(&cargo[tgt].name, path),
                     file_id,
                     &cargo[tgt].name,
                     cargo[tgt].is_proc_macro,
@@ -931,9 +928,9 @@ fn cargo_to_crate_graph(
         if let Some((rustc_workspace, rustc_build_scripts)) = rustc {
             handle_rustc_crates(
                 &mut crate_graph,
+                &mut proc_macros,
                 &mut pkg_to_lib_crate,
                 load,
-                load_proc_macro,
                 rustc_workspace,
                 cargo,
                 &public_deps,
@@ -952,7 +949,7 @@ fn cargo_to_crate_graph(
             );
         }
     }
-    crate_graph
+    (crate_graph, proc_macros)
 }
 
 fn detached_files_to_crate_graph(
@@ -961,7 +958,7 @@ fn detached_files_to_crate_graph(
     detached_files: &[AbsPathBuf],
     sysroot: Option<&Sysroot>,
     target_layout: TargetLayoutLoadResult,
-) -> CrateGraph {
+) -> (CrateGraph, ProcMacroPaths) {
     let _p = profile::span("detached_files_to_crate_graph");
     let mut crate_graph = CrateGraph::default();
     let (public_deps, _libproc_macro) = match sysroot {
@@ -998,7 +995,6 @@ fn detached_files_to_crate_graph(
             cfg_options.clone(),
             cfg_options.clone(),
             Env::default(),
-            Ok(Vec::new()),
             false,
             CrateOrigin::CratesIo {
                 repo: None,
@@ -1009,14 +1005,14 @@ fn detached_files_to_crate_graph(
 
         public_deps.add_to_crate_graph(&mut crate_graph, detached_file_crate);
     }
-    crate_graph
+    (crate_graph, FxHashMap::default())
 }
 
 fn handle_rustc_crates(
     crate_graph: &mut CrateGraph,
+    proc_macros: &mut ProcMacroPaths,
     pkg_to_lib_crate: &mut FxHashMap<Package, CrateId>,
     load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
-    load_proc_macro: &mut dyn FnMut(&str, &AbsPath) -> ProcMacroLoadResult,
     rustc_workspace: &CargoWorkspace,
     cargo: &CargoWorkspace,
     public_deps: &SysrootPublicDeps,
@@ -1075,10 +1071,10 @@ fn handle_rustc_crates(
                 if let Some(file_id) = load(&rustc_workspace[tgt].root) {
                     let crate_id = add_target_crate_root(
                         crate_graph,
+                        proc_macros,
                         &rustc_workspace[pkg],
                         build_scripts.get_output(pkg),
                         cfg_options.clone(),
-                        &mut |path| load_proc_macro(&rustc_workspace[tgt].name, path),
                         file_id,
                         &rustc_workspace[tgt].name,
                         rustc_workspace[tgt].is_proc_macro,
@@ -1140,10 +1136,10 @@ fn handle_rustc_crates(
 
 fn add_target_crate_root(
     crate_graph: &mut CrateGraph,
+    proc_macros: &mut ProcMacroPaths,
     pkg: &PackageData,
     build_data: Option<&BuildScriptOutput>,
     cfg_options: CfgOptions,
-    load_proc_macro: &mut dyn FnMut(&AbsPath) -> ProcMacroLoadResult,
     file_id: FileId,
     cargo_name: &str,
     is_proc_macro: bool,
@@ -1176,14 +1172,8 @@ fn add_target_crate_root(
         }
     }
 
-    let proc_macro = match build_data.as_ref().map(|it| it.proc_macro_dylib_path.as_ref()) {
-        Some(Some(it)) => load_proc_macro(it),
-        Some(None) => Err("no proc macro dylib present".into()),
-        None => Err("crate has not (yet) been built".into()),
-    };
-
     let display_name = CrateDisplayName::from_canonical_name(cargo_name.to_string());
-    crate_graph.add_crate_root(
+    let crate_id = crate_graph.add_crate_root(
         file_id,
         edition,
         Some(display_name),
@@ -1191,11 +1181,19 @@ fn add_target_crate_root(
         cfg_options,
         potential_cfg_options,
         env,
-        proc_macro,
         is_proc_macro,
         CrateOrigin::CratesIo { repo: pkg.repository.clone(), name: Some(pkg.name.clone()) },
         target_layout,
-    )
+    );
+    let proc_macro = match build_data.as_ref().map(|it| &it.proc_macro_dylib_path) {
+        Some(it) => it.clone().map(Ok),
+        None => Some(Err("crate has not (yet) been built".into())),
+    };
+    if let Some(proc_macro) = proc_macro {
+        proc_macros.insert(crate_id, proc_macro.map(|path| (Some(cargo_name.to_owned()), path)));
+    }
+
+    crate_id
 }
 
 #[derive(Default)]
@@ -1237,7 +1235,6 @@ fn sysroot_to_crate_graph(
                 cfg_options.clone(),
                 cfg_options.clone(),
                 env,
-                Err("no proc macro loaded for sysroot crate".into()),
                 false,
                 CrateOrigin::Lang(LangCrateOrigin::from(&*sysroot[krate].name)),
                 target_layout.clone(),
diff --git a/crates/rust-analyzer/src/cli/load_cargo.rs b/crates/rust-analyzer/src/cli/load_cargo.rs
index 5a958d963e4..f5bc3c12c14 100644
--- a/crates/rust-analyzer/src/cli/load_cargo.rs
+++ b/crates/rust-analyzer/src/cli/load_cargo.rs
@@ -6,7 +6,10 @@ use anyhow::Result;
 use crossbeam_channel::{unbounded, Receiver};
 use hir::db::DefDatabase;
 use ide::{AnalysisHost, Change};
-use ide_db::{base_db::CrateGraph, FxHashMap};
+use ide_db::{
+    base_db::{CrateGraph, ProcMacros},
+    FxHashMap,
+};
 use proc_macro_api::ProcMacroServer;
 use project_model::{CargoConfig, ProjectManifest, ProjectWorkspace};
 use vfs::{loader::Handle, AbsPath, AbsPathBuf};
@@ -66,7 +69,7 @@ pub fn load_workspace(
         Box::new(loader)
     };
 
-    let proc_macro_client = match &load_config.with_proc_macro_server {
+    let proc_macro_server = match &load_config.with_proc_macro_server {
         ProcMacroServerChoice::Sysroot => ws
             .find_sysroot_proc_macro_srv()
             .ok_or_else(|| "failed to find sysroot proc-macro server".to_owned())
@@ -79,10 +82,7 @@ pub fn load_workspace(
         ProcMacroServerChoice::None => Err("proc macro server disabled".to_owned()),
     };
 
-    let crate_graph = ws.to_crate_graph(
-        &mut |_, path: &AbsPath| {
-            load_proc_macro(proc_macro_client.as_ref().map_err(|e| &**e), path, &[])
-        },
+    let (crate_graph, proc_macros) = ws.to_crate_graph(
         &mut |path: &AbsPath| {
             let contents = loader.load_sync(path);
             let path = vfs::VfsPath::from(path.to_path_buf());
@@ -91,6 +91,21 @@ pub fn load_workspace(
         },
         extra_env,
     );
+    let proc_macros = {
+        let proc_macro_server = match &proc_macro_server {
+            Ok(it) => Ok(it),
+            Err(e) => Err(e.as_str()),
+        };
+        proc_macros
+            .into_iter()
+            .map(|(crate_id, path)| {
+                (
+                    crate_id,
+                    path.and_then(|(_, path)| load_proc_macro(proc_macro_server, &path, &[])),
+                )
+            })
+            .collect()
+    };
 
     let project_folders = ProjectFolders::new(&[ws], &[]);
     loader.set_config(vfs::loader::Config {
@@ -100,17 +115,23 @@ pub fn load_workspace(
     });
 
     tracing::debug!("crate graph: {:?}", crate_graph);
-    let host =
-        load_crate_graph(crate_graph, project_folders.source_root_config, &mut vfs, &receiver);
+    let host = load_crate_graph(
+        crate_graph,
+        proc_macros,
+        project_folders.source_root_config,
+        &mut vfs,
+        &receiver,
+    );
 
     if load_config.prefill_caches {
         host.analysis().parallel_prime_caches(1, |_| {})?;
     }
-    Ok((host, vfs, proc_macro_client.ok()))
+    Ok((host, vfs, proc_macro_server.ok()))
 }
 
 fn load_crate_graph(
     crate_graph: CrateGraph,
+    proc_macros: ProcMacros,
     source_root_config: SourceRootConfig,
     vfs: &mut vfs::Vfs,
     receiver: &Receiver<vfs::loader::Message>,
@@ -149,6 +170,7 @@ fn load_crate_graph(
     analysis_change.set_roots(source_roots);
 
     analysis_change.set_crate_graph(crate_graph);
+    analysis_change.set_proc_macros(proc_macros);
 
     host.apply_change(analysis_change);
     host
diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs
index aca6c923570..d02714ad1ea 100644
--- a/crates/rust-analyzer/src/global_state.rs
+++ b/crates/rust-analyzer/src/global_state.rs
@@ -59,10 +59,11 @@ pub(crate) struct GlobalState {
     pub(crate) mem_docs: MemDocs,
     pub(crate) semantic_tokens_cache: Arc<Mutex<FxHashMap<Url, SemanticTokens>>>,
     pub(crate) shutdown_requested: bool,
-    pub(crate) proc_macro_changed: bool,
     pub(crate) last_reported_status: Option<lsp_ext::ServerStatusParams>,
     pub(crate) source_root_config: SourceRootConfig,
-    pub(crate) proc_macro_clients: Vec<Result<ProcMacroServer, String>>,
+
+    pub(crate) proc_macro_changed: bool,
+    pub(crate) proc_macro_clients: Arc<[Result<ProcMacroServer, String>]>,
 
     pub(crate) flycheck: Arc<[FlycheckHandle]>,
     pub(crate) flycheck_sender: Sender<flycheck::Message>,
@@ -151,10 +152,11 @@ impl GlobalState {
             mem_docs: MemDocs::default(),
             semantic_tokens_cache: Arc::new(Default::default()),
             shutdown_requested: false,
-            proc_macro_changed: false,
             last_reported_status: None,
             source_root_config: SourceRootConfig::default(),
-            proc_macro_clients: vec![],
+
+            proc_macro_changed: false,
+            proc_macro_clients: Arc::new([]),
 
             flycheck: Arc::new([]),
             flycheck_sender,
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index 2fca2ab851d..8866515bb94 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -5,6 +5,7 @@
 use std::{
     io::Write as _,
     process::{self, Stdio},
+    sync::Arc,
 };
 
 use anyhow::Context;
@@ -44,7 +45,7 @@ use crate::{
 };
 
 pub(crate) fn handle_workspace_reload(state: &mut GlobalState, _: ()) -> Result<()> {
-    state.proc_macro_clients.clear();
+    state.proc_macro_clients = Arc::new([]);
     state.proc_macro_changed = false;
 
     state.fetch_workspaces_queue.request_op("reload workspace request".to_string());
@@ -52,6 +53,14 @@ pub(crate) fn handle_workspace_reload(state: &mut GlobalState, _: ()) -> Result<
     Ok(())
 }
 
+pub(crate) fn handle_proc_macros_reload(state: &mut GlobalState, _: ()) -> Result<()> {
+    state.proc_macro_clients = Arc::new([]);
+    state.proc_macro_changed = false;
+
+    state.fetch_build_data_queue.request_op("reload proc macros request".to_string());
+    Ok(())
+}
+
 pub(crate) fn handle_cancel_flycheck(state: &mut GlobalState, _: ()) -> Result<()> {
     let _p = profile::span("handle_stop_flycheck");
     state.flycheck.iter().for_each(|flycheck| flycheck.cancel());
diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs
index c7b513db981..2f8829ec738 100644
--- a/crates/rust-analyzer/src/lsp_ext.rs
+++ b/crates/rust-analyzer/src/lsp_ext.rs
@@ -51,6 +51,14 @@ impl Request for ReloadWorkspace {
     const METHOD: &'static str = "rust-analyzer/reloadWorkspace";
 }
 
+pub enum ReloadProcMacros {}
+
+impl Request for ReloadProcMacros {
+    type Params = ();
+    type Result = ();
+    const METHOD: &'static str = "rust-analyzer/reloadProcMacros";
+}
+
 pub enum SyntaxTree {}
 
 impl Request for SyntaxTree {
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index 67a54cde68c..8db526e0b76 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -24,7 +24,7 @@ use crate::{
     handlers, lsp_ext,
     lsp_utils::{apply_document_changes, notification_is, Progress},
     mem_docs::DocumentData,
-    reload::{self, BuildDataProgress, ProjectWorkspaceProgress},
+    reload::{self, BuildDataProgress, ProcMacroProgress, ProjectWorkspaceProgress},
     Result,
 };
 
@@ -68,6 +68,7 @@ pub(crate) enum Task {
     PrimeCaches(PrimeCachesProgress),
     FetchWorkspace(ProjectWorkspaceProgress),
     FetchBuildData(BuildDataProgress),
+    LoadProcMacros(ProcMacroProgress),
 }
 
 #[derive(Debug)]
@@ -488,6 +489,21 @@ impl GlobalState {
                 };
 
                 if let Some(state) = state {
+                    self.report_progress("Building", state, msg, None, None);
+                }
+            }
+            Task::LoadProcMacros(progress) => {
+                let (state, msg) = match progress {
+                    ProcMacroProgress::Begin => (Some(Progress::Begin), None),
+                    ProcMacroProgress::Report(msg) => (Some(Progress::Report), Some(msg)),
+                    ProcMacroProgress::End(proc_macro_load_result) => {
+                        self.set_proc_macros(proc_macro_load_result);
+
+                        (Some(Progress::End), None)
+                    }
+                };
+
+                if let Some(state) = state {
                     self.report_progress("Loading", state, msg, None, None);
                 }
             }
@@ -633,6 +649,7 @@ impl GlobalState {
 
         dispatcher
             .on_sync_mut::<lsp_ext::ReloadWorkspace>(handlers::handle_workspace_reload)
+            .on_sync_mut::<lsp_ext::ReloadProcMacros>(handlers::handle_proc_macros_reload)
             .on_sync_mut::<lsp_ext::MemoryUsage>(handlers::handle_memory_usage)
             .on_sync_mut::<lsp_ext::ShuffleCrateGraph>(handlers::handle_shuffle_crate_graph)
             .on_sync::<lsp_ext::JoinLines>(handlers::handle_join_lines)
diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs
index 138b8446b90..9c6edb46f41 100644
--- a/crates/rust-analyzer/src/reload.rs
+++ b/crates/rust-analyzer/src/reload.rs
@@ -12,7 +12,7 @@
 //! correct. Instead, we try to provide a best-effort service. Even if the
 //! project is currently loading and we don't have a full project model, we
 //! still want to respond to various  requests.
-use std::{collections::hash_map::Entry, mem, sync::Arc};
+use std::{collections::hash_map::Entry, iter, mem, sync::Arc};
 
 use flycheck::{FlycheckConfig, FlycheckHandle};
 use hir::db::DefDatabase;
@@ -20,7 +20,7 @@ use ide::Change;
 use ide_db::{
     base_db::{
         CrateGraph, Env, ProcMacro, ProcMacroExpander, ProcMacroExpansionError, ProcMacroKind,
-        ProcMacroLoadResult, SourceRoot, VfsPath,
+        ProcMacroLoadResult, ProcMacroPaths, ProcMacros, SourceRoot, VfsPath,
     },
     FxHashMap,
 };
@@ -54,6 +54,13 @@ pub(crate) enum BuildDataProgress {
     End((Arc<Vec<ProjectWorkspace>>, Vec<anyhow::Result<WorkspaceBuildScripts>>)),
 }
 
+#[derive(Debug)]
+pub(crate) enum ProcMacroProgress {
+    Begin,
+    Report(String),
+    End(ProcMacros),
+}
+
 impl GlobalState {
     pub(crate) fn is_quiescent(&self) -> bool {
         !(self.last_reported_status.is_none()
@@ -216,6 +223,59 @@ impl GlobalState {
         });
     }
 
+    pub(crate) fn load_proc_macros(&mut self, paths: Vec<ProcMacroPaths>) {
+        tracing::info!("will load proc macros");
+        let dummy_replacements = self.config.dummy_replacements().clone();
+        let proc_macro_clients = self.proc_macro_clients.clone();
+
+        self.task_pool.handle.spawn_with_sender(move |sender| {
+            sender.send(Task::LoadProcMacros(ProcMacroProgress::Begin)).unwrap();
+
+            let dummy_replacements = &dummy_replacements;
+            let progress = {
+                let sender = sender.clone();
+                &move |msg| {
+                    sender.send(Task::LoadProcMacros(ProcMacroProgress::Report(msg))).unwrap()
+                }
+            };
+
+            let mut res = FxHashMap::default();
+            for (client, paths) in proc_macro_clients
+                .iter()
+                .map(|res| res.as_ref().map_err(|e| &**e))
+                .chain(iter::repeat_with(|| Err("Proc macros are disabled")))
+                .zip(paths)
+            {
+                res.extend(paths.into_iter().map(move |(crate_id, res)| {
+                    (
+                        crate_id,
+                        res.and_then(|(crate_name, path)| {
+                            progress(path.display().to_string());
+                            load_proc_macro(
+                                client,
+                                &path,
+                                crate_name
+                                    .as_deref()
+                                    .and_then(|crate_name| {
+                                        dummy_replacements.get(crate_name).map(|v| &**v)
+                                    })
+                                    .unwrap_or_default(),
+                            )
+                        }),
+                    )
+                }));
+            }
+
+            sender.send(Task::LoadProcMacros(ProcMacroProgress::End(res))).unwrap();
+        });
+    }
+
+    pub(crate) fn set_proc_macros(&mut self, proc_macros: ProcMacros) {
+        let mut change = Change::new();
+        change.set_proc_macros(proc_macros);
+        self.analysis_host.apply_change(change);
+    }
+
     pub(crate) fn switch_workspaces(&mut self, cause: Cause) {
         let _p = profile::span("GlobalState::switch_workspaces");
         tracing::info!(%cause, "will switch workspaces");
@@ -303,8 +363,6 @@ impl GlobalState {
             );
         }
 
-        let mut change = Change::new();
-
         let files_config = self.config.files();
         let project_folders = ProjectFolders::new(&self.workspaces, &files_config.exclude);
 
@@ -353,11 +411,10 @@ impl GlobalState {
             watch,
             version: self.vfs_config_version,
         });
+        self.source_root_config = project_folders.source_root_config;
 
         // Create crate graph from all the workspaces
-        let crate_graph = {
-            let dummy_replacements = self.config.dummy_replacements();
-
+        let (crate_graph, proc_macro_paths) = {
             let vfs = &mut self.vfs.write().0;
             let loader = &mut self.loader;
             let mem_docs = &self.mem_docs;
@@ -376,33 +433,26 @@ impl GlobalState {
             };
 
             let mut crate_graph = CrateGraph::default();
-            for (idx, ws) in self.workspaces.iter().enumerate() {
-                let proc_macro_client = match self.proc_macro_clients.get(idx) {
-                    Some(res) => res.as_ref().map_err(|e| &**e),
-                    None => Err("Proc macros are disabled"),
-                };
-                let mut load_proc_macro = move |crate_name: &str, path: &AbsPath| {
-                    load_proc_macro(
-                        proc_macro_client,
-                        path,
-                        dummy_replacements.get(crate_name).map(|v| &**v).unwrap_or_default(),
-                    )
-                };
-                crate_graph.extend(ws.to_crate_graph(
-                    &mut load_proc_macro,
-                    &mut load,
-                    &self.config.cargo().extra_env,
-                ));
+            let mut proc_macros = Vec::default();
+            for ws in &**self.workspaces {
+                let (other, mut crate_proc_macros) =
+                    ws.to_crate_graph(&mut load, &self.config.cargo().extra_env);
+                crate_graph.extend(other, &mut crate_proc_macros);
+                proc_macros.push(crate_proc_macros);
             }
-            crate_graph
+            (crate_graph, proc_macros)
         };
+        let mut change = Change::new();
         change.set_crate_graph(crate_graph);
-
-        self.source_root_config = project_folders.source_root_config;
-
         self.analysis_host.apply_change(change);
         self.process_changes();
+
+        if same_workspaces && !self.fetch_workspaces_queue.op_requested() {
+            self.load_proc_macros(proc_macro_paths);
+        }
+
         self.reload_flycheck();
+
         tracing::info!("did switch workspaces");
     }
 
diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md
index de142203208..11eda94f5bb 100644
--- a/docs/dev/lsp-extensions.md
+++ b/docs/dev/lsp-extensions.md
@@ -1,5 +1,5 @@
 <!---
-lsp_ext.rs hash: 37f31ae648632897
+lsp_ext.rs hash: 92fe1037312754df
 
 If you need to change the above hash to make the test pass, please check if you
 need to adjust this doc as well and ping this issue:
diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts
index 8a953577e99..8ce3466ed44 100644
--- a/editors/code/src/commands.ts
+++ b/editors/code/src/commands.ts
@@ -749,6 +749,10 @@ export function reloadWorkspace(ctx: CtxInit): Cmd {
     return async () => ctx.client.sendRequest(ra.reloadWorkspace);
 }
 
+export function reloadProcMacros(ctx: CtxInit): Cmd {
+    return async () => ctx.client.sendRequest(ra.reloadProcMacros);
+}
+
 export function addProject(ctx: CtxInit): Cmd {
     return async () => {
         const discoverProjectCommand = ctx.config.discoverProjectCommand;
diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts
index 85579453a63..8da8b0d63a1 100644
--- a/editors/code/src/ctx.ts
+++ b/editors/code/src/ctx.ts
@@ -378,10 +378,13 @@ export class Ctx {
         if (statusBar.tooltip.value) {
             statusBar.tooltip.appendText("\n\n");
         }
+        statusBar.tooltip.appendMarkdown("\n\n[Open logs](command:rust-analyzer.openLogs)");
         statusBar.tooltip.appendMarkdown(
             "\n\n[Reload Workspace](command:rust-analyzer.reloadWorkspace)"
         );
-        statusBar.tooltip.appendMarkdown("\n\n[Open logs](command:rust-analyzer.openLogs)");
+        statusBar.tooltip.appendMarkdown(
+            "\n\n[Rebuild Proc Macros](command:rust-analyzer.reloadProcMacros)"
+        );
         statusBar.tooltip.appendMarkdown("\n\n[Restart server](command:rust-analyzer.startServer)");
         statusBar.tooltip.appendMarkdown("\n\n[Stop server](command:rust-analyzer.stopServer)");
         if (!status.quiescent) icon = "$(sync~spin) ";
diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts
index 872d7199b83..a03777d1c0a 100644
--- a/editors/code/src/lsp_ext.ts
+++ b/editors/code/src/lsp_ext.ts
@@ -43,6 +43,7 @@ export const relatedTests = new lc.RequestType<lc.TextDocumentPositionParams, Te
     "rust-analyzer/relatedTests"
 );
 export const reloadWorkspace = new lc.RequestType0<null, void>("rust-analyzer/reloadWorkspace");
+export const reloadProcMacros = new lc.RequestType0<null, void>("rust-analyzer/reloadProcMacros");
 
 export const runFlycheck = new lc.NotificationType<{
     textDocument: lc.TextDocumentIdentifier | null;
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts
index d5de00561b1..7079f235cab 100644
--- a/editors/code/src/main.ts
+++ b/editors/code/src/main.ts
@@ -153,6 +153,7 @@ function createCommands(): Record<string, CommandFactory> {
         memoryUsage: { enabled: commands.memoryUsage },
         shuffleCrateGraph: { enabled: commands.shuffleCrateGraph },
         reloadWorkspace: { enabled: commands.reloadWorkspace },
+        reloadProcMacros: { enabled: commands.reloadProcMacros },
         addProject: { enabled: commands.addProject },
         matchingBrace: { enabled: commands.matchingBrace },
         joinLines: { enabled: commands.joinLines },