about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/layout/tests.rs4
-rw-r--r--src/tools/rust-analyzer/crates/project-model/src/build_scripts.rs14
-rw-r--r--src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs14
-rw-r--r--src/tools/rust-analyzer/crates/project-model/src/env.rs4
-rw-r--r--src/tools/rust-analyzer/crates/project-model/src/rustc_cfg.rs8
-rw-r--r--src/tools/rust-analyzer/crates/project-model/src/sysroot.rs204
-rw-r--r--src/tools/rust-analyzer/crates/project-model/src/target_data_layout.rs6
-rw-r--r--src/tools/rust-analyzer/crates/project-model/src/tests.rs13
-rw-r--r--src/tools/rust-analyzer/crates/project-model/src/workspace.rs266
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/cli/rustc_tests.rs6
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs6
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/ext.rs1
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs73
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/tests/crate_graph.rs4
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/main.rs6
-rw-r--r--src/tools/rust-analyzer/docs/dev/lsp-extensions.md2
-rw-r--r--src/tools/rust-analyzer/editors/code/src/ctx.ts6
-rw-r--r--src/tools/rust-analyzer/editors/code/src/lsp_ext.ts1
18 files changed, 277 insertions, 361 deletions
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/layout/tests.rs b/src/tools/rust-analyzer/crates/hir-ty/src/layout/tests.rs
index 6c1eccb75e6..392bda51b52 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/layout/tests.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/layout/tests.rs
@@ -1,7 +1,7 @@
 use chalk_ir::{AdtId, TyKind};
 use either::Either;
 use hir_def::db::DefDatabase;
-use project_model::target_data_layout::RustcDataLayoutConfig;
+use project_model::{target_data_layout::RustcDataLayoutConfig, Sysroot};
 use rustc_hash::FxHashMap;
 use test_fixture::WithFixture;
 use triomphe::Arc;
