Repository: rust-lang/rust-analyzer
Pull request #16794 (2024-03-08 21:54:26)

Some minor changes in the test explorer lsp extension
followup #16662 

cc @nemethf @ShuiRuTian 


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 903141eee9..03f9ffb1ec 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1425,9 +1425,9 @@ dependencies = [
 
 [[package]]
 name = "ra-ap-rustc_abi"
-version = "0.42.0"
+version = "0.40.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c2ae52e2d5b08762c9464b541345f519b8719d57b643b73632bade43ecece9dc"
+checksum = "2548746ce549476a7aa982099dfe5139f939099d83077b3393baf5e920292f64"
 dependencies = [
  "bitflags 2.4.2",
  "ra-ap-rustc_index",
@@ -1436,9 +1436,9 @@ dependencies = [
 
 [[package]]
 name = "ra-ap-rustc_index"
-version = "0.42.0"
+version = "0.40.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bfd7e10c7853fe79443d46e1d2d8ab09fe99926118e59653fb8b480d5045f126"
+checksum = "371db64f1be25aae96650b5528c76066ac325f0fe23efec0aab1179d75eea5c3"
 dependencies = [
  "arrayvec",
  "ra-ap-rustc_index_macros",
@@ -1447,9 +1447,9 @@ dependencies = [
 
 [[package]]
 name = "ra-ap-rustc_index_macros"
-version = "0.42.0"
+version = "0.40.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "47f1d1c589be6c9a9e852fadee0e60329c0f862e87442ac2fe5adae30663cc76"
+checksum = "fa4a454193807f557a991f52157250fd2db95abfb96da65e85355e6949802a98"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1459,9 +1459,9 @@ dependencies = [
 
 [[package]]
 name = "ra-ap-rustc_lexer"
-version = "0.42.0"
+version = "0.40.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fa852373a757b4c723bbdc96ced7f575cad68a1e266e45fee12bc4c69a482d80"
+checksum = "4f346eb8d73041fd48280373f929803c4650771da631b1d076d5ed0ca4216b48"
 dependencies = [
  "unicode-properties",
  "unicode-xid",
@@ -1469,9 +1469,9 @@ dependencies = [
 
 [[package]]
 name = "ra-ap-rustc_parse_format"
-version = "0.42.0"
+version = "0.40.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2afe3c49accd95a53ac4d72ae13bafc7d115bdd80c8cd56ab09e6fc68f482210"
+checksum = "85f8a94cf47d86b93e7c2440de4308de74e1d5cb71ed3a9df7dcde2550742e6e"
 dependencies = [
  "ra-ap-rustc_index",
  "ra-ap-rustc_lexer",
@@ -1479,9 +1479,9 @@ dependencies = [
 
 [[package]]
 name = "ra-ap-rustc_pattern_analysis"
-version = "0.42.0"
+version = "0.40.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1253da23515d80c377a3998731e0ec3794997b62b989fd47db73efbde6a0bd7c"
+checksum = "c9cb49490fadb58e6b8cc825f73157a33730495bfd5c6e9eef067a4b50ad1836"
 dependencies = [
  "ra-ap-rustc_index",
  "rustc-hash",
diff --git a/Cargo.toml b/Cargo.toml
index 440f46a938..eb3be72396 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -84,11 +84,11 @@ tt = { path = "./crates/tt", version = "0.0.0" }
 vfs-notify = { path = "./crates/vfs-notify", version = "0.0.0" }
 vfs = { path = "./crates/vfs", version = "0.0.0" }
 
-ra-ap-rustc_lexer = { version = "0.42.0", default-features = false }
-ra-ap-rustc_parse_format = { version = "0.42.0", default-features = false }
-ra-ap-rustc_index = { version = "0.42.0", default-features = false }
-ra-ap-rustc_abi = { version = "0.42.0", default-features = false }
-ra-ap-rustc_pattern_analysis = { version = "0.42.0", default-features = false }
+ra-ap-rustc_lexer = { version = "0.40.0", default-features = false }
+ra-ap-rustc_parse_format = { version = "0.40.0", default-features = false }
+ra-ap-rustc_index = { version = "0.40.0", default-features = false }
+ra-ap-rustc_abi = { version = "0.40.0", default-features = false }
+ra-ap-rustc_pattern_analysis = { version = "0.40.0", default-features = false }
 
 # local crates that aren't published to crates.io. These should not have versions.
 sourcegen = { path = "./crates/sourcegen" }
diff --git a/crates/base-db/src/lib.rs b/crates/base-db/src/lib.rs
index 758d2a45c8..cb2e6cdaa2 100644
--- a/crates/base-db/src/lib.rs
+++ b/crates/base-db/src/lib.rs
@@ -43,7 +43,7 @@ pub trait Upcast<T: ?Sized> {
 }
 
 pub const DEFAULT_PARSE_LRU_CAP: usize = 128;
-pub const DEFAULT_BORROWCK_LRU_CAP: usize = 1024;
+pub const DEFAULT_BORROWCK_LRU_CAP: usize = 256;
 
 pub trait FileLoader {
     /// Text of the file.
diff --git a/crates/hir-def/src/attr.rs b/crates/hir-def/src/attr.rs
index 21536098b8..519706c65f 100644
--- a/crates/hir-def/src/attr.rs
+++ b/crates/hir-def/src/attr.rs
@@ -348,7 +348,7 @@ impl AttrsWithOwner {
                         .raw_attrs(AttrOwner::ModItem(definition_tree_id.value.into()))
                         .clone(),
                     ModuleOrigin::BlockExpr { id, .. } => {
-                        let tree = db.block_item_tree(id);
+                        let tree = db.block_item_tree_query(id);
                         tree.raw_attrs(AttrOwner::TopLevel).clone()
                     }
                 }
diff --git a/crates/hir-def/src/body/lower.rs b/crates/hir-def/src/body/lower.rs
index 6669127789..18a6c5c2cd 100644
--- a/crates/hir-def/src/body/lower.rs
+++ b/crates/hir-def/src/body/lower.rs
@@ -703,8 +703,7 @@ impl ExprCollector<'_> {
         let Some(try_from_output) = LangItem::TryTraitFromOutput.path(self.db, self.krate) else {
             return self.collect_block(e);
         };
-        let label = self
-            .alloc_label_desugared(Label { name: Name::generate_new_name(self.body.labels.len()) });
+        let label = self.alloc_label_desugared(Label { name: Name::generate_new_name() });
         let old_label = self.current_try_block_label.replace(label);
 
         let (btail, expr_id) = self.with_labeled_rib(label, |this| {
@@ -841,7 +840,7 @@ impl ExprCollector<'_> {
                 this.collect_expr_opt(e.loop_body().map(|it| it.into()))
             }),
         };
-        let iter_name = Name::generate_new_name(self.body.exprs.len());
+        let iter_name = Name::generate_new_name();
         let iter_expr = self.alloc_expr(Expr::Path(Path::from(iter_name.clone())), syntax_ptr);
         let iter_expr_mut = self.alloc_expr(
             Expr::Ref { expr: iter_expr, rawness: Rawness::Ref, mutability: Mutability::Mut },
@@ -902,7 +901,7 @@ impl ExprCollector<'_> {
             Expr::Call { callee: try_branch, args: Box::new([operand]), is_assignee_expr: false },
             syntax_ptr,
         );
-        let continue_name = Name::generate_new_name(self.body.bindings.len());
+        let continue_name = Name::generate_new_name();
         let continue_binding =
             self.alloc_binding(continue_name.clone(), BindingAnnotation::Unannotated);
         let continue_bpat =
@@ -917,7 +916,7 @@ impl ExprCollector<'_> {
             guard: None,
             expr: self.alloc_expr(Expr::Path(Path::from(continue_name)), syntax_ptr),
         };
-        let break_name = Name::generate_new_name(self.body.bindings.len());
+        let break_name = Name::generate_new_name();
         let break_binding = self.alloc_binding(break_name.clone(), BindingAnnotation::Unannotated);
         let break_bpat = self.alloc_pat_desugared(Pat::Bind { id: break_binding, subpat: None });
         self.add_definition_to_binding(break_binding, break_bpat);
diff --git a/crates/hir-def/src/data/adt.rs b/crates/hir-def/src/data/adt.rs
index 5790e600f6..f07b125766 100644
--- a/crates/hir-def/src/data/adt.rs
+++ b/crates/hir-def/src/data/adt.rs
@@ -400,7 +400,7 @@ pub(crate) fn lower_struct(
     item_tree: &ItemTree,
     fields: &Fields,
 ) -> StructKind {
-    let ctx = LowerCtx::new(db, ast.file_id);
+    let ctx = LowerCtx::with_file_id(db, ast.file_id);
 
     match (&ast.value, fields) {
         (ast::StructKind::Tuple(fl), Fields::Tuple(fields)) => {
@@ -415,9 +415,7 @@ pub(crate) fn lower_struct(
                     || FieldData {
                         name: Name::new_tuple_field(i),
                         type_ref: Interned::new(TypeRef::from_ast_opt(&ctx, fd.ty())),
-                        visibility: RawVisibility::from_ast(db, fd.visibility(), &mut |range| {
-                            ctx.span_map().span_for_range(range).ctx
-                        }),
+                        visibility: RawVisibility::from_ast(db, ast.with_value(fd.visibility())),
                     },
                 );
             }
@@ -435,9 +433,7 @@ pub(crate) fn lower_struct(
                     || FieldData {
                         name: fd.name().map(|n| n.as_name()).unwrap_or_else(Name::missing),
                         type_ref: Interned::new(TypeRef::from_ast_opt(&ctx, fd.ty())),
-                        visibility: RawVisibility::from_ast(db, fd.visibility(), &mut |range| {
-                            ctx.span_map().span_for_range(range).ctx
-                        }),
+                        visibility: RawVisibility::from_ast(db, ast.with_value(fd.visibility())),
                     },
                 );
             }
diff --git a/crates/hir-def/src/db.rs b/crates/hir-def/src/db.rs
index 544ed6bc34..68f57600ec 100644
--- a/crates/hir-def/src/db.rs
+++ b/crates/hir-def/src/db.rs
@@ -87,11 +87,15 @@ pub trait DefDatabase: InternDatabase + ExpandDatabase + Upcast<dyn ExpandDataba
     fn file_item_tree(&self, file_id: HirFileId) -> Arc<ItemTree>;
 
     #[salsa::invoke(ItemTree::block_item_tree_query)]
-    fn block_item_tree(&self, block_id: BlockId) -> Arc<ItemTree>;
+    fn block_item_tree_query(&self, block_id: BlockId) -> Arc<ItemTree>;
 
-    #[salsa::invoke(DefMap::crate_def_map_query)]
+    #[salsa::invoke(crate_def_map_wait)]
+    #[salsa::transparent]
     fn crate_def_map(&self, krate: CrateId) -> Arc<DefMap>;
 
+    #[salsa::invoke(DefMap::crate_def_map_query)]
+    fn crate_def_map_query(&self, krate: CrateId) -> Arc<DefMap>;
+
     /// Computes the block-level `DefMap`.
     #[salsa::invoke(DefMap::block_def_map_query)]
     fn block_def_map(&self, block: BlockId) -> Arc<DefMap>;
@@ -249,6 +253,11 @@ fn include_macro_invoc(db: &dyn DefDatabase, krate: CrateId) -> Vec<(MacroCallId
         .collect()
 }
 
+fn crate_def_map_wait(db: &dyn DefDatabase, krate: CrateId) -> Arc<DefMap> {
+    let _p = tracing::span!(tracing::Level::INFO, "crate_def_map:wait").entered();
+    db.crate_def_map_query(krate)
+}
+
 fn crate_supports_no_std(db: &dyn DefDatabase, crate_id: CrateId) -> bool {
     let file = db.crate_graph()[crate_id].root_file_id;
     let item_tree = db.file_item_tree(file.into());
diff --git a/crates/hir-def/src/expander.rs b/crates/hir-def/src/expander.rs
index b0872fcdc0..b99df1ed59 100644
--- a/crates/hir-def/src/expander.rs
+++ b/crates/hir-def/src/expander.rs
@@ -1,7 +1,5 @@
 //! Macro expansion utilities.
 
-use std::cell::OnceCell;
-
 use base_db::CrateId;
 use cfg::CfgOptions;
 use drop_bomb::DropBomb;
@@ -20,7 +18,7 @@ use crate::{
 #[derive(Debug)]
 pub struct Expander {
     cfg_options: CfgOptions,
-    span_map: OnceCell<SpanMap>,
+    span_map: SpanMap,
     krate: CrateId,
     current_file_id: HirFileId,
     pub(crate) module: ModuleId,
@@ -44,7 +42,7 @@ impl Expander {
             recursion_depth: 0,
             recursion_limit,
             cfg_options: db.crate_graph()[module.krate].cfg_options.clone(),
-            span_map: OnceCell::new(),
+            span_map: db.span_map(current_file_id),
             krate: module.krate,
         }
     }
@@ -102,7 +100,7 @@ impl Expander {
     }
 
     pub fn ctx<'a>(&self, db: &'a dyn DefDatabase) -> LowerCtx<'a> {
-        LowerCtx::with_span_map_cell(db, self.current_file_id, self.span_map.clone())
+        LowerCtx::new(db, self.span_map.clone(), self.current_file_id)
     }
 
     pub(crate) fn in_file<T>(&self, value: T) -> InFile<T> {
@@ -110,15 +108,7 @@ impl Expander {
     }
 
     pub(crate) fn parse_attrs(&self, db: &dyn DefDatabase, owner: &dyn ast::HasAttrs) -> Attrs {
-        Attrs::filter(
-            db,
-            self.krate,
-            RawAttrs::new(
-                db.upcast(),
-                owner,
-                self.span_map.get_or_init(|| db.span_map(self.current_file_id)).as_ref(),
-            ),
-        )
+        Attrs::filter(db, self.krate, RawAttrs::new(db.upcast(), owner, self.span_map.as_ref()))
     }
 
     pub(crate) fn cfg_options(&self) -> &CfgOptions {
@@ -130,7 +120,7 @@ impl Expander {
     }
 
     pub(crate) fn parse_path(&mut self, db: &dyn DefDatabase, path: ast::Path) -> Option<Path> {
-        let ctx = LowerCtx::with_span_map_cell(db, self.current_file_id, self.span_map.clone());
+        let ctx = LowerCtx::new(db, self.span_map.clone(), self.current_file_id);
         Path::from_src(&ctx, path)
     }
 
@@ -175,11 +165,10 @@ impl Expander {
                     let parse = res.value.0.cast::<T>()?;
 
                     self.recursion_depth += 1;
-                    let old_span_map = OnceCell::new();
-                    if let Some(prev) = self.span_map.take() {
-                        _ = old_span_map.set(prev);
-                    };
-                    _ = self.span_map.set(SpanMap::ExpansionSpanMap(res.value.1));
+                    let old_span_map = std::mem::replace(
+                        &mut self.span_map,
+                        SpanMap::ExpansionSpanMap(res.value.1),
+                    );
                     let old_file_id =
                         std::mem::replace(&mut self.current_file_id, macro_file.into());
                     let mark = Mark {
@@ -198,6 +187,6 @@ impl Expander {
 #[derive(Debug)]
 pub struct Mark {
     file_id: HirFileId,
-    span_map: OnceCell<SpanMap>,
+    span_map: SpanMap,
     bomb: DropBomb,
 }
diff --git a/crates/hir-def/src/find_path.rs b/crates/hir-def/src/find_path.rs
index 0cd4a5db8c..26247ba5b5 100644
--- a/crates/hir-def/src/find_path.rs
+++ b/crates/hir-def/src/find_path.rs
@@ -611,10 +611,8 @@ mod tests {
         let parsed_path_file = syntax::SourceFile::parse(&format!("use {path};"));
         let ast_path =
             parsed_path_file.syntax_node().descendants().find_map(syntax::ast::Path::cast).unwrap();
-        let mod_path = ModPath::from_src(&db, ast_path, &mut |range| {
-            db.span_map(pos.file_id.into()).as_ref().span_for_range(range).ctx
-        })
-        .unwrap();
+        let mod_path =
+            ModPath::from_src(&db, ast_path, db.span_map(pos.file_id.into()).as_ref()).unwrap();
 
         let def_map = module.def_map(&db);
         let resolved = def_map
diff --git a/crates/hir-def/src/hir/type_ref.rs b/crates/hir-def/src/hir/type_ref.rs
index ec207a7f96..8db00f9d76 100644
--- a/crates/hir-def/src/hir/type_ref.rs
+++ b/crates/hir-def/src/hir/type_ref.rs
@@ -251,7 +251,7 @@ impl TypeRef {
                 TypeRef::DynTrait(type_bounds_from_ast(ctx, inner.type_bound_list()))
             }
             ast::Type::MacroType(mt) => match mt.macro_call() {
-                Some(mc) => TypeRef::Macro(ctx.ast_id(&mc)),
+                Some(mc) => ctx.ast_id(&mc).map(TypeRef::Macro).unwrap_or(TypeRef::Error),
                 None => TypeRef::Error,
             },
         }
@@ -398,8 +398,9 @@ pub enum ConstRef {
 impl ConstRef {
     pub(crate) fn from_const_arg(lower_ctx: &LowerCtx<'_>, arg: Option<ast::ConstArg>) -> Self {
         if let Some(arg) = arg {
+            let ast_id = lower_ctx.ast_id(&arg);
             if let Some(expr) = arg.expr() {
-                return Self::from_expr(expr, Some(lower_ctx.ast_id(&arg)));
+                return Self::from_expr(expr, ast_id);
             }
         }
         Self::Scalar(LiteralConstRef::Unknown)
diff --git a/crates/hir-def/src/item_tree.rs b/crates/hir-def/src/item_tree.rs
index bd3d377ec0..008fa7ef13 100644
--- a/crates/hir-def/src/item_tree.rs
+++ b/crates/hir-def/src/item_tree.rs
@@ -29,6 +29,9 @@
 //!
 //! In general, any item in the `ItemTree` stores its `AstId`, which allows mapping it back to its
 //! surface syntax.
+//!
+//! Note that we cannot store [`span::Span`]s inside of this, as typing in an item invalidates its
+//! encompassing span!
 
 mod lower;
 mod pretty;
@@ -389,7 +392,7 @@ impl TreeId {
 
     pub(crate) fn item_tree(&self, db: &dyn DefDatabase) -> Arc<ItemTree> {
         match self.block {
-            Some(block) => db.block_item_tree(block),
+            Some(block) => db.block_item_tree_query(block),
             None => db.file_item_tree(self.file),
         }
     }
@@ -821,13 +824,11 @@ impl Use {
         // Note: The AST unwraps are fine, since if they fail we should have never obtained `index`.
         let ast = InFile::new(file_id, self.ast_id).to_node(db.upcast());
         let ast_use_tree = ast.use_tree().expect("missing `use_tree`");
-        let (_, source_map) = lower::lower_use_tree(db, ast_use_tree, &mut |range| {
-            db.span_map(file_id).span_for_range(range).ctx
-        })
-        .expect("failed to lower use tree");
+        let span_map = db.span_map(file_id);
+        let (_, source_map) = lower::lower_use_tree(db, span_map.as_ref(), ast_use_tree)
+            .expect("failed to lower use tree");
         source_map[index].clone()
     }
-
     /// Maps a `UseTree` contained in this import back to its AST node.
     pub fn use_tree_source_map(
         &self,
@@ -838,11 +839,10 @@ impl Use {
         // Note: The AST unwraps are fine, since if they fail we should have never obtained `index`.
         let ast = InFile::new(file_id, self.ast_id).to_node(db.upcast());
         let ast_use_tree = ast.use_tree().expect("missing `use_tree`");
-        lower::lower_use_tree(db, ast_use_tree, &mut |range| {
-            db.span_map(file_id).span_for_range(range).ctx
-        })
-        .expect("failed to lower use tree")
-        .1
+        let span_map = db.span_map(file_id);
+        lower::lower_use_tree(db, span_map.as_ref(), ast_use_tree)
+            .expect("failed to lower use tree")
+            .1
     }
 }
 
diff --git a/crates/hir-def/src/item_tree/lower.rs b/crates/hir-def/src/item_tree/lower.rs
index bf3d54f4ca..21cffafa95 100644
--- a/crates/hir-def/src/item_tree/lower.rs
+++ b/crates/hir-def/src/item_tree/lower.rs
@@ -4,7 +4,7 @@ use std::collections::hash_map::Entry;
 
 use hir_expand::{mod_path::path, name, name::AsName, span_map::SpanMapRef, HirFileId};
 use la_arena::Arena;
-use span::{AstIdMap, SyntaxContextId};
+use span::AstIdMap;
 use syntax::{
     ast::{self, HasModuleItem, HasName, HasTypeBounds, IsString},
     AstNode,
@@ -45,7 +45,7 @@ impl<'a> Ctx<'a> {
             db,
             tree: ItemTree::default(),
             source_ast_id_map: db.ast_id_map(file),
-            body_ctx: crate::lower::LowerCtx::new(db, file),
+            body_ctx: crate::lower::LowerCtx::with_file_id(db, file),
         }
     }
 
@@ -535,9 +535,7 @@ impl<'a> Ctx<'a> {
     fn lower_use(&mut self, use_item: &ast::Use) -> Option<FileItemTreeId<Use>> {
         let visibility = self.lower_visibility(use_item);
         let ast_id = self.source_ast_id_map.ast_id(use_item);
-        let (use_tree, _) = lower_use_tree(self.db, use_item.use_tree()?, &mut |range| {
-            self.span_map().span_for_range(range).ctx
-        })?;
+        let (use_tree, _) = lower_use_tree(self.db, self.span_map(), use_item.use_tree()?)?;
 
         let res = Use { visibility, ast_id, use_tree };
         Some(id(self.data().uses.alloc(res)))
@@ -560,9 +558,7 @@ impl<'a> Ctx<'a> {
 
     fn lower_macro_call(&mut self, m: &ast::MacroCall) -> Option<FileItemTreeId<MacroCall>> {
         let span_map = self.span_map();
-        let path = Interned::new(ModPath::from_src(self.db.upcast(), m.path()?, &mut |range| {
-            span_map.span_for_range(range).ctx
-        })?);
+        let path = Interned::new(ModPath::from_src(self.db.upcast(), m.path()?, span_map)?);
         let ast_id = self.source_ast_id_map.ast_id(m);
         let expand_to = hir_expand::ExpandTo::from_call_site(m);
         let res = MacroCall {
@@ -676,9 +672,8 @@ impl<'a> Ctx<'a> {
     }
 
     fn lower_visibility(&mut self, item: &dyn ast::HasVisibility) -> RawVisibilityId {
-        let vis = RawVisibility::from_ast(self.db, item.visibility(), &mut |range| {
-            self.span_map().span_for_range(range).ctx
-        });
+        let vis =
+            RawVisibility::from_opt_ast_with_span_map(self.db, item.visibility(), self.span_map());
         self.data().vis.alloc(vis)
     }
 
@@ -750,15 +745,12 @@ fn lower_abi(abi: ast::Abi) -> Interned<str> {
 
 struct UseTreeLowering<'a> {
     db: &'a dyn DefDatabase,
+    span_map: SpanMapRef<'a>,
     mapping: Arena<ast::UseTree>,
 }
 
 impl UseTreeLowering<'_> {
-    fn lower_use_tree(
-        &mut self,
-        tree: ast::UseTree,
-        span_for_range: &mut dyn FnMut(::tt::TextRange) -> SyntaxContextId,
-    ) -> Option<UseTree> {
+    fn lower_use_tree(&mut self, tree: ast::UseTree) -> Option<UseTree> {
         if let Some(use_tree_list) = tree.use_tree_list() {
             let prefix = match tree.path() {
                 // E.g. use something::{{{inner}}};
@@ -766,17 +758,15 @@ impl UseTreeLowering<'_> {
                 // E.g. `use something::{inner}` (prefix is `None`, path is `something`)
                 // or `use something::{path::{inner::{innerer}}}` (prefix is `something::path`, path is `inner`)
                 Some(path) => {
-                    match ModPath::from_src(self.db.upcast(), path, span_for_range) {
+                    match ModPath::from_src(self.db.upcast(), path, self.span_map) {
                         Some(it) => Some(it),
                         None => return None, // FIXME: report errors somewhere
                     }
                 }
             };
 
-            let list = use_tree_list
-                .use_trees()
-                .filter_map(|tree| self.lower_use_tree(tree, span_for_range))
-                .collect();
+            let list =
+                use_tree_list.use_trees().filter_map(|tree| self.lower_use_tree(tree)).collect();
 
             Some(
                 self.use_tree(
@@ -787,7 +777,7 @@ impl UseTreeLowering<'_> {
         } else {
             let is_glob = tree.star_token().is_some();
             let path = match tree.path() {
-                Some(path) => Some(ModPath::from_src(self.db.upcast(), path, span_for_range)?),
+                Some(path) => Some(ModPath::from_src(self.db.upcast(), path, self.span_map)?),
                 None => None,
             };
             let alias = tree.rename().map(|a| {
@@ -823,10 +813,10 @@ impl UseTreeLowering<'_> {
 
 pub(crate) fn lower_use_tree(
     db: &dyn DefDatabase,
+    span_map: SpanMapRef<'_>,
     tree: ast::UseTree,
-    span_for_range: &mut dyn FnMut(::tt::TextRange) -> SyntaxContextId,
 ) -> Option<(UseTree, Arena<ast::UseTree>)> {
-    let mut lowering = UseTreeLowering { db, mapping: Arena::new() };
-    let tree = lowering.lower_use_tree(tree, span_for_range)?;
+    let mut lowering = UseTreeLowering { db, span_map, mapping: Arena::new() };
+    let tree = lowering.lower_use_tree(tree)?;
     Some((tree, lowering.mapping))
 }
diff --git a/crates/hir-def/src/lib.rs b/crates/hir-def/src/lib.rs
index d63f2268aa..de3ab57a12 100644
--- a/crates/hir-def/src/lib.rs
+++ b/crates/hir-def/src/lib.rs
@@ -1341,11 +1341,8 @@ impl AsMacroCall for InFile<&ast::MacroCall> {
         let expands_to = hir_expand::ExpandTo::from_call_site(self.value);
         let ast_id = AstId::new(self.file_id, db.ast_id_map(self.file_id).ast_id(self.value));
         let span_map = db.span_map(self.file_id);
-        let path = self.value.path().and_then(|path| {
-            path::ModPath::from_src(db, path, &mut |range| {
-                span_map.as_ref().span_for_range(range).ctx
-            })
-        });
+        let path =
+            self.value.path().and_then(|path| path::ModPath::from_src(db, path, span_map.as_ref()));
 
         let Some(path) = path else {
             return Ok(ExpandResult::only_err(ExpandError::other("malformed macro invocation")));
diff --git a/crates/hir-def/src/lower.rs b/crates/hir-def/src/lower.rs
index d574d80a8e..2fa6acdf17 100644
--- a/crates/hir-def/src/lower.rs
+++ b/crates/hir-def/src/lower.rs
@@ -13,36 +13,39 @@ use crate::{db::DefDatabase, path::Path};
 
 pub struct LowerCtx<'a> {
     pub db: &'a dyn DefDatabase,
-    file_id: HirFileId,
-    span_map: OnceCell<SpanMap>,
-    ast_id_map: OnceCell<Arc<AstIdMap>>,
+    span_map: SpanMap,
+    // FIXME: This optimization is probably pointless, ast id map should pretty much always exist anyways.
+    ast_id_map: Option<(HirFileId, OnceCell<Arc<AstIdMap>>)>,
 }
 
 impl<'a> LowerCtx<'a> {
-    pub fn new(db: &'a dyn DefDatabase, file_id: HirFileId) -> Self {
-        LowerCtx { db, file_id, span_map: OnceCell::new(), ast_id_map: OnceCell::new() }
+    pub fn new(db: &'a dyn DefDatabase, span_map: SpanMap, file_id: HirFileId) -> Self {
+        LowerCtx { db, span_map, ast_id_map: Some((file_id, OnceCell::new())) }
     }
 
-    pub fn with_span_map_cell(
-        db: &'a dyn DefDatabase,
-        file_id: HirFileId,
-        span_map: OnceCell<SpanMap>,
-    ) -> Self {
-        LowerCtx { db, file_id, span_map, ast_id_map: OnceCell::new() }
+    pub fn with_file_id(db: &'a dyn DefDatabase, file_id: HirFileId) -> Self {
+        LowerCtx {
+            db,
+            span_map: db.span_map(file_id),
+            ast_id_map: Some((file_id, OnceCell::new())),
+        }
+    }
+
+    pub fn with_span_map(db: &'a dyn DefDatabase, span_map: SpanMap) -> Self {
+        LowerCtx { db, span_map, ast_id_map: None }
     }
 
     pub(crate) fn span_map(&self) -> SpanMapRef<'_> {
-        self.span_map.get_or_init(|| self.db.span_map(self.file_id)).as_ref()
+        self.span_map.as_ref()
     }
 
     pub(crate) fn lower_path(&self, ast: ast::Path) -> Option<Path> {
         Path::from_src(self, ast)
     }
 
-    pub(crate) fn ast_id<N: AstIdNode>(&self, item: &N) -> AstId<N> {
-        InFile::new(
-            self.file_id,
-            self.ast_id_map.get_or_init(|| self.db.ast_id_map(self.file_id)).ast_id(item),
-        )
+    pub(crate) fn ast_id<N: AstIdNode>(&self, item: &N) -> Option<AstId<N>> {
+        let &(file_id, ref ast_id_map) = self.ast_id_map.as_ref()?;
+        let ast_id_map = ast_id_map.get_or_init(|| self.db.ast_id_map(file_id));
+        Some(InFile::new(file_id, ast_id_map.ast_id(item)))
     }
 }
diff --git a/crates/hir-def/src/visibility.rs b/crates/hir-def/src/visibility.rs
index 1ef8fa772a..0f3fac1cec 100644
--- a/crates/hir-def/src/visibility.rs
+++ b/crates/hir-def/src/visibility.rs
@@ -2,8 +2,8 @@
 
 use std::iter;
 
+use hir_expand::{span_map::SpanMapRef, InFile};
 use la_arena::ArenaMap;
-use span::SyntaxContextId;
 use syntax::ast;
 use triomphe::Arc;
 
@@ -34,25 +34,36 @@ impl RawVisibility {
     }
 
     pub(crate) fn from_ast(
+        db: &dyn DefDatabase,
+        node: InFile<Option<ast::Visibility>>,
+    ) -> RawVisibility {
+        let node = match node.transpose() {
+            None => return RawVisibility::private(),
+            Some(node) => node,
+        };
+        Self::from_ast_with_span_map(db, node.value, db.span_map(node.file_id).as_ref())
+    }
+
+    pub(crate) fn from_opt_ast_with_span_map(
         db: &dyn DefDatabase,
         node: Option<ast::Visibility>,
-        span_for_range: &mut dyn FnMut(::tt::TextRange) -> SyntaxContextId,
+        span_map: SpanMapRef<'_>,
     ) -> RawVisibility {
         let node = match node {
             None => return RawVisibility::private(),
             Some(node) => node,
         };
-        Self::from_ast_with_span_map(db, node, span_for_range)
+        Self::from_ast_with_span_map(db, node, span_map)
     }
 
     fn from_ast_with_span_map(
         db: &dyn DefDatabase,
         node: ast::Visibility,
-        span_for_range: &mut dyn FnMut(::tt::TextRange) -> SyntaxContextId,
+        span_map: SpanMapRef<'_>,
     ) -> RawVisibility {
         let path = match node.kind() {
             ast::VisibilityKind::In(path) => {
-                let path = ModPath::from_src(db.upcast(), path, span_for_range);
+                let path = ModPath::from_src(db.upcast(), path, span_map);
                 match path {
                     None => return RawVisibility::private(),
                     Some(path) => path,
diff --git a/crates/hir-expand/src/attrs.rs b/crates/hir-expand/src/attrs.rs
index 7793e99532..1c92dea38e 100644
--- a/crates/hir-expand/src/attrs.rs
+++ b/crates/hir-expand/src/attrs.rs
@@ -90,7 +90,7 @@ impl RawAttrs {
     }
 
     /// Processes `cfg_attr`s, returning the resulting semantic `Attrs`.
-    // FIXME: This should return a different type, signaling it was filtered?
+    // FIXME: This should return a different type
     pub fn filter(self, db: &dyn ExpandDatabase, krate: CrateId) -> RawAttrs {
         let has_cfg_attrs = self
             .iter()
@@ -201,9 +201,7 @@ impl Attr {
         span_map: SpanMapRef<'_>,
         id: AttrId,
     ) -> Option<Attr> {
-        let path = Interned::new(ModPath::from_src(db, ast.path()?, &mut |range| {
-            span_map.span_for_range(range).ctx
-        })?);
+        let path = Interned::new(ModPath::from_src(db, ast.path()?, span_map)?);
         let span = span_map.span_for_range(ast.syntax().text_range());
         let input = if let Some(ast::Expr::Literal(lit)) = ast.expr() {
             let value = match lit.kind() {
diff --git a/crates/hir-expand/src/builtin_derive_macro.rs b/crates/hir-expand/src/builtin_derive_macro.rs
index 66dec7d89e..f36f48baae 100644
--- a/crates/hir-expand/src/builtin_derive_macro.rs
+++ b/crates/hir-expand/src/builtin_derive_macro.rs
@@ -10,12 +10,10 @@ use crate::{
     hygiene::span_with_def_site_ctxt,
     name::{AsName, Name},
     quote::dollar_crate,
-    span_map::ExpansionSpanMap,
+    span_map::SpanMapRef,
     tt,
 };
-use syntax::ast::{
-    self, AstNode, FieldList, HasAttrs, HasGenericParams, HasModuleItem, HasName, HasTypeBounds,
-};
+use syntax::ast::{self, AstNode, FieldList, HasAttrs, HasGenericParams, HasName, HasTypeBounds};
 
 use crate::{db::ExpandDatabase, name, quote, ExpandError, ExpandResult};
 
@@ -27,7 +25,7 @@ macro_rules! register_builtin {
         }
 
         impl BuiltinDeriveExpander {
-            pub fn expander(&self) -> fn(Span, &tt::Subtree) -> ExpandResult<tt::Subtree>  {
+            pub fn expander(&self) -> fn(Span, &ast::Adt, SpanMapRef<'_>) -> ExpandResult<tt::Subtree>  {
                 match *self {
                     $( BuiltinDeriveExpander::$trait => $expand, )*
                 }
@@ -49,11 +47,12 @@ impl BuiltinDeriveExpander {
         &self,
         db: &dyn ExpandDatabase,
         id: MacroCallId,
-        tt: &tt::Subtree,
+        tt: &ast::Adt,
+        token_map: SpanMapRef<'_>,
     ) -> ExpandResult<tt::Subtree> {
         let span = db.lookup_intern_macro_call(id).call_site;
         let span = span_with_def_site_ctxt(db, span, id);
-        self.expander()(span, tt)
+        self.expander()(span, tt, token_map)
     }
 }
 
@@ -127,7 +126,7 @@ impl VariantShape {
         }
     }
 
-    fn from(tm: &ExpansionSpanMap, value: Option<FieldList>) -> Result<Self, ExpandError> {
+    fn from(tm: SpanMapRef<'_>, value: Option<FieldList>) -> Result<Self, ExpandError> {
         let r = match value {
             None => VariantShape::Unit,
             Some(FieldList::RecordFieldList(it)) => VariantShape::Struct(
@@ -203,13 +202,11 @@ struct BasicAdtInfo {
     associated_types: Vec<tt::Subtree>,
 }
 
-fn parse_adt(tt: &tt::Subtree, call_site: Span) -> Result<BasicAdtInfo, ExpandError> {
-    let (parsed, tm) = &mbe::token_tree_to_syntax_node(tt, mbe::TopEntryPoint::MacroItems);
-    let macro_items = ast::MacroItems::cast(parsed.syntax_node())
-        .ok_or_else(|| ExpandError::other("invalid item definition"))?;
-    let item = macro_items.items().next().ok_or_else(|| ExpandError::other("no item found"))?;
-    let adt = &ast::Adt::cast(item.syntax().clone())
-        .ok_or_else(|| ExpandError::other("expected struct, enum or union"))?;
+fn parse_adt(
+    tm: SpanMapRef<'_>,
+    adt: &ast::Adt,
+    call_site: Span,
+) -> Result<BasicAdtInfo, ExpandError> {
     let (name, generic_param_list, where_clause, shape) = match adt {
         ast::Adt::Struct(it) => (
             it.name(),
@@ -325,14 +322,14 @@ fn parse_adt(tt: &tt::Subtree, call_site: Span) -> Result<BasicAdtInfo, ExpandEr
 }
 
 fn name_to_token(
-    token_map: &ExpansionSpanMap,
+    token_map: SpanMapRef<'_>,
     name: Option<ast::Name>,
 ) -> Result<tt::Ident, ExpandError> {
     let name = name.ok_or_else(|| {
         debug!("parsed item has no name");
         ExpandError::other("missing name")
     })?;
-    let span = token_map.span_at(name.syntax().text_range().start());
+    let span = token_map.span_for_range(name.syntax().text_range());
     let name_token = tt::Ident { span, text: name.text().into() };
     Ok(name_token)
 }
@@ -369,12 +366,14 @@ fn name_to_token(
 /// where B1, ..., BN are the bounds given by `bounds_paths`. Z is a phantom type, and
 /// therefore does not get bound by the derived trait.
 fn expand_simple_derive(
+    // FIXME: use
     invoc_span: Span,
-    tt: &tt::Subtree,
+    tt: &ast::Adt,
+    tm: SpanMapRef<'_>,
     trait_path: tt::Subtree,
     make_trait_body: impl FnOnce(&BasicAdtInfo) -> tt::Subtree,
 ) -> ExpandResult<tt::Subtree> {
-    let info = match parse_adt(tt, invoc_span) {
+    let info = match parse_adt(tm, tt, invoc_span) {
         Ok(info) => info,
         Err(e) => {
             return ExpandResult::new(
@@ -417,14 +416,14 @@ fn expand_simple_derive(
     ExpandResult::ok(expanded)
 }
 
-fn copy_expand(span: Span, tt: &tt::Subtree) -> ExpandResult<tt::Subtree> {
+fn copy_expand(span: Span, tt: &ast::Adt, tm: SpanMapRef<'_>) -> ExpandResult<tt::Subtree> {
     let krate = dollar_crate(span);
-    expand_simple_derive(span, tt, quote! {span => #krate::marker::Copy }, |_| quote! {span =>})
+    expand_simple_derive(span, tt, tm, quote! {span => #krate::marker::Copy }, |_| quote! {span =>})
 }
 
-fn clone_expand(span: Span, tt: &tt::Subtree) -> ExpandResult<tt::Subtree> {
+fn clone_expand(span: Span, tt: &ast::Adt, tm: SpanMapRef<'_>) -> ExpandResult<tt::Subtree> {
     let krate = dollar_crate(span);
-    expand_simple_derive(span, tt, quote! {span => #krate::clone::Clone }, |adt| {
+    expand_simple_derive(span, tt, tm, quote! {span => #krate::clone::Clone }, |adt| {
         if matches!(adt.shape, AdtShape::Union) {
             let star = tt::Punct { char: '*', spacing: ::tt::Spacing::Alone, span };
             return quote! {span =>
@@ -473,9 +472,9 @@ fn and_and(span: Span) -> tt::Subtree {
     quote! {span => #and& }
 }
 
-fn default_expand(span: Span, tt: &tt::Subtree) -> ExpandResult<tt::Subtree> {
+fn default_expand(span: Span, tt: &ast::Adt, tm: SpanMapRef<'_>) -> ExpandResult<tt::Subtree> {
     let krate = &dollar_crate(span);
-    expand_simple_derive(span, tt, quote! {span => #krate::default::Default }, |adt| {
+    expand_simple_derive(span, tt, tm, quote! {span => #krate::default::Default }, |adt| {
         let body = match &adt.shape {
             AdtShape::Struct(fields) => {
                 let name = &adt.name;
@@ -512,9 +511,9 @@ fn default_expand(span: Span, tt: &tt::Subtree) -> ExpandResult<tt::Subtree> {
     })
 }
 
-fn debug_expand(span: Span, tt: &tt::Subtree) -> ExpandResult<tt::Subtree> {
+fn debug_expand(span: Span, tt: &ast::Adt, tm: SpanMapRef<'_>) -> ExpandResult<tt::Subtree> {
     let krate = &dollar_crate(span);
-    expand_simple_derive(span, tt, quote! {span => #krate::fmt::Debug }, |adt| {
+    expand_simple_derive(span, tt, tm, quote! {span => #krate::fmt::Debug }, |adt| {
         let for_variant = |name: String, v: &VariantShape| match v {
             VariantShape::Struct(fields) => {
                 let for_fields = fields.iter().map(|it| {
@@ -584,9 +583,9 @@ fn debug_expand(span: Span, tt: &tt::Subtree) -> ExpandResult<tt::Subtree> {
     })
 }
 
-fn hash_expand(span: Span, tt: &tt::Subtree) -> ExpandResult<tt::Subtree> {
+fn hash_expand(span: Span, tt: &ast::Adt, tm: SpanMapRef<'_>) -> ExpandResult<tt::Subtree> {
     let krate = &dollar_crate(span);
-    expand_simple_derive(span, tt, quote! {span => #krate::hash::Hash }, |adt| {
+    expand_simple_derive(span, tt, tm, quote! {span => #krate::hash::Hash }, |adt| {
         if matches!(adt.shape, AdtShape::Union) {
             // FIXME: Return expand error here
             return quote! {span =>};
@@ -631,14 +630,14 @@ fn hash_expand(span: Span, tt: &tt::Subtree) -> ExpandResult<tt::Subtree> {
     })
 }
 
-fn eq_expand(span: Span, tt: &tt::Subtree) -> ExpandResult<tt::Subtree> {
+fn eq_expand(span: Span, tt: &ast::Adt, tm: SpanMapRef<'_>) -> ExpandResult<tt::Subtree> {
     let krate = dollar_crate(span);
-    expand_simple_derive(span, tt, quote! {span => #krate::cmp::Eq }, |_| quote! {span =>})
+    expand_simple_derive(span, tt, tm, quote! {span => #krate::cmp::Eq }, |_| quote! {span =>})
 }
 
-fn partial_eq_expand(span: Span, tt: &tt::Subtree) -> ExpandResult<tt::Subtree> {
+fn partial_eq_expand(span: Span, tt: &ast::Adt, tm: SpanMapRef<'_>) -> ExpandResult<tt::Subtree> {
     let krate = dollar_crate(span);
-    expand_simple_derive(span, tt, quote! {span => #krate::cmp::PartialEq }, |adt| {
+    expand_simple_derive(span, tt, tm, quote! {span => #krate::cmp::PartialEq }, |adt| {
         if matches!(adt.shape, AdtShape::Union) {
             // FIXME: Return expand error here
             return quote! {span =>};
@@ -708,9 +707,9 @@ fn self_and_other_patterns(
     (self_patterns, other_patterns)
 }
 
-fn ord_expand(span: Span, tt: &tt::Subtree) -> ExpandResult<tt::Subtree> {
+fn ord_expand(span: Span, tt: &ast::Adt, tm: SpanMapRef<'_>) -> ExpandResult<tt::Subtree> {
     let krate = &dollar_crate(span);
-    expand_simple_derive(span, tt, quote! {span => #krate::cmp::Ord }, |adt| {
+    expand_simple_derive(span, tt, tm, quote! {span => #krate::cmp::Ord }, |adt| {
         fn compare(
             krate: &tt::Ident,
             left: tt::Subtree,
@@ -766,9 +765,9 @@ fn ord_expand(span: Span, tt: &tt::Subtree) -> ExpandResult<tt::Subtree> {
     })
 }
 
-fn partial_ord_expand(span: Span, tt: &tt::Subtree) -> ExpandResult<tt::Subtree> {
+fn partial_ord_expand(span: Span, tt: &ast::Adt, tm: SpanMapRef<'_>) -> ExpandResult<tt::Subtree> {
     let krate = &dollar_crate(span);
-    expand_simple_derive(span, tt, quote! {span => #krate::cmp::PartialOrd }, |adt| {
+    expand_simple_derive(span, tt, tm, quote! {span => #krate::cmp::PartialOrd }, |adt| {
         fn compare(
             krate: &tt::Ident,
             left: tt::Subtree,
diff --git a/crates/hir-expand/src/db.rs b/crates/hir-expand/src/db.rs
index 6f69ee15ac..3a65bc7c6e 100644
--- a/crates/hir-expand/src/db.rs
+++ b/crates/hir-expand/src/db.rs
@@ -215,6 +215,11 @@ pub fn expand_speculative(
         MacroDefKind::BuiltInAttr(BuiltinAttrExpander::Derive, _) => {
             pseudo_derive_attr_expansion(&tt, attr_arg.as_ref()?, loc.call_site)
         }
+        MacroDefKind::BuiltInDerive(expander, ..) => {
+            // this cast is a bit sus, can we avoid losing the typedness here?
+            let adt = ast::Adt::cast(speculative_args.clone()).unwrap();
+            expander.expand(db, actual_macro_call, &adt, span_map)
+        }
         MacroDefKind::Declarative(it) => db.decl_macro_expander(loc.krate, it).expand_unhygienic(
             db,
             tt,
@@ -222,9 +227,6 @@ pub fn expand_speculative(
             loc.call_site,
         ),
         MacroDefKind::BuiltIn(it, _) => it.expand(db, actual_macro_call, &tt).map_err(Into::into),
-        MacroDefKind::BuiltInDerive(it, ..) => {
-            it.expand(db, actual_macro_call, &tt).map_err(Into::into)
-        }
         MacroDefKind::BuiltInEager(it, _) => {
             it.expand(db, actual_macro_call, &tt).map_err(Into::into)
         }
@@ -319,7 +321,6 @@ pub(crate) fn parse_with_map(
     }
 }
 
-// FIXME: for derive attributes, this will return separate copies of the same structures!
 fn macro_arg(
     db: &dyn ExpandDatabase,
     id: MacroCallId,
@@ -525,6 +526,16 @@ fn macro_expand(
 
     let ExpandResult { value: tt, mut err } = match loc.def.kind {
         MacroDefKind::ProcMacro(..) => return db.expand_proc_macro(macro_call_id).map(CowArc::Arc),
+        MacroDefKind::BuiltInDerive(expander, ..) => {
+            let (root, map) = parse_with_map(db, loc.kind.file_id());
+            let root = root.syntax_node();
+            let MacroCallKind::Derive { ast_id, .. } = loc.kind else { unreachable!() };
+            let node = ast_id.to_ptr(db).to_node(&root);
+
+            // FIXME: Use censoring
+            let _censor = censor_for_macro_input(&loc, node.syntax());
+            expander.expand(db, macro_call_id, &node, map.as_ref())
+        }
         _ => {
             let ValueResult { value: (macro_arg, undo_info), err } = db.macro_arg(macro_call_id);
             let format_parse_err = |err: Arc<Box<[SyntaxError]>>| {
@@ -558,9 +569,6 @@ fn macro_expand(
                         err: err.map(format_parse_err),
                     };
                 }
-                MacroDefKind::BuiltInDerive(it, _) => {
-                    it.expand(db, macro_call_id, arg).map_err(Into::into)
-                }
                 MacroDefKind::BuiltInEager(it, _) => {
                     it.expand(db, macro_call_id, arg).map_err(Into::into)
                 }
diff --git a/crates/hir-expand/src/eager.rs b/crates/hir-expand/src/eager.rs
index 5337a5bb02..da85c2ec7a 100644
--- a/crates/hir-expand/src/eager.rs
+++ b/crates/hir-expand/src/eager.rs
@@ -27,6 +27,7 @@ use crate::{
     ast::{self, AstNode},
     db::ExpandDatabase,
     mod_path::ModPath,
+    span_map::SpanMapRef,
     EagerCallInfo, ExpandError, ExpandResult, ExpandTo, ExpansionSpanMap, InFile, Intern,
     MacroCallId, MacroCallKind, MacroCallLoc, MacroDefId, MacroDefKind,
 };
@@ -154,9 +155,10 @@ fn eager_macro_recur(
             }
         };
 
-        let def = match call.path().and_then(|path| {
-            ModPath::from_src(db, path, &mut |range| span_map.span_at(range.start()).ctx)
-        }) {
+        let def = match call
+            .path()
+            .and_then(|path| ModPath::from_src(db, path, SpanMapRef::ExpansionSpanMap(span_map)))
+        {
             Some(path) => match macro_resolver(path.clone()) {
                 Some(def) => def,
                 None => {
diff --git a/crates/hir-expand/src/mod_path.rs b/crates/hir-expand/src/mod_path.rs
index fc186d2c26..ae10ad2f80 100644
--- a/crates/hir-expand/src/mod_path.rs
+++ b/crates/hir-expand/src/mod_path.rs
@@ -9,6 +9,7 @@ use crate::{
     db::ExpandDatabase,
     hygiene::{marks_rev, SyntaxContextExt, Transparency},
     name::{known, AsName, Name},
+    span_map::SpanMapRef,
     tt,
 };
 use base_db::CrateId;
@@ -48,9 +49,9 @@ impl ModPath {
     pub fn from_src(
         db: &dyn ExpandDatabase,
         path: ast::Path,
-        span_for_range: &mut dyn FnMut(::tt::TextRange) -> SyntaxContextId,
+        span_map: SpanMapRef<'_>,
     ) -> Option<ModPath> {
-        convert_path(db, path, span_for_range)
+        convert_path(db, path, span_map)
     }
 
     pub fn from_tt(db: &dyn ExpandDatabase, tt: &[tt::TokenTree]) -> Option<ModPath> {
@@ -220,7 +221,7 @@ fn display_fmt_path(
 fn convert_path(
     db: &dyn ExpandDatabase,
     path: ast::Path,
-    span_for_range: &mut dyn FnMut(::tt::TextRange) -> SyntaxContextId,
+    span_map: SpanMapRef<'_>,
 ) -> Option<ModPath> {
     let mut segments = path.segments();
 
@@ -229,9 +230,12 @@ fn convert_path(
         ast::PathSegmentKind::Name(name_ref) => {
             if name_ref.text() == "$crate" {
                 ModPath::from_kind(
-                    resolve_crate_root(db, span_for_range(name_ref.syntax().text_range()))
-                        .map(PathKind::DollarCrate)
-                        .unwrap_or(PathKind::Crate),
+                    resolve_crate_root(
+                        db,
+                        span_map.span_for_range(name_ref.syntax().text_range()).ctx,
+                    )
+                    .map(PathKind::DollarCrate)
+                    .unwrap_or(PathKind::Crate),
                 )
             } else {
                 let mut res = ModPath::from_kind(
@@ -285,7 +289,7 @@ fn convert_path(
     // We follow what it did anyway :)
     if mod_path.segments.len() == 1 && mod_path.kind == PathKind::Plain {
         if let Some(_macro_call) = path.syntax().parent().and_then(ast::MacroCall::cast) {
-            let syn_ctx = span_for_range(segment.syntax().text_range());
+            let syn_ctx = span_map.span_for_range(segment.syntax().text_range()).ctx;
             if let Some(macro_call_id) = db.lookup_intern_syntax_context(syn_ctx).outer_expn {
                 if db.lookup_intern_macro_call(macro_call_id).def.local_inner {
                     mod_path.kind = match resolve_crate_root(db, syn_ctx) {
diff --git a/crates/hir-expand/src/name.rs b/crates/hir-expand/src/name.rs
index 0b69799e6b..cf17d90ed1 100644
--- a/crates/hir-expand/src/name.rs
+++ b/crates/hir-expand/src/name.rs
@@ -111,11 +111,15 @@ impl Name {
         self == &Name::missing()
     }
 
-    /// Generates a new name that attempts to be unique. Should only be used when body lowering and
-    /// creating desugared locals and labels. The caller is responsible for picking an index
-    /// that is stable across re-executions
-    pub fn generate_new_name(idx: usize) -> Name {
-        Name::new_text(format_smolstr!("<ra@gennew>{idx}"))
+    /// Generates a new name which is only equal to itself, by incrementing a counter. Due
+    /// its implementation, it should not be used in things that salsa considers, like
+    /// type names or field names, and it should be only used in names of local variables
+    /// and labels and similar things.
+    pub fn generate_new_name() -> Name {
+        use std::sync::atomic::{AtomicUsize, Ordering};
+        static CNT: AtomicUsize = AtomicUsize::new(0);
+        let c = CNT.fetch_add(1, Ordering::Relaxed);
+        Name::new_text(format_smolstr!("<ra@gennew>{c}"))
     }
 
     /// Returns the tuple index this name represents if it is a tuple field.
diff --git a/crates/hir-expand/src/span_map.rs b/crates/hir-expand/src/span_map.rs
index ef86be6709..4a60a94856 100644
--- a/crates/hir-expand/src/span_map.rs
+++ b/crates/hir-expand/src/span_map.rs
@@ -31,13 +31,11 @@ impl mbe::SpanMapper<Span> for SpanMap {
         self.span_for_range(range)
     }
 }
-
 impl mbe::SpanMapper<Span> for SpanMapRef<'_> {
     fn span_for(&self, range: TextRange) -> Span {
         self.span_for_range(range)
     }
 }
-
 impl SpanMap {
     pub fn span_for_range(&self, range: TextRange) -> Span {
         match self {
diff --git a/crates/hir-ty/src/db.rs b/crates/hir-ty/src/db.rs
index 28c497989f..f9e8cff553 100644
--- a/crates/hir-ty/src/db.rs
+++ b/crates/hir-ty/src/db.rs
@@ -31,9 +31,13 @@ use hir_expand::name::Name;
 
 #[salsa::query_group(HirDatabaseStorage)]
 pub trait HirDatabase: DefDatabase + Upcast<dyn DefDatabase> {
-    #[salsa::invoke(crate::infer::infer_query)]
+    #[salsa::invoke(infer_wait)]
+    #[salsa::transparent]
     fn infer(&self, def: DefWithBodyId) -> Arc<InferenceResult>;
 
+    #[salsa::invoke(crate::infer::infer_query)]
+    fn infer_query(&self, def: DefWithBodyId) -> Arc<InferenceResult>;
+
     // region:mir
 
     #[salsa::invoke(crate::mir::mir_body_query)]
@@ -254,7 +258,8 @@ pub trait HirDatabase: DefDatabase + Upcast<dyn DefDatabase> {
         env: Arc<TraitEnvironment>,
     ) -> Ty;
 
-    #[salsa::invoke(crate::traits::trait_solve_query)]
+    #[salsa::invoke(trait_solve_wait)]
+    #[salsa::transparent]
     fn trait_solve(
         &self,
         krate: CrateId,
@@ -262,6 +267,14 @@ pub trait HirDatabase: DefDatabase + Upcast<dyn DefDatabase> {
         goal: crate::Canonical<crate::InEnvironment<crate::Goal>>,
     ) -> Option<crate::Solution>;
 
+    #[salsa::invoke(crate::traits::trait_solve_query)]
+    fn trait_solve_query(
+        &self,
+        krate: CrateId,
+        block: Option<BlockId>,
+        goal: crate::Canonical<crate::InEnvironment<crate::Goal>>,
+    ) -> Option<crate::Solution>;
+
     #[salsa::invoke(chalk_db::program_clauses_for_chalk_env_query)]
     fn program_clauses_for_chalk_env(
         &self,
@@ -271,6 +284,38 @@ pub trait HirDatabase: DefDatabase + Upcast<dyn DefDatabase> {
     ) -> chalk_ir::ProgramClauses<Interner>;
 }
 
+fn infer_wait(db: &dyn HirDatabase, def: DefWithBodyId) -> Arc<InferenceResult> {
+    let detail = match def {
+        DefWithBodyId::FunctionId(it) => db.function_data(it).name.display(db.upcast()).to_string(),
+        DefWithBodyId::StaticId(it) => {
+            db.static_data(it).name.clone().display(db.upcast()).to_string()
+        }
+        DefWithBodyId::ConstId(it) => db
+            .const_data(it)
+            .name
+            .clone()
+            .unwrap_or_else(Name::missing)
+            .display(db.upcast())
+            .to_string(),
+        DefWithBodyId::VariantId(it) => {
+            db.enum_variant_data(it).name.display(db.upcast()).to_string()
+        }
+        DefWithBodyId::InTypeConstId(it) => format!("in type const {it:?}"),
+    };
+    let _p = tracing::span!(tracing::Level::INFO, "infer:wait", ?detail).entered();
+    db.infer_query(def)
+}
+
+fn trait_solve_wait(
+    db: &dyn HirDatabase,
+    krate: CrateId,
+    block: Option<BlockId>,
+    goal: crate::Canonical<crate::InEnvironment<crate::Goal>>,
+) -> Option<crate::Solution> {
+    let _p = tracing::span!(tracing::Level::INFO, "trait_solve::wait").entered();
+    db.trait_solve_query(krate, block, goal)
+}
+
 #[test]
 fn hir_database_is_object_safe() {
     fn _assert_object_safe(_: &dyn HirDatabase) {}
diff --git a/crates/hir-ty/src/diagnostics/expr.rs b/crates/hir-ty/src/diagnostics/expr.rs
index 67cfbc294d..dfc0a25bf4 100644
--- a/crates/hir-ty/src/diagnostics/expr.rs
+++ b/crates/hir-ty/src/diagnostics/expr.rs
@@ -182,7 +182,7 @@ impl ExprValidator {
         db: &dyn HirDatabase,
     ) {
         let scrut_ty = &self.infer[scrutinee_expr];
-        if scrut_ty.contains_unknown() {
+        if scrut_ty.is_unknown() {
             return;
         }
 
@@ -239,7 +239,6 @@ impl ExprValidator {
             m_arms.as_slice(),
             scrut_ty.clone(),
             ValidityConstraint::ValidOnly,
-            None,
         ) {
             Ok(report) => report,
             Err(()) => return,
@@ -267,9 +266,6 @@ impl ExprValidator {
             };
             let Some(initializer) = initializer else { continue };
             let ty = &self.infer[initializer];
-            if ty.contains_unknown() {
-                continue;
-            }
 
             let mut have_errors = false;
             let deconstructed_pat = self.lower_pattern(&cx, pat, db, &mut have_errors);
@@ -287,7 +283,6 @@ impl ExprValidator {
                 &[match_arm],
                 ty.clone(),
                 ValidityConstraint::ValidOnly,
-                None,
             ) {
                 Ok(v) => v,
                 Err(e) => {
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 ca05842879..e98a946a87 100644
--- a/crates/hir-ty/src/diagnostics/match_check/pat_analysis.rs
+++ b/crates/hir-ty/src/diagnostics/match_check/pat_analysis.rs
@@ -8,7 +8,7 @@ use rustc_hash::FxHashMap;
 use rustc_pattern_analysis::{
     constructor::{Constructor, ConstructorSet, VariantVisibility},
     index::IdxContainer,
-    Captures, PrivateUninhabitedField, TypeCx,
+    Captures, TypeCx,
 };
 use smallvec::{smallvec, SmallVec};
 use stdx::never;
@@ -88,21 +88,39 @@ impl<'p> MatchCheckCtx<'p> {
         }
     }
 
-    // This lists the fields of a variant along with their types.
-    fn list_variant_fields<'a>(
+    // In the cases of either a `#[non_exhaustive]` field list or a non-public field, we hide
+    // uninhabited fields in order not to reveal the uninhabitedness of the whole variant.
+    // This lists the fields we keep along with their types.
+    fn list_variant_nonhidden_fields<'a>(
         &'a self,
         ty: &'a Ty,
         variant: VariantId,
     ) -> impl Iterator<Item = (LocalFieldId, Ty)> + Captures<'a> + Captures<'p> {
-        let (_, substs) = ty.as_adt().unwrap();
+        let cx = self;
+        let (adt, substs) = ty.as_adt().unwrap();
+
+        let adt_is_local = variant.module(cx.db.upcast()).krate() == cx.module.krate();
+
+        // Whether we must not match the fields of this variant exhaustively.
+        let is_non_exhaustive =
+            cx.db.attrs(variant.into()).by_key("non_exhaustive").exists() && !adt_is_local;
 
-        let field_tys = self.db.field_types(variant);
-        let fields_len = variant.variant_data(self.db.upcast()).fields().len() as u32;
+        let visibility = cx.db.field_visibilities(variant);
+        let field_ty = cx.db.field_types(variant);
+        let fields_len = variant.variant_data(cx.db.upcast()).fields().len() as u32;
 
-        (0..fields_len).map(|idx| LocalFieldId::from_raw(idx.into())).map(move |fid| {
-            let ty = field_tys[fid].clone().substitute(Interner, substs);
-            let ty = normalize(self.db, self.db.trait_environment_for_body(self.body), ty);
-            (fid, ty)
+        (0..fields_len).map(|idx| LocalFieldId::from_raw(idx.into())).filter_map(move |fid| {
+            let ty = field_ty[fid].clone().substitute(Interner, substs);
+            let ty = normalize(cx.db, cx.db.trait_environment_for_body(cx.body), ty);
+            let is_visible = matches!(adt, hir_def::AdtId::EnumId(..))
+                || visibility[fid].is_visible_from(cx.db.upcast(), cx.module);
+            let is_uninhabited = cx.is_uninhabited(&ty);
+
+            if is_uninhabited && (!is_visible || is_non_exhaustive) {
+                None
+            } else {
+                Some((fid, ty))
+            }
         })
     }
 
@@ -181,16 +199,23 @@ impl<'p> MatchCheckCtx<'p> {
                             }
                         };
                         let variant = Self::variant_id_for_adt(&ctor, adt.0).unwrap();
-                        // Fill a vec with wildcards, then place the fields we have at the right
-                        // index.
-                        let mut wilds: Vec<_> = self
-                            .list_variant_fields(&pat.ty, variant)
-                            .map(|(_, ty)| ty)
-                            .map(DeconstructedPat::wildcard)
-                            .collect();
+                        let fields_len = variant.variant_data(self.db.upcast()).fields().len();
+                        // For each field in the variant, we store the relevant index into `self.fields` if any.
+                        let mut field_id_to_id: Vec<Option<usize>> = vec![None; fields_len];
+                        let tys = self
+                            .list_variant_nonhidden_fields(&pat.ty, variant)
+                            .enumerate()
+                            .map(|(i, (fid, ty))| {
+                                let field_idx: u32 = fid.into_raw().into();
+                                field_id_to_id[field_idx as usize] = Some(i);
+                                ty
+                            });
+                        let mut wilds: Vec<_> = tys.map(DeconstructedPat::wildcard).collect();
                         for pat in subpatterns {
-                            let field_id: u32 = pat.field.into_raw().into();
-                            wilds[field_id as usize] = self.lower_pat(&pat.pattern);
+                            let field_idx: u32 = pat.field.into_raw().into();
+                            if let Some(i) = field_id_to_id[field_idx as usize] {
+                                wilds[i] = self.lower_pat(&pat.pattern);
+                            }
                         }
                         fields = wilds;
                     }
@@ -238,7 +263,7 @@ impl<'p> MatchCheckCtx<'p> {
                 TyKind::Adt(adt, substs) => {
                     let variant = Self::variant_id_for_adt(pat.ctor(), adt.0).unwrap();
                     let subpatterns = self
-                        .list_variant_fields(pat.ty(), variant)
+                        .list_variant_nonhidden_fields(pat.ty(), variant)
                         .zip(subpatterns)
                         .map(|((field, _ty), pattern)| FieldPat { field, pattern })
                         .collect();
@@ -261,7 +286,7 @@ impl<'p> MatchCheckCtx<'p> {
             Ref => PatKind::Deref { subpattern: subpatterns.next().unwrap() },
             Slice(_) => unimplemented!(),
             &Str(void) => match void {},
-            Wildcard | NonExhaustive | Hidden | PrivateUninhabited => PatKind::Wild,
+            Wildcard | NonExhaustive | Hidden => PatKind::Wild,
             Missing | F32Range(..) | F64Range(..) | Opaque(..) | Or => {
                 never!("can't convert to pattern: {:?}", pat.ctor());
                 PatKind::Wild
@@ -301,7 +326,7 @@ impl<'p> TypeCx for MatchCheckCtx<'p> {
                         1
                     } else {
                         let variant = Self::variant_id_for_adt(ctor, adt).unwrap();
-                        variant.variant_data(self.db.upcast()).fields().len()
+                        self.list_variant_nonhidden_fields(ty, variant).count()
                     }
                 }
                 _ => {
@@ -312,7 +337,7 @@ impl<'p> TypeCx for MatchCheckCtx<'p> {
             Ref => 1,
             Slice(..) => unimplemented!(),
             Bool(..) | IntRange(..) | F32Range(..) | F64Range(..) | Str(..) | Opaque(..)
-            | NonExhaustive | PrivateUninhabited | Hidden | Missing | Wildcard => 0,
+            | NonExhaustive | Hidden | Missing | Wildcard => 0,
             Or => {
                 never!("The `Or` constructor doesn't have a fixed arity");
                 0
@@ -324,13 +349,13 @@ impl<'p> TypeCx for MatchCheckCtx<'p> {
         &'a self,
         ctor: &'a rustc_pattern_analysis::constructor::Constructor<Self>,
         ty: &'a Self::Ty,
-    ) -> impl ExactSizeIterator<Item = (Self::Ty, PrivateUninhabitedField)> + Captures<'a> {
-        let single = |ty| smallvec![(ty, PrivateUninhabitedField(false))];
+    ) -> impl ExactSizeIterator<Item = Self::Ty> + Captures<'a> {
+        let single = |ty| smallvec![ty];
         let tys: SmallVec<[_; 2]> = match ctor {
             Struct | Variant(_) | UnionField => match ty.kind(Interner) {
                 TyKind::Tuple(_, substs) => {
                     let tys = substs.iter(Interner).map(|ty| ty.assert_ty_ref(Interner));
-                    tys.cloned().map(|ty| (ty, PrivateUninhabitedField(false))).collect()
+                    tys.cloned().collect()
                 }
                 TyKind::Ref(.., rty) => single(rty.clone()),
                 &TyKind::Adt(AdtId(adt), ref substs) => {
@@ -341,27 +366,7 @@ impl<'p> TypeCx for MatchCheckCtx<'p> {
                         single(subst_ty)
                     } else {
                         let variant = Self::variant_id_for_adt(ctor, adt).unwrap();
-                        let (adt, _) = ty.as_adt().unwrap();
-
-                        let adt_is_local =
-                            variant.module(self.db.upcast()).krate() == self.module.krate();
-                        // Whether we must not match the fields of this variant exhaustively.
-                        let is_non_exhaustive =
-                            self.db.attrs(variant.into()).by_key("non_exhaustive").exists()
-                                && !adt_is_local;
-                        let visibilities = self.db.field_visibilities(variant);
-
-                        self.list_variant_fields(ty, variant)
-                            .map(move |(fid, ty)| {
-                                let is_visible = matches!(adt, hir_def::AdtId::EnumId(..))
-                                    || visibilities[fid]
-                                        .is_visible_from(self.db.upcast(), self.module);
-                                let is_uninhabited = self.is_uninhabited(&ty);
-                                let private_uninhabited =
-                                    is_uninhabited && (!is_visible || is_non_exhaustive);
-                                (ty, PrivateUninhabitedField(private_uninhabited))
-                            })
-                            .collect()
+                        self.list_variant_nonhidden_fields(ty, variant).map(|(_, ty)| ty).collect()
                     }
                 }
                 ty_kind => {
@@ -378,7 +383,7 @@ impl<'p> TypeCx for MatchCheckCtx<'p> {
             },
             Slice(_) => unreachable!("Found a `Slice` constructor in match checking"),
             Bool(..) | IntRange(..) | F32Range(..) | F64Range(..) | Str(..) | Opaque(..)
-            | NonExhaustive | PrivateUninhabited | Hidden | Missing | Wildcard => smallvec![],
+            | NonExhaustive | Hidden | Missing | Wildcard => smallvec![],
             Or => {
                 never!("called `Fields::wildcards` on an `Or` ctor");
                 smallvec![]
@@ -473,11 +478,6 @@ impl<'p> TypeCx for MatchCheckCtx<'p> {
     fn bug(&self, fmt: fmt::Arguments<'_>) {
         debug!("{}", fmt)
     }
-
-    fn complexity_exceeded(&self) -> Result<(), Self::Error> {
-        // FIXME(Nadrieril): make use of the complexity counter.
-        Err(())
-    }
 }
 
 impl<'p> fmt::Debug for MatchCheckCtx<'p> {
diff --git a/crates/hir-ty/src/display.rs b/crates/hir-ty/src/display.rs
index 20964f5acb..fe191a456a 100644
--- a/crates/hir-ty/src/display.rs
+++ b/crates/hir-ty/src/display.rs
@@ -1781,7 +1781,10 @@ impl HirDisplay for TypeRef {
                 f.write_joined(bounds, " + ")?;
             }
             TypeRef::Macro(macro_call) => {
-                let ctx = hir_def::lower::LowerCtx::new(f.db.upcast(), macro_call.file_id);
+                let ctx = hir_def::lower::LowerCtx::with_span_map(
+                    f.db.upcast(),
+                    f.db.span_map(macro_call.file_id),
+                );
                 let macro_call = macro_call.to_node(f.db.upcast());
                 match macro_call.path() {
                     Some(path) => match Path::from_src(&ctx, path) {
diff --git a/crates/hir-ty/src/layout.rs b/crates/hir-ty/src/layout.rs
index dea292711d..be1c8d9094 100644
--- a/crates/hir-ty/src/layout.rs
+++ b/crates/hir-ty/src/layout.rs
@@ -1,6 +1,6 @@
 //! Compute the binary representation of a type
 
-use std::{borrow::Cow, fmt};
+use std::fmt;
 
 use base_db::salsa::Cycle;
 use chalk_ir::{AdtId, FloatTy, IntTy, TyKind, UintTy};
@@ -114,8 +114,8 @@ struct LayoutCx<'a> {
 impl<'a> LayoutCalculator for LayoutCx<'a> {
     type TargetDataLayoutRef = &'a TargetDataLayout;
 
-    fn delayed_bug(&self, txt: impl Into<Cow<'static, str>>) {
-        never!("{}", txt.into());
+    fn delayed_bug(&self, txt: String) {
+        never!("{}", txt);
     }
 
     fn current_data_layout(&self) -> &'a TargetDataLayout {
diff --git a/crates/hir/src/db.rs b/crates/hir/src/db.rs
index 1d74f9a4bb..557c8d29a1 100644
--- a/crates/hir/src/db.rs
+++ b/crates/hir/src/db.rs
@@ -4,20 +4,20 @@
 //!
 //! But we need this for at least LRU caching at the query level.
 pub use hir_def::db::{
-    AttrsQuery, BlockDefMapQuery, BodyQuery, BodyWithSourceMapQuery, ConstDataQuery,
-    ConstVisibilityQuery, CrateLangItemsQuery, CrateSupportsNoStdQuery, DefDatabase,
-    DefDatabaseStorage, EnumDataQuery, EnumVariantDataWithDiagnosticsQuery, ExprScopesQuery,
-    ExternCrateDeclDataQuery, FieldVisibilitiesQuery, FieldsAttrsQuery, FieldsAttrsSourceMapQuery,
-    FileItemTreeQuery, FunctionDataQuery, FunctionVisibilityQuery, GenericParamsQuery,
-    ImplDataWithDiagnosticsQuery, ImportMapQuery, InternAnonymousConstQuery, InternBlockQuery,
-    InternConstQuery, InternDatabase, InternDatabaseStorage, InternEnumQuery,
-    InternExternBlockQuery, InternExternCrateQuery, InternFunctionQuery, InternImplQuery,
-    InternInTypeConstQuery, InternMacro2Query, InternMacroRulesQuery, InternProcMacroQuery,
-    InternStaticQuery, InternStructQuery, InternTraitAliasQuery, InternTraitQuery,
-    InternTypeAliasQuery, InternUnionQuery, InternUseQuery, LangItemQuery, Macro2DataQuery,
-    MacroRulesDataQuery, ProcMacroDataQuery, StaticDataQuery, StructDataWithDiagnosticsQuery,
-    TraitAliasDataQuery, TraitDataWithDiagnosticsQuery, TypeAliasDataQuery,
-    UnionDataWithDiagnosticsQuery,
+    AttrsQuery, BlockDefMapQuery, BlockItemTreeQueryQuery, BodyQuery, BodyWithSourceMapQuery,
+    ConstDataQuery, ConstVisibilityQuery, CrateDefMapQueryQuery, CrateLangItemsQuery,
+    CrateSupportsNoStdQuery, DefDatabase, DefDatabaseStorage, EnumDataQuery,
+    EnumVariantDataWithDiagnosticsQuery, ExprScopesQuery, ExternCrateDeclDataQuery,
+    FieldVisibilitiesQuery, FieldsAttrsQuery, FieldsAttrsSourceMapQuery, FileItemTreeQuery,
+    FunctionDataQuery, FunctionVisibilityQuery, GenericParamsQuery, ImplDataWithDiagnosticsQuery,
+    ImportMapQuery, InternAnonymousConstQuery, InternBlockQuery, InternConstQuery, InternDatabase,
+    InternDatabaseStorage, InternEnumQuery, InternExternBlockQuery, InternExternCrateQuery,
+    InternFunctionQuery, InternImplQuery, InternInTypeConstQuery, InternMacro2Query,
+    InternMacroRulesQuery, InternProcMacroQuery, InternStaticQuery, InternStructQuery,
+    InternTraitAliasQuery, InternTraitQuery, InternTypeAliasQuery, InternUnionQuery,
+    InternUseQuery, LangItemQuery, Macro2DataQuery, MacroRulesDataQuery, ProcMacroDataQuery,
+    StaticDataQuery, StructDataWithDiagnosticsQuery, TraitAliasDataQuery,
+    TraitDataWithDiagnosticsQuery, TypeAliasDataQuery, UnionDataWithDiagnosticsQuery,
 };
 pub use hir_expand::db::{
     AstIdMapQuery, DeclMacroExpanderQuery, ExpandDatabase, ExpandDatabaseStorage,
diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs
index 99907ea15b..ca8bd65510 100644
--- a/crates/hir/src/semantics.rs
+++ b/crates/hir/src/semantics.rs
@@ -1073,7 +1073,7 @@ impl<'db> SemanticsImpl<'db> {
 
     pub fn resolve_type(&self, ty: &ast::Type) -> Option<Type> {
         let analyze = self.analyze(ty.syntax())?;
-        let ctx = LowerCtx::new(self.db.upcast(), analyze.file_id);
+        let ctx = LowerCtx::with_file_id(self.db.upcast(), analyze.file_id);
         let ty = hir_ty::TyLoweringContext::new_maybe_unowned(
             self.db,
             &analyze.resolver,
@@ -1085,7 +1085,8 @@ impl<'db> SemanticsImpl<'db> {
 
     pub fn resolve_trait(&self, path: &ast::Path) -> Option<Trait> {
         let analyze = self.analyze(path.syntax())?;
-        let ctx = LowerCtx::new(self.db.upcast(), analyze.file_id);
+        let span_map = self.db.span_map(analyze.file_id);
+        let ctx = LowerCtx::with_span_map(self.db.upcast(), span_map);
         let hir_path = Path::from_src(&ctx, path.clone())?;
         match analyze.resolver.resolve_path_in_type_ns_fully(self.db.upcast(), &hir_path)? {
             TypeNs::TraitId(id) => Some(Trait { id }),
@@ -1693,7 +1694,7 @@ impl SemanticsScope<'_> {
     /// Resolve a path as-if it was written at the given scope. This is
     /// necessary a heuristic, as it doesn't take hygiene into account.
     pub fn speculative_resolve(&self, path: &ast::Path) -> Option<PathResolution> {
-        let ctx = LowerCtx::new(self.db.upcast(), self.file_id);
+        let ctx = LowerCtx::with_file_id(self.db.upcast(), self.file_id);
         let path = Path::from_src(&ctx, path.clone())?;
         resolve_hir_path(self.db, &self.resolver, &path)
     }
diff --git a/crates/hir/src/source_analyzer.rs b/crates/hir/src/source_analyzer.rs
index f87e0a3897..a147102bcd 100644
--- a/crates/hir/src/source_analyzer.rs
+++ b/crates/hir/src/source_analyzer.rs
@@ -549,7 +549,7 @@ impl SourceAnalyzer {
         db: &dyn HirDatabase,
         macro_call: InFile<&ast::MacroCall>,
     ) -> Option<Macro> {
-        let ctx = LowerCtx::new(db.upcast(), macro_call.file_id);
+        let ctx = LowerCtx::with_file_id(db.upcast(), macro_call.file_id);
         let path = macro_call.value.path().and_then(|ast| Path::from_src(&ctx, ast))?;
         self.resolver
             .resolve_path_as_macro(db.upcast(), path.mod_path()?, Some(MacroSubNs::Bang))
@@ -662,7 +662,7 @@ impl SourceAnalyzer {
         }
 
         // This must be a normal source file rather than macro file.
-        let ctx = LowerCtx::new(db.upcast(), self.file_id);
+        let ctx = LowerCtx::with_span_map(db.upcast(), db.span_map(self.file_id));
         let hir_path = Path::from_src(&ctx, path.clone())?;
 
         // Case where path is a qualifier of a use tree, e.g. foo::bar::{Baz, Qux} where we are
diff --git a/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs b/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs
index 2725a97de8..9d72d3af09 100644
--- a/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs
+++ b/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs
@@ -274,22 +274,4 @@ fn main() {
 "#,
         );
     }
-
-    #[test]
-    fn escaped_literals() {
-        check_assist(
-            extract_expressions_from_format_string,
-            r#"
-//- minicore: fmt
-fn main() {
-    print!("\n$ {x + 1}$0");
-}
-            "#,
-            r#"
-fn main() {
-    print!("\n$ {}"$0, x + 1);
-}
-            "#,
-        );
-    }
 }
diff --git a/crates/ide-completion/src/completions/postfix.rs b/crates/ide-completion/src/completions/postfix.rs
index 361ad821f4..72c0885e92 100644
--- a/crates/ide-completion/src/completions/postfix.rs
+++ b/crates/ide-completion/src/completions/postfix.rs
@@ -258,7 +258,7 @@ pub(crate) fn complete_postfix(
 }
 
 fn get_receiver_text(receiver: &ast::Expr, receiver_is_ambiguous_float_literal: bool) -> String {
-    let mut text = if receiver_is_ambiguous_float_literal {
+    let text = if receiver_is_ambiguous_float_literal {
         let text = receiver.syntax().text();
         let without_dot = ..text.len() - TextSize::of('.');
         text.slice(without_dot).to_string()
@@ -267,18 +267,12 @@ fn get_receiver_text(receiver: &ast::Expr, receiver_is_ambiguous_float_literal:
     };
 
     // The receiver texts should be interpreted as-is, as they are expected to be
-    // normal Rust expressions.
-    escape_snippet_bits(&mut text);
-    text
-}
-
-/// Escapes `\` and `$` so that they don't get interpreted as snippet-specific constructs.
-///
-/// Note that we don't need to escape the other characters that can be escaped,
-/// because they wouldn't be treated as snippet-specific constructs without '$'.
-fn escape_snippet_bits(text: &mut String) {
-    stdx::replace(text, '\\', "\\\\");
-    stdx::replace(text, '$', "\\$");
+    // normal Rust expressions. We escape '\' and '$' so they don't get treated as
+    // snippet-specific constructs.
+    //
+    // Note that we don't need to escape the other characters that can be escaped,
+    // because they wouldn't be treated as snippet-specific constructs without '$'.
+    text.replace('\\', "\\\\").replace('$', "\\$")
 }
 
 fn include_references(initial_element: &ast::Expr) -> (ast::Expr, ast::Expr) {
diff --git a/crates/ide-completion/src/completions/postfix/format_like.rs b/crates/ide-completion/src/completions/postfix/format_like.rs
index fd50fd4e8c..cb242e4aa6 100644
--- a/crates/ide-completion/src/completions/postfix/format_like.rs
+++ b/crates/ide-completion/src/completions/postfix/format_like.rs
@@ -17,15 +17,13 @@
 // image::https://user-images.githubusercontent.com/48062697/113020656-b560f500-917a-11eb-87de-02991f61beb8.gif[]
 
 use ide_db::{
-    syntax_helpers::format_string_exprs::{parse_format_exprs, with_placeholders, Arg},
+    syntax_helpers::format_string_exprs::{parse_format_exprs, with_placeholders},
     SnippetCap,
 };
 use syntax::{ast, AstToken};
 
 use crate::{
-    completions::postfix::{build_postfix_snippet_builder, escape_snippet_bits},
-    context::CompletionContext,
-    Completions,
+    completions::postfix::build_postfix_snippet_builder, context::CompletionContext, Completions,
 };
 
 /// Mapping ("postfix completion item" => "macro to use")
@@ -53,15 +51,7 @@ pub(crate) fn add_format_like_completions(
         None => return,
     };
 
-    if let Ok((mut out, mut exprs)) = parse_format_exprs(receiver_text.text()) {
-        // Escape any snippet bits in the out text and any of the exprs.
-        escape_snippet_bits(&mut out);
-        for arg in &mut exprs {
-            if let Arg::Ident(text) | Arg::Expr(text) = arg {
-                escape_snippet_bits(text)
-            }
-        }
-
+    if let Ok((out, exprs)) = parse_format_exprs(receiver_text.text()) {
         let exprs = with_placeholders(exprs);
         for (label, macro_name) in KINDS {
             let snippet = if exprs.is_empty() {
diff --git a/crates/ide-db/src/apply_change.rs b/crates/ide-db/src/apply_change.rs
index 017635d88e..f769f72d46 100644
--- a/crates/ide-db/src/apply_change.rs
+++ b/crates/ide-db/src/apply_change.rs
@@ -91,6 +91,7 @@ impl RootDatabase {
             crate::symbol_index::LocalRootsQuery
             crate::symbol_index::LibraryRootsQuery
             // HirDatabase
+            hir::db::InferQueryQuery
             hir::db::MirBodyQuery
             hir::db::BorrowckQuery
             hir::db::TyQuery
@@ -129,10 +130,12 @@ impl RootDatabase {
             hir::db::FnDefVarianceQuery
             hir::db::AdtVarianceQuery
             hir::db::AssociatedTyValueQuery
+            hir::db::TraitSolveQueryQuery
             hir::db::ProgramClausesForChalkEnvQuery
 
             // DefDatabase
             hir::db::FileItemTreeQuery
+            hir::db::CrateDefMapQueryQuery
             hir::db::BlockDefMapQuery
             hir::db::StructDataWithDiagnosticsQuery
             hir::db::UnionDataWithDiagnosticsQuery
@@ -162,6 +165,7 @@ impl RootDatabase {
             hir::db::FunctionVisibilityQuery
             hir::db::ConstVisibilityQuery
             hir::db::CrateSupportsNoStdQuery
+            hir::db::BlockItemTreeQueryQuery
             hir::db::ExternCrateDeclDataQuery
             hir::db::InternAnonymousConstQuery
             hir::db::InternExternCrateQuery
diff --git a/crates/ide-db/src/lib.rs b/crates/ide-db/src/lib.rs
index be08b37bac..0d11858ed7 100644
--- a/crates/ide-db/src/lib.rs
+++ b/crates/ide-db/src/lib.rs
@@ -216,6 +216,7 @@ impl RootDatabase {
 
             // DefDatabase
             hir_db::FileItemTreeQuery
+            hir_db::CrateDefMapQueryQuery
             hir_db::BlockDefMapQuery
             hir_db::StructDataWithDiagnosticsQuery
             hir_db::UnionDataWithDiagnosticsQuery
@@ -247,6 +248,7 @@ impl RootDatabase {
             hir_db::CrateSupportsNoStdQuery
 
             // HirDatabase
+            hir_db::InferQueryQuery
             hir_db::MirBodyQuery
             hir_db::BorrowckQuery
             hir_db::TyQuery
@@ -285,6 +287,7 @@ impl RootDatabase {
             hir_db::FnDefVarianceQuery
             hir_db::AdtVarianceQuery
             hir_db::AssociatedTyValueQuery
+            hir_db::TraitSolveQueryQuery
             hir_db::ProgramClausesForChalkEnvQuery
 
             // SymbolsDatabase
diff --git a/crates/ide-db/src/syntax_helpers/format_string_exprs.rs b/crates/ide-db/src/syntax_helpers/format_string_exprs.rs
index 8ab5a6ede3..49594aee9f 100644
--- a/crates/ide-db/src/syntax_helpers/format_string_exprs.rs
+++ b/crates/ide-db/src/syntax_helpers/format_string_exprs.rs
@@ -11,12 +11,15 @@ pub enum Arg {
     Expr(String),
 }
 
-/// Add placeholders like `$1` and `$2` in place of [`Arg::Placeholder`],
-/// and unwraps the [`Arg::Ident`] and [`Arg::Expr`] enums.
-/// ```rust
-/// # use ide_db::syntax_helpers::format_string_exprs::*;
-/// assert_eq!(with_placeholders(vec![Arg::Ident("ident".to_owned()), Arg::Placeholder, Arg::Expr("expr + 2".to_owned())]), vec!["ident".to_owned(), "$1".to_owned(), "expr + 2".to_owned()])
-/// ```
+/**
+ Add placeholders like `$1` and `$2` in place of [`Arg::Placeholder`],
+ and unwraps the [`Arg::Ident`] and [`Arg::Expr`] enums.
+ ```rust
+ # use ide_db::syntax_helpers::format_string_exprs::*;
+ assert_eq!(with_placeholders(vec![Arg::Ident("ident".to_owned()), Arg::Placeholder, Arg::Expr("expr + 2".to_owned())]), vec!["ident".to_owned(), "$1".to_owned(), "expr + 2".to_owned()])
+ ```
+*/
+
 pub fn with_placeholders(args: Vec<Arg>) -> Vec<String> {
     let mut placeholder_id = 1;
     args.into_iter()
@@ -31,15 +34,18 @@ pub fn with_placeholders(args: Vec<Arg>) -> Vec<String> {
         .collect()
 }
 
-/// Parser for a format-like string. It is more allowing in terms of string contents,
-/// as we expect variable placeholders to be filled with expressions.
-///
-/// Splits a format string that may contain expressions
-/// like
-/// ```rust
-/// # use ide_db::syntax_helpers::format_string_exprs::*;
-/// assert_eq!(parse_format_exprs("{ident} {} {expr + 42} ").unwrap(), ("{ident} {} {} ".to_owned(), vec![Arg::Placeholder, Arg::Expr("expr + 42".to_owned())]));
-/// ```
+/**
+ Parser for a format-like string. It is more allowing in terms of string contents,
+ as we expect variable placeholders to be filled with expressions.
+
+ Built for completions and assists, and escapes `\` and `$` in output.
+ (See the comments on `get_receiver_text()` for detail.)
+ Splits a format string that may contain expressions
+ like
+ ```rust
+ assert_eq!(parse("{ident} {} {expr + 42} ").unwrap(), ("{} {} {}", vec![Arg::Ident("ident"), Arg::Placeholder, Arg::Expr("expr + 42")]));
+ ```
+*/
 pub fn parse_format_exprs(input: &str) -> Result<(String, Vec<Arg>), ()> {
     #[derive(Debug, Clone, Copy, PartialEq)]
     enum State {
@@ -73,6 +79,9 @@ pub fn parse_format_exprs(input: &str) -> Result<(String, Vec<Arg>), ()> {
                 state = State::MaybeIncorrect;
             }
             (State::NotArg, _) => {
+                if matches!(chr, '\\' | '$') {
+                    output.push('\\');
+                }
                 output.push(chr);
             }
             (State::MaybeIncorrect, '}') => {
@@ -101,6 +110,9 @@ pub fn parse_format_exprs(input: &str) -> Result<(String, Vec<Arg>), ()> {
                 state = State::FormatOpts;
             }
             (State::MaybeArg, _) => {
+                if matches!(chr, '\\' | '$') {
+                    current_expr.push('\\');
+                }
                 current_expr.push(chr);
 
                 // While Rust uses the unicode sets of XID_start and XID_continue for Identifiers
@@ -160,6 +172,9 @@ pub fn parse_format_exprs(input: &str) -> Result<(String, Vec<Arg>), ()> {
                     state = State::Expr;
                 }
 
+                if matches!(chr, '\\' | '$') {
+                    current_expr.push('\\');
+                }
                 current_expr.push(chr);
             }
             (State::FormatOpts, '}') => {
@@ -167,6 +182,9 @@ pub fn parse_format_exprs(input: &str) -> Result<(String, Vec<Arg>), ()> {
                 state = State::NotArg;
             }
             (State::FormatOpts, _) => {
+                if matches!(chr, '\\' | '$') {
+                    output.push('\\');
+                }
                 output.push(chr);
             }
         }
@@ -199,15 +217,15 @@ mod tests {
     fn format_str_parser() {
         let test_vector = &[
             ("no expressions", expect![["no expressions"]]),
-            (r"no expressions with \$0$1", expect![r"no expressions with \$0$1"]),
+            (r"no expressions with \$0$1", expect![r"no expressions with \\\$0\$1"]),
             ("{expr} is {2 + 2}", expect![["{expr} is {}; 2 + 2"]]),
             ("{expr:?}", expect![["{expr:?}"]]),
-            ("{expr:1$}", expect![[r"{expr:1$}"]]),
-            ("{:1$}", expect![[r"{:1$}; $1"]]),
-            ("{:>padding$}", expect![[r"{:>padding$}; $1"]]),
+            ("{expr:1$}", expect![[r"{expr:1\$}"]]),
+            ("{:1$}", expect![[r"{:1\$}; $1"]]),
+            ("{:>padding$}", expect![[r"{:>padding\$}; $1"]]),
             ("{}, {}, {0}", expect![[r"{}, {}, {0}; $1, $2"]]),
             ("{}, {}, {0:b}", expect![[r"{}, {}, {0:b}; $1, $2"]]),
-            ("{$0}", expect![[r"{}; $0"]]),
+            ("{$0}", expect![[r"{}; \$0"]]),
             ("{malformed", expect![["-"]]),
             ("malformed}", expect![["-"]]),
             ("{{correct", expect![["{{correct"]]),
diff --git a/crates/ide-diagnostics/src/handlers/missing_match_arms.rs b/crates/ide-diagnostics/src/handlers/missing_match_arms.rs
index 67daa172b2..8596f5792e 100644
--- a/crates/ide-diagnostics/src/handlers/missing_match_arms.rs
+++ b/crates/ide-diagnostics/src/handlers/missing_match_arms.rs
@@ -597,19 +597,21 @@ fn bang(never: !) {
 
     #[test]
     fn unknown_type() {
-        check_diagnostics_no_bails(
+        cov_mark::check_count!(validate_match_bailed_out, 1);
+
+        check_diagnostics(
             r#"
 enum Option<T> { Some(T), None }
 
 #[allow(unused)]
 fn main() {
     // `Never` is deliberately not defined so that it's an uninferred type.
-    // We ignore these to avoid triggering bugs in the analysis.
     match Option::<Never>::None {
         None => (),
         Some(never) => match never {},
     }
     match Option::<Never>::None {
+        //^^^^^^^^^^^^^^^^^^^^^ error: missing match arm: `None` not covered
         Option::Some(_never) => {},
     }
 }
@@ -617,18 +619,6 @@ fn main() {
         );
     }
 
-    #[test]
-    fn arity_mismatch_issue_16746() {
-        check_diagnostics_with_disabled(
-            r#"
-fn main() {
-    let (a, ) = (0, 0);
-}
-"#,
-            &["E0308"],
-        );
-    }
-
     #[test]
     fn tuple_of_bools_with_ellipsis_at_end_missing_arm() {
         check_diagnostics_no_bails(
diff --git a/crates/load-cargo/src/lib.rs b/crates/load-cargo/src/lib.rs
index a1c089520d..29c1251cce 100644
--- a/crates/load-cargo/src/lib.rs
+++ b/crates/load-cargo/src/lib.rs
@@ -10,7 +10,7 @@ use hir_expand::proc_macro::{
     ProcMacros,
 };
 use ide_db::{
-    base_db::{CrateGraph, Env, SourceRoot, SourceRootId},
+    base_db::{CrateGraph, Env, SourceRoot},
     prime_caches, ChangeWithProcMacros, FxHashMap, RootDatabase,
 };
 use itertools::Itertools;
@@ -231,7 +231,7 @@ impl ProjectFolders {
             res.load.push(entry);
 
             if root.is_local {
-                local_filesets.push(fsc.len() as u64);
+                local_filesets.push(fsc.len());
             }
             fsc.add_file_set(file_set_roots)
         }
@@ -246,7 +246,7 @@ impl ProjectFolders {
 #[derive(Default, Debug)]
 pub struct SourceRootConfig {
     pub fsc: FileSetConfig,
-    pub local_filesets: Vec<u64>,
+    pub local_filesets: Vec<usize>,
 }
 
 impl SourceRootConfig {
@@ -256,7 +256,7 @@ impl SourceRootConfig {
             .into_iter()
             .enumerate()
             .map(|(idx, file_set)| {
-                let is_local = self.local_filesets.contains(&(idx as u64));
+                let is_local = self.local_filesets.contains(&idx);
                 if is_local {
                     SourceRoot::new_local(file_set)
                 } else {
@@ -265,31 +265,6 @@ impl SourceRootConfig {
             })
             .collect()
     }
-
-    /// Maps local source roots to their parent source roots by bytewise comparing of root paths .
-    /// If a `SourceRoot` doesn't have a parent and is local then it is not contained in this mapping but it can be asserted that it is a root `SourceRoot`.
-    pub fn source_root_parent_map(&self) -> FxHashMap<SourceRootId, SourceRootId> {
-        let roots = self.fsc.roots();
-        let mut map = FxHashMap::<SourceRootId, SourceRootId>::default();
-        roots
-            .iter()
-            .enumerate()
-            .filter(|(_, (_, id))| self.local_filesets.contains(id))
-            .filter_map(|(idx, (root, root_id))| {
-                // We are interested in parents if they are also local source roots.
-                // So instead of a non-local parent we may take a local ancestor as a parent to a node.
-                roots.iter().take(idx).find_map(|(root2, root2_id)| {
-                    if self.local_filesets.contains(root2_id) && root.starts_with(root2) {
-                        return Some((root_id, root2_id));
-                    }
-                    None
-                })
-            })
-            .for_each(|(child, parent)| {
-                map.insert(SourceRootId(*child as u32), SourceRootId(*parent as u32));
-            });
-        map
-    }
 }
 
 /// Load the proc-macros for the given lib path, replacing all expanders whose names are in `dummy_replace`
@@ -422,11 +397,6 @@ mod tests {
 
     use super::*;
 
-    use ide_db::base_db::SourceRootId;
-    use vfs::{file_set::FileSetConfigBuilder, VfsPath};
-
-    use crate::SourceRootConfig;
-
     #[test]
     fn test_loading_rust_analyzer() {
         let path = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap().parent().unwrap();
@@ -443,124 +413,4 @@ mod tests {
         // RA has quite a few crates, but the exact count doesn't matter
         assert!(n_crates > 20);
     }
-
-    #[test]
-    fn unrelated_sources() {
-        let mut builder = FileSetConfigBuilder::default();
-        builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/abc".to_owned())]);
-        builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def".to_owned())]);
-        let fsc = builder.build();
-        let src = SourceRootConfig { fsc, local_filesets: vec![0, 1] };
-        let vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
-
-        assert_eq!(vc, vec![])
-    }
-
-    #[test]
-    fn unrelated_source_sharing_dirname() {
-        let mut builder = FileSetConfigBuilder::default();
-        builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/abc".to_owned())]);
-        builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/abc".to_owned())]);
-        let fsc = builder.build();
-        let src = SourceRootConfig { fsc, local_filesets: vec![0, 1] };
-        let vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
-
-        assert_eq!(vc, vec![])
-    }
-
-    #[test]
-    fn basic_child_parent() {
-        let mut builder = FileSetConfigBuilder::default();
-        builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/abc".to_owned())]);
-        builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/abc/def".to_owned())]);
-        let fsc = builder.build();
-        let src = SourceRootConfig { fsc, local_filesets: vec![0, 1] };
-        let vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
-
-        assert_eq!(vc, vec![(SourceRootId(1), SourceRootId(0))])
-    }
-
-    #[test]
-    fn basic_child_parent_with_unrelated_parents_sib() {
-        let mut builder = FileSetConfigBuilder::default();
-        builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/abc".to_owned())]);
-        builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def".to_owned())]);
-        builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/abc".to_owned())]);
-        let fsc = builder.build();
-        let src = SourceRootConfig { fsc, local_filesets: vec![0, 1, 2] };
-        let vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
-
-        assert_eq!(vc, vec![(SourceRootId(2), SourceRootId(1))])
-    }
-
-    #[test]
-    fn deep_sources_with_parent_missing() {
-        let mut builder = FileSetConfigBuilder::default();
-        builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/abc".to_owned())]);
-        builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/ghi".to_owned())]);
-        builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/abc".to_owned())]);
-        let fsc = builder.build();
-        let src = SourceRootConfig { fsc, local_filesets: vec![0, 1, 2] };
-        let vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
-
-        assert_eq!(vc, vec![])
-    }
-
-    #[test]
-    fn ancestor_can_be_parent() {
-        let mut builder = FileSetConfigBuilder::default();
-        builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/abc".to_owned())]);
-        builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def".to_owned())]);
-        builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/ghi/jkl".to_owned())]);
-        let fsc = builder.build();
-        let src = SourceRootConfig { fsc, local_filesets: vec![0, 1, 2] };
-        let vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
-
-        assert_eq!(vc, vec![(SourceRootId(2), SourceRootId(1))])
-    }
-
-    #[test]
-    fn ancestor_can_be_parent_2() {
-        let mut builder = FileSetConfigBuilder::default();
-        builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/abc".to_owned())]);
-        builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def".to_owned())]);
-        builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/ghi/jkl".to_owned())]);
-        builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/ghi/klm".to_owned())]);
-        let fsc = builder.build();
-        let src = SourceRootConfig { fsc, local_filesets: vec![0, 1, 2, 3] };
-        let mut vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
-        vc.sort_by(|x, y| x.0 .0.cmp(&y.0 .0));
-
-        assert_eq!(vc, vec![(SourceRootId(2), SourceRootId(1)), (SourceRootId(3), SourceRootId(1))])
-    }
-
-    #[test]
-    fn non_locals_are_skipped() {
-        let mut builder = FileSetConfigBuilder::default();
-        builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/abc".to_owned())]);
-        builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def".to_owned())]);
-        builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/ghi/jkl".to_owned())]);
-        builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/klm".to_owned())]);
-        let fsc = builder.build();
-        let src = SourceRootConfig { fsc, local_filesets: vec![0, 1, 3] };
-        let mut vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
-        vc.sort_by(|x, y| x.0 .0.cmp(&y.0 .0));
-
-        assert_eq!(vc, vec![(SourceRootId(3), SourceRootId(1)),])
-    }
-
-    #[test]
-    fn child_binds_ancestor_if_parent_nonlocal() {
-        let mut builder = FileSetConfigBuilder::default();
-        builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/abc".to_owned())]);
-        builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def".to_owned())]);
-        builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/klm".to_owned())]);
-        builder.add_file_set(vec![VfsPath::new_virtual_path("/ROOT/def/klm/jkl".to_owned())]);
-        let fsc = builder.build();
-        let src = SourceRootConfig { fsc, local_filesets: vec![0, 1, 3] };
-        let mut vc = src.source_root_parent_map().into_iter().collect::<Vec<_>>();
-        vc.sort_by(|x, y| x.0 .0.cmp(&y.0 .0));
-
-        assert_eq!(vc, vec![(SourceRootId(3), SourceRootId(1)),])
-    }
 }
diff --git a/crates/parser/src/grammar.rs b/crates/parser/src/grammar.rs
index 4e5837312f..34715628f1 100644
--- a/crates/parser/src/grammar.rs
+++ b/crates/parser/src/grammar.rs
@@ -244,7 +244,7 @@ impl BlockLike {
     }
 }
 
-const VISIBILITY_FIRST: TokenSet = TokenSet::new(&[T![pub]]);
+const VISIBILITY_FIRST: TokenSet = TokenSet::new(&[T![pub], T![crate]]);
 
 fn opt_visibility(p: &mut Parser<'_>, in_tuple_field: bool) -> bool {
     if !p.at(T![pub]) {
@@ -416,12 +416,14 @@ fn delimited(
         if !parser(p) {
             break;
         }
-        if !p.eat(delim) {
+        if !p.at(delim) {
             if p.at_ts(first_set) {
                 p.error(format!("expected {:?}", delim));
             } else {
                 break;
             }
+        } else {
+            p.bump(delim);
         }
     }
     p.expect(ket);
diff --git a/crates/parser/src/grammar/expressions.rs b/crates/parser/src/grammar/expressions.rs
index 861fcedda2..6b660180f8 100644
--- a/crates/parser/src/grammar/expressions.rs
+++ b/crates/parser/src/grammar/expressions.rs
@@ -211,8 +211,9 @@ fn current_op(p: &Parser<'_>) -> (u8, SyntaxKind, Associativity) {
         T![>] if p.at(T![>>])  => (9,  T![>>],  Left),
         T![>] if p.at(T![>=])  => (5,  T![>=],  Left),
         T![>]                  => (5,  T![>],   Left),
+        T![=] if p.at(T![=>])  => NOT_AN_OP,
         T![=] if p.at(T![==])  => (5,  T![==],  Left),
-        T![=] if !p.at(T![=>]) => (1,  T![=],   Right),
+        T![=]                  => (1,  T![=],   Right),
         T![<] if p.at(T![<=])  => (5,  T![<=],  Left),
         T![<] if p.at(T![<<=]) => (1,  T![<<=], Right),
         T![<] if p.at(T![<<])  => (9,  T![<<],  Left),
@@ -246,7 +247,7 @@ fn current_op(p: &Parser<'_>) -> (u8, SyntaxKind, Associativity) {
 fn expr_bp(
     p: &mut Parser<'_>,
     m: Option<Marker>,
-    r: Restrictions,
+    mut r: Restrictions,
     bp: u8,
 ) -> Option<(CompletedMarker, BlockLike)> {
     let m = m.unwrap_or_else(|| {
@@ -294,6 +295,10 @@ fn expr_bp(
         let m = lhs.precede(p);
         p.bump(op);
 
+        // test binop_resets_statementness
+        // fn f() { v = {1}&2; }
+        r = Restrictions { prefer_stmt: false, ..r };
+
         if is_range {
             // test postfix_range
             // fn foo() {
@@ -314,9 +319,6 @@ fn expr_bp(
             Associativity::Left => op_bp + 1,
             Associativity::Right => op_bp,
         };
-
-        // test binop_resets_statementness
-        // fn f() { v = {1}&2; }
         expr_bp(p, None, Restrictions { prefer_stmt: false, ..r }, op_bp);
         lhs = m.complete(p, if is_range { RANGE_EXPR } else { BIN_EXPR });
     }
@@ -343,7 +345,7 @@ fn lhs(p: &mut Parser<'_>, r: Restrictions) -> Option<(CompletedMarker, BlockLik
         T![&] => {
             m = p.start();
             p.bump(T![&]);
-            if p.at_contextual_kw(T![raw]) && [T![mut], T![const]].contains(&p.nth(1)) {
+            if p.at_contextual_kw(T![raw]) && (p.nth_at(1, T![mut]) || p.nth_at(1, T![const])) {
                 p.bump_remap(T![raw]);
                 p.bump_any();
             } else {
diff --git a/crates/parser/src/grammar/expressions/atom.rs b/crates/parser/src/grammar/expressions/atom.rs
index 72848a1f2b..48600641ad 100644
--- a/crates/parser/src/grammar/expressions/atom.rs
+++ b/crates/parser/src/grammar/expressions/atom.rs
@@ -147,7 +147,7 @@ pub(super) fn atom_expr(
         T![async] if la == T![move] && p.nth(2) == T!['{'] => {
             let m = p.start();
             p.bump(T![async]);
-            p.bump(T![move]);
+            p.eat(T![move]);
             stmt_list(p);
             m.complete(p, BLOCK_EXPR)
         }
@@ -390,7 +390,8 @@ fn if_expr(p: &mut Parser<'_>) -> CompletedMarker {
     p.bump(T![if]);
     expr_no_struct(p);
     block_expr(p);
-    if p.eat(T![else]) {
+    if p.at(T![else]) {
+        p.bump(T![else]);
         if p.at(T![if]) {
             if_expr(p);
         } else {
diff --git a/crates/parser/src/grammar/generic_params.rs b/crates/parser/src/grammar/generic_params.rs
index 6c05abc023..4498daf21a 100644
--- a/crates/parser/src/grammar/generic_params.rs
+++ b/crates/parser/src/grammar/generic_params.rs
@@ -170,7 +170,7 @@ fn type_bound(p: &mut Parser<'_>) -> bool {
                 _ => (),
             }
             if paths::is_use_path_start(p) {
-                types::path_type_bounds(p, false);
+                types::path_type_(p, false);
             } else {
                 m.abandon(p);
                 return false;
diff --git a/crates/parser/src/grammar/items.rs b/crates/parser/src/grammar/items.rs
index 25c00ccf5f..243a219525 100644
--- a/crates/parser/src/grammar/items.rs
+++ b/crates/parser/src/grammar/items.rs
@@ -70,7 +70,8 @@ pub(super) fn item_or_macro(p: &mut Parser<'_>, stop_on_r_curly: bool) {
     // macro_rules! {};
     // macro_rules! ()
     // macro_rules! []
-    if paths::is_use_path_start(p) {
+    let no_ident = p.at_contextual_kw(T![macro_rules]) && p.nth_at(1, BANG) && !p.nth_at(2, IDENT);
+    if paths::is_use_path_start(p) || no_ident {
         macro_call(p, m);
         return;
     }
@@ -155,19 +156,27 @@ pub(super) fn opt_item(p: &mut Parser<'_>, m: Marker) -> Result<(), Marker> {
             // impl T for Foo {
             //     default async fn foo() {}
             // }
-            T![async]
-                if p.nth_at(2, T![fn]) || (p.nth_at(2, T![unsafe]) && p.nth_at(3, T![fn])) =>
-            {
-                p.bump_remap(T![default]);
-                p.bump(T![async]);
-
-                // test default_async_unsafe_fn
-                // impl T for Foo {
-                //     default async unsafe fn foo() {}
-                // }
-                p.eat(T![unsafe]);
-
-                has_mods = true;
+            T![async] => {
+                let mut maybe_fn = p.nth(2);
+                let is_unsafe = if matches!(maybe_fn, T![unsafe]) {
+                    // test default_async_unsafe_fn
+                    // impl T for Foo {
+                    //     default async unsafe fn foo() {}
+                    // }
+                    maybe_fn = p.nth(3);
+                    true
+                } else {
+                    false
+                };
+
+                if matches!(maybe_fn, T![fn]) {
+                    p.bump_remap(T![default]);
+                    p.bump(T![async]);
+                    if is_unsafe {
+                        p.bump(T![unsafe]);
+                    }
+                    has_mods = true;
+                }
             }
             _ => (),
         }
@@ -410,9 +419,11 @@ fn fn_(p: &mut Parser<'_>, m: Marker) {
     // fn foo<T>() where T: Copy {}
     generic_params::opt_where_clause(p);
 
-    // test fn_decl
-    // trait T { fn foo(); }
-    if !p.eat(T![;]) {
+    if p.at(T![;]) {
+        // test fn_decl
+        // trait T { fn foo(); }
+        p.bump(T![;]);
+    } else {
         expressions::block_expr(p);
     }
     m.complete(p, FN);
diff --git a/crates/parser/src/grammar/items/traits.rs b/crates/parser/src/grammar/items/traits.rs
index c215185d63..a8a1ccb15e 100644
--- a/crates/parser/src/grammar/items/traits.rs
+++ b/crates/parser/src/grammar/items/traits.rs
@@ -119,11 +119,11 @@ fn not_a_qualified_path(p: &Parser<'_>) -> bool {
     // we disambiguate it in favor of generics (`impl<T> ::absolute::Path<T> { ... }`)
     // because this is what almost always expected in practice, qualified paths in impls
     // (`impl <Type>::AssocTy { ... }`) aren't even allowed by type checker at the moment.
-    if [T![#], T![>], T![const]].contains(&p.nth(1)) {
+    if p.nth(1) == T![#] || p.nth(1) == T![>] || p.nth(1) == T![const] {
         return true;
     }
-    ([LIFETIME_IDENT, IDENT].contains(&p.nth(1)))
-        && ([T![>], T![,], T![:], T![=]].contains(&p.nth(2)))
+    (p.nth(1) == LIFETIME_IDENT || p.nth(1) == IDENT)
+        && (p.nth(2) == T![>] || p.nth(2) == T![,] || p.nth(2) == T![:] || p.nth(2) == T![=])
 }
 
 // test_err impl_type
diff --git a/crates/parser/src/grammar/params.rs b/crates/parser/src/grammar/params.rs
index c535267c16..846da28cb0 100644
--- a/crates/parser/src/grammar/params.rs
+++ b/crates/parser/src/grammar/params.rs
@@ -76,16 +76,19 @@ fn list_(p: &mut Parser<'_>, flavor: Flavor) {
             m.abandon(p);
             if p.eat(T![,]) {
                 continue;
+            } else {
+                break;
             }
-            break;
         }
         param(p, m, flavor);
-        if !p.eat(T![,]) {
+        if !p.at(T![,]) {
             if p.at_ts(PARAM_FIRST.union(ATTRIBUTE_FIRST)) {
                 p.error("expected `,`");
             } else {
                 break;
             }
+        } else {
+            p.bump(T![,]);
         }
     }
 
diff --git a/crates/parser/src/grammar/patterns.rs b/crates/parser/src/grammar/patterns.rs
index eff6b66404..5036742337 100644
--- a/crates/parser/src/grammar/patterns.rs
+++ b/crates/parser/src/grammar/patterns.rs
@@ -255,7 +255,9 @@ fn is_literal_pat_start(p: &Parser<'_>) -> bool {
 fn literal_pat(p: &mut Parser<'_>) -> CompletedMarker {
     assert!(is_literal_pat_start(p));
     let m = p.start();
-    p.eat(T![-]);
+    if p.at(T![-]) {
+        p.bump(T![-]);
+    }
     expressions::literal(p);
     m.complete(p, LITERAL_PAT)
 }
@@ -466,12 +468,14 @@ fn slice_pat(p: &mut Parser<'_>) -> CompletedMarker {
 fn pat_list(p: &mut Parser<'_>, ket: SyntaxKind) {
     while !p.at(EOF) && !p.at(ket) {
         pattern_top(p);
-        if !p.eat(T![,]) {
+        if !p.at(T![,]) {
             if p.at_ts(PAT_TOP_FIRST) {
                 p.error(format!("expected {:?}, got {:?}", T![,], p.current()));
             } else {
                 break;
             }
+        } else {
+            p.bump(T![,]);
         }
     }
 }
diff --git a/crates/parser/src/grammar/types.rs b/crates/parser/src/grammar/types.rs
index 18ec570cd5..96a6cdeaaf 100644
--- a/crates/parser/src/grammar/types.rs
+++ b/crates/parser/src/grammar/types.rs
@@ -48,7 +48,7 @@ fn type_with_bounds_cond(p: &mut Parser<'_>, allow_bounds: bool) {
         T![impl] => impl_trait_type(p),
         T![dyn] => dyn_trait_type(p),
         // Some path types are not allowed to have bounds (no plus)
-        T![<] => path_type_bounds(p, allow_bounds),
+        T![<] => path_type_(p, allow_bounds),
         _ if paths::is_path_start(p) => path_or_macro_type_(p, allow_bounds),
         LIFETIME_IDENT if p.nth_at(1, T![+]) => bare_dyn_trait_type(p),
         _ => {
@@ -294,7 +294,7 @@ fn bare_dyn_trait_type(p: &mut Parser<'_>) {
 // type C = self::Foo;
 // type D = super::Foo;
 pub(super) fn path_type(p: &mut Parser<'_>) {
-    path_type_bounds(p, true);
+    path_type_(p, true);
 }
 
 // test macro_call_type
@@ -323,7 +323,7 @@ fn path_or_macro_type_(p: &mut Parser<'_>, allow_bounds: bool) {
     }
 }
 
-pub(super) fn path_type_bounds(p: &mut Parser<'_>, allow_bounds: bool) {
+pub(super) fn path_type_(p: &mut Parser<'_>, allow_bounds: bool) {
     assert!(paths::is_path_start(p));
     let m = p.start();
     paths::type_path(p);
diff --git a/crates/parser/src/parser.rs b/crates/parser/src/parser.rs
index 051461243a..ef413c6375 100644
--- a/crates/parser/src/parser.rs
+++ b/crates/parser/src/parser.rs
@@ -250,9 +250,12 @@ impl<'t> Parser<'t> {
 
     /// Create an error node and consume the next token.
     pub(crate) fn err_recover(&mut self, message: &str, recovery: TokenSet) {
-        if matches!(self.current(), T!['{'] | T!['}']) {
-            self.error(message);
-            return;
+        match self.current() {
+            T!['{'] | T!['}'] => {
+                self.error(message);
+                return;
+            }
+            _ => (),
         }
 
         if self.at_ts(recovery) {
diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs
index 0e560e54ed..560410e332 100644
--- a/crates/rust-analyzer/src/global_state.rs
+++ b/crates/rust-analyzer/src/global_state.rs
@@ -8,7 +8,7 @@ use std::{collections::hash_map::Entry, time::Instant};
 use crossbeam_channel::{unbounded, Receiver, Sender};
 use flycheck::FlycheckHandle;
 use hir::ChangeWithProcMacros;
-use ide::{Analysis, AnalysisHost, Cancellable, FileId, SourceRootId};
+use ide::{Analysis, AnalysisHost, Cancellable, FileId};
 use ide_db::base_db::{CrateId, ProcMacroPaths};
 use load_cargo::SourceRootConfig;
 use lsp_types::{SemanticTokens, Url};
@@ -66,8 +66,6 @@ pub(crate) struct GlobalState {
     pub(crate) diagnostics: DiagnosticCollection,
     pub(crate) mem_docs: MemDocs,
     pub(crate) source_root_config: SourceRootConfig,
-    /// A mapping that maps a local source root's `SourceRootId` to it parent's `SourceRootId`, if it has one.
-    pub(crate) local_roots_parent_map: FxHashMap<SourceRootId, SourceRootId>,
     pub(crate) semantic_tokens_cache: Arc<Mutex<FxHashMap<Url, SemanticTokens>>>,
 
     // status
@@ -206,7 +204,6 @@ impl GlobalState {
             send_hint_refresh_query: false,
             last_reported_status: None,
             source_root_config: SourceRootConfig::default(),
-            local_roots_parent_map: FxHashMap::default(),
             config_errors: Default::default(),
 
             proc_macro_clients: Arc::from_iter([]),
diff --git a/crates/rust-analyzer/src/integrated_benchmarks.rs b/crates/rust-analyzer/src/integrated_benchmarks.rs
index 3bba4847f9..28eae6079c 100644
--- a/crates/rust-analyzer/src/integrated_benchmarks.rs
+++ b/crates/rust-analyzer/src/integrated_benchmarks.rs
@@ -11,9 +11,7 @@
 //! which you can use to paste the command in terminal and add `--release` manually.
 
 use hir::ChangeWithProcMacros;
-use ide::{
-    AnalysisHost, CallableSnippets, CompletionConfig, DiagnosticsConfig, FilePosition, TextSize,
-};
+use ide::{AnalysisHost, CallableSnippets, CompletionConfig, FilePosition, TextSize};
 use ide_db::{
     imports::insert_use::{ImportGranularity, InsertUseConfig},
     SnippetCap,
@@ -159,7 +157,7 @@ fn integrated_completion_benchmark() {
         analysis.completions(&config, position, None).unwrap();
     }
 
-    let _g = crate::tracing::hprof::init("*");
+    crate::tracing::hprof::init("*>5");
 
     let completion_offset = {
         let _it = stdx::timeit("change");
@@ -246,80 +244,6 @@ fn integrated_completion_benchmark() {
     }
 }
 
-#[test]
-fn integrated_diagnostics_benchmark() {
-    if std::env::var("RUN_SLOW_BENCHES").is_err() {
-        return;
-    }
-
-    // Load rust-analyzer itself.
-    let workspace_to_load = project_root();
-    let file = "./crates/hir/src/lib.rs";
-
-    let cargo_config = CargoConfig {
-        sysroot: Some(project_model::RustLibSource::Discover),
-        ..CargoConfig::default()
-    };
-    let load_cargo_config = LoadCargoConfig {
-        load_out_dirs_from_check: true,
-        with_proc_macro_server: ProcMacroServerChoice::None,
-        prefill_caches: true,
-    };
-
-    let (db, vfs, _proc_macro) = {
-        let _it = stdx::timeit("workspace loading");
-        load_workspace_at(&workspace_to_load, &cargo_config, &load_cargo_config, &|_| {}).unwrap()
-    };
-    let mut host = AnalysisHost::with_database(db);
-
-    let file_id = {
-        let file = workspace_to_load.join(file);
-        let path = VfsPath::from(AbsPathBuf::assert(file));
-        vfs.file_id(&path).unwrap_or_else(|| panic!("can't find virtual file for {path}"))
-    };
-
-    let diagnostics_config = DiagnosticsConfig {
-        enabled: false,
-        proc_macros_enabled: true,
-        proc_attr_macros_enabled: true,
-        disable_experimental: true,
-        disabled: Default::default(),
-        expr_fill_default: Default::default(),
-        style_lints: false,
-        insert_use: InsertUseConfig {
-            granularity: ImportGranularity::Crate,
-            enforce_granularity: false,
-            prefix_kind: hir::PrefixKind::ByCrate,
-            group: true,
-            skip_glob_imports: true,
-        },
-        prefer_no_std: false,
-        prefer_prelude: false,
-    };
-    host.analysis()
-        .diagnostics(&diagnostics_config, ide::AssistResolveStrategy::None, file_id)
-        .unwrap();
-
-    let _g = crate::tracing::hprof::init("*>1");
-
-    {
-        let _it = stdx::timeit("change");
-        let mut text = host.analysis().file_text(file_id).unwrap().to_string();
-        patch(&mut text, "db.struct_data(self.id)", "();\ndb.struct_data(self.id)");
-        let mut change = ChangeWithProcMacros::new();
-        change.change_file(file_id, Some(Arc::from(text)));
-        host.apply_change(change);
-    };
-
-    {
-        let _p = tracing::span!(tracing::Level::INFO, "diagnostics").entered();
-        let _span = profile::cpu_span();
-        host.analysis()
-            .diagnostics(&diagnostics_config, ide::AssistResolveStrategy::None, file_id)
-            .unwrap();
-    }
-}
-
 fn patch(what: &mut String, from: &str, to: &str) -> usize {
     let idx = what.find(from).unwrap();
     *what = what.replacen(from, to, 1);
diff --git a/crates/rust-analyzer/src/lsp/ext.rs b/crates/rust-analyzer/src/lsp/ext.rs
index 842e765d9d..86ab652f8e 100644
--- a/crates/rust-analyzer/src/lsp/ext.rs
+++ b/crates/rust-analyzer/src/lsp/ext.rs
@@ -171,7 +171,7 @@ pub struct DiscoverTestParams {
 
 #[derive(Deserialize, Serialize, Debug)]
 #[serde(rename_all = "camelCase")]
-pub enum TestItemIcon {
+pub enum TestItemKind {
     Package,
     Module,
     Test,
@@ -182,7 +182,7 @@ pub enum TestItemIcon {
 pub struct TestItem {
     pub id: String,
     pub label: String,
-    pub icon: TestItemIcon,
+    pub kind: TestItemKind,
     pub can_resolve_children: bool,
     pub parent: Option<String>,
     pub text_document: Option<TextDocumentIdentifier>,
diff --git a/crates/rust-analyzer/src/lsp/to_proto.rs b/crates/rust-analyzer/src/lsp/to_proto.rs
index f0bb7d8af3..e2b55f4a5c 100644
--- a/crates/rust-analyzer/src/lsp/to_proto.rs
+++ b/crates/rust-analyzer/src/lsp/to_proto.rs
@@ -1506,10 +1506,10 @@ pub(crate) fn test_item(
     lsp_ext::TestItem {
         id: test_item.id,
         label: test_item.label,
-        icon: match test_item.kind {
-            ide::TestItemKind::Crate => lsp_ext::TestItemIcon::Package,
-            ide::TestItemKind::Module => lsp_ext::TestItemIcon::Module,
-            ide::TestItemKind::Function => lsp_ext::TestItemIcon::Test,
+        kind: match test_item.kind {
+            ide::TestItemKind::Crate => lsp_ext::TestItemKind::Package,
+            ide::TestItemKind::Module => lsp_ext::TestItemKind::Module,
+            ide::TestItemKind::Function => lsp_ext::TestItemKind::Test,
         },
         can_resolve_children: matches!(
             test_item.kind,
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index 99c00d0384..bca6db19dc 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -779,6 +779,7 @@ impl GlobalState {
             flycheck::CargoTestMessage::Test { name, state } => {
                 let state = match state {
                     flycheck::TestState::Started => lsp_ext::TestState::Started,
+                    flycheck::TestState::Ignored => lsp_ext::TestState::Skipped,
                     flycheck::TestState::Ok => lsp_ext::TestState::Passed,
                     flycheck::TestState::Failed { stdout } => {
                         lsp_ext::TestState::Failed { message: stdout }
diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs
index c2725e1fad..95d51baab3 100644
--- a/crates/rust-analyzer/src/reload.rs
+++ b/crates/rust-analyzer/src/reload.rs
@@ -515,7 +515,6 @@ impl GlobalState {
             version: self.vfs_config_version,
         });
         self.source_root_config = project_folders.source_root_config;
-        self.local_roots_parent_map = self.source_root_config.source_root_parent_map();
 
         self.recreate_crate_graph(cause);
 
diff --git a/crates/rust-analyzer/src/tracing/config.rs b/crates/rust-analyzer/src/tracing/config.rs
index f77d989330..fcdbd1e6d9 100644
--- a/crates/rust-analyzer/src/tracing/config.rs
+++ b/crates/rust-analyzer/src/tracing/config.rs
@@ -4,10 +4,13 @@
 use std::io;
 
 use anyhow::Context;
-use tracing::level_filters::LevelFilter;
+use tracing::{level_filters::LevelFilter, Level};
 use tracing_subscriber::{
-    filter::Targets, fmt::MakeWriter, layer::SubscriberExt, util::SubscriberInitExt, Layer,
-    Registry,
+    filter::{self, Targets},
+    fmt::{format::FmtSpan, MakeWriter},
+    layer::SubscriberExt,
+    util::SubscriberInitExt,
+    Layer, Registry,
 };
 use tracing_tree::HierarchicalLayer;
 
@@ -47,7 +50,10 @@ where
 
         let writer = self.writer;
 
-        let ra_fmt_layer = tracing_subscriber::fmt::layer().with_writer(writer).with_filter(filter);
+        let ra_fmt_layer = tracing_subscriber::fmt::layer()
+            .with_span_events(FmtSpan::CLOSE)
+            .with_writer(writer)
+            .with_filter(filter);
 
         let mut chalk_layer = None;
         if let Some(chalk_filter) = self.chalk_filter {
@@ -68,7 +74,32 @@ where
             );
         };
 
-        let profiler_layer = self.profile_filter.map(|spec| hprof::layer(&spec));
+        let mut profiler_layer = None;
+        if let Some(spec) = self.profile_filter {
+            let (write_filter, allowed_names) = hprof::WriteFilter::from_spec(&spec);
+
+            // this filter the first pass for `tracing`: these are all the "profiling" spans, but things like
+            // span depth or duration are not filtered here: that only occurs at write time.
+            let profile_filter = filter::filter_fn(move |metadata| {
+                let allowed = match &allowed_names {
+                    Some(names) => names.contains(metadata.name()),
+                    None => true,
+                };
+
+                metadata.is_span()
+                    && allowed
+                    && metadata.level() >= &Level::INFO
+                    && !metadata.target().starts_with("salsa")
+                    && !metadata.target().starts_with("chalk")
+            });
+
+            let layer = hprof::SpanTree::default()
+                .aggregate(true)
+                .spec_filter(write_filter)
+                .with_filter(profile_filter);
+
+            profiler_layer = Some(layer);
+        }
 
         Registry::default().with(ra_fmt_layer).with(chalk_layer).with(profiler_layer).try_init()?;
 
diff --git a/crates/rust-analyzer/src/tracing/hprof.rs b/crates/rust-analyzer/src/tracing/hprof.rs
index 73f94671f2..5fcda6ba13 100644
--- a/crates/rust-analyzer/src/tracing/hprof.rs
+++ b/crates/rust-analyzer/src/tracing/hprof.rs
@@ -53,14 +53,6 @@ use tracing_subscriber::{
 use crate::tracing::hprof;
 
 pub fn init(spec: &str) -> tracing::subscriber::DefaultGuard {
-    let subscriber = Registry::default().with(layer(spec));
-    tracing::subscriber::set_default(subscriber)
-}
-
-pub fn layer<S>(spec: &str) -> impl Layer<S>
-where
-    S: Subscriber + for<'span> tracing_subscriber::registry::LookupSpan<'span>,
-{
     let (write_filter, allowed_names) = WriteFilter::from_spec(spec);
 
     // this filter the first pass for `tracing`: these are all the "profiling" spans, but things like
@@ -71,15 +63,20 @@ where
             None => true,
         };
 
-        allowed
-            && metadata.is_span()
+        metadata.is_span()
+            && allowed
             && metadata.level() >= &Level::INFO
             && !metadata.target().starts_with("salsa")
-            && metadata.name() != "compute_exhaustiveness_and_usefulness"
             && !metadata.target().starts_with("chalk")
     });
 
-    hprof::SpanTree::default().aggregate(true).spec_filter(write_filter).with_filter(profile_filter)
+    let layer = hprof::SpanTree::default()
+        .aggregate(true)
+        .spec_filter(write_filter)
+        .with_filter(profile_filter);
+
+    let subscriber = Registry::default().with(layer);
+    tracing::subscriber::set_default(subscriber)
 }
 
 #[derive(Default, Debug)]
diff --git a/crates/salsa/salsa-macros/src/query_group.rs b/crates/salsa/salsa-macros/src/query_group.rs
index 5983765eec..a868d920b6 100644
--- a/crates/salsa/salsa-macros/src/query_group.rs
+++ b/crates/salsa/salsa-macros/src/query_group.rs
@@ -235,24 +235,13 @@ 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 s = format!("{trait_name}::{fn_name}");
-            Some(quote! {
-                let _p = tracing::span!(tracing::Level::DEBUG, #s, #(#key_names = tracing::field::debug(&#key_names)),*).entered();
-            })
-        } else {
-            None
-        }
-        .into_iter();
-
         query_fn_definitions.extend(quote! {
             fn #fn_name(&self, #(#key_names: #keys),*) -> #value {
-                #(#tracing),*
                 // Create a shim to force the code to be monomorphized in the
                 // query crate. Our experiments revealed that this makes a big
                 // difference in total compilation time in rust-analyzer, though
                 // it's not totally obvious why that should be.
-                fn __shim(db: &(dyn #trait_name + '_), #(#key_names: #keys),*) -> #value {
+                fn __shim(db: &(dyn #trait_name + '_),  #(#key_names: #keys),*) -> #value {
                     salsa::plumbing::get_query_table::<#qt>(db).get((#(#key_names),*))
                 }
                 __shim(self, #(#key_names),*)
diff --git a/crates/salsa/src/derived.rs b/crates/salsa/src/derived.rs
index 3b5bd7f9e3..153df999f5 100644
--- a/crates/salsa/src/derived.rs
+++ b/crates/salsa/src/derived.rs
@@ -136,7 +136,7 @@ where
     ) -> 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)
+        write!(fmt, "{}({:?})", Q::QUERY_NAME, key)
     }
 
     fn maybe_changed_after(
@@ -146,13 +146,13 @@ where
         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())
+        let read = self.slot_map.read();
+        let Some((key, slot)) = read.get_index(index as usize) else {
+            return false;
         };
+        let (key, slot) = (key.clone(), slot.clone());
+        // note: this drop is load-bearing. removing it would causes deadlocks.
+        drop(read);
         slot.maybe_changed_after(db, revision, &key)
     }
 
diff --git a/crates/span/src/ast_id.rs b/crates/span/src/ast_id.rs
index 332745aae6..2d98aa81e5 100644
--- a/crates/span/src/ast_id.rs
+++ b/crates/span/src/ast_id.rs
@@ -83,28 +83,24 @@ register_ast_id_node! {
     Item,
         Adt,
             Enum,
-                Variant,
             Struct,
-                RecordField,
-                TupleField,
             Union,
-        AssocItem,
-            Const,
-            Fn,
-            MacroCall,
-            TypeAlias,
+        Const,
         ExternBlock,
         ExternCrate,
+        Fn,
         Impl,
         Macro,
             MacroDef,
             MacroRules,
+        MacroCall,
         Module,
         Static,
         Trait,
         TraitAlias,
+        TypeAlias,
         Use,
-    BlockExpr, ConstArg, Param, SelfParam
+    AssocItem, BlockExpr, Variant, RecordField, TupleField, ConstArg, Param, SelfParam
 }
 
 /// Maps items' `SyntaxNode`s to `ErasedFileAstId`s and back.
diff --git a/crates/vfs/src/file_set.rs b/crates/vfs/src/file_set.rs
index 7eeb10d544..0392ef3ceb 100644
--- a/crates/vfs/src/file_set.rs
+++ b/crates/vfs/src/file_set.rs
@@ -123,11 +123,6 @@ impl FileSetConfig {
         self.n_file_sets
     }
 
-    /// Get the lexicographically ordered vector of the underlying map.
-    pub fn roots(&self) -> Vec<(Vec<u8>, u64)> {
-        self.map.stream().into_byte_vec()
-    }
-
     /// Returns the set index for the given `path`.
     ///
     /// `scratch_space` is used as a buffer and will be entirely replaced.
diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md
index 17e2eb5da5..af5b4e51ef 100644
--- a/docs/dev/lsp-extensions.md
+++ b/docs/dev/lsp-extensions.md
@@ -1,5 +1,5 @@
 <!---
-lsp/ext.rs hash: 4b06686d086b7d9b
+lsp/ext.rs hash: 6bc140531b403717
 
 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:
@@ -416,7 +416,9 @@ interface TestItem {
     range?: lc.Range | undefined;
     // A human readable name for this test
     label: string;
-    icon: "package" | "module" | "test";
+    // The kind of this test item. Based on the kind,
+	// an icon is chosen by the editor. 
+    kind: "package" | "module" | "test";
     // True if this test may have children not available eagerly
     canResolveChildren: boolean;
     // The id of the parent test in the test tree. If not present, this test
@@ -425,6 +427,10 @@ interface TestItem {
     // The information useful for running the test. The client can use `runTest`
     // request for simple execution, but for more complex execution forms
     // like debugging, this field is useful.
+    // Note that this field includes some information about label and location as well, but
+    // those exist just for keeping things in sync with other methods of running runnables
+    // (for example using one consistent name in the vscode's launch.json) so for any propose
+    // other than running tests this field should not be used.
     runnable?: Runnable | undefined;
 };
 
@@ -451,8 +457,14 @@ the same as the one in `experimental/discoverTest` response.
 **Request:** `RunTestParams`
 
 ```typescript
-interface DiscoverTestParams {
+interface RunTestParams {
+    // Id of the tests to be run. If a test is included, all of its children are included implicitly. If
+    // this property is undefined, then the server should simply run all tests.
     include?: string[] | undefined;
+    // An array of test ids the user has marked as excluded from the test included in this run; exclusions
+    // should apply after inclusions.
+    // May be omitted if no exclusions were requested. Server should not run excluded tests or
+    // any children of excluded tests.
     exclude?: string[] | undefined;
 }
 ```
@@ -480,9 +492,16 @@ a `experimental/endRunTest` when is done.
 **Notification:** `ChangeTestStateParams`
 
 ```typescript
-type TestState = { tag: "failed"; message: string }
-    | { tag: "passed" }
-    | { tag: "started" };
+type TestState = { tag: "passed" } 
+    | {
+        tag: "failed"; 
+        // The standard error of the test, containing the panic message. Clients should
+        // render it similar to a terminal, and e.g. handle ansi colors.
+        message: string;
+    }
+    | { tag: "started" }
+    | { tag: "enqueued" }
+    | { tag: "skipped" };
 
 interface ChangeTestStateParams {
     testId: string;
diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts
index acbc5cbeae..31ac3d9413 100644
--- a/editors/code/src/lsp_ext.ts
+++ b/editors/code/src/lsp_ext.ts
@@ -76,7 +76,7 @@ export type RunTestParams = {
 export type TestItem = {
     id: string;
     label: string;
-    icon: "package" | "module" | "test";
+    kind: "package" | "module" | "test";
     canResolveChildren: boolean;
     parent?: string | undefined;
     textDocument?: lc.TextDocumentIdentifier | undefined;
@@ -84,7 +84,12 @@ export type TestItem = {
     runnable?: Runnable | undefined;
 };
 export type DiscoverTestResults = { tests: TestItem[]; scope: string[] };
-export type TestState = { tag: "failed"; message: string } | { tag: "passed" } | { tag: "started" };
+export type TestState =
+    | { tag: "failed"; message: string }
+    | { tag: "passed" }
+    | { tag: "started" }
+    | { tag: "enqueued" }
+    | { tag: "skipped" };
 export type ChangeTestStateParams = { testId: string; state: TestState };
 export const discoverTest = new lc.RequestType<DiscoverTestParams, DiscoverTestResults, void>(
     "experimental/discoverTest",


Test patch:
diff --git a/crates/flycheck/src/test_runner.rs b/crates/flycheck/src/test_runner.rs
index b7d966989b..6dac5899ee 100644
--- a/crates/flycheck/src/test_runner.rs
+++ b/crates/flycheck/src/test_runner.rs
@@ -14,6 +14,7 @@ use crate::command::{CommandHandle, ParseFromLine};
 pub enum TestState {
     Started,
     Ok,
+    Ignored,
     Failed { stdout: String },
 }
 
diff --git a/crates/salsa/tests/cycles.rs b/crates/salsa/tests/cycles.rs
index ea5d15a250..2be167bd8d 100644
--- a/crates/salsa/tests/cycles.rs
+++ b/crates/salsa/tests/cycles.rs
@@ -171,8 +171,8 @@ fn cycle_memoized() {
     let cycle = extract_cycle(|| db.memoized_a());
     expect![[r#"
         [
-            "cycles::MemoizedAQuery::memoized_a(())",
-            "cycles::MemoizedBQuery::memoized_b(())",
+            "memoized_a(())",
+            "memoized_b(())",
         ]
     "#]]
     .assert_debug_eq(&cycle.unexpected_participants(&db));
@@ -184,8 +184,8 @@ fn cycle_volatile() {
     let cycle = extract_cycle(|| db.volatile_a());
     expect![[r#"
         [
-            "cycles::VolatileAQuery::volatile_a(())",
-            "cycles::VolatileBQuery::volatile_b(())",
+            "volatile_a(())",
+            "volatile_b(())",
         ]
     "#]]
     .assert_debug_eq(&cycle.unexpected_participants(&db));
@@ -222,8 +222,8 @@ fn inner_cycle() {
     let cycle = err.unwrap_err().cycle;
     expect![[r#"
         [
-            "cycles::CycleAQuery::cycle_a(())",
-            "cycles::CycleBQuery::cycle_b(())",
+            "cycle_a(())",
+            "cycle_b(())",
         ]
     "#]]
     .assert_debug_eq(&cycle);
@@ -262,8 +262,8 @@ fn cycle_revalidate_unchanged_twice() {
         Err(
             Error {
                 cycle: [
-                    "cycles::CycleAQuery::cycle_a(())",
-                    "cycles::CycleBQuery::cycle_b(())",
+                    "cycle_a(())",
+                    "cycle_b(())",
                 ],
             },
         )
@@ -344,8 +344,8 @@ fn cycle_mixed_1() {
         Err(
             Error {
                 cycle: [
-                    "cycles::CycleBQuery::cycle_b(())",
-                    "cycles::CycleCQuery::cycle_c(())",
+                    "cycle_b(())",
+                    "cycle_c(())",
                 ],
             },
         )
@@ -371,9 +371,9 @@ fn cycle_mixed_2() {
         Err(
             Error {
                 cycle: [
-                    "cycles::CycleAQuery::cycle_a(())",
-                    "cycles::CycleBQuery::cycle_b(())",
-                    "cycles::CycleCQuery::cycle_c(())",
+                    "cycle_a(())",
+                    "cycle_b(())",
+                    "cycle_c(())",
                 ],
             },
         )
@@ -400,16 +400,16 @@ fn cycle_deterministic_order() {
             Err(
                 Error {
                     cycle: [
-                        "cycles::CycleAQuery::cycle_a(())",
-                        "cycles::CycleBQuery::cycle_b(())",
+                        "cycle_a(())",
+                        "cycle_b(())",
                     ],
                 },
             ),
             Err(
                 Error {
                     cycle: [
-                        "cycles::CycleAQuery::cycle_a(())",
-                        "cycles::CycleBQuery::cycle_b(())",
+                        "cycle_a(())",
+                        "cycle_b(())",
                     ],
                 },
             ),
@@ -445,24 +445,24 @@ fn cycle_multiple() {
             Err(
                 Error {
                     cycle: [
-                        "cycles::CycleAQuery::cycle_a(())",
-                        "cycles::CycleBQuery::cycle_b(())",
+                        "cycle_a(())",
+                        "cycle_b(())",
                     ],
                 },
             ),
             Err(
                 Error {
                     cycle: [
-                        "cycles::CycleAQuery::cycle_a(())",
-                        "cycles::CycleBQuery::cycle_b(())",
+                        "cycle_a(())",
+                        "cycle_b(())",
                     ],
                 },
             ),
             Err(
                 Error {
                     cycle: [
-                        "cycles::CycleAQuery::cycle_a(())",
-                        "cycles::CycleBQuery::cycle_b(())",
+                        "cycle_a(())",
+                        "cycle_b(())",
                     ],
                 },
             ),
@@ -485,7 +485,7 @@ fn cycle_recovery_set_but_not_participating() {
     let r = extract_cycle(|| drop(db.cycle_a()));
     expect![[r#"
         [
-            "cycles::CycleCQuery::cycle_c(())",
+            "cycle_c(())",
         ]
     "#]]
     .assert_debug_eq(&r.all_participants(&db));
diff --git a/crates/salsa/tests/on_demand_inputs.rs b/crates/salsa/tests/on_demand_inputs.rs
index cad594f536..677d633ee7 100644
--- a/crates/salsa/tests/on_demand_inputs.rs
+++ b/crates/salsa/tests/on_demand_inputs.rs
@@ -103,10 +103,10 @@ fn on_demand_input_durability() {
     expect_test::expect![[r#"
         RefCell {
             value: [
-                "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: on_demand_inputs::BQuery::b(1) } }",
-                "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: on_demand_inputs::AQuery::a(1) } }",
-                "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: on_demand_inputs::BQuery::b(2) } }",
-                "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: on_demand_inputs::AQuery::a(2) } }",
+                "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: b(1) } }",
+                "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: a(1) } }",
+                "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: b(2) } }",
+                "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: a(2) } }",
             ],
         }
     "#]].assert_debug_eq(&events);
@@ -119,11 +119,11 @@ fn on_demand_input_durability() {
     expect_test::expect![[r#"
         RefCell {
             value: [
-                "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: on_demand_inputs::CQuery::c(1) } }",
-                "Event { runtime_id: RuntimeId { counter: 0 }, kind: DidValidateMemoizedValue { database_key: on_demand_inputs::BQuery::b(1) } }",
-                "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: on_demand_inputs::CQuery::c(2) } }",
-                "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: on_demand_inputs::AQuery::a(2) } }",
-                "Event { runtime_id: RuntimeId { counter: 0 }, kind: DidValidateMemoizedValue { database_key: on_demand_inputs::BQuery::b(2) } }",
+                "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: c(1) } }",
+                "Event { runtime_id: RuntimeId { counter: 0 }, kind: DidValidateMemoizedValue { database_key: b(1) } }",
+                "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: c(2) } }",
+                "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: a(2) } }",
+                "Event { runtime_id: RuntimeId { counter: 0 }, kind: DidValidateMemoizedValue { database_key: b(2) } }",
             ],
         }
     "#]].assert_debug_eq(&events);
@@ -137,10 +137,10 @@ fn on_demand_input_durability() {
     expect_test::expect![[r#"
         RefCell {
             value: [
-                "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: on_demand_inputs::AQuery::a(1) } }",
-                "Event { runtime_id: RuntimeId { counter: 0 }, kind: DidValidateMemoizedValue { database_key: on_demand_inputs::CQuery::c(1) } }",
-                "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: on_demand_inputs::AQuery::a(2) } }",
-                "Event { runtime_id: RuntimeId { counter: 0 }, kind: DidValidateMemoizedValue { database_key: on_demand_inputs::CQuery::c(2) } }",
+                "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: a(1) } }",
+                "Event { runtime_id: RuntimeId { counter: 0 }, kind: DidValidateMemoizedValue { database_key: c(1) } }",
+                "Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: a(2) } }",
+                "Event { runtime_id: RuntimeId { counter: 0 }, kind: DidValidateMemoizedValue { database_key: c(2) } }",
             ],
         }
     "#]].assert_debug_eq(&events);
diff --git a/crates/salsa/tests/parallel/parallel_cycle_none_recover.rs b/crates/salsa/tests/parallel/parallel_cycle_none_recover.rs
index 2930c4e379..e94982f513 100644
--- a/crates/salsa/tests/parallel/parallel_cycle_none_recover.rs
+++ b/crates/salsa/tests/parallel/parallel_cycle_none_recover.rs
@@ -27,8 +27,8 @@ fn parallel_cycle_none_recover() {
     if let Some(c) = err_b.downcast_ref::<salsa::Cycle>() {
         expect![[r#"
             [
-                "parallel::parallel_cycle_none_recover::AQuery::a(-1)",
-                "parallel::parallel_cycle_none_recover::BQuery::b(-1)",
+                "a(-1)",
+                "b(-1)",
             ]
         "#]]
         .assert_debug_eq(&c.unexpected_participants(&db));
diff --git a/editors/code/src/test_explorer.ts b/editors/code/src/test_explorer.ts
index e89a5cfc39..2f0b4d5b5c 100644
--- a/editors/code/src/test_explorer.ts
+++ b/editors/code/src/test_explorer.ts
@@ -85,7 +85,7 @@ export const prepareTestExplorer = (
         };
         const test = testController.createTestItem(
             item.id,
-            `$(${iconToVscodeMap[item.icon]}) ${item.label}`,
+            `$(${iconToVscodeMap[item.kind]}) ${item.label}`,
             uri,
         );
         test.range = range;
@@ -150,6 +150,10 @@ export const prepareTestExplorer = (
                 currentTestRun!.passed(test);
             } else if (results.state.tag === "started") {
                 currentTestRun!.started(test);
+            } else if (results.state.tag === "skipped") {
+                currentTestRun!.skipped(test);
+            } else if (results.state.tag === "enqueued") {
+                currentTestRun!.enqueued(test);
             }
         }),
     );