Repository: rust-lang/rust-analyzer
Pull request #17639 (2024-07-19 16:09:57)

Some more small salsa memory improvements
This does limit our lru limits to 2^16 but if you want to set them higher than that you might as well not set them at all. Also makes `LRU` opt-in per query now, allowing us to drop all the unnecessary LRU stuff for most queries


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index b98a1195d8..2a1e7c4d59 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4,9 +4,9 @@ version = 3
 
 [[package]]
 name = "addr2line"
-version = "0.22.0"
+version = "0.21.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678"
+checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
 dependencies = [
  "gimli",
 ]
@@ -28,9 +28,9 @@ dependencies = [
 
 [[package]]
 name = "anyhow"
-version = "1.0.86"
+version = "1.0.83"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
+checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3"
 
 [[package]]
 name = "arbitrary"
@@ -52,16 +52,16 @@ checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
 
 [[package]]
 name = "backtrace"
-version = "0.3.72"
+version = "0.3.71"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "17c6a35df3749d2e8bb1b7b21a976d82b15548788d2735b9d82f329268f71a11"
+checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d"
 dependencies = [
  "addr2line",
  "cc",
  "cfg-if",
  "libc",
  "miniz_oxide",
- "object 0.35.0",
+ "object 0.32.2",
  "rustc-demangle",
 ]
 
@@ -104,9 +104,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
 
 [[package]]
 name = "camino"
-version = "1.1.7"
+version = "1.1.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e0ec6b951b160caa93cc0c7b209e5a3bff7aae9062213451ac99493cd844c239"
+checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c"
 dependencies = [
  "serde",
 ]
@@ -136,9 +136,9 @@ dependencies = [
 
 [[package]]
 name = "cc"
-version = "1.0.98"
+version = "1.0.97"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f"
+checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4"
 
 [[package]]
 name = "cfg"
@@ -232,18 +232,18 @@ checksum = "0d48d8f76bd9331f19fe2aaf3821a9f9fb32c3963e1e3d6ce82a8c09cef7444a"
 
 [[package]]
 name = "crc32fast"
-version = "1.4.2"
+version = "1.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
+checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa"
 dependencies = [
  "cfg-if",
 ]
 
 [[package]]
 name = "crossbeam-channel"
-version = "0.5.13"
+version = "0.5.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2"
+checksum = "ab3db02a9c5b5121e1e42fbdb1aeb65f5e02624cc58c43f2884c6ccac0b82f95"
 dependencies = [
  "crossbeam-utils",
 ]
@@ -269,9 +269,9 @@ dependencies = [
 
 [[package]]
 name = "crossbeam-utils"
-version = "0.8.20"
+version = "0.8.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
+checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
 
 [[package]]
 name = "ctrlc"
@@ -366,9 +366,9 @@ checksum = "9bda8e21c04aca2ae33ffc2fd8c23134f3cac46db123ba97bd9d3f3b8a4a85e1"
 
 [[package]]
 name = "either"
-version = "1.12.0"
+version = "1.11.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b"
+checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2"
 
 [[package]]
 name = "ena"
@@ -431,7 +431,6 @@ dependencies = [
  "crossbeam-channel",
  "paths",
  "process-wrap",
- "project-model",
  "rustc-hash",
  "serde",
  "serde_json",
@@ -477,9 +476,9 @@ dependencies = [
 
 [[package]]
 name = "gimli"
-version = "0.29.0"
+version = "0.28.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd"
+checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
 
 [[package]]
 name = "hashbrown"
@@ -917,9 +916,9 @@ dependencies = [
 
 [[package]]
 name = "libmimalloc-sys"
-version = "0.1.38"
+version = "0.1.37"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0e7bb23d733dfcc8af652a78b7bf232f0e967710d044732185e561e47c0336b6"
+checksum = "81eb4061c0582dedea1cbc7aff2240300dd6982e0239d1c99e65c1dbf4a30ba7"
 dependencies = [
  "cc",
  "libc",
@@ -1087,18 +1086,18 @@ dependencies = [
 
 [[package]]
 name = "mimalloc"
-version = "0.1.42"
+version = "0.1.41"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e9186d86b79b52f4a77af65604b51225e8db1d6ee7e3f41aec1e40829c71a176"
+checksum = "9f41a2280ded0da56c8cf898babb86e8f10651a34adcfff190ae9a1159c6908d"
 dependencies = [
  "libmimalloc-sys",
 ]
 
 [[package]]
 name = "miniz_oxide"
-version = "0.7.3"
+version = "0.7.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae"
+checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7"
 dependencies = [
  "adler",
 ]
@@ -1163,9 +1162,9 @@ dependencies = [
 
 [[package]]
 name = "nu-ansi-term"
-version = "0.50.0"
+version = "0.49.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd2800e1520bdc966782168a627aa5d1ad92e33b984bf7c7615d31280c83ff14"
+checksum = "c073d3c1930d0751774acf49e66653acecb416c3a54c6ec095a9b11caddb5a68"
 dependencies = [
  "windows-sys 0.48.0",
 ]
@@ -1188,18 +1187,18 @@ dependencies = [
 
 [[package]]
 name = "object"
-version = "0.33.0"
+version = "0.32.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d8dd6c0cdf9429bce006e1362bfce61fa1bfd8c898a643ed8d2b471934701d3d"
+checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441"
 dependencies = [
  "memchr",
 ]
 
 [[package]]
 name = "object"
-version = "0.35.0"
+version = "0.33.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b8ec7ab813848ba4522158d5517a6093db1ded27575b070f4177b8d12b41db5e"
+checksum = "d8dd6c0cdf9429bce006e1362bfce61fa1bfd8c898a643ed8d2b471934701d3d"
 dependencies = [
  "memchr",
 ]
@@ -1224,9 +1223,9 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
 
 [[package]]
 name = "parking_lot"
-version = "0.12.3"
+version = "0.12.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
+checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb"
 dependencies = [
  "lock_api",
  "parking_lot_core",
@@ -1380,9 +1379,9 @@ dependencies = [
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.85"
+version = "1.0.82"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23"
+checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b"
 dependencies = [
  "unicode-ident",
 ]
@@ -1801,18 +1800,18 @@ dependencies = [
 
 [[package]]
 name = "serde"
-version = "1.0.203"
+version = "1.0.201"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094"
+checksum = "780f1cebed1629e4753a1a38a3c72d30b97ec044f0aef68cb26650a3c5cf363c"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.203"
+version = "1.0.201"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
+checksum = "c5e405930b9796f1c00bee880d03fc7e0bb4b9a11afc776885ffe84320da2865"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1844,9 +1843,9 @@ dependencies = [
 
 [[package]]
 name = "serde_spanned"
-version = "0.6.6"
+version = "0.6.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0"
+checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1"
 dependencies = [
  "serde",
 ]
@@ -1868,9 +1867,9 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
 
 [[package]]
 name = "smol_str"
-version = "0.2.2"
+version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead"
+checksum = "e6845563ada680337a52d43bb0b29f396f2d911616f6573012645b9e3d048a49"
 dependencies = [
  "serde",
 ]
@@ -1923,9 +1922,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "2.0.66"
+version = "2.0.63"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5"
+checksum = "bf5be731623ca1a1fb7d8be6f261a3be6d3e2337b8a1f97be944d020c8fcb704"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -2010,18 +2009,18 @@ checksum = "f18aa187839b2bdb1ad2fa35ead8c4c2976b64e4363c386d45ac0f7ee85c9233"
 
 [[package]]
 name = "thiserror"
-version = "1.0.61"
+version = "1.0.60"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709"
+checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18"
 dependencies = [
  "thiserror-impl",
 ]
 
 [[package]]
 name = "thiserror-impl"
-version = "1.0.61"
+version = "1.0.60"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
+checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -2105,9 +2104,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
 
 [[package]]
 name = "toml"
-version = "0.8.14"
+version = "0.8.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335"
+checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3"
 dependencies = [
  "serde",
  "serde_spanned",
@@ -2117,18 +2116,18 @@ dependencies = [
 
 [[package]]
 name = "toml_datetime"
-version = "0.6.6"
+version = "0.6.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf"
+checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
 dependencies = [
  "serde",
 ]
 
 [[package]]
 name = "toml_edit"
-version = "0.22.14"
+version = "0.22.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38"
+checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef"
 dependencies = [
  "indexmap",
  "serde",
@@ -2202,9 +2201,9 @@ dependencies = [
 
 [[package]]
 name = "tracing-tree"
-version = "0.3.1"
+version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b56c62d2c80033cb36fae448730a2f2ef99410fe3ecbffc916681a32f6807dbe"
+checksum = "65139ecd2c3f6484c3b99bc01c77afe21e95473630747c7aca525e78b0666675"
 dependencies = [
  "nu-ansi-term",
  "tracing-core",
@@ -2214,9 +2213,9 @@ dependencies = [
 
 [[package]]
 name = "triomphe"
-version = "0.1.12"
+version = "0.1.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b2cb4fbb9995eeb36ac86fadf24031ccd58f99d6b4b2d7b911db70bddb80d90"
+checksum = "859eb650cfee7434994602c3a68b25d77ad9e68c8a6cd491616ef86661382eb3"
 dependencies = [
  "serde",
  "stable_deref_trait",
@@ -2556,9 +2555,9 @@ checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
 
 [[package]]
 name = "winnow"
-version = "0.6.11"
+version = "0.6.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "56c52728401e1dc672a56e81e593e912aa54c78f40246869f78359a2bf24d29d"
+checksum = "c3c52e9c97a68071b23e836c9380edae937f17b9c4667bd021973efc689f618d"
 dependencies = [
  "memchr",
 ]
diff --git a/crates/base-db/src/lib.rs b/crates/base-db/src/lib.rs
index d29828fd50..f319f98537 100644
--- a/crates/base-db/src/lib.rs
+++ b/crates/base-db/src/lib.rs
@@ -42,9 +42,9 @@ pub trait Upcast<T: ?Sized> {
     fn upcast(&self) -> &T;
 }
 
-pub const DEFAULT_FILE_TEXT_LRU_CAP: usize = 16;
-pub const DEFAULT_PARSE_LRU_CAP: usize = 128;
-pub const DEFAULT_BORROWCK_LRU_CAP: usize = 2024;
+pub const DEFAULT_FILE_TEXT_LRU_CAP: u16 = 16;
+pub const DEFAULT_PARSE_LRU_CAP: u16 = 128;
+pub const DEFAULT_BORROWCK_LRU_CAP: u16 = 2024;
 
 pub trait FileLoader {
     /// Text of the file.
@@ -59,6 +59,7 @@ pub trait FileLoader {
 #[salsa::query_group(SourceDatabaseStorage)]
 pub trait SourceDatabase: FileLoader + std::fmt::Debug {
     /// Parses the file into the syntax tree.
+    #[salsa::lru]
     fn parse(&self, file_id: EditionedFileId) -> Parse<ast::SourceFile>;
 
     /// Returns the set of errors obtained from parsing the file including validation errors.
@@ -105,6 +106,7 @@ pub trait SourceDatabaseExt: SourceDatabase {
     #[salsa::input]
     fn compressed_file_text(&self, file_id: FileId) -> Arc<[u8]>;
 
+    #[salsa::lru]
     fn file_text(&self, file_id: FileId) -> Arc<str>;
 
     /// Path to a file, relative to the root of its source root.
diff --git a/crates/flycheck/Cargo.toml b/crates/flycheck/Cargo.toml
index bb3a94c8da..d81a5fe340 100644
--- a/crates/flycheck/Cargo.toml
+++ b/crates/flycheck/Cargo.toml
@@ -24,7 +24,6 @@ process-wrap.workspace = true
 paths.workspace = true
 stdx.workspace = true
 toolchain.workspace = true
-project-model.workspace = true
 
 [lints]
 workspace = true
\ No newline at end of file
diff --git a/crates/flycheck/src/lib.rs b/crates/flycheck/src/lib.rs
index ce148037fc..778def5d2b 100644
--- a/crates/flycheck/src/lib.rs
+++ b/crates/flycheck/src/lib.rs
@@ -20,7 +20,6 @@ pub use cargo_metadata::diagnostic::{
 use toolchain::Tool;
 
 mod command;
-pub mod project_json;
 mod test_runner;
 
 use command::{CommandHandle, ParseFromLine};
@@ -241,7 +240,7 @@ enum FlycheckStatus {
     Finished,
 }
 
-pub const SAVED_FILE_PLACEHOLDER: &str = "$saved_file";
+const SAVED_FILE_PLACEHOLDER: &str = "$saved_file";
 
 impl FlycheckActor {
     fn new(
diff --git a/crates/flycheck/src/project_json.rs b/crates/flycheck/src/project_json.rs
deleted file mode 100644
index b6e4495bc6..0000000000
--- a/crates/flycheck/src/project_json.rs
+++ /dev/null
@@ -1,152 +0,0 @@
-//! A `cargo-metadata`-equivalent for non-Cargo build systems.
-use std::{io, process::Command};
-
-use crossbeam_channel::Sender;
-use paths::{AbsPathBuf, Utf8Path, Utf8PathBuf};
-use project_model::ProjectJsonData;
-use serde::{Deserialize, Serialize};
-use serde_json::Value;
-
-use crate::command::{CommandHandle, ParseFromLine};
-
-pub const ARG_PLACEHOLDER: &str = "{arg}";
-
-/// A command wrapper for getting a `rust-project.json`.
-///
-/// This is analogous to `cargo-metadata`, but for non-Cargo build systems.
-pub struct Discover {
-    command: Vec<String>,
-    sender: Sender<DiscoverProjectMessage>,
-}
-
-#[derive(PartialEq, Clone, Debug, Serialize)]
-#[serde(rename_all = "camelCase")]
-pub enum DiscoverArgument {
-    Path(#[serde(serialize_with = "serialize_abs_pathbuf")] AbsPathBuf),
-    Buildfile(#[serde(serialize_with = "serialize_abs_pathbuf")] AbsPathBuf),
-}
-
-fn serialize_abs_pathbuf<S>(path: &AbsPathBuf, se: S) -> Result<S::Ok, S::Error>
-where
-    S: serde::Serializer,
-{
-    let path: &Utf8Path = path.as_ref();
-    se.serialize_str(path.as_str())
-}
-
-impl Discover {
-    /// Create a new [Discover].
-    pub fn new(sender: Sender<DiscoverProjectMessage>, command: Vec<String>) -> Self {
-        Self { sender, command }
-    }
-
-    /// Spawn the command inside [Discover] and report progress, if any.
-    pub fn spawn(&self, discover_arg: DiscoverArgument) -> io::Result<DiscoverHandle> {
-        let command = &self.command[0];
-        let args = &self.command[1..];
-
-        let args: Vec<String> = args
-            .iter()
-            .map(|arg| {
-                if arg == ARG_PLACEHOLDER {
-                    serde_json::to_string(&discover_arg).expect("Unable to serialize args")
-                } else {
-                    arg.to_owned()
-                }
-            })
-            .collect();
-
-        let mut cmd = Command::new(command);
-        cmd.args(args);
-
-        Ok(DiscoverHandle { _handle: CommandHandle::spawn(cmd, self.sender.clone())? })
-    }
-}
-
-/// A handle to a spawned [Discover].
-#[derive(Debug)]
-pub struct DiscoverHandle {
-    _handle: CommandHandle<DiscoverProjectMessage>,
-}
-
-/// An enum containing either progress messages, an error,
-/// or the materialized `rust-project`.
-#[derive(Debug, Clone, Deserialize, Serialize)]
-#[serde(tag = "kind")]
-#[serde(rename_all = "snake_case")]
-enum DiscoverProjectData {
-    Finished { buildfile: Utf8PathBuf, project: ProjectJsonData },
-    Error { error: String, source: Option<String> },
-    Progress { message: String },
-}
-
-#[derive(Debug, PartialEq, Clone)]
-pub enum DiscoverProjectMessage {
-    Finished { project: ProjectJsonData, buildfile: AbsPathBuf },
-    Error { error: String, source: Option<String> },
-    Progress { message: String },
-}
-
-impl DiscoverProjectMessage {
-    fn new(data: DiscoverProjectData) -> Self {
-        match data {
-            DiscoverProjectData::Finished { project, buildfile, .. } => {
-                let buildfile = buildfile.try_into().expect("Unable to make path absolute");
-                DiscoverProjectMessage::Finished { project, buildfile }
-            }
-            DiscoverProjectData::Error { error, source } => {
-                DiscoverProjectMessage::Error { error, source }
-            }
-            DiscoverProjectData::Progress { message } => {
-                DiscoverProjectMessage::Progress { message }
-            }
-        }
-    }
-}
-
-impl ParseFromLine for DiscoverProjectMessage {
-    fn from_line(line: &str, _error: &mut String) -> Option<Self> {
-        // can the line even be deserialized as JSON?
-        let Ok(data) = serde_json::from_str::<Value>(line) else {
-            let err = DiscoverProjectData::Error { error: line.to_owned(), source: None };
-            return Some(DiscoverProjectMessage::new(err));
-        };
-
-        let Ok(data) = serde_json::from_value::<DiscoverProjectData>(data) else {
-            return None;
-        };
-
-        let msg = DiscoverProjectMessage::new(data);
-        Some(msg)
-    }
-
-    fn from_eof() -> Option<Self> {
-        None
-    }
-}
-
-#[test]
-fn test_deserialization() {
-    let message = r#"
-    {"kind": "progress", "message":"querying build system","input":{"files":["src/main.rs"]}}
-    "#;
-    let message: DiscoverProjectData =
-        serde_json::from_str(message).expect("Unable to deserialize message");
-    assert!(matches!(message, DiscoverProjectData::Progress { .. }));
-
-    let message = r#"
-    {"kind": "error", "error":"failed to deserialize command output","source":"command"}
-    "#;
-
-    let message: DiscoverProjectData =
-        serde_json::from_str(message).expect("Unable to deserialize message");
-    assert!(matches!(message, DiscoverProjectData::Error { .. }));
-
-    let message = r#"
-    {"kind": "finished", "project": {"sysroot": "foo", "crates": [], "runnables": []}, "buildfile":"rust-analyzer/BUILD"}
-    "#;
-
-    let message: DiscoverProjectData =
-        serde_json::from_str(message).expect("Unable to deserialize message");
-    assert!(matches!(message, DiscoverProjectData::Finished { .. }));
-}
diff --git a/crates/hir-expand/src/db.rs b/crates/hir-expand/src/db.rs
index 8be2dcf0de..5a499d2efd 100644
--- a/crates/hir-expand/src/db.rs
+++ b/crates/hir-expand/src/db.rs
@@ -65,7 +65,7 @@ pub trait ExpandDatabase: SourceDatabase {
     #[salsa::transparent]
     fn parse_or_expand_with_err(&self, file_id: HirFileId) -> ExpandResult<Parse<SyntaxNode>>;
     /// Implementation for the macro case.
-    // This query is LRU cached
+    #[salsa::lru]
     fn parse_macro_expansion(
         &self,
         macro_file: MacroFileId,
diff --git a/crates/hir-ty/src/db.rs b/crates/hir-ty/src/db.rs
index 734aad4945..9a1f2158bf 100644
--- a/crates/hir-ty/src/db.rs
+++ b/crates/hir-ty/src/db.rs
@@ -61,6 +61,7 @@ pub trait HirDatabase: DefDatabase + Upcast<dyn DefDatabase> {
     ) -> Result<Arc<MirBody>, MirLowerError>;
 
     #[salsa::invoke(crate::mir::borrowck_query)]
+    #[salsa::lru]
     fn borrowck(&self, def: DefWithBodyId) -> Result<Arc<[BorrowckResult]>, MirLowerError>;
 
     #[salsa::invoke(crate::consteval::const_eval_query)]
diff --git a/crates/hir-ty/src/diagnostics/match_check/pat_analysis.rs b/crates/hir-ty/src/diagnostics/match_check/pat_analysis.rs
index 7bf8af4cab..ff62344012 100644
--- a/crates/hir-ty/src/diagnostics/match_check/pat_analysis.rs
+++ b/crates/hir-ty/src/diagnostics/match_check/pat_analysis.rs
@@ -5,10 +5,11 @@ use std::fmt;
 use hir_def::{DefWithBodyId, EnumId, EnumVariantId, HasModule, LocalFieldId, ModuleId, VariantId};
 use intern::sym;
 use once_cell::unsync::Lazy;
+use rustc_index::IndexVec;
 use rustc_pattern_analysis::{
     constructor::{Constructor, ConstructorSet, VariantVisibility},
     usefulness::{compute_match_usefulness, PlaceValidity, UsefulnessReport},
-    Captures, IndexVec, PatCx, PrivateUninhabitedField,
+    Captures, PatCx, PrivateUninhabitedField,
 };
 use smallvec::{smallvec, SmallVec};
 use stdx::never;
@@ -53,7 +54,7 @@ impl EnumVariantContiguousIndex {
     }
 }
 
-impl rustc_pattern_analysis::Idx for EnumVariantContiguousIndex {
+impl rustc_index::Idx for EnumVariantContiguousIndex {
     fn new(idx: usize) -> Self {
         EnumVariantContiguousIndex(idx)
     }
diff --git a/crates/ide-assists/src/handlers/merge_imports.rs b/crates/ide-assists/src/handlers/merge_imports.rs
index 4171230836..7f751c93e4 100644
--- a/crates/ide-assists/src/handlers/merge_imports.rs
+++ b/crates/ide-assists/src/handlers/merge_imports.rs
@@ -164,7 +164,6 @@ impl Merge for ast::UseTree {
     }
 }
 
-#[derive(Debug)]
 enum Edit {
     Remove(Either<ast::Use, ast::UseTree>),
     Replace(SyntaxNode, SyntaxNode),
@@ -734,72 +733,4 @@ use std::{
             r"use std::fmt::{Debug, Display};",
         );
     }
-
-    #[test]
-    fn test_merge_with_synonymous_imports_1() {
-        check_assist(
-            merge_imports,
-            r"
-mod top {
-    pub(crate) mod a {
-        pub(crate) struct A;
-    }
-    pub(crate) mod b {
-        pub(crate) struct B;
-        pub(crate) struct D;
-    }
-}
-
-use top::a::A;
-use $0top::b::{B, B as C};
-",
-            r"
-mod top {
-    pub(crate) mod a {
-        pub(crate) struct A;
-    }
-    pub(crate) mod b {
-        pub(crate) struct B;
-        pub(crate) struct D;
-    }
-}
-
-use top::{a::A, b::{B, B as C}};
-",
-        );
-    }
-
-    #[test]
-    fn test_merge_with_synonymous_imports_2() {
-        check_assist(
-            merge_imports,
-            r"
-mod top {
-    pub(crate) mod a {
-        pub(crate) struct A;
-    }
-    pub(crate) mod b {
-        pub(crate) struct B;
-        pub(crate) struct D;
-    }
-}
-
-use top::a::A;
-use $0top::b::{B as D, B as C};
-",
-            r"
-mod top {
-    pub(crate) mod a {
-        pub(crate) struct A;
-    }
-    pub(crate) mod b {
-        pub(crate) struct B;
-        pub(crate) struct D;
-    }
-}
-
-use top::{a::A, b::{B as D, B as C}};
-",
-        );
-    }
 }
diff --git a/crates/ide-db/src/imports/merge_imports.rs b/crates/ide-db/src/imports/merge_imports.rs
index 926fae0d31..9cacb6b1a6 100644
--- a/crates/ide-db/src/imports/merge_imports.rs
+++ b/crates/ide-db/src/imports/merge_imports.rs
@@ -93,25 +93,17 @@ fn try_merge_trees_mut(lhs: &ast::UseTree, rhs: &ast::UseTree, merge: MergeBehav
         let rhs_path = rhs.path()?;
 
         let (lhs_prefix, rhs_prefix) = common_prefix(&lhs_path, &rhs_path)?;
-        if lhs.is_simple_path()
+        if !(lhs.is_simple_path()
             && rhs.is_simple_path()
             && lhs_path == lhs_prefix
-            && rhs_path == rhs_prefix
+            && rhs_path == rhs_prefix)
         {
-            // we can't merge if the renames are different (`A as a` and `A as b`),
-            // and we can safely return here
-            let lhs_name = lhs.rename().and_then(|lhs_name| lhs_name.name());
-            let rhs_name = rhs.rename().and_then(|rhs_name| rhs_name.name());
-            if lhs_name != rhs_name {
-                return None;
-            }
-
+            lhs.split_prefix(&lhs_prefix);
+            rhs.split_prefix(&rhs_prefix);
+        } else {
             ted::replace(lhs.syntax(), rhs.syntax());
             // we can safely return here, in this case `recursive_merge` doesn't do anything
             return Some(());
-        } else {
-            lhs.split_prefix(&lhs_prefix);
-            rhs.split_prefix(&rhs_prefix);
         }
     }
     recursive_merge(lhs, rhs, merge)
diff --git a/crates/ide-db/src/lib.rs b/crates/ide-db/src/lib.rs
index e4507a983e..322c4e9e5c 100644
--- a/crates/ide-db/src/lib.rs
+++ b/crates/ide-db/src/lib.rs
@@ -145,7 +145,7 @@ impl Default for RootDatabase {
 }
 
 impl RootDatabase {
-    pub fn new(lru_capacity: Option<usize>) -> RootDatabase {
+    pub fn new(lru_capacity: Option<u16>) -> RootDatabase {
         let mut db = RootDatabase { storage: ManuallyDrop::new(salsa::Storage::default()) };
         db.set_crate_graph_with_durability(Default::default(), Durability::HIGH);
         db.set_proc_macros_with_durability(Default::default(), Durability::HIGH);
@@ -161,7 +161,7 @@ impl RootDatabase {
         self.set_expand_proc_attr_macros_with_durability(true, Durability::HIGH);
     }
 
-    pub fn update_base_query_lru_capacities(&mut self, lru_capacity: Option<usize>) {
+    pub fn update_base_query_lru_capacities(&mut self, lru_capacity: Option<u16>) {
         let lru_capacity = lru_capacity.unwrap_or(base_db::DEFAULT_PARSE_LRU_CAP);
         base_db::FileTextQuery.in_db_mut(self).set_lru_capacity(DEFAULT_FILE_TEXT_LRU_CAP);
         base_db::ParseQuery.in_db_mut(self).set_lru_capacity(lru_capacity);
@@ -170,7 +170,7 @@ impl RootDatabase {
         hir::db::BorrowckQuery.in_db_mut(self).set_lru_capacity(base_db::DEFAULT_BORROWCK_LRU_CAP);
     }
 
-    pub fn update_lru_capacities(&mut self, lru_capacities: &FxHashMap<Box<str>, usize>) {
+    pub fn update_lru_capacities(&mut self, lru_capacities: &FxHashMap<Box<str>, u16>) {
         use hir::db as hir_db;
 
         base_db::FileTextQuery.in_db_mut(self).set_lru_capacity(DEFAULT_FILE_TEXT_LRU_CAP);
@@ -192,135 +192,6 @@ impl RootDatabase {
                 .copied()
                 .unwrap_or(base_db::DEFAULT_BORROWCK_LRU_CAP),
         );
-
-        macro_rules! update_lru_capacity_per_query {
-            ($( $module:ident :: $query:ident )*) => {$(
-                if let Some(&cap) = lru_capacities.get(stringify!($query)) {
-                    $module::$query.in_db_mut(self).set_lru_capacity(cap);
-                }
-            )*}
-        }
-        update_lru_capacity_per_query![
-            // SourceDatabase
-            // base_db::ParseQuery
-            // base_db::CrateGraphQuery
-            // base_db::ProcMacrosQuery
-
-            // SourceDatabaseExt
-            base_db::FileTextQuery
-            // base_db::FileSourceRootQuery
-            // base_db::SourceRootQuery
-            base_db::SourceRootCratesQuery
-
-            // ExpandDatabase
-            hir_db::AstIdMapQuery
-            // hir_db::ParseMacroExpansionQuery
-            // hir_db::InternMacroCallQuery
-            hir_db::MacroArgQuery
-            hir_db::DeclMacroExpanderQuery
-            // hir_db::MacroExpandQuery
-            hir_db::ExpandProcMacroQuery
-            hir_db::ParseMacroExpansionErrorQuery
-
-            // DefDatabase
-            hir_db::FileItemTreeQuery
-            hir_db::BlockDefMapQuery
-            hir_db::StructDataWithDiagnosticsQuery
-            hir_db::UnionDataWithDiagnosticsQuery
-            hir_db::EnumDataQuery
-            hir_db::EnumVariantDataWithDiagnosticsQuery
-            hir_db::ImplDataWithDiagnosticsQuery
-            hir_db::TraitDataWithDiagnosticsQuery
-            hir_db::TraitAliasDataQuery
-            hir_db::TypeAliasDataQuery
-            hir_db::FunctionDataQuery
-            hir_db::ConstDataQuery
-            hir_db::StaticDataQuery
-            hir_db::Macro2DataQuery
-            hir_db::MacroRulesDataQuery
-            hir_db::ProcMacroDataQuery
-            hir_db::BodyWithSourceMapQuery
-            hir_db::BodyQuery
-            hir_db::ExprScopesQuery
-            hir_db::GenericParamsQuery
-            hir_db::FieldsAttrsQuery
-            hir_db::FieldsAttrsSourceMapQuery
-            hir_db::AttrsQuery
-            hir_db::CrateLangItemsQuery
-            hir_db::LangItemQuery
-            hir_db::ImportMapQuery
-            hir_db::FieldVisibilitiesQuery
-            hir_db::FunctionVisibilityQuery
-            hir_db::ConstVisibilityQuery
-            hir_db::CrateSupportsNoStdQuery
-
-            // HirDatabase
-            hir_db::MirBodyQuery
-            hir_db::BorrowckQuery
-            hir_db::TyQuery
-            hir_db::ValueTyQuery
-            hir_db::ImplSelfTyQuery
-            hir_db::ConstParamTyQuery
-            hir_db::ConstEvalQuery
-            hir_db::ConstEvalDiscriminantQuery
-            hir_db::ImplTraitQuery
-            hir_db::FieldTypesQuery
-            hir_db::LayoutOfAdtQuery
-            hir_db::TargetDataLayoutQuery
-            hir_db::CallableItemSignatureQuery
-            hir_db::ReturnTypeImplTraitsQuery
-            hir_db::GenericPredicatesForParamQuery
-            hir_db::GenericPredicatesQuery
-            hir_db::TraitEnvironmentQuery
-            hir_db::GenericDefaultsQuery
-            hir_db::InherentImplsInCrateQuery
-            hir_db::InherentImplsInBlockQuery
-            hir_db::IncoherentInherentImplCratesQuery
-            hir_db::TraitImplsInCrateQuery
-            hir_db::TraitImplsInBlockQuery
-            hir_db::TraitImplsInDepsQuery
-            // hir_db::InternCallableDefQuery
-            // hir_db::InternLifetimeParamIdQuery
-            // hir_db::InternImplTraitIdQuery
-            // hir_db::InternTypeOrConstParamIdQuery
-            // hir_db::InternClosureQuery
-            // hir_db::InternCoroutineQuery
-            hir_db::AssociatedTyDataQuery
-            hir_db::TraitDatumQuery
-            hir_db::AdtDatumQuery
-            hir_db::ImplDatumQuery
-            hir_db::FnDefDatumQuery
-            hir_db::FnDefVarianceQuery
-            hir_db::AdtVarianceQuery
-            hir_db::AssociatedTyValueQuery
-            hir_db::ProgramClausesForChalkEnvQuery
-
-            // SymbolsDatabase
-            symbol_index::ModuleSymbolsQuery
-            symbol_index::LibrarySymbolsQuery
-            // symbol_index::LocalRootsQuery
-            // symbol_index::LibraryRootsQuery
-
-            // LineIndexDatabase
-            crate::LineIndexQuery
-
-            // InternDatabase
-            // hir_db::InternFunctionQuery
-            // hir_db::InternStructQuery
-            // hir_db::InternUnionQuery
-            // hir_db::InternEnumQuery
-            // hir_db::InternConstQuery
-            // hir_db::InternStaticQuery
-            // hir_db::InternTraitQuery
-            // hir_db::InternTraitAliasQuery
-            // hir_db::InternTypeAliasQuery
-            // hir_db::InternImplQuery
-            // hir_db::InternExternBlockQuery
-            // hir_db::InternBlockQuery
-            // hir_db::InternMacro2Query
-            // hir_db::InternProcMacroQuery
-            // hir_db::InternMacroRulesQuery
-        ];
     }
 }
 
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index a154b644a3..34a6f154db 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -161,7 +161,7 @@ pub struct AnalysisHost {
 }
 
 impl AnalysisHost {
-    pub fn new(lru_capacity: Option<usize>) -> AnalysisHost {
+    pub fn new(lru_capacity: Option<u16>) -> AnalysisHost {
         AnalysisHost { db: RootDatabase::new(lru_capacity) }
     }
 
@@ -169,11 +169,11 @@ impl AnalysisHost {
         AnalysisHost { db }
     }
 
-    pub fn update_lru_capacity(&mut self, lru_capacity: Option<usize>) {
+    pub fn update_lru_capacity(&mut self, lru_capacity: Option<u16>) {
         self.db.update_base_query_lru_capacities(lru_capacity);
     }
 
-    pub fn update_lru_capacities(&mut self, lru_capacities: &FxHashMap<Box<str>, usize>) {
+    pub fn update_lru_capacities(&mut self, lru_capacities: &FxHashMap<Box<str>, u16>) {
         self.db.update_lru_capacities(lru_capacities);
     }
 
diff --git a/crates/load-cargo/src/lib.rs b/crates/load-cargo/src/lib.rs
index 1c81240e4f..e4260c44e6 100644
--- a/crates/load-cargo/src/lib.rs
+++ b/crates/load-cargo/src/lib.rs
@@ -17,6 +17,7 @@ use itertools::Itertools;
 use proc_macro_api::{MacroDylib, ProcMacroServer};
 use project_model::{CargoConfig, ManifestPath, PackageRoot, ProjectManifest, ProjectWorkspace};
 use span::Span;
+use tracing::instrument;
 use vfs::{file_set::FileSetConfig, loader::Handle, AbsPath, AbsPathBuf, VfsPath};
 
 pub struct LoadCargoConfig {
@@ -50,6 +51,7 @@ pub fn load_workspace_at(
     load_workspace(workspace, &cargo_config.extra_env, load_config)
 }
 
+#[instrument(skip_all)]
 pub fn load_workspace(
     ws: ProjectWorkspace,
     extra_env: &FxHashMap<String, String>,
@@ -377,7 +379,7 @@ fn load_crate_graph(
 ) -> RootDatabase {
     let ProjectWorkspace { toolchain, target_layout, .. } = ws;
 
-    let lru_cap = std::env::var("RA_LRU_CAP").ok().and_then(|it| it.parse::<usize>().ok());
+    let lru_cap = std::env::var("RA_LRU_CAP").ok().and_then(|it| it.parse::<u16>().ok());
     let mut db = RootDatabase::new(lru_cap);
     let mut analysis_change = ChangeWithProcMacros::new();
 
diff --git a/crates/project-model/src/project_json.rs b/crates/project-model/src/project_json.rs
index 421ef25a45..4b55f9d2b9 100644
--- a/crates/project-model/src/project_json.rs
+++ b/crates/project-model/src/project_json.rs
@@ -276,16 +276,6 @@ impl ProjectJson {
         self.manifest.as_ref()
     }
 
-    pub fn crate_by_buildfile(&self, path: &AbsPath) -> Option<Build> {
-        // this is fast enough for now, but it's unfortunate that this is O(crates).
-        let path: &std::path::Path = path.as_ref();
-        self.crates
-            .iter()
-            .filter(|krate| krate.is_workspace_member)
-            .filter_map(|krate| krate.build.clone())
-            .find(|build| build.build_file.as_std_path() == path)
-    }
-
     /// Returns the path to the project's manifest or root folder, if no manifest exists.
     pub fn manifest_or_root(&self) -> &AbsPath {
         self.manifest.as_ref().map_or(&self.project_root, |manifest| manifest.as_ref())
@@ -296,7 +286,7 @@ impl ProjectJson {
     }
 }
 
-#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
+#[derive(Serialize, Deserialize, Debug, Clone)]
 pub struct ProjectJsonData {
     sysroot: Option<Utf8PathBuf>,
     sysroot_src: Option<Utf8PathBuf>,
@@ -305,7 +295,7 @@ pub struct ProjectJsonData {
     runnables: Vec<RunnableData>,
 }
 
-#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
+#[derive(Serialize, Deserialize, Debug, Clone)]
 struct CrateData {
     display_name: Option<String>,
     root_module: Utf8PathBuf,
@@ -329,7 +319,7 @@ struct CrateData {
     build: Option<BuildData>,
 }
 
-#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
+#[derive(Serialize, Deserialize, Debug, Clone)]
 #[serde(rename = "edition")]
 enum EditionData {
     #[serde(rename = "2015")]
@@ -342,7 +332,7 @@ enum EditionData {
     Edition2024,
 }
 
-#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
+#[derive(Debug, Clone, Serialize, Deserialize)]
 pub struct BuildData {
     label: String,
     build_file: Utf8PathBuf,
@@ -429,7 +419,7 @@ pub(crate) struct Dep {
     pub(crate) name: CrateName,
 }
 
-#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
+#[derive(Serialize, Deserialize, Debug, Clone)]
 struct CrateSource {
     include_dirs: Vec<Utf8PathBuf>,
     exclude_dirs: Vec<Utf8PathBuf>,
diff --git a/crates/project-model/src/workspace.rs b/crates/project-model/src/workspace.rs
index 8c77da8f38..c3b0802756 100644
--- a/crates/project-model/src/workspace.rs
+++ b/crates/project-model/src/workspace.rs
@@ -31,7 +31,6 @@ use crate::{
     utf8_stdout, CargoConfig, CargoWorkspace, InvocationStrategy, ManifestPath, Package,
     ProjectJson, ProjectManifest, Sysroot, TargetData, TargetKind, WorkspaceBuildScripts,
 };
-use tracing::{debug, error, info};
 
 pub type FileLoader<'a> = &'a mut dyn for<'b> FnMut(&'b AbsPath) -> Option<FileId>;
 
@@ -251,7 +250,7 @@ impl ProjectWorkspace {
                 };
 
                 let rustc =  rustc_dir.and_then(|rustc_dir| {
-                    info!(workspace = %cargo_toml, rustc_dir = %rustc_dir, "Using rustc source");
+                    tracing::info!(workspace = %cargo_toml, rustc_dir = %rustc_dir, "Using rustc source");
                     match CargoWorkspace::fetch_metadata(
                         &rustc_dir,
                         cargo_toml.parent(),
@@ -768,9 +767,9 @@ impl ProjectWorkspace {
         };
 
         if matches!(sysroot.mode(), SysrootMode::Stitched(_)) && crate_graph.patch_cfg_if() {
-            debug!("Patched std to depend on cfg-if")
+            tracing::debug!("Patched std to depend on cfg-if")
         } else {
-            debug!("Did not patch std to depend on cfg-if")
+            tracing::debug!("Did not patch std to depend on cfg-if")
         }
         (crate_graph, proc_macros)
     }
@@ -918,11 +917,6 @@ fn project_json_to_crate_graph(
                         CrateOrigin::Local { repo: None, name: None }
                     },
                 );
-                debug!(
-                    ?crate_graph_crate_id,
-                    crate = display_name.as_ref().map(|name| name.canonical_name().as_str()),
-                    "added root to crate graph"
-                );
                 if *is_proc_macro {
                     if let Some(path) = proc_macro_dylib_path.clone() {
                         let node = Ok((
@@ -937,7 +931,6 @@ fn project_json_to_crate_graph(
         )
         .collect();
 
-    debug!(map = ?idx_to_crate_id);
     for (from_idx, krate) in project.crates() {
         if let Some(&from) = idx_to_crate_id.get(&from_idx) {
             public_deps.add_to_crate_graph(crate_graph, from);
@@ -1163,7 +1156,7 @@ fn detached_file_to_crate_graph(
     let file_id = match load(detached_file) {
         Some(file_id) => file_id,
         None => {
-            error!("Failed to load detached file {:?}", detached_file);
+            tracing::error!("Failed to load detached file {:?}", detached_file);
             return (crate_graph, FxHashMap::default());
         }
     };
@@ -1358,7 +1351,7 @@ fn add_target_crate_root(
     crate_id
 }
 
-#[derive(Default, Debug)]
+#[derive(Default)]
 struct SysrootPublicDeps {
     deps: Vec<(CrateName, CrateId, bool)>,
 }
diff --git a/crates/rust-analyzer/src/bin/main.rs b/crates/rust-analyzer/src/bin/main.rs
index 6a980a153c..1985093bc5 100644
--- a/crates/rust-analyzer/src/bin/main.rs
+++ b/crates/rust-analyzer/src/bin/main.rs
@@ -175,7 +175,6 @@ fn run_server() -> anyhow::Result<()> {
             return Err(e.into());
         }
     };
-
     tracing::info!("InitializeParams: {}", initialize_params);
     let lsp_types::InitializeParams {
         root_uri,
@@ -265,10 +264,7 @@ fn run_server() -> anyhow::Result<()> {
         return Err(e.into());
     }
 
-    if config.discover_workspace_config().is_none()
-        && !config.has_linked_projects()
-        && config.detached_files().is_empty()
-    {
+    if !config.has_linked_projects() && config.detached_files().is_empty() {
         config.rediscover_workspaces();
     }
 
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 9550a22c50..bb1db2ef18 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -108,7 +108,7 @@ config_data! {
         /// targets and features, with the following base command line:
         ///
         /// ```bash
-        /// cargo check --quiet --workspace --message-format=json --all-targets --keep-going
+        /// cargo check --quiet --workspace --message-format=json --all-targets
         /// ```
         /// .
         cargo_buildScripts_overrideCommand: Option<Vec<String>> = None,
@@ -283,9 +283,9 @@ config_data! {
         linkedProjects: Vec<ManifestOrProjectJson> = vec![],
 
         /// Number of syntax trees rust-analyzer keeps in memory. Defaults to 128.
-        lru_capacity: Option<usize>                 = None,
+        lru_capacity: Option<u16>                 = None,
         /// Sets the LRU capacity of the specified queries.
-        lru_query_capacities: FxHashMap<Box<str>, usize> = FxHashMap::default(),
+        lru_query_capacities: FxHashMap<Box<str>, u16> = FxHashMap::default(),
 
         /// These proc-macros will be ignored when trying to expand them.
         ///
@@ -328,102 +328,6 @@ config_data! {
         /// `textDocument/rangeFormatting` request. The rustfmt option is unstable and only
         /// available on a nightly build.
         rustfmt_rangeFormatting_enable: bool = false,
-
-        /// Enables automatic discovery of projects using [`DiscoverWorkspaceConfig::command`].
-        ///
-        /// [`DiscoverWorkspaceConfig`] also requires setting `progress_label` and `files_to_watch`.
-        /// `progress_label` is used for the title in progress indicators, whereas `files_to_watch`
-        /// is used to determine which build system-specific files should be watched in order to
-        /// reload rust-analyzer.
-        ///
-        /// Below is an example of a valid configuration:
-        /// ```json
-        /// "rust-analyzer.workspace.discoverConfig": {
-        ///     "command": [
-        ///         "rust-project",
-        ///         "develop-json",
-        ///         {arg}
-        ///     ],
-        ///     "progressLabel": "rust-analyzer",
-        ///     "filesToWatch": [
-        ///         "BUCK",
-        ///     ],
-        /// }
-        /// ```
-        ///
-        /// ## On `DiscoverWorkspaceConfig::command`
-        ///
-        /// **Warning**: This format is provisional and subject to change.
-        ///
-        /// [`DiscoverWorkspaceConfig::command`] *must* return a JSON object
-        /// corresponding to `DiscoverProjectData::Finished`:
-        ///
-        /// ```norun
-        /// #[derive(Debug, Clone, Deserialize, Serialize)]
-        /// #[serde(tag = "kind")]
-        /// #[serde(rename_all = "snake_case")]
-        /// enum DiscoverProjectData {
-        ///     Finished { buildfile: Utf8PathBuf, project: ProjectJsonData },
-        ///     Error { error: String, source: Option<String> },
-        ///     Progress { message: String },
-        /// }
-        /// ```
-        ///
-        /// As JSON, `DiscoverProjectData::Finished` is:
-        ///
-        /// ```json
-        /// {
-        ///     // the internally-tagged representation of the enum.
-        ///     "kind": "finished",
-        ///     // the file used by a non-Cargo build system to define
-        ///     // a package or target.
-        ///     "buildfile": "rust-analyzer/BUILD",
-        ///     // the contents of a rust-project.json, elided for brevity
-        ///     "project": {
-        ///         "sysroot": "foo",
-        ///         "crates": []
-        ///     }
-        /// }
-        /// ```
-        ///
-        /// It is encouraged, but not required, to use the other variants on
-        /// `DiscoverProjectData` to provide a more polished end-user experience.
-        ///
-        /// `DiscoverWorkspaceConfig::command` may *optionally* include an `{arg}`,
-        /// which will be substituted with the JSON-serialized form of the following
-        /// enum:
-        ///
-        /// ```norun
-        /// #[derive(PartialEq, Clone, Debug, Serialize)]
-        /// #[serde(rename_all = "camelCase")]
-        /// pub enum DiscoverArgument {
-        ///    Path(AbsPathBuf),
-        ///    Buildfile(AbsPathBuf),
-        /// }
-        /// ```
-        ///
-        /// The JSON representation of `DiscoverArgument::Path` is:
-        ///
-        /// ```json
-        /// {
-        ///     "path": "src/main.rs"
-        /// }
-        /// ```
-        ///
-        /// Similarly, the JSON representation of `DiscoverArgument::Buildfile` is:
-        ///
-        /// ```
-        /// {
-        ///     "buildfile": "BUILD"
-        /// }
-        /// ```
-        ///
-        /// `DiscoverArgument::Path` is used to find and generate a `rust-project.json`,
-        /// and therefore, a workspace, whereas `DiscoverArgument::buildfile` is used to
-        /// to update an existing workspace. As a reference for implementors,
-        /// buck2's `rust-project` will likely be useful:
-        /// https://github.com/facebook/buck2/tree/main/integrations/rust-project.
-        workspace_discoverConfig: Option<DiscoverWorkspaceConfig> = None,
     }
 }
 
@@ -1020,21 +924,6 @@ impl Config {
         );
         (config, e, should_update)
     }
-
-    pub fn add_linked_projects(&mut self, data: ProjectJsonData, buildfile: AbsPathBuf) {
-        let linked_projects = &mut self.client_config.0.global.linkedProjects;
-
-        let new_project = ManifestOrProjectJson::DiscoveredProjectJson { data, buildfile };
-        match linked_projects {
-            Some(projects) => {
-                match projects.iter_mut().find(|p| p.manifest() == new_project.manifest()) {
-                    Some(p) => *p = new_project,
-                    None => projects.push(new_project),
-                }
-            }
-            None => *linked_projects = Some(vec![new_project]),
-        }
-    }
 }
 
 #[derive(Default, Debug)]
@@ -1099,14 +988,6 @@ impl From<ProjectJson> for LinkedProject {
     }
 }
 
-#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct DiscoverWorkspaceConfig {
-    pub command: Vec<String>,
-    pub progress_label: String,
-    pub files_to_watch: Vec<String>,
-}
-
 pub struct CallInfoConfig {
     pub params_only: bool,
     pub docs: bool,
@@ -1647,27 +1528,15 @@ impl Config {
     pub fn has_linked_projects(&self) -> bool {
         !self.linkedProjects().is_empty()
     }
-
-    pub fn linked_manifests(&self) -> impl Iterator<Item = &AbsPath> + '_ {
+    pub fn linked_manifests(&self) -> impl Iterator<Item = &Utf8Path> + '_ {
         self.linkedProjects().iter().filter_map(|it| match it {
             ManifestOrProjectJson::Manifest(p) => Some(&**p),
-            // despite having a buildfile, using this variant as a manifest
-            // will fail.
-            ManifestOrProjectJson::DiscoveredProjectJson { .. } => None,
-            ManifestOrProjectJson::ProjectJson { .. } => None,
+            ManifestOrProjectJson::ProjectJson(_) => None,
         })
     }
-
     pub fn has_linked_project_jsons(&self) -> bool {
-        self.linkedProjects()
-            .iter()
-            .any(|it| matches!(it, ManifestOrProjectJson::ProjectJson { .. }))
+        self.linkedProjects().iter().any(|it| matches!(it, ManifestOrProjectJson::ProjectJson(_)))
     }
-
-    pub fn discover_workspace_config(&self) -> Option<&DiscoverWorkspaceConfig> {
-        self.workspace_discoverConfig().as_ref()
-    }
-
     pub fn linked_or_discovered_projects(&self) -> Vec<LinkedProject> {
         match self.linkedProjects().as_slice() {
             [] => {
@@ -1692,12 +1561,6 @@ impl Config {
                             .ok()
                             .map(Into::into)
                     }
-                    ManifestOrProjectJson::DiscoveredProjectJson { data, buildfile } => {
-                        let root_path =
-                            buildfile.parent().expect("Unable to get parent of buildfile");
-
-                        Some(ProjectJson::new(None, root_path, data.clone()).into())
-                    }
                     ManifestOrProjectJson::ProjectJson(it) => {
                         Some(ProjectJson::new(None, &self.root_path, it.clone()).into())
                     }
@@ -1743,11 +1606,11 @@ impl Config {
         extra_env
     }
 
-    pub fn lru_parse_query_capacity(&self) -> Option<usize> {
+    pub fn lru_parse_query_capacity(&self) -> Option<u16> {
         self.lru_capacity().to_owned()
     }
 
-    pub fn lru_query_capacities_config(&self) -> Option<&FxHashMap<Box<str>, usize>> {
+    pub fn lru_query_capacities_config(&self) -> Option<&FxHashMap<Box<str>, u16>> {
         self.lru_query_capacities().is_empty().not().then(|| self.lru_query_capacities())
     }
 
@@ -2238,49 +2101,11 @@ mod single_or_array {
     }
 }
 
-#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
+#[derive(Serialize, Deserialize, Debug, Clone)]
 #[serde(untagged)]
 enum ManifestOrProjectJson {
-    Manifest(
-        #[serde(serialize_with = "serialize_abs_pathbuf")]
-        #[serde(deserialize_with = "deserialize_abs_pathbuf")]
-        AbsPathBuf,
-    ),
+    Manifest(Utf8PathBuf),
     ProjectJson(ProjectJsonData),
-    DiscoveredProjectJson {
-        data: ProjectJsonData,
-        #[serde(serialize_with = "serialize_abs_pathbuf")]
-        #[serde(deserialize_with = "deserialize_abs_pathbuf")]
-        buildfile: AbsPathBuf,
-    },
-}
-
-fn deserialize_abs_pathbuf<'de, D>(de: D) -> std::result::Result<AbsPathBuf, D::Error>
-where
-    D: serde::de::Deserializer<'de>,
-{
-    let path = String::deserialize(de)?;
-
-    AbsPathBuf::try_from(path.as_ref())
-        .map_err(|err| serde::de::Error::custom(format!("invalid path name: {err:?}")))
-}
-
-fn serialize_abs_pathbuf<S>(path: &AbsPathBuf, se: S) -> Result<S::Ok, S::Error>
-where
-    S: serde::Serializer,
-{
-    let path: &Utf8Path = path.as_ref();
-    se.serialize_str(path.as_str())
-}
-
-impl ManifestOrProjectJson {
-    fn manifest(&self) -> Option<&AbsPath> {
-        match self {
-            ManifestOrProjectJson::Manifest(manifest) => Some(manifest),
-            ManifestOrProjectJson::DiscoveredProjectJson { buildfile, .. } => Some(buildfile),
-            ManifestOrProjectJson::ProjectJson(_) => None,
-        }
-    }
 }
 
 #[derive(Serialize, Deserialize, Debug, Clone)]
@@ -2926,7 +2751,7 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json
         "FxHashMap<String, String>" => set! {
             "type": "object",
         },
-        "FxHashMap<Box<str>, usize>" => set! {
+        "FxHashMap<Box<str>, u16>" => set! {
             "type": "object",
         },
         "FxHashMap<String, Option<String>>" => set! {
@@ -2936,6 +2761,11 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json
             "type": ["null", "integer"],
             "minimum": 0,
         },
+        "Option<u16>" => set! {
+            "type": ["null", "integer"],
+            "minimum": 0,
+            "maximum": 65535,
+        },
         "Option<String>" => set! {
             "type": ["null", "string"],
         },
@@ -3259,29 +3089,6 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json
                 },
             ],
         },
-        "Option<DiscoverWorkspaceConfig>" => set! {
-            "anyOf": [
-                {
-                    "type": "null"
-                },
-                {
-                    "type": "object",
-                    "properties": {
-                        "command": {
-                            "type": "array",
-                            "items": { "type": "string" }
-                        },
-                        "progressLabel": {
-                            "type": "string"
-                        },
-                        "filesToWatch": {
-                            "type": "array",
-                            "items": { "type": "string" }
-                        },
-                    }
-                }
-            ]
-        },
         _ => panic!("missing entry for {ty}: {default} (field {field})"),
     }
 
diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs
index 9fd9bee537..1976e4de30 100644
--- a/crates/rust-analyzer/src/global_state.rs
+++ b/crates/rust-analyzer/src/global_state.rs
@@ -6,7 +6,7 @@
 use std::{ops::Not as _, time::Instant};
 
 use crossbeam_channel::{unbounded, Receiver, Sender};
-use flycheck::{project_json, FlycheckHandle};
+use flycheck::FlycheckHandle;
 use hir::ChangeWithProcMacros;
 use ide::{Analysis, AnalysisHost, Cancellable, FileId, SourceRootId};
 use ide_db::base_db::{CrateId, ProcMacroPaths, SourceDatabaseExt};
@@ -20,9 +20,9 @@ use parking_lot::{
 use proc_macro_api::ProcMacroServer;
 use project_model::{ManifestPath, ProjectWorkspace, ProjectWorkspaceKind, WorkspaceBuildScripts};
 use rustc_hash::{FxHashMap, FxHashSet};
-use tracing::{span, trace, Level};
+use tracing::{span, Level};
 use triomphe::Arc;
-use vfs::{AbsPathBuf, AnchoredPathBuf, ChangeKind, Vfs};
+use vfs::{AnchoredPathBuf, ChangeKind, Vfs};
 
 use crate::{
     config::{Config, ConfigChange, ConfigErrors},
@@ -41,11 +41,6 @@ use crate::{
     task_pool::{TaskPool, TaskQueue},
 };
 
-pub(crate) struct FetchWorkspaceRequest {
-    pub(crate) path: Option<AbsPathBuf>,
-    pub(crate) force_crate_graph_reload: bool,
-}
-
 // Enforces drop order
 pub(crate) struct Handle<H, C> {
     pub(crate) handle: H,
@@ -100,11 +95,6 @@ pub(crate) struct GlobalState {
     pub(crate) test_run_receiver: Receiver<flycheck::CargoTestMessage>,
     pub(crate) test_run_remaining_jobs: usize,
 
-    // Project loading
-    pub(crate) discover_handle: Option<project_json::DiscoverHandle>,
-    pub(crate) discover_sender: Sender<project_json::DiscoverProjectMessage>,
-    pub(crate) discover_receiver: Receiver<project_json::DiscoverProjectMessage>,
-
     // VFS
     pub(crate) loader: Handle<Box<dyn vfs::loader::Handle>, Receiver<vfs::loader::Message>>,
     pub(crate) vfs: Arc<RwLock<(vfs::Vfs, IntMap<FileId, LineEndings>)>>,
@@ -144,12 +134,11 @@ pub(crate) struct GlobalState {
 
     // op queues
     pub(crate) fetch_workspaces_queue:
-        OpQueue<FetchWorkspaceRequest, Option<(Vec<anyhow::Result<ProjectWorkspace>>, bool)>>,
+        OpQueue<bool, Option<(Vec<anyhow::Result<ProjectWorkspace>>, bool)>>,
     pub(crate) fetch_build_data_queue:
         OpQueue<(), (Arc<Vec<ProjectWorkspace>>, Vec<anyhow::Result<WorkspaceBuildScripts>>)>,
     pub(crate) fetch_proc_macros_queue: OpQueue<Vec<ProcMacroPaths>, bool>,
     pub(crate) prime_caches_queue: OpQueue,
-    pub(crate) discover_workspace_queue: OpQueue,
 
     /// A deferred task queue.
     ///
@@ -157,7 +146,7 @@ pub(crate) struct GlobalState {
     /// handlers, as accessing the database may block latency-sensitive
     /// interactions and should be moved away from the main thread.
     ///
-    /// For certain features, such as [`GlobalState::handle_discover_msg`],
+    /// For certain features, such as [`lsp_ext::UnindexedProjectParams`],
     /// this queue should run only *after* [`GlobalState::process_changes`] has
     /// been called.
     pub(crate) deferred_task_queue: TaskQueue,
@@ -213,9 +202,6 @@ impl GlobalState {
         }
         let (flycheck_sender, flycheck_receiver) = unbounded();
         let (test_run_sender, test_run_receiver) = unbounded();
-
-        let (discover_sender, discover_receiver) = unbounded();
-
         let mut this = GlobalState {
             sender,
             req_queue: ReqQueue::default(),
@@ -247,10 +233,6 @@ impl GlobalState {
             test_run_receiver,
             test_run_remaining_jobs: 0,
 
-            discover_handle: None,
-            discover_sender,
-            discover_receiver,
-
             vfs: Arc::new(RwLock::new((vfs::Vfs::default(), IntMap::default()))),
             vfs_config_version: 0,
             vfs_progress_config_version: 0,
@@ -265,7 +247,6 @@ impl GlobalState {
             fetch_proc_macros_queue: OpQueue::default(),
 
             prime_caches_queue: OpQueue::default(),
-            discover_workspace_queue: OpQueue::default(),
 
             deferred_task_queue: task_queue,
         };
@@ -315,24 +296,11 @@ impl GlobalState {
                         modified_rust_files.push(file.file_id);
                     }
 
-                    let additional_files = self
-                        .config
-                        .discover_workspace_config()
-                        .map(|cfg| {
-                            cfg.files_to_watch.iter().map(String::as_str).collect::<Vec<&str>>()
-                        })
-                        .unwrap_or_default();
-
                     let path = path.to_path_buf();
                     if file.is_created_or_deleted() {
                         workspace_structure_change.get_or_insert((path, false)).1 |=
                             self.crate_graph_file_dependencies.contains(vfs_path);
-                    } else if reload::should_refresh_for_change(
-                        &path,
-                        file.kind(),
-                        &additional_files,
-                    ) {
-                        trace!(?path, kind = ?file.kind(), "refreshing for a change");
+                    } else if reload::should_refresh_for_change(&path, file.kind()) {
                         workspace_structure_change.get_or_insert((path.clone(), false));
                     }
                 }
@@ -451,7 +419,7 @@ impl GlobalState {
 
                 self.fetch_workspaces_queue.request_op(
                     format!("workspace vfs file change: {path}"),
-                    FetchWorkspaceRequest { path: Some(path.to_owned()), force_crate_graph_reload },
+                    force_crate_graph_reload,
                 );
             }
         }
diff --git a/crates/rust-analyzer/src/handlers/notification.rs b/crates/rust-analyzer/src/handlers/notification.rs
index 4d9cbae7d1..095d7c941c 100644
--- a/crates/rust-analyzer/src/handlers/notification.rs
+++ b/crates/rust-analyzer/src/handlers/notification.rs
@@ -14,7 +14,7 @@ use vfs::{AbsPathBuf, ChangeKind, VfsPath};
 
 use crate::{
     config::{Config, ConfigChange},
-    global_state::{FetchWorkspaceRequest, GlobalState},
+    global_state::GlobalState,
     lsp::{from_proto, utils::apply_document_changes},
     lsp_ext::{self, RunFlycheckParams},
     mem_docs::DocumentData,
@@ -73,9 +73,7 @@ pub(crate) fn handle_did_open_text_document(
 
         tracing::info!("New file content set {:?}", params.text_document.text);
         state.vfs.write().0.set_file_contents(path, Some(params.text_document.text.into_bytes()));
-        if state.config.discover_workspace_config().is_some()
-            || state.config.notifications().unindexed_project
-        {
+        if state.config.notifications().unindexed_project {
             tracing::debug!("queuing task");
             let _ = state
                 .deferred_task_queue
@@ -152,29 +150,15 @@ pub(crate) fn handle_did_save_text_document(
 
     if let Ok(vfs_path) = from_proto::vfs_path(&params.text_document.uri) {
         // Re-fetch workspaces if a workspace related file has changed
-        if let Some(path) = vfs_path.as_path() {
-            let additional_files = &state
-                .config
-                .discover_workspace_config()
-                .map(|cfg| cfg.files_to_watch.iter().map(String::as_str).collect::<Vec<&str>>())
-                .unwrap_or_default();
-
-            if reload::should_refresh_for_change(path, ChangeKind::Modify, additional_files) {
-                state.fetch_workspaces_queue.request_op(
-                    format!("workspace vfs file change saved {path}"),
-                    FetchWorkspaceRequest {
-                        path: Some(path.to_owned()),
-                        force_crate_graph_reload: false,
-                    },
-                );
-            } else if state.detached_files.contains(path) {
-                state.fetch_workspaces_queue.request_op(
-                    format!("detached file saved {path}"),
-                    FetchWorkspaceRequest {
-                        path: Some(path.to_owned()),
-                        force_crate_graph_reload: false,
-                    },
-                );
+        if let Some(abs_path) = vfs_path.as_path() {
+            if reload::should_refresh_for_change(abs_path, ChangeKind::Modify) {
+                state
+                    .fetch_workspaces_queue
+                    .request_op(format!("workspace vfs file change saved {abs_path}"), false);
+            } else if state.detached_files.contains(abs_path) {
+                state
+                    .fetch_workspaces_queue
+                    .request_op(format!("detached file saved {abs_path}"), false);
             }
         }
 
@@ -256,9 +240,7 @@ pub(crate) fn handle_did_change_workspace_folders(
 
     if !config.has_linked_projects() && config.detached_files().is_empty() {
         config.rediscover_workspaces();
-
-        let req = FetchWorkspaceRequest { path: None, force_crate_graph_reload: false };
-        state.fetch_workspaces_queue.request_op("client workspaces changed".to_owned(), req);
+        state.fetch_workspaces_queue.request_op("client workspaces changed".to_owned(), false)
     }
 
     Ok(())
diff --git a/crates/rust-analyzer/src/handlers/request.rs b/crates/rust-analyzer/src/handlers/request.rs
index 8ec159278d..8996143114 100644
--- a/crates/rust-analyzer/src/handlers/request.rs
+++ b/crates/rust-analyzer/src/handlers/request.rs
@@ -37,7 +37,7 @@ use vfs::{AbsPath, AbsPathBuf, FileId, VfsPath};
 use crate::{
     config::{Config, RustfmtConfig, WorkspaceSymbolConfig},
     diff::diff,
-    global_state::{FetchWorkspaceRequest, GlobalState, GlobalStateSnapshot},
+    global_state::{GlobalState, GlobalStateSnapshot},
     hack_recover_crate_name,
     line_index::LineEndings,
     lsp::{
@@ -57,8 +57,7 @@ pub(crate) fn handle_workspace_reload(state: &mut GlobalState, _: ()) -> anyhow:
     state.proc_macro_clients = Arc::from_iter([]);
     state.build_deps_changed = false;
 
-    let req = FetchWorkspaceRequest { path: None, force_crate_graph_reload: false };
-    state.fetch_workspaces_queue.request_op("reload workspace request".to_owned(), req);
+    state.fetch_workspaces_queue.request_op("reload workspace request".to_owned(), false);
     Ok(())
 }
 
diff --git a/crates/rust-analyzer/src/lsp/ext.rs b/crates/rust-analyzer/src/lsp/ext.rs
index efa5d47fba..9a852067f2 100644
--- a/crates/rust-analyzer/src/lsp/ext.rs
+++ b/crates/rust-analyzer/src/lsp/ext.rs
@@ -531,7 +531,7 @@ pub struct ServerStatusParams {
     pub message: Option<String>,
 }
 
-#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug)]
+#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
 #[serde(rename_all = "camelCase")]
 pub enum Health {
     Ok,
diff --git a/crates/rust-analyzer/src/lsp/utils.rs b/crates/rust-analyzer/src/lsp/utils.rs
index 46797ec658..800c0eee53 100644
--- a/crates/rust-analyzer/src/lsp/utils.rs
+++ b/crates/rust-analyzer/src/lsp/utils.rs
@@ -74,6 +74,7 @@ impl GlobalState {
         }
     }
 
+    /// Sends a notification to the client containing the error `message`.
     /// If `additional_info` is [`Some`], appends a note to the notification telling to check the logs.
     /// This will always log `message` + `additional_info` to the server's error log.
     pub(crate) fn show_and_log_error(&mut self, message: String, additional_info: Option<String>) {
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index db90d2d964..07414a6e49 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -9,19 +9,18 @@ use std::{
 
 use always_assert::always;
 use crossbeam_channel::{select, Receiver};
-use flycheck::project_json;
 use ide_db::base_db::{SourceDatabase, SourceDatabaseExt, VfsPath};
 use lsp_server::{Connection, Notification, Request};
 use lsp_types::{notification::Notification as _, TextDocumentIdentifier};
 use stdx::thread::ThreadIntent;
 use tracing::{span, Level};
-use vfs::{AbsPathBuf, FileId};
+use vfs::FileId;
 
 use crate::{
     config::Config,
     diagnostics::{fetch_native_diagnostics, DiagnosticsGeneration},
     dispatch::{NotificationDispatcher, RequestDispatcher},
-    global_state::{file_id_to_url, url_to_file_id, FetchWorkspaceRequest, GlobalState},
+    global_state::{file_id_to_url, url_to_file_id, GlobalState},
     hack_recover_crate_name,
     lsp::{
         from_proto, to_proto,
@@ -63,7 +62,6 @@ enum Event {
     Vfs(vfs::loader::Message),
     Flycheck(flycheck::Message),
     TestResult(flycheck::CargoTestMessage),
-    DiscoverProject(project_json::DiscoverProjectMessage),
 }
 
 impl fmt::Display for Event {
@@ -75,7 +73,6 @@ impl fmt::Display for Event {
             Event::Flycheck(_) => write!(f, "Event::Flycheck"),
             Event::QueuedTask(_) => write!(f, "Event::QueuedTask"),
             Event::TestResult(_) => write!(f, "Event::TestResult"),
-            Event::DiscoverProject(_) => write!(f, "Event::DiscoverProject"),
         }
     }
 }
@@ -89,7 +86,6 @@ pub(crate) enum QueuedTask {
 #[derive(Debug)]
 pub(crate) enum Task {
     Response(lsp_server::Response),
-    DiscoverLinkedProjects(DiscoverProjectParam),
     ClientNotification(lsp_ext::UnindexedProjectParams),
     Retry(lsp_server::Request),
     Diagnostics(DiagnosticsGeneration, Vec<(FileId, Vec<lsp_types::Diagnostic>)>),
@@ -101,12 +97,6 @@ pub(crate) enum Task {
     BuildDepsHaveChanged,
 }
 
-#[derive(Debug)]
-pub(crate) enum DiscoverProjectParam {
-    Buildfile(AbsPathBuf),
-    Path(AbsPathBuf),
-}
-
 #[derive(Debug)]
 pub(crate) enum PrimeCachesProgress {
     Begin,
@@ -144,7 +134,6 @@ impl fmt::Debug for Event {
             Event::Vfs(it) => fmt::Debug::fmt(it, f),
             Event::Flycheck(it) => fmt::Debug::fmt(it, f),
             Event::TestResult(it) => fmt::Debug::fmt(it, f),
-            Event::DiscoverProject(it) => fmt::Debug::fmt(it, f),
         }
     }
 }
@@ -154,24 +143,14 @@ impl GlobalState {
         self.update_status_or_notify();
 
         if self.config.did_save_text_document_dynamic_registration() {
-            let additional_patterns = self
-                .config
-                .discover_workspace_config()
-                .map(|cfg| cfg.files_to_watch.clone().into_iter())
-                .into_iter()
-                .flatten()
-                .map(|f| format!("**/{f}"));
-            self.register_did_save_capability(additional_patterns);
+            self.register_did_save_capability();
         }
 
-        if self.config.discover_workspace_config().is_none() {
-            let req = FetchWorkspaceRequest { path: None, force_crate_graph_reload: false };
-            self.fetch_workspaces_queue.request_op("startup".to_owned(), req);
-            if let Some((cause, FetchWorkspaceRequest { path, force_crate_graph_reload })) =
-                self.fetch_workspaces_queue.should_start_op()
-            {
-                self.fetch_workspaces(cause, path, force_crate_graph_reload);
-            }
+        self.fetch_workspaces_queue.request_op("startup".to_owned(), false);
+        if let Some((cause, force_crate_graph_reload)) =
+            self.fetch_workspaces_queue.should_start_op()
+        {
+            self.fetch_workspaces(cause, force_crate_graph_reload);
         }
 
         while let Some(event) = self.next_event(&inbox) {
@@ -188,36 +167,32 @@ impl GlobalState {
         anyhow::bail!("client exited without proper shutdown sequence")
     }
 
-    fn register_did_save_capability(&mut self, additional_patterns: impl Iterator<Item = String>) {
-        let additional_filters = additional_patterns.map(|pattern| lsp_types::DocumentFilter {
-            language: None,
-            scheme: None,
-            pattern: (Some(pattern)),
-        });
-
-        let mut selectors = vec![
-            lsp_types::DocumentFilter {
-                language: None,
-                scheme: None,
-                pattern: Some("**/*.rs".into()),
-            },
-            lsp_types::DocumentFilter {
-                language: None,
-                scheme: None,
-                pattern: Some("**/Cargo.toml".into()),
-            },
-            lsp_types::DocumentFilter {
-                language: None,
-                scheme: None,
-                pattern: Some("**/Cargo.lock".into()),
-            },
-        ];
-        selectors.extend(additional_filters);
-
+    fn register_did_save_capability(&mut self) {
         let save_registration_options = lsp_types::TextDocumentSaveRegistrationOptions {
             include_text: Some(false),
             text_document_registration_options: lsp_types::TextDocumentRegistrationOptions {
-                document_selector: Some(selectors),
+                document_selector: Some(vec![
+                    lsp_types::DocumentFilter {
+                        language: None,
+                        scheme: None,
+                        pattern: Some("**/*.rs".into()),
+                    },
+                    lsp_types::DocumentFilter {
+                        language: None,
+                        scheme: None,
+                        pattern: Some("**/Cargo.toml".into()),
+                    },
+                    lsp_types::DocumentFilter {
+                        language: None,
+                        scheme: None,
+                        pattern: Some("**/Cargo.lock".into()),
+                    },
+                    lsp_types::DocumentFilter {
+                        language: None,
+                        scheme: None,
+                        pattern: Some("**/rust-analyzer.toml".into()),
+                    },
+                ]),
             },
         };
 
@@ -255,8 +230,6 @@ impl GlobalState {
             recv(self.test_run_receiver) -> task =>
                 Some(Event::TestResult(task.unwrap())),
 
-            recv(self.discover_receiver) -> task =>
-                Some(Event::DiscoverProject(task.unwrap())),
         }
     }
 
@@ -367,13 +340,6 @@ impl GlobalState {
                     self.handle_cargo_test_msg(message);
                 }
             }
-            Event::DiscoverProject(message) => {
-                self.handle_discover_msg(message);
-                // Coalesce many project discovery events into a single loop turn.
-                while let Ok(message) = self.discover_receiver.try_recv() {
-                    self.handle_discover_msg(message);
-                }
-            }
         }
         let event_handling_duration = loop_start.elapsed();
 
@@ -461,13 +427,11 @@ impl GlobalState {
             }
         }
 
-        if self.config.cargo_autoreload_config()
-            || self.config.discover_workspace_config().is_some()
-        {
-            if let Some((cause, FetchWorkspaceRequest { path, force_crate_graph_reload })) =
+        if self.config.cargo_autoreload_config() {
+            if let Some((cause, force_crate_graph_reload)) =
                 self.fetch_workspaces_queue.should_start_op()
             {
-                self.fetch_workspaces(cause, path, force_crate_graph_reload);
+                self.fetch_workspaces(cause, force_crate_graph_reload);
             }
         }
 
@@ -683,35 +647,6 @@ impl GlobalState {
 
                 self.report_progress("Fetching", state, msg, None, None);
             }
-            Task::DiscoverLinkedProjects(arg) => {
-                if let Some(cfg) = self.config.discover_workspace_config() {
-                    if !self.discover_workspace_queue.op_in_progress() {
-                        // the clone is unfortunately necessary to avoid a borrowck error when
-                        // `self.report_progress` is called later
-                        let title = &cfg.progress_label.clone();
-                        let command = cfg.command.clone();
-                        let discover =
-                            project_json::Discover::new(self.discover_sender.clone(), command);
-
-                        self.report_progress(title, Progress::Begin, None, None, None);
-                        self.discover_workspace_queue
-                            .request_op("Discovering workspace".to_owned(), ());
-                        let _ = self.discover_workspace_queue.should_start_op();
-
-                        let arg = match arg {
-                            DiscoverProjectParam::Buildfile(it) => {
-                                project_json::DiscoverArgument::Buildfile(it)
-                            }
-                            DiscoverProjectParam::Path(it) => {
-                                project_json::DiscoverArgument::Path(it)
-                            }
-                        };
-
-                        let handle = discover.spawn(arg).unwrap();
-                        self.discover_handle = Some(handle);
-                    }
-                }
-            }
             Task::FetchBuildData(progress) => {
                 let (state, msg) = match progress {
                     BuildDataProgress::Begin => (Some(Progress::Begin), None),
@@ -820,17 +755,10 @@ impl GlobalState {
                     let id = from_proto::file_id(&snap, &uri).expect("unable to get FileId");
                     if let Ok(crates) = &snap.analysis.crates_for(id) {
                         if crates.is_empty() {
-                            if snap.config.discover_workspace_config().is_some() {
-                                let path =
-                                    from_proto::abs_path(&uri).expect("Unable to get AbsPath");
-                                let arg = DiscoverProjectParam::Path(path);
-                                sender.send(Task::DiscoverLinkedProjects(arg)).unwrap();
-                            } else if snap.config.notifications().unindexed_project {
-                                let params = lsp_ext::UnindexedProjectParams {
-                                    text_documents: vec![lsp_types::TextDocumentIdentifier { uri }],
-                                };
-                                sender.send(Task::ClientNotification(params)).unwrap();
+                            let params = lsp_ext::UnindexedProjectParams {
+                                text_documents: vec![lsp_types::TextDocumentIdentifier { uri }],
                             };
+                            sender.send(Task::ClientNotification(params)).unwrap();
                         } else {
                             tracing::debug!(?uri, "is indexed");
                         }
@@ -859,33 +787,6 @@ impl GlobalState {
         }
     }
 
-    fn handle_discover_msg(&mut self, message: project_json::DiscoverProjectMessage) {
-        let title = self
-            .config
-            .discover_workspace_config()
-            .map(|cfg| cfg.progress_label.clone())
-            .expect("No title could be found; this is a bug");
-        match message {
-            project_json::DiscoverProjectMessage::Finished { project, buildfile } => {
-                self.report_progress(&title, Progress::End, None, None, None);
-                self.discover_workspace_queue.op_completed(());
-
-                let mut config = Config::clone(&*self.config);
-                config.add_linked_projects(project, buildfile);
-                self.update_configuration(config);
-            }
-            project_json::DiscoverProjectMessage::Progress { message } => {
-                self.report_progress(&title, Progress::Report, Some(message), None, None)
-            }
-            project_json::DiscoverProjectMessage::Error { error, source } => {
-                let message = format!("Project discovery failed: {error}");
-                self.discover_workspace_queue.op_completed(());
-                self.show_and_log_error(message.clone(), source);
-                self.report_progress(&title, Progress::End, Some(message), None, None)
-            }
-        }
-    }
-
     fn handle_cargo_test_msg(&mut self, message: flycheck::CargoTestMessage) {
         match message {
             flycheck::CargoTestMessage::Test { name, state } => {
diff --git a/crates/rust-analyzer/src/op_queue.rs b/crates/rust-analyzer/src/op_queue.rs
index 99f9e9829c..932730fc23 100644
--- a/crates/rust-analyzer/src/op_queue.rs
+++ b/crates/rust-analyzer/src/op_queue.rs
@@ -3,7 +3,6 @@
 
 pub(crate) type Cause = String;
 
-#[derive(Debug)]
 pub(crate) struct OpQueue<Args = (), Output = ()> {
     op_requested: Option<(Cause, Args)>,
     op_in_progress: bool,
diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs
index fb16f28a14..1039daf850 100644
--- a/crates/rust-analyzer/src/reload.rs
+++ b/crates/rust-analyzer/src/reload.rs
@@ -33,12 +33,11 @@ use vfs::{AbsPath, AbsPathBuf, ChangeKind};
 
 use crate::{
     config::{Config, FilesWatcher, LinkedProject},
-    global_state::{FetchWorkspaceRequest, GlobalState},
+    global_state::GlobalState,
     lsp_ext,
-    main_loop::{DiscoverProjectParam, Task},
+    main_loop::Task,
     op_queue::Cause,
 };
-use tracing::{debug, info};
 
 #[derive(Debug)]
 pub(crate) enum ProjectWorkspaceProgress {
@@ -67,7 +66,6 @@ impl GlobalState {
             || self.fetch_workspaces_queue.op_in_progress()
             || self.fetch_build_data_queue.op_in_progress()
             || self.fetch_proc_macros_queue.op_in_progress()
-            || self.discover_workspace_queue.op_in_progress()
             || self.vfs_progress_config_version < self.vfs_config_version
             || self.vfs_progress_n_done < self.vfs_progress_n_total)
     }
@@ -83,11 +81,9 @@ impl GlobalState {
                 &self.config.lru_query_capacities_config().cloned().unwrap_or_default(),
             );
         }
-
         if self.config.linked_or_discovered_projects() != old_config.linked_or_discovered_projects()
         {
-            let req = FetchWorkspaceRequest { path: None, force_crate_graph_reload: false };
-            self.fetch_workspaces_queue.request_op("discovered projects changed".to_owned(), req)
+            self.fetch_workspaces_queue.request_op("discovered projects changed".to_owned(), false)
         } else if self.config.flycheck() != old_config.flycheck() {
             self.reload_flycheck();
         }
@@ -113,7 +109,6 @@ impl GlobalState {
         if !self.config.cargo_autoreload()
             && self.is_quiescent()
             && self.fetch_workspaces_queue.op_requested()
-            && self.config.discover_workspace_config().is_none()
         {
             status.health |= lsp_ext::Health::Warning;
             message.push_str("Auto-reloading is disabled and the workspace has changed, a manual workspace reload is required.\n\n");
@@ -129,6 +124,7 @@ impl GlobalState {
             status.health |= lsp_ext::Health::Warning;
             message.push_str("Failed to run build scripts of some packages.\n\n");
         }
+
         if let Some(err) = &self.config_errors {
             status.health |= lsp_ext::Health::Warning;
             format_to!(message, "{err}\n");
@@ -221,13 +217,8 @@ impl GlobalState {
         status
     }
 
-    pub(crate) fn fetch_workspaces(
-        &mut self,
-        cause: Cause,
-        path: Option<AbsPathBuf>,
-        force_crate_graph_reload: bool,
-    ) {
-        info!(%cause, "will fetch workspaces");
+    pub(crate) fn fetch_workspaces(&mut self, cause: Cause, force_crate_graph_reload: bool) {
+        tracing::info!(%cause, "will fetch workspaces");
 
         self.task_pool.handle.spawn_with_sender(ThreadIntent::Worker, {
             let linked_projects = self.config.linked_or_discovered_projects();
@@ -240,10 +231,6 @@ impl GlobalState {
                 .filter_map(Result::ok)
                 .collect();
             let cargo_config = self.config.cargo();
-            let discover_command = self.config.discover_workspace_config().cloned();
-            let is_quiescent = !(self.discover_workspace_queue.op_in_progress()
-                || self.vfs_progress_config_version < self.vfs_config_version
-                || self.vfs_progress_n_done < self.vfs_progress_n_total);
 
             move |sender| {
                 let progress = {
@@ -257,28 +244,10 @@ impl GlobalState {
 
                 sender.send(Task::FetchWorkspace(ProjectWorkspaceProgress::Begin)).unwrap();
 
-                if let (Some(_command), Some(path)) = (&discover_command, &path) {
-                    let build = linked_projects.iter().find_map(|project| match project {
-                        LinkedProject::InlineJsonProject(it) => it.crate_by_buildfile(path),
-                        _ => None,
-                    });
-
-                    if let Some(build) = build {
-                        if is_quiescent {
-                            let path = AbsPathBuf::try_from(build.build_file)
-                                .expect("Unable to convert to an AbsPath");
-                            let arg = DiscoverProjectParam::Buildfile(path);
-                            sender.send(Task::DiscoverLinkedProjects(arg)).unwrap();
-                        }
-                    }
-                }
-
                 let mut workspaces = linked_projects
                     .iter()
                     .map(|project| match project {
                         LinkedProject::ProjectManifest(manifest) => {
-                            debug!(path = %manifest, "loading project from manifest");
-
                             project_model::ProjectWorkspace::load(
                                 manifest.clone(),
                                 &cargo_config,
@@ -286,13 +255,12 @@ impl GlobalState {
                             )
                         }
                         LinkedProject::InlineJsonProject(it) => {
-                            let workspace = project_model::ProjectWorkspace::load_inline(
+                            Ok(project_model::ProjectWorkspace::load_inline(
                                 it.clone(),
                                 cargo_config.target.as_deref(),
                                 &cargo_config.extra_env,
                                 &cargo_config.cfg_overrides,
-                            );
-                            Ok(workspace)
+                            ))
                         }
                     })
                     .collect::<Vec<_>>();
@@ -318,7 +286,7 @@ impl GlobalState {
                     ));
                 }
 
-                info!(?workspaces, "did fetch workspaces");
+                tracing::info!("did fetch workspaces {:?}", workspaces);
                 sender
                     .send(Task::FetchWorkspace(ProjectWorkspaceProgress::End(
                         workspaces,
@@ -330,7 +298,7 @@ impl GlobalState {
     }
 
     pub(crate) fn fetch_build_data(&mut self, cause: Cause) {
-        info!(%cause, "will fetch build data");
+        tracing::info!(%cause, "will fetch build data");
         let workspaces = Arc::clone(&self.workspaces);
         let config = self.config.cargo();
         let root_path = self.config.root_path().clone();
@@ -356,7 +324,7 @@ impl GlobalState {
     }
 
     pub(crate) fn fetch_proc_macros(&mut self, cause: Cause, paths: Vec<ProcMacroPaths>) {
-        info!(%cause, "will load proc macros");
+        tracing::info!(%cause, "will load proc macros");
         let ignored_proc_macros = self.config.ignored_proc_macros().clone();
         let proc_macro_clients = self.proc_macro_clients.clone();
 
@@ -427,7 +395,6 @@ impl GlobalState {
             return;
         };
 
-        info!(%cause, ?force_reload_crate_graph);
         if self.fetch_workspace_error().is_err() && !self.workspaces.is_empty() {
             if *force_reload_crate_graph {
                 self.recreate_crate_graph(cause);
@@ -449,7 +416,7 @@ impl GlobalState {
         if same_workspaces {
             let (workspaces, build_scripts) = self.fetch_build_data_queue.last_op_result();
             if Arc::ptr_eq(workspaces, &self.workspaces) {
-                info!("set build scripts to workspaces");
+                tracing::debug!("set build scripts to workspaces");
 
                 let workspaces = workspaces
                     .iter()
@@ -461,10 +428,9 @@ impl GlobalState {
                     })
                     .collect::<Vec<_>>();
                 // Workspaces are the same, but we've updated build data.
-                info!("same workspace, but new build data");
                 self.workspaces = Arc::new(workspaces);
             } else {
-                info!("build scripts do not match the version of the active workspace");
+                tracing::info!("build scripts do not match the version of the active workspace");
                 if *force_reload_crate_graph {
                     self.recreate_crate_graph(cause);
                 }
@@ -474,7 +440,7 @@ impl GlobalState {
                 return;
             }
         } else {
-            info!("abandon build scripts for workspaces");
+            tracing::debug!("abandon build scripts for workspaces");
 
             // Here, we completely changed the workspace (Cargo.toml edit), so
             // we don't care about build-script results, they are stale.
@@ -569,7 +535,7 @@ impl GlobalState {
         if (self.proc_macro_clients.is_empty() || !same_workspaces)
             && self.config.expand_proc_macros()
         {
-            info!("Spawning proc-macro servers");
+            tracing::info!("Spawning proc-macro servers");
 
             self.proc_macro_clients = Arc::from_iter(self.workspaces.iter().map(|ws| {
                 let path = match self.config.proc_macro_srv() {
@@ -596,7 +562,7 @@ impl GlobalState {
 
                     _ => Default::default(),
                 };
-                info!("Using proc-macro server at {path}");
+                tracing::info!("Using proc-macro server at {path}");
 
                 ProcMacroServer::spawn(&path, &env).map_err(|err| {
                     tracing::error!(
@@ -622,14 +588,12 @@ impl GlobalState {
         self.source_root_config = project_folders.source_root_config;
         self.local_roots_parent_map = Arc::new(self.source_root_config.source_root_parent_map());
 
-        info!(?cause, "recreating the crate graph");
         self.recreate_crate_graph(cause);
 
-        info!("did switch workspaces");
+        tracing::info!("did switch workspaces");
     }
 
     fn recreate_crate_graph(&mut self, cause: String) {
-        info!(?cause, "Building Crate Graph");
         self.report_progress(
             "Building CrateGraph",
             crate::lsp::utils::Progress::Begin,
@@ -694,19 +658,12 @@ impl GlobalState {
         let Some((last_op_result, _)) = self.fetch_workspaces_queue.last_op_result() else {
             return Ok(());
         };
-
-        if !self.discover_workspace_queue.op_in_progress() {
-            if last_op_result.is_empty() {
-                stdx::format_to!(buf, "rust-analyzer failed to discover workspace");
-            } else {
-                for ws in last_op_result {
-                    if let Err(err) = ws {
-                        stdx::format_to!(
-                            buf,
-                            "rust-analyzer failed to load workspace: {:#}\n",
-                            err
-                        );
-                    }
+        if last_op_result.is_empty() {
+            stdx::format_to!(buf, "rust-analyzer failed to discover workspace");
+        } else {
+            for ws in last_op_result {
+                if let Err(err) = ws {
+                    stdx::format_to!(buf, "rust-analyzer failed to load workspace: {:#}\n", err);
                 }
             }
         }
@@ -861,11 +818,7 @@ pub fn ws_to_crate_graph(
     (crate_graph, proc_macro_paths, layouts, toolchains)
 }
 
-pub(crate) fn should_refresh_for_change(
-    path: &AbsPath,
-    change_kind: ChangeKind,
-    additional_paths: &[&str],
-) -> bool {
+pub(crate) fn should_refresh_for_change(path: &AbsPath, change_kind: ChangeKind) -> bool {
     const IMPLICIT_TARGET_FILES: &[&str] = &["build.rs", "src/main.rs", "src/lib.rs"];
     const IMPLICIT_TARGET_DIRS: &[&str] = &["src/bin", "examples", "tests", "benches"];
 
@@ -877,11 +830,6 @@ pub(crate) fn should_refresh_for_change(
     if let "Cargo.toml" | "Cargo.lock" = file_name {
         return true;
     }
-
-    if additional_paths.contains(&file_name) {
-        return true;
-    }
-
     if change_kind == ChangeKind::Modify {
         return false;
     }
diff --git a/crates/rust-analyzer/src/tracing/config.rs b/crates/rust-analyzer/src/tracing/config.rs
index f330754f19..fcdbf6c694 100644
--- a/crates/rust-analyzer/src/tracing/config.rs
+++ b/crates/rust-analyzer/src/tracing/config.rs
@@ -48,10 +48,7 @@ where
 
         let writer = self.writer;
 
-        let ra_fmt_layer = tracing_subscriber::fmt::layer()
-            .with_target(false)
-            .with_writer(writer)
-            .with_filter(filter);
+        let ra_fmt_layer = tracing_subscriber::fmt::layer().with_writer(writer).with_filter(filter);
 
         let mut chalk_layer = None;
         if let Some(chalk_filter) = self.chalk_filter {
diff --git a/crates/rust-analyzer/src/version.rs b/crates/rust-analyzer/src/version.rs
index 2ff967416c..1e829299e6 100644
--- a/crates/rust-analyzer/src/version.rs
+++ b/crates/rust-analyzer/src/version.rs
@@ -15,7 +15,7 @@ pub struct VersionInfo {
     pub version: &'static str,
     /// The release channel we were built for (stable/beta/nightly/dev).
     ///
-    /// `None` if not built via bootstrap.
+    /// `None` if not built via rustbuild.
     pub release_channel: Option<&'static str>,
     /// Information about the Git repository we may have been built from.
     ///
diff --git a/crates/salsa/salsa-macros/src/query_group.rs b/crates/salsa/salsa-macros/src/query_group.rs
index 4e70741239..eeaf008a15 100644
--- a/crates/salsa/salsa-macros/src/query_group.rs
+++ b/crates/salsa/salsa-macros/src/query_group.rs
@@ -53,7 +53,11 @@ pub(crate) fn query_group(args: TokenStream, input: TokenStream) -> TokenStream
                         num_storages += 1;
                     }
                     "dependencies" => {
-                        storage = QueryStorage::Dependencies;
+                        storage = QueryStorage::LruDependencies;
+                        num_storages += 1;
+                    }
+                    "lru" => {
+                        storage = QueryStorage::LruMemoized;
                         num_storages += 1;
                     }
                     "input" => {
@@ -235,7 +239,7 @@ pub(crate) fn query_group(args: TokenStream, input: TokenStream) -> TokenStream
 
         queries_with_storage.push(fn_name);
 
-        let tracing = if let QueryStorage::Memoized = query.storage {
+        let tracing = if let QueryStorage::Memoized | QueryStorage::LruMemoized = query.storage {
             let s = format!("{trait_name}::{fn_name}");
             Some(quote! {
                 let _p = tracing::debug_span!(#s, #(#key_names = tracing::field::debug(&#key_names)),*).entered();
@@ -376,8 +380,9 @@ pub(crate) fn query_group(args: TokenStream, input: TokenStream) -> TokenStream
 
         let storage = match &query.storage {
             QueryStorage::Memoized => quote!(salsa::plumbing::MemoizedStorage<Self>),
-            QueryStorage::Dependencies => {
-                quote!(salsa::plumbing::DependencyStorage<Self>)
+            QueryStorage::LruMemoized => quote!(salsa::plumbing::LruMemoizedStorage<Self>),
+            QueryStorage::LruDependencies => {
+                quote!(salsa::plumbing::LruDependencyStorage<Self>)
             }
             QueryStorage::Input if query.keys.is_empty() => {
                 quote!(salsa::plumbing::UnitInputStorage<Self>)
@@ -724,7 +729,8 @@ impl Query {
 #[derive(Debug, Clone, PartialEq, Eq)]
 enum QueryStorage {
     Memoized,
-    Dependencies,
+    LruDependencies,
+    LruMemoized,
     Input,
     Interned,
     InternedLookup { intern_query_type: Ident },
@@ -739,7 +745,9 @@ impl QueryStorage {
             | QueryStorage::Interned
             | QueryStorage::InternedLookup { .. }
             | QueryStorage::Transparent => false,
-            QueryStorage::Memoized | QueryStorage::Dependencies => true,
+            QueryStorage::Memoized | QueryStorage::LruMemoized | QueryStorage::LruDependencies => {
+                true
+            }
         }
     }
 }
diff --git a/crates/salsa/src/derived.rs b/crates/salsa/src/derived.rs
index fd31ab2041..8b2fdd6b19 100644
--- a/crates/salsa/src/derived.rs
+++ b/crates/salsa/src/derived.rs
@@ -1,9 +1,7 @@
 use crate::debug::TableEntry;
 use crate::durability::Durability;
 use crate::hash::FxIndexMap;
-use crate::lru::Lru;
 use crate::plumbing::DerivedQueryStorageOps;
-use crate::plumbing::LruQueryStorageOps;
 use crate::plumbing::QueryFunction;
 use crate::plumbing::QueryStorageMassOps;
 use crate::plumbing::QueryStorageOps;
@@ -13,7 +11,6 @@ use crate::{Database, DatabaseKeyIndex, QueryDb, Revision};
 use parking_lot::RwLock;
 use std::borrow::Borrow;
 use std::hash::Hash;
-use std::marker::PhantomData;
 use triomphe::Arc;
 
 mod slot;
@@ -22,79 +19,33 @@ use slot::Slot;
 /// Memoized queries store the result plus a list of the other queries
 /// that they invoked. This means we can avoid recomputing them when
 /// none of those inputs have changed.
-pub type MemoizedStorage<Q> = DerivedStorage<Q, AlwaysMemoizeValue>;
-
-/// "Dependency" queries just track their dependencies and not the
-/// actual value (which they produce on demand). This lessens the
-/// storage requirements.
-pub type DependencyStorage<Q> = DerivedStorage<Q, NeverMemoizeValue>;
+pub type MemoizedStorage<Q> = DerivedStorage<Q>;
 
 /// Handles storage where the value is 'derived' by executing a
 /// function (in contrast to "inputs").
-pub struct DerivedStorage<Q, MP>
+pub struct DerivedStorage<Q>
 where
     Q: QueryFunction,
-    MP: MemoizationPolicy<Q>,
 {
     group_index: u16,
-    lru_list: Lru<Slot<Q, MP>>,
-    slot_map: RwLock<FxIndexMap<Q::Key, Arc<Slot<Q, MP>>>>,
-    policy: PhantomData<MP>,
+    slot_map: RwLock<FxIndexMap<Q::Key, Arc<Slot<Q>>>>,
 }
 
-impl<Q, MP> std::panic::RefUnwindSafe for DerivedStorage<Q, MP>
+impl<Q> std::panic::RefUnwindSafe for DerivedStorage<Q>
 where
     Q: QueryFunction,
-    MP: MemoizationPolicy<Q>,
+
     Q::Key: std::panic::RefUnwindSafe,
     Q::Value: std::panic::RefUnwindSafe,
 {
 }
 
-pub trait MemoizationPolicy<Q>: Send + Sync
-where
-    Q: QueryFunction,
-{
-    fn should_memoize_value(key: &Q::Key) -> bool;
-
-    fn memoized_value_eq(old_value: &Q::Value, new_value: &Q::Value) -> bool;
-}
-
-pub enum AlwaysMemoizeValue {}
-impl<Q> MemoizationPolicy<Q> for AlwaysMemoizeValue
+impl<Q> DerivedStorage<Q>
 where
     Q: QueryFunction,
     Q::Value: Eq,
 {
-    fn should_memoize_value(_key: &Q::Key) -> bool {
-        true
-    }
-
-    fn memoized_value_eq(old_value: &Q::Value, new_value: &Q::Value) -> bool {
-        old_value == new_value
-    }
-}
-
-pub enum NeverMemoizeValue {}
-impl<Q> MemoizationPolicy<Q> for NeverMemoizeValue
-where
-    Q: QueryFunction,
-{
-    fn should_memoize_value(_key: &Q::Key) -> bool {
-        false
-    }
-
-    fn memoized_value_eq(_old_value: &Q::Value, _new_value: &Q::Value) -> bool {
-        panic!("cannot reach since we never memoize")
-    }
-}
-
-impl<Q, MP> DerivedStorage<Q, MP>
-where
-    Q: QueryFunction,
-    MP: MemoizationPolicy<Q>,
-{
-    fn slot(&self, key: &Q::Key) -> Arc<Slot<Q, MP>> {
+    fn slot(&self, key: &Q::Key) -> Arc<Slot<Q>> {
         if let Some(v) = self.slot_map.read().get(key) {
             return v.clone();
         }
@@ -111,20 +62,15 @@ where
     }
 }
 
-impl<Q, MP> QueryStorageOps<Q> for DerivedStorage<Q, MP>
+impl<Q> QueryStorageOps<Q> for DerivedStorage<Q>
 where
     Q: QueryFunction,
-    MP: MemoizationPolicy<Q>,
+    Q::Value: Eq,
 {
     const CYCLE_STRATEGY: crate::plumbing::CycleRecoveryStrategy = Q::CYCLE_STRATEGY;
 
     fn new(group_index: u16) -> Self {
-        DerivedStorage {
-            group_index,
-            slot_map: RwLock::new(FxIndexMap::default()),
-            lru_list: Default::default(),
-            policy: PhantomData,
-        }
+        DerivedStorage { group_index, slot_map: RwLock::new(FxIndexMap::default()) }
     }
 
     fn fmt_index(
@@ -161,10 +107,6 @@ where
         let slot = self.slot(key);
         let StampedValue { value, durability, changed_at } = slot.read(db, key);
 
-        if let Some(evicted) = self.lru_list.record_use(&slot) {
-            evicted.evict();
-        }
-
         db.salsa_runtime().report_query_read_and_unwind_if_cycle_resulted(
             slot.database_key_index(),
             durability,
@@ -175,7 +117,7 @@ where
     }
 
     fn durability(&self, db: &<Q as QueryDb<'_>>::DynDb, key: &Q::Key) -> Durability {
-        self.slot(key).durability(db)
+        self.slot_map.read().get(key).map_or(Durability::LOW, |slot| slot.durability(db))
     }
 
     fn entries<C>(&self, _db: &<Q as QueryDb<'_>>::DynDb) -> C
@@ -187,31 +129,19 @@ where
     }
 }
 
-impl<Q, MP> QueryStorageMassOps for DerivedStorage<Q, MP>
+impl<Q> QueryStorageMassOps for DerivedStorage<Q>
 where
     Q: QueryFunction,
-    MP: MemoizationPolicy<Q>,
 {
     fn purge(&self) {
-        self.lru_list.purge();
         *self.slot_map.write() = Default::default();
     }
 }
 
-impl<Q, MP> LruQueryStorageOps for DerivedStorage<Q, MP>
-where
-    Q: QueryFunction,
-    MP: MemoizationPolicy<Q>,
-{
-    fn set_lru_capacity(&self, new_capacity: usize) {
-        self.lru_list.set_lru_capacity(new_capacity);
-    }
-}
-
-impl<Q, MP> DerivedQueryStorageOps<Q> for DerivedStorage<Q, MP>
+impl<Q> DerivedQueryStorageOps<Q> for DerivedStorage<Q>
 where
     Q: QueryFunction,
-    MP: MemoizationPolicy<Q>,
+    Q::Value: Eq,
 {
     fn invalidate<S>(&self, runtime: &mut Runtime, key: &S)
     where
diff --git a/crates/salsa/src/derived/slot.rs b/crates/salsa/src/derived/slot.rs
index 5c21fd8d97..de7a397607 100644
--- a/crates/salsa/src/derived/slot.rs
+++ b/crates/salsa/src/derived/slot.rs
@@ -1,8 +1,5 @@
 use crate::debug::TableEntry;
-use crate::derived::MemoizationPolicy;
 use crate::durability::Durability;
-use crate::lru::LruIndex;
-use crate::lru::LruNode;
 use crate::plumbing::{DatabaseOps, QueryFunction};
 use crate::revision::Revision;
 use crate::runtime::local_state::ActiveQueryGuard;
@@ -14,21 +11,18 @@ use crate::runtime::WaitResult;
 use crate::Cycle;
 use crate::{Database, DatabaseKeyIndex, Event, EventKind, QueryDb};
 use parking_lot::{RawRwLock, RwLock};
-use std::marker::PhantomData;
 use std::ops::Deref;
 use std::sync::atomic::{AtomicBool, Ordering};
 use tracing::{debug, info};
 
-pub(super) struct Slot<Q, MP>
+pub(super) struct Slot<Q>
 where
     Q: QueryFunction,
-    MP: MemoizationPolicy<Q>,
 {
     key_index: u32,
+    // FIXME: Yeet this
     group_index: u16,
     state: RwLock<QueryState<Q>>,
-    lru_index: LruIndex,
-    policy: PhantomData<MP>,
 }
 
 /// Defines the "current state" of query's memoized results.
@@ -54,7 +48,7 @@ where
 
 struct Memo<V> {
     /// The result of the query, if we decide to memoize it.
-    value: Option<V>,
+    value: V,
 
     /// Last revision when this memo was verified; this begins
     /// as the current revision.
@@ -77,12 +71,6 @@ enum ProbeState<V, G> {
     /// verified in this revision.
     Stale(G),
 
-    /// There is an entry, and it has been verified
-    /// in this revision, but it has no cached
-    /// value. The `Revision` is the revision where the
-    /// value last changed (if we were to recompute it).
-    NoValue(G, Revision),
-
     /// There is an entry which has been verified,
     /// and it has the following value-- or, we blocked
     /// on another thread, and that resulted in a cycle.
@@ -103,18 +91,16 @@ enum MaybeChangedSinceProbeState<G> {
     Stale(G),
 }
 
-impl<Q, MP> Slot<Q, MP>
+impl<Q> Slot<Q>
 where
     Q: QueryFunction,
-    MP: MemoizationPolicy<Q>,
+    Q::Value: Eq,
 {
     pub(super) fn new(database_key_index: DatabaseKeyIndex) -> Self {
         Self {
             key_index: database_key_index.key_index,
             group_index: database_key_index.group_index,
             state: RwLock::new(QueryState::NotComputed),
-            lru_index: LruIndex::default(),
-            policy: PhantomData,
         }
     }
 
@@ -146,9 +132,7 @@ where
         loop {
             match self.probe(db, self.state.read(), runtime, revision_now) {
                 ProbeState::UpToDate(v) => return v,
-                ProbeState::Stale(..) | ProbeState::NoValue(..) | ProbeState::NotComputed(..) => {
-                    break
-                }
+                ProbeState::Stale(..) | ProbeState::NotComputed(..) => break,
                 ProbeState::Retry => continue,
             }
         }
@@ -176,9 +160,7 @@ where
         let mut old_memo = loop {
             match self.probe(db, self.state.upgradable_read(), runtime, revision_now) {
                 ProbeState::UpToDate(v) => return v,
-                ProbeState::Stale(state)
-                | ProbeState::NotComputed(state)
-                | ProbeState::NoValue(state, _) => {
+                ProbeState::Stale(state) | ProbeState::NotComputed(state) => {
                     type RwLockUpgradableReadGuard<'a, T> =
                         lock_api::RwLockUpgradableReadGuard<'a, RawRwLock, T>;
 
@@ -226,7 +208,7 @@ where
         runtime: &Runtime,
         revision_now: Revision,
         active_query: ActiveQueryGuard<'_>,
-        panic_guard: PanicGuard<'_, Q, MP>,
+        panic_guard: PanicGuard<'_, Q>,
         old_memo: Option<Memo<Q::Value>>,
         key: &Q::Key,
     ) -> StampedValue<Q::Value> {
@@ -285,22 +267,18 @@ where
         // "backdate" its `changed_at` revision to be the same as the
         // old value.
         if let Some(old_memo) = &old_memo {
-            if let Some(old_value) = &old_memo.value {
-                // Careful: if the value became less durable than it
-                // used to be, that is a "breaking change" that our
-                // consumers must be aware of. Becoming *more* durable
-                // is not. See the test `constant_to_non_constant`.
-                if revisions.durability >= old_memo.revisions.durability
-                    && MP::memoized_value_eq(old_value, &value)
-                {
-                    debug!(
-                        "read_upgrade({:?}): value is equal, back-dating to {:?}",
-                        self, old_memo.revisions.changed_at,
-                    );
-
-                    assert!(old_memo.revisions.changed_at <= revisions.changed_at);
-                    revisions.changed_at = old_memo.revisions.changed_at;
-                }
+            // Careful: if the value became less durable than it
+            // used to be, that is a "breaking change" that our
+            // consumers must be aware of. Becoming *more* durable
+            // is not. See the test `constant_to_non_constant`.
+            if revisions.durability >= old_memo.revisions.durability && old_memo.value == value {
+                debug!(
+                    "read_upgrade({:?}): value is equal, back-dating to {:?}",
+                    self, old_memo.revisions.changed_at,
+                );
+
+                assert!(old_memo.revisions.changed_at <= revisions.changed_at);
+                revisions.changed_at = old_memo.revisions.changed_at;
             }
         }
 
@@ -310,8 +288,7 @@ where
             changed_at: revisions.changed_at,
         };
 
-        let memo_value =
-            if self.should_memoize_value(key) { Some(new_value.value.clone()) } else { None };
+        let memo_value = new_value.value.clone();
 
         debug!("read_upgrade({:?}): result.revisions = {:#?}", self, revisions,);
 
@@ -371,20 +348,16 @@ where
                     return ProbeState::Stale(state);
                 }
 
-                if let Some(value) = &memo.value {
-                    let value = StampedValue {
-                        durability: memo.revisions.durability,
-                        changed_at: memo.revisions.changed_at,
-                        value: value.clone(),
-                    };
+                let value = &memo.value;
+                let value = StampedValue {
+                    durability: memo.revisions.durability,
+                    changed_at: memo.revisions.changed_at,
+                    value: value.clone(),
+                };
 
-                    info!("{:?}: returning memoized value changed at {:?}", self, value.changed_at);
+                info!("{:?}: returning memoized value changed at {:?}", self, value.changed_at);
 
-                    ProbeState::UpToDate(value)
-                } else {
-                    let changed_at = memo.revisions.changed_at;
-                    ProbeState::NoValue(state, changed_at)
-                }
+                ProbeState::UpToDate(value)
             }
         }
     }
@@ -407,21 +380,9 @@ where
         match &*self.state.read() {
             QueryState::NotComputed => None,
             QueryState::InProgress { .. } => Some(TableEntry::new(key.clone(), None)),
-            QueryState::Memoized(memo) => Some(TableEntry::new(key.clone(), memo.value.clone())),
-        }
-    }
-
-    pub(super) fn evict(&self) {
-        let mut state = self.state.write();
-        if let QueryState::Memoized(memo) = &mut *state {
-            // Evicting a value with an untracked input could
-            // lead to inconsistencies. Note that we can't check
-            // `has_untracked_input` when we add the value to the cache,
-            // because inputs can become untracked in the next revision.
-            if memo.has_untracked_input() {
-                return;
+            QueryState::Memoized(memo) => {
+                Some(TableEntry::new(key.clone(), Some(memo.value.clone())))
             }
-            memo.value = None;
         }
     }
 
@@ -489,8 +450,7 @@ where
 
             // If we know when value last changed, we can return right away.
             // Note that we don't need the actual value to be available.
-            ProbeState::NoValue(_, changed_at)
-            | ProbeState::UpToDate(StampedValue { value: _, durability: _, changed_at }) => {
+            ProbeState::UpToDate(StampedValue { value: _, durability: _, changed_at }) => {
                 MaybeChangedSinceProbeState::ChangedAt(changed_at)
             }
 
@@ -545,7 +505,7 @@ where
             let maybe_changed = old_memo.revisions.changed_at > revision;
             panic_guard.proceed(Some(old_memo));
             maybe_changed
-        } else if old_memo.value.is_some() {
+        } else {
             // We found that this memoized value may have changed
             // but we have an old value. We can re-run the code and
             // actually *check* if it has changed.
@@ -559,12 +519,6 @@ where
                 key,
             );
             changed_at > revision
-        } else {
-            // We found that inputs to this memoized value may have chanced
-            // but we don't have an old value to compare against or re-use.
-            // No choice but to drop the memo and say that its value may have changed.
-            panic_guard.proceed(None);
-            true
         }
     }
 
@@ -583,10 +537,6 @@ where
             mutex_guard,
         )
     }
-
-    fn should_memoize_value(&self, key: &Q::Key) -> bool {
-        MP::should_memoize_value(key)
-    }
 }
 
 impl<Q> QueryState<Q>
@@ -598,21 +548,21 @@ where
     }
 }
 
-struct PanicGuard<'me, Q, MP>
+struct PanicGuard<'me, Q>
 where
     Q: QueryFunction,
-    MP: MemoizationPolicy<Q>,
+    Q::Value: Eq,
 {
-    slot: &'me Slot<Q, MP>,
+    slot: &'me Slot<Q>,
     runtime: &'me Runtime,
 }
 
-impl<'me, Q, MP> PanicGuard<'me, Q, MP>
+impl<'me, Q> PanicGuard<'me, Q>
 where
     Q: QueryFunction,
-    MP: MemoizationPolicy<Q>,
+    Q::Value: Eq,
 {
-    fn new(slot: &'me Slot<Q, MP>, runtime: &'me Runtime) -> Self {
+    fn new(slot: &'me Slot<Q>, runtime: &'me Runtime) -> Self {
         Self { slot, runtime }
     }
 
@@ -666,10 +616,10 @@ Please report this bug to https://github.com/salsa-rs/salsa/issues."
     }
 }
 
-impl<'me, Q, MP> Drop for PanicGuard<'me, Q, MP>
+impl<'me, Q> Drop for PanicGuard<'me, Q>
 where
     Q: QueryFunction,
-    MP: MemoizationPolicy<Q>,
+    Q::Value: Eq,
 {
     fn drop(&mut self) {
         if std::thread::panicking() {
@@ -702,15 +652,11 @@ where
         revision_now: Revision,
         active_query: &ActiveQueryGuard<'_>,
     ) -> Option<StampedValue<V>> {
-        // If we don't have a memoized value, nothing to validate.
-        if self.value.is_none() {
-            return None;
-        }
         if self.verify_revisions(db, revision_now, active_query) {
-            self.value.clone().map(|value| StampedValue {
+            Some(StampedValue {
                 durability: self.revisions.durability,
                 changed_at: self.revisions.changed_at,
-                value,
+                value: self.value.clone(),
             })
         } else {
             None
@@ -788,58 +734,42 @@ where
         self.verified_at = revision_now;
         true
     }
-
-    fn has_untracked_input(&self) -> bool {
-        self.revisions.untracked
-    }
 }
 
-impl<Q, MP> std::fmt::Debug for Slot<Q, MP>
+impl<Q> std::fmt::Debug for Slot<Q>
 where
     Q: QueryFunction,
-    MP: MemoizationPolicy<Q>,
 {
     fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         write!(fmt, "{:?}", Q::default())
     }
 }
 
-impl<Q, MP> LruNode for Slot<Q, MP>
-where
-    Q: QueryFunction,
-    MP: MemoizationPolicy<Q>,
-{
-    fn lru_index(&self) -> &LruIndex {
-        &self.lru_index
-    }
-}
-
-/// Check that `Slot<Q, MP>: Send + Sync` as long as
+/// Check that `Slot<Q, >: Send + Sync` as long as
 /// `DB::DatabaseData: Send + Sync`, which in turn implies that
 /// `Q::Key: Send + Sync`, `Q::Value: Send + Sync`.
 #[allow(dead_code)]
-fn check_send_sync<Q, MP>()
+fn check_send_sync<Q>()
 where
     Q: QueryFunction,
-    MP: MemoizationPolicy<Q>,
+
     Q::Key: Send + Sync,
     Q::Value: Send + Sync,
 {
     fn is_send_sync<T: Send + Sync>() {}
-    is_send_sync::<Slot<Q, MP>>();
+    is_send_sync::<Slot<Q>>();
 }
 
-/// Check that `Slot<Q, MP>: 'static` as long as
+/// Check that `Slot<Q, >: 'static` as long as
 /// `DB::DatabaseData: 'static`, which in turn implies that
 /// `Q::Key: 'static`, `Q::Value: 'static`.
 #[allow(dead_code)]
-fn check_static<Q, MP>()
+fn check_static<Q>()
 where
     Q: QueryFunction + 'static,
-    MP: MemoizationPolicy<Q> + 'static,
     Q::Key: 'static,
     Q::Value: 'static,
 {
     fn is_static<T: 'static>() {}
-    is_static::<Slot<Q, MP>>();
+    is_static::<Slot<Q>>();
 }
diff --git a/crates/salsa/src/derived_lru.rs b/crates/salsa/src/derived_lru.rs
new file mode 100644
index 0000000000..bdb448e241
--- /dev/null
+++ b/crates/salsa/src/derived_lru.rs
@@ -0,0 +1,233 @@
+use crate::debug::TableEntry;
+use crate::durability::Durability;
+use crate::hash::FxIndexMap;
+use crate::lru::Lru;
+use crate::plumbing::DerivedQueryStorageOps;
+use crate::plumbing::LruQueryStorageOps;
+use crate::plumbing::QueryFunction;
+use crate::plumbing::QueryStorageMassOps;
+use crate::plumbing::QueryStorageOps;
+use crate::runtime::StampedValue;
+use crate::Runtime;
+use crate::{Database, DatabaseKeyIndex, QueryDb, Revision};
+use parking_lot::RwLock;
+use std::borrow::Borrow;
+use std::hash::Hash;
+use std::marker::PhantomData;
+use triomphe::Arc;
+
+mod slot;
+use slot::Slot;
+
+/// Memoized queries store the result plus a list of the other queries
+/// that they invoked. This means we can avoid recomputing them when
+/// none of those inputs have changed.
+pub type MemoizedStorage<Q> = DerivedStorage<Q, AlwaysMemoizeValue>;
+
+/// "Dependency" queries just track their dependencies and not the
+/// actual value (which they produce on demand). This lessens the
+/// storage requirements.
+pub type DependencyStorage<Q> = DerivedStorage<Q, NeverMemoizeValue>;
+
+/// Handles storage where the value is 'derived' by executing a
+/// function (in contrast to "inputs").
+pub struct DerivedStorage<Q, MP>
+where
+    Q: QueryFunction,
+    MP: MemoizationPolicy<Q>,
+{
+    group_index: u16,
+    lru_list: Lru<Slot<Q, MP>>,
+    slot_map: RwLock<FxIndexMap<Q::Key, Arc<Slot<Q, MP>>>>,
+    policy: PhantomData<MP>,
+}
+
+impl<Q, MP> std::panic::RefUnwindSafe for DerivedStorage<Q, MP>
+where
+    Q: QueryFunction,
+    MP: MemoizationPolicy<Q>,
+    Q::Key: std::panic::RefUnwindSafe,
+    Q::Value: std::panic::RefUnwindSafe,
+{
+}
+
+pub trait MemoizationPolicy<Q>: Send + Sync
+where
+    Q: QueryFunction,
+{
+    fn should_memoize_value(key: &Q::Key) -> bool;
+
+    fn memoized_value_eq(old_value: &Q::Value, new_value: &Q::Value) -> bool;
+}
+
+pub enum AlwaysMemoizeValue {}
+impl<Q> MemoizationPolicy<Q> for AlwaysMemoizeValue
+where
+    Q: QueryFunction,
+    Q::Value: Eq,
+{
+    fn should_memoize_value(_key: &Q::Key) -> bool {
+        true
+    }
+
+    fn memoized_value_eq(old_value: &Q::Value, new_value: &Q::Value) -> bool {
+        old_value == new_value
+    }
+}
+
+pub enum NeverMemoizeValue {}
+impl<Q> MemoizationPolicy<Q> for NeverMemoizeValue
+where
+    Q: QueryFunction,
+{
+    fn should_memoize_value(_key: &Q::Key) -> bool {
+        false
+    }
+
+    fn memoized_value_eq(_old_value: &Q::Value, _new_value: &Q::Value) -> bool {
+        panic!("cannot reach since we never memoize")
+    }
+}
+
+impl<Q, MP> DerivedStorage<Q, MP>
+where
+    Q: QueryFunction,
+    MP: MemoizationPolicy<Q>,
+{
+    fn slot(&self, key: &Q::Key) -> Arc<Slot<Q, MP>> {
+        if let Some(v) = self.slot_map.read().get(key) {
+            return v.clone();
+        }
+
+        let mut write = self.slot_map.write();
+        let entry = write.entry(key.clone());
+        let key_index = entry.index() as u32;
+        let database_key_index = DatabaseKeyIndex {
+            group_index: self.group_index,
+            query_index: Q::QUERY_INDEX,
+            key_index,
+        };
+        entry.or_insert_with(|| Arc::new(Slot::new(database_key_index))).clone()
+    }
+}
+
+impl<Q, MP> QueryStorageOps<Q> for DerivedStorage<Q, MP>
+where
+    Q: QueryFunction,
+    MP: MemoizationPolicy<Q>,
+{
+    const CYCLE_STRATEGY: crate::plumbing::CycleRecoveryStrategy = Q::CYCLE_STRATEGY;
+
+    fn new(group_index: u16) -> Self {
+        DerivedStorage {
+            group_index,
+            slot_map: RwLock::new(FxIndexMap::default()),
+            lru_list: Default::default(),
+            policy: PhantomData,
+        }
+    }
+
+    fn fmt_index(
+        &self,
+        _db: &<Q as QueryDb<'_>>::DynDb,
+        index: u32,
+        fmt: &mut std::fmt::Formatter<'_>,
+    ) -> std::fmt::Result {
+        let slot_map = self.slot_map.read();
+        let key = slot_map.get_index(index as usize).unwrap().0;
+        write!(fmt, "{}::{}({:?})", std::any::type_name::<Q>(), Q::QUERY_NAME, key)
+    }
+
+    fn maybe_changed_after(
+        &self,
+        db: &<Q as QueryDb<'_>>::DynDb,
+        index: u32,
+        revision: Revision,
+    ) -> bool {
+        debug_assert!(revision < db.salsa_runtime().current_revision());
+        let (key, slot) = {
+            let read = self.slot_map.read();
+            let Some((key, slot)) = read.get_index(index as usize) else {
+                return false;
+            };
+            (key.clone(), slot.clone())
+        };
+        slot.maybe_changed_after(db, revision, &key)
+    }
+
+    fn fetch(&self, db: &<Q as QueryDb<'_>>::DynDb, key: &Q::Key) -> Q::Value {
+        db.unwind_if_cancelled();
+
+        let slot = self.slot(key);
+        let StampedValue { value, durability, changed_at } = slot.read(db, key);
+
+        if let Some(evicted) = self.lru_list.record_use(&slot) {
+            evicted.evict();
+        }
+
+        db.salsa_runtime().report_query_read_and_unwind_if_cycle_resulted(
+            slot.database_key_index(),
+            durability,
+            changed_at,
+        );
+
+        value
+    }
+
+    fn durability(&self, db: &<Q as QueryDb<'_>>::DynDb, key: &Q::Key) -> Durability {
+        self.slot(key).durability(db)
+    }
+
+    fn entries<C>(&self, _db: &<Q as QueryDb<'_>>::DynDb) -> C
+    where
+        C: std::iter::FromIterator<TableEntry<Q::Key, Q::Value>>,
+    {
+        let slot_map = self.slot_map.read();
+        slot_map.iter().filter_map(|(key, slot)| slot.as_table_entry(key)).collect()
+    }
+}
+
+impl<Q, MP> QueryStorageMassOps for DerivedStorage<Q, MP>
+where
+    Q: QueryFunction,
+    MP: MemoizationPolicy<Q>,
+{
+    fn purge(&self) {
+        self.lru_list.purge();
+        *self.slot_map.write() = Default::default();
+    }
+}
+
+impl<Q, MP> LruQueryStorageOps for DerivedStorage<Q, MP>
+where
+    Q: QueryFunction,
+    MP: MemoizationPolicy<Q>,
+{
+    fn set_lru_capacity(&self, new_capacity: u16) {
+        self.lru_list.set_lru_capacity(new_capacity);
+    }
+}
+
+impl<Q, MP> DerivedQueryStorageOps<Q> for DerivedStorage<Q, MP>
+where
+    Q: QueryFunction,
+    MP: MemoizationPolicy<Q>,
+{
+    fn invalidate<S>(&self, runtime: &mut Runtime, key: &S)
+    where
+        S: Eq + Hash,
+        Q::Key: Borrow<S>,
+    {
+        runtime.with_incremented_revision(|new_revision| {
+            let map_read = self.slot_map.read();
+
+            if let Some(slot) = map_read.get(key) {
+                if let Some(durability) = slot.invalidate(new_revision) {
+                    return Some(durability);
+                }
+            }
+
+            None
+        })
+    }
+}
diff --git a/crates/salsa/src/derived_lru/slot.rs b/crates/salsa/src/derived_lru/slot.rs
new file mode 100644
index 0000000000..d0e4b5422b
--- /dev/null
+++ b/crates/salsa/src/derived_lru/slot.rs
@@ -0,0 +1,845 @@
+use crate::debug::TableEntry;
+use crate::derived_lru::MemoizationPolicy;
+use crate::durability::Durability;
+use crate::lru::LruIndex;
+use crate::lru::LruNode;
+use crate::plumbing::{DatabaseOps, QueryFunction};
+use crate::revision::Revision;
+use crate::runtime::local_state::ActiveQueryGuard;
+use crate::runtime::local_state::QueryRevisions;
+use crate::runtime::Runtime;
+use crate::runtime::RuntimeId;
+use crate::runtime::StampedValue;
+use crate::runtime::WaitResult;
+use crate::Cycle;
+use crate::{Database, DatabaseKeyIndex, Event, EventKind, QueryDb};
+use parking_lot::{RawRwLock, RwLock};
+use std::marker::PhantomData;
+use std::ops::Deref;
+use std::sync::atomic::{AtomicBool, Ordering};
+use tracing::{debug, info};
+
+pub(super) struct Slot<Q, MP>
+where
+    Q: QueryFunction,
+    MP: MemoizationPolicy<Q>,
+{
+    key_index: u32,
+    group_index: u16,
+    state: RwLock<QueryState<Q>>,
+    lru_index: LruIndex,
+    policy: PhantomData<MP>,
+}
+
+/// Defines the "current state" of query's memoized results.
+enum QueryState<Q>
+where
+    Q: QueryFunction,
+{
+    NotComputed,
+
+    /// The runtime with the given id is currently computing the
+    /// result of this query.
+    InProgress {
+        id: RuntimeId,
+
+        /// Set to true if any other queries are blocked,
+        /// waiting for this query to complete.
+        anyone_waiting: AtomicBool,
+    },
+
+    /// We have computed the query already, and here is the result.
+    Memoized(Memo<Q::Value>),
+}
+
+struct Memo<V> {
+    /// The result of the query, if we decide to memoize it.
+    value: Option<V>,
+
+    /// Last revision when this memo was verified; this begins
+    /// as the current revision.
+    pub(crate) verified_at: Revision,
+
+    /// Revision information
+    revisions: QueryRevisions,
+}
+
+/// Return value of `probe` helper.
+enum ProbeState<V, G> {
+    /// Another thread was active but has completed.
+    /// Try again!
+    Retry,
+
+    /// No entry for this key at all.
+    NotComputed(G),
+
+    /// There is an entry, but its contents have not been
+    /// verified in this revision.
+    Stale(G),
+
+    /// There is an entry, and it has been verified
+    /// in this revision, but it has no cached
+    /// value. The `Revision` is the revision where the
+    /// value last changed (if we were to recompute it).
+    NoValue(G, Revision),
+
+    /// There is an entry which has been verified,
+    /// and it has the following value-- or, we blocked
+    /// on another thread, and that resulted in a cycle.
+    UpToDate(V),
+}
+
+/// Return value of `maybe_changed_after_probe` helper.
+enum MaybeChangedSinceProbeState<G> {
+    /// Another thread was active but has completed.
+    /// Try again!
+    Retry,
+
+    /// Value may have changed in the given revision.
+    ChangedAt(Revision),
+
+    /// There is a stale cache entry that has not been
+    /// verified in this revision, so we can't say.
+    Stale(G),
+}
+
+impl<Q, MP> Slot<Q, MP>
+where
+    Q: QueryFunction,
+    MP: MemoizationPolicy<Q>,
+{
+    pub(super) fn new(database_key_index: DatabaseKeyIndex) -> Self {
+        Self {
+            key_index: database_key_index.key_index,
+            group_index: database_key_index.group_index,
+            state: RwLock::new(QueryState::NotComputed),
+            lru_index: LruIndex::default(),
+            policy: PhantomData,
+        }
+    }
+
+    pub(super) fn database_key_index(&self) -> DatabaseKeyIndex {
+        DatabaseKeyIndex {
+            group_index: self.group_index,
+            query_index: Q::QUERY_INDEX,
+            key_index: self.key_index,
+        }
+    }
+
+    pub(super) fn read(
+        &self,
+        db: &<Q as QueryDb<'_>>::DynDb,
+        key: &Q::Key,
+    ) -> StampedValue<Q::Value> {
+        let runtime = db.salsa_runtime();
+
+        // NB: We don't need to worry about people modifying the
+        // revision out from under our feet. Either `db` is a frozen
+        // database, in which case there is a lock, or the mutator
+        // thread is the current thread, and it will be prevented from
+        // doing any `set` invocations while the query function runs.
+        let revision_now = runtime.current_revision();
+
+        info!("{:?}: invoked at {:?}", self, revision_now,);
+
+        // First, do a check with a read-lock.
+        loop {
+            match self.probe(db, self.state.read(), runtime, revision_now) {
+                ProbeState::UpToDate(v) => return v,
+                ProbeState::Stale(..) | ProbeState::NoValue(..) | ProbeState::NotComputed(..) => {
+                    break
+                }
+                ProbeState::Retry => continue,
+            }
+        }
+
+        self.read_upgrade(db, key, revision_now)
+    }
+
+    /// Second phase of a read operation: acquires an upgradable-read
+    /// and -- if needed -- validates whether inputs have changed,
+    /// recomputes value, etc. This is invoked after our initial probe
+    /// shows a potentially out of date value.
+    fn read_upgrade(
+        &self,
+        db: &<Q as QueryDb<'_>>::DynDb,
+        key: &Q::Key,
+        revision_now: Revision,
+    ) -> StampedValue<Q::Value> {
+        let runtime = db.salsa_runtime();
+
+        debug!("{:?}: read_upgrade(revision_now={:?})", self, revision_now,);
+
+        // Check with an upgradable read to see if there is a value
+        // already. (This permits other readers but prevents anyone
+        // else from running `read_upgrade` at the same time.)
+        let mut old_memo = loop {
+            match self.probe(db, self.state.upgradable_read(), runtime, revision_now) {
+                ProbeState::UpToDate(v) => return v,
+                ProbeState::Stale(state)
+                | ProbeState::NotComputed(state)
+                | ProbeState::NoValue(state, _) => {
+                    type RwLockUpgradableReadGuard<'a, T> =
+                        lock_api::RwLockUpgradableReadGuard<'a, RawRwLock, T>;
+
+                    let mut state = RwLockUpgradableReadGuard::upgrade(state);
+                    match std::mem::replace(&mut *state, QueryState::in_progress(runtime.id())) {
+                        QueryState::Memoized(old_memo) => break Some(old_memo),
+                        QueryState::InProgress { .. } => unreachable!(),
+                        QueryState::NotComputed => break None,
+                    }
+                }
+                ProbeState::Retry => continue,
+            }
+        };
+
+        let panic_guard = PanicGuard::new(self, runtime);
+        let active_query = runtime.push_query(self.database_key_index());
+
+        // If we have an old-value, it *may* now be stale, since there
+        // has been a new revision since the last time we checked. So,
+        // first things first, let's walk over each of our previous
+        // inputs and check whether they are out of date.
+        if let Some(memo) = &mut old_memo {
+            if let Some(value) = memo.verify_value(db.ops_database(), revision_now, &active_query) {
+                info!("{:?}: validated old memoized value", self,);
+
+                db.salsa_event(Event {
+                    runtime_id: runtime.id(),
+                    kind: EventKind::DidValidateMemoizedValue {
+                        database_key: self.database_key_index(),
+                    },
+                });
+
+                panic_guard.proceed(old_memo);
+
+                return value;
+            }
+        }
+
+        self.execute(db, runtime, revision_now, active_query, panic_guard, old_memo, key)
+    }
+
+    fn execute(
+        &self,
+        db: &<Q as QueryDb<'_>>::DynDb,
+        runtime: &Runtime,
+        revision_now: Revision,
+        active_query: ActiveQueryGuard<'_>,
+        panic_guard: PanicGuard<'_, Q, MP>,
+        old_memo: Option<Memo<Q::Value>>,
+        key: &Q::Key,
+    ) -> StampedValue<Q::Value> {
+        tracing::info!("{:?}: executing query", self.database_key_index().debug(db));
+
+        db.salsa_event(Event {
+            runtime_id: db.salsa_runtime().id(),
+            kind: EventKind::WillExecute { database_key: self.database_key_index() },
+        });
+
+        // Query was not previously executed, or value is potentially
+        // stale, or value is absent. Let's execute!
+        let value = match Cycle::catch(|| Q::execute(db, key.clone())) {
+            Ok(v) => v,
+            Err(cycle) => {
+                tracing::debug!(
+                    "{:?}: caught cycle {:?}, have strategy {:?}",
+                    self.database_key_index().debug(db),
+                    cycle,
+                    Q::CYCLE_STRATEGY,
+                );
+                match Q::CYCLE_STRATEGY {
+                    crate::plumbing::CycleRecoveryStrategy::Panic => {
+                        panic_guard.proceed(None);
+                        cycle.throw()
+                    }
+                    crate::plumbing::CycleRecoveryStrategy::Fallback => {
+                        if let Some(c) = active_query.take_cycle() {
+                            assert!(c.is(&cycle));
+                            Q::cycle_fallback(db, &cycle, key)
+                        } else {
+                            // we are not a participant in this cycle
+                            debug_assert!(!cycle
+                                .participant_keys()
+                                .any(|k| k == self.database_key_index()));
+                            cycle.throw()
+                        }
+                    }
+                }
+            }
+        };
+
+        let mut revisions = active_query.pop();
+
+        // We assume that query is side-effect free -- that is, does
+        // not mutate the "inputs" to the query system. Sanity check
+        // that assumption here, at least to the best of our ability.
+        assert_eq!(
+            runtime.current_revision(),
+            revision_now,
+            "revision altered during query execution",
+        );
+
+        // If the new value is equal to the old one, then it didn't
+        // really change, even if some of its inputs have. So we can
+        // "backdate" its `changed_at` revision to be the same as the
+        // old value.
+        if let Some(old_memo) = &old_memo {
+            if let Some(old_value) = &old_memo.value {
+                // Careful: if the value became less durable than it
+                // used to be, that is a "breaking change" that our
+                // consumers must be aware of. Becoming *more* durable
+                // is not. See the test `constant_to_non_constant`.
+                if revisions.durability >= old_memo.revisions.durability
+                    && MP::memoized_value_eq(old_value, &value)
+                {
+                    debug!(
+                        "read_upgrade({:?}): value is equal, back-dating to {:?}",
+                        self, old_memo.revisions.changed_at,
+                    );
+
+                    assert!(old_memo.revisions.changed_at <= revisions.changed_at);
+                    revisions.changed_at = old_memo.revisions.changed_at;
+                }
+            }
+        }
+
+        let new_value = StampedValue {
+            value,
+            durability: revisions.durability,
+            changed_at: revisions.changed_at,
+        };
+
+        let memo_value =
+            if self.should_memoize_value(key) { Some(new_value.value.clone()) } else { None };
+
+        debug!("read_upgrade({:?}): result.revisions = {:#?}", self, revisions,);
+
+        panic_guard.proceed(Some(Memo { value: memo_value, verified_at: revision_now, revisions }));
+
+        new_value
+    }
+
+    /// Helper for `read` that does a shallow check (not recursive) if we have an up-to-date value.
+    ///
+    /// Invoked with the guard `state` corresponding to the `QueryState` of some `Slot` (the guard
+    /// can be either read or write). Returns a suitable `ProbeState`:
+    ///
+    /// - `ProbeState::UpToDate(r)` if the table has an up-to-date value (or we blocked on another
+    ///   thread that produced such a value).
+    /// - `ProbeState::StaleOrAbsent(g)` if either (a) there is no memo for this key, (b) the memo
+    ///   has no value; or (c) the memo has not been verified at the current revision.
+    ///
+    /// Note that in case `ProbeState::UpToDate`, the lock will have been released.
+    fn probe<StateGuard>(
+        &self,
+        db: &<Q as QueryDb<'_>>::DynDb,
+        state: StateGuard,
+        runtime: &Runtime,
+        revision_now: Revision,
+    ) -> ProbeState<StampedValue<Q::Value>, StateGuard>
+    where
+        StateGuard: Deref<Target = QueryState<Q>>,
+    {
+        match &*state {
+            QueryState::NotComputed => ProbeState::NotComputed(state),
+
+            QueryState::InProgress { id, anyone_waiting } => {
+                let other_id = *id;
+
+                // NB: `Ordering::Relaxed` is sufficient here,
+                // as there are no loads that are "gated" on this
+                // value. Everything that is written is also protected
+                // by a lock that must be acquired. The role of this
+                // boolean is to decide *whether* to acquire the lock,
+                // not to gate future atomic reads.
+                anyone_waiting.store(true, Ordering::Relaxed);
+
+                self.block_on_or_unwind(db, runtime, other_id, state);
+
+                // Other thread completely normally, so our value may be available now.
+                ProbeState::Retry
+            }
+
+            QueryState::Memoized(memo) => {
+                debug!(
+                    "{:?}: found memoized value, verified_at={:?}, changed_at={:?}",
+                    self, memo.verified_at, memo.revisions.changed_at,
+                );
+
+                if memo.verified_at < revision_now {
+                    return ProbeState::Stale(state);
+                }
+
+                if let Some(value) = &memo.value {
+                    let value = StampedValue {
+                        durability: memo.revisions.durability,
+                        changed_at: memo.revisions.changed_at,
+                        value: value.clone(),
+                    };
+
+                    info!("{:?}: returning memoized value changed at {:?}", self, value.changed_at);
+
+                    ProbeState::UpToDate(value)
+                } else {
+                    let changed_at = memo.revisions.changed_at;
+                    ProbeState::NoValue(state, changed_at)
+                }
+            }
+        }
+    }
+
+    pub(super) fn durability(&self, db: &<Q as QueryDb<'_>>::DynDb) -> Durability {
+        match &*self.state.read() {
+            QueryState::NotComputed => Durability::LOW,
+            QueryState::InProgress { .. } => panic!("query in progress"),
+            QueryState::Memoized(memo) => {
+                if memo.check_durability(db.salsa_runtime()) {
+                    memo.revisions.durability
+                } else {
+                    Durability::LOW
+                }
+            }
+        }
+    }
+
+    pub(super) fn as_table_entry(&self, key: &Q::Key) -> Option<TableEntry<Q::Key, Q::Value>> {
+        match &*self.state.read() {
+            QueryState::NotComputed => None,
+            QueryState::InProgress { .. } => Some(TableEntry::new(key.clone(), None)),
+            QueryState::Memoized(memo) => Some(TableEntry::new(key.clone(), memo.value.clone())),
+        }
+    }
+
+    pub(super) fn evict(&self) {
+        let mut state = self.state.write();
+        if let QueryState::Memoized(memo) = &mut *state {
+            // Evicting a value with an untracked input could
+            // lead to inconsistencies. Note that we can't check
+            // `has_untracked_input` when we add the value to the cache,
+            // because inputs can become untracked in the next revision.
+            if memo.has_untracked_input() {
+                return;
+            }
+            memo.value = None;
+        }
+    }
+
+    pub(super) fn invalidate(&self, new_revision: Revision) -> Option<Durability> {
+        tracing::debug!("Slot::invalidate(new_revision = {:?})", new_revision);
+        match &mut *self.state.write() {
+            QueryState::Memoized(memo) => {
+                memo.revisions.untracked = true;
+                memo.revisions.inputs = None;
+                memo.revisions.changed_at = new_revision;
+                Some(memo.revisions.durability)
+            }
+            QueryState::NotComputed => None,
+            QueryState::InProgress { .. } => unreachable!(),
+        }
+    }
+
+    pub(super) fn maybe_changed_after(
+        &self,
+        db: &<Q as QueryDb<'_>>::DynDb,
+        revision: Revision,
+        key: &Q::Key,
+    ) -> bool {
+        let runtime = db.salsa_runtime();
+        let revision_now = runtime.current_revision();
+
+        db.unwind_if_cancelled();
+
+        debug!(
+            "maybe_changed_after({:?}) called with revision={:?}, revision_now={:?}",
+            self, revision, revision_now,
+        );
+
+        // Do an initial probe with just the read-lock.
+        //
+        // If we find that a cache entry for the value is present
+        // but hasn't been verified in this revision, we'll have to
+        // do more.
+        loop {
+            match self.maybe_changed_after_probe(db, self.state.read(), runtime, revision_now) {
+                MaybeChangedSinceProbeState::Retry => continue,
+                MaybeChangedSinceProbeState::ChangedAt(changed_at) => return changed_at > revision,
+                MaybeChangedSinceProbeState::Stale(state) => {
+                    drop(state);
+                    return self.maybe_changed_after_upgrade(db, revision, key);
+                }
+            }
+        }
+    }
+
+    fn maybe_changed_after_probe<StateGuard>(
+        &self,
+        db: &<Q as QueryDb<'_>>::DynDb,
+        state: StateGuard,
+        runtime: &Runtime,
+        revision_now: Revision,
+    ) -> MaybeChangedSinceProbeState<StateGuard>
+    where
+        StateGuard: Deref<Target = QueryState<Q>>,
+    {
+        match self.probe(db, state, runtime, revision_now) {
+            ProbeState::Retry => MaybeChangedSinceProbeState::Retry,
+
+            ProbeState::Stale(state) => MaybeChangedSinceProbeState::Stale(state),
+
+            // If we know when value last changed, we can return right away.
+            // Note that we don't need the actual value to be available.
+            ProbeState::NoValue(_, changed_at)
+            | ProbeState::UpToDate(StampedValue { value: _, durability: _, changed_at }) => {
+                MaybeChangedSinceProbeState::ChangedAt(changed_at)
+            }
+
+            // If we have nothing cached, then value may have changed.
+            ProbeState::NotComputed(_) => MaybeChangedSinceProbeState::ChangedAt(revision_now),
+        }
+    }
+
+    fn maybe_changed_after_upgrade(
+        &self,
+        db: &<Q as QueryDb<'_>>::DynDb,
+        revision: Revision,
+        key: &Q::Key,
+    ) -> bool {
+        let runtime = db.salsa_runtime();
+        let revision_now = runtime.current_revision();
+
+        // Get an upgradable read lock, which permits other reads but no writers.
+        // Probe again. If the value is stale (needs to be verified), then upgrade
+        // to a write lock and swap it with InProgress while we work.
+        let mut old_memo = match self.maybe_changed_after_probe(
+            db,
+            self.state.upgradable_read(),
+            runtime,
+            revision_now,
+        ) {
+            MaybeChangedSinceProbeState::ChangedAt(changed_at) => return changed_at > revision,
+
+            // If another thread was active, then the cache line is going to be
+            // either verified or cleared out. Just recurse to figure out which.
+            // Note that we don't need an upgradable read.
+            MaybeChangedSinceProbeState::Retry => {
+                return self.maybe_changed_after(db, revision, key)
+            }
+
+            MaybeChangedSinceProbeState::Stale(state) => {
+                type RwLockUpgradableReadGuard<'a, T> =
+                    lock_api::RwLockUpgradableReadGuard<'a, RawRwLock, T>;
+
+                let mut state = RwLockUpgradableReadGuard::upgrade(state);
+                match std::mem::replace(&mut *state, QueryState::in_progress(runtime.id())) {
+                    QueryState::Memoized(old_memo) => old_memo,
+                    QueryState::NotComputed | QueryState::InProgress { .. } => unreachable!(),
+                }
+            }
+        };
+
+        let panic_guard = PanicGuard::new(self, runtime);
+        let active_query = runtime.push_query(self.database_key_index());
+
+        if old_memo.verify_revisions(db.ops_database(), revision_now, &active_query) {
+            let maybe_changed = old_memo.revisions.changed_at > revision;
+            panic_guard.proceed(Some(old_memo));
+            maybe_changed
+        } else if old_memo.value.is_some() {
+            // We found that this memoized value may have changed
+            // but we have an old value. We can re-run the code and
+            // actually *check* if it has changed.
+            let StampedValue { changed_at, .. } = self.execute(
+                db,
+                runtime,
+                revision_now,
+                active_query,
+                panic_guard,
+                Some(old_memo),
+                key,
+            );
+            changed_at > revision
+        } else {
+            // We found that inputs to this memoized value may have chanced
+            // but we don't have an old value to compare against or re-use.
+            // No choice but to drop the memo and say that its value may have changed.
+            panic_guard.proceed(None);
+            true
+        }
+    }
+
+    /// Helper: see [`Runtime::try_block_on_or_unwind`].
+    fn block_on_or_unwind<MutexGuard>(
+        &self,
+        db: &<Q as QueryDb<'_>>::DynDb,
+        runtime: &Runtime,
+        other_id: RuntimeId,
+        mutex_guard: MutexGuard,
+    ) {
+        runtime.block_on_or_unwind(
+            db.ops_database(),
+            self.database_key_index(),
+            other_id,
+            mutex_guard,
+        )
+    }
+
+    fn should_memoize_value(&self, key: &Q::Key) -> bool {
+        MP::should_memoize_value(key)
+    }
+}
+
+impl<Q> QueryState<Q>
+where
+    Q: QueryFunction,
+{
+    fn in_progress(id: RuntimeId) -> Self {
+        QueryState::InProgress { id, anyone_waiting: Default::default() }
+    }
+}
+
+struct PanicGuard<'me, Q, MP>
+where
+    Q: QueryFunction,
+    MP: MemoizationPolicy<Q>,
+{
+    slot: &'me Slot<Q, MP>,
+    runtime: &'me Runtime,
+}
+
+impl<'me, Q, MP> PanicGuard<'me, Q, MP>
+where
+    Q: QueryFunction,
+    MP: MemoizationPolicy<Q>,
+{
+    fn new(slot: &'me Slot<Q, MP>, runtime: &'me Runtime) -> Self {
+        Self { slot, runtime }
+    }
+
+    /// Indicates that we have concluded normally (without panicking).
+    /// If `opt_memo` is some, then this memo is installed as the new
+    /// memoized value. If `opt_memo` is `None`, then the slot is cleared
+    /// and has no value.
+    fn proceed(mut self, opt_memo: Option<Memo<Q::Value>>) {
+        self.overwrite_placeholder(WaitResult::Completed, opt_memo);
+        std::mem::forget(self)
+    }
+
+    /// Overwrites the `InProgress` placeholder for `key` that we
+    /// inserted; if others were blocked, waiting for us to finish,
+    /// then notify them.
+    fn overwrite_placeholder(&mut self, wait_result: WaitResult, opt_memo: Option<Memo<Q::Value>>) {
+        let old_value = {
+            let mut write = self.slot.state.write();
+            match opt_memo {
+                // Replace the `InProgress` marker that we installed with the new
+                // memo, thus releasing our unique access to this key.
+                Some(memo) => std::mem::replace(&mut *write, QueryState::Memoized(memo)),
+
+                // We had installed an `InProgress` marker, but we panicked before
+                // it could be removed. At this point, we therefore "own" unique
+                // access to our slot, so we can just remove the key.
+                None => std::mem::replace(&mut *write, QueryState::NotComputed),
+            }
+        };
+
+        match old_value {
+            QueryState::InProgress { id, anyone_waiting } => {
+                assert_eq!(id, self.runtime.id());
+
+                // NB: As noted on the `store`, `Ordering::Relaxed` is
+                // sufficient here. This boolean signals us on whether to
+                // acquire a mutex; the mutex will guarantee that all writes
+                // we are interested in are visible.
+                if anyone_waiting.load(Ordering::Relaxed) {
+                    self.runtime
+                        .unblock_queries_blocked_on(self.slot.database_key_index(), wait_result);
+                }
+            }
+            _ => panic!(
+                "\
+Unexpected panic during query evaluation, aborting the process.
+
+Please report this bug to https://github.com/salsa-rs/salsa/issues."
+            ),
+        }
+    }
+}
+
+impl<'me, Q, MP> Drop for PanicGuard<'me, Q, MP>
+where
+    Q: QueryFunction,
+    MP: MemoizationPolicy<Q>,
+{
+    fn drop(&mut self) {
+        if std::thread::panicking() {
+            // We panicked before we could proceed and need to remove `key`.
+            self.overwrite_placeholder(WaitResult::Panicked, None)
+        } else {
+            // If no panic occurred, then panic guard ought to be
+            // "forgotten" and so this Drop code should never run.
+            panic!(".forget() was not called")
+        }
+    }
+}
+
+impl<V> Memo<V>
+where
+    V: Clone,
+{
+    /// Determines whether the value stored in this memo (if any) is still
+    /// valid in the current revision. If so, returns a stamped value.
+    ///
+    /// If needed, this will walk each dependency and
+    /// recursively invoke `maybe_changed_after`, which may in turn
+    /// re-execute the dependency. This can cause cycles to occur,
+    /// so the current query must be pushed onto the
+    /// stack to permit cycle detection and recovery: therefore,
+    /// takes the `active_query` argument as evidence.
+    fn verify_value(
+        &mut self,
+        db: &dyn Database,
+        revision_now: Revision,
+        active_query: &ActiveQueryGuard<'_>,
+    ) -> Option<StampedValue<V>> {
+        // If we don't have a memoized value, nothing to validate.
+        if self.value.is_none() {
+            return None;
+        }
+        if self.verify_revisions(db, revision_now, active_query) {
+            self.value.clone().map(|value| StampedValue {
+                durability: self.revisions.durability,
+                changed_at: self.revisions.changed_at,
+                value,
+            })
+        } else {
+            None
+        }
+    }
+
+    /// Determines whether the value represented by this memo is still
+    /// valid in the current revision; note that the value itself is
+    /// not needed for this check. If needed, this will walk each
+    /// dependency and recursively invoke `maybe_changed_after`, which
+    /// may in turn re-execute the dependency. This can cause cycles to occur,
+    /// so the current query must be pushed onto the
+    /// stack to permit cycle detection and recovery: therefore,
+    /// takes the `active_query` argument as evidence.
+    fn verify_revisions(
+        &mut self,
+        db: &dyn Database,
+        revision_now: Revision,
+        _active_query: &ActiveQueryGuard<'_>,
+    ) -> bool {
+        assert!(self.verified_at != revision_now);
+        let verified_at = self.verified_at;
+
+        debug!(
+            "verify_revisions: verified_at={:?}, revision_now={:?}, inputs={:#?}",
+            verified_at, revision_now, self.revisions.inputs
+        );
+
+        if self.check_durability(db.salsa_runtime()) {
+            return self.mark_value_as_verified(revision_now);
+        }
+
+        match &self.revisions.inputs {
+            // We can't validate values that had untracked inputs; just have to
+            // re-execute.
+            None if self.revisions.untracked => return false,
+            None => {}
+
+            // Check whether any of our inputs changed since the
+            // **last point where we were verified** (not since we
+            // last changed). This is important: if we have
+            // memoized values, then an input may have changed in
+            // revision R2, but we found that *our* value was the
+            // same regardless, so our change date is still
+            // R1. But our *verification* date will be R2, and we
+            // are only interested in finding out whether the
+            // input changed *again*.
+            Some(inputs) => {
+                let changed_input =
+                    inputs.slice.iter().find(|&&input| db.maybe_changed_after(input, verified_at));
+                if let Some(input) = changed_input {
+                    debug!("validate_memoized_value: `{:?}` may have changed", input);
+
+                    return false;
+                }
+            }
+        };
+
+        self.mark_value_as_verified(revision_now)
+    }
+
+    /// True if this memo is known not to have changed based on its durability.
+    fn check_durability(&self, runtime: &Runtime) -> bool {
+        let last_changed = runtime.last_changed_revision(self.revisions.durability);
+        debug!(
+            "check_durability(last_changed={:?} <= verified_at={:?}) = {:?}",
+            last_changed,
+            self.verified_at,
+            last_changed <= self.verified_at,
+        );
+        last_changed <= self.verified_at
+    }
+
+    fn mark_value_as_verified(&mut self, revision_now: Revision) -> bool {
+        self.verified_at = revision_now;
+        true
+    }
+
+    fn has_untracked_input(&self) -> bool {
+        self.revisions.untracked
+    }
+}
+
+impl<Q, MP> std::fmt::Debug for Slot<Q, MP>
+where
+    Q: QueryFunction,
+    MP: MemoizationPolicy<Q>,
+{
+    fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(fmt, "{:?}", Q::default())
+    }
+}
+
+impl<Q, MP> LruNode for Slot<Q, MP>
+where
+    Q: QueryFunction,
+    MP: MemoizationPolicy<Q>,
+{
+    fn lru_index(&self) -> &LruIndex {
+        &self.lru_index
+    }
+}
+
+/// Check that `Slot<Q, MP>: Send + Sync` as long as
+/// `DB::DatabaseData: Send + Sync`, which in turn implies that
+/// `Q::Key: Send + Sync`, `Q::Value: Send + Sync`.
+#[allow(dead_code)]
+fn check_send_sync<Q, MP>()
+where
+    Q: QueryFunction,
+    MP: MemoizationPolicy<Q>,
+    Q::Key: Send + Sync,
+    Q::Value: Send + Sync,
+{
+    fn is_send_sync<T: Send + Sync>() {}
+    is_send_sync::<Slot<Q, MP>>();
+}
+
+/// Check that `Slot<Q, MP>: 'static` as long as
+/// `DB::DatabaseData: 'static`, which in turn implies that
+/// `Q::Key: 'static`, `Q::Value: 'static`.
+#[allow(dead_code)]
+fn check_static<Q, MP>()
+where
+    Q: QueryFunction + 'static,
+    MP: MemoizationPolicy<Q> + 'static,
+    Q::Key: 'static,
+    Q::Value: 'static,
+{
+    fn is_static<T: 'static>() {}
+    is_static::<Slot<Q, MP>>();
+}
diff --git a/crates/salsa/src/interned.rs b/crates/salsa/src/interned.rs
index eef8bcc814..359662ec6b 100644
--- a/crates/salsa/src/interned.rs
+++ b/crates/salsa/src/interned.rs
@@ -121,8 +121,8 @@ impl InternValueTrivial for String {}
 
 #[derive(Debug)]
 struct Slot<V> {
-    /// DatabaseKeyIndex for this slot.
-    database_key_index: DatabaseKeyIndex,
+    /// key index for this slot.
+    key_index: u32,
 
     /// Value that was interned.
     value: V,
@@ -199,13 +199,8 @@ where
         };
 
         let create_slot = |index: InternId| {
-            let database_key_index = DatabaseKeyIndex {
-                group_index: self.group_index,
-                query_index: Q::QUERY_INDEX,
-                key_index: index.as_u32(),
-            };
             Arc::new(Slot {
-                database_key_index,
+                key_index: index.as_u32(),
                 value: insert(Q::Value::from_intern_id(index)),
                 interned_at: revision_now,
             })
@@ -242,7 +237,11 @@ where
         };
         let changed_at = slot.interned_at;
         db.salsa_runtime().report_query_read_and_unwind_if_cycle_resulted(
-            slot.database_key_index,
+            DatabaseKeyIndex {
+                group_index: self.group_index,
+                query_index: Q::QUERY_INDEX,
+                key_index: slot.key_index,
+            },
             INTERN_DURABILITY,
             changed_at,
         );
@@ -294,7 +293,11 @@ where
         };
         let changed_at = slot.interned_at;
         db.salsa_runtime().report_query_read_and_unwind_if_cycle_resulted(
-            slot.database_key_index,
+            DatabaseKeyIndex {
+                group_index: self.group_index,
+                query_index: Q::QUERY_INDEX,
+                key_index: slot.key_index,
+            },
             INTERN_DURABILITY,
             changed_at,
         );
@@ -414,7 +417,11 @@ where
         let value = slot.value.clone();
         let interned_at = slot.interned_at;
         db.salsa_runtime().report_query_read_and_unwind_if_cycle_resulted(
-            slot.database_key_index,
+            DatabaseKeyIndex {
+                group_index: interned_storage.group_index,
+                query_index: Q::QUERY_INDEX,
+                key_index: slot.key_index,
+            },
             INTERN_DURABILITY,
             interned_at,
         );
diff --git a/crates/salsa/src/lib.rs b/crates/salsa/src/lib.rs
index e11e6e2e19..48d6dc2e38 100644
--- a/crates/salsa/src/lib.rs
+++ b/crates/salsa/src/lib.rs
@@ -10,6 +10,7 @@
 //! from previous invocations as appropriate.
 
 mod derived;
+mod derived_lru;
 mod durability;
 mod hash;
 mod input;
@@ -577,7 +578,7 @@ where
     /// cost of potential extra recalculations of evicted values.
     ///
     /// If `cap` is zero, all values are preserved, this is the default.
-    pub fn set_lru_capacity(&self, cap: usize)
+    pub fn set_lru_capacity(&self, cap: u16)
     where
         Q::Storage: plumbing::LruQueryStorageOps,
     {
diff --git a/crates/salsa/src/lru.rs b/crates/salsa/src/lru.rs
index f63f4c1e98..a6f96beeab 100644
--- a/crates/salsa/src/lru.rs
+++ b/crates/salsa/src/lru.rs
@@ -1,7 +1,7 @@
 use oorandom::Rand64;
 use parking_lot::Mutex;
 use std::fmt::Debug;
-use std::sync::atomic::AtomicUsize;
+use std::sync::atomic::AtomicU16;
 use std::sync::atomic::Ordering;
 use triomphe::Arc;
 
@@ -20,15 +20,15 @@ pub(crate) struct Lru<Node>
 where
     Node: LruNode,
 {
-    green_zone: AtomicUsize,
+    green_zone: AtomicU16,
     data: Mutex<LruData<Node>>,
 }
 
 #[derive(Debug)]
 struct LruData<Node> {
-    end_red_zone: usize,
-    end_yellow_zone: usize,
-    end_green_zone: usize,
+    end_red_zone: u16,
+    end_yellow_zone: u16,
+    end_green_zone: u16,
     rng: Rand64,
     entries: Vec<Arc<Node>>,
 }
@@ -39,9 +39,9 @@ pub(crate) trait LruNode: Sized + Debug {
 
 #[derive(Debug)]
 pub(crate) struct LruIndex {
-    /// Index in the appropriate LRU list, or std::usize::MAX if not a
+    /// Index in the appropriate LRU list, or std::u16::MAX if not a
     /// member.
-    index: AtomicUsize,
+    index: AtomicU16,
 }
 
 impl<Node> Default for Lru<Node>
@@ -68,12 +68,12 @@ where
 
     #[cfg_attr(not(test), allow(dead_code))]
     fn with_seed(seed: &str) -> Self {
-        Lru { green_zone: AtomicUsize::new(0), data: Mutex::new(LruData::with_seed(seed)) }
+        Lru { green_zone: AtomicU16::new(0), data: Mutex::new(LruData::with_seed(seed)) }
     }
 
     /// Adjust the total number of nodes permitted to have a value at
     /// once.  If `len` is zero, this disables LRU caching completely.
-    pub(crate) fn set_lru_capacity(&self, len: usize) {
+    pub(crate) fn set_lru_capacity(&self, len: u16) {
         let mut data = self.data.lock();
 
         // We require each zone to have at least 1 slot. Therefore,
@@ -143,23 +143,24 @@ where
         LruData { end_yellow_zone: 0, end_green_zone: 0, end_red_zone: 0, entries: Vec::new(), rng }
     }
 
-    fn green_zone(&self) -> std::ops::Range<usize> {
+    fn green_zone(&self) -> std::ops::Range<u16> {
         0..self.end_green_zone
     }
 
-    fn yellow_zone(&self) -> std::ops::Range<usize> {
+    fn yellow_zone(&self) -> std::ops::Range<u16> {
         self.end_green_zone..self.end_yellow_zone
     }
 
-    fn red_zone(&self) -> std::ops::Range<usize> {
+    fn red_zone(&self) -> std::ops::Range<u16> {
         self.end_yellow_zone..self.end_red_zone
     }
 
-    fn resize(&mut self, len_green_zone: usize, len_yellow_zone: usize, len_red_zone: usize) {
+    fn resize(&mut self, len_green_zone: u16, len_yellow_zone: u16, len_red_zone: u16) {
         self.end_green_zone = len_green_zone;
         self.end_yellow_zone = self.end_green_zone + len_yellow_zone;
         self.end_red_zone = self.end_yellow_zone + len_red_zone;
-        let entries = std::mem::replace(&mut self.entries, Vec::with_capacity(self.end_red_zone));
+        let entries =
+            std::mem::replace(&mut self.entries, Vec::with_capacity(self.end_red_zone as usize));
 
         tracing::debug!("green_zone = {:?}", self.green_zone());
         tracing::debug!("yellow_zone = {:?}", self.yellow_zone());
@@ -207,7 +208,7 @@ where
 
         // Easy case: we still have capacity. Push it, and then promote
         // it up to the appropriate zone.
-        let len = self.entries.len();
+        let len = self.entries.len() as u16;
         if len < self.end_red_zone {
             self.entries.push(node.clone());
             node.lru_index().store(len);
@@ -218,7 +219,7 @@ where
         // Harder case: no capacity. Create some by evicting somebody from red
         // zone and then promoting.
         let victim_index = self.pick_index(self.red_zone());
-        let victim_node = std::mem::replace(&mut self.entries[victim_index], node.clone());
+        let victim_node = std::mem::replace(&mut self.entries[victim_index as usize], node.clone());
         tracing::debug!("evicting red node {:?} from {}", victim_node, victim_index);
         victim_node.lru_index().clear();
         self.promote_red_to_green(node, victim_index);
@@ -231,7 +232,7 @@ where
     ///
     /// NB: It is not required that `node.lru_index()` is up-to-date
     /// when entering this method.
-    fn promote_red_to_green(&mut self, node: &Arc<Node>, red_index: usize) {
+    fn promote_red_to_green(&mut self, node: &Arc<Node>, red_index: u16) {
         debug_assert!(self.red_zone().contains(&red_index));
 
         // Pick a yellow at random and switch places with it.
@@ -242,12 +243,12 @@ where
         let yellow_index = self.pick_index(self.yellow_zone());
         tracing::debug!(
             "demoting yellow node {:?} from {} to red at {}",
-            self.entries[yellow_index],
+            self.entries[yellow_index as usize],
             yellow_index,
             red_index,
         );
-        self.entries.swap(yellow_index, red_index);
-        self.entries[red_index].lru_index().store(red_index);
+        self.entries.swap(yellow_index as usize, red_index as usize);
+        self.entries[red_index as usize].lru_index().store(red_index);
 
         // Now move ourselves up into the green zone.
         self.promote_yellow_to_green(node, yellow_index);
@@ -259,51 +260,51 @@ where
     ///
     /// NB: It is not required that `node.lru_index()` is up-to-date
     /// when entering this method.
-    fn promote_yellow_to_green(&mut self, node: &Arc<Node>, yellow_index: usize) {
+    fn promote_yellow_to_green(&mut self, node: &Arc<Node>, yellow_index: u16) {
         debug_assert!(self.yellow_zone().contains(&yellow_index));
 
         // Pick a yellow at random and switch places with it.
         let green_index = self.pick_index(self.green_zone());
         tracing::debug!(
             "demoting green node {:?} from {} to yellow at {}",
-            self.entries[green_index],
+            self.entries[green_index as usize],
             green_index,
             yellow_index
         );
-        self.entries.swap(green_index, yellow_index);
-        self.entries[yellow_index].lru_index().store(yellow_index);
+        self.entries.swap(green_index as usize, yellow_index as usize);
+        self.entries[yellow_index as usize].lru_index().store(yellow_index);
         node.lru_index().store(green_index);
 
         tracing::debug!("promoted {:?} to green index {}", node, green_index);
     }
 
-    fn pick_index(&mut self, zone: std::ops::Range<usize>) -> usize {
-        let end_index = std::cmp::min(zone.end, self.entries.len());
-        self.rng.rand_range(zone.start as u64..end_index as u64) as usize
+    fn pick_index(&mut self, zone: std::ops::Range<u16>) -> u16 {
+        let end_index = std::cmp::min(zone.end, self.entries.len() as u16);
+        self.rng.rand_range(zone.start as u64..end_index as u64) as u16
     }
 }
 
 impl Default for LruIndex {
     fn default() -> Self {
-        Self { index: AtomicUsize::new(usize::MAX) }
+        Self { index: AtomicU16::new(u16::MAX) }
     }
 }
 
 impl LruIndex {
-    fn load(&self) -> usize {
+    fn load(&self) -> u16 {
         self.index.load(Ordering::Acquire) // see note on ordering below
     }
 
-    fn store(&self, value: usize) {
+    fn store(&self, value: u16) {
         self.index.store(value, Ordering::Release) // see note on ordering below
     }
 
     fn clear(&self) {
-        self.store(usize::MAX);
+        self.store(u16::MAX);
     }
 
     fn is_in_lru(&self) -> bool {
-        self.load() != usize::MAX
+        self.load() != u16::MAX
     }
 }
 
diff --git a/crates/salsa/src/plumbing.rs b/crates/salsa/src/plumbing.rs
index 1dfde63986..e96b9daa97 100644
--- a/crates/salsa/src/plumbing.rs
+++ b/crates/salsa/src/plumbing.rs
@@ -12,8 +12,9 @@ use std::fmt::Debug;
 use std::hash::Hash;
 use triomphe::Arc;
 
-pub use crate::derived::DependencyStorage;
 pub use crate::derived::MemoizedStorage;
+pub use crate::derived_lru::DependencyStorage as LruDependencyStorage;
+pub use crate::derived_lru::MemoizedStorage as LruMemoizedStorage;
 pub use crate::input::{InputStorage, UnitInputStorage};
 pub use crate::interned::InternedStorage;
 pub use crate::interned::LookupInternedStorage;
@@ -228,7 +229,7 @@ where
 /// that is, storage whose value is not derived from other storage but
 /// is set independently.
 pub trait LruQueryStorageOps {
-    fn set_lru_capacity(&self, new_capacity: usize);
+    fn set_lru_capacity(&self, new_capacity: u16);
 }
 
 pub trait DerivedQueryStorageOps<Q>
diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md
index fbb4fc6113..74acb6f994 100644
--- a/docs/dev/lsp-extensions.md
+++ b/docs/dev/lsp-extensions.md
@@ -1,5 +1,5 @@
 <!---
-lsp/ext.rs hash: 278250dba58cd879
+lsp/ext.rs hash: 39b47906286ad9c
 
 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/docs/user/generated_config.adoc b/docs/user/generated_config.adoc
index cb876e8536..bbfae1839b 100644
--- a/docs/user/generated_config.adoc
+++ b/docs/user/generated_config.adoc
@@ -82,7 +82,7 @@ By default, a cargo invocation will be constructed for the configured
 targets and features, with the following base command line:
 
 ```bash
-cargo check --quiet --workspace --message-format=json --all-targets --keep-going
+cargo check --quiet --workspace --message-format=json --all-targets
 ```
 .
 --
@@ -1015,104 +1015,6 @@ Show documentation.
 --
 Whether to insert closing angle brackets when typing an opening angle bracket of a generic argument list.
 --
-[[rust-analyzer.workspace.discoverConfig]]rust-analyzer.workspace.discoverConfig (default: `null`)::
-+
---
-Enables automatic discovery of projects using [`DiscoverWorkspaceConfig::command`].
-
-[`DiscoverWorkspaceConfig`] also requires setting `progress_label` and `files_to_watch`.
-`progress_label` is used for the title in progress indicators, whereas `files_to_watch`
-is used to determine which build system-specific files should be watched in order to
-reload rust-analyzer.
-
-Below is an example of a valid configuration:
-```json
-"rust-analyzer.workspace.discoverConfig": {
-    "command": [
-        "rust-project",
-        "develop-json",
-        {arg}
-    ],
-    "progressLabel": "rust-analyzer",
-    "filesToWatch": [
-        "BUCK",
-    ],
-}
-```
-
-## On `DiscoverWorkspaceConfig::command`
-
-**Warning**: This format is provisional and subject to change.
-
-[`DiscoverWorkspaceConfig::command`] *must* return a JSON object
-corresponding to `DiscoverProjectData::Finished`:
-
-```norun
-#[derive(Debug, Clone, Deserialize, Serialize)]
-#[serde(tag = "kind")]
-#[serde(rename_all = "snake_case")]
-enum DiscoverProjectData {
-    Finished { buildfile: Utf8PathBuf, project: ProjectJsonData },
-    Error { error: String, source: Option<String> },
-    Progress { message: String },
-}
-```
-
-As JSON, `DiscoverProjectData::Finished` is:
-
-```json
-{
-    // the internally-tagged representation of the enum.
-    "kind": "finished",
-    // the file used by a non-Cargo build system to define
-    // a package or target.
-    "buildfile": "rust-analyzer/BUILD",
-    // the contents of a rust-project.json, elided for brevity
-    "project": {
-        "sysroot": "foo",
-        "crates": []
-    }
-}
-```
-
-It is encouraged, but not required, to use the other variants on
-`DiscoverProjectData` to provide a more polished end-user experience.
-
-`DiscoverWorkspaceConfig::command` may *optionally* include an `{arg}`,
-which will be substituted with the JSON-serialized form of the following
-enum:
-
-```norun
-#[derive(PartialEq, Clone, Debug, Serialize)]
-#[serde(rename_all = "camelCase")]
-pub enum DiscoverArgument {
-   Path(AbsPathBuf),
-   Buildfile(AbsPathBuf),
-}
-```
-
-The JSON representation of `DiscoverArgument::Path` is:
-
-```json
-{
-    "path": "src/main.rs"
-}
-```
-
-Similarly, the JSON representation of `DiscoverArgument::Buildfile` is:
-
-```
-{
-    "buildfile": "BUILD"
-}
-```
-
-`DiscoverArgument::Path` is used to find and generate a `rust-project.json`,
-and therefore, a workspace, whereas `DiscoverArgument::buildfile` is used to
-to update an existing workspace. As a reference for implementors,
-buck2's `rust-project` will likely be useful:
-https://github.com/facebook/buck2/tree/main/integrations/rust-project.
---
 [[rust-analyzer.workspace.symbol.search.kind]]rust-analyzer.workspace.symbol.search.kind (default: `"only_types"`)::
 +
 --
diff --git a/editors/code/package.json b/editors/code/package.json
index b1e6e4837a..899d3c1495 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -713,7 +713,7 @@
                 "title": "cargo",
                 "properties": {
                     "rust-analyzer.cargo.buildScripts.overrideCommand": {
-                        "markdownDescription": "Override the command rust-analyzer uses to run build scripts and\nbuild procedural macros. The command is required to output json\nand should therefore include `--message-format=json` or a similar\noption.\n\nIf there are multiple linked projects/workspaces, this command is invoked for\neach of them, with the working directory being the workspace root\n(i.e., the folder containing the `Cargo.toml`). This can be overwritten\nby changing `#rust-analyzer.cargo.buildScripts.invocationStrategy#` and\n`#rust-analyzer.cargo.buildScripts.invocationLocation#`.\n\nBy default, a cargo invocation will be constructed for the configured\ntargets and features, with the following base command line:\n\n```bash\ncargo check --quiet --workspace --message-format=json --all-targets --keep-going\n```\n.",
+                        "markdownDescription": "Override the command rust-analyzer uses to run build scripts and\nbuild procedural macros. The command is required to output json\nand should therefore include `--message-format=json` or a similar\noption.\n\nIf there are multiple linked projects/workspaces, this command is invoked for\neach of them, with the working directory being the workspace root\n(i.e., the folder containing the `Cargo.toml`). This can be overwritten\nby changing `#rust-analyzer.cargo.buildScripts.invocationStrategy#` and\n`#rust-analyzer.cargo.buildScripts.invocationLocation#`.\n\nBy default, a cargo invocation will be constructed for the configured\ntargets and features, with the following base command line:\n\n```bash\ncargo check --quiet --workspace --message-format=json --all-targets\n```\n.",
                         "default": null,
                         "type": [
                             "null",
@@ -2252,7 +2252,8 @@
                             "null",
                             "integer"
                         ],
-                        "minimum": 0
+                        "minimum": 0,
+                        "maximum": 65535
                     }
                 }
             },
@@ -2590,40 +2591,6 @@
                     }
                 }
             },
-            {
-                "title": "workspace",
-                "properties": {
-                    "rust-analyzer.workspace.discoverConfig": {
-                        "markdownDescription": "Enables automatic discovery of projects using [`DiscoverWorkspaceConfig::command`].\n\n[`DiscoverWorkspaceConfig`] also requires setting `progress_label` and `files_to_watch`.\n`progress_label` is used for the title in progress indicators, whereas `files_to_watch`\nis used to determine which build system-specific files should be watched in order to\nreload rust-analyzer.\n\nBelow is an example of a valid configuration:\n```json\n\"rust-analyzer.workspace.discoverConfig\": {\n        \"command\": [\n                \"rust-project\",\n                \"develop-json\",\n                {arg}\n        ],\n        \"progressLabel\": \"rust-analyzer\",\n        \"filesToWatch\": [\n                \"BUCK\",\n        ],\n}\n```\n\n## On `DiscoverWorkspaceConfig::command`\n\n**Warning**: This format is provisional and subject to change.\n\n[`DiscoverWorkspaceConfig::command`] *must* return a JSON object\ncorresponding to `DiscoverProjectData::Finished`:\n\n```norun\n#[derive(Debug, Clone, Deserialize, Serialize)]\n#[serde(tag = \"kind\")]\n#[serde(rename_all = \"snake_case\")]\nenum DiscoverProjectData {\n        Finished { buildfile: Utf8PathBuf, project: ProjectJsonData },\n        Error { error: String, source: Option<String> },\n        Progress { message: String },\n}\n```\n\nAs JSON, `DiscoverProjectData::Finished` is:\n\n```json\n{\n        // the internally-tagged representation of the enum.\n        \"kind\": \"finished\",\n        // the file used by a non-Cargo build system to define\n        // a package or target.\n        \"buildfile\": \"rust-analyzer/BUILD\",\n        // the contents of a rust-project.json, elided for brevity\n        \"project\": {\n                \"sysroot\": \"foo\",\n                \"crates\": []\n        }\n}\n```\n\nIt is encouraged, but not required, to use the other variants on\n`DiscoverProjectData` to provide a more polished end-user experience.\n\n`DiscoverWorkspaceConfig::command` may *optionally* include an `{arg}`,\nwhich will be substituted with the JSON-serialized form of the following\nenum:\n\n```norun\n#[derive(PartialEq, Clone, Debug, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub enum DiscoverArgument {\n     Path(AbsPathBuf),\n     Buildfile(AbsPathBuf),\n}\n```\n\nThe JSON representation of `DiscoverArgument::Path` is:\n\n```json\n{\n        \"path\": \"src/main.rs\"\n}\n```\n\nSimilarly, the JSON representation of `DiscoverArgument::Buildfile` is:\n\n```\n{\n        \"buildfile\": \"BUILD\"\n}\n```\n\n`DiscoverArgument::Path` is used to find and generate a `rust-project.json`,\nand therefore, a workspace, whereas `DiscoverArgument::buildfile` is used to\nto update an existing workspace. As a reference for implementors,\nbuck2's `rust-project` will likely be useful:\nhttps://github.com/facebook/buck2/tree/main/integrations/rust-project.",
-                        "default": null,
-                        "anyOf": [
-                            {
-                                "type": "null"
-                            },
-                            {
-                                "type": "object",
-                                "properties": {
-                                    "command": {
-                                        "type": "array",
-                                        "items": {
-                                            "type": "string"
-                                        }
-                                    },
-                                    "progressLabel": {
-                                        "type": "string"
-                                    },
-                                    "filesToWatch": {
-                                        "type": "array",
-                                        "items": {
-                                            "type": "string"
-                                        }
-                                    }
-                                }
-                            }
-                        ]
-                    }
-                }
-            },
             {
                 "title": "workspace",
                 "properties": {
@@ -3216,6 +3183,9 @@
                 },
                 {
                     "command": "rust-analyzer.openWalkthrough"
+                },
+                {
+                    "command": "rust-analyzer.openFAQ"
                 }
             ],
             "editor/context": [
diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts
index 621b969578..72ca2923dd 100644
--- a/editors/code/src/commands.ts
+++ b/editors/code/src/commands.ts
@@ -1518,3 +1518,13 @@ export function openWalkthrough(_: Ctx): Cmd {
         );
     };
 }
+
+export function openFAQ(_: Ctx): Cmd {
+    return async () => {
+        await vscode.commands.executeCommand(
+            "workbench.action.openWalkthrough",
+            "rust-lang.rust-analyzer#faq",
+            true,
+        );
+    };
+}
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts
index db1c377b46..a23f3656e3 100644
--- a/editors/code/src/main.ts
+++ b/editors/code/src/main.ts
@@ -179,6 +179,7 @@ function createCommands(): Record<string, CommandFactory> {
         toggleCheckOnSave: { enabled: commands.toggleCheckOnSave },
         toggleLSPLogs: { enabled: commands.toggleLSPLogs },
         openWalkthrough: { enabled: commands.openWalkthrough },
+        openFAQ: { enabled: commands.openFAQ },
         // Internal commands which are invoked by the server.
         applyActionGroup: { enabled: commands.applyActionGroup },
         applySnippetWorkspaceEdit: { enabled: commands.applySnippetWorkspaceEditCommand },
diff --git a/rust-version b/rust-version
index 18e32e5c65..424c93a752 100644
--- a/rust-version
+++ b/rust-version
@@ -1,1 +1,1 @@
-a91f7d72f12efcc00ecf71591f066c534d45ddf7
+bcf1f6db4594ae6132378b179a30cdb3599a863d
diff --git a/xtask/src/flags.rs b/xtask/src/flags.rs
index ebb9e71a4f..fd4291de9e 100644
--- a/xtask/src/flags.rs
+++ b/xtask/src/flags.rs
@@ -289,7 +289,7 @@ impl Malloc {
 
 impl Install {
     pub(crate) fn server(&self) -> Option<ServerOpt> {
-        if (self.client || self.proc_macro_server) && !self.server {
+        if !self.server {
             return None;
         }
         let malloc = if self.mimalloc {
@@ -308,7 +308,7 @@ impl Install {
         Some(ProcMacroServerOpt { dev_rel: self.dev_rel })
     }
     pub(crate) fn client(&self) -> Option<ClientOpt> {
-        if (self.server || self.proc_macro_server) && !self.client {
+        if !self.client {
             return None;
         }
         Some(ClientOpt { code_bin: self.code_bin.clone() })


Test patch:
diff --git a/crates/rust-analyzer/tests/slow-tests/main.rs b/crates/rust-analyzer/tests/slow-tests/main.rs
index 6bbf82a775..aa17b587e0 100644
--- a/crates/rust-analyzer/tests/slow-tests/main.rs
+++ b/crates/rust-analyzer/tests/slow-tests/main.rs
@@ -27,6 +27,7 @@ use lsp_types::{
     InlayHint, InlayHintLabel, InlayHintParams, PartialResultParams, Position, Range,
     RenameFilesParams, TextDocumentItem, TextDocumentPositionParams, WorkDoneProgressParams,
 };
+
 use rust_analyzer::lsp::ext::{OnEnter, Runnables, RunnablesParams, UnindexedProject};
 use serde_json::json;
 use stdx::format_to_acc;
diff --git a/crates/salsa/tests/lru.rs b/crates/salsa/tests/lru.rs
index 3da8519b08..ef98a2c32b 100644
--- a/crates/salsa/tests/lru.rs
+++ b/crates/salsa/tests/lru.rs
@@ -24,7 +24,9 @@ impl Drop for HotPotato {
 
 #[salsa::query_group(QueryGroupStorage)]
 trait QueryGroup: salsa::Database {
+    #[salsa::lru]
     fn get(&self, x: u32) -> Arc<HotPotato>;
+    #[salsa::lru]
     fn get_volatile(&self, x: u32) -> usize;
 }