@@ -17,7 +17,7 @@ mod closure;
 
 fn current_machine_data_layout() -> String {
     project_model::target_data_layout::get(
-        RustcDataLayoutConfig::Rustc(None),
+        RustcDataLayoutConfig::Rustc(&Sysroot::empty()),
         None,
         &FxHashMap::default(),
     )
diff --git a/src/tools/rust-analyzer/crates/project-model/src/build_scripts.rs b/src/tools/rust-analyzer/crates/project-model/src/build_scripts.rs
index 8e1f7fdcded..d2f423590e2 100644
--- a/src/tools/rust-analyzer/crates/project-model/src/build_scripts.rs
+++ b/src/tools/rust-analyzer/crates/project-model/src/build_scripts.rs
@@ -65,7 +65,7 @@ impl WorkspaceBuildScripts {
         allowed_features: &FxHashSet<String>,
         manifest_path: &ManifestPath,
         toolchain: Option<&Version>,
-        sysroot: Option<&Sysroot>,
+        sysroot: &Sysroot,
     ) -> io::Result<Command> {
         const RUST_1_75: Version = Version::new(1, 75, 0);
         let mut cmd = match config.run_build_script_command.as_deref() {
@@ -75,7 +75,7 @@ impl WorkspaceBuildScripts {
                 cmd
             }
             _ => {
-                let mut cmd = Sysroot::tool(sysroot, Tool::Cargo);
+                let mut cmd = sysroot.tool(Tool::Cargo);
 
                 cmd.args(["check", "--quiet", "--workspace", "--message-format=json"]);
                 cmd.args(&config.extra_args);
@@ -149,7 +149,7 @@ impl WorkspaceBuildScripts {
         workspace: &CargoWorkspace,
         progress: &dyn Fn(String),
         toolchain: Option<&Version>,
-        sysroot: Option<&Sysroot>,
+        sysroot: &Sysroot,
     ) -> io::Result<WorkspaceBuildScripts> {
         let current_dir = match &config.invocation_location {
             InvocationLocation::Root(root) if config.run_build_script_command.is_some() => {
@@ -195,7 +195,7 @@ impl WorkspaceBuildScripts {
             // This is not gonna be used anyways, so just construct a dummy here
             &ManifestPath::try_from(workspace_root.clone()).unwrap(),
             None,
-            None,
+            &Sysroot::empty(),
         )?;
         // NB: Cargo.toml could have been modified between `cargo metadata` and
         // `cargo check`. We shouldn't assume that package ids we see here are
@@ -412,7 +412,7 @@ impl WorkspaceBuildScripts {
         rustc: &CargoWorkspace,
         current_dir: &AbsPath,
         extra_env: &FxHashMap<String, String>,
-        sysroot: Option<&Sysroot>,
+        sysroot: &Sysroot,
     ) -> Self {
         let mut bs = WorkspaceBuildScripts::default();
         for p in rustc.packages() {
@@ -420,7 +420,7 @@ impl WorkspaceBuildScripts {
         }
         let res = (|| {
             let target_libdir = (|| {
-                let mut cargo_config = Sysroot::tool(sysroot, Tool::Cargo);
+                let mut cargo_config = sysroot.tool(Tool::Cargo);
                 cargo_config.envs(extra_env);
                 cargo_config
                     .current_dir(current_dir)
@@ -429,7 +429,7 @@ impl WorkspaceBuildScripts {
                 if let Ok(it) = utf8_stdout(cargo_config) {
                     return Ok(it);
                 }
-                let mut cmd = Sysroot::tool(sysroot, Tool::Rustc);
+                let mut cmd = sysroot.tool(Tool::Rustc);
                 cmd.envs(extra_env);
                 cmd.args(["--print", "target-libdir"]);
                 utf8_stdout(cmd)
diff --git a/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs b/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs
index 9955f2687c9..632ba1cacf2 100644
--- a/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs
+++ b/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs
@@ -258,12 +258,12 @@ impl CargoWorkspace {
         cargo_toml: &ManifestPath,
         current_dir: &AbsPath,
         config: &CargoConfig,
-        sysroot: Option<&Sysroot>,
+        sysroot: &Sysroot,
         progress: &dyn Fn(String),
     ) -> anyhow::Result<cargo_metadata::Metadata> {
         let targets = find_list_of_build_targets(config, cargo_toml, sysroot);
 
-        let cargo = Sysroot::tool(sysroot, Tool::Cargo);
+        let cargo = sysroot.tool(Tool::Cargo);
         let mut meta = MetadataCommand::new();
         meta.cargo_path(cargo.get_program());
         cargo.get_envs().for_each(|(var, val)| _ = meta.env(var, val.unwrap_or_default()));
@@ -536,7 +536,7 @@ impl CargoWorkspace {
 fn find_list_of_build_targets(
     config: &CargoConfig,
     cargo_toml: &ManifestPath,
-    sysroot: Option<&Sysroot>,
+    sysroot: &Sysroot,
 ) -> Vec<String> {
     if let Some(target) = &config.target {
         return [target.into()].to_vec();
@@ -553,9 +553,9 @@ fn find_list_of_build_targets(
 fn rustc_discover_host_triple(
     cargo_toml: &ManifestPath,
     extra_env: &FxHashMap<String, String>,
-    sysroot: Option<&Sysroot>,
+    sysroot: &Sysroot,
 ) -> Option<String> {
-    let mut rustc = Sysroot::tool(sysroot, Tool::Rustc);
+    let mut rustc = sysroot.tool(Tool::Rustc);
     rustc.envs(extra_env);
     rustc.current_dir(cargo_toml.parent()).arg("-vV");
     tracing::debug!("Discovering host platform by {:?}", rustc);
@@ -581,9 +581,9 @@ fn rustc_discover_host_triple(
 fn cargo_config_build_target(
     cargo_toml: &ManifestPath,
     extra_env: &FxHashMap<String, String>,
-    sysroot: Option<&Sysroot>,
+    sysroot: &Sysroot,
 ) -> Vec<String> {
-    let mut cargo_config = Sysroot::tool(sysroot, Tool::Cargo);
+    let mut cargo_config = sysroot.tool(Tool::Cargo);
     cargo_config.envs(extra_env);
     cargo_config
         .current_dir(cargo_toml.parent())
diff --git a/src/tools/rust-analyzer/crates/project-model/src/env.rs b/src/tools/rust-analyzer/crates/project-model/src/env.rs
index 5520cdaff6b..88fb10a68c6 100644
--- a/src/tools/rust-analyzer/crates/project-model/src/env.rs
+++ b/src/tools/rust-analyzer/crates/project-model/src/env.rs
@@ -62,9 +62,9 @@ pub(crate) fn inject_rustc_tool_env(env: &mut Env, cargo_name: &str, kind: Targe
 pub(crate) fn cargo_config_env(
     manifest: &ManifestPath,
     extra_env: &FxHashMap<String, String>,
-    sysroot: Option<&Sysroot>,
+    sysroot: &Sysroot,
 ) -> FxHashMap<String, String> {
-    let mut cargo_config = Sysroot::tool(sysroot, Tool::Cargo);
+    let mut cargo_config = sysroot.tool(Tool::Cargo);
     cargo_config.envs(extra_env);
     cargo_config
         .current_dir(manifest.parent())
diff --git a/src/tools/rust-analyzer/crates/project-model/src/rustc_cfg.rs b/src/tools/rust-analyzer/crates/project-model/src/rustc_cfg.rs
index 4f69b2b96f0..26499308ce9 100644
--- a/src/tools/rust-analyzer/crates/project-model/src/rustc_cfg.rs
+++ b/src/tools/rust-analyzer/crates/project-model/src/rustc_cfg.rs
@@ -10,10 +10,10 @@ use crate::{cfg::CfgFlag, utf8_stdout, ManifestPath, Sysroot};
 pub(crate) enum RustcCfgConfig<'a> {
     /// Use `rustc --print cfg`, either from with the binary from the sysroot or by discovering via
     /// [`toolchain::rustc`].
-    Rustc(Option<&'a Sysroot>),
+    Rustc(&'a Sysroot),
     /// Use `cargo --print cfg`, either from with the binary from the sysroot or by discovering via
     /// [`toolchain::cargo`].
-    Cargo(Option<&'a Sysroot>, &'a ManifestPath),
+    Cargo(&'a Sysroot, &'a ManifestPath),
 }
 
 pub(crate) fn get(
@@ -65,7 +65,7 @@ fn get_rust_cfgs(
 ) -> anyhow::Result<String> {
     let sysroot = match config {
         RustcCfgConfig::Cargo(sysroot, cargo_toml) => {
-            let mut cmd = Sysroot::tool(sysroot, Tool::Cargo);
+            let mut cmd = sysroot.tool(Tool::Cargo);
 
             cmd.envs(extra_env);
             cmd.current_dir(cargo_toml.parent())
@@ -86,7 +86,7 @@ fn get_rust_cfgs(
         RustcCfgConfig::Rustc(sysroot) => sysroot,
     };
 
-    let mut cmd = Sysroot::tool(sysroot, Tool::Rustc);
+    let mut cmd = sysroot.tool(Tool::Rustc);
     cmd.envs(extra_env);
     cmd.args(["--print", "cfg", "-O"]);
     if let Some(target) = target {
diff --git a/src/tools/rust-analyzer/crates/project-model/src/sysroot.rs b/src/tools/rust-analyzer/crates/project-model/src/sysroot.rs
index e6bbe6ede8a..653e7157bcb 100644
--- a/src/tools/rust-analyzer/crates/project-model/src/sysroot.rs
+++ b/src/tools/rust-analyzer/crates/project-model/src/sysroot.rs
@@ -4,7 +4,7 @@
 //! but we can't process `.rlib` and need source code instead. The source code
 //! is typically installed with `rustup component add rust-src` command.
 
-use std::{env, fs, ops, process::Command, sync::Arc};
+use std::{env, fs, ops, process::Command};
 
 use anyhow::{format_err, Result};
 use base_db::CrateName;
@@ -16,30 +16,19 @@ use toolchain::{probe_for_binary, Tool};
 
 use crate::{utf8_stdout, CargoConfig, CargoWorkspace, ManifestPath};
 
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, PartialEq, Eq)]
 pub struct Sysroot {
-    root: AbsPathBuf,
-    src_root: Option<Result<AbsPathBuf, Arc<anyhow::Error>>>,
+    root: Option<AbsPathBuf>,
+    src_root: Option<AbsPathBuf>,
     mode: SysrootMode,
-}
-
-impl Eq for Sysroot {}
-impl PartialEq for Sysroot {
-    fn eq(&self, other: &Self) -> bool {
-        self.root == other.root
-            && self.mode == other.mode
-            && match (&self.src_root, &other.src_root) {
-                (Some(Ok(this)), Some(Ok(other))) => this == other,
-                (None, None) | (Some(Err(_)), Some(Err(_))) => true,
-                _ => false,
-            }
-    }
+    error: Option<String>,
 }
 
 #[derive(Debug, Clone, Eq, PartialEq)]
 pub(crate) enum SysrootMode {
     Workspace(CargoWorkspace),
     Stitched(Stitched),
+    Empty,
 }
 
 #[derive(Debug, Clone, Eq, PartialEq)]
@@ -89,70 +78,40 @@ pub(crate) struct SysrootCrateData {
 }
 
 impl Sysroot {
+    pub const fn empty() -> Sysroot {
+        Sysroot { root: None, src_root: None, mode: SysrootMode::Empty, error: None }
+    }
+
     /// Returns sysroot "root" directory, where `bin/`, `etc/`, `lib/`, `libexec/`
     /// subfolder live, like:
     /// `$HOME/.rustup/toolchains/nightly-2022-07-23-x86_64-unknown-linux-gnu`
-    pub fn root(&self) -> &AbsPath {
-        &self.root
+    pub fn root(&self) -> Option<&AbsPath> {
+        self.root.as_deref()
     }
 
     /// Returns the sysroot "source" directory, where stdlib sources are located, like:
     /// `$HOME/.rustup/toolchains/nightly-2022-07-23-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library`
     pub fn src_root(&self) -> Option<&AbsPath> {
-        self.src_root.as_ref()?.as_deref().ok()
+        self.src_root.as_deref()
     }
 
     pub fn is_empty(&self) -> bool {
         match &self.mode {
             SysrootMode::Workspace(ws) => ws.packages().next().is_none(),
             SysrootMode::Stitched(stitched) => stitched.crates.is_empty(),
+            SysrootMode::Empty => true,
         }
     }
 
-    pub fn loading_warning(&self) -> Option<String> {
-        let src_root = match &self.src_root {
-            None => return Some(format!("sysroot at `{}` has no library sources", self.root)),
-            Some(Ok(src_root)) => src_root,
-            Some(Err(e)) => return Some(e.to_string()),
-        };
-        let has_core = match &self.mode {
-            SysrootMode::Workspace(ws) => ws.packages().any(|p| ws[p].name == "core"),
-            SysrootMode::Stitched(stitched) => stitched.by_name("core").is_some(),
-        };
-        if !has_core {
-            let var_note = if env::var_os("RUST_SRC_PATH").is_some() {
-                " (`RUST_SRC_PATH` might be incorrect, try unsetting it)"
-            } else {
-                " try running `rustup component add rust-src` to possible fix this"
-            };
-            Some(format!("could not find libcore in loaded sysroot at `{}`{var_note}", src_root,))
-        } else {
-            None
-        }
-    }
-
-    pub fn check_has_core(&self) -> Result<(), String> {
-        let Some(Ok(src_root)) = &self.src_root else { return Ok(()) };
-        let has_core = match &self.mode {
-            SysrootMode::Workspace(ws) => ws.packages().any(|p| ws[p].name == "core"),
-            SysrootMode::Stitched(stitched) => stitched.by_name("core").is_some(),
-        };
-        if !has_core {
-            let var_note = if env::var_os("RUST_SRC_PATH").is_some() {
-                " (`RUST_SRC_PATH` might be incorrect, try unsetting it)"
-            } else {
-                " try running `rustup component add rust-src` to possible fix this"
-            };
-            Err(format!("could not find libcore in loaded sysroot at `{}`{var_note}", src_root,))
-        } else {
-            Ok(())
-        }
+    pub fn error(&self) -> Option<&str> {
+        self.error.as_deref()
     }
 
     pub fn num_packages(&self) -> usize {
         match &self.mode {
             SysrootMode::Workspace(ws) => ws.packages().count(),
             SysrootMode::Stitched(c) => c.crates().count(),
+            SysrootMode::Empty => 0,
         }
     }
 
@@ -168,63 +127,50 @@ impl Sysroot {
         dir: &AbsPath,
         extra_env: &FxHashMap<String, String>,
         metadata: bool,
-    ) -> Result<Sysroot> {
-        tracing::debug!("discovering sysroot for {dir}");
-        let sysroot_dir = discover_sysroot_dir(dir, extra_env)?;
-        let sysroot_src_dir =
-            discover_sysroot_src_dir_or_add_component(&sysroot_dir, dir, extra_env);
-        Ok(Sysroot::load(sysroot_dir, Some(sysroot_src_dir), metadata))
-    }
-
-    pub fn discover_no_source(
-        dir: &AbsPath,
-        extra_env: &FxHashMap<String, String>,
-    ) -> Result<Sysroot> {
-        tracing::debug!("discovering sysroot for {dir}");
-        let sysroot_dir = discover_sysroot_dir(dir, extra_env)?;
-        let sysroot_src_dir =
-            discover_sysroot_src_dir_or_add_component(&sysroot_dir, dir, extra_env);
-        Ok(Sysroot::load(sysroot_dir, Some(sysroot_src_dir), false))
+    ) -> Sysroot {
+        let sysroot_dir = discover_sysroot_dir(dir, extra_env);
+        let sysroot_src_dir = sysroot_dir.as_ref().ok().map(|sysroot_dir| {
+            discover_sysroot_src_dir_or_add_component(sysroot_dir, dir, extra_env)
+        });
+        Sysroot::load_core_check(Some(sysroot_dir), sysroot_src_dir, metadata)
     }
 
     pub fn discover_with_src_override(
         current_dir: &AbsPath,
         extra_env: &FxHashMap<String, String>,
-        src: AbsPathBuf,
+        sysroot_src_dir: AbsPathBuf,
         metadata: bool,
-    ) -> Result<Sysroot> {
-        tracing::debug!("discovering sysroot for {current_dir}");
-        let sysroot_dir = discover_sysroot_dir(current_dir, extra_env)?;
-        Ok(Sysroot::load(sysroot_dir, Some(Ok(src)), metadata))
+    ) -> Sysroot {
+        let sysroot_dir = discover_sysroot_dir(current_dir, extra_env);
+        Sysroot::load_core_check(Some(sysroot_dir), Some(Ok(sysroot_src_dir)), metadata)
     }
 
-    pub fn discover_rustc_src(&self) -> Option<ManifestPath> {
-        get_rustc_src(&self.root)
+    pub fn discover_sysroot_src_dir(sysroot_dir: AbsPathBuf, metadata: bool) -> Sysroot {
+        let sysroot_src_dir = discover_sysroot_src_dir(&sysroot_dir)
+            .ok_or_else(|| format_err!("can't find standard library sources in {sysroot_dir}"));
+        Sysroot::load_core_check(Some(Ok(sysroot_dir)), Some(sysroot_src_dir), metadata)
     }
 
-    pub fn with_sysroot_dir(sysroot_dir: AbsPathBuf, metadata: bool) -> Result<Sysroot> {
-        let sysroot_src_dir = discover_sysroot_src_dir(&sysroot_dir).ok_or_else(|| {
-            format_err!("can't load standard library from sysroot path {sysroot_dir}")
-        });
-        Ok(Sysroot::load(sysroot_dir, Some(sysroot_src_dir), metadata))
+    pub fn discover_rustc_src(&self) -> Option<ManifestPath> {
+        get_rustc_src(self.root()?)
     }
 
     /// Returns a command to run a tool preferring the cargo proxies if the sysroot exists.
-    pub fn tool(sysroot: Option<&Self>, tool: Tool) -> Command {
-        match sysroot {
-            Some(sysroot) => {
+    pub fn tool(&self, tool: Tool) -> Command {
+        match self.root() {
+            Some(root) => {
                 // special case rustc, we can look that up directly in the sysroot's bin folder
                 // as it should never invoke another cargo binary
                 if let Tool::Rustc = tool {
                     if let Some(path) =
-                        probe_for_binary(sysroot.root.join("bin").join(Tool::Rustc.name()).into())
+                        probe_for_binary(root.join("bin").join(Tool::Rustc.name()).into())
                     {
                         return Command::new(path);
                     }
                 }
 
                 let mut cmd = Command::new(tool.prefer_proxy());
-                cmd.env("RUSTUP_TOOLCHAIN", AsRef::<std::path::Path>::as_ref(&sysroot.root));
+                cmd.env("RUSTUP_TOOLCHAIN", AsRef::<std::path::Path>::as_ref(root));
                 cmd
             }
             _ => Command::new(tool.path()),
@@ -232,35 +178,89 @@ impl Sysroot {
     }
 
     pub fn discover_proc_macro_srv(&self) -> anyhow::Result<AbsPathBuf> {
+        let Some(root) = self.root() else {
+            return Err(anyhow::format_err!("no sysroot",));
+        };
         ["libexec", "lib"]
             .into_iter()
-            .map(|segment| self.root().join(segment).join("rust-analyzer-proc-macro-srv"))
+            .map(|segment| root.join(segment).join("rust-analyzer-proc-macro-srv"))
             .find_map(|server_path| probe_for_binary(server_path.into()))
             .map(AbsPathBuf::assert)
             .ok_or_else(|| {
-                anyhow::format_err!("cannot find proc-macro server in sysroot `{}`", self.root())
+                anyhow::format_err!("cannot find proc-macro server in sysroot `{}`", root)
             })
     }
 
     pub fn load(
-        sysroot_dir: AbsPathBuf,
+        sysroot_dir: Option<AbsPathBuf>,
+        sysroot_src_dir: Option<AbsPathBuf>,
+        metadata: bool,
+    ) -> Sysroot {
+        Self::load_core_check(sysroot_dir.map(Ok), sysroot_src_dir.map(Ok), metadata)
+    }
+
+    fn load_core_check(
+        sysroot_dir: Option<Result<AbsPathBuf, anyhow::Error>>,
+        sysroot_src_dir: Option<Result<AbsPathBuf, anyhow::Error>>,
+        metadata: bool,
+    ) -> Sysroot {
+        let mut sysroot = Self::load_(sysroot_dir, sysroot_src_dir, metadata);
+        if sysroot.error.is_none() {
+            if let Some(src_root) = &sysroot.src_root {
+                let has_core = match &sysroot.mode {
+                    SysrootMode::Workspace(ws) => ws.packages().any(|p| ws[p].name == "core"),
+                    SysrootMode::Stitched(stitched) => stitched.by_name("core").is_some(),
+                    SysrootMode::Empty => true,
+                };
+                if !has_core {
+                    let var_note = if env::var_os("RUST_SRC_PATH").is_some() {
+                        " (env var `RUST_SRC_PATH` is set and may be incorrect, try unsetting it)"
+                    } else {
+                        ", try running `rustup component add rust-src` to possibly fix this"
+                    };
+                    sysroot.error = Some(format!(
+                        "sysroot at `{}` is missing a `core` library{var_note}",
+                        src_root,
+                    ));
+                }
+            }
+        }
+        sysroot
+    }
+
+    fn load_(
+        sysroot_dir: Option<Result<AbsPathBuf, anyhow::Error>>,
         sysroot_src_dir: Option<Result<AbsPathBuf, anyhow::Error>>,
         metadata: bool,
     ) -> Sysroot {
+        let sysroot_dir = match sysroot_dir {
+            Some(Ok(sysroot_dir)) => Some(sysroot_dir),
+            Some(Err(e)) => {
+                return Sysroot {
+                    root: None,
+                    src_root: None,
+                    mode: SysrootMode::Empty,
+                    error: Some(e.to_string()),
+                }
+            }
+            None => None,
+        };
         let sysroot_src_dir = match sysroot_src_dir {
             Some(Ok(sysroot_src_dir)) => sysroot_src_dir,
             Some(Err(e)) => {
                 return Sysroot {
                     root: sysroot_dir,
-                    src_root: Some(Err(Arc::new(e))),
-                    mode: SysrootMode::Stitched(Stitched { crates: Arena::default() }),
+                    src_root: None,
+                    mode: SysrootMode::Empty,
+                    error: Some(e.to_string()),
                 }
             }
             None => {
                 return Sysroot {
                     root: sysroot_dir,
                     src_root: None,
-                    mode: SysrootMode::Stitched(Stitched { crates: Arena::default() }),
+                    mode: SysrootMode::Empty,
+                    error: None,
                 }
             }
         };
@@ -284,7 +284,7 @@ impl Sysroot {
                     &sysroot_cargo_toml,
                     &current_dir,
                     &cargo_config,
-                    None,
+                    &Sysroot::empty(),
                     &|_| (),
                 )
                 .map_err(|e| {
@@ -368,8 +368,9 @@ impl Sysroot {
                 let cargo_workspace = CargoWorkspace::new(res, sysroot_cargo_toml);
                 Some(Sysroot {
                     root: sysroot_dir.clone(),
-                    src_root: Some(Ok(sysroot_src_dir.clone())),
+                    src_root: Some(sysroot_src_dir.clone()),
                     mode: SysrootMode::Workspace(cargo_workspace),
+                    error: None,
                 })
             })();
             if let Some(sysroot) = sysroot {
@@ -420,8 +421,9 @@ impl Sysroot {
         }
         Sysroot {
             root: sysroot_dir,
-            src_root: Some(Ok(sysroot_src_dir)),
+            src_root: Some(sysroot_src_dir),
             mode: SysrootMode::Stitched(stitched),
+            error: None,
         }
     }
 }
diff --git a/src/tools/rust-analyzer/crates/project-model/src/target_data_layout.rs b/src/tools/rust-analyzer/crates/project-model/src/target_data_layout.rs
index 4e810a0232e..8a8a2d32558 100644
--- a/src/tools/rust-analyzer/crates/project-model/src/target_data_layout.rs
+++ b/src/tools/rust-analyzer/crates/project-model/src/target_data_layout.rs
@@ -9,10 +9,10 @@ use crate::{utf8_stdout, ManifestPath, Sysroot};
 pub enum RustcDataLayoutConfig<'a> {
     /// Use `rustc --print target-spec-json`, either from with the binary from the sysroot or by discovering via
     /// [`toolchain::rustc`].
-    Rustc(Option<&'a Sysroot>),
+    Rustc(&'a Sysroot),
     /// Use `cargo --print target-spec-json`, either from with the binary from the sysroot or by discovering via
     /// [`toolchain::cargo`].
-    Cargo(Option<&'a Sysroot>, &'a ManifestPath),
+    Cargo(&'a Sysroot, &'a ManifestPath),
 }
 
 pub fn get(
@@ -28,7 +28,7 @@ pub fn get(
     };
     let sysroot = match config {
         RustcDataLayoutConfig::Cargo(sysroot, cargo_toml) => {
-            let mut cmd = Sysroot::tool(sysroot, Tool::Cargo);
+            let mut cmd = sysroot.tool(Tool::Cargo);
             cmd.envs(extra_env);
             cmd.current_dir(cargo_toml.parent())
                 .args([
diff --git a/src/tools/rust-analyzer/crates/project-model/src/tests.rs b/src/tools/rust-analyzer/crates/project-model/src/tests.rs
index 3d5a934fc92..a6730863d6b 100644
--- a/src/tools/rust-analyzer/crates/project-model/src/tests.rs
+++ b/src/tools/rust-analyzer/crates/project-model/src/tests.rs
@@ -34,7 +34,7 @@ fn load_cargo_with_overrides(
             cargo_config_extra_env: Default::default(),
         },
         cfg_overrides,
-        sysroot: Err(None),
+        sysroot: Sysroot::empty(),
         rustc_cfg: Vec::new(),
         toolchain: None,
         target_layout: Err("target_data_layout not loaded".into()),
@@ -57,7 +57,7 @@ fn load_cargo_with_fake_sysroot(
             rustc: Err(None),
             cargo_config_extra_env: Default::default(),
         },
-        sysroot: Ok(get_fake_sysroot()),
+        sysroot: get_fake_sysroot(),
         rustc_cfg: Vec::new(),
         cfg_overrides: Default::default(),
         toolchain: None,
@@ -77,7 +77,7 @@ fn load_cargo_with_fake_sysroot(
 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 sysroot = get_fake_sysroot();
     let project_workspace = ProjectWorkspace {
         kind: ProjectWorkspaceKind::Json(project),
         sysroot,
@@ -144,7 +144,7 @@ fn get_fake_sysroot() -> Sysroot {
     // fake sysroot, so we give them both the same path:
     let sysroot_dir = AbsPathBuf::assert(sysroot_path);
     let sysroot_src_dir = sysroot_dir.clone();
-    Sysroot::load(sysroot_dir, Some(Ok(sysroot_src_dir)), false)
+    Sysroot::load(Some(sysroot_dir), Some(sysroot_src_dir), false)
 }
 
 fn rooted_project_json(data: ProjectJsonData) -> ProjectJson {
@@ -281,12 +281,11 @@ fn smoke_test_real_sysroot_cargo() {
     let manifest_path =
         ManifestPath::try_from(AbsPathBuf::try_from(meta.workspace_root.clone()).unwrap()).unwrap();
     let cargo_workspace = CargoWorkspace::new(meta, manifest_path);
-    let sysroot = Ok(Sysroot::discover(
+    let sysroot = Sysroot::discover(
         AbsPath::assert(Utf8Path::new(env!("CARGO_MANIFEST_DIR"))),
         &Default::default(),
         true,
-    )
-    .unwrap());
+    );
 
     let project_workspace = ProjectWorkspace {
         kind: ProjectWorkspaceKind::Cargo {
diff --git a/src/tools/rust-analyzer/crates/project-model/src/workspace.rs b/src/tools/rust-analyzer/crates/project-model/src/workspace.rs
index 85621444e33..0d2174073a2 100644
--- a/src/tools/rust-analyzer/crates/project-model/src/workspace.rs
+++ b/src/tools/rust-analyzer/crates/project-model/src/workspace.rs
@@ -48,7 +48,7 @@ pub struct PackageRoot {
 pub struct ProjectWorkspace {
     pub kind: ProjectWorkspaceKind,
     /// The sysroot loaded for this workspace.
-    pub sysroot: Result<Sysroot, Option<String>>,
+    pub sysroot: Sysroot,
     /// 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
@@ -112,7 +112,7 @@ impl fmt::Debug for ProjectWorkspace {
                 .debug_struct("Cargo")
                 .field("root", &cargo.workspace_root().file_name())
                 .field("n_packages", &cargo.packages().len())
-                .field("sysroot", &sysroot.is_ok())
+                .field("n_sysroot_crates", &sysroot.num_packages())
                 .field(
                     "n_rustc_compiler_crates",
                     &rustc.as_ref().map(|a| a.as_ref()).map_or(0, |(rc, _)| rc.packages().len()),
@@ -125,11 +125,9 @@ impl fmt::Debug for ProjectWorkspace {
                 .finish(),
             ProjectWorkspaceKind::Json(project) => {
                 let mut debug_struct = f.debug_struct("Json");
-                debug_struct.field("n_crates", &project.n_crates());
-                if let Ok(sysroot) = sysroot {
-                    debug_struct.field("n_sysroot_crates", &sysroot.num_packages());
-                }
                 debug_struct
+                    .field("n_crates", &project.n_crates())
+                    .field("n_sysroot_crates", &sysroot.num_packages())
                     .field("n_rustc_cfg", &rustc_cfg.len())
                     .field("toolchain", &toolchain)
                     .field("data_layout", &target_layout)
@@ -144,7 +142,7 @@ impl fmt::Debug for ProjectWorkspace {
                 .debug_struct("DetachedFiles")
                 .field("file", &file)
                 .field("cargo_script", &cargo_script.is_some())
-                .field("sysroot", &sysroot.is_ok())
+                .field("n_sysroot_crates", &sysroot.num_packages())
                 .field("cargo_script", &cargo_script.is_some())
                 .field("n_rustc_cfg", &rustc_cfg.len())
                 .field("toolchain", &toolchain)
@@ -158,7 +156,7 @@ impl fmt::Debug for ProjectWorkspace {
 
 fn get_toolchain_version(
     current_dir: &AbsPath,
-    sysroot: Option<&Sysroot>,
+    sysroot: &Sysroot,
     tool: Tool,
     extra_env: &FxHashMap<String, String>,
     prefix: &str,
@@ -213,41 +211,37 @@ impl ProjectWorkspace {
             }
             ProjectManifest::CargoToml(cargo_toml) => {
                 let sysroot = match (&config.sysroot, &config.sysroot_src) {
-                    (Some(RustLibSource::Path(path)), None) => {
-                        Sysroot::with_sysroot_dir(path.clone(), config.sysroot_query_metadata).map_err(|e| {
-                          Some(format!("Failed to find sysroot at {path}:{e}"))
-                        })
-                    }
-                    (Some(RustLibSource::Discover), None) => {
-                        Sysroot::discover(cargo_toml.parent(), &config.extra_env, config.sysroot_query_metadata).map_err(|e| {
-                            Some(format!("Failed to find sysroot for Cargo.toml file {cargo_toml}. Is rust-src installed? {e}"))
-                        })
-                    }
-                    (Some(RustLibSource::Path(sysroot)), Some(sysroot_src)) => {
-                        Ok(Sysroot::load(sysroot.clone(), Some(Ok(sysroot_src.clone())), config.sysroot_query_metadata))
-                    }
+                    (Some(RustLibSource::Discover), None) => Sysroot::discover(
+                        cargo_toml.parent(),
+                        &config.extra_env,
+                        config.sysroot_query_metadata,
+                    ),
                     (Some(RustLibSource::Discover), Some(sysroot_src)) => {
                         Sysroot::discover_with_src_override(
                             cargo_toml.parent(),
                             &config.extra_env,
-                            sysroot_src.clone(), config.sysroot_query_metadata,
-                        ).map_err(|e| {
-                            Some(format!("Failed to find sysroot for Cargo.toml file {cargo_toml}. Is rust-src installed? {e}"))
-                        })
+                            sysroot_src.clone(),
+                            config.sysroot_query_metadata,
+                        )
                     }
-                    (None, _) => Err(None),
+                    (Some(RustLibSource::Path(path)), None) => Sysroot::discover_sysroot_src_dir(
+                        path.clone(),
+                        config.sysroot_query_metadata,
+                    ),
+                    (Some(RustLibSource::Path(sysroot)), Some(sysroot_src)) => Sysroot::load(
+                        Some(sysroot.clone()),
+                        Some(sysroot_src.clone()),
+                        config.sysroot_query_metadata,
+                    ),
+                    (None, _) => Sysroot::empty(),
                 };
-                let sysroot_ref = sysroot.as_ref().ok();
-
-                if let Ok(sysroot) = &sysroot {
-                    tracing::info!(workspace = %cargo_toml, src_root = ?sysroot.src_root(), root = %sysroot.root(), "Using sysroot");
-                }
+                tracing::info!(workspace = %cargo_toml, src_root = ?sysroot.src_root(), root = ?sysroot.root(), "Using sysroot");
 
                 let rustc_dir = match &config.rustc_source {
                     Some(RustLibSource::Path(path)) => ManifestPath::try_from(path.clone())
                         .map_err(|p| Some(format!("rustc source path is not absolute: {p}"))),
                     Some(RustLibSource::Discover) => {
-                        sysroot_ref.and_then(Sysroot::discover_rustc_src).ok_or_else(|| {
+                        sysroot.discover_rustc_src().ok_or_else(|| {
                             Some("Failed to discover rustc source for sysroot.".to_owned())
                         })
                     }
@@ -263,7 +257,7 @@ impl ProjectWorkspace {
                             features: crate::CargoFeatures::default(),
                             ..config.clone()
                         },
-                        sysroot_ref,
+                        &sysroot,
                         progress,
                     ) {
                         Ok(meta) => {
@@ -272,7 +266,7 @@ impl ProjectWorkspace {
                                 &workspace,
                                 cargo_toml.parent(),
                                 &config.extra_env,
-                                sysroot_ref
+                                &sysroot
                             );
                             Ok(Box::new((workspace, buildscripts)))
                         }
@@ -290,7 +284,7 @@ impl ProjectWorkspace {
 
                 let toolchain = get_toolchain_version(
                     cargo_toml.parent(),
-                    sysroot_ref,
+                    &sysroot,
                     Tool::Cargo,
                     &config.extra_env,
                     "cargo ",
@@ -298,12 +292,12 @@ impl ProjectWorkspace {
                 let rustc_cfg = rustc_cfg::get(
                     config.target.as_deref(),
                     &config.extra_env,
-                    RustcCfgConfig::Cargo(sysroot_ref, cargo_toml),
+                    RustcCfgConfig::Cargo(&sysroot, cargo_toml),
                 );
 
                 let cfg_overrides = config.cfg_overrides.clone();
                 let data_layout = target_data_layout::get(
-                    RustcDataLayoutConfig::Cargo(sysroot_ref, cargo_toml),
+                    RustcDataLayoutConfig::Cargo(&sysroot, cargo_toml),
                     config.target.as_deref(),
                     &config.extra_env,
                 );
@@ -315,7 +309,7 @@ impl ProjectWorkspace {
                     cargo_toml,
                     cargo_toml.parent(),
                     config,
-                    sysroot_ref,
+                    &sysroot,
                     progress,
                 )
                 .with_context(|| {
@@ -326,7 +320,7 @@ impl ProjectWorkspace {
                 let cargo = CargoWorkspace::new(meta, cargo_toml.clone());
 
                 let cargo_config_extra_env =
-                    cargo_config_env(cargo_toml, &config.extra_env, sysroot_ref);
+                    cargo_config_env(cargo_toml, &config.extra_env, &sysroot);
                 ProjectWorkspace {
                     kind: ProjectWorkspaceKind::Cargo {
                         cargo,
@@ -354,32 +348,13 @@ impl ProjectWorkspace {
         extra_env: &FxHashMap<String, String>,
         cfg_overrides: &CfgOverrides,
     ) -> ProjectWorkspace {
-        let sysroot = match (project_json.sysroot.clone(), project_json.sysroot_src.clone()) {
-            (Some(sysroot), Some(sysroot_src)) => {
-                Ok(Sysroot::load(sysroot, Some(Ok(sysroot_src)), false))
-            }
-            (Some(sysroot), None) => {
-                // assume sysroot is structured like rustup's and guess `sysroot_src`
-                let sysroot_src =
-                    sysroot.join("lib").join("rustlib").join("src").join("rust").join("library");
-                Ok(Sysroot::load(sysroot, Some(Ok(sysroot_src)), false))
-            }
-            (None, Some(sysroot_src)) => {
-                // assume sysroot is structured like rustup's and guess `sysroot`
-                let mut sysroot = sysroot_src.clone();
-                for _ in 0..5 {
-                    sysroot.pop();
-                }
-                Ok(Sysroot::load(sysroot, Some(Ok(sysroot_src)), false))
-            }
-            (None, None) => Err(None),
-        };
-        let sysroot_ref = sysroot.as_ref().ok();
-        let cfg_config = RustcCfgConfig::Rustc(sysroot_ref);
-        let data_layout_config = RustcDataLayoutConfig::Rustc(sysroot_ref);
+        let sysroot =
+            Sysroot::load(project_json.sysroot.clone(), project_json.sysroot_src.clone(), false);
+        let cfg_config = RustcCfgConfig::Rustc(&sysroot);
+        let data_layout_config = RustcDataLayoutConfig::Rustc(&sysroot);
         let toolchain = match get_toolchain_version(
             project_json.path(),
-            sysroot_ref,
+            &sysroot,
             Tool::Rustc,
             extra_env,
             "rustc ",
@@ -410,24 +385,16 @@ impl ProjectWorkspace {
         let dir = detached_file.parent();
         let sysroot = match &config.sysroot {
             Some(RustLibSource::Path(path)) => {
-                Sysroot::with_sysroot_dir(path.clone(), config.sysroot_query_metadata)
-                    .map_err(|e| Some(format!("Failed to find sysroot at {path}:{e}")))
+                Sysroot::discover_sysroot_src_dir(path.clone(), config.sysroot_query_metadata)
+            }
+            Some(RustLibSource::Discover) => {
+                Sysroot::discover(dir, &config.extra_env, config.sysroot_query_metadata)
             }
-            Some(RustLibSource::Discover) => Sysroot::discover(
-                dir,
-                &config.extra_env,
-                config.sysroot_query_metadata,
-            )
-            .map_err(|e| {
-                Some(format!("Failed to find sysroot for {dir}. Is rust-src installed? {e}"))
-            }),
-            None => Err(None),
+            None => Sysroot::empty(),
         };
 
-        let sysroot_ref = sysroot.as_ref().ok();
         let toolchain =
-            match get_toolchain_version(dir, sysroot_ref, Tool::Rustc, &config.extra_env, "rustc ")
-            {
+            match get_toolchain_version(dir, &sysroot, Tool::Rustc, &config.extra_env, "rustc ") {
                 Ok(it) => it,
                 Err(e) => {
                     tracing::error!("{e}");
@@ -435,25 +402,24 @@ impl ProjectWorkspace {
                 }
             };
 
-        let rustc_cfg = rustc_cfg::get(None, &config.extra_env, RustcCfgConfig::Rustc(sysroot_ref));
+        let rustc_cfg = rustc_cfg::get(None, &config.extra_env, RustcCfgConfig::Rustc(&sysroot));
         let data_layout = target_data_layout::get(
-            RustcDataLayoutConfig::Rustc(sysroot_ref),
+            RustcDataLayoutConfig::Rustc(&sysroot),
             None,
             &config.extra_env,
         );
 
         let cargo_script =
-            CargoWorkspace::fetch_metadata(detached_file, dir, config, sysroot_ref, &|_| ())
-                .ok()
-                .map(|ws| {
+            CargoWorkspace::fetch_metadata(detached_file, dir, config, &sysroot, &|_| ()).ok().map(
+                |ws| {
                     (
                         CargoWorkspace::new(ws, detached_file.clone()),
                         WorkspaceBuildScripts::default(),
                     )
-                });
+                },
+            );
 
-        let cargo_config_extra_env =
-            cargo_config_env(detached_file, &config.extra_env, sysroot_ref);
+        let cargo_config_extra_env = cargo_config_env(detached_file, &config.extra_env, &sysroot);
         Ok(ProjectWorkspace {
             kind: ProjectWorkspaceKind::DetachedFile {
                 file: detached_file.to_owned(),
@@ -489,7 +455,7 @@ impl ProjectWorkspace {
                     cargo,
                     progress,
                     self.toolchain.as_ref(),
-                    self.sysroot.as_ref().ok(),
+                    &self.sysroot,
                 )
                 .with_context(|| {
                     format!("Failed to run build scripts for {}", cargo.workspace_root())
@@ -562,17 +528,7 @@ impl ProjectWorkspace {
     }
 
     pub fn find_sysroot_proc_macro_srv(&self) -> anyhow::Result<AbsPathBuf> {
-        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()
-            )),
-            Err(Some(e)) => Err(anyhow::format_err!(
-                "cannot find proc-macro server, the workspace `{}` is missing a sysroot: {e}",
-                self.manifest_or_root()
-            )),
-        }
+        self.sysroot.discover_proc_macro_srv()
     }
 
     /// Returns the roots for the current `ProjectWorkspace`
@@ -580,39 +536,37 @@ impl ProjectWorkspace {
     /// the root is a member of the current workspace
     pub fn to_roots(&self) -> Vec<PackageRoot> {
         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()
-                        .filter_map(|pkg| {
-                            if ws[pkg].is_local {
-                                // the local ones are included in the main `PackageRoot`` below
-                                return None;
-                            }
-                            let pkg_root = ws[pkg].manifest.parent().to_path_buf();
-
-                            let include = vec![pkg_root.clone()];
-
-                            let exclude = vec![
-                                pkg_root.join(".git"),
-                                pkg_root.join("target"),
-                                pkg_root.join("tests"),
-                                pkg_root.join("examples"),
-                                pkg_root.join("benches"),
-                            ];
-                            Some(PackageRoot { is_local: false, include, exclude })
-                        })
-                        .collect(),
-                    SysrootMode::Stitched(_) => vec![],
-                };
+            let mut r = match self.sysroot.mode() {
+                SysrootMode::Workspace(ws) => ws
+                    .packages()
+                    .filter_map(|pkg| {
+                        if ws[pkg].is_local {
+                            // the local ones are included in the main `PackageRoot`` below
+                            return None;
+                        }
+                        let pkg_root = ws[pkg].manifest.parent().to_path_buf();
+
+                        let include = vec![pkg_root.clone()];
+
+                        let exclude = vec![
+                            pkg_root.join(".git"),
+                            pkg_root.join("target"),
+                            pkg_root.join("tests"),
+                            pkg_root.join("examples"),
+                            pkg_root.join("benches"),
+                        ];
+                        Some(PackageRoot { is_local: false, include, exclude })
+                    })
+                    .collect(),
+                SysrootMode::Stitched(_) | SysrootMode::Empty => vec![],
+            };
 
-                r.push(PackageRoot {
-                    is_local: false,
-                    include: sysroot.src_root().map(|it| it.to_path_buf()).into_iter().collect(),
-                    exclude: Vec::new(),
-                });
-                r
-            })
+            r.push(PackageRoot {
+                is_local: false,
+                include: self.sysroot.src_root().map(|it| it.to_path_buf()).into_iter().collect(),
+                exclude: Vec::new(),
+            });
+            r
         };
         match &self.kind {
             ProjectWorkspaceKind::Json(project) => project
@@ -731,19 +685,15 @@ impl ProjectWorkspace {
     }
 
     pub fn n_packages(&self) -> usize {
+        let sysroot_package_len = self.sysroot.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()
-            }
+            ProjectWorkspaceKind::Json(project) => sysroot_package_len + project.n_crates(),
             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 = self.sysroot.as_ref().map_or(0, |it| it.num_packages());
                 cargo.packages().len() + sysroot_package_len + rustc_package_len
             }
             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())
             }
@@ -764,7 +714,7 @@ impl ProjectWorkspace {
                     rustc_cfg.clone(),
                     load,
                     project,
-                    sysroot.as_ref().ok(),
+                    sysroot,
                     extra_env,
                     cfg_overrides,
                 ),
@@ -780,7 +730,7 @@ impl ProjectWorkspace {
                     load,
                     rustc.as_ref().map(|a| a.as_ref()).ok(),
                     cargo,
-                    sysroot.as_ref().ok(),
+                    sysroot,
                     rustc_cfg.clone(),
                     cfg_overrides,
                     build_scripts,
@@ -793,7 +743,7 @@ impl ProjectWorkspace {
                         &mut |path| load(path),
                         None,
                         cargo,
-                        sysroot.as_ref().ok(),
+                        sysroot,
                         rustc_cfg.clone(),
                         cfg_overrides,
                         build_scripts,
@@ -803,7 +753,7 @@ impl ProjectWorkspace {
                         rustc_cfg.clone(),
                         load,
                         file,
-                        sysroot.as_ref().ok(),
+                        sysroot,
                         cfg_overrides,
                     )
                 },
@@ -811,9 +761,7 @@ impl ProjectWorkspace {
             ),
         };
 
-        if matches!(sysroot.as_ref().map(|it| it.mode()), Ok(SysrootMode::Stitched(_)))
-            && crate_graph.patch_cfg_if()
-        {
+        if matches!(sysroot.mode(), SysrootMode::Stitched(_)) && crate_graph.patch_cfg_if() {
             tracing::debug!("Patched std to depend on cfg-if")
         } else {
             tracing::debug!("Did not patch std to depend on cfg-if")
@@ -892,15 +840,14 @@ fn project_json_to_crate_graph(
     rustc_cfg: Vec<CfgFlag>,
     load: FileLoader<'_>,
     project: &ProjectJson,
-    sysroot: Option<&Sysroot>,
+    sysroot: &Sysroot,
     extra_env: &FxHashMap<String, String>,
     override_cfg: &CfgOverrides,
 ) -> (CrateGraph, ProcMacroPaths) {
     let mut res = (CrateGraph::default(), ProcMacroPaths::default());
     let (crate_graph, proc_macros) = &mut res;
-    let sysroot_deps = sysroot
-        .as_ref()
-        .map(|sysroot| sysroot_to_crate_graph(crate_graph, sysroot, rustc_cfg.clone(), load));
+    let (public_deps, libproc_macro) =
+        sysroot_to_crate_graph(crate_graph, sysroot, rustc_cfg.clone(), load);
 
     let r_a_cfg_flag = CfgFlag::Atom("rust_analyzer".to_owned());
     let mut cfg_cache: FxHashMap<&str, Vec<CfgFlag>> = FxHashMap::default();
@@ -978,11 +925,9 @@ fn project_json_to_crate_graph(
 
     for (from_idx, krate) in project.crates() {
         if let Some(&from) = idx_to_crate_id.get(&from_idx) {
-            if let Some((public_deps, libproc_macro)) = &sysroot_deps {
-                public_deps.add_to_crate_graph(crate_graph, from);
-                if let Some(proc_macro) = libproc_macro {
-                    add_proc_macro_dep(crate_graph, from, *proc_macro, krate.is_proc_macro);
-                }
+            public_deps.add_to_crate_graph(crate_graph, from);
+            if let Some(proc_macro) = libproc_macro {
+                add_proc_macro_dep(crate_graph, from, proc_macro, krate.is_proc_macro);
             }
 
             for dep in &krate.deps {
@@ -999,7 +944,7 @@ fn cargo_to_crate_graph(
     load: FileLoader<'_>,
     rustc: Option<&(CargoWorkspace, WorkspaceBuildScripts)>,
     cargo: &CargoWorkspace,
-    sysroot: Option<&Sysroot>,
+    sysroot: &Sysroot,
     rustc_cfg: Vec<CfgFlag>,
     override_cfg: &CfgOverrides,
     build_scripts: &WorkspaceBuildScripts,
@@ -1008,10 +953,8 @@ fn cargo_to_crate_graph(
     let mut res = (CrateGraph::default(), ProcMacroPaths::default());
     let crate_graph = &mut res.0;
     let proc_macros = &mut res.1;
-    let (public_deps, libproc_macro) = match sysroot {
-        Some(sysroot) => sysroot_to_crate_graph(crate_graph, sysroot, rustc_cfg.clone(), load),
-        None => (SysrootPublicDeps::default(), None),
-    };
+    let (public_deps, libproc_macro) =
+        sysroot_to_crate_graph(crate_graph, sysroot, rustc_cfg.clone(), load);
 
     let cfg_options = CfgOptions::from_iter(rustc_cfg);
 
@@ -1188,15 +1131,13 @@ fn detached_file_to_crate_graph(
     rustc_cfg: Vec<CfgFlag>,
     load: FileLoader<'_>,
     detached_file: &ManifestPath,
-    sysroot: Option<&Sysroot>,
+    sysroot: &Sysroot,
     override_cfg: &CfgOverrides,
 ) -> (CrateGraph, ProcMacroPaths) {
     let _p = tracing::span!(tracing::Level::INFO, "detached_file_to_crate_graph").entered();
     let mut crate_graph = CrateGraph::default();
-    let (public_deps, _libproc_macro) = match sysroot {
-        Some(sysroot) => sysroot_to_crate_graph(&mut crate_graph, sysroot, rustc_cfg.clone(), load),
-        None => (SysrootPublicDeps::default(), None),
-    };
+    let (public_deps, _libproc_macro) =
+        sysroot_to_crate_graph(&mut crate_graph, sysroot, rustc_cfg.clone(), load);
 
     let mut cfg_options = CfgOptions::from_iter(rustc_cfg);
     cfg_options.insert_atom("test".into());
@@ -1431,7 +1372,7 @@ fn sysroot_to_crate_graph(
                 load,
                 None,
                 cargo,
-                None,
+                &Sysroot::empty(),
                 rustc_cfg,
                 &CfgOverrides {
                     global: CfgDiff::new(
@@ -1554,6 +1495,7 @@ fn sysroot_to_crate_graph(
                 stitched.proc_macro().and_then(|it| sysroot_crates.get(&it).copied());
             (public_deps, libproc_macro)
         }
+        SysrootMode::Empty => (SysrootPublicDeps { deps: vec![] }, None),
     }
 }
 
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/rustc_tests.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/rustc_tests.rs
index 85f964b1dd9..e9a4db7a2b0 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/rustc_tests.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/rustc_tests.rs
@@ -69,11 +69,9 @@ impl Tester {
         let cargo_config =
             CargoConfig { sysroot: Some(RustLibSource::Discover), ..Default::default() };
 
-        let sysroot =
-            Ok(Sysroot::discover(tmp_file.parent().unwrap(), &cargo_config.extra_env, false)
-                .unwrap());
+        let sysroot = Sysroot::discover(tmp_file.parent().unwrap(), &cargo_config.extra_env, false);
         let data_layout = target_data_layout::get(
-            RustcDataLayoutConfig::Rustc(sysroot.as_ref().ok()),
+            RustcDataLayoutConfig::Rustc(&sysroot),
             None,
             &cargo_config.extra_env,
         );
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs
index 5ee0456c15d..41996db32ea 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs
@@ -1783,18 +1783,18 @@ pub(crate) fn handle_open_docs(
     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()))
+            Some((cargo, &ws.sysroot))
         }
         ProjectWorkspaceKind::Json { .. } => None,
         ProjectWorkspaceKind::DetachedFile { .. } => None,
     });
 
     let (cargo, sysroot) = match ws_and_sysroot {
-        Some((ws, sysroot)) => (Some(ws), sysroot),
+        Some((ws, sysroot)) => (Some(ws), Some(sysroot)),
         _ => (None, None),
     };
 
-    let sysroot = sysroot.map(|p| p.root().as_str());
+    let sysroot = sysroot.and_then(|p| p.root()).map(|it| it.as_str());
     let target_dir = cargo.map(|cargo| cargo.target_directory()).map(|p| p.as_str());
 
     let Ok(remote_urls) = snap.analysis.external_docs(position, target_dir, sysroot) else {
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/ext.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/ext.rs
index 2cf9b53f7c8..03bd83aab5f 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/ext.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/ext.rs
@@ -500,7 +500,6 @@ pub struct ServerStatusParams {
     pub health: Health,
     pub quiescent: bool,
     pub message: Option<String>,
-    pub workspace_info: Option<String>,
 }
 
 #[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs
index fd14efa1da5..627be7e951a 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs
@@ -103,7 +103,6 @@ impl GlobalState {
             health: lsp_ext::Health::Ok,
             quiescent: self.is_quiescent(),
             message: None,
-            workspace_info: None,
         };
         let mut message = String::new();
 
@@ -164,53 +163,37 @@ impl GlobalState {
             let proc_macro_clients =
                 self.proc_macro_clients.iter().map(Some).chain(iter::repeat_with(|| None));
 
-            let mut workspace_info = "Loaded workspaces:\n".to_owned();
             for (ws, proc_macro_client) in self.workspaces.iter().zip(proc_macro_clients) {
-                format_to!(workspace_info, "- `{}`\n", ws.manifest_or_root());
-                format_to!(workspace_info, "    - sysroot:");
-
-                match ws.sysroot.as_ref() {
-                    Err(None) => format_to!(workspace_info, " None"),
-                    Err(Some(e)) => {
-                        status.health |= lsp_ext::Health::Warning;
-                        format_to!(workspace_info, " {e}");
-                    }
-                    Ok(s) => {
-                        format_to!(workspace_info, " `{}`", s.root().to_string());
-                        if let Some(err) = s
-                            .check_has_core()
-                            .err()
-                            .inspect(|_| status.health |= lsp_ext::Health::Warning)
-                        {
-                            format_to!(workspace_info, " ({err})");
-                        }
-                        if let Some(src_root) = s.src_root() {
-                            format_to!(
-                                workspace_info,
-                                "\n        - sysroot source: `{}`",
-                                src_root
-                            );
-                        }
-                        format_to!(workspace_info, "\n");
-                    }
+                if let Some(err) = ws.sysroot.error() {
+                    status.health |= lsp_ext::Health::Warning;
+                    format_to!(
+                        message,
+                        "Workspace `{}` has sysroot errors: ",
+                        ws.manifest_or_root()
+                    );
+                    message.push_str(err);
+                    message.push_str("\n\n");
                 }
-
-                if let ProjectWorkspaceKind::Cargo { rustc: Err(Some(e)), .. } = &ws.kind {
+                if let ProjectWorkspaceKind::Cargo { rustc: Err(Some(err)), .. } = &ws.kind {
                     status.health |= lsp_ext::Health::Warning;
-                    format_to!(workspace_info, "    - rustc workspace: {e}\n");
+                    format_to!(
+                        message,
+                        "Failed loading rustc_private crates for workspace `{}`: ",
+                        ws.manifest_or_root()
+                    );
+                    message.push_str(err);
+                    message.push_str("\n\n");
                 };
-                if let Some(proc_macro_client) = proc_macro_client {
-                    format_to!(workspace_info, "    - proc-macro server: ");
-                    match proc_macro_client {
-                        Ok(it) => format_to!(workspace_info, "`{}`\n", it.path()),
-                        Err(e) => {
-                            status.health |= lsp_ext::Health::Warning;
-                            format_to!(workspace_info, "{e}\n")
-                        }
-                    }
+                if let Some(Err(err)) = proc_macro_client {
+                    status.health |= lsp_ext::Health::Warning;
+                    format_to!(
+                        message,
+                        "Failed spawning proc-macro server for workspace `{}`: {err}",
+                        ws.manifest_or_root()
+                    );
+                    message.push_str("\n\n");
                 }
             }
-            status.workspace_info = Some(workspace_info);
         }
 
         if !message.is_empty() {
@@ -534,8 +517,8 @@ impl GlobalState {
                         .map(|(a, b)| (a.clone(), b.clone()))
                         .chain(
                             ws.sysroot
-                                .as_ref()
-                                .map(|it| ("RUSTUP_TOOLCHAIN".to_owned(), it.root().to_string())),
+                                .root()
+                                .map(|it| ("RUSTUP_TOOLCHAIN".to_owned(), it.to_string())),
                         )
                         .collect(),
 
@@ -719,7 +702,7 @@ impl GlobalState {
                                 }
                                 ProjectWorkspaceKind::DetachedFile { .. } => return None,
                             },
-                            ws.sysroot.as_ref().ok().map(|sysroot| sysroot.root().to_owned()),
+                            ws.sysroot.root().map(ToOwned::to_owned),
                         ))
                     })
                     .map(|(id, (root, manifest_path), sysroot_root)| {
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/tests/crate_graph.rs b/src/tools/rust-analyzer/crates/rust-analyzer/tests/crate_graph.rs
index 59b229cd064..66481d3d7f5 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/tests/crate_graph.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/tests/crate_graph.rs
@@ -21,7 +21,7 @@ fn load_cargo_with_fake_sysroot(file: &str) -> ProjectWorkspace {
             rustc: Err(None),
             cargo_config_extra_env: Default::default(),
         },
-        sysroot: Ok(get_fake_sysroot()),
+        sysroot: get_fake_sysroot(),
         rustc_cfg: Vec::new(),
         cfg_overrides: Default::default(),
         toolchain: None,
@@ -69,7 +69,7 @@ fn get_fake_sysroot() -> Sysroot {
     // fake sysroot, so we give them both the same path:
     let sysroot_dir = AbsPathBuf::assert_utf8(sysroot_path);
     let sysroot_src_dir = sysroot_dir.clone();
-    Sysroot::load(sysroot_dir, Some(Ok(sysroot_src_dir)), false)
+    Sysroot::load(Some(sysroot_dir), Some(sysroot_src_dir), false)
 }
 
 #[test]
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/main.rs b/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/main.rs
index 5a1397bbb0e..d886e405e14 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/main.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/main.rs
@@ -1059,11 +1059,11 @@ fn resolve_proc_macro() {
         return;
     }
 
-    let sysroot = project_model::Sysroot::discover_no_source(
+    let sysroot = project_model::Sysroot::discover(
         &AbsPathBuf::assert_utf8(std::env::current_dir().unwrap()),
         &Default::default(),
-    )
-    .unwrap();
+        false,
+    );
 
     let proc_macro_server_path = sysroot.discover_proc_macro_srv().unwrap();
 
diff --git a/src/tools/rust-analyzer/docs/dev/lsp-extensions.md b/src/tools/rust-analyzer/docs/dev/lsp-extensions.md
index 46c1ccb79bf..c8728c33ea0 100644
--- a/src/tools/rust-analyzer/docs/dev/lsp-extensions.md
+++ b/src/tools/rust-analyzer/docs/dev/lsp-extensions.md
@@ -1,5 +1,5 @@
 <!---
-lsp/ext.rs hash: a39009c351009d16
+lsp/ext.rs hash: 422dcc22c2e56166
 
 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/src/tools/rust-analyzer/editors/code/src/ctx.ts b/src/tools/rust-analyzer/editors/code/src/ctx.ts
index 5b683dccdc2..474e18b722f 100644
--- a/src/tools/rust-analyzer/editors/code/src/ctx.ts
+++ b/src/tools/rust-analyzer/editors/code/src/ctx.ts
@@ -472,12 +472,6 @@ export class Ctx implements RustAnalyzerExtensionApi {
         if (status.message) {
             statusBar.tooltip.appendText(status.message);
         }
-        if (status.workspaceInfo) {
-            if (statusBar.tooltip.value) {
-                statusBar.tooltip.appendMarkdown("\n\n---\n\n");
-            }
-            statusBar.tooltip.appendMarkdown(status.workspaceInfo);
-        }
         if (statusBar.tooltip.value) {
             statusBar.tooltip.appendMarkdown("\n\n---\n\n");
         }
diff --git a/src/tools/rust-analyzer/editors/code/src/lsp_ext.ts b/src/tools/rust-analyzer/editors/code/src/lsp_ext.ts
index 7d6b16b0197..9a7a4aae959 100644
--- a/src/tools/rust-analyzer/editors/code/src/lsp_ext.ts
+++ b/src/tools/rust-analyzer/editors/code/src/lsp_ext.ts
@@ -241,7 +241,6 @@ export type ServerStatusParams = {
     health: "ok" | "warning" | "error";
     quiescent: boolean;
     message?: string;
-    workspaceInfo?: string;
 };
 export type SsrParams = {
     query: string;