Repository: rust-lang/rust-analyzer
Pull request #17119 (2024-04-21 15:35:11)

internal: Extract common fields out of `ProjectWorkspace` variants


Patch:
diff --git a/crates/flycheck/src/lib.rs b/crates/flycheck/src/lib.rs
index 5dfaaf7742..6d5ca8321e 100644
--- a/crates/flycheck/src/lib.rs
+++ b/crates/flycheck/src/lib.rs
@@ -125,8 +125,10 @@ impl FlycheckHandle {
         config: FlycheckConfig,
         sysroot_root: Option<AbsPathBuf>,
         workspace_root: AbsPathBuf,
+        manifest_path: Option<AbsPathBuf>,
     ) -> FlycheckHandle {
-        let actor = FlycheckActor::new(id, sender, config, sysroot_root, workspace_root);
+        let actor =
+            FlycheckActor::new(id, sender, config, sysroot_root, workspace_root, manifest_path);
         let (sender, receiver) = unbounded::<StateChange>();
         let thread = stdx::thread::Builder::new(stdx::thread::ThreadIntent::Worker)
             .name("Flycheck".to_owned())
@@ -205,6 +207,7 @@ struct FlycheckActor {
     id: usize,
     sender: Box<dyn Fn(Message) + Send>,
     config: FlycheckConfig,
+    manifest_path: Option<AbsPathBuf>,
     /// Either the workspace root of the workspace we are flychecking,
     /// or the project root of the project.
     root: AbsPathBuf,
@@ -233,6 +236,7 @@ impl FlycheckActor {
         config: FlycheckConfig,
         sysroot_root: Option<AbsPathBuf>,
         workspace_root: AbsPathBuf,
+        manifest_path: Option<AbsPathBuf>,
     ) -> FlycheckActor {
         tracing::info!(%id, ?workspace_root, "Spawning flycheck");
         FlycheckActor {
@@ -241,6 +245,7 @@ impl FlycheckActor {
             config,
             sysroot_root,
             root: workspace_root,
+            manifest_path,
             command_handle: None,
             command_receiver: None,
         }
@@ -388,8 +393,13 @@ impl FlycheckActor {
                     "--message-format=json"
                 });
 
-                cmd.arg("--manifest-path");
-                cmd.arg(self.root.join("Cargo.toml"));
+                if let Some(manifest_path) = &self.manifest_path {
+                    cmd.arg("--manifest-path");
+                    cmd.arg(manifest_path);
+                    if manifest_path.extension().map_or(false, |ext| ext == "rs") {
+                        cmd.arg("-Zscript");
+                    }
+                }
 
                 options.apply_on_command(&mut cmd);
                 (cmd, options.extra_args.clone())
diff --git a/crates/load-cargo/src/lib.rs b/crates/load-cargo/src/lib.rs
index 31b0c8cdec..1232322ee3 100644
--- a/crates/load-cargo/src/lib.rs
+++ b/crates/load-cargo/src/lib.rs
@@ -333,9 +333,7 @@ fn load_crate_graph(
     vfs: &mut vfs::Vfs,
     receiver: &Receiver<vfs::loader::Message>,
 ) -> RootDatabase {
-    let (ProjectWorkspace::Cargo { toolchain, target_layout, .. }
-    | ProjectWorkspace::Json { toolchain, target_layout, .. }
-    | ProjectWorkspace::DetachedFile { toolchain, target_layout, .. }) = ws;
+    let ProjectWorkspace { toolchain, target_layout, .. } = ws;
 
     let lru_cap = std::env::var("RA_LRU_CAP").ok().and_then(|it| it.parse::<usize>().ok());
     let mut db = RootDatabase::new(lru_cap);
diff --git a/crates/project-model/src/build_scripts.rs b/crates/project-model/src/build_scripts.rs
index 3fdd59967b..4dc3b3f349 100644
--- a/crates/project-model/src/build_scripts.rs
+++ b/crates/project-model/src/build_scripts.rs
@@ -116,7 +116,7 @@ impl WorkspaceBuildScripts {
                     }
                 }
 
-                if manifest_path.extension().map_or(false, |ext| ext == "rs") {
+                if manifest_path.is_rust_manifest() {
                     cmd.arg("-Zscript");
                 }
 
diff --git a/crates/project-model/src/cargo_workspace.rs b/crates/project-model/src/cargo_workspace.rs
index 88ba5a0da9..9955f2687c 100644
--- a/crates/project-model/src/cargo_workspace.rs
+++ b/crates/project-model/src/cargo_workspace.rs
@@ -307,7 +307,7 @@ impl CargoWorkspace {
             );
         }
         // The manifest is a rust file, so this means its a script manifest
-        if cargo_toml.extension().is_some_and(|ext| ext == "rs") {
+        if cargo_toml.is_rust_manifest() {
             // Deliberately don't set up RUSTC_BOOTSTRAP or a nightly override here, the user should
             // opt into it themselves.
             other_options.push("-Zscript".to_owned());
diff --git a/crates/project-model/src/env.rs b/crates/project-model/src/env.rs
index 762e01c917..5520cdaff6 100644
--- a/crates/project-model/src/env.rs
+++ b/crates/project-model/src/env.rs
@@ -60,16 +60,19 @@ pub(crate) fn inject_rustc_tool_env(env: &mut Env, cargo_name: &str, kind: Targe
 }
 
 pub(crate) fn cargo_config_env(
-    cargo_toml: &ManifestPath,
+    manifest: &ManifestPath,
     extra_env: &FxHashMap<String, String>,
     sysroot: Option<&Sysroot>,
 ) -> FxHashMap<String, String> {
     let mut cargo_config = Sysroot::tool(sysroot, Tool::Cargo);
     cargo_config.envs(extra_env);
     cargo_config
-        .current_dir(cargo_toml.parent())
+        .current_dir(manifest.parent())
         .args(["-Z", "unstable-options", "config", "get", "env"])
         .env("RUSTC_BOOTSTRAP", "1");
+    if manifest.is_rust_manifest() {
+        cargo_config.arg("-Zscript");
+    }
     // if successful we receive `env.key.value = "value" per entry
     tracing::debug!("Discovering cargo config env by {:?}", cargo_config);
     utf8_stdout(cargo_config).map(parse_output_cargo_config_env).unwrap_or_default()
diff --git a/crates/project-model/src/lib.rs b/crates/project-model/src/lib.rs
index 5428f061b7..181c07f46b 100644
--- a/crates/project-model/src/lib.rs
+++ b/crates/project-model/src/lib.rs
@@ -52,7 +52,7 @@ pub use crate::{
     manifest_path::ManifestPath,
     project_json::{ProjectJson, ProjectJsonData},
     sysroot::Sysroot,
-    workspace::{FileLoader, PackageRoot, ProjectWorkspace},
+    workspace::{FileLoader, PackageRoot, ProjectWorkspace, ProjectWorkspaceKind},
 };
 pub use cargo_metadata::Metadata;
 
diff --git a/crates/project-model/src/manifest_path.rs b/crates/project-model/src/manifest_path.rs
index 6929a75895..2331c0c36c 100644
--- a/crates/project-model/src/manifest_path.rs
+++ b/crates/project-model/src/manifest_path.rs
@@ -38,6 +38,10 @@ impl ManifestPath {
     pub fn canonicalize(&self) -> ! {
         (**self).canonicalize()
     }
+
+    pub fn is_rust_manifest(&self) -> bool {
+        self.file.extension().map_or(false, |ext| ext == "rs")
+    }
 }
 
 impl fmt::Display for ManifestPath {
diff --git a/crates/project-model/src/workspace.rs b/crates/project-model/src/workspace.rs
index 9f6e75171b..7f845f85ae 100644
--- a/crates/project-model/src/workspace.rs
+++ b/crates/project-model/src/workspace.rs
@@ -44,50 +44,39 @@ pub struct PackageRoot {
 }
 
 #[derive(Clone)]
-pub enum ProjectWorkspace {
+pub struct ProjectWorkspace {
+    pub kind: ProjectWorkspaceKind,
+    /// The sysroot loaded for this workspace.
+    pub sysroot: Result<Sysroot, Option<String>>,
+    /// Holds cfg flags for the current target. We get those by running
+    /// `rustc --print cfg`.
+    // FIXME: make this a per-crate map, as, eg, build.rs might have a
+    // different target.
+    pub rustc_cfg: Vec<CfgFlag>,
+    /// The toolchain version used by this workspace.
+    pub toolchain: Option<Version>,
+    /// The target data layout queried for workspace.
+    pub target_layout: TargetLayoutLoadResult,
+    /// A set of cfg overrides for this workspace.
+    pub cfg_overrides: CfgOverrides,
+}
+
+#[derive(Clone)]
+pub enum ProjectWorkspaceKind {
     /// Project workspace was discovered by running `cargo metadata` and `rustc --print sysroot`.
     Cargo {
         /// The workspace as returned by `cargo metadata`.
         cargo: CargoWorkspace,
         /// The build script results for the workspace.
         build_scripts: WorkspaceBuildScripts,
-        /// The sysroot loaded for this workspace.
-        sysroot: Result<Sysroot, Option<String>>,
         /// The rustc workspace loaded for this workspace. An `Err(None)` means loading has been
         /// disabled or was otherwise not requested.
         rustc: Result<Box<(CargoWorkspace, WorkspaceBuildScripts)>, Option<String>>,
-        /// Holds cfg flags for the current target. We get those by running
-        /// `rustc --print cfg`.
-        // FIXME: make this a per-crate map, as, eg, build.rs might have a
-        // different target.
-        rustc_cfg: Vec<CfgFlag>,
-        /// A set of cfg overrides for this workspace.
-        cfg_overrides: CfgOverrides,
-        /// The toolchain version used by this workspace.
-        toolchain: Option<Version>,
-        /// The target data layout queried for workspace.
-        target_layout: TargetLayoutLoadResult,
         /// Environment variables set in the `.cargo/config` file.
         cargo_config_extra_env: FxHashMap<String, String>,
     },
     /// Project workspace was manually specified using a `rust-project.json` file.
-    Json {
-        /// The loaded project json file.
-        project: ProjectJson,
-        /// The sysroot loaded for this workspace.
-        sysroot: Result<Sysroot, Option<String>>,
-        /// Holds cfg flags for the current target. We get those by running
-        /// `rustc --print cfg`.
-        // FIXME: make this a per-crate map, as, eg, build.rs might have a
-        // different target.
-        rustc_cfg: Vec<CfgFlag>,
-        /// The toolchain version used by this workspace.
-        toolchain: Option<Version>,
-        /// The target data layout queried for workspace.
-        target_layout: TargetLayoutLoadResult,
-        /// A set of cfg overrides for this workspace.
-        cfg_overrides: CfgOverrides,
-    },
+    Json(ProjectJson),
     // FIXME: The primary limitation of this approach is that the set of detached files needs to be fixed at the beginning.
     // That's not the end user experience we should strive for.
     // Ideally, you should be able to just open a random detached file in existing cargo projects, and get the basic features working.
@@ -101,37 +90,22 @@ pub enum ProjectWorkspace {
     DetachedFile {
         /// The file in question.
         file: ManifestPath,
-        /// The sysroot loaded for this workspace.
-        sysroot: Result<Sysroot, Option<String>>,
-        /// Holds cfg flags for the current target. We get those by running
-        /// `rustc --print cfg`.
-        // FIXME: make this a per-crate map, as, eg, build.rs might have a
-        // different target.
-        rustc_cfg: Vec<CfgFlag>,
-        /// The toolchain version used by this workspace.
-        toolchain: Option<Version>,
-        /// The target data layout queried for workspace.
-        target_layout: TargetLayoutLoadResult,
-        /// A set of cfg overrides for the files.
-        cfg_overrides: CfgOverrides,
         /// Is this file a cargo script file?
-        cargo_script: Option<(CargoWorkspace, WorkspaceBuildScripts)>,
+        cargo: Option<(CargoWorkspace, WorkspaceBuildScripts)>,
+        /// Environment variables set in the `.cargo/config` file.
+        cargo_config_extra_env: FxHashMap<String, String>,
     },
 }
 
 impl fmt::Debug for ProjectWorkspace {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         // Make sure this isn't too verbose.
-        match self {
-            ProjectWorkspace::Cargo {
+        let Self { kind, sysroot, rustc_cfg, toolchain, target_layout, cfg_overrides } = self;
+        match kind {
+            ProjectWorkspaceKind::Cargo {
                 cargo,
                 build_scripts: _,
-                sysroot,
                 rustc,
-                rustc_cfg,
-                cfg_overrides,
-                toolchain,
-                target_layout,
                 cargo_config_extra_env,
             } => f
                 .debug_struct("Cargo")
@@ -148,14 +122,7 @@ impl fmt::Debug for ProjectWorkspace {
                 .field("data_layout", &target_layout)
                 .field("cargo_config_extra_env", &cargo_config_extra_env)
                 .finish(),
-            ProjectWorkspace::Json {
-                project,
-                sysroot,
-                rustc_cfg,
-                toolchain,
-                target_layout: data_layout,
-                cfg_overrides,
-            } => {
+            ProjectWorkspaceKind::Json(project) => {
                 let mut debug_struct = f.debug_struct("Json");
                 debug_struct.field("n_crates", &project.n_crates());
                 if let Ok(sysroot) = sysroot {
@@ -164,18 +131,14 @@ impl fmt::Debug for ProjectWorkspace {
                 debug_struct
                     .field("n_rustc_cfg", &rustc_cfg.len())
                     .field("toolchain", &toolchain)
-                    .field("data_layout", &data_layout)
+                    .field("data_layout", &target_layout)
                     .field("n_cfg_overrides", &cfg_overrides.len());
                 debug_struct.finish()
             }
-            ProjectWorkspace::DetachedFile {
+            ProjectWorkspaceKind::DetachedFile {
                 file,
-                sysroot,
-                rustc_cfg,
-                toolchain,
-                target_layout,
-                cfg_overrides,
-                cargo_script,
+                cargo: cargo_script,
+                cargo_config_extra_env,
             } => f
                 .debug_struct("DetachedFiles")
                 .field("file", &file)
@@ -186,6 +149,7 @@ impl fmt::Debug for ProjectWorkspace {
                 .field("toolchain", &toolchain)
                 .field("data_layout", &target_layout)
                 .field("n_cfg_overrides", &cfg_overrides.len())
+                .field("cargo_config_extra_env", &cargo_config_extra_env)
                 .finish(),
         }
     }
@@ -361,18 +325,20 @@ impl ProjectWorkspace {
 
                 let cargo_config_extra_env =
                     cargo_config_env(cargo_toml, &config.extra_env, sysroot_ref);
-                ProjectWorkspace::Cargo {
-                    cargo,
-                    build_scripts: WorkspaceBuildScripts::default(),
+                ProjectWorkspace {
+                    kind: ProjectWorkspaceKind::Cargo {
+                        cargo,
+                        build_scripts: WorkspaceBuildScripts::default(),
+                        rustc,
+                        cargo_config_extra_env,
+                    },
                     sysroot,
-                    rustc,
                     rustc_cfg,
                     cfg_overrides,
                     toolchain,
                     target_layout: data_layout
                         .map(Arc::from)
                         .map_err(|it| Arc::from(it.to_string())),
-                    cargo_config_extra_env,
                 }
             }
         };
@@ -425,8 +391,8 @@ impl ProjectWorkspace {
 
         let rustc_cfg = rustc_cfg::get(target, extra_env, cfg_config);
         let data_layout = target_data_layout::get(data_layout_config, target, extra_env);
-        ProjectWorkspace::Json {
-            project: project_json,
+        ProjectWorkspace {
+            kind: ProjectWorkspaceKind::Json(project_json),
             sysroot,
             rustc_cfg,
             toolchain,
@@ -484,14 +450,19 @@ impl ProjectWorkspace {
                     )
                 });
 
-        Ok(ProjectWorkspace::DetachedFile {
-            file: detached_file.to_owned(),
+        let cargo_config_extra_env =
+            cargo_config_env(detached_file, &config.extra_env, sysroot_ref);
+        Ok(ProjectWorkspace {
+            kind: ProjectWorkspaceKind::DetachedFile {
+                file: detached_file.to_owned(),
+                cargo: cargo_script,
+                cargo_config_extra_env,
+            },
             sysroot,
             rustc_cfg,
             toolchain,
             target_layout: data_layout.map(Arc::from).map_err(|it| Arc::from(it.to_string())),
             cfg_overrides: config.cfg_overrides.clone(),
-            cargo_script,
         })
     }
 
@@ -508,27 +479,22 @@ impl ProjectWorkspace {
         config: &CargoConfig,
         progress: &dyn Fn(String),
     ) -> anyhow::Result<WorkspaceBuildScripts> {
-        match self {
-            ProjectWorkspace::DetachedFile {
-                cargo_script: Some((cargo, _)),
-                toolchain,
-                sysroot,
-                ..
-            }
-            | ProjectWorkspace::Cargo { cargo, toolchain, sysroot, .. } => {
+        match &self.kind {
+            ProjectWorkspaceKind::DetachedFile { cargo: Some((cargo, _)), .. }
+            | ProjectWorkspaceKind::Cargo { cargo, .. } => {
                 WorkspaceBuildScripts::run_for_workspace(
                     config,
                     cargo,
                     progress,
-                    toolchain,
-                    sysroot.as_ref().ok(),
+                    &self.toolchain,
+                    self.sysroot.as_ref().ok(),
                 )
                 .with_context(|| {
                     format!("Failed to run build scripts for {}", cargo.workspace_root())
                 })
             }
-            ProjectWorkspace::DetachedFile { cargo_script: None, .. }
-            | ProjectWorkspace::Json { .. } => Ok(WorkspaceBuildScripts::default()),
+            ProjectWorkspaceKind::DetachedFile { cargo: None, .. }
+            | ProjectWorkspaceKind::Json { .. } => Ok(WorkspaceBuildScripts::default()),
         }
     }
 
@@ -548,8 +514,8 @@ impl ProjectWorkspace {
 
         let cargo_ws: Vec<_> = workspaces
             .iter()
-            .filter_map(|it| match it {
-                ProjectWorkspace::Cargo { cargo, .. } => Some(cargo),
+            .filter_map(|it| match &it.kind {
+                ProjectWorkspaceKind::Cargo { cargo, .. } => Some(cargo),
                 _ => None,
             })
             .collect();
@@ -563,8 +529,8 @@ impl ProjectWorkspace {
 
         workspaces
             .iter()
-            .map(|it| match it {
-                ProjectWorkspace::Cargo { cargo, .. } => match outputs {
+            .map(|it| match &it.kind {
+                ProjectWorkspaceKind::Cargo { cargo, .. } => match outputs {
                     Ok(outputs) => Ok(outputs.next().unwrap()),
                     Err(e) => Err(e.clone()).with_context(|| {
                         format!("Failed to run build scripts for {}", cargo.workspace_root())
@@ -576,40 +542,33 @@ impl ProjectWorkspace {
     }
 
     pub fn set_build_scripts(&mut self, bs: WorkspaceBuildScripts) {
-        match self {
-            ProjectWorkspace::Cargo { build_scripts, .. }
-            | ProjectWorkspace::DetachedFile { cargo_script: Some((_, build_scripts)), .. } => {
+        match &mut self.kind {
+            ProjectWorkspaceKind::Cargo { build_scripts, .. }
+            | ProjectWorkspaceKind::DetachedFile { cargo: Some((_, build_scripts)), .. } => {
                 *build_scripts = bs
             }
             _ => assert_eq!(bs, WorkspaceBuildScripts::default()),
         }
     }
 
-    pub fn workspace_definition_path(&self) -> &AbsPath {
-        match self {
-            ProjectWorkspace::Cargo { cargo, .. } => cargo.workspace_root(),
-            ProjectWorkspace::Json { project, .. } => project.path(),
-            ProjectWorkspace::DetachedFile { file, .. } => file,
+    pub fn manifest_or_root(&self) -> &AbsPath {
+        match &self.kind {
+            ProjectWorkspaceKind::Cargo { cargo, .. } => cargo.manifest_path(),
+            ProjectWorkspaceKind::Json(project) => project.path(),
+            ProjectWorkspaceKind::DetachedFile { file, .. } => file,
         }
     }
 
     pub fn find_sysroot_proc_macro_srv(&self) -> anyhow::Result<AbsPathBuf> {
-        match self {
-            ProjectWorkspace::Cargo { sysroot: Ok(sysroot), .. }
-            | ProjectWorkspace::Json { sysroot: Ok(sysroot), .. }
-            | ProjectWorkspace::DetachedFile { sysroot: Ok(sysroot), .. } => {
-                sysroot.discover_proc_macro_srv()
-            }
-            ProjectWorkspace::DetachedFile { .. } => {
-                Err(anyhow::format_err!("cannot find proc-macro server, no sysroot was found"))
-            }
-            ProjectWorkspace::Cargo { cargo, .. } => Err(anyhow::format_err!(
-                "cannot find proc-macro-srv, the workspace `{}` is missing a sysroot",
-                cargo.workspace_root()
+        match &self.sysroot {
+            Ok(sysroot) => sysroot.discover_proc_macro_srv(),
+            Err(None) => Err(anyhow::format_err!(
+                "cannot find proc-macro server, the workspace `{}` is missing a sysroot",
+                self.manifest_or_root()
             )),
-            ProjectWorkspace::Json { project, .. } => Err(anyhow::format_err!(
-                "cannot find proc-macro-srv, the workspace `{}` is missing a sysroot",
-                project.path()
+            Err(Some(e)) => Err(anyhow::format_err!(
+                "cannot find proc-macro server, the workspace `{}` is missing a sysroot: {e}",
+                self.manifest_or_root()
             )),
         }
     }
@@ -618,8 +577,8 @@ impl ProjectWorkspace {
     /// The return type contains the path and whether or not
     /// the root is a member of the current workspace
     pub fn to_roots(&self) -> Vec<PackageRoot> {
-        let mk_sysroot = |sysroot: Result<_, _>| {
-            sysroot.into_iter().flat_map(move |sysroot: &Sysroot| {
+        let mk_sysroot = || {
+            self.sysroot.as_ref().into_iter().flat_map(move |sysroot: &Sysroot| {
                 let mut r = match sysroot.mode() {
                     SysrootMode::Workspace(ws) => ws
                         .packages()
@@ -653,15 +612,8 @@ impl ProjectWorkspace {
                 r
             })
         };
-        match self {
-            ProjectWorkspace::Json {
-                project,
-                sysroot,
-                rustc_cfg: _,
-                toolchain: _,
-                target_layout: _,
-                cfg_overrides: _,
-            } => project
+        match &self.kind {
+            ProjectWorkspaceKind::Json(project) => project
                 .crates()
                 .map(|(_, krate)| PackageRoot {
                     is_local: krate.is_workspace_member,
@@ -670,17 +622,12 @@ impl ProjectWorkspace {
                 })
                 .collect::<FxHashSet<_>>()
                 .into_iter()
-                .chain(mk_sysroot(sysroot.as_ref()))
+                .chain(mk_sysroot())
                 .collect::<Vec<_>>(),
-            ProjectWorkspace::Cargo {
+            ProjectWorkspaceKind::Cargo {
                 cargo,
-                sysroot,
                 rustc,
-                rustc_cfg: _,
-                cfg_overrides: _,
                 build_scripts,
-                toolchain: _,
-                target_layout: _,
                 cargo_config_extra_env: _,
             } => {
                 cargo
@@ -721,7 +668,7 @@ impl ProjectWorkspace {
                         }
                         PackageRoot { is_local, include, exclude }
                     })
-                    .chain(mk_sysroot(sysroot.as_ref()))
+                    .chain(mk_sysroot())
                     .chain(rustc.iter().map(|a| a.as_ref()).flat_map(|(rustc, _)| {
                         rustc.packages().map(move |krate| PackageRoot {
                             is_local: false,
@@ -731,7 +678,7 @@ impl ProjectWorkspace {
                     }))
                     .collect()
             }
-            ProjectWorkspace::DetachedFile { file, cargo_script, sysroot, .. } => {
+            ProjectWorkspaceKind::DetachedFile { file, cargo: cargo_script, .. } => {
                 iter::once(PackageRoot {
                     is_local: true,
                     include: vec![file.as_ref().to_owned()],
@@ -775,26 +722,26 @@ impl ProjectWorkspace {
                         PackageRoot { is_local, include, exclude }
                     })
                 }))
-                .chain(mk_sysroot(sysroot.as_ref()))
+                .chain(mk_sysroot())
                 .collect()
             }
         }
     }
 
     pub fn n_packages(&self) -> usize {
-        match self {
-            ProjectWorkspace::Json { project, sysroot, .. } => {
-                let sysroot_package_len = sysroot.as_ref().map_or(0, |it| it.num_packages());
+        match &self.kind {
+            ProjectWorkspaceKind::Json(project) => {
+                let sysroot_package_len = self.sysroot.as_ref().map_or(0, |it| it.num_packages());
                 sysroot_package_len + project.n_crates()
             }
-            ProjectWorkspace::Cargo { cargo, sysroot, rustc, .. } => {
+            ProjectWorkspaceKind::Cargo { cargo, rustc, .. } => {
                 let rustc_package_len =
                     rustc.as_ref().map(|a| a.as_ref()).map_or(0, |(it, _)| it.packages().len());
-                let sysroot_package_len = sysroot.as_ref().map_or(0, |it| it.num_packages());
+                let sysroot_package_len = self.sysroot.as_ref().map_or(0, |it| it.num_packages());
                 cargo.packages().len() + sysroot_package_len + rustc_package_len
             }
-            ProjectWorkspace::DetachedFile { sysroot, cargo_script, .. } => {
-                let sysroot_package_len = sysroot.as_ref().map_or(0, |it| it.num_packages());
+            ProjectWorkspaceKind::DetachedFile { cargo: cargo_script, .. } => {
+                let sysroot_package_len = self.sysroot.as_ref().map_or(0, |it| it.num_packages());
                 sysroot_package_len
                     + cargo_script.as_ref().map_or(1, |(cargo, _)| cargo.packages().len())
             }
@@ -808,15 +755,9 @@ impl ProjectWorkspace {
     ) -> (CrateGraph, ProcMacroPaths) {
         let _p = tracing::span!(tracing::Level::INFO, "ProjectWorkspace::to_crate_graph").entered();
 
-        let ((mut crate_graph, proc_macros), sysroot) = match self {
-            ProjectWorkspace::Json {
-                project,
-                sysroot,
-                rustc_cfg,
-                toolchain: _,
-                target_layout: _,
-                cfg_overrides,
-            } => (
+        let Self { kind, sysroot, cfg_overrides, rustc_cfg, .. } = self;
+        let ((mut crate_graph, proc_macros), sysroot) = match kind {
+            ProjectWorkspaceKind::Json(project) => (
                 project_json_to_crate_graph(
                     rustc_cfg.clone(),
                     load,
@@ -827,15 +768,10 @@ impl ProjectWorkspace {
                 ),
                 sysroot,
             ),
-            ProjectWorkspace::Cargo {
+            ProjectWorkspaceKind::Cargo {
                 cargo,
-                sysroot,
                 rustc,
-                rustc_cfg,
-                cfg_overrides,
                 build_scripts,
-                toolchain: _,
-                target_layout: _,
                 cargo_config_extra_env: _,
             } => (
                 cargo_to_crate_graph(
@@ -849,15 +785,7 @@ impl ProjectWorkspace {
                 ),
                 sysroot,
             ),
-            ProjectWorkspace::DetachedFile {
-                file,
-                sysroot,
-                rustc_cfg,
-                toolchain: _,
-                target_layout: _,
-                cfg_overrides,
-                cargo_script,
-            } => (
+            ProjectWorkspaceKind::DetachedFile { file, cargo: cargo_script, .. } => (
                 if let Some((cargo, build_scripts)) = cargo_script {
                     cargo_to_crate_graph(
                         &mut |path| load(path),
@@ -892,93 +820,60 @@ impl ProjectWorkspace {
     }
 
     pub fn eq_ignore_build_data(&self, other: &Self) -> bool {
-        match (self, other) {
+        let Self { kind, sysroot, rustc_cfg, toolchain, target_layout, cfg_overrides, .. } = self;
+        let Self {
+            kind: o_kind,
+            sysroot: o_sysroot,
+            rustc_cfg: o_rustc_cfg,
+            toolchain: o_toolchain,
+            target_layout: o_target_layout,
+            cfg_overrides: o_cfg_overrides,
+            ..
+        } = other;
+        (match (kind, o_kind) {
             (
-                Self::Cargo {
+                ProjectWorkspaceKind::Cargo {
                     cargo,
-                    sysroot,
                     rustc,
-                    rustc_cfg,
-                    cfg_overrides,
-                    toolchain,
                     cargo_config_extra_env,
                     build_scripts: _,
-                    target_layout: _,
                 },
-                Self::Cargo {
+                ProjectWorkspaceKind::Cargo {
                     cargo: o_cargo,
-                    sysroot: o_sysroot,
                     rustc: o_rustc,
-                    rustc_cfg: o_rustc_cfg,
-                    cfg_overrides: o_cfg_overrides,
-                    toolchain: o_toolchain,
                     cargo_config_extra_env: o_cargo_config_extra_env,
                     build_scripts: _,
-                    target_layout: _,
                 },
             ) => {
                 cargo == o_cargo
                     && rustc == o_rustc
-                    && rustc_cfg == o_rustc_cfg
-                    && cfg_overrides == o_cfg_overrides
-                    && toolchain == o_toolchain
-                    && sysroot == o_sysroot
                     && cargo_config_extra_env == o_cargo_config_extra_env
             }
-            (
-                Self::Json {
-                    project,
-                    sysroot,
-                    rustc_cfg,
-                    toolchain,
-                    target_layout: _,
-                    cfg_overrides,
-                },
-                Self::Json {
-                    project: o_project,
-                    sysroot: o_sysroot,
-                    rustc_cfg: o_rustc_cfg,
-                    toolchain: o_toolchain,
-                    target_layout: _,
-                    cfg_overrides: o_cfg_overrides,
-                },
-            ) => {
+            (ProjectWorkspaceKind::Json(project), ProjectWorkspaceKind::Json(o_project)) => {
                 project == o_project
-                    && rustc_cfg == o_rustc_cfg
-                    && sysroot == o_sysroot
-                    && toolchain == o_toolchain
-                    && cfg_overrides == o_cfg_overrides
             }
             (
-                Self::DetachedFile {
+                ProjectWorkspaceKind::DetachedFile {
                     file,
-                    sysroot,
-                    rustc_cfg,
-                    cargo_script: Some((cargo_script, _)),
-                    toolchain,
-                    target_layout,
-                    cfg_overrides,
+                    cargo: Some((cargo_script, _)),
+                    cargo_config_extra_env,
                 },
-                Self::DetachedFile {
+                ProjectWorkspaceKind::DetachedFile {
                     file: o_file,
-                    sysroot: o_sysroot,
-                    rustc_cfg: o_rustc_cfg,
-                    cargo_script: Some((o_cargo_script, _)),
-                    toolchain: o_toolchain,
-                    target_layout: o_target_layout,
-                    cfg_overrides: o_cfg_overrides,
+                    cargo: Some((o_cargo_script, _)),
+                    cargo_config_extra_env: o_cargo_config_extra_env,
                 },
             ) => {
                 file == o_file
-                    && sysroot == o_sysroot
-                    && rustc_cfg == o_rustc_cfg
-                    && toolchain == o_toolchain
-                    && target_layout == o_target_layout
-                    && cfg_overrides == o_cfg_overrides
                     && cargo_script == o_cargo_script
+                    && cargo_config_extra_env == o_cargo_config_extra_env
             }
-            _ => false,
-        }
+            _ => return false,
+        }) && sysroot == o_sysroot
+            && rustc_cfg == o_rustc_cfg
+            && toolchain == o_toolchain
+            && target_layout == o_target_layout
+            && cfg_overrides == o_cfg_overrides
     }
 
     /// Returns `true` if the project workspace is [`Json`].
@@ -986,7 +881,7 @@ impl ProjectWorkspace {
     /// [`Json`]: ProjectWorkspace::Json
     #[must_use]
     pub fn is_json(&self) -> bool {
-        matches!(self, Self::Json { .. })
+        matches!(self.kind, ProjectWorkspaceKind::Json { .. })
     }
 }
 
diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs
index eaf3f511c4..1fcb5d4496 100644
--- a/crates/rust-analyzer/src/global_state.rs
+++ b/crates/rust-analyzer/src/global_state.rs
@@ -19,7 +19,8 @@ use parking_lot::{
 };
 use proc_macro_api::ProcMacroServer;
 use project_model::{
-    CargoWorkspace, ManifestPath, ProjectWorkspace, Target, WorkspaceBuildScripts,
+    CargoWorkspace, ManifestPath, ProjectWorkspace, ProjectWorkspaceKind, Target,
+    WorkspaceBuildScripts,
 };
 use rustc_hash::{FxHashMap, FxHashSet};
 use triomphe::Arc;
@@ -518,12 +519,13 @@ impl GlobalStateSnapshot {
         let file_id = self.analysis.crate_root(crate_id).ok()?;
         let path = self.vfs_read().file_path(file_id).clone();
         let path = path.as_path()?;
-        self.workspaces.iter().find_map(|ws| match ws {
-            ProjectWorkspace::Cargo { cargo, .. } => {
+        self.workspaces.iter().find_map(|ws| match &ws.kind {
+            ProjectWorkspaceKind::Cargo { cargo, .. }
+            | ProjectWorkspaceKind::DetachedFile { cargo: Some((cargo, _)), .. } => {
                 cargo.target_by_root(path).map(|it| (cargo, it))
             }
-            ProjectWorkspace::Json { .. } => None,
-            ProjectWorkspace::DetachedFile { .. } => None,
+            ProjectWorkspaceKind::Json { .. } => None,
+            ProjectWorkspaceKind::DetachedFile { .. } => None,
         })
     }
 
diff --git a/crates/rust-analyzer/src/handlers/notification.rs b/crates/rust-analyzer/src/handlers/notification.rs
index 4b8c3d06ce..fee8e97273 100644
--- a/crates/rust-analyzer/src/handlers/notification.rs
+++ b/crates/rust-analyzer/src/handlers/notification.rs
@@ -289,17 +289,19 @@ fn run_flycheck(state: &mut GlobalState, vfs_path: VfsPath) -> bool {
 
             // Find all workspaces that have at least one target containing the saved file
             let workspace_ids = world.workspaces.iter().enumerate().filter_map(|(idx, ws)| {
-                let package = match ws {
-                    project_model::ProjectWorkspace::Cargo { cargo, .. } => {
-                        cargo.packages().find_map(|pkg| {
-                            let has_target_with_root = cargo[pkg]
-                                .targets
-                                .iter()
-                                .any(|&it| crate_root_paths.contains(&cargo[it].root.as_path()));
-                            has_target_with_root.then(|| cargo[pkg].name.clone())
-                        })
-                    }
-                    project_model::ProjectWorkspace::Json { project, .. } => {
+                let package = match &ws.kind {
+                    project_model::ProjectWorkspaceKind::Cargo { cargo, .. }
+                    | project_model::ProjectWorkspaceKind::DetachedFile {
+                        cargo: Some((cargo, _)),
+                        ..
+                    } => cargo.packages().find_map(|pkg| {
+                        let has_target_with_root = cargo[pkg]
+                            .targets
+                            .iter()
+                            .any(|&it| crate_root_paths.contains(&cargo[it].root.as_path()));
+                        has_target_with_root.then(|| cargo[pkg].name.clone())
+                    }),
+                    project_model::ProjectWorkspaceKind::Json(project) => {
                         if !project.crates().any(|(_, krate)| {
                             crate_root_paths.contains(&krate.root_module.as_path())
                         }) {
@@ -307,8 +309,7 @@ fn run_flycheck(state: &mut GlobalState, vfs_path: VfsPath) -> bool {
                         }
                         None
                     }
-                    // FIXME
-                    project_model::ProjectWorkspace::DetachedFile { .. } => return None,
+                    project_model::ProjectWorkspaceKind::DetachedFile { .. } => return None,
                 };
                 Some((idx, package))
             });
diff --git a/crates/rust-analyzer/src/handlers/request.rs b/crates/rust-analyzer/src/handlers/request.rs
index 39cdef08f8..6afcc2c51f 100644
--- a/crates/rust-analyzer/src/handlers/request.rs
+++ b/crates/rust-analyzer/src/handlers/request.rs
@@ -27,7 +27,7 @@ use lsp_types::{
     SemanticTokensResult, SymbolInformation, SymbolTag, TextDocumentIdentifier, Url, WorkspaceEdit,
 };
 use paths::Utf8PathBuf;
-use project_model::{ManifestPath, ProjectWorkspace, TargetKind};
+use project_model::{ManifestPath, ProjectWorkspaceKind, TargetKind};
 use serde_json::json;
 use stdx::{format_to, never};
 use syntax::{algo, ast, AstNode, TextRange, TextSize};
@@ -99,10 +99,7 @@ pub(crate) fn handle_analyzer_status(
         format_to!(
             buf,
             "Workspace root folders: {:?}",
-            snap.workspaces
-                .iter()
-                .map(|ws| ws.workspace_definition_path())
-                .collect::<Vec<&AbsPath>>()
+            snap.workspaces.iter().map(|ws| ws.manifest_or_root()).collect::<Vec<&AbsPath>>()
         );
     }
     buf.push_str("\nAnalysis:\n");
@@ -228,7 +225,7 @@ pub(crate) fn handle_run_test(
     };
     let mut handles = vec![];
     for ws in &*state.workspaces {
-        if let ProjectWorkspace::Cargo { cargo, .. } = ws {
+        if let ProjectWorkspaceKind::Cargo { cargo, .. } = &ws.kind {
             let handle = flycheck::CargoTestHandle::new(
                 test_path,
                 state.config.cargo_test_options(),
@@ -769,8 +766,11 @@ pub(crate) fn handle_parent_module(
             let links: Vec<LocationLink> = snap
                 .workspaces
                 .iter()
-                .filter_map(|ws| match ws {
-                    ProjectWorkspace::Cargo { cargo, .. } => cargo.parent_manifests(&manifest_path),
+                .filter_map(|ws| match &ws.kind {
+                    ProjectWorkspaceKind::Cargo { cargo, .. }
+                    | ProjectWorkspaceKind::DetachedFile { cargo: Some((cargo, _)), .. } => {
+                        cargo.parent_manifests(&manifest_path)
+                    }
                     _ => None,
                 })
                 .flatten()
@@ -1758,13 +1758,13 @@ pub(crate) fn handle_open_docs(
     let _p = tracing::span!(tracing::Level::INFO, "handle_open_docs").entered();
     let position = from_proto::file_position(&snap, params)?;
 
-    let ws_and_sysroot = snap.workspaces.iter().find_map(|ws| match ws {
-        ProjectWorkspace::Cargo { cargo, sysroot, .. }
-        | ProjectWorkspace::DetachedFile { cargo_script: Some((cargo, _)), sysroot, .. } => {
-            Some((cargo, sysroot.as_ref().ok()))
+    let ws_and_sysroot = snap.workspaces.iter().find_map(|ws| match &ws.kind {
+        ProjectWorkspaceKind::Cargo { cargo, .. }
+        | ProjectWorkspaceKind::DetachedFile { cargo: Some((cargo, _)), .. } => {
+            Some((cargo, ws.sysroot.as_ref().ok()))
         }
-        ProjectWorkspace::Json { .. } => None,
-        ProjectWorkspace::DetachedFile { .. } => None,
+        ProjectWorkspaceKind::Json { .. } => None,
+        ProjectWorkspaceKind::DetachedFile { .. } => None,
     });
 
     let (cargo, sysroot) = match ws_and_sysroot {
diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs
index 4cc174731c..f2b6e3ed03 100644
--- a/crates/rust-analyzer/src/reload.rs
+++ b/crates/rust-analyzer/src/reload.rs
@@ -25,7 +25,7 @@ use ide_db::{
 use itertools::Itertools;
 use load_cargo::{load_proc_macro, ProjectFolders};
 use proc_macro_api::ProcMacroServer;
-use project_model::{ManifestPath, ProjectWorkspace, WorkspaceBuildScripts};
+use project_model::{ManifestPath, ProjectWorkspace, ProjectWorkspaceKind, WorkspaceBuildScripts};
 use stdx::{format_to, thread::ThreadIntent};
 use triomphe::Arc;
 use vfs::{AbsPath, AbsPathBuf, ChangeKind};
@@ -151,9 +151,7 @@ impl GlobalState {
         }
 
         for ws in self.workspaces.iter() {
-            let (ProjectWorkspace::Cargo { sysroot, .. }
-            | ProjectWorkspace::Json { sysroot, .. }
-            | ProjectWorkspace::DetachedFile { sysroot, .. }) = ws;
+            let sysroot = ws.sysroot.as_ref();
             match sysroot {
                 Err(None) => (),
                 Err(Some(e)) => {
@@ -169,7 +167,7 @@ impl GlobalState {
                     }
                 }
             }
-            if let ProjectWorkspace::Cargo { rustc: Err(Some(e)), .. } = ws {
+            if let ProjectWorkspaceKind::Cargo { rustc: Err(Some(e)), .. } = &ws.kind {
                 status.health = lsp_ext::Health::Warning;
                 message.push_str(e);
                 message.push_str("\n\n");
@@ -501,20 +499,25 @@ impl GlobalState {
                     None => ws.find_sysroot_proc_macro_srv()?,
                 };
 
-                let env =
-                    match ws {
-                        ProjectWorkspace::Cargo { cargo_config_extra_env, sysroot, .. } => {
-                            cargo_config_extra_env
-                                .iter()
-                                .chain(self.config.extra_env())
-                                .map(|(a, b)| (a.clone(), b.clone()))
-                                .chain(sysroot.as_ref().map(|it| {
-                                    ("RUSTUP_TOOLCHAIN".to_owned(), it.root().to_string())
-                                }))
-                                .collect()
-                        }
-                        _ => Default::default(),
-                    };
+                let env = match &ws.kind {
+                    ProjectWorkspaceKind::Cargo { cargo_config_extra_env, .. }
+                    | ProjectWorkspaceKind::DetachedFile {
+                        cargo: Some(_),
+                        cargo_config_extra_env,
+                        ..
+                    } => cargo_config_extra_env
+                        .iter()
+                        .chain(self.config.extra_env())
+                        .map(|(a, b)| (a.clone(), b.clone()))
+                        .chain(
+                            ws.sysroot
+                                .as_ref()
+                                .map(|it| ("RUSTUP_TOOLCHAIN".to_owned(), it.root().to_string())),
+                        )
+                        .collect(),
+
+                    _ => Default::default(),
+                };
                 tracing::info!("Using proc-macro server at {path}");
 
                 ProcMacroServer::spawn(path.clone(), &env).map_err(|err| {
@@ -561,8 +564,8 @@ impl GlobalState {
         self.detached_files = self
             .workspaces
             .iter()
-            .filter_map(|ws| match ws {
-                ProjectWorkspace::DetachedFile { file, .. } => Some(file.clone()),
+            .filter_map(|ws| match &ws.kind {
+                ProjectWorkspaceKind::DetachedFile { file, .. } => Some(file.clone()),
                 _ => None,
             })
             .collect();
@@ -666,33 +669,37 @@ impl GlobalState {
                 config,
                 None,
                 self.config.root_path().clone(),
+                None,
             )],
             flycheck::InvocationStrategy::PerWorkspace => {
                 self.workspaces
                     .iter()
                     .enumerate()
-                    .filter_map(|(id, w)| match w {
-                        ProjectWorkspace::Cargo { cargo, sysroot, .. } => Some((
+                    .filter_map(|(id, ws)| {
+                        Some((
                             id,
-                            cargo.workspace_root(),
-                            sysroot.as_ref().ok().map(|sysroot| sysroot.root().to_owned()),
-                        )),
-                        ProjectWorkspace::Json { project, sysroot, .. } => {
-                            // Enable flychecks for json projects if a custom flycheck command was supplied
-                            // in the workspace configuration.
-                            match config {
-                                FlycheckConfig::CustomCommand { .. } => Some((
-                                    id,
-                                    project.path(),
-                                    sysroot.as_ref().ok().map(|sysroot| sysroot.root().to_owned()),
-                                )),
-                                _ => None,
-                            }
-                        }
-                        // FIXME
-                        ProjectWorkspace::DetachedFile { .. } => None,
+                            match &ws.kind {
+                                ProjectWorkspaceKind::Cargo { cargo, .. }
+                                | ProjectWorkspaceKind::DetachedFile {
+                                    cargo: Some((cargo, _)),
+                                    ..
+                                } => (cargo.workspace_root(), Some(cargo.manifest_path())),
+                                ProjectWorkspaceKind::Json(project) => {
+                                    // Enable flychecks for json projects if a custom flycheck command was supplied
+                                    // in the workspace configuration.
+                                    match config {
+                                        FlycheckConfig::CustomCommand { .. } => {
+                                            (project.path(), None)
+                                        }
+                                        _ => return None,
+                                    }
+                                }
+                                ProjectWorkspaceKind::DetachedFile { .. } => return None,
+                            },
+                            ws.sysroot.as_ref().ok().map(|sysroot| sysroot.root().to_owned()),
+                        ))
                     })
-                    .map(|(id, root, sysroot_root)| {
+                    .map(|(id, (root, manifest_path), sysroot_root)| {
                         let sender = sender.clone();
                         FlycheckHandle::spawn(
                             id,
@@ -700,6 +707,7 @@ impl GlobalState {
                             config.clone(),
                             sysroot_root,
                             root.to_path_buf(),
+                            manifest_path.map(|it| it.to_path_buf()),
                         )
                     })
                     .collect()
@@ -729,9 +737,7 @@ pub fn ws_to_crate_graph(
         let (other, mut crate_proc_macros) = ws.to_crate_graph(&mut load, extra_env);
         let num_layouts = layouts.len();
         let num_toolchains = toolchains.len();
-        let (ProjectWorkspace::Cargo { toolchain, target_layout, .. }
-        | ProjectWorkspace::Json { toolchain, target_layout, .. }
-        | ProjectWorkspace::DetachedFile { toolchain, target_layout, .. }) = ws;
+        let ProjectWorkspace { toolchain, target_layout, .. } = ws;
 
         let mapping = crate_graph.extend(
             other,


Test patch:
diff --git a/crates/project-model/src/tests.rs b/crates/project-model/src/tests.rs
index 41351d5dc0..1308015d13 100644
--- a/crates/project-model/src/tests.rs
+++ b/crates/project-model/src/tests.rs
@@ -10,8 +10,8 @@ use serde::de::DeserializeOwned;
 use triomphe::Arc;
 
 use crate::{
-    CargoWorkspace, CfgOverrides, ManifestPath, ProjectJson, ProjectJsonData, ProjectWorkspace,
-    Sysroot, WorkspaceBuildScripts,
+    workspace::ProjectWorkspaceKind, CargoWorkspace, CfgOverrides, ManifestPath, ProjectJson,
+    ProjectJsonData, ProjectWorkspace, Sysroot, WorkspaceBuildScripts,
 };
 
 fn load_cargo(file: &str) -> (CrateGraph, ProcMacroPaths) {
@@ -26,16 +26,18 @@ fn load_cargo_with_overrides(
     let manifest_path =
         ManifestPath::try_from(AbsPathBuf::try_from(meta.workspace_root.clone()).unwrap()).unwrap();
     let cargo_workspace = CargoWorkspace::new(meta, manifest_path);
-    let project_workspace = ProjectWorkspace::Cargo {
-        cargo: cargo_workspace,
-        build_scripts: WorkspaceBuildScripts::default(),
+    let project_workspace = ProjectWorkspace {
+        kind: ProjectWorkspaceKind::Cargo {
+            cargo: cargo_workspace,
+            build_scripts: WorkspaceBuildScripts::default(),
+            rustc: Err(None),
+            cargo_config_extra_env: Default::default(),
+        },
+        cfg_overrides,
         sysroot: Err(None),
-        rustc: Err(None),
         rustc_cfg: Vec::new(),
-        cfg_overrides,
         toolchain: None,
         target_layout: Err("target_data_layout not loaded".into()),
-        cargo_config_extra_env: Default::default(),
     };
     to_crate_graph(project_workspace)
 }
@@ -48,16 +50,18 @@ fn load_cargo_with_fake_sysroot(
     let manifest_path =
         ManifestPath::try_from(AbsPathBuf::try_from(meta.workspace_root.clone()).unwrap()).unwrap();
     let cargo_workspace = CargoWorkspace::new(meta, manifest_path);
-    let project_workspace = ProjectWorkspace::Cargo {
-        cargo: cargo_workspace,
-        build_scripts: WorkspaceBuildScripts::default(),
+    let project_workspace = ProjectWorkspace {
+        kind: ProjectWorkspaceKind::Cargo {
+            cargo: cargo_workspace,
+            build_scripts: WorkspaceBuildScripts::default(),
+            rustc: Err(None),
+            cargo_config_extra_env: Default::default(),
+        },
         sysroot: Ok(get_fake_sysroot()),
-        rustc: Err(None),
         rustc_cfg: Vec::new(),
         cfg_overrides: Default::default(),
         toolchain: None,
         target_layout: Err("target_data_layout not loaded".into()),
-        cargo_config_extra_env: Default::default(),
     };
     project_workspace.to_crate_graph(
         &mut {
@@ -74,8 +78,8 @@ 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());
-    let project_workspace = ProjectWorkspace::Json {
-        project,
+    let project_workspace = ProjectWorkspace {
+        kind: ProjectWorkspaceKind::Json(project),
         sysroot,
         rustc_cfg: Vec::new(),
         toolchain: None,
@@ -284,16 +288,18 @@ fn smoke_test_real_sysroot_cargo() {
     )
     .unwrap());
 
-    let project_workspace = ProjectWorkspace::Cargo {
-        cargo: cargo_workspace,
-        build_scripts: WorkspaceBuildScripts::default(),
+    let project_workspace = ProjectWorkspace {
+        kind: ProjectWorkspaceKind::Cargo {
+            cargo: cargo_workspace,
+            build_scripts: WorkspaceBuildScripts::default(),
+            rustc: Err(None),
+            cargo_config_extra_env: Default::default(),
+        },
         sysroot,
-        rustc: Err(None),
         rustc_cfg: Vec::new(),
         cfg_overrides: Default::default(),
         toolchain: None,
         target_layout: Err("target_data_layout not loaded".into()),
-        cargo_config_extra_env: Default::default(),
     };
     project_workspace.to_crate_graph(
         &mut {
diff --git a/crates/rust-analyzer/src/cli/rustc_tests.rs b/crates/rust-analyzer/src/cli/rustc_tests.rs
index cd15d86aa6..85f964b1dd 100644
--- a/crates/rust-analyzer/src/cli/rustc_tests.rs
+++ b/crates/rust-analyzer/src/cli/rustc_tests.rs
@@ -11,7 +11,8 @@ use itertools::Either;
 use profile::StopWatch;
 use project_model::target_data_layout::RustcDataLayoutConfig;
 use project_model::{
-    target_data_layout, CargoConfig, ManifestPath, ProjectWorkspace, RustLibSource, Sysroot,
+    target_data_layout, CargoConfig, ManifestPath, ProjectWorkspace, ProjectWorkspaceKind,
+    RustLibSource, Sysroot,
 };
 
 use load_cargo::{load_workspace, LoadCargoConfig, ProcMacroServerChoice};
@@ -77,14 +78,17 @@ impl Tester {
             &cargo_config.extra_env,
         );
 
-        let workspace = ProjectWorkspace::DetachedFile {
-            file: ManifestPath::try_from(tmp_file).unwrap(),
+        let workspace = ProjectWorkspace {
+            kind: ProjectWorkspaceKind::DetachedFile {
+                file: ManifestPath::try_from(tmp_file).unwrap(),
+                cargo: None,
+                cargo_config_extra_env: Default::default(),
+            },
             sysroot,
             rustc_cfg: vec![],
             toolchain: None,
             target_layout: data_layout.map(Arc::from).map_err(|it| Arc::from(it.to_string())),
             cfg_overrides: Default::default(),
-            cargo_script: None,
         };
         let load_cargo_config = LoadCargoConfig {
             load_out_dirs_from_check: false,
diff --git a/crates/rust-analyzer/tests/crate_graph.rs b/crates/rust-analyzer/tests/crate_graph.rs
index e833ea5e11..59b229cd06 100644
--- a/crates/rust-analyzer/tests/crate_graph.rs
+++ b/crates/rust-analyzer/tests/crate_graph.rs
@@ -1,7 +1,8 @@
 use std::path::PathBuf;
 
 use project_model::{
-    CargoWorkspace, ManifestPath, Metadata, ProjectWorkspace, Sysroot, WorkspaceBuildScripts,
+    CargoWorkspace, ManifestPath, Metadata, ProjectWorkspace, ProjectWorkspaceKind, Sysroot,
+    WorkspaceBuildScripts,
 };
 use rust_analyzer::ws_to_crate_graph;
 use rustc_hash::FxHashMap;
@@ -13,16 +14,18 @@ fn load_cargo_with_fake_sysroot(file: &str) -> ProjectWorkspace {
     let manifest_path =
         ManifestPath::try_from(AbsPathBuf::try_from(meta.workspace_root.clone()).unwrap()).unwrap();
     let cargo_workspace = CargoWorkspace::new(meta, manifest_path);
-    ProjectWorkspace::Cargo {
-        cargo: cargo_workspace,
-        build_scripts: WorkspaceBuildScripts::default(),
+    ProjectWorkspace {
+        kind: ProjectWorkspaceKind::Cargo {
+            cargo: cargo_workspace,
+            build_scripts: WorkspaceBuildScripts::default(),
+            rustc: Err(None),
+            cargo_config_extra_env: Default::default(),
+        },
         sysroot: Ok(get_fake_sysroot()),
-        rustc: Err(None),
         rustc_cfg: Vec::new(),
         cfg_overrides: Default::default(),
         toolchain: None,
         target_layout: Err("target_data_layout not loaded".into()),
-        cargo_config_extra_env: Default::default(),
     }
 }
 
diff --git a/crates/rust-analyzer/tests/slow-tests/main.rs b/crates/rust-analyzer/tests/slow-tests/main.rs
index 2ca86bc50a..5a1397bbb0 100644
--- a/crates/rust-analyzer/tests/slow-tests/main.rs
+++ b/crates/rust-analyzer/tests/slow-tests/main.rs
@@ -150,7 +150,7 @@ use dependency2::Spam;
     )
     .with_config(serde_json::json!({
         "cargo": { "sysroot": null },
-        "detachedFiles": ["/src/lib.rs"],
+        "linkedProjects": ["src/lib.rs"],
     }))
     .server()
     .wait_until_workspace_is_loaded();