diff --git a/crates/project-model/src/workspace.rs b/crates/project-model/src/workspace.rs index 916447fdffa..5f23d9fe826 100644 --- a/crates/project-model/src/workspace.rs +++ b/crates/project-model/src/workspace.rs @@ -672,6 +672,14 @@ impl ProjectWorkspace { _ => false, } } + + /// Returns `true` if the project workspace is [`Json`]. + /// + /// [`Json`]: ProjectWorkspace::Json + #[must_use] + pub fn is_json(&self) -> bool { + matches!(self, Self::Json { .. }) + } } fn project_json_to_crate_graph( diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs index 7b27a067062..0c76ac8b925 100644 --- a/crates/rust-analyzer/src/reload.rs +++ b/crates/rust-analyzer/src/reload.rs @@ -296,11 +296,25 @@ impl GlobalState { let workspaces = workspaces.iter().filter_map(|res| res.as_ref().ok().cloned()).collect::>(); - let same_workspaces = workspaces.len() == self.workspaces.len() - && workspaces - .iter() - .zip(self.workspaces.iter()) - .all(|(l, r)| l.eq_ignore_build_data(r)); + // `different_workspaces` is used to spawn a new proc macro server for a newly-added + // rust workspace (most commonly sourced from a `rust-project.json`). While the algorithm + // to find the new workspaces is quadratic, we generally expect that the number of total + // workspaces to remain in the low single digits. the `cloned_workspace` is needed for borrowck + // reasons. + let cloned_workspaces = workspaces.clone(); + let different_workspaces = cloned_workspaces + .iter() + .filter(|ws| { + !self + .workspaces + .iter() + .find(|existing_ws| ws.eq_ignore_build_data(&existing_ws)) + .is_some() + }) + .collect::>(); + let same_workspaces = different_workspaces.is_empty(); + + tracing::debug!(current_workspaces = ?self.workspaces, new_workspaces = ?workspaces, ?same_workspaces, "comparing workspaces"); if same_workspaces { let (workspaces, build_scripts) = self.fetch_build_data_queue.last_op_result(); @@ -370,11 +384,10 @@ impl GlobalState { let files_config = self.config.files(); let project_folders = ProjectFolders::new(&self.workspaces, &files_config.exclude); - if self.proc_macro_clients.is_empty() { + if self.proc_macro_clients.is_empty() || !different_workspaces.is_empty() { if let Some((path, path_manually_set)) = self.config.proc_macro_srv() { tracing::info!("Spawning proc-macro servers"); - self.proc_macro_clients = self - .workspaces + self.proc_macro_clients = different_workspaces .iter() .map(|ws| { let (path, args): (_, &[_]) = if path_manually_set { @@ -448,7 +461,19 @@ impl GlobalState { }; let mut change = Change::new(); - if same_workspaces { + // `self.fetch_proc_macros_queue.request_op(cause, proc_macro_paths)` is only called in + // when `switch_workspaces` is called _without_ changing workspaces. This typically occurs + // when build scripts have finishing building, but when rust-analyzer is used with a + // rust-project.json, the build scripts have already been built by the external build system + // that generated the `rust-project.json`. + + // Therefore, in order to allow _new_ workspaces added via rust-project.json (e.g., after + // a workspace was already added), we check whether this is the same workspace _or_ + // if any of the new workspaces is a `rust-project.json`. + // + // The else branch is used to provide better diagnostics to users while procedural macros + // are still being built. + if same_workspaces || different_workspaces.iter().any(|ws| ws.is_json()) { if self.config.expand_proc_macros() { self.fetch_proc_macros_queue.request_op(cause, proc_macro_paths); }