Rollup merge of #158244 - kantnero:attribute-docs-deprecated-warn, r=GuillaumeGomez,traviscross

Attribute docs `deprecated` , `warn`, `allow`, `cfg`, `deny`, and `forbid`

Added documentation for built-in `deprecated` , `warn`, `allow`, `cfg`, `deny`, and `forbid`  attributes using the #[doc(attribute = "...")] mechanism.

Part of rust-lang/rust#157604

@rustbot r? @GuillaumeGomez
cc @fmease
cc @mejrs
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..1d270e7
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,2 @@
+github: rustfoundation
+custom: ["rust-lang.org/funding"]
diff --git a/.github/actions/github-release/Dockerfile b/.github/actions/github-release/Dockerfile
index 5849eac..ad296ce 100644
--- a/.github/actions/github-release/Dockerfile
+++ b/.github/actions/github-release/Dockerfile
@@ -1,4 +1,4 @@
-FROM node:slim
+FROM node:24.16-slim@sha256:ca520832af80fa37a57c14077ed0fcdd83b5aefccc356059fdc3a9a05b78ae1f
 
 COPY . /action
 WORKDIR /action
diff --git a/.gitignore b/.gitignore
index 3beabe3..32374da 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,6 +7,7 @@
 *.log
 *.iml
 .vscode/settings.json
+.vim/coc-settings.json
 .DS_Store
 /out/
 /dump.lsif
diff --git a/Cargo.lock b/Cargo.lock
index 5e39dfe..602af6f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -90,7 +90,7 @@
  "cfg-if",
  "libc",
  "miniz_oxide",
- "object 0.37.3",
+ "object",
  "rustc-demangle",
  "windows-link",
 ]
@@ -1003,15 +1003,6 @@
 ]
 
 [[package]]
-name = "home"
-version = "0.5.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d"
-dependencies = [
- "windows-sys 0.61.2",
-]
-
-[[package]]
 name = "icu_collections"
 version = "2.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1417,16 +1408,6 @@
 checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
 
 [[package]]
-name = "libloading"
-version = "0.8.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
-dependencies = [
- "cfg-if",
- "windows-link",
-]
-
-[[package]]
 name = "libmimalloc-sys"
 version = "0.1.49"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1590,15 +1571,6 @@
 checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8"
 
 [[package]]
-name = "memmap2"
-version = "0.9.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3"
-dependencies = [
- "libc",
-]
-
-[[package]]
 name = "memoffset"
 version = "0.9.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1758,15 +1730,6 @@
 
 [[package]]
 name = "object"
-version = "0.36.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
-dependencies = [
- "memchr",
-]
-
-[[package]]
-name = "object"
 version = "0.37.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe"
@@ -1992,11 +1955,7 @@
 dependencies = [
  "expect-test",
  "intern",
- "libc",
- "libloading",
  "line-index 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "memmap2",
- "object 0.36.7",
  "paths",
  "proc-macro-test",
  "span",
@@ -3148,7 +3107,6 @@
 version = "0.0.0"
 dependencies = [
  "camino",
- "home",
 ]
 
 [[package]]
diff --git a/Cargo.toml b/Cargo.toml
index 37e5dbb..3e2815e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -116,17 +116,8 @@
 indexmap = { version = "2.9.0", features = ["serde"] }
 itertools = "0.14.0"
 libc = "0.2.172"
-libloading = "0.8.8"
-memmap2 = "0.9.5"
 nohash-hasher = "0.2.0"
 oorandom = "11.1.5"
-object = { version = "0.36.7", default-features = false, features = [
-    "std",
-    "read_core",
-    "elf",
-    "macho",
-    "pe",
-] }
 postcard = { version = "1.1.3", features = ["alloc"] }
 process-wrap = { version = "9.1.0", features = ["std"] }
 pulldown-cmark-to-cmark = "10.0.4"
diff --git a/crates/hir-def/src/expr_store/lower.rs b/crates/hir-def/src/expr_store/lower.rs
index 3094e08..a69755b 100644
--- a/crates/hir-def/src/expr_store/lower.rs
+++ b/crates/hir-def/src/expr_store/lower.rs
@@ -671,6 +671,7 @@
                     }
 
                     pl.params()
+                        .filter(|it| it.dotdotdot_token().is_none())
                         .map(|it| {
                             let type_ref = self.lower_type_ref_opt(it.ty(), impl_trait_lower_fn);
                             let name = match it.pat() {
@@ -2403,6 +2404,10 @@
         statements: &mut Vec<Statement>,
         mac: ast::MacroExpr,
     ) -> Option<ExprId> {
+        if !self.check_cfg(&ast::Expr::MacroExpr(mac.clone())) {
+            return None;
+        }
+
         let mac_call = mac.macro_call()?;
         let syntax_ptr = AstPtr::new(&ast::Expr::from(mac));
         let macro_ptr = AstPtr::new(&mac_call);
@@ -2446,10 +2451,6 @@
             }
             ast::Stmt::ExprStmt(stmt) => {
                 let expr = stmt.expr();
-                match &expr {
-                    Some(expr) if !self.check_cfg(expr) => return,
-                    _ => (),
-                }
                 let has_semi = stmt.semicolon_token().is_some();
                 // Note that macro could be expanded to multiple statements
                 if let Some(ast::Expr::MacroExpr(mac)) = expr {
diff --git a/crates/hir-def/src/expr_store/tests/body.rs b/crates/hir-def/src/expr_store/tests/body.rs
index e97718c..da412a6 100644
--- a/crates/hir-def/src/expr_store/tests/body.rs
+++ b/crates/hir-def/src/expr_store/tests/body.rs
@@ -688,3 +688,31 @@
         }"#]],
     );
 }
+
+#[test]
+fn foo() {
+    pretty_print(
+        r#"
+macro_rules! foo {
+    () => {
+        1
+    };
+}
+
+fn foo() -> i64 {
+    #[cfg(true)]
+    {
+        5
+    }
+    #[cfg(false)]
+    foo!()
+}
+    "#,
+        expect![[r#"
+            fn foo() {
+                {
+                    5
+                }
+            }"#]],
+    );
+}
diff --git a/crates/hir-expand/src/db.rs b/crates/hir-expand/src/db.rs
index 5131156..33672d1 100644
--- a/crates/hir-expand/src/db.rs
+++ b/crates/hir-expand/src/db.rs
@@ -141,11 +141,6 @@
     fn syntax_context(&self, file: HirFileId, edition: Edition) -> SyntaxContext;
 }
 
-#[salsa_macros::interned(no_lifetime, id = span::SyntaxContext, revisions = usize::MAX)]
-pub struct SyntaxContextWrapper {
-    pub data: SyntaxContext,
-}
-
 fn syntax_context(db: &dyn ExpandDatabase, file: HirFileId, edition: Edition) -> SyntaxContext {
     match file {
         HirFileId::FileId(_) => SyntaxContext::root(edition),
diff --git a/crates/hir-ty/src/consteval.rs b/crates/hir-ty/src/consteval.rs
index d6580d3..a880ae5 100644
--- a/crates/hir-ty/src/consteval.rs
+++ b/crates/hir-ty/src/consteval.rs
@@ -87,18 +87,24 @@
     let valtree = match (ty.kind(), value) {
         (TyKind::Uint(uint), Literal::Uint(value, _)) => {
             let size = uint.bit_width().map(Size::from_bits).unwrap_or(data_layout.pointer_size());
-            let scalar = ScalarInt::try_from_uint(*value, size).unwrap();
+            let Some(scalar) = ScalarInt::try_from_uint(*value, size) else {
+                return Ok(Const::error(interner));
+            };
             ValTreeKind::Leaf(scalar)
         }
         (TyKind::Uint(uint), Literal::Int(value, _)) => {
             // `Literal::Int` is the default, so we also need to account for the type being uint.
             let size = uint.bit_width().map(Size::from_bits).unwrap_or(data_layout.pointer_size());
-            let scalar = ScalarInt::try_from_uint(*value as u128, size).unwrap();
+            let Some(scalar) = ScalarInt::try_from_uint(*value as u128, size) else {
+                return Ok(Const::error(interner));
+            };
             ValTreeKind::Leaf(scalar)
         }
         (TyKind::Int(int), Literal::Int(value, _)) => {
             let size = int.bit_width().map(Size::from_bits).unwrap_or(data_layout.pointer_size());
-            let scalar = ScalarInt::try_from_int(*value, size).unwrap();
+            let Some(scalar) = ScalarInt::try_from_int(*value, size) else {
+                return Ok(Const::error(interner));
+            };
             ValTreeKind::Leaf(scalar)
         }
         (TyKind::Bool, Literal::Bool(value)) => ValTreeKind::Leaf(ScalarInt::from(*value)),
@@ -219,7 +225,9 @@
         return Const::error(interner);
     };
     let usize_ty = interner.default_types().types.usize;
-    let scalar = ScalarInt::try_from_uint(value, data_layout.pointer_size()).unwrap();
+    let Some(scalar) = ScalarInt::try_from_uint(value, data_layout.pointer_size()) else {
+        return Const::error(interner);
+    };
     Const::new_valtree(interner, usize_ty, ValTreeKind::Leaf(scalar))
 }
 
diff --git a/crates/hir-ty/src/consteval/tests.rs b/crates/hir-ty/src/consteval/tests.rs
index 5421b97..0e51594 100644
--- a/crates/hir-ty/src/consteval/tests.rs
+++ b/crates/hir-ty/src/consteval/tests.rs
@@ -2634,6 +2634,19 @@
 }
 
 #[test]
+fn const_generic_fixed_width() {
+    check_number(
+        r#"
+    const fn m<const N: u64>() -> u64 {
+        N
+    }
+    const GOAL: u64 = m::<0>();
+    "#,
+        0,
+    );
+}
+
+#[test]
 fn layout_of_type_with_associated_type_field_defined_inside_body() {
     check_number(
         r#"
diff --git a/crates/hir-ty/src/db.rs b/crates/hir-ty/src/db.rs
index baf8bbd..93ae26a 100644
--- a/crates/hir-ty/src/db.rs
+++ b/crates/hir-ty/src/db.rs
@@ -6,8 +6,8 @@
 use either::Either;
 use hir_def::{
     AdtId, BuiltinDeriveImplId, CallableDefId, ConstId, ConstParamId, EnumVariantId,
-    ExpressionStoreOwnerId, FunctionId, GenericDefId, HasModule, ImplId, LifetimeParamId,
-    LocalFieldId, ModuleId, StaticId, TraitId, TypeAliasId, VariantId,
+    ExpressionStoreOwnerId, FunctionId, GenericDefId, HasModule, ImplId, LocalFieldId, ModuleId,
+    StaticId, TraitId, TypeAliasId, VariantId,
     builtin_derive::BuiltinDeriveImplMethod,
     db::DefDatabase,
     expr_store::ExpressionStore,
@@ -296,19 +296,6 @@
 
 #[salsa_macros::interned(no_lifetime, debug, revisions = usize::MAX)]
 #[derive(PartialOrd, Ord)]
-pub struct InternedLifetimeParamId {
-    /// This stores the param and its index.
-    pub loc: (LifetimeParamId, u32),
-}
-
-#[salsa_macros::interned(no_lifetime, debug, revisions = usize::MAX)]
-#[derive(PartialOrd, Ord)]
-pub struct InternedConstParamId {
-    pub loc: ConstParamId,
-}
-
-#[salsa_macros::interned(no_lifetime, debug, revisions = usize::MAX)]
-#[derive(PartialOrd, Ord)]
 pub struct InternedOpaqueTyId {
     pub loc: ImplTraitId,
 }
diff --git a/crates/hir-ty/src/infer/callee.rs b/crates/hir-ty/src/infer/callee.rs
index 057ba7f..a661731 100644
--- a/crates/hir-ty/src/infer/callee.rs
+++ b/crates/hir-ty/src/infer/callee.rs
@@ -173,6 +173,8 @@
                 // impl forces the closure kind to `FnOnce` i.e. `u8`.
                 let kind_ty = autoderef.ctx().table.next_ty_var(call_expr.into());
                 let interner = autoderef.ctx().interner();
+
+                // Ignore splatting, it is unsupported on closures.
                 let call_sig = interner.mk_fn_sig(
                     [coroutine_closure_sig.tupled_inputs_ty],
                     coroutine_closure_sig.to_coroutine(
diff --git a/crates/hir-ty/src/infer/closure/analysis/expr_use_visitor.rs b/crates/hir-ty/src/infer/closure/analysis/expr_use_visitor.rs
index 34f9508..d8a8cce 100644
--- a/crates/hir-ty/src/infer/closure/analysis/expr_use_visitor.rs
+++ b/crates/hir-ty/src/infer/closure/analysis/expr_use_visitor.rs
@@ -1016,8 +1016,9 @@
                     }
                 }
                 Pat::Expr(expr) => {
-                    // Destructuring assignment.
                     this.mutate_expr(expr)?;
+                    // Destructuring assignment moves
+                    this.consume_or_copy(place);
                 }
                 Pat::Or(_)
                 | Pat::Box { .. }
diff --git a/crates/hir-ty/src/lib.rs b/crates/hir-ty/src/lib.rs
index cc48ba0..1fddfc0 100644
--- a/crates/hir-ty/src/lib.rs
+++ b/crates/hir-ty/src/lib.rs
@@ -464,6 +464,7 @@
         args.tuple_fields(),
         ret,
         false,
+        // FIXME(splat): handle splatted arguments
         Safety::Safe,
         ExternAbi::Rust,
     ));
diff --git a/crates/hir-ty/src/lower.rs b/crates/hir-ty/src/lower.rs
index fae63dd..d63ac7f 100644
--- a/crates/hir-ty/src/lower.rs
+++ b/crates/hir-ty/src/lower.rs
@@ -637,6 +637,7 @@
                     fn_.abi,
                     if fn_.is_unsafe { Safety::Unsafe } else { Safety::Safe },
                     fn_.is_varargs,
+                    // FIXME(splat): handle splatted arguments
                 ),
                 inputs_and_output: Tys::new_from_slice(&args),
             }),
diff --git a/crates/hir-ty/src/mir/eval.rs b/crates/hir-ty/src/mir/eval.rs
index eb0e4c6..c69febd 100644
--- a/crates/hir-ty/src/mir/eval.rs
+++ b/crates/hir-ty/src/mir/eval.rs
@@ -1935,25 +1935,37 @@
                 Ok(Interval::new(addr, 4))
             }
             TyKind::Int(int_ty) => {
-                let size = int_ty.bit_width().unwrap_or(self.ptr_size() as u64);
-                let value = valtree.inner().to_leaf().to_int(Size::from_bytes(size));
-                let addr = self.heap_allocate(size as usize, size as usize)?;
-                self.write_memory(addr, &value.to_le_bytes()[..size as usize])?;
-                Ok(Interval::new(addr, size as usize))
+                let size = int_ty
+                    .bit_width()
+                    .map(Size::from_bits)
+                    .unwrap_or_else(|| Size::from_bytes(self.ptr_size() as u64));
+                let bytes = size.bytes_usize();
+
+                let value = valtree.inner().to_leaf().to_int(size);
+                let addr = self.heap_allocate(bytes, bytes)?;
+                self.write_memory(addr, &value.to_le_bytes()[..bytes])?;
+                Ok(Interval::new(addr, bytes))
             }
             TyKind::Uint(uint_ty) => {
-                let size = uint_ty.bit_width().unwrap_or(self.ptr_size() as u64);
-                let value = valtree.inner().to_leaf().to_uint(Size::from_bytes(size));
-                let addr = self.heap_allocate(size as usize, size as usize)?;
-                self.write_memory(addr, &value.to_le_bytes()[..size as usize])?;
-                Ok(Interval::new(addr, size as usize))
+                let size = uint_ty
+                    .bit_width()
+                    .map(Size::from_bits)
+                    .unwrap_or_else(|| Size::from_bytes(self.ptr_size() as u64));
+                let bytes = size.bytes_usize();
+
+                let value = valtree.inner().to_leaf().to_uint(size);
+                let addr = self.heap_allocate(bytes, bytes)?;
+                self.write_memory(addr, &value.to_le_bytes()[..bytes])?;
+                Ok(Interval::new(addr, bytes))
             }
             TyKind::Float(float_ty) => {
-                let size = float_ty.bit_width();
-                let value = valtree.inner().to_leaf().to_uint(Size::from_bytes(size));
-                let addr = self.heap_allocate(size as usize, size as usize)?;
-                self.write_memory(addr, &value.to_le_bytes()[..size as usize])?;
-                Ok(Interval::new(addr, size as usize))
+                let size = Size::from_bits(float_ty.bit_width());
+                let bytes = size.bytes_usize();
+
+                let value = valtree.inner().to_leaf().to_uint(size);
+                let addr = self.heap_allocate(bytes, bytes)?;
+                self.write_memory(addr, &value.to_le_bytes()[..bytes])?;
+                Ok(Interval::new(addr, bytes))
             }
             TyKind::RawPtr(..) => {
                 let size = self.ptr_size();
@@ -1995,15 +2007,13 @@
                             _ => not_supported!("unsupported const"),
                         })
                         .collect::<Result<'_, Vec<_>>>()?;
+                    let item_size = item_layout.size.bytes_usize();
                     let items_addr = self.heap_allocate(
-                        items.len() * (item_layout.size.bits() as usize),
-                        item_layout.align.bits_usize(),
+                        items.len() * item_size,
+                        item_layout.align.bytes() as usize,
                     )?;
                     for (i, item) in items.iter().enumerate() {
-                        self.copy_from_interval(
-                            items_addr.offset(i * (item_layout.size.bits() as usize)),
-                            *item,
-                        )?;
+                        self.copy_from_interval(items_addr.offset(i * item_size), *item)?;
                     }
                     let ref_addr = self.heap_allocate(self.ptr_size() * 2, self.ptr_size())?;
                     self.write_memory(ref_addr, &items_addr.to_bytes())?;
diff --git a/crates/hir-ty/src/next_solver/infer/mod.rs b/crates/hir-ty/src/next_solver/infer/mod.rs
index 6b6dd54..2c2f7db 100644
--- a/crates/hir-ty/src/next_solver/infer/mod.rs
+++ b/crates/hir-ty/src/next_solver/infer/mod.rs
@@ -843,7 +843,7 @@
         GenericArgs::for_item(self.interner, def_id, |_index, kind, _| self.var_for_def(kind, span))
     }
 
-    /// Like `fresh_args_for_item()`, but first uses the args from `first`.
+    /// Like [`Self::fresh_args_for_item`], but first uses the args from `first`.
     pub fn fill_rest_fresh_args(
         &self,
         span: Span,
diff --git a/crates/hir-ty/src/next_solver/interner.rs b/crates/hir-ty/src/next_solver/interner.rs
index b466fe0..3c2976e 100644
--- a/crates/hir-ty/src/next_solver/interner.rs
+++ b/crates/hir-ty/src/next_solver/interner.rs
@@ -1128,6 +1128,9 @@
             SolverDefId::ConstId(def_id) => {
                 AliasTermKind::UnevaluatedConst { def_id: GeneralConstIdWrapper(def_id.into()) }
             }
+            SolverDefId::StaticId(def_id) => {
+                AliasTermKind::UnevaluatedConst { def_id: GeneralConstIdWrapper(def_id.into()) }
+            }
             SolverDefId::AnonConstId(def_id) => {
                 AliasTermKind::UnevaluatedConst { def_id: GeneralConstIdWrapper(def_id.into()) }
             }
@@ -2235,6 +2238,7 @@
         self.replace_escaping_bound_vars_uncached(value.skip_binder(), delegate)
     }
 
+    // FIXME: add splat support when the experiment is complete
     pub fn mk_fn_sig<I>(
         self,
         inputs: I,
@@ -2626,8 +2630,8 @@
                 }
 
                 #[inline]
-                fn visit_slice(header: &[<Self as ::intern::SliceInternable>::SliceType], gc: &mut ::intern::GarbageCollector) {
-                    header.generic_visit_with(gc);
+                fn visit_slice(slice: &[<Self as ::intern::SliceInternable>::SliceType], gc: &mut ::intern::GarbageCollector) {
+                    slice.generic_visit_with(gc);
                 }
             }
         )*
diff --git a/crates/hir-ty/src/next_solver/ty.rs b/crates/hir-ty/src/next_solver/ty.rs
index fe31d44..d57d824 100644
--- a/crates/hir-ty/src/next_solver/ty.rs
+++ b/crates/hir-ty/src/next_solver/ty.rs
@@ -1514,6 +1514,7 @@
                 TyKind::Tuple(params) => params,
                 _ => panic!(),
             };
+            // Ignore splatting, it is unsupported on closures.
             self.mk_fn_sig(params, s.output(), s.c_variadic(), safety, ExternAbi::Rust)
         })
     }
diff --git a/crates/hir-ty/src/tests/closure_captures.rs b/crates/hir-ty/src/tests/closure_captures.rs
index 3942a7a..bf0e60c 100644
--- a/crates/hir-ty/src/tests/closure_captures.rs
+++ b/crates/hir-ty/src/tests/closure_captures.rs
@@ -617,3 +617,40 @@
         expect!["102..126;85..88;114..117 ByRef(Immutable) *foo &'<erased> Foo"],
     );
 }
+
+#[test]
+fn method_call_field_access_regression() {
+    check_closure_captures(
+        r#"
+//- minicore:copy, fn
+struct NonCopy;
+
+struct Wrapper {
+    field: NonCopy,
+}
+
+impl Wrapper {
+    fn wrapped(&self) -> NonCopy {
+        NonCopy
+    }
+}
+
+pub struct Wrapper2 {
+    field1: NonCopy,
+    field2: NonCopy,
+}
+
+fn fun(wrapper: Wrapper) {
+    pub fn update<T>(_: impl FnOnce(&mut T)) {
+        todo!()
+    }
+
+    update::<Wrapper2>(|this| {
+        this.field1 = wrapper.wrapped();
+        this.field2 = wrapper.field;
+    });
+}
+    "#,
+        expect!["319..411;206..213;350..357,391..398 ByValue wrapper Wrapper"],
+    );
+}
diff --git a/crates/hir-ty/src/tests/regression/new_solver.rs b/crates/hir-ty/src/tests/regression/new_solver.rs
index c77b20f..fb00a75 100644
--- a/crates/hir-ty/src/tests/regression/new_solver.rs
+++ b/crates/hir-ty/src/tests/regression/new_solver.rs
@@ -395,6 +395,26 @@
 }
 
 #[test]
+fn static_as_array_len_does_not_panic() {
+    check_no_mismatches(
+        r#"
+static S: usize = 8;
+const A: [u8; S] = [0; 8];
+    "#,
+    );
+}
+
+#[test]
+fn oversized_array_len_does_not_panic() {
+    // The array length literal does not fit in `usize`; interning it must not panic.
+    check_no_mismatches(
+        r#"
+fn f(_: [u8; 18446744073709551616]) {}
+    "#,
+    );
+}
+
+#[test]
 fn another_20654_case() {
     check_no_mismatches(
         r#"
diff --git a/crates/hir-ty/src/traits.rs b/crates/hir-ty/src/traits.rs
index 4c76ae9..2ca9ebe 100644
--- a/crates/hir-ty/src/traits.rs
+++ b/crates/hir-ty/src/traits.rs
@@ -20,7 +20,7 @@
 use intern::sym;
 use rustc_type_ir::{
     TypeVisitableExt, TypingMode,
-    inherent::{BoundExistentialPredicates, IntoKind},
+    inherent::{BoundExistentialPredicates, IntoKind, Ty as _},
 };
 
 use crate::{
@@ -154,6 +154,9 @@
 
     let args = create_args(&infcx);
     let trait_ref = rustc_type_ir::TraitRef::new_from_args(interner, trait_.into(), args);
+    if trait_ref.self_ty().is_ty_error() {
+        return false;
+    }
 
     let obligation = Obligation::new(interner, ObligationCause::dummy(), env.param_env, trait_ref);
     infcx.predicate_must_hold_modulo_regions(&obligation)
diff --git a/crates/ide-assists/src/handlers/destructure_tuple_binding.rs b/crates/ide-assists/src/handlers/destructure_tuple_binding.rs
index 2755962..a272351 100644
--- a/crates/ide-assists/src/handlers/destructure_tuple_binding.rs
+++ b/crates/ide-assists/src/handlers/destructure_tuple_binding.rs
@@ -1236,15 +1236,11 @@
         fn destructure_in_sub_pattern() {
             check_sub_pattern_assist(
                 r#"
-#![feature(bindings_after_at)]
-
 fn main() {
     let $0t = (1,2);
 }
                 "#,
                 r#"
-#![feature(bindings_after_at)]
-
 fn main() {
     let t @ ($0_0, _1) = (1,2);
 }
diff --git a/crates/ide-assists/src/handlers/extract_module.rs b/crates/ide-assists/src/handlers/extract_module.rs
index 40eaed0..40c54e9 100644
--- a/crates/ide-assists/src/handlers/extract_module.rs
+++ b/crates/ide-assists/src/handlers/extract_module.rs
@@ -17,9 +17,10 @@
     ast::{
         self, HasVisibility,
         edit::{AstNodeEdit, IndentLevel},
-        make,
+        syntax_factory::SyntaxFactory,
     },
-    match_ast, ted,
+    match_ast,
+    syntax_editor::{Position, SyntaxEditor},
 };
 
 use crate::{AssistContext, Assists};
@@ -121,15 +122,18 @@
             let (usages_to_be_processed, record_fields, use_stmts_to_be_inserted) =
                 module.get_usages_and_record_fields(ctx, module_text_range);
 
+            let make = SyntaxFactory::without_mappings();
+
             builder.edit_file(ctx.vfs_file_id());
             use_stmts_to_be_inserted.into_iter().for_each(|(_, use_stmt)| {
                 builder.insert(ctx.selection_trimmed().end(), format!("\n{use_stmt}"));
             });
 
-            let import_items = module.resolve_imports(curr_parent_module, ctx);
-            module.change_visibility(record_fields);
+            let import_items = module.resolve_imports(curr_parent_module, ctx, &make);
+            module.change_visibility(record_fields, &make);
 
-            let module_def = generate_module_def(&impl_parent, &module).indent(old_item_indent);
+            let module_def =
+                generate_module_def(&impl_parent, &module, &make).indent(old_item_indent);
 
             let mut usages_to_be_processed_for_cur_file = vec![];
             for (file_id, usages) in usages_to_be_processed {
@@ -185,6 +189,7 @@
 fn generate_module_def(
     parent_impl: &Option<ast::Impl>,
     Module { name, body_items, use_items }: &Module,
+    make: &SyntaxFactory,
 ) -> ast::Module {
     let items: Vec<_> = if let Some(impl_) = parent_impl.as_ref()
         && let Some(self_ty) = impl_.self_ty()
@@ -195,11 +200,15 @@
             .filter_map(ast::AssocItem::cast)
             .map(|it| it.indent(IndentLevel(1)))
             .collect_vec();
-        let assoc_item_list = make::assoc_item_list(Some(assoc_items)).clone_for_update();
-        let impl_ = impl_.reset_indent();
-        ted::replace(impl_.get_or_create_assoc_item_list().syntax(), assoc_item_list.syntax());
+        let impl_reset = impl_.reset_indent();
+        let (editor, impl_root) = SyntaxEditor::with_ast_node(&impl_reset);
+        let assoc_item_list = editor.make().assoc_item_list(assoc_items);
+        if let Some(existing_list) = impl_root.assoc_item_list() {
+            editor.replace(existing_list.syntax(), assoc_item_list.syntax());
+        }
+        let impl_ = ast::Impl::cast(editor.finish().new_root().clone()).unwrap();
         // Add the import for enum/struct corresponding to given impl block
-        let use_impl = make_use_stmt_of_node_with_super(self_ty.syntax());
+        let use_impl = make_use_stmt_of_node_with_super(self_ty.syntax(), make);
         once(use_impl)
             .chain(use_items.iter().cloned())
             .chain(once(ast::Item::Impl(impl_)))
@@ -209,19 +218,18 @@
     };
 
     let items = items.into_iter().map(|it| it.reset_indent().indent(IndentLevel(1))).collect_vec();
-    let module_body = make::item_list(Some(items));
-
-    let module_name = make::name(name);
-    make::mod_(module_name, Some(module_body))
+    let module_body = make.item_list(items);
+    let module_name = make.name(name);
+    make.mod_(module_name, Some(module_body))
 }
 
-fn make_use_stmt_of_node_with_super(node_syntax: &SyntaxNode) -> ast::Item {
-    let super_path = make::ext::ident_path("super");
-    let node_path = make::ext::ident_path(&node_syntax.to_string());
-    let use_ = make::use_(
+fn make_use_stmt_of_node_with_super(node_syntax: &SyntaxNode, make: &SyntaxFactory) -> ast::Item {
+    let super_path = make.ident_path("super");
+    let node_path = make.path_from_text(&node_syntax.to_string());
+    let use_ = make.use_(
+        [],
         None,
-        None,
-        make::use_tree(make::join_paths(vec![super_path, node_path]), None, None, false),
+        make.use_tree(make.path_concat(super_path, node_path), None, None, false),
     );
 
     ast::Item::from(use_)
@@ -385,18 +393,30 @@
                     if use_.syntax().parent().is_some_and(|parent| parent == covering_node)
                         && use_stmts_set.insert(use_.syntax().text_range().start())
                     {
-                        let use_ = use_stmts_to_be_inserted
-                            .entry(use_.syntax().text_range().start())
-                            .or_insert_with(|| use_.clone_subtree().clone_for_update());
-                        for seg in use_
-                            .syntax()
-                            .descendants()
-                            .filter_map(ast::NameRef::cast)
-                            .filter(|seg| seg.syntax().to_string() == name_ref.to_string())
-                        {
-                            let new_ref = make::path_from_text(&format!("{mod_name}::{seg}"))
-                                .clone_for_update();
-                            ted::replace(seg.syntax().parent()?, new_ref.syntax());
+                        let key = use_.syntax().text_range().start();
+                        let entry =
+                            use_stmts_to_be_inserted.entry(key).or_insert_with(|| use_.clone());
+                        let (editor, edit_root) = SyntaxEditor::with_ast_node(&*entry);
+                        let replacements: Vec<_> = {
+                            let make = editor.make();
+                            edit_root
+                                .syntax()
+                                .descendants()
+                                .filter_map(ast::NameRef::cast)
+                                .filter(|seg| seg.syntax().to_string() == name_ref.to_string())
+                                .filter_map(|seg| {
+                                    Some((
+                                        seg.syntax().parent()?,
+                                        make.path_from_text(&format!("{mod_name}::{seg}")),
+                                    ))
+                                })
+                                .collect()
+                        };
+                        if !replacements.is_empty() {
+                            for (parent, new_ref) in &replacements {
+                                editor.replace(parent, new_ref.syntax());
+                            }
+                            *entry = ast::Use::cast(editor.finish().new_root().clone()).unwrap();
                         }
                     }
                 }
@@ -407,18 +427,23 @@
         }
     }
 
-    fn change_visibility(&mut self, record_fields: Vec<SyntaxNode>) {
-        let (mut replacements, record_field_parents, impls) =
-            get_replacements_for_visibility_change(&mut self.body_items, false);
+    fn change_visibility(&mut self, record_fields: Vec<SyntaxNode>, make: &SyntaxFactory) {
+        for item in &mut self.body_items {
+            let (_, root) = SyntaxEditor::with_ast_node(&item.reset_indent());
+            *item = root;
+        }
 
-        let mut impl_items = impls
+        let (mut replacements, record_field_parents, impls) =
+            get_replacements_for_visibility_change(&self.body_items);
+
+        let impl_items = impls
             .into_iter()
             .flat_map(|impl_| impl_.syntax().descendants())
             .filter_map(ast::Item::cast)
             .collect_vec();
 
         let (mut impl_item_replacements, _, _) =
-            get_replacements_for_visibility_change(&mut impl_items, true);
+            get_replacements_for_visibility_change(&impl_items);
 
         replacements.append(&mut impl_item_replacements);
 
@@ -432,17 +457,39 @@
             }
         }
 
-        for (vis, syntax) in replacements {
-            let item = syntax.children_with_tokens().find(|node_or_token| {
-                match node_or_token.kind() {
-                    // We're skipping comments, doc comments, and attribute macros that may precede the keyword
-                    // that the visibility should be placed before.
-                    SyntaxKind::COMMENT | SyntaxKind::ATTR | SyntaxKind::WHITESPACE => false,
-                    _ => true,
-                }
-            });
+        for body_item in &mut self.body_items {
+            let insert_targets: Vec<_> = replacements
+                .iter()
+                .filter(|(vis, syntax)| {
+                    vis.is_none()
+                        && (syntax == body_item.syntax()
+                            || syntax.ancestors().any(|a| &a == body_item.syntax()))
+                })
+                .filter_map(|(_, syntax)| {
+                    syntax.children_with_tokens().find(|nt| {
+                        !matches!(
+                            nt.kind(),
+                            SyntaxKind::COMMENT | SyntaxKind::ATTR | SyntaxKind::WHITESPACE
+                        )
+                    })
+                })
+                .collect();
 
-            add_change_vis(vis, item);
+            if insert_targets.is_empty() {
+                continue;
+            }
+
+            let (editor, _) = SyntaxEditor::new(body_item.syntax().clone());
+            for target in insert_targets {
+                editor.insert_all(
+                    Position::before(target),
+                    vec![
+                        make.visibility_pub_crate().syntax().clone().into(),
+                        make.whitespace(" ").into(),
+                    ],
+                );
+            }
+            *body_item = ast::Item::cast(editor.finish().new_root().clone()).unwrap();
         }
     }
 
@@ -450,6 +497,7 @@
         &mut self,
         module: Option<ast::Module>,
         ctx: &AssistContext<'_, '_>,
+        make: &SyntaxFactory,
     ) -> Vec<TextRange> {
         let mut imports_to_remove = vec![];
         let mut node_set = FxHashSet::default();
@@ -476,7 +524,8 @@
                 })
                 .for_each(|(node, def)| {
                     if node_set.insert(node.to_string())
-                        && let Some(import) = self.process_def_in_sel(def, &node, &module, ctx)
+                        && let Some(import) =
+                            self.process_def_in_sel(def, &node, &module, ctx, make)
                     {
                         check_intersection_and_push(&mut imports_to_remove, import);
                     }
@@ -492,6 +541,7 @@
         use_node: &SyntaxNode,
         curr_parent_module: &Option<ast::Module>,
         ctx: &AssistContext<'_, '_>,
+        make: &SyntaxFactory,
     ) -> Option<TextRange> {
         //We only need to find in the current file
         let selection_range = ctx.selection_trimmed();
@@ -567,7 +617,7 @@
                     // mod -> ust_stmt transversal
                     // true  | false -> super import insertion
                     // true  | true -> super import insertion
-                    let super_use_node = make_use_stmt_of_node_with_super(use_node);
+                    let super_use_node = make_use_stmt_of_node_with_super(use_node, make);
                     self.use_items.insert(0, super_use_node);
                 }
                 None => {}
@@ -590,14 +640,14 @@
                     if !first_path_in_use_tree_str.contains("super")
                         && !first_path_in_use_tree_str.contains("crate")
                     {
-                        let super_path = make::ext::ident_path("super");
+                        let super_path = make.ident_path("super");
                         use_tree_str.push(super_path);
                     }
                 }
 
                 use_tree_paths = Some(use_tree_str);
             } else if def_in_mod && def_out_sel {
-                let super_use_node = make_use_stmt_of_node_with_super(use_node);
+                let super_use_node = make_use_stmt_of_node_with_super(use_node, make);
                 self.use_items.insert(0, super_use_node);
             }
         }
@@ -609,7 +659,7 @@
                 && let Some(first_path_in_use_tree) = use_tree_paths.first()
                 && first_path_in_use_tree.to_string().contains("super")
             {
-                use_tree_paths.insert(0, make::ext::ident_path("super"));
+                use_tree_paths.insert(0, make.ident_path("super"));
             }
 
             let is_item = matches!(
@@ -624,12 +674,12 @@
                     | Definition::TypeAlias(_)
             );
 
-            if (def_out_sel || !is_item) && use_stmt_not_in_sel {
-                let use_ = make::use_(
-                    None,
-                    None,
-                    make::use_tree(make::join_paths(use_tree_paths), None, None, false),
-                );
+            if (def_out_sel || !is_item)
+                && use_stmt_not_in_sel
+                && let Some(joined) =
+                    use_tree_paths.into_iter().reduce(|acc, p| make.path_concat(acc, p))
+            {
+                let use_ = make.use_([], None, make.use_tree(joined, None, None, false));
                 self.use_items.insert(0, ast::Item::from(use_));
             }
         }
@@ -741,8 +791,7 @@
 }
 
 fn get_replacements_for_visibility_change(
-    items: &mut [ast::Item],
-    is_clone_for_updated: bool,
+    items: &[ast::Item],
 ) -> (
     Vec<(Option<ast::Visibility>, SyntaxNode)>,
     Vec<(Option<ast::Visibility>, SyntaxNode)>,
@@ -753,9 +802,6 @@
     let mut impls = Vec::new();
 
     for item in items {
-        if !is_clone_for_updated {
-            *item = item.clone_for_update();
-        }
         //Use stmts are ignored
         macro_rules! push_to_replacement {
             ($it:ident) => {
@@ -812,15 +858,6 @@
     Some(use_tree_str)
 }
 
-fn add_change_vis(vis: Option<ast::Visibility>, node_or_token_opt: Option<syntax::SyntaxElement>) {
-    if vis.is_none()
-        && let Some(node_or_token) = node_or_token_opt
-    {
-        let pub_crate_vis = make::visibility_pub_crate().clone_for_update();
-        ted::insert(ted::Position::before(node_or_token), pub_crate_vis.syntax());
-    }
-}
-
 fn indent_range_before_given_node(node: &SyntaxNode) -> Option<TextRange> {
     node.siblings_with_tokens(syntax::Direction::Prev)
         .find(|x| x.kind() == WHITESPACE)
diff --git a/crates/ide-assists/src/handlers/inline_local_variable.rs b/crates/ide-assists/src/handlers/inline_local_variable.rs
index 531adf6..7a82e8b 100644
--- a/crates/ide-assists/src/handlers/inline_local_variable.rs
+++ b/crates/ide-assists/src/handlers/inline_local_variable.rs
@@ -1,19 +1,20 @@
 use either::{Either, for_both};
-use hir::{PathResolution, Semantics};
+use hir::{EditionedFileId, PathResolution, Semantics};
 use ide_db::{
-    EditionedFileId, RootDatabase,
+    RootDatabase,
     defs::Definition,
-    search::{FileReference, FileReferenceNode, UsageSearchResult},
+    search::{FileReference, UsageSearchResult},
 };
 use syntax::{
-    Direction, TextRange,
+    Direction, T, TextRange,
     ast::{self, AstNode, AstToken, HasName},
-    syntax_editor::{Element, SyntaxEditor},
+    syntax_editor::{Element, Position, SyntaxEditor},
 };
 
 use crate::{
     AssistId,
     assist_context::{AssistContext, Assists},
+    utils::{cover_edit_range, original_range_in},
 };
 
 // Assist: inline_local_variable
@@ -34,11 +35,12 @@
 // ```
 pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext<'_, '_>) -> Option<()> {
     let file_id = ctx.file_id();
+    let source = ctx.source_file().syntax();
     let range = ctx.selection_trimmed();
     let InlineData { let_stmt, delete_let, references, target } =
-        if let Some(path_expr) = ctx.find_node_at_offset::<ast::PathExpr>() {
+        if let Some(path_expr) = ctx.find_node_at_offset_with_descend::<ast::PathExpr>() {
             inline_usage(&ctx.sema, path_expr, range, file_id)
-        } else if let Some(let_stmt) = ctx.find_node_at_offset() {
+        } else if let Some(let_stmt) = ctx.find_node_at_offset_with_descend() {
             inline_let(&ctx.sema, let_stmt, range, file_id)
         } else {
             None
@@ -47,71 +49,65 @@
         either::Either::Left(it) => it.initializer()?,
         either::Either::Right(it) => it.expr()?,
     };
-
-    let wrap_in_parens = references
-        .into_iter()
-        .filter_map(|FileReference { range, name, .. }| match name {
-            FileReferenceNode::NameRef(name) => Some((range, name)),
-            _ => None,
-        })
-        .map(|(range, name_ref)| {
-            if range != name_ref.syntax().text_range() {
-                // Do not rename inside macros
-                // FIXME: This feels like a bad heuristic for macros
-                return None;
+    let needs_parens = |name_ref: &ast::NameRef| {
+        let usage_node =
+            name_ref.syntax().ancestors().find(|it| ast::PathExpr::can_cast(it.kind()));
+        let usage_parent = usage_node.as_ref().and_then(|it| it.parent());
+        match (usage_node, usage_parent) {
+            (Some(usage), Some(parent)) => {
+                initializer_expr.needs_parens_in_place_of(&parent, &usage)
             }
-            let usage_node =
-                name_ref.syntax().ancestors().find(|it| ast::PathExpr::can_cast(it.kind()));
-            let usage_parent_option = usage_node.as_ref().and_then(|it| it.parent());
-            let usage_parent = match usage_parent_option {
-                Some(u) => u,
-                None => return Some((name_ref, false)),
-            };
-            let should_wrap = initializer_expr
-                .needs_parens_in_place_of(&usage_parent, usage_node.as_ref().unwrap());
-            Some((name_ref, should_wrap))
-        })
-        .collect::<Option<Vec<_>>>()?;
-
-    let target = match target {
-        ast::NameOrNameRef::Name(it) => it.syntax().clone(),
-        ast::NameOrNameRef::NameRef(it) => it.syntax().clone(),
+            _ => false,
+        }
     };
 
     acc.add(
         AssistId::refactor_inline("inline_local_variable"),
         "Inline variable",
-        target.text_range(),
-        move |builder| {
-            let editor = builder.make_editor(&target);
+        target,
+        |builder| {
+            let editor = builder.make_editor(source);
             let make = editor.make();
-            if delete_let {
-                editor.delete(let_stmt.syntax());
+            if delete_let
+                && let Some(original) = original_range_in(file_id, &ctx.sema, let_stmt.syntax())
+            {
+                let place = cover_edit_range(source, original);
+                editor.delete_all(place.clone());
 
+                // Processing let-expr in let-chain
+                // FIXME: process let-expr in macro, but this case is very rare
                 if let Some(bin_expr) = let_stmt.syntax().parent().and_then(ast::BinExpr::cast)
                     && let Some(op_token) = bin_expr.op_token()
                 {
                     editor.delete(&op_token);
                     remove_whitespace(op_token, Direction::Prev, &editor);
-                    remove_whitespace(let_stmt.syntax(), Direction::Prev, &editor);
+                    remove_whitespace(place.start(), Direction::Prev, &editor);
                 } else {
-                    remove_whitespace(let_stmt.syntax(), Direction::Next, &editor);
+                    remove_whitespace(place.end(), Direction::Next, &editor);
                 }
             }
 
-            for (name, should_wrap) in wrap_in_parens {
-                let replacement = if should_wrap {
+            for FileReference { range, name, .. } in references {
+                let Some(name) = name.as_name_ref().cloned() else { continue };
+                let replacement = if needs_parens(&name) {
                     make.expr_paren(initializer_expr.clone()).into()
                 } else {
                     initializer_expr.clone()
                 };
 
-                if let Some(record_field) = ast::RecordExprField::for_field_name(&name) {
+                let place = cover_edit_range(source, range);
+                if ast::RecordExprField::for_field_name(&name).is_some() {
                     cov_mark::hit!(inline_field_shorthand);
-                    let replacement = make.record_expr_field(name, Some(replacement));
-                    editor.replace(record_field.syntax(), replacement.syntax());
+                    editor.insert_all(
+                        Position::after(place.end()),
+                        vec![
+                            make.token(T![:]).into(),
+                            make.whitespace(" ").into(),
+                            replacement.syntax().clone().into(),
+                        ],
+                    );
                 } else {
-                    editor.replace(name.syntax(), replacement.syntax());
+                    editor.replace_all(place, vec![replacement.syntax().clone().into()]);
                 }
             }
             builder.add_file_edits(ctx.vfs_file_id(), editor);
@@ -122,7 +118,7 @@
 struct InlineData {
     let_stmt: Either<ast::LetStmt, ast::LetExpr>,
     delete_let: bool,
-    target: ast::NameOrNameRef,
+    target: TextRange,
     references: Vec<FileReference>,
 }
 
@@ -140,20 +136,18 @@
         cov_mark::hit!(test_not_inline_mut_variable);
         return None;
     }
-    if !bind_pat.syntax().text_range().contains_range(range) {
+    let target = original_range_in(file_id, sema, bind_pat.name()?.syntax())?;
+    if !target.contains_range(range) {
         cov_mark::hit!(not_applicable_outside_of_bind_pat);
         return None;
     }
 
     let local = sema.to_def(&bind_pat)?;
-    let UsageSearchResult { mut references } = Definition::Local(local).usages(sema).all();
-    match references.remove(&file_id) {
-        Some(references) => Some(InlineData {
-            let_stmt,
-            delete_let: true,
-            target: ast::NameOrNameRef::Name(bind_pat.name()?),
-            references,
-        }),
+    let UsageSearchResult { references } = Definition::Local(local).usages(sema).all();
+    let references = references.into_iter().flat_map(|it| it.1).collect::<Vec<_>>();
+
+    match references.first() {
+        Some(_) => Some(InlineData { let_stmt, delete_let: true, target, references }),
         None => {
             cov_mark::hit!(test_not_applicable_if_variable_unused);
             None
@@ -169,7 +163,8 @@
 ) -> Option<InlineData> {
     let path = path_expr.path()?;
     let name = path.as_single_name_ref()?;
-    if !name.syntax().text_range().contains_range(range) {
+    let target = original_range_in(file_id, sema, name.syntax())?;
+    if !target.contains_range(range) {
         cov_mark::hit!(test_not_inline_selection_too_broad);
         return None;
     }
@@ -193,12 +188,12 @@
 
     let let_stmt = AstNode::cast(bind_pat.syntax().parent()?)?;
 
-    let UsageSearchResult { mut references } = Definition::Local(local).usages(sema).all();
-    let mut references = references.remove(&file_id)?;
+    let UsageSearchResult { references } = Definition::Local(local).usages(sema).all();
+    let mut references = references.into_iter().flat_map(|it| it.1).collect::<Vec<_>>();
     let delete_let = references.len() == 1;
     references.retain(|fref| fref.name.as_name_ref() == Some(&name));
 
-    Some(InlineData { let_stmt, delete_let, target: ast::NameOrNameRef::NameRef(name), references })
+    Some(InlineData { let_stmt, delete_let, target, references })
 }
 
 fn remove_whitespace(elem: impl Element, dir: Direction, editor: &SyntaxEditor) {
@@ -969,8 +964,8 @@
     }
 
     #[test]
-    fn not_applicable_on_local_usage_in_macro() {
-        check_assist_not_applicable(
+    fn local_usage_in_macro() {
+        check_assist(
             inline_local_variable,
             r#"
 macro_rules! m {
@@ -978,11 +973,19 @@
 }
 fn f() {
     let xyz = 0;
-    m!(xyz$0); // replacing it would break the macro
+    m!(xyz$0); // some macros may break, but it's best to support them
+}
+"#,
+            r#"
+macro_rules! m {
+    ($i:ident) => { $i }
+}
+fn f() {
+    m!(0); // some macros may break, but it's best to support them
 }
 "#,
         );
-        check_assist_not_applicable(
+        check_assist(
             inline_local_variable,
             r#"
 macro_rules! m {
@@ -990,7 +993,84 @@
 }
 fn f() {
     let xyz$0 = 0;
-    m!(xyz); // replacing it would break the macro
+    m!(xyz); // some macros may break, but it's best to support them
+}
+"#,
+            r#"
+macro_rules! m {
+    ($i:ident) => { $i }
+}
+fn f() {
+    m!(0); // some macros may break, but it's best to support them
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn local_def_in_macro() {
+        check_assist(
+            inline_local_variable,
+            r#"
+macro_rules! i {
+    ($($t:tt)*) => { $($t)* }
+}
+fn f() {
+    i!(let xyz = 0;);
+    _ = xyz$0;
+}
+"#,
+            r#"
+macro_rules! i {
+    ($($t:tt)*) => { $($t)* }
+}
+fn f() {
+    i!();
+    _ = 0;
+}
+"#,
+        );
+        check_assist(
+            inline_local_variable,
+            r#"
+macro_rules! i {
+    ($($t:tt)*) => { $($t)* }
+}
+fn f() {
+    i!(let xyz = 0;);
+    _ = xyz$0;
+    _ = xyz;
+}
+"#,
+            r#"
+macro_rules! i {
+    ($($t:tt)*) => { $($t)* }
+}
+fn f() {
+    i!(let xyz = 0;);
+    _ = 0;
+    _ = xyz;
+}
+"#,
+        );
+        check_assist(
+            inline_local_variable,
+            r#"
+macro_rules! i {
+    ($($t:tt)*) => { $($t)* }
+}
+fn f() {
+    i!(let $0xyz = 0;);
+    _ = xyz;
+}
+"#,
+            r#"
+macro_rules! i {
+    ($($t:tt)*) => { $($t)* }
+}
+fn f() {
+    i!();
+    _ = 0;
 }
 "#,
         );
diff --git a/crates/ide-assists/src/handlers/inline_macro.rs b/crates/ide-assists/src/handlers/inline_macro.rs
index 002791b..f4ac75d 100644
--- a/crates/ide-assists/src/handlers/inline_macro.rs
+++ b/crates/ide-assists/src/handlers/inline_macro.rs
@@ -1,8 +1,11 @@
 use hir::db::ExpandDatabase;
 use ide_db::syntax_helpers::prettify_macro_expansion;
-use syntax::ast::{self, AstNode, edit::AstNodeEdit};
+use syntax::ast::{self, AstNode, edit::IndentLevel};
 
-use crate::{AssistContext, AssistId, Assists};
+use crate::{
+    AssistContext, AssistId, Assists,
+    utils::{cover_edit_range, original_range_in},
+};
 
 // Assist: inline_macro
 //
@@ -36,24 +39,40 @@
 // }
 // ```
 pub(crate) fn inline_macro(acc: &mut Assists, ctx: &AssistContext<'_, '_>) -> Option<()> {
-    let unexpanded = ctx.find_node_at_offset::<ast::MacroCall>()?;
-    let macro_call = ctx.sema.to_def(&unexpanded)?;
+    let source = ctx.source_file().syntax();
+    let sel = ctx.selection_trimmed();
+    let (macro_call, text_range) = ctx
+        .sema
+        .find_nodes_at_offset_with_descend::<ast::MacroCall>(source, ctx.offset())
+        .find_map(|macro_call_node| {
+            let macro_call = ctx.sema.to_def(&macro_call_node)?;
+            let original_range =
+                original_range_in(ctx.file_id(), &ctx.sema, macro_call_node.syntax())?;
+            original_range.contains_range(sel).then_some((macro_call, original_range))
+        })?;
     let target_crate_id = ctx.sema.file_to_module_def(ctx.vfs_file_id())?.krate(ctx.db()).into();
-    let text_range = unexpanded.syntax().text_range();
 
     acc.add(
         AssistId::refactor_inline("inline_macro"),
         "Inline macro".to_owned(),
         text_range,
         |builder| {
-            let editor = builder.make_editor(unexpanded.syntax());
             let expanded = ctx.sema.parse_or_expand(macro_call.into());
             let span_map = ctx.sema.db.expansion_span_map(macro_call);
             // Don't call `prettify_macro_expansion()` outside the actual assist action; it does some heavy rowan tree manipulation,
             // which can be very costly for big macros when it is done *even without the assist being invoked*.
             let expanded = prettify_macro_expansion(ctx.db(), expanded, span_map, target_crate_id);
-            let expanded = ast::edit::indent(&expanded, unexpanded.indent_level());
-            editor.replace(unexpanded.syntax(), expanded);
+
+            // macro_call is from an expansion, use source position for indent
+            let indent = source
+                .token_at_offset(text_range.start())
+                .right_biased()
+                .map_or_else(IndentLevel::zero, |t| IndentLevel::from_token(&t));
+            let expanded = ast::edit::indent(&expanded, indent);
+
+            let editor = builder.make_editor(source);
+            let place = cover_edit_range(source, text_range);
+            editor.replace_all(place, vec![expanded.into()]);
             builder.add_file_edits(ctx.vfs_file_id(), editor);
         },
     )
@@ -103,6 +122,7 @@
 "#
         };
     }
+
     #[test]
     fn inline_macro_target() {
         check_assist_target(
@@ -379,4 +399,35 @@
 "#,
         );
     }
+
+    #[test]
+    fn inline_macro_in_macro() {
+        check_assist(
+            inline_macro,
+            r#"
+macro_rules! foo { () => { 2 }; }
+macro_rules! m { ($($tt:tt)*) => { $($tt)* }; }
+fn f() { m! { $0foo!(); } }
+"#,
+            r#"
+macro_rules! foo { () => { 2 }; }
+macro_rules! m { ($($tt:tt)*) => { $($tt)* }; }
+fn f() { m! { 2; } }
+"#,
+        );
+        check_assist(
+            inline_macro,
+            r#"
+//- proc_macros: identity
+macro_rules! foo { () => { 2 }; }
+#[proc_macros::identity]
+fn f() { $0foo!(); }
+"#,
+            r#"
+macro_rules! foo { () => { 2 }; }
+#[proc_macros::identity]
+fn f() { 2; }
+"#,
+        );
+    }
 }
diff --git a/crates/ide-assists/src/handlers/inline_type_alias.rs b/crates/ide-assists/src/handlers/inline_type_alias.rs
index e4a1314..bb76e27 100644
--- a/crates/ide-assists/src/handlers/inline_type_alias.rs
+++ b/crates/ide-assists/src/handlers/inline_type_alias.rs
@@ -135,7 +135,20 @@
                 PathResolution::SelfType(imp) => {
                     concrete_type = imp.source(ctx.db())?.value.self_ty()?;
                 }
-                // FIXME: should also work in ADT definitions
+                PathResolution::Def(hir::ModuleDef::Adt(adt)) => {
+                    let make = SyntaxFactory::without_mappings();
+                    let src = adt.source(ctx.db())?.value;
+                    let name = src.name()?;
+                    let generic_params = src.generic_param_list();
+                    let name_ref = make.name_ref(&name.text());
+                    let segment = match generic_params {
+                        Some(params) => {
+                            make.path_segment_generics(name_ref, params.to_generic_args(&make))
+                        }
+                        None => make.path_segment(name_ref),
+                    };
+                    concrete_type = make.ty_path_from_segments([segment], false);
+                }
                 _ => return None,
             }
 
@@ -997,6 +1010,68 @@
     }
 
     #[test]
+    fn inline_self_type_in_adt_definition() {
+        check_assist(
+            inline_type_alias,
+            r#"
+enum Foo {
+    A(i32),
+    B(Box<Self$0>),
+}
+"#,
+            r#"
+enum Foo {
+    A(i32),
+    B(Box<Foo>),
+}
+"#,
+        );
+        check_assist(
+            inline_type_alias,
+            r#"
+struct Foo {
+    a: Box<Self$0>,
+}
+"#,
+            r#"
+struct Foo {
+    a: Box<Foo>,
+}
+"#,
+        );
+        check_assist(
+            inline_type_alias,
+            r#"
+struct Foo<T> {
+    a: T,
+    b: Box<Self$0>,
+}
+"#,
+            r#"
+struct Foo<T> {
+    a: T,
+    b: Box<Foo<T>>,
+}
+"#,
+        );
+        check_assist(
+            inline_type_alias,
+            r#"
+union Foo {
+    a: u32,
+    b: std::mem::ManuallyDrop<Box<Self$0>>,
+}
+"#,
+            r#"
+union Foo {
+    a: u32,
+    b: std::mem::ManuallyDrop<Box<Foo>>,
+}
+"#,
+        );
+    }
+
+    #[test]
     fn inline_types_with_lifetime() {
         check_assist(
             inline_type_alias_uses,
diff --git a/crates/ide-assists/src/handlers/merge_imports.rs b/crates/ide-assists/src/handlers/merge_imports.rs
index dc40a6a..4234a60 100644
--- a/crates/ide-assists/src/handlers/merge_imports.rs
+++ b/crates/ide-assists/src/handlers/merge_imports.rs
@@ -1,10 +1,12 @@
-use either::Either;
 use ide_db::imports::{
     insert_use::{ImportGranularity, InsertUseConfig},
     merge_imports::{MergeBehavior, try_merge_imports, try_merge_trees},
 };
 use syntax::{
-    AstNode, SyntaxElement, SyntaxNode, algo::neighbor, ast, match_ast, syntax_editor::Removable,
+    AstNode, SyntaxElement,
+    algo::neighbor,
+    ast, match_ast,
+    syntax_editor::{Removable, SyntaxEditor},
 };
 
 use crate::{
@@ -13,8 +15,6 @@
     utils::next_prev,
 };
 
-use Edit::*;
-
 // Assist: merge_imports
 //
 // Merges neighbor imports with a common prefix.
@@ -28,16 +28,17 @@
 // use std::{fmt::Formatter, io};
 // ```
 pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext<'_, '_>) -> Option<()> {
-    let (target, edits) = if ctx.has_empty_selection() {
+    let (target, editor) = if ctx.has_empty_selection() {
         // Merge a neighbor
         cov_mark::hit!(merge_with_use_item_neighbors);
         let tree = ctx.find_node_at_offset::<ast::UseTree>()?.top_use_tree();
         let target = tree.syntax().text_range();
 
         let use_item = tree.syntax().parent().and_then(ast::Use::cast)?;
-        let mut neighbor = next_prev().find_map(|dir| neighbor(&use_item, dir)).into_iter();
-        let edits = use_item.try_merge_from(&mut neighbor, &ctx.config.insert_use);
-        (target, edits?)
+        let neighbor = next_prev().find_map(|dir| neighbor(&use_item, dir))?;
+        let (editor, _) = SyntaxEditor::new(use_item.syntax().parent()?.ancestors().last()?);
+        merge_uses(use_item, vec![neighbor], &ctx.config.insert_use, &editor)?;
+        (target, editor)
     } else {
         // Merge selected
         let selection_range = ctx.selection_trimmed();
@@ -50,104 +51,80 @@
         });
 
         let first_selected = selected_nodes.next()?;
-        let edits = match_ast! {
+        let (editor, _) = SyntaxEditor::new(parent_node.ancestors().last().unwrap());
+        match_ast! {
             match first_selected {
                 ast::Use(use_item) => {
                     cov_mark::hit!(merge_with_selected_use_item_neighbors);
-                    use_item.try_merge_from(&mut selected_nodes.filter_map(ast::Use::cast), &ctx.config.insert_use)
+                    merge_uses(
+                        use_item,
+                        selected_nodes.filter_map(ast::Use::cast).collect(),
+                        &ctx.config.insert_use,
+                        &editor,
+                    )?;
                 },
                 ast::UseTree(use_tree) => {
                     cov_mark::hit!(merge_with_selected_use_tree_neighbors);
-                    use_tree.try_merge_from(&mut selected_nodes.filter_map(ast::UseTree::cast), &ctx.config.insert_use)
+                    merge_use_trees(
+                        use_tree,
+                        selected_nodes.filter_map(ast::UseTree::cast).collect(),
+                        &editor,
+                    )?;
                 },
                 _ => return None,
             }
-        };
-        (selection_range, edits?)
-    };
-
-    let parent_node = match ctx.covering_element() {
-        SyntaxElement::Node(n) => n,
-        SyntaxElement::Token(t) => t.parent()?,
+        }
+        (selection_range, editor)
     };
 
     acc.add(AssistId::refactor_rewrite("merge_imports"), "Merge imports", target, |builder| {
-        let editor = builder.make_editor(&parent_node);
-
-        for edit in edits {
-            match edit {
-                Remove(it) => {
-                    let node = it.as_ref();
-                    if let Some(left) = node.left() {
-                        left.remove(&editor);
-                    } else if let Some(right) = node.right() {
-                        right.remove(&editor);
-                    }
-                }
-                Replace(old, new) => {
-                    editor.replace(old, &new);
-                }
-            }
-        }
         builder.add_file_edits(ctx.vfs_file_id(), editor);
     })
 }
 
-trait Merge: AstNode + Clone {
-    fn try_merge_from(
-        self,
-        items: &mut dyn Iterator<Item = Self>,
-        cfg: &InsertUseConfig,
-    ) -> Option<Vec<Edit>> {
-        let mut edits = Vec::new();
-        let mut merged = self.clone();
-        for item in items {
-            merged = merged.try_merge(&item, cfg)?;
-            edits.push(Edit::Remove(item.into_either()));
-        }
-        if !edits.is_empty() {
-            edits.push(Edit::replace(self, merged));
-            Some(edits)
-        } else {
-            None
-        }
+fn merge_uses(
+    first: ast::Use,
+    rest: Vec<ast::Use>,
+    cfg: &InsertUseConfig,
+    editor: &SyntaxEditor,
+) -> Option<()> {
+    if rest.is_empty() {
+        return None;
     }
-    fn try_merge(&self, other: &Self, cfg: &InsertUseConfig) -> Option<Self>;
-    fn into_either(self) -> Either<ast::Use, ast::UseTree>;
+
+    let mb = match cfg.granularity {
+        ImportGranularity::One => MergeBehavior::One,
+        _ => MergeBehavior::Crate,
+    };
+    let mut merged = first.clone();
+    for item in &rest {
+        merged = try_merge_imports(editor.make(), &merged, item, mb)?;
+    }
+    for item in rest {
+        item.remove(editor);
+    }
+    editor.replace(first.syntax(), merged.syntax());
+    Some(())
 }
 
-impl Merge for ast::Use {
-    fn try_merge(&self, other: &Self, cfg: &InsertUseConfig) -> Option<Self> {
-        let mb = match cfg.granularity {
-            ImportGranularity::One => MergeBehavior::One,
-            _ => MergeBehavior::Crate,
-        };
-        try_merge_imports(self, other, mb)
+fn merge_use_trees(
+    first: ast::UseTree,
+    rest: Vec<ast::UseTree>,
+    editor: &SyntaxEditor,
+) -> Option<()> {
+    if rest.is_empty() {
+        return None;
     }
-    fn into_either(self) -> Either<ast::Use, ast::UseTree> {
-        Either::Left(self)
-    }
-}
 
-impl Merge for ast::UseTree {
-    fn try_merge(&self, other: &Self, _: &InsertUseConfig) -> Option<Self> {
-        try_merge_trees(self, other, MergeBehavior::Crate)
+    let mut merged = first.clone();
+    for item in &rest {
+        merged = try_merge_trees(editor.make(), &merged, item, MergeBehavior::Crate)?;
     }
-    fn into_either(self) -> Either<ast::Use, ast::UseTree> {
-        Either::Right(self)
+    for item in rest {
+        item.remove(editor);
     }
-}
-
-#[derive(Debug)]
-enum Edit {
-    Remove(Either<ast::Use, ast::UseTree>),
-    Replace(SyntaxNode, SyntaxNode),
-}
-
-impl Edit {
-    fn replace(old: impl AstNode, new: impl AstNode) -> Self {
-        Edit::Replace(old.syntax().clone(), new.syntax().clone())
-    }
+    editor.replace(first.syntax(), merged.syntax());
+    Some(())
 }
 
 #[cfg(test)]
@@ -559,6 +536,62 @@
     }
 
     #[test]
+    fn mod_indent_whitespace() {
+        check_assist(
+            merge_imports,
+            r"
+mod tests {
+    use foo$0::bar;
+    use foo::baz;
+    fn feature() {}
+}
+",
+            r"
+mod tests {
+    use foo::{bar, baz};
+    fn feature() {}
+}
+",
+        );
+        check_assist(
+            merge_imports,
+            r"
+mod tests {
+    use foo$0::bar;
+    use foo::baz;
+
+    fn feature() {}
+}
+",
+            r"
+mod tests {
+    use foo::{bar, baz};
+
+    fn feature() {}
+}
+",
+        );
+        check_assist(
+            merge_imports,
+            r"
+mod tests {
+    use foo::bar;
+    use foo$0::baz;
+
+    fn feature() {}
+}
+",
+            r"
+mod tests {
+    use foo::{bar, baz};
+
+    fn feature() {}
+}
+",
+        );
+    }
+
+    #[test]
     fn works_with_trailing_comma() {
         check_assist(
             merge_imports,
diff --git a/crates/ide-assists/src/handlers/normalize_import.rs b/crates/ide-assists/src/handlers/normalize_import.rs
index f97a3e5..906f38c 100644
--- a/crates/ide-assists/src/handlers/normalize_import.rs
+++ b/crates/ide-assists/src/handlers/normalize_import.rs
@@ -1,5 +1,5 @@
 use ide_db::imports::merge_imports::try_normalize_import;
-use syntax::{AstNode, ast};
+use syntax::{AstNode, ast, syntax_editor::SyntaxEditor};
 
 use crate::{
     AssistId,
@@ -25,11 +25,13 @@
     };
 
     let target = use_item.syntax().text_range();
+    let (editor, _) = SyntaxEditor::new(use_item.syntax().ancestors().last().unwrap());
     let normalized_use_item =
-        try_normalize_import(&use_item, ctx.config.insert_use.granularity.into())?;
+        try_normalize_import(editor.make(), &use_item, ctx.config.insert_use.granularity.into())?;
+    editor.replace(use_item.syntax(), normalized_use_item.syntax());
 
     acc.add(AssistId::refactor_rewrite("normalize_import"), "Normalize import", target, |builder| {
-        builder.replace_ast(use_item, normalized_use_item);
+        builder.add_file_edits(ctx.vfs_file_id(), editor);
     })
 }
 
diff --git a/crates/ide-assists/src/handlers/replace_if_let_with_match.rs b/crates/ide-assists/src/handlers/replace_if_let_with_match.rs
index ff2d054..5225202 100644
--- a/crates/ide-assists/src/handlers/replace_if_let_with_match.rs
+++ b/crates/ide-assists/src/handlers/replace_if_let_with_match.rs
@@ -17,7 +17,7 @@
     AssistContext, AssistId, Assists,
     utils::{
         does_pat_match_variant, does_pat_variant_nested_or_literal, unwrap_trivial_block,
-        wrap_paren,
+        wrap_paren_in_guard_chain,
     },
 };
 
@@ -303,7 +303,7 @@
                 _ => make.expr_let(if_let_pat, scrutinee).into(),
             };
             let condition = if let Some(guard) = guard {
-                let guard = wrap_paren(guard, make, ast::prec::ExprPrecedence::LAnd);
+                let guard = wrap_paren_in_guard_chain(guard, make);
                 make.expr_bin(condition, ast::BinaryOp::LogicOp(ast::LogicOp::And), guard).into()
             } else {
                 condition
@@ -712,13 +712,11 @@
         check_assist(
             replace_if_let_with_match,
             r#"
-#![feature(if_let_guard)]
 fn main() {
     if $0let true = true && let Some(1) = None {} else { other() }
 }
 "#,
             r#"
-#![feature(if_let_guard)]
 fn main() {
     match true {
         true if let Some(1) = None => {}
@@ -731,7 +729,6 @@
         check_assist(
             replace_if_let_with_match,
             r#"
-#![feature(if_let_guard)]
 fn main() {
     if true {
         $0if let ParenExpr(expr) = cond
@@ -758,7 +755,6 @@
 }
 "#,
             r#"
-#![feature(if_let_guard)]
 fn main() {
     if true {
         match cond {
@@ -816,13 +812,11 @@
         check_assist(
             replace_if_let_with_match,
             r#"
-#![feature(if_let_guard)]
 fn main() {
     if $0let true = true && let Some(1) = None {}
 }
 "#,
             r#"
-#![feature(if_let_guard)]
 fn main() {
     match true {
         true if let Some(1) = None => {}
@@ -2460,7 +2454,7 @@
     }
 
     #[test]
-    fn test_replace_match_with_if_let_chain() {
+    fn test_replace_match_with_if_let_with_simple_guard() {
         check_assist(
             replace_match_with_if_let,
             r#"
@@ -2505,6 +2499,73 @@
     }
 
     #[test]
+    fn test_replace_match_with_if_let_with_if_let_guard() {
+        check_assist(
+            replace_match_with_if_let,
+            r#"
+fn main() {
+    match$0 Some(0) {
+        Some(n) if let Some(m) = n.checked_add(1) => (),
+        _ => code(),
+    }
+}
+"#,
+            r#"
+fn main() {
+    if let Some(n) = Some(0) && let Some(m) = n.checked_add(1) {
+        ()
+    } else {
+        code()
+    }
+}
+"#,
+        );
+
+        check_assist(
+            replace_match_with_if_let,
+            r#"
+fn main() {
+    match$0 Some(0) {
+        Some(n) if let Some(m) = n.checked_add(1) && m > 5 => (),
+        _ => code(),
+    }
+}
+        "#,
+            r#"
+fn main() {
+    if let Some(n) = Some(0) && let Some(m) = n.checked_add(1) && m > 5 {
+        ()
+    } else {
+        code()
+    }
+}
+        "#,
+        );
+
+        // what if the `let` expr is not the first one in the guard?
+        check_assist(
+            replace_match_with_if_let,
+            r#"
+fn main() {
+    match$0 Some(0) {
+        Some(n) if n > 5 && let Some(m) = n.checked_add(1) => (),
+        _ => code(),
+    }
+}
+        "#,
+            r#"
+fn main() {
+    if let Some(n) = Some(0) && n > 5 && let Some(m) = n.checked_add(1) {
+        ()
+    } else {
+        code()
+    }
+}
+        "#,
+        );
+    }
+
+    #[test]
     fn test_replace_match_with_if_let_not_applicable_pat2_is_ident_pat() {
         check_assist_not_applicable(
             replace_match_with_if_let,
diff --git a/crates/ide-assists/src/utils.rs b/crates/ide-assists/src/utils.rs
index 1b6c9a5..2c4fb5f 100644
--- a/crates/ide-assists/src/utils.rs
+++ b/crates/ide-assists/src/utils.rs
@@ -110,6 +110,26 @@
     param.needs_parens_in_place_of(call.syntax(), callable.syntax())
 }
 
+pub(crate) fn wrap_paren_in_guard_chain(guard: ast::Expr, make: &SyntaxFactory) -> ast::Expr {
+    if needs_parens_in_guard_chain(make, &guard) { make.expr_paren(guard).into() } else { guard }
+}
+
+fn needs_parens_in_guard_chain(make: &SyntaxFactory, guard: &ast::Expr) -> bool {
+    let ast::Expr::BinExpr(if_let_and_guard) = make.expr_bin_op(
+        make.expr_unit(),
+        ast::BinaryOp::LogicOp(ast::LogicOp::And),
+        make.expr_unit(),
+    ) else {
+        stdx::never!("`SyntaxFactory::expr_bin_op` returns a `BinExpr`");
+        return false;
+    };
+    let Some(fake_guard) = if_let_and_guard.rhs() else {
+        stdx::never!("invalid make call");
+        return false;
+    };
+    guard.needs_parens_in_place_of(if_let_and_guard.syntax(), fake_guard.syntax())
+}
+
 /// This is a method with a heuristics to support test methods annotated with custom test annotations, such as
 /// `#[test_case(...)]`, `#[tokio::test]` and similar.
 /// Also a regular `#[test]` annotation is supported.
@@ -1177,6 +1197,15 @@
     is_const
 }
 
+pub(crate) fn original_range_in(
+    file_id: hir::EditionedFileId,
+    sema: &Semantics<'_, RootDatabase>,
+    value: &SyntaxNode,
+) -> Option<TextRange> {
+    let original = sema.original_range_opt(value)?;
+    (original.file_id == file_id).then_some(original.range)
+}
+
 // FIXME: #20460 When hir-ty can analyze the `never` statement at the end of block, remove it
 pub(crate) fn is_never_block(
     sema: &Semantics<'_, RootDatabase>,
diff --git a/crates/ide-completion/src/config.rs b/crates/ide-completion/src/config.rs
index ac52c81..0739084 100644
--- a/crates/ide-completion/src/config.rs
+++ b/crates/ide-completion/src/config.rs
@@ -45,6 +45,7 @@
     Always,
     Methods,
     SubItems,
+    Variants,
 }
 
 #[derive(Clone, Debug, PartialEq, Eq)]
diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs
index f7bb143..507ecaf 100644
--- a/crates/ide-completion/src/context.rs
+++ b/crates/ide-completion/src/context.rs
@@ -868,10 +868,22 @@
                 _ => None,
             })
             .collect::<Vec<_>>();
+        let exclude_variants = exclude_flyimport
+            .iter()
+            .flat_map(|it| match it {
+                (ModuleDef::Adt(hir::Adt::Enum(enum_)), AutoImportExclusionType::Variants) => {
+                    enum_.variants(db)
+                }
+                _ => vec![],
+            })
+            .collect::<Vec<_>>();
         exclude_flyimport
             .extend(exclude_traits.iter().map(|&t| (t.into(), AutoImportExclusionType::Always)));
         exclude_flyimport
             .extend(exclude_subitems.into_iter().map(|it| (it, AutoImportExclusionType::Always)));
+        exclude_flyimport.extend(
+            exclude_variants.into_iter().map(|it| (it.into(), AutoImportExclusionType::Always)),
+        );
 
         // FIXME: This should be part of `CompletionAnalysis` / `expand_and_analyze`
         let complete_semicolon = if !config.add_semicolon_to_unit {
diff --git a/crates/ide-completion/src/render.rs b/crates/ide-completion/src/render.rs
index e48847c..7cb1eaa 100644
--- a/crates/ide-completion/src/render.rs
+++ b/crates/ide-completion/src/render.rs
@@ -2372,7 +2372,7 @@
 }
 fn foo(s: S) { s.$0 }
 "#,
-            CompletionItemKind::SymbolKind(SymbolKind::Method),
+            SymbolKind::Method,
             expect![[r#"
                 [
                     CompletionItem {
diff --git a/crates/ide-completion/src/tests/expression.rs b/crates/ide-completion/src/tests/expression.rs
index e49afa6..adf4dda 100644
--- a/crates/ide-completion/src/tests/expression.rs
+++ b/crates/ide-completion/src/tests/expression.rs
@@ -3042,6 +3042,76 @@
 }
 
 #[test]
+fn flyimport_excluded_enum_variants_from_flyimport() {
+    check_with_config(
+        CompletionConfig {
+            exclude_flyimport: vec![(
+                "ra_test_fixture::Foo".to_owned(),
+                AutoImportExclusionType::Variants,
+            )],
+            ..TEST_CONFIG
+        },
+        r#"
+enum Foo {
+    Variant1,
+    Variant2,
+}
+fn foo() {
+    V$0
+}
+        "#,
+        expect![[r#"
+            ct CONST                   Unit
+            en Enum                    Enum
+            en Foo                      Foo
+            fn foo()                   fn()
+            fn function()              fn()
+            ma makro!(…) macro_rules! makro
+            md module::
+            sc STATIC                  Unit
+            st Record                Record
+            st Tuple                  Tuple
+            st Unit                    Unit
+            un Union                  Union
+            ev TupleV(…)        TupleV(u32)
+            bt u32                      u32
+            kw async
+            kw const
+            kw crate::
+            kw enum
+            kw extern
+            kw false
+            kw fn
+            kw for
+            kw if
+            kw if let
+            kw impl
+            kw impl for
+            kw let
+            kw letm
+            kw loop
+            kw match
+            kw mod
+            kw return
+            kw self::
+            kw static
+            kw struct
+            kw trait
+            kw true
+            kw type
+            kw union
+            kw unsafe
+            kw use
+            kw while
+            kw while let
+            sn macro_rules
+            sn pd
+            sn ppd
+        "#]],
+    );
+}
+
+#[test]
 fn excluded_trait_method_is_excluded_from_path_completion() {
     check_with_config(
         CompletionConfig {
@@ -4109,3 +4179,31 @@
         "#]],
     );
 }
+
+#[test]
+fn no_await_on_error_type() {
+    check(
+        r#"
+//- minicore: future
+fn foo(t: T) {
+    let _ = t.$0;
+}
+        "#,
+        expect![[r#"
+                sn box  Box::new(expr)
+                sn call function(expr)
+                sn const      const {}
+                sn dbg      dbg!(expr)
+                sn dbgr    dbg!(&expr)
+                sn deref         *expr
+                sn if       if expr {}
+                sn match match expr {}
+                sn not           !expr
+                sn ref           &expr
+                sn refm      &mut expr
+                sn return  return expr
+                sn unsafe    unsafe {}
+                sn while while expr {}
+            "#]],
+    );
+}
diff --git a/crates/ide-db/src/apply_change.rs b/crates/ide-db/src/apply_change.rs
index b77a18f..7a3c466 100644
--- a/crates/ide-db/src/apply_change.rs
+++ b/crates/ide-db/src/apply_change.rs
@@ -1,16 +1,21 @@
 //! Applies changes to the IDE state transactionally.
 
+use std::time::{Duration, Instant};
+
 use profile::Bytes;
 use salsa::Database as _;
 
 use crate::{ChangeWithProcMacros, RootDatabase};
 
 impl RootDatabase {
-    pub fn apply_change(&mut self, change: ChangeWithProcMacros) {
+    pub fn apply_change(&mut self, change: ChangeWithProcMacros) -> Duration {
         let _p = tracing::info_span!("RootDatabase::apply_change").entered();
+        let now = Instant::now();
         self.trigger_cancellation();
+        let elapsed = now.elapsed();
         tracing::trace!("apply_change {:?}", change);
         change.apply(self);
+        elapsed
     }
 
     // Feature: Memory Usage
diff --git a/crates/ide-db/src/imports/insert_use.rs b/crates/ide-db/src/imports/insert_use.rs
index c3949f8..27e3ed6 100644
--- a/crates/ide-db/src/imports/insert_use.rs
+++ b/crates/ide-db/src/imports/insert_use.rs
@@ -17,7 +17,7 @@
     RootDatabase,
     imports::merge_imports::{
         MergeBehavior, NormalizationStyle, common_prefix, eq_attrs, eq_visibility,
-        try_merge_imports, use_tree_cmp,
+        try_merge_imports, use_tree_cmp, wrap_in_tree_list,
     },
 };
 
@@ -251,7 +251,7 @@
     let mut use_tree = make.use_tree(path, None, alias, false);
     if mb == Some(MergeBehavior::One)
         && use_tree.path().is_some()
-        && let Some(wrapped) = use_tree.wrap_in_tree_list_with_editor()
+        && let Some(wrapped) = wrap_in_tree_list(&use_tree, make)
     {
         use_tree = wrapped;
     }
@@ -263,7 +263,9 @@
         for existing_use in
             scope.as_syntax_node().children().filter_map(ast::Use::cast).filter(filter)
         {
-            if let Some(merged) = try_merge_imports(&existing_use, &use_item, mb) {
+            if let Some(merged) =
+                try_merge_imports(syntax_editor.make(), &existing_use, &use_item, mb)
+            {
                 syntax_editor.replace(existing_use.syntax(), merged.syntax());
                 return;
             }
diff --git a/crates/ide-db/src/imports/insert_use/tests.rs b/crates/ide-db/src/imports/insert_use/tests.rs
index 4fa05c4..34ff7c8 100644
--- a/crates/ide-db/src/imports/insert_use/tests.rs
+++ b/crates/ide-db/src/imports/insert_use/tests.rs
@@ -1,4 +1,5 @@
 use stdx::trim_indent;
+use syntax::ast::syntax_factory::SyntaxFactory;
 use test_fixture::WithFixture;
 use test_utils::{CURSOR_MARKER, assert_eq_text};
 
@@ -1430,7 +1431,8 @@
         .find_map(ast::Use::cast)
         .unwrap();
 
-    let result = try_merge_imports(&use0, &use1, mb);
+    let make = SyntaxFactory::without_mappings();
+    let result = try_merge_imports(&make, &use0, &use1, mb);
     assert_eq!(result.map(|u| u.to_string()), None);
 }
 
@@ -1495,7 +1497,8 @@
         .find_map(ast::Use::cast)
         .unwrap();
 
-    let result = try_merge_imports(&use0, &use1, mb);
+    let make = SyntaxFactory::without_mappings();
+    let result = try_merge_imports(&make, &use0, &use1, mb);
     assert_eq!(result.map(|u| u.to_string().trim().to_owned()), Some(last.trim().to_owned()));
 }
 
@@ -1525,7 +1528,8 @@
         .find_map(ast::Use::cast)
         .unwrap();
 
-    let result = try_merge_imports(&use0, &use1, MergeBehavior::Crate);
+    let make = SyntaxFactory::without_mappings();
+    let result = try_merge_imports(&make, &use0, &use1, MergeBehavior::Crate);
     assert_eq!(result, None);
 }
 
diff --git a/crates/ide-db/src/imports/merge_imports.rs b/crates/ide-db/src/imports/merge_imports.rs
index bbd351a..9b68c27 100644
--- a/crates/ide-db/src/imports/merge_imports.rs
+++ b/crates/ide-db/src/imports/merge_imports.rs
@@ -4,12 +4,12 @@
 use itertools::{EitherOrBoth, Itertools};
 use parser::T;
 use syntax::{
-    Direction, SyntaxElement, ToSmolStr, algo,
+    ToSmolStr,
     ast::{
-        self, AstNode, HasAttrs, HasName, HasVisibility, PathSegmentKind, edit_in_place::Removable,
-        make,
+        self, AstNode, HasAttrs, HasName, HasVisibility, PathSegmentKind,
+        syntax_factory::SyntaxFactory,
     },
-    ted::{self, Position},
+    syntax_editor::{Position, SyntaxEditor},
 };
 
 use crate::syntax_helpers::node_ext::vis_eq;
@@ -39,8 +39,8 @@
 }
 
 /// Merge `rhs` into `lhs` keeping both intact.
-/// Returned AST is mutable.
 pub fn try_merge_imports(
+    make: &SyntaxFactory,
     lhs: &ast::Use,
     rhs: &ast::Use,
     merge_behavior: MergeBehavior,
@@ -53,39 +53,39 @@
         return None;
     }
 
-    let lhs = lhs.clone_subtree().clone_for_update();
-    let rhs = rhs.clone_subtree().clone_for_update();
     let lhs_tree = lhs.use_tree()?;
     let rhs_tree = rhs.use_tree()?;
-    try_merge_trees_mut(&lhs_tree, &rhs_tree, merge_behavior)?;
+    let merged_tree = try_merge_trees_with_factory(lhs_tree, rhs_tree, merge_behavior, make)?;
 
     // Ignore `None` result because normalization should not affect the merge result.
-    try_normalize_use_tree_mut(&lhs_tree, merge_behavior.into());
+    let use_tree = try_normalize_use_tree(merged_tree.clone(), merge_behavior.into(), make)
+        .unwrap_or(merged_tree);
 
-    Some(lhs)
+    make_use_with_tree(lhs, use_tree)
 }
 
 /// Merge `rhs` into `lhs` keeping both intact.
-/// Returned AST is mutable.
 pub fn try_merge_trees(
+    make: &SyntaxFactory,
     lhs: &ast::UseTree,
     rhs: &ast::UseTree,
     merge: MergeBehavior,
 ) -> Option<ast::UseTree> {
-    let lhs = lhs.clone_subtree().clone_for_update();
-    let rhs = rhs.clone_subtree().clone_for_update();
-    try_merge_trees_mut(&lhs, &rhs, merge)?;
+    let merged = try_merge_trees_with_factory(lhs.clone(), rhs.clone(), merge, make)?;
 
     // Ignore `None` result because normalization should not affect the merge result.
-    try_normalize_use_tree_mut(&lhs, merge.into());
-
-    Some(lhs)
+    Some(try_normalize_use_tree(merged.clone(), merge.into(), make).unwrap_or(merged))
 }
 
-fn try_merge_trees_mut(lhs: &ast::UseTree, rhs: &ast::UseTree, merge: MergeBehavior) -> Option<()> {
+fn try_merge_trees_with_factory(
+    mut lhs: ast::UseTree,
+    mut rhs: ast::UseTree,
+    merge: MergeBehavior,
+    make: &SyntaxFactory,
+) -> Option<ast::UseTree> {
     if merge == MergeBehavior::One {
-        lhs.wrap_in_tree_list();
-        rhs.wrap_in_tree_list();
+        lhs = wrap_in_tree_list(&lhs, make).unwrap_or(lhs);
+        rhs = wrap_in_tree_list(&rhs, make).unwrap_or(rhs);
     } else {
         let lhs_path = lhs.path()?;
         let rhs_path = rhs.path()?;
@@ -100,46 +100,53 @@
             // and we can safely return here
             let lhs_name = lhs.rename().and_then(|lhs_name| lhs_name.name());
             let rhs_name = rhs.rename().and_then(|rhs_name| rhs_name.name());
-            if lhs_name != rhs_name {
+            if lhs_name.as_ref().map(|name| name.text())
+                != rhs_name.as_ref().map(|name| name.text())
+            {
                 return None;
             }
 
-            ted::replace(lhs.syntax(), rhs.syntax());
-            // we can safely return here, in this case `recursive_merge` doesn't do anything
-            return Some(());
+            return Some(rhs);
         } else {
-            lhs.split_prefix(&lhs_prefix);
-            rhs.split_prefix(&rhs_prefix);
+            lhs = split_prefix(&lhs, &lhs_prefix, make)?;
+            rhs = split_prefix(&rhs, &rhs_prefix, make)?;
         }
     }
-    recursive_merge(lhs, rhs, merge)
+    recursive_merge(lhs, rhs, merge, make)
 }
 
 /// Recursively merges rhs to lhs
 #[must_use]
-fn recursive_merge(lhs: &ast::UseTree, rhs: &ast::UseTree, merge: MergeBehavior) -> Option<()> {
+fn recursive_merge(
+    lhs: ast::UseTree,
+    rhs: ast::UseTree,
+    merge: MergeBehavior,
+    make: &SyntaxFactory,
+) -> Option<ast::UseTree> {
     let mut use_trees: Vec<ast::UseTree> = lhs
-        .use_tree_list()
-        .into_iter()
-        .flat_map(|list| list.use_trees())
-        // We use Option here to early return from this function(this is not the
-        // same as a `filter` op).
+        .use_tree_list()?
+        .use_trees()
+        // We use Option here to early return from this function. This is not the
+        // same as a `filter` op.
         .map(|tree| merge.is_tree_allowed(&tree).then_some(tree))
         .collect::<Option<_>>()?;
+
     // Sorts the use trees similar to rustfmt's algorithm for ordering imports
     // (see `use_tree_cmp` doc).
     use_trees.sort_unstable_by(use_tree_cmp);
-    for rhs_t in rhs.use_tree_list().into_iter().flat_map(|list| list.use_trees()) {
+
+    for rhs_t in rhs.use_tree_list()?.use_trees() {
         if !merge.is_tree_allowed(&rhs_t) {
             return None;
         }
 
         match use_trees.binary_search_by(|lhs_t| use_tree_cmp_bin_search(lhs_t, &rhs_t)) {
             Ok(idx) => {
-                let lhs_t = &mut use_trees[idx];
+                let mut lhs_t = use_trees[idx].clone();
                 let lhs_path = lhs_t.path()?;
                 let rhs_path = rhs_t.path()?;
                 let (lhs_prefix, rhs_prefix) = common_prefix(&lhs_path, &rhs_path)?;
+
                 if lhs_prefix == lhs_path && rhs_prefix == rhs_path {
                     let tree_is_self = |tree: &ast::UseTree| {
                         tree.path().as_ref().map(path_is_self).unwrap_or(false)
@@ -157,20 +164,20 @@
                     };
 
                     if lhs_t.rename().and_then(|x| x.underscore_token()).is_some() {
-                        ted::replace(lhs_t.syntax(), rhs_t.syntax());
-                        *lhs_t = rhs_t;
+                        use_trees[idx] = rhs_t;
                         continue;
                     }
 
-                    match (tree_contains_self(lhs_t), tree_contains_self(&rhs_t)) {
+                    match (tree_contains_self(&lhs_t), tree_contains_self(&rhs_t)) {
                         (Some(true), None) => {
-                            remove_subtree_if_only_self(lhs_t);
+                            lhs_t = remove_subtree_if_only_self(lhs_t, make)?;
+                            use_trees[idx] = lhs_t;
                             continue;
                         }
                         (None, Some(true)) => {
-                            ted::replace(lhs_t.syntax(), rhs_t.syntax());
-                            *lhs_t = rhs_t;
-                            remove_subtree_if_only_self(lhs_t);
+                            lhs_t = rhs_t;
+                            lhs_t = remove_subtree_if_only_self(lhs_t, make)?;
+                            use_trees[idx] = lhs_t;
                             continue;
                         }
                         _ => (),
@@ -180,9 +187,11 @@
                         continue;
                     }
                 }
-                lhs_t.split_prefix(&lhs_prefix);
-                rhs_t.split_prefix(&rhs_prefix);
-                recursive_merge(lhs_t, &rhs_t, merge)?;
+
+                lhs_t = split_prefix(&lhs_t, &lhs_prefix, make)?;
+                let rhs_t = split_prefix(&rhs_t, &rhs_prefix, make)?;
+                lhs_t = recursive_merge(lhs_t, rhs_t, merge, make)?;
+                use_trees[idx] = lhs_t;
             }
             Err(_)
                 if merge == MergeBehavior::Module
@@ -192,15 +201,12 @@
                 return None;
             }
             Err(insert_idx) => {
-                use_trees.insert(insert_idx, rhs_t.clone());
-                // We simply add the use tree to the end of tree list. Ordering of use trees
-                // and imports is done by the `try_normalize_*` functions. The sorted `use_trees`
-                // vec is only used for binary search.
-                lhs.get_or_create_use_tree_list().add_use_tree(rhs_t);
+                use_trees.insert(insert_idx, rhs_t);
             }
         }
     }
-    Some(())
+
+    with_use_tree_list(&lhs, use_trees, make)
 }
 
 /// Style to follow when normalizing a use tree.
@@ -250,241 +256,216 @@
 /// - `foo::{bar::Qux, bar::{self}}` -> `{foo::bar::{self, Qux}}`
 /// - `foo::bar::{self}` -> `{foo::bar}`
 /// - `foo::bar` -> `{foo::bar}`
-pub fn try_normalize_import(use_item: &ast::Use, style: NormalizationStyle) -> Option<ast::Use> {
-    let use_item = use_item.clone_subtree().clone_for_update();
-    try_normalize_use_tree_mut(&use_item.use_tree()?, style)?;
-    Some(use_item)
+pub fn try_normalize_import(
+    make: &SyntaxFactory,
+    use_item: &ast::Use,
+    style: NormalizationStyle,
+) -> Option<ast::Use> {
+    let use_tree = try_normalize_use_tree(use_item.use_tree()?, style, make)?;
+
+    make_use_with_tree(use_item, use_tree)
 }
 
-fn try_normalize_use_tree_mut(use_tree: &ast::UseTree, style: NormalizationStyle) -> Option<()> {
+fn try_normalize_use_tree(
+    use_tree: ast::UseTree,
+    style: NormalizationStyle,
+    make: &SyntaxFactory,
+) -> Option<ast::UseTree> {
     if style == NormalizationStyle::One {
+        let mut use_tree = use_tree;
         let mut modified = false;
-        modified |= use_tree.wrap_in_tree_list().is_some();
-        modified |= recursive_normalize(use_tree, style).is_some();
-        if !modified {
-            // Either the use tree was already normalized or its semantically empty.
-            return None;
+        if let Some(wrapped) = wrap_in_tree_list(&use_tree, make) {
+            use_tree = wrapped;
+            modified = true;
         }
-    } else {
-        recursive_normalize(use_tree, NormalizationStyle::Default)?;
+        if let Some(normalized) = recursive_normalize(use_tree.clone(), style, make) {
+            use_tree = normalized;
+            modified = true;
+        }
+        return modified.then_some(use_tree);
     }
-    Some(())
+
+    recursive_normalize(use_tree, NormalizationStyle::Default, make)
 }
 
 /// Recursively normalizes a use tree and its subtrees (if any).
-fn recursive_normalize(use_tree: &ast::UseTree, style: NormalizationStyle) -> Option<()> {
+fn recursive_normalize(
+    use_tree: ast::UseTree,
+    style: NormalizationStyle,
+    make: &SyntaxFactory,
+) -> Option<ast::UseTree> {
     let use_tree_list = use_tree.use_tree_list()?;
-    let merge_subtree_into_parent_tree = |single_subtree: &ast::UseTree| {
-        let subtree_is_only_self = single_subtree.path().as_ref().is_some_and(path_is_self);
-
-        let merged_path = match (use_tree.path(), single_subtree.path()) {
-            // If the subtree is `{self}` then we cannot merge: `use
-            // foo::bar::{self}` is not equivalent to `use foo::bar`. See
-            // https://github.com/rust-lang/rust-analyzer/pull/17140#issuecomment-2079189725.
-            _ if subtree_is_only_self => None,
-
-            (None, None) => None,
-            (Some(outer), None) => Some(outer),
-            (None, Some(inner)) => Some(inner),
-            (Some(outer), Some(inner)) => Some(make::path_concat(outer, inner).clone_for_update()),
-        };
-
-        if merged_path.is_some()
-            || single_subtree.use_tree_list().is_some()
-            || single_subtree.star_token().is_some()
-        {
-            ted::remove_all_iter(use_tree.syntax().children_with_tokens());
-            if let Some(path) = merged_path {
-                ted::insert_raw(Position::first_child_of(use_tree.syntax()), path.syntax());
-                if single_subtree.use_tree_list().is_some() || single_subtree.star_token().is_some()
-                {
-                    ted::insert_raw(
-                        Position::last_child_of(use_tree.syntax()),
-                        make::token(T![::]),
-                    );
-                }
-            }
-            if let Some(inner_use_tree_list) = single_subtree.use_tree_list() {
-                ted::insert_raw(
-                    Position::last_child_of(use_tree.syntax()),
-                    inner_use_tree_list.syntax(),
-                );
-            } else if single_subtree.star_token().is_some() {
-                ted::insert_raw(Position::last_child_of(use_tree.syntax()), make::token(T![*]));
-            } else if let Some(rename) = single_subtree.rename() {
-                ted::insert_raw(
-                    Position::last_child_of(use_tree.syntax()),
-                    make::tokens::single_space(),
-                );
-                ted::insert_raw(Position::last_child_of(use_tree.syntax()), rename.syntax());
-            }
-            Some(())
-        } else {
-            // Bail on semantically empty use trees.
-            None
-        }
-    };
-    let one_style_tree_list = |subtree: &ast::UseTree| match (
-        subtree.path().is_none() && subtree.star_token().is_none() && subtree.rename().is_none(),
-        subtree.use_tree_list(),
-    ) {
-        (true, tree_list) => tree_list,
-        _ => None,
-    };
-    let add_element_to_list = |elem: SyntaxElement, elements: &mut Vec<SyntaxElement>| {
-        if !elements.is_empty() {
-            elements.push(make::token(T![,]).into());
-            elements.push(make::tokens::single_space().into());
-        }
-        elements.push(elem);
-    };
-    if let Some((single_subtree,)) = use_tree_list.use_trees().collect_tuple() {
+    let mut subtrees = use_tree_list.use_trees().collect::<Vec<_>>();
+    if subtrees.len() == 1 {
         if style == NormalizationStyle::One {
-            // Only normalize descendant subtrees if the normalization style is "one".
-            recursive_normalize(&single_subtree, NormalizationStyle::Default)?;
+            let subtree = subtrees.pop()?;
+            let normalized = recursive_normalize(subtree, NormalizationStyle::Default, make)?;
+            return with_use_tree_list(&use_tree, vec![normalized], make);
+        }
+
+        let merged = merge_single_subtree_into_parent_tree(use_tree, make)?;
+        return Some(recursive_normalize(merged.clone(), style, make).unwrap_or(merged));
+    }
+
+    let mut modified = false;
+    let mut new_use_tree_list = Vec::new();
+    for subtree in subtrees {
+        if one_style_tree_list(&subtree).is_some() {
+            let mut elements = Vec::new();
+            flatten_one_style_tree(subtree, &mut elements, &mut modified, make);
+            new_use_tree_list.extend(elements);
+            modified = true;
+        } else if let Some(normalized) =
+            recursive_normalize(subtree.clone(), NormalizationStyle::Default, make)
+        {
+            new_use_tree_list.push(normalized);
+            modified = true;
         } else {
-            // Otherwise, merge the single subtree into it's parent (if possible)
-            // and then normalize the result.
-            merge_subtree_into_parent_tree(&single_subtree)?;
-            recursive_normalize(use_tree, style);
-        }
-    } else {
-        // Tracks whether any changes have been made to the use tree.
-        let mut modified = false;
-
-        // Recursively un-nests (if necessary) and then normalizes each subtree in the tree list.
-        for subtree in use_tree_list.use_trees() {
-            if let Some(one_tree_list) = one_style_tree_list(&subtree) {
-                let mut elements = Vec::new();
-                let mut one_tree_list_iter = one_tree_list.use_trees();
-                let mut prev_skipped = Vec::new();
-                loop {
-                    let mut prev_skipped_iter = prev_skipped.into_iter();
-                    let mut curr_skipped = Vec::new();
-
-                    while let Some(sub_sub_tree) =
-                        one_tree_list_iter.next().or(prev_skipped_iter.next())
-                    {
-                        if let Some(sub_one_tree_list) = one_style_tree_list(&sub_sub_tree) {
-                            curr_skipped.extend(sub_one_tree_list.use_trees());
-                        } else {
-                            modified |=
-                                recursive_normalize(&sub_sub_tree, NormalizationStyle::Default)
-                                    .is_some();
-                            add_element_to_list(
-                                sub_sub_tree.syntax().clone().into(),
-                                &mut elements,
-                            );
-                        }
-                    }
-
-                    if curr_skipped.is_empty() {
-                        // Un-nesting is complete.
-                        break;
-                    }
-                    prev_skipped = curr_skipped;
-                }
-
-                // Either removes the subtree (if its semantically empty) or replaces it with
-                // the un-nested elements.
-                if elements.is_empty() {
-                    subtree.remove();
-                } else {
-                    ted::replace_with_many(subtree.syntax(), elements);
-                }
-                // Silence unused assignment warning on `modified`.
-                let _ = modified;
-                modified = true;
-            } else {
-                modified |= recursive_normalize(&subtree, NormalizationStyle::Default).is_some();
-            }
-        }
-
-        // Merge all merge-able subtrees.
-        let mut tree_list_iter = use_tree_list.use_trees();
-        let mut anchor = tree_list_iter.next()?;
-        let mut prev_skipped = Vec::new();
-        loop {
-            let mut has_merged = false;
-            let mut prev_skipped_iter = prev_skipped.into_iter();
-            let mut next_anchor = None;
-            let mut curr_skipped = Vec::new();
-
-            while let Some(candidate) = tree_list_iter.next().or(prev_skipped_iter.next()) {
-                let result = try_merge_trees_mut(&anchor, &candidate, MergeBehavior::Crate);
-                if result.is_some() {
-                    // Remove merged subtree.
-                    candidate.remove();
-                    has_merged = true;
-                } else if next_anchor.is_none() {
-                    next_anchor = Some(candidate);
-                } else {
-                    curr_skipped.push(candidate);
-                }
-            }
-
-            if has_merged {
-                // Normalize the merge result.
-                recursive_normalize(&anchor, NormalizationStyle::Default);
-                modified = true;
-            }
-
-            let (Some(next_anchor), true) = (next_anchor, !curr_skipped.is_empty()) else {
-                // Merging is complete.
-                break;
-            };
-
-            // Try to merge the remaining subtrees in the next iteration.
-            anchor = next_anchor;
-            prev_skipped = curr_skipped;
-        }
-
-        let mut subtrees: Vec<_> = use_tree_list.use_trees().collect();
-        // Merge the remaining subtree into its parent, if its only one and
-        // the normalization style is not "one".
-        if subtrees.len() == 1 && style != NormalizationStyle::One {
-            modified |= merge_subtree_into_parent_tree(&subtrees[0]).is_some();
-        }
-        // Order the remaining subtrees (if necessary).
-        if subtrees.len() > 1 {
-            let mut did_sort = false;
-            subtrees.sort_unstable_by(|a, b| {
-                let order = use_tree_cmp_bin_search(a, b);
-                if !did_sort && order == Ordering::Less {
-                    did_sort = true;
-                }
-                order
-            });
-            if did_sort {
-                let start = use_tree_list
-                    .l_curly_token()
-                    .and_then(|l_curly| algo::non_trivia_sibling(l_curly.into(), Direction::Next))
-                    .filter(|it| it.kind() != T!['}']);
-                let end = use_tree_list
-                    .r_curly_token()
-                    .and_then(|r_curly| algo::non_trivia_sibling(r_curly.into(), Direction::Prev))
-                    .filter(|it| it.kind() != T!['{']);
-                if let Some((start, end)) = start.zip(end) {
-                    // Attempt to insert elements while preserving preceding and trailing trivia.
-                    let mut elements = Vec::new();
-                    for subtree in subtrees {
-                        add_element_to_list(subtree.syntax().clone().into(), &mut elements);
-                    }
-                    ted::replace_all(start..=end, elements);
-                } else {
-                    let new_use_tree_list = make::use_tree_list(subtrees).clone_for_update();
-                    ted::replace(use_tree_list.syntax(), new_use_tree_list.syntax());
-                }
-                modified = true;
-            }
-        }
-
-        if !modified {
-            // Either the use tree was already normalized or its semantically empty.
-            return None;
+            new_use_tree_list.push(subtree);
         }
     }
-    Some(())
+
+    let mut use_tree =
+        if modified { with_use_tree_list(&use_tree, new_use_tree_list, make)? } else { use_tree };
+
+    let mut use_tree_list = use_tree.use_tree_list()?.use_trees().collect::<Vec<_>>();
+    let mut anchor_idx = 0;
+    let mut merged_any = false;
+    while anchor_idx < use_tree_list.len() {
+        let mut candidate_idx = anchor_idx + 1;
+        while candidate_idx < use_tree_list.len() {
+            if let Some(mut merged) = try_merge_trees_with_factory(
+                use_tree_list[anchor_idx].clone(),
+                use_tree_list[candidate_idx].clone(),
+                MergeBehavior::Crate,
+                make,
+            ) {
+                if let Some(normalized) =
+                    recursive_normalize(merged.clone(), NormalizationStyle::Default, make)
+                {
+                    merged = normalized;
+                }
+
+                use_tree_list[anchor_idx] = merged;
+                use_tree_list.remove(candidate_idx);
+                merged_any = true;
+            } else {
+                candidate_idx += 1;
+            }
+        }
+
+        anchor_idx += 1;
+    }
+    if merged_any {
+        use_tree = with_use_tree_list(&use_tree, use_tree_list, make)?;
+        modified = true;
+    }
+
+    if style != NormalizationStyle::One {
+        let subtrees = use_tree.use_tree_list()?.use_trees().collect::<Vec<_>>();
+        if subtrees.len() == 1
+            && let Some(merged) = merge_single_subtree_into_parent_tree(use_tree.clone(), make)
+        {
+            use_tree = merged;
+            modified = true;
+        }
+    }
+
+    if let Some(list) = use_tree.use_tree_list() {
+        let mut use_tree_list = list.use_trees().collect::<Vec<_>>();
+        if use_tree_list
+            .windows(2)
+            .any(|trees| use_tree_cmp_bin_search(&trees[0], &trees[1]).is_gt())
+        {
+            use_tree_list.sort_unstable_by(use_tree_cmp_bin_search);
+            use_tree = with_use_tree_list(&use_tree, use_tree_list, make)?;
+            modified = true;
+        }
+    }
+
+    modified.then_some(use_tree)
+}
+
+fn flatten_one_style_tree(
+    subtree: ast::UseTree,
+    elements: &mut Vec<ast::UseTree>,
+    modified: &mut bool,
+    make: &SyntaxFactory,
+) {
+    let Some(one_tree_list) = one_style_tree_list(&subtree) else { return };
+    let mut one_tree_list_iter = one_tree_list.use_trees();
+    let mut prev_skipped = Vec::new();
+    loop {
+        let mut prev_skipped_iter = prev_skipped.into_iter();
+        let mut curr_skipped = Vec::new();
+
+        while let Some(sub_sub_tree) =
+            one_tree_list_iter.next().or_else(|| prev_skipped_iter.next())
+        {
+            if let Some(sub_one_tree_list) = one_style_tree_list(&sub_sub_tree) {
+                curr_skipped.extend(sub_one_tree_list.use_trees());
+            } else if let Some(normalized) =
+                recursive_normalize(sub_sub_tree.clone(), NormalizationStyle::Default, make)
+            {
+                *modified = true;
+                elements.push(normalized);
+            } else {
+                elements.push(sub_sub_tree);
+            }
+        }
+
+        if curr_skipped.is_empty() {
+            break;
+        }
+        prev_skipped = curr_skipped;
+    }
+}
+
+fn merge_single_subtree_into_parent_tree(
+    use_tree: ast::UseTree,
+    make: &SyntaxFactory,
+) -> Option<ast::UseTree> {
+    let single_subtree = get_single_subtree(&use_tree)?;
+    let subtree_is_only_self = single_subtree.path().as_ref().is_some_and(path_is_self);
+
+    let merged_path = match (use_tree.path(), single_subtree.path()) {
+        _ if subtree_is_only_self => None,
+        (None, None) => None,
+        (Some(outer), None) => Some(outer),
+        (None, Some(inner)) => Some(inner),
+        (Some(outer), Some(inner)) => Some(make.path_concat(outer, inner)),
+    };
+
+    let list = single_subtree.use_tree_list();
+    let list_is_none = list.is_none();
+    let star = single_subtree.star_token().is_some();
+    if merged_path.is_some() || list.is_some() || star {
+        let rename = (!star && list_is_none).then(|| single_subtree.rename()).flatten();
+        make_use_tree_from_parts(make, merged_path, list, rename, star)
+    } else {
+        None
+    }
+}
+
+fn one_style_tree_list(subtree: &ast::UseTree) -> Option<ast::UseTreeList> {
+    (subtree.path().is_none() && subtree.star_token().is_none() && subtree.rename().is_none())
+        .then(|| subtree.use_tree_list())
+        .flatten()
+}
+
+fn remove_subtree_if_only_self(
+    use_tree: ast::UseTree,
+    make: &SyntaxFactory,
+) -> Option<ast::UseTree> {
+    let Some(single_subtree) = get_single_subtree(&use_tree) else {
+        return Some(use_tree);
+    };
+    match (use_tree.path(), single_subtree.path()) {
+        (Some(path), Some(inner)) if path_is_self(&inner) => {
+            Some(make.use_tree(path, None, use_tree.rename(), false))
+        }
+        _ => Some(use_tree),
+    }
 }
 
 /// Traverses both paths until they differ, returning the common prefix of both.
@@ -513,10 +494,9 @@
 fn use_tree_cmp_bin_search(lhs: &ast::UseTree, rhs: &ast::UseTree) -> Ordering {
     let lhs_is_simple_path = lhs.is_simple_path() && lhs.rename().is_none();
     let rhs_is_simple_path = rhs.is_simple_path() && rhs.rename().is_none();
-    match (
-        lhs.path().as_ref().and_then(ast::Path::first_segment),
-        rhs.path().as_ref().and_then(ast::Path::first_segment),
-    ) {
+    let lhs_segment = lhs.path().and_then(|path| path.first_segment());
+    let rhs_segment = rhs.path().and_then(|path| path.first_segment());
+    match (lhs_segment, rhs_segment) {
         (None, None) => match (lhs_is_simple_path, rhs_is_simple_path) {
             (true, true) => Ordering::Equal,
             (true, false) => Ordering::Less,
@@ -701,14 +681,164 @@
         .map(|(single_subtree,)| single_subtree)
 }
 
-fn remove_subtree_if_only_self(use_tree: &ast::UseTree) {
-    let Some(single_subtree) = get_single_subtree(use_tree) else { return };
-    match (use_tree.path(), single_subtree.path()) {
-        (Some(_), Some(inner)) if path_is_self(&inner) => {
-            ted::remove_all_iter(single_subtree.syntax().children_with_tokens());
-        }
-        _ => (),
+fn make_use_with_tree(original: &ast::Use, use_tree: ast::UseTree) -> Option<ast::Use> {
+    let (editor, use_item) = SyntaxEditor::with_ast_node(original);
+    let original_tree = use_item.use_tree()?;
+    editor.replace(original_tree.syntax(), use_tree.syntax());
+    let edit = editor.finish();
+    ast::Use::cast(edit.new_root().clone())
+}
+
+fn make_use_tree_list(
+    make: &SyntaxFactory,
+    use_trees: Vec<ast::UseTree>,
+    style_source: Option<&ast::UseTreeList>,
+) -> Option<ast::UseTreeList> {
+    let use_tree_list = make.use_tree_list(use_trees);
+    let Some(style_source) = style_source else {
+        return Some(use_tree_list);
+    };
+
+    let source_l_curly = style_source.l_curly_token()?;
+    let source_r_curly = style_source.r_curly_token()?;
+
+    let leading_ws = source_l_curly.next_token().filter(|token| token.kind().is_trivia());
+
+    let trailing_ws = source_r_curly.prev_token().filter(|token| token.kind().is_trivia());
+
+    let source_trailing_token = trailing_ws
+        .as_ref()
+        .and_then(|token| token.prev_token())
+        .or_else(|| source_r_curly.prev_token());
+
+    let source_has_trailing_comma =
+        source_trailing_token.is_some_and(|token| token.kind() == T![,]);
+
+    let (editor, use_tree_list) = SyntaxEditor::with_ast_node(&use_tree_list);
+    let make = editor.make();
+
+    if let Some(leading_ws) = leading_ws {
+        editor.insert(
+            Position::after(use_tree_list.l_curly_token()?),
+            make.whitespace(leading_ws.text()),
+        );
     }
+
+    let r_curly = use_tree_list.r_curly_token()?;
+
+    let generated_has_trailing_comma = r_curly
+        .prev_token()
+        .and_then(|token| if token.kind().is_trivia() { token.prev_token() } else { Some(token) })
+        .is_some_and(|token| token.kind() == T![,]);
+
+    let mut trailing = Vec::new();
+
+    if source_has_trailing_comma
+        && !generated_has_trailing_comma
+        && use_tree_list.use_trees().next().is_some()
+    {
+        trailing.push(make.token(T![,]).into());
+    }
+
+    if let Some(trailing_ws) = trailing_ws {
+        trailing.push(make.whitespace(trailing_ws.text()).into());
+    }
+
+    if !trailing.is_empty() {
+        editor.insert_all(Position::before(r_curly), trailing);
+    }
+
+    let edit = editor.finish();
+    ast::UseTreeList::cast(edit.new_root().clone())
+}
+
+fn make_use_tree_from_list(make: &SyntaxFactory, list: ast::UseTreeList) -> Option<ast::UseTree> {
+    let placeholder = make.use_tree_glob();
+    let (editor, use_tree) = SyntaxEditor::with_ast_node(&placeholder);
+    let first_child = use_tree.syntax().first_child_or_token()?;
+    let last_child = use_tree.syntax().last_child_or_token()?;
+    editor.replace_all(first_child..=last_child, vec![list.syntax().clone().into()]);
+    let edit = editor.finish();
+    ast::UseTree::cast(edit.new_root().clone())
+}
+
+fn make_use_tree_from_parts(
+    make: &SyntaxFactory,
+    path: Option<ast::Path>,
+    list: Option<ast::UseTreeList>,
+    rename: Option<ast::Rename>,
+    star: bool,
+) -> Option<ast::UseTree> {
+    match (path, list, star) {
+        (Some(path), list, star) => Some(make.use_tree(path, list, rename, star)),
+        (None, Some(list), false) if rename.is_none() => make_use_tree_from_list(make, list),
+        (None, None, true) if rename.is_none() => Some(make.use_tree_glob()),
+        (None, None, false) if rename.is_none() => None,
+        _ => None,
+    }
+}
+
+fn with_use_tree_list(
+    use_tree: &ast::UseTree,
+    use_trees: Vec<ast::UseTree>,
+    make: &SyntaxFactory,
+) -> Option<ast::UseTree> {
+    let list = make_use_tree_list(make, use_trees, use_tree.use_tree_list().as_ref())?;
+    make_use_tree_from_parts(
+        make,
+        use_tree.path(),
+        Some(list),
+        use_tree.rename(),
+        use_tree.star_token().is_some(),
+    )
+}
+
+pub(crate) fn wrap_in_tree_list(
+    use_tree: &ast::UseTree,
+    make: &SyntaxFactory,
+) -> Option<ast::UseTree> {
+    if use_tree.path().is_none()
+        && use_tree.use_tree_list().is_some()
+        && use_tree.rename().is_none()
+        && use_tree.star_token().is_none()
+    {
+        return None;
+    }
+
+    let list = make_use_tree_list(make, vec![use_tree.clone()], None)?;
+    make_use_tree_from_list(make, list)
+}
+
+fn split_prefix(
+    use_tree: &ast::UseTree,
+    prefix: &ast::Path,
+    make: &SyntaxFactory,
+) -> Option<ast::UseTree> {
+    let path = use_tree.path()?;
+    if path == *prefix && use_tree.use_tree_list().is_some() {
+        return Some(use_tree.clone());
+    }
+
+    let suffix = if path == *prefix {
+        if use_tree.star_token().is_some() {
+            make.use_tree_glob()
+        } else {
+            let self_path = make.path_unqualified(make.path_segment_self());
+            make.use_tree(self_path, None, use_tree.rename(), false)
+        }
+    } else {
+        let suffix_segments = path.segments().skip(prefix.segments().count());
+        let suffix_path = make.path_from_segments(suffix_segments, false);
+        make.use_tree(
+            suffix_path,
+            use_tree.use_tree_list(),
+            use_tree.rename(),
+            use_tree.star_token().is_some(),
+        )
+    };
+
+    let list = make_use_tree_list(make, vec![suffix], None)?;
+    Some(make.use_tree(prefix.clone(), Some(list), None, false))
 }
 
 // Taken from rustfmt
diff --git a/crates/ide-diagnostics/src/handlers/invalid_cast.rs b/crates/ide-diagnostics/src/handlers/invalid_cast.rs
index bd8fa69..e1c2053 100644
--- a/crates/ide-diagnostics/src/handlers/invalid_cast.rs
+++ b/crates/ide-diagnostics/src/handlers/invalid_cast.rs
@@ -1025,7 +1025,6 @@
         check_diagnostics(
             r#"
 //- minicore: coerce_unsized, dispatch_from_dyn
-#![feature(trait_upcasting)]
 trait Foo {}
 trait Bar: Foo {}
 
diff --git a/crates/ide-diagnostics/src/handlers/mismatched_arg_count.rs b/crates/ide-diagnostics/src/handlers/mismatched_arg_count.rs
index f6293e3..d1c3d1c 100644
--- a/crates/ide-diagnostics/src/handlers/mismatched_arg_count.rs
+++ b/crates/ide-diagnostics/src/handlers/mismatched_arg_count.rs
@@ -355,6 +355,30 @@
     }
 
     #[test]
+    fn varargs_fn_pointer() {
+        check_diagnostics(
+            r#"
+struct Funcs {
+    f: unsafe extern "C" fn(u8, u8, ...) -> i32,
+    g: unsafe extern "C" fn(...) -> i32,
+}
+
+fn f(funcs: Funcs) {
+    unsafe {
+        (funcs.f)(0, 1);
+        (funcs.f)(0, 1, 2);
+        (funcs.f)(0);
+                 //^ error: expected 2 arguments, found 1
+        (funcs.g)();
+        (funcs.g)(0);
+        (funcs.g)(0, 1);
+    }
+}
+        "#,
+        )
+    }
+
+    #[test]
     fn arg_count_lambda() {
         check_diagnostics(
             r#"
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index 88cb570..dded015 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -60,6 +60,7 @@
 mod view_syntax_tree;
 
 use std::panic::{AssertUnwindSafe, UnwindSafe};
+use std::time::Duration;
 
 use cfg::CfgOptions;
 use fetch_crates::CrateInfo;
@@ -197,8 +198,8 @@
 
     /// Applies changes to the current state of the world. If there are
     /// outstanding snapshots, they will be canceled.
-    pub fn apply_change(&mut self, change: ChangeWithProcMacros) {
-        self.db.apply_change(change);
+    pub fn apply_change(&mut self, change: ChangeWithProcMacros) -> Duration {
+        self.db.apply_change(change)
     }
 
     /// NB: this clears the database
diff --git a/crates/ide/src/navigation_target.rs b/crates/ide/src/navigation_target.rs
index b8c14dc0..2403142 100644
--- a/crates/ide/src/navigation_target.rs
+++ b/crates/ide/src/navigation_target.rs
@@ -50,7 +50,6 @@
     pub kind: Option<SymbolKind>,
     pub container_name: Option<Symbol>,
     pub description: Option<String>,
-    // FIXME: Use the database lifetime here.
     pub docs: Option<Documentation<'static>>,
     /// In addition to a `name` field, a `NavigationTarget` may also be aliased
     /// In such cases we want a `NavigationTarget` to be accessible by its alias
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_general.html b/crates/ide/src/syntax_highlighting/test_data/highlight_general.html
index 1184739..2f2c7f2 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_general.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_general.html
@@ -128,9 +128,9 @@
     <span class="keyword">let</span> <span class="variable declaration mutable reference">y</span> <span class="operator">=</span> <span class="operator">&</span><span class="keyword">mut</span> <span class="variable mutable">x</span><span class="semicolon">;</span>
     <span class="keyword">let</span> <span class="variable declaration reference">z</span> <span class="operator">=</span> <span class="operator">&</span><span class="variable mutable reference">y</span><span class="semicolon">;</span>
 
-    <span class="keyword">let</span> <span class="struct">Foo</span> <span class="brace">{</span> <span class="field">x</span><span class="colon">:</span> <span class="variable declaration">z</span><span class="comma">,</span> <span class="variable callable declaration">y</span> <span class="brace">}</span> <span class="operator">=</span> <span class="struct">Foo</span> <span class="brace">{</span> <span class="field">x</span><span class="colon">:</span> <span class="variable reference">z</span><span class="comma">,</span> <span class="variable mutable reference">y</span> <span class="brace">}</span><span class="semicolon">;</span>
+    <span class="keyword">let</span> <span class="struct">Foo</span> <span class="brace">{</span> <span class="field">x</span><span class="colon">:</span> <span class="variable declaration">z</span><span class="comma">,</span> <span class="variable declaration">y</span> <span class="brace">}</span> <span class="operator">=</span> <span class="struct">Foo</span> <span class="brace">{</span> <span class="field">x</span><span class="colon">:</span> <span class="variable reference">z</span><span class="comma">,</span> <span class="variable mutable reference">y</span> <span class="brace">}</span><span class="semicolon">;</span>
 
-    <span class="variable callable">y</span><span class="semicolon">;</span>
+    <span class="variable">y</span><span class="semicolon">;</span>
 
     <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">foo</span> <span class="operator">=</span> <span class="struct">Foo</span> <span class="brace">{</span> <span class="field">x</span><span class="comma">,</span> <span class="unresolved_reference">y</span><span class="colon">:</span> <span class="variable mutable">x</span> <span class="brace">}</span><span class="semicolon">;</span>
     <span class="keyword">let</span> <span class="variable declaration">foo2</span> <span class="operator">=</span> <span class="struct">Foo</span> <span class="brace">{</span> <span class="field">x</span><span class="comma">,</span> <span class="unresolved_reference">y</span><span class="colon">:</span> <span class="variable mutable">x</span> <span class="brace">}</span><span class="semicolon">;</span>
@@ -143,7 +143,7 @@
     <span class="variable mutable">copy</span><span class="operator">.</span><span class="method mutable reference">qux</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
     <span class="variable mutable">copy</span><span class="operator">.</span><span class="method">baz</span><span class="parenthesis">(</span><span class="variable mutable">copy</span><span class="parenthesis">)</span><span class="semicolon">;</span>
 
-    <span class="keyword">let</span> <span class="variable callable declaration">a</span> <span class="operator">=</span> <span class="punctuation">|</span><span class="value_param callable declaration">x</span><span class="punctuation">|</span> <span class="value_param callable">x</span><span class="semicolon">;</span>
+    <span class="keyword">let</span> <span class="variable callable declaration">a</span> <span class="operator">=</span> <span class="punctuation">|</span><span class="value_param declaration">x</span><span class="punctuation">|</span> <span class="value_param">x</span><span class="semicolon">;</span>
     <span class="keyword">let</span> <span class="variable callable declaration">bar</span> <span class="operator">=</span> <span class="struct">Foo</span><span class="operator">::</span><span class="method associated consuming">baz</span><span class="semicolon">;</span>
 
     <span class="keyword">let</span> <span class="variable declaration">baz</span> <span class="operator">=</span> <span class="parenthesis">(</span><span class="numeric_literal">-</span><span class="numeric_literal">42</span><span class="comma">,</span><span class="parenthesis">)</span><span class="semicolon">;</span>
@@ -173,13 +173,13 @@
 <span class="brace">}</span>
 
 <span class="keyword async">async</span> <span class="keyword">fn</span> <span class="function async declaration">learn_and_sing</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
-    <span class="keyword">let</span> <span class="variable callable declaration">song</span> <span class="operator">=</span> <span class="unresolved_reference">learn_song</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="operator">.</span><span class="keyword async control">await</span><span class="semicolon">;</span>
-    <span class="unresolved_reference">sing_song</span><span class="parenthesis">(</span><span class="variable callable">song</span><span class="parenthesis">)</span><span class="operator">.</span><span class="keyword async control">await</span><span class="semicolon">;</span>
+    <span class="keyword">let</span> <span class="variable declaration">song</span> <span class="operator">=</span> <span class="unresolved_reference">learn_song</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="operator">.</span><span class="keyword async control">await</span><span class="semicolon">;</span>
+    <span class="unresolved_reference">sing_song</span><span class="parenthesis">(</span><span class="variable consuming">song</span><span class="parenthesis">)</span><span class="operator">.</span><span class="keyword async control">await</span><span class="semicolon">;</span>
 <span class="brace">}</span>
 
 <span class="keyword async">async</span> <span class="keyword">fn</span> <span class="function async declaration">async_main</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
     <span class="keyword">let</span> <span class="variable declaration">f1</span> <span class="operator">=</span> <span class="function async">learn_and_sing</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
-    <span class="keyword">let</span> <span class="variable callable declaration">f2</span> <span class="operator">=</span> <span class="unresolved_reference">dance</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+    <span class="keyword">let</span> <span class="variable declaration">f2</span> <span class="operator">=</span> <span class="unresolved_reference">dance</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
     <span class="unresolved_reference">futures</span><span class="operator">::</span><span class="unresolved_reference">join</span><span class="macro_bang">!</span><span class="parenthesis">(</span>f1<span class="comma">,</span> f2<span class="parenthesis">)</span><span class="semicolon">;</span>
 <span class="brace">}</span>
 
diff --git a/crates/intern/Cargo.toml b/crates/intern/Cargo.toml
index 2ba802f..adf49ca 100644
--- a/crates/intern/Cargo.toml
+++ b/crates/intern/Cargo.toml
@@ -12,7 +12,6 @@
 [lib]
 doctest = false
 
-
 [dependencies]
 dashmap.workspace = true
 # We need to freeze the version of the crate, as it needs to match with dashmap
diff --git a/crates/intern/src/gc.rs b/crates/intern/src/gc.rs
index f4e8f75..596b05e 100644
--- a/crates/intern/src/gc.rs
+++ b/crates/intern/src/gc.rs
@@ -34,9 +34,7 @@
         for item in storage {
             let item = item.key();
             let addr = Arc::as_ptr(item).addr();
-            if Arc::strong_count(item) > 1 {
-                // The item is referenced from the outside.
-                gc.alive.insert(addr);
+            if Arc::strong_count(item) > 1 && gc.alive.insert(addr) {
                 item.visit_with(gc);
             }
         }
@@ -60,9 +58,7 @@
         for item in storage {
             let item = item.key();
             let addr = ThinArc::as_ptr(item).addr();
-            if ThinArc::strong_count(item) > 1 {
-                // The item is referenced from the outside.
-                gc.alive.insert(addr);
+            if ThinArc::strong_count(item) > 1 && gc.alive.insert(addr) {
                 T::visit_header(&item.header.header, gc);
                 T::visit_slice(&item.slice, gc);
             }
@@ -81,7 +77,7 @@
 
 pub trait GcInternedSliceVisit: SliceInternable {
     fn visit_header(header: &Self::Header, gc: &mut GarbageCollector);
-    fn visit_slice(header: &[Self::SliceType], gc: &mut GarbageCollector);
+    fn visit_slice(slice: &[Self::SliceType], gc: &mut GarbageCollector);
 }
 
 #[derive(Default)]
@@ -103,11 +99,13 @@
         self.storages.push(&InternedSliceStorage::<T>(PhantomData));
     }
 
+    /// Collects unreachable GC-managed interned values.
+    ///
     /// # Safety
     ///
     ///  - This cannot be called if there are some not-yet-recorded type values.
-    ///  - All relevant storages must have been added; that is, within the full graph of values,
-    ///    the added storages must form a DAG.
+    ///  - All storages that can contain live GC-managed values must have been added, and those
+    ///    storages must be closed over the GC-managed values reachable from them.
     ///  - [`GcInternedVisit`] and [`GcInternedSliceVisit`] must mark all values reachable from the node.
     pub unsafe fn collect(mut self) {
         if cfg!(feature = "prevent-gc") {
@@ -136,8 +134,9 @@
         &mut self,
         interned: InternedRef<'_, T>,
     ) -> ControlFlow<()> {
+        const { assert!(T::USE_GC) };
+
         if interned.strong_count() > 1 {
-            // It will be visited anyway, so short-circuit
             return ControlFlow::Break(());
         }
         let addr = interned.as_raw().addr();
@@ -148,8 +147,9 @@
         &mut self,
         interned: InternedSliceRef<'_, T>,
     ) -> ControlFlow<()> {
+        const { assert!(T::USE_GC) };
+
         if interned.strong_count() > 1 {
-            // It will be visited anyway, so short-circuit
             return ControlFlow::Break(());
         }
         let addr = interned.as_raw().addr();
@@ -240,7 +240,7 @@
         impl GcInternedSliceVisit for StringSlice {
             fn visit_header(_header: &Self::Header, _gc: &mut GarbageCollector) {}
 
-            fn visit_slice(_header: &[Self::SliceType], _gc: &mut GarbageCollector) {}
+            fn visit_slice(_slice: &[Self::SliceType], _gc: &mut GarbageCollector) {}
         }
 
         let (a, d) = {
@@ -276,6 +276,10 @@
         gc.add_storage::<GcString>();
         unsafe { gc.collect() };
 
+        if !cfg!(feature = "prevent-gc") {
+            assert_eq!(<GcString as crate::Internable>::storage().get().len(), 1);
+            assert_eq!(<StringSlice as crate::SliceInternable>::storage().get().len(), 1);
+        }
         assert_eq!(a.0, "abc");
         assert_eq!(d.header.length, 2);
         assert_eq!(d.header.header, "abc");
@@ -288,6 +292,11 @@
         gc.add_slice_storage::<StringSlice>();
         gc.add_storage::<GcString>();
         unsafe { gc.collect() };
+
+        if !cfg!(feature = "prevent-gc") {
+            assert_eq!(<GcString as crate::Internable>::storage().get().len(), 0);
+            assert_eq!(<StringSlice as crate::SliceInternable>::storage().get().len(), 0);
+        }
     }
 
     #[test]
@@ -309,7 +318,7 @@
         impl GcInternedSliceVisit for StringSlice {
             fn visit_header(_header: &Self::Header, _gc: &mut GarbageCollector) {}
 
-            fn visit_slice(_header: &[Self::SliceType], _gc: &mut GarbageCollector) {}
+            fn visit_slice(_slice: &[Self::SliceType], _gc: &mut GarbageCollector) {}
         }
 
         let outer = {
@@ -322,6 +331,10 @@
         gc.add_storage::<GcInterned>();
         unsafe { gc.collect() };
 
+        if !cfg!(feature = "prevent-gc") {
+            assert_eq!(<GcInterned as crate::Internable>::storage().get().len(), 1);
+            assert_eq!(<StringSlice as crate::SliceInternable>::storage().get().len(), 1);
+        }
         assert_eq!(outer.0.header.header, "abc");
         assert_eq!(outer.0.slice, [123, 456, 789]);
 
@@ -331,5 +344,10 @@
         gc.add_slice_storage::<StringSlice>();
         gc.add_storage::<GcInterned>();
         unsafe { gc.collect() };
+
+        if !cfg!(feature = "prevent-gc") {
+            assert_eq!(<GcInterned as crate::Internable>::storage().get().len(), 0);
+            assert_eq!(<StringSlice as crate::SliceInternable>::storage().get().len(), 0);
+        }
     }
 }
diff --git a/crates/parser/src/output.rs b/crates/parser/src/output.rs
index 2f09b11..ce64db8 100644
--- a/crates/parser/src/output.rs
+++ b/crates/parser/src/output.rs
@@ -18,7 +18,7 @@
     ///
     /// ```text
     /// |16 bit kind|8 bit n_input_tokens|4 bit tag|4 bit leftover|
-    /// ``````
+    /// ```
     event: Vec<u32>,
     error: Vec<String>,
 }
diff --git a/crates/proc-macro-srv/Cargo.toml b/crates/proc-macro-srv/Cargo.toml
index 72d394d..1b86eac 100644
--- a/crates/proc-macro-srv/Cargo.toml
+++ b/crates/proc-macro-srv/Cargo.toml
@@ -13,9 +13,6 @@
 doctest = false
 
 [dependencies]
-object.workspace = true
-libloading.workspace = true
-memmap2.workspace = true
 temp-dir.workspace = true
 
 paths.workspace = true
@@ -24,9 +21,6 @@
 intern.workspace = true
 
 
-[target.'cfg(unix)'.dependencies]
-libc.workspace = true
-
 [dev-dependencies]
 expect-test.workspace = true
 line-index.workspace = true
@@ -35,7 +29,7 @@
 proc-macro-test.path = "./proc-macro-test"
 
 [features]
-default = ["in-rust-tree"]
+default = []
 in-rust-tree = []
 
 [lints]
diff --git a/crates/proc-macro-srv/src/dylib.rs b/crates/proc-macro-srv/src/dylib.rs
index f654d21..1978a68 100644
--- a/crates/proc-macro-srv/src/dylib.rs
+++ b/crates/proc-macro-srv/src/dylib.rs
@@ -4,22 +4,18 @@
 
 use rustc_codegen_ssa::back::metadata::DefaultMetadataLoader;
 use rustc_interface::util::rustc_version_str;
-use rustc_metadata::locator::MetadataError;
 use rustc_proc_macro::bridge;
 use rustc_session::config::host_tuple;
 use rustc_target::spec::{Target, TargetTuple};
 use std::path::Path;
-use std::{fmt, fs, io, time::SystemTime};
+use std::{fs, io, time::SystemTime};
 use temp_dir::TempDir;
 
-use libloading::Library;
-use object::Object;
 use paths::{Utf8Path, Utf8PathBuf};
 
 use crate::{
     PanicMessage, ProcMacroClientHandle, ProcMacroKind, ProcMacroSrvSpan, TrackedEnv,
-    dylib::proc_macros::{ProcMacroClients, ProcMacros},
-    token_stream::TokenStream,
+    dylib::proc_macros::ProcMacros, token_stream::TokenStream,
 };
 
 pub(crate) struct Expander {
@@ -28,10 +24,7 @@
 }
 
 impl Expander {
-    pub(crate) fn new(
-        temp_dir: &TempDir,
-        lib: &Utf8Path,
-    ) -> Result<Expander, LoadProcMacroDylibError> {
+    pub(crate) fn new(temp_dir: &TempDir, lib: &Utf8Path) -> io::Result<Expander> {
         // Some libraries for dynamic loading require canonicalized path even when it is
         // already absolute
         let lib = lib.canonicalize_utf8()?;
@@ -78,61 +71,17 @@
     }
 }
 
-#[derive(Debug)]
-pub enum LoadProcMacroDylibError {
-    Io(io::Error),
-    LibLoading(libloading::Error),
-    MetadataError(MetadataError<'static>),
-}
-
-impl fmt::Display for LoadProcMacroDylibError {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        match self {
-            Self::Io(e) => e.fmt(f),
-            Self::LibLoading(e) => e.fmt(f),
-            Self::MetadataError(e) => e.fmt(f),
-        }
-    }
-}
-
-impl From<io::Error> for LoadProcMacroDylibError {
-    fn from(e: io::Error) -> Self {
-        LoadProcMacroDylibError::Io(e)
-    }
-}
-
-impl From<libloading::Error> for LoadProcMacroDylibError {
-    fn from(e: libloading::Error) -> Self {
-        LoadProcMacroDylibError::LibLoading(e)
-    }
-}
-
-impl From<MetadataError<'_>> for LoadProcMacroDylibError {
-    fn from(e: MetadataError<'_>) -> Self {
-        LoadProcMacroDylibError::MetadataError(match e {
-            MetadataError::NotPresent(path) => MetadataError::NotPresent(path.into_owned().into()),
-            MetadataError::LoadFailure(err) => MetadataError::LoadFailure(err),
-            MetadataError::VersionMismatch { expected_version, found_version } => {
-                MetadataError::VersionMismatch { expected_version, found_version }
-            }
-        })
-    }
-}
-
 struct ProcMacroLibrary {
-    // this contains references to the library, so make sure this drops before _lib
     proc_macros: ProcMacros,
-    // Hold on to the library so it doesn't unload
-    _lib: Library,
 }
 
 impl ProcMacroLibrary {
-    fn open(path: &Utf8Path) -> Result<Self, LoadProcMacroDylibError> {
-        let proc_macro_kinds = rustc_span::create_default_session_globals_then(|| {
+    fn open(path: &Utf8Path) -> io::Result<Self> {
+        let proc_macros = rustc_span::create_default_session_globals_then(|| {
             let (target, _) =
                 Target::search(&TargetTuple::from_tuple(host_tuple()), Path::new(""), false)
                     .unwrap();
-            rustc_metadata::locator::get_proc_macro_info(
+            rustc_metadata::locator::get_proc_macros(
                 &target,
                 path.as_ref(),
                 &DefaultMetadataLoader,
@@ -140,63 +89,10 @@
             )
         })?;
 
-        let file = fs::File::open(path)?;
-        #[allow(clippy::undocumented_unsafe_blocks)] // FIXME
-        let file = unsafe { memmap2::Mmap::map(&file) }?;
-        let obj = object::File::parse(&*file)
-            .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
-        let symbol_name =
-            find_registrar_symbol(&obj).map_err(invalid_data_err)?.ok_or_else(|| {
-                invalid_data_err(format!("Cannot find registrar symbol in file {path}"))
-            })?;
-
-        // SAFETY: We have verified the validity of the dylib as a proc-macro library
-        let lib = unsafe { load_library(path) }.map_err(invalid_data_err)?;
-        // SAFETY: We have verified the validity of the dylib as a proc-macro library
-        // The 'static lifetime is a lie, it's actually the lifetime of the library but unavoidable
-        // due to self-referentiality
-        // But we make sure that we do not drop it before the symbol is dropped
-        let proc_macros =
-            unsafe { lib.get::<&'static &'static ProcMacroClients>(symbol_name.as_bytes()) };
-        match proc_macros {
-            Ok(proc_macros) => Ok(ProcMacroLibrary {
-                proc_macros: ProcMacros::new(*proc_macros, proc_macro_kinds),
-                _lib: lib,
-            }),
-            Err(e) => Err(e.into()),
-        }
+        Ok(ProcMacroLibrary { proc_macros: ProcMacros::new(proc_macros) })
     }
 }
 
-fn invalid_data_err(e: impl Into<Box<dyn std::error::Error + Send + Sync>>) -> io::Error {
-    io::Error::new(io::ErrorKind::InvalidData, e)
-}
-
-fn is_derive_registrar_symbol(symbol: &str) -> bool {
-    const NEW_REGISTRAR_SYMBOL: &str = "_rustc_proc_macro_decls_";
-    symbol.contains(NEW_REGISTRAR_SYMBOL)
-}
-
-fn find_registrar_symbol(obj: &object::File<'_>) -> object::Result<Option<String>> {
-    Ok(obj
-        .exports()?
-        .into_iter()
-        .map(|export| export.name())
-        .filter_map(|sym| String::from_utf8(sym.into()).ok())
-        .find(|sym| is_derive_registrar_symbol(sym))
-        .map(|sym| {
-            // From MacOS docs:
-            // https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/dlsym.3.html
-            // Unlike other dyld API's, the symbol name passed to dlsym() must NOT be
-            // prepended with an underscore.
-            if cfg!(target_os = "macos") && sym.starts_with('_') {
-                sym[1..].to_owned()
-            } else {
-                sym
-            }
-        }))
-}
-
 /// Copy the dylib to temp directory to prevent locking in Windows
 #[cfg(windows)]
 fn ensure_file_with_lock_free_access(
@@ -233,54 +129,3 @@
 ) -> io::Result<Utf8PathBuf> {
     Ok(path.to_owned())
 }
-
-/// Loads dynamic library in platform dependent manner.
-///
-/// For unix, you have to use RTLD_DEEPBIND flag to escape problems described
-/// [here](https://github.com/fedochet/rust-proc-macro-panic-inside-panic-expample)
-/// and [here](https://github.com/rust-lang/rust/issues/60593).
-///
-/// Usage of RTLD_DEEPBIND
-/// [here](https://github.com/fedochet/rust-proc-macro-panic-inside-panic-expample/issues/1)
-///
-/// It seems that on Windows that behaviour is default, so we do nothing in that case.
-///
-/// # Safety
-///
-/// The caller is responsible for ensuring that the path is valid proc-macro library
-#[cfg(windows)]
-unsafe fn load_library(file: &Utf8Path) -> Result<Library, libloading::Error> {
-    // SAFETY: The caller is responsible for ensuring that the path is valid proc-macro library
-    unsafe { Library::new(file) }
-}
-
-/// Loads dynamic library in platform dependent manner.
-///
-/// For unix, you have to use RTLD_DEEPBIND flag to escape problems described
-/// [here](https://github.com/fedochet/rust-proc-macro-panic-inside-panic-expample)
-/// and [here](https://github.com/rust-lang/rust/issues/60593).
-///
-/// Usage of RTLD_DEEPBIND
-/// [here](https://github.com/fedochet/rust-proc-macro-panic-inside-panic-expample/issues/1)
-///
-/// It seems that on Windows that behaviour is default, so we do nothing in that case.
-///
-/// # Safety
-///
-/// The caller is responsible for ensuring that the path is valid proc-macro library
-#[cfg(unix)]
-unsafe fn load_library(file: &Utf8Path) -> Result<Library, libloading::Error> {
-    // not defined by POSIX, different values on mips vs other targets
-    #[cfg(target_env = "gnu")]
-    use libc::RTLD_DEEPBIND;
-    use libloading::os::unix::Library as UnixLibrary;
-    // defined by POSIX
-    use libloading::os::unix::RTLD_NOW;
-
-    // MUSL and bionic don't have it..
-    #[cfg(not(target_env = "gnu"))]
-    const RTLD_DEEPBIND: std::os::raw::c_int = 0x0;
-
-    // SAFETY: The caller is responsible for ensuring that the path is valid proc-macro library
-    unsafe { UnixLibrary::open(Some(file), RTLD_NOW | RTLD_DEEPBIND).map(|lib| lib.into()) }
-}
diff --git a/crates/proc-macro-srv/src/dylib/proc_macros.rs b/crates/proc-macro-srv/src/dylib/proc_macros.rs
index ef6752d..4976298 100644
--- a/crates/proc-macro-srv/src/dylib/proc_macros.rs
+++ b/crates/proc-macro-srv/src/dylib/proc_macros.rs
@@ -4,9 +4,6 @@
 };
 use rustc_proc_macro::bridge;
 
-#[repr(transparent)]
-pub(crate) struct ProcMacroClients([bridge::client::Client]);
-
 impl From<bridge::PanicMessage> for crate::PanicMessage {
     fn from(p: bridge::PanicMessage) -> Self {
         Self { message: p.into_string() }
@@ -17,10 +14,9 @@
 
 impl ProcMacros {
     pub(super) fn new(
-        clients: &ProcMacroClients,
-        kinds: Vec<rustc_metadata::ProcMacroKind>,
+        macros: Vec<(bridge::client::Client, rustc_metadata::ProcMacroKind)>,
     ) -> Self {
-        ProcMacros(clients.0.iter().copied().zip(kinds).collect::<Vec<_>>())
+        ProcMacros(macros)
     }
 
     pub(crate) fn expand<'a, S: ProcMacroSrvSpan>(
diff --git a/crates/proc-macro-srv/src/lib.rs b/crates/proc-macro-srv/src/lib.rs
index b8cd362..2a3a1bc 100644
--- a/crates/proc-macro-srv/src/lib.rs
+++ b/crates/proc-macro-srv/src/lib.rs
@@ -10,9 +10,10 @@
 
 #![cfg(feature = "in-rust-tree")]
 #![feature(proc_macro_internals, proc_macro_diagnostic, proc_macro_span, rustc_private)]
-#![expect(unreachable_pub, internal_features, clippy::disallowed_types, clippy::print_stderr)]
+#![expect(internal_features, clippy::disallowed_types, clippy::print_stderr)]
 #![allow(unused_features, unused_crate_dependencies)]
 #![deny(deprecated_safe, clippy::undocumented_unsafe_blocks)]
+#![cfg_attr(test, expect(unreachable_pub))]
 
 extern crate rustc_codegen_ssa;
 extern crate rustc_driver as _;
diff --git a/crates/project-model/src/build_dependencies.rs b/crates/project-model/src/build_dependencies.rs
index e44af96..352f41a 100644
--- a/crates/project-model/src/build_dependencies.rs
+++ b/crates/project-model/src/build_dependencies.rs
@@ -560,7 +560,7 @@
 
 // FIXME: Find a better way to know if it is a dylib.
 fn is_dylib(path: &Utf8Path) -> bool {
-    match path.extension().map(|e| e.to_owned().to_lowercase()) {
+    match path.extension().map(|e| e.to_ascii_lowercase()) {
         None => false,
         Some(ext) => matches!(ext.as_str(), "dll" | "dylib" | "so"),
     }
diff --git a/crates/project-model/src/workspace.rs b/crates/project-model/src/workspace.rs
index 6755336..c9bf803 100644
--- a/crates/project-model/src/workspace.rs
+++ b/crates/project-model/src/workspace.rs
@@ -811,6 +811,7 @@
                     .packages()
                     .map(|pkg| {
                         let is_local = cargo[pkg].is_local;
+                        let is_member = cargo[pkg].is_member;
                         let pkg_root = cargo[pkg].manifest.parent().to_path_buf();
 
                         let mut include = vec![pkg_root.clone()];
@@ -844,9 +845,11 @@
                         let mut exclude = vec![pkg_root.join(".git")];
                         if is_local {
                             include.extend(self.extra_includes.iter().cloned());
-
                             exclude.push(pkg_root.join("target"));
-                        } else {
+                        }
+                        if !is_member {
+                            // For non-workspace-members, we only resolve library targets,
+                            // so none of these need to be loaded into the VFS.
                             exclude.push(pkg_root.join("tests"));
                             exclude.push(pkg_root.join("examples"));
                             exclude.push(pkg_root.join("benches"));
@@ -874,6 +877,7 @@
                 .chain(cargo_script.iter().flat_map(|(cargo, build_scripts, _)| {
                     cargo.packages().map(|pkg| {
                         let is_local = cargo[pkg].is_local;
+                        let is_member = cargo[pkg].is_member;
                         let pkg_root = cargo[pkg].manifest.parent().to_path_buf();
 
                         let mut include = vec![pkg_root.clone()];
@@ -909,7 +913,10 @@
                             include.extend(self.extra_includes.iter().cloned());
 
                             exclude.push(pkg_root.join("target"));
-                        } else {
+                        }
+                        if !is_member {
+                            // For non-workspace-members, we only resolve library targets,
+                            // so none of these need to be loaded into the VFS.
                             exclude.push(pkg_root.join("tests"));
                             exclude.push(pkg_root.join("examples"));
                             exclude.push(pkg_root.join("benches"));
diff --git a/crates/rust-analyzer/src/cli/rustc_tests.rs b/crates/rust-analyzer/src/cli/rustc_tests.rs
index 49f2835..69405b4 100644
--- a/crates/rust-analyzer/src/cli/rustc_tests.rs
+++ b/crates/rust-analyzer/src/cli/rustc_tests.rs
@@ -64,6 +64,7 @@
     fn new() -> Result<Self> {
         let mut path = AbsPathBuf::assert_utf8(std::env::temp_dir());
         path.push("ra-rustc-test");
+        std::fs::create_dir_all(&path)?;
         let tmp_file = path.join("ra-rustc-test.rs");
         std::fs::write(&tmp_file, "")?;
         let cargo_config = CargoConfig {
diff --git a/crates/rust-analyzer/src/cli/scip.rs b/crates/rust-analyzer/src/cli/scip.rs
index bca38ed..d3acbb9 100644
--- a/crates/rust-analyzer/src/cli/scip.rs
+++ b/crates/rust-analyzer/src/cli/scip.rs
@@ -237,7 +237,7 @@
             let token = si.tokens.get(id).unwrap();
 
             let Some(definition) = token.definition else {
-                break;
+                continue;
             };
 
             let file_id = definition.file_id;
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 6f532e4..4ea4473 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -662,9 +662,12 @@
         /// For traits the type "methods" can be used to only exclude the methods but not the trait
         /// itself.
         ///
-        /// For modules the type "subItems" can be used to only exclude the all items in it but not the module
+        /// For modules the type "sub_items" can be used to only exclude the all items in it but not the module
         /// itself. This does not include items defined in nested modules.
         ///
+        /// For enums the type "variants" can be used to only exclude the all variants in it but not the enum
+        /// itself.
+        ///
         /// This setting also inherits `#rust-analyzer.completion.excludeTraits#`.
         completion_autoimport_exclude: Vec<AutoImportExclusion> = vec![
             AutoImportExclusion::Verbose { path: "core::borrow::Borrow".to_owned(), r#type: AutoImportExclusionType::Methods },
@@ -1944,6 +1947,9 @@
                             AutoImportExclusionType::SubItems => {
                                 ide_completion::AutoImportExclusionType::SubItems
                             }
+                            AutoImportExclusionType::Variants => {
+                                ide_completion::AutoImportExclusionType::Variants
+                            }
                         },
                     ),
                 })
@@ -3004,6 +3010,7 @@
     Always,
     Methods,
     SubItems,
+    Variants,
 }
 
 #[derive(Serialize, Deserialize, Debug, Clone)]
@@ -4135,7 +4142,7 @@
                             },
                             "type": {
                                 "type": "string",
-                                "enum": ["always", "methods", "subItems"],
+                                "enum": ["always", "methods", "sub_items", "variants"],
                                 "enumDescriptions": [
                                     "Do not show this item or its methods (if it is a trait) in auto-import completions.",
                                     "Do not show this trait's methods in auto-import completions.",
diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs
index afd4162..f91e953 100644
--- a/crates/rust-analyzer/src/global_state.rs
+++ b/crates/rust-analyzer/src/global_state.rs
@@ -330,7 +330,7 @@
         this
     }
 
-    pub(crate) fn process_changes(&mut self) -> bool {
+    pub(crate) fn process_changes(&mut self) -> (bool, Option<Duration>) {
         let _p = span!(Level::INFO, "GlobalState::process_changes").entered();
         // We cannot directly resolve a change in a ratoml file to a format
         // that can be used by the config module because config talks
@@ -343,7 +343,7 @@
         let mut guard = self.vfs.write();
         let changed_files = guard.0.take_changes();
         if changed_files.is_empty() {
-            return false;
+            return (false, None);
         }
 
         let (change, modified_rust_files, workspace_structure_change) =
@@ -439,7 +439,7 @@
                 (change, modified_rust_files, workspace_structure_change)
             });
 
-        self.analysis_host.apply_change(change);
+        let cancellation_time = self.analysis_host.apply_change(change);
 
         if !modified_ratoml_files.is_empty()
             || !self.config.same_source_root_parent_map(&self.local_roots_parent_map)
@@ -561,7 +561,7 @@
             }
         }
 
-        true
+        (true, Some(cancellation_time))
     }
 
     pub(crate) fn snapshot(&self) -> GlobalStateSnapshot {
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index edf3da5..d966e29 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -307,15 +307,11 @@
         let _p = tracing::info_span!("GlobalState::handle_event", event = %event).entered();
 
         let event_dbg_msg = format!("{event:?}");
-        tracing::debug!(?loop_start, ?event, "handle_event");
-        if tracing::enabled!(tracing::Level::TRACE) {
-            let task_queue_len = self.task_pool.handle.len();
-            if task_queue_len > 0 {
-                tracing::trace!("task queue len: {}", task_queue_len);
-            }
-        }
+        tracing::debug!(?event, "handle_event");
 
         let was_quiescent = self.is_quiescent();
+
+        let mut cancellation_time = None;
         match event {
             Event::Lsp(msg) => match msg {
                 lsp_server::Message::Request(req) => self.on_new_request(loop_start, req),
@@ -326,7 +322,9 @@
                 let _p = tracing::info_span!("GlobalState::handle_event/queued_task").entered();
                 self.handle_deferred_task(task);
                 // Coalesce multiple deferred task events into one loop turn
-                while let Ok(task) = self.deferred_task_queue.receiver.try_recv() {
+                while loop_start.elapsed() < Duration::from_millis(50)
+                    && let Ok(task) = self.deferred_task_queue.receiver.try_recv()
+                {
                     self.handle_deferred_task(task);
                 }
             }
@@ -334,14 +332,16 @@
                 let _p = tracing::info_span!("GlobalState::handle_event/task").entered();
                 let mut prime_caches_progress = Vec::new();
 
-                self.handle_task(&mut prime_caches_progress, task);
+                cancellation_time = self.handle_task(&mut prime_caches_progress, task);
                 // Coalesce multiple task events into one loop turn
-                while let Ok(task) = self.task_pool.receiver.try_recv() {
+                while loop_start.elapsed() < Duration::from_millis(50)
+                    && let Ok(task) = self.task_pool.receiver.try_recv()
+                {
                     self.handle_task(&mut prime_caches_progress, task);
                 }
 
                 let title = "Indexing";
-                let cancel_token = Some("rustAnalyzer/cachePriming".to_owned());
+                let cancel_token = || Some("rustAnalyzer/cachePriming".to_owned());
 
                 let mut last_report = None;
                 for progress in prime_caches_progress {
@@ -352,7 +352,7 @@
                                 Progress::Begin,
                                 None,
                                 Some(0.0),
-                                cancel_token.clone(),
+                                cancel_token(),
                             );
                         }
                         PrimeCachesProgress::Report(report) => {
@@ -387,6 +387,16 @@
                             if cancelled {
                                 self.prime_caches_queue
                                     .request_op("restart after cancellation".to_owned(), ());
+                            } else if self.config.check_on_save(None)
+                                && self.config.flycheck_workspace(None)
+                                && !self.fetch_build_data_queue.op_requested()
+                            {
+                                // Priming finished; now run the deferred initial workspace flycheck
+                                // (kept off the critical path so `cargo check` doesn't contend with
+                                // cache priming for CPU).
+                                self.flycheck
+                                    .iter()
+                                    .for_each(|flycheck| flycheck.restart_workspace(None));
                             }
                             if let Some((message, fraction, title)) = last_report.take() {
                                 self.report_progress(
@@ -394,7 +404,7 @@
                                     Progress::Report,
                                     message,
                                     Some(fraction),
-                                    cancel_token.clone(),
+                                    cancel_token(),
                                 );
                             }
                             self.report_progress(
@@ -402,7 +412,7 @@
                                 Progress::End,
                                 None,
                                 Some(1.0),
-                                cancel_token.clone(),
+                                cancel_token(),
                             );
                         }
                     };
@@ -413,7 +423,7 @@
                         Progress::Report,
                         message,
                         Some(fraction),
-                        cancel_token.clone(),
+                        cancel_token(),
                     );
                 }
             }
@@ -422,7 +432,9 @@
                 let mut last_progress_report = None;
                 self.handle_vfs_msg(message, &mut last_progress_report);
                 // Coalesce many VFS event into a single loop turn
-                while let Ok(message) = self.loader.receiver.try_recv() {
+                while loop_start.elapsed() < Duration::from_millis(50)
+                    && let Ok(message) = self.loader.receiver.try_recv()
+                {
                     self.handle_vfs_msg(message, &mut last_progress_report);
                 }
                 if let Some((message, fraction)) = last_progress_report {
@@ -439,7 +451,9 @@
                 let mut cargo_finished = false;
                 self.handle_flycheck_msg(message, &mut cargo_finished);
                 // Coalesce many flycheck updates into a single loop turn
-                while let Ok(message) = self.flycheck_receiver.try_recv() {
+                while loop_start.elapsed() < Duration::from_millis(50)
+                    && let Ok(message) = self.flycheck_receiver.try_recv()
+                {
                     self.handle_flycheck_msg(message, &mut cargo_finished);
                 }
                 if cargo_finished {
@@ -453,14 +467,18 @@
                 let _p = tracing::info_span!("GlobalState::handle_event/test_result").entered();
                 self.handle_cargo_test_msg(message);
                 // Coalesce many test result event into a single loop turn
-                while let Ok(message) = self.test_run_receiver.try_recv() {
+                while loop_start.elapsed() < Duration::from_millis(50)
+                    && let Ok(message) = self.test_run_receiver.try_recv()
+                {
                     self.handle_cargo_test_msg(message);
                 }
             }
             Event::DiscoverProject(message) => {
                 self.handle_discover_msg(message);
                 // Coalesce many project discovery events into a single loop turn.
-                while let Ok(message) = self.discover_receiver.try_recv() {
+                while loop_start.elapsed() < Duration::from_millis(50)
+                    && let Ok(message) = self.discover_receiver.try_recv()
+                {
                     self.handle_discover_msg(message);
                 }
             }
@@ -469,26 +487,29 @@
             }
         }
         let event_handling_duration = loop_start.elapsed();
-        let (state_changed, memdocs_added_or_removed) = if self.vfs_done {
-            if let Some(cause) = self.wants_to_switch.take() {
-                self.switch_workspaces(cause);
-            }
-            (self.process_changes(), self.mem_docs.take_changes())
-        } else {
-            (false, false)
+        let ((state_changed, changes_cancellation_time), memdocs_added_or_removed) =
+            if self.vfs_done {
+                if let Some(cause) = self.wants_to_switch.take() {
+                    cancellation_time = match (cancellation_time, self.switch_workspaces(cause)) {
+                        (Some(a), Some(b)) => Some(a + b),
+                        (Some(d), None) | (None, Some(d)) => Some(d),
+                        (None, None) => None,
+                    };
+                }
+                (self.process_changes(), self.mem_docs.take_changes())
+            } else {
+                ((false, None), false)
+            };
+        cancellation_time = match (cancellation_time, changes_cancellation_time) {
+            (Some(a), Some(b)) => Some(a + b),
+            (Some(d), None) | (None, Some(d)) => Some(d),
+            (None, None) => None,
         };
 
         let mut gc_elapsed = None;
         if self.is_quiescent() {
             let became_quiescent = !was_quiescent;
             if became_quiescent {
-                if self.config.check_on_save(None)
-                    && self.config.flycheck_workspace(None)
-                    && !self.fetch_build_data_queue.op_requested()
-                {
-                    // Project has loaded properly, kick off initial flycheck
-                    self.flycheck.iter().for_each(|flycheck| flycheck.restart_workspace(None));
-                }
                 // delay initial cache priming until proc macros are loaded, or we will load up a bunch of garbage into salsa
                 let proc_macros_loaded = self.config.prefill_caches()
                     && (!self.config.expand_proc_macros()
@@ -496,6 +517,19 @@
                 if proc_macros_loaded {
                     self.prime_caches_queue.request_op("became quiescent".to_owned(), ());
                 }
+                if self.config.check_on_save(None)
+                    && self.config.flycheck_workspace(None)
+                    && !self.fetch_build_data_queue.op_requested()
+                {
+                    if !self.config.prefill_caches() {
+                        self.flycheck.iter().for_each(|flycheck| flycheck.restart_workspace(None));
+                    } else if proc_macros_loaded
+                        && !self.prime_caches_queue.op_in_progress()
+                        && !self.prime_caches_queue.op_requested()
+                    {
+                        self.flycheck.iter().for_each(|flycheck| flycheck.restart_workspace(None));
+                    }
+                }
             }
 
             let client_refresh = became_quiescent || state_changed;
@@ -593,11 +627,13 @@
             tracing::warn!(
                 "overly long loop turn took {loop_duration:?}:\n\
                 (event handling took {event_handling_duration:?}): {event_dbg_msg}\n\
+                (cancellation took {cancellation_time:?})
                 (garbage collection took {gc_elapsed:?})"
             );
             self.poke_rust_analyzer_developer(format!(
                 "overly long loop turn took {loop_duration:?}:\n\
                 (event handling took {event_handling_duration:?}): {event_dbg_msg}\n\
+                (cancellation took {cancellation_time:?})
                 (garbage collection took {gc_elapsed:?})"
             ));
         }
@@ -803,7 +839,12 @@
         }
     }
 
-    fn handle_task(&mut self, prime_caches_progress: &mut Vec<PrimeCachesProgress>, task: Task) {
+    fn handle_task(
+        &mut self,
+        prime_caches_progress: &mut Vec<PrimeCachesProgress>,
+        task: Task,
+    ) -> Option<Duration> {
+        let mut cancellation_time = None;
         match task {
             Task::Response(response) => self.respond(response),
             // Only retry requests that haven't been cancelled. Otherwise we do unnecessary work.
@@ -906,8 +947,9 @@
                     ProcMacroProgress::Report(msg) => (Some(Progress::Report), Some(msg)),
                     ProcMacroProgress::End(change) => {
                         self.fetch_proc_macros_queue.op_completed(true);
-                        self.analysis_host.apply_change(change);
-                        self.finish_loading_crate_graph();
+                        cancellation_time = Some(self.analysis_host.apply_change(change));
+                        // FIXME This feels a bit off, this should go through similar machinery as build scripts?
+                        _ = self.finish_loading_crate_graph();
                         (Some(Progress::End), None)
                     }
                 };
@@ -921,6 +963,7 @@
                 self.send_notification::<lsp_ext::DiscoveredTests>(tests);
             }
         }
+        cancellation_time
     }
 
     fn handle_vfs_msg(
diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs
index 74fd0e6..4940def 100644
--- a/crates/rust-analyzer/src/reload.rs
+++ b/crates/rust-analyzer/src/reload.rs
@@ -13,7 +13,7 @@
 //! project is currently loading and we don't have a full project model, we
 //! still want to respond to various  requests.
 // FIXME: This is a mess that needs some untangling work
-use std::{iter, mem, sync::atomic::AtomicUsize};
+use std::{iter, mem, sync::atomic::AtomicUsize, time::Duration};
 
 use hir::{ChangeWithProcMacros, ProcMacrosBuilder, db::DefDatabase};
 use ide_db::{
@@ -468,25 +468,22 @@
         });
     }
 
-    pub(crate) fn switch_workspaces(&mut self, cause: Cause) {
+    pub(crate) fn switch_workspaces(&mut self, cause: Cause) -> Option<Duration> {
         let _p = tracing::info_span!("GlobalState::switch_workspaces").entered();
         tracing::info!(%cause, "will switch workspaces");
 
-        let Some(FetchWorkspaceResponse { workspaces, force_crate_graph_reload }) =
-            self.fetch_workspaces_queue.last_op_result()
-        else {
-            return;
-        };
+        let FetchWorkspaceResponse { workspaces, force_crate_graph_reload } =
+            self.fetch_workspaces_queue.last_op_result()?;
         let switching_from_empty_workspace = self.workspaces.is_empty();
 
         info!(%cause, ?force_crate_graph_reload, %switching_from_empty_workspace);
         if self.fetch_workspace_error().is_err() && !switching_from_empty_workspace {
             if *force_crate_graph_reload {
-                self.recreate_crate_graph(cause, false);
+                return self.recreate_crate_graph(cause, false);
             }
             // It only makes sense to switch to a partially broken workspace
             // if we don't have any workspace at all yet.
-            return;
+            return None;
         }
 
         let workspaces =
@@ -501,7 +498,7 @@
         if same_workspaces {
             if switching_from_empty_workspace {
                 // Switching from empty to empty is a no-op
-                return;
+                return None;
             }
             if let Some(FetchBuildDataResponse { workspaces, build_scripts }) =
                 self.fetch_build_data_queue.last_op_result()
@@ -524,20 +521,20 @@
                 } else {
                     info!("build scripts do not match the version of the active workspace");
                     if *force_crate_graph_reload {
-                        self.recreate_crate_graph(cause, switching_from_empty_workspace);
+                        return self.recreate_crate_graph(cause, switching_from_empty_workspace);
                     }
 
                     // Current build scripts do not match the version of the active
                     // workspace, so there's nothing for us to update.
-                    return;
+                    return None;
                 }
             } else {
                 if *force_crate_graph_reload {
-                    self.recreate_crate_graph(cause, switching_from_empty_workspace);
+                    return self.recreate_crate_graph(cause, switching_from_empty_workspace);
                 }
 
                 // No build scripts but unchanged workspaces, nothing to do here
-                return;
+                return None;
             }
         } else {
             info!("abandon build scripts for workspaces");
@@ -560,7 +557,7 @@
                     // `switch_workspaces()` will be called again when build scripts already run, which should
                     // take a short time. If we update the workspace now we will invalidate proc macros and cfgs,
                     // and then when build scripts complete we will invalidate them again.
-                    return;
+                    return None;
                 }
             }
         }
@@ -733,13 +730,15 @@
         self.local_roots_parent_map = Arc::new(self.source_root_config.source_root_parent_map());
 
         info!(?cause, "recreating the crate graph");
-        self.recreate_crate_graph(cause, switching_from_empty_workspace);
+        let cancellation_time = self.recreate_crate_graph(cause, switching_from_empty_workspace);
 
         info!("did switch workspaces");
+        cancellation_time
     }
 
-    fn recreate_crate_graph(&mut self, cause: String, initial_build: bool) {
+    fn recreate_crate_graph(&mut self, cause: String, initial_build: bool) -> Option<Duration> {
         info!(?cause, "Building Crate Graph");
+        let mut cancellation_time = None;
         self.report_progress(
             "Building CrateGraph",
             crate::lsp::utils::Progress::Begin,
@@ -795,9 +794,8 @@
             }
 
             change.set_crate_graph(crate_graph);
-            self.analysis_host.apply_change(change);
-
-            self.finish_loading_crate_graph();
+            cancellation_time = Some(self.analysis_host.apply_change(change));
+            _ = self.finish_loading_crate_graph();
         } else {
             change.set_crate_graph(crate_graph);
             self.fetch_proc_macros_queue.request_op(cause, (change, proc_macro_paths));
@@ -810,11 +808,13 @@
             None,
             None,
         );
+        cancellation_time
     }
 
-    pub(crate) fn finish_loading_crate_graph(&mut self) {
-        self.process_changes();
+    pub(crate) fn finish_loading_crate_graph(&mut self) -> Option<Duration> {
+        let (_, cancellation_time) = self.process_changes();
         self.reload_flycheck();
+        cancellation_time
     }
 
     pub(super) fn fetch_workspace_error(&self) -> Result<(), String> {
diff --git a/crates/rust-analyzer/src/target_spec.rs b/crates/rust-analyzer/src/target_spec.rs
index 81d9786..fdcd2c9 100644
--- a/crates/rust-analyzer/src/target_spec.rs
+++ b/crates/rust-analyzer/src/target_spec.rs
@@ -153,6 +153,9 @@
                     Some(CargoTargetSpec { target_kind: TargetKind::Test, .. }) => {
                         config.test_command
                     }
+                    Some(CargoTargetSpec { target_kind: TargetKind::Bench, .. }) => {
+                        config.bench_command
+                    }
                     _ => "run".to_owned(),
                 };
                 cargo_args.push(subcommand);
@@ -225,6 +228,9 @@
                 Some(CargoTargetSpec { target_kind: TargetKind::Test, .. }) => {
                     (config.test_override_command, None)
                 }
+                Some(CargoTargetSpec { target_kind: TargetKind::Bench, .. }) => {
+                    (config.bench_override_command, None)
+                }
                 _ => (None, None),
             },
         };
@@ -319,7 +325,11 @@
 
     pub(crate) fn push_to(self, buf: &mut Vec<String>, kind: &RunnableKind) {
         buf.push("--package".to_owned());
-        buf.push(self.package);
+        if self.package.contains(":") {
+            buf.push(self.package_id.to_string());
+        } else {
+            buf.push(self.package);
+        }
 
         // Can't mix --doc with other target flags
         if let RunnableKind::DocTest { .. } = kind {
diff --git a/crates/rust-analyzer/src/task_pool.rs b/crates/rust-analyzer/src/task_pool.rs
index 104cd3d..2da52f7 100644
--- a/crates/rust-analyzer/src/task_pool.rs
+++ b/crates/rust-analyzer/src/task_pool.rs
@@ -40,10 +40,6 @@
         })
     }
 
-    pub(crate) fn len(&self) -> usize {
-        self.pool.len()
-    }
-
     pub(crate) fn is_empty(&self) -> bool {
         self.pool.is_empty()
     }
diff --git a/crates/syntax/src/ast.rs b/crates/syntax/src/ast.rs
index dc592a4..d8c7e15 100644
--- a/crates/syntax/src/ast.rs
+++ b/crates/syntax/src/ast.rs
@@ -1,7 +1,6 @@
 //! Abstract Syntax Tree, layered on top of untyped `SyntaxNode`s
 
 pub mod edit;
-pub mod edit_in_place;
 mod expr_ext;
 mod generated;
 pub mod make;
diff --git a/crates/syntax/src/ast/edit.rs b/crates/syntax/src/ast/edit.rs
index 2e3a401..eaa3690 100644
--- a/crates/syntax/src/ast/edit.rs
+++ b/crates/syntax/src/ast/edit.rs
@@ -13,7 +13,7 @@
     SyntaxKind::{ATTR, COMMENT, WHITESPACE},
     SyntaxNode, SyntaxToken,
     ast::{self, AstNode, HasName, make},
-    syntax_editor::{Position, SyntaxEditor, SyntaxMappingBuilder},
+    syntax_editor::{Position, Removable, SyntaxEditor, SyntaxMappingBuilder},
 };
 
 use super::syntax_factory::SyntaxFactory;
@@ -263,6 +263,109 @@
     level.clone_increase_indent(node)
 }
 
+impl ast::GenericParamList {
+    /// Constructs a matching [`ast::GenericArgList`]
+    pub fn to_generic_args(&self, make: &SyntaxFactory) -> ast::GenericArgList {
+        let args = self.generic_params().filter_map(|param| match param {
+            ast::GenericParam::LifetimeParam(it) => {
+                Some(ast::GenericArg::LifetimeArg(make.lifetime_arg(it.lifetime()?)))
+            }
+            ast::GenericParam::TypeParam(it) => {
+                Some(ast::GenericArg::TypeArg(make.type_arg(make.ty_name(it.name()?))))
+            }
+            ast::GenericParam::ConstParam(it) => {
+                // Name-only const params get parsed as `TypeArg`s
+                Some(ast::GenericArg::TypeArg(make.type_arg(make.ty_name(it.name()?))))
+            }
+        });
+
+        make::generic_arg_list(args)
+    }
+}
+
+impl ast::UseTree {
+    /// Deletes the usetree node represented by the input. Recursively removes parents, including use nodes that become empty.
+    pub fn remove_recursive(self, editor: &SyntaxEditor) {
+        let parent = self.syntax().parent();
+
+        if let Some(u) = parent.clone().and_then(ast::Use::cast) {
+            u.remove(editor);
+        } else if let Some(u) = parent.and_then(ast::UseTreeList::cast) {
+            if u.use_trees().nth(1).is_none()
+                || u.use_trees().all(|use_tree| {
+                    use_tree.syntax() == self.syntax() || editor.deleted(use_tree.syntax())
+                })
+            {
+                u.parent_use_tree().remove_recursive(editor);
+                return;
+            }
+            self.remove(editor);
+            u.remove_unnecessary_braces(editor);
+        }
+    }
+
+    /// Splits off the given prefix, making it the path component of the use tree,
+    /// appending the rest of the path to all UseTreeList items.
+    ///
+    /// # Examples
+    ///
+    /// `prefix$0::suffix` -> `prefix::{suffix}`
+    ///
+    /// `prefix$0` -> `prefix::{self}`
+    ///
+    /// `prefix$0::*` -> `prefix::{*}`
+    pub fn split_prefix_with_editor(&self, editor: &SyntaxEditor, prefix: &ast::Path) {
+        debug_assert_eq!(self.path(), Some(prefix.top_path()));
+
+        let make = editor.make();
+        let path = self.path().unwrap();
+        let suffix = if path == *prefix {
+            if self.use_tree_list().is_some() {
+                return;
+            } else if self.star_token().is_some() {
+                make.use_tree_glob()
+            } else {
+                let self_path = make.path_unqualified(make.path_segment_self());
+                make.use_tree(self_path, None, self.rename(), false)
+            }
+        } else {
+            let suffix_segments = path.segments().skip(prefix.segments().count());
+            let suffix_path = make.path_from_segments(suffix_segments, false);
+            make.use_tree(
+                suffix_path,
+                self.use_tree_list(),
+                self.rename(),
+                self.star_token().is_some(),
+            )
+        };
+        let use_tree_list = make.use_tree_list(once(suffix));
+        let new_use_tree = make.use_tree(prefix.clone(), Some(use_tree_list), None, false);
+
+        editor.replace(self.syntax(), new_use_tree.syntax());
+    }
+}
+
+impl ast::RecordExprField {
+    /// This will either replace the initializer, or in the case that this is a shorthand convert
+    /// the initializer into the name ref and insert the expr as the new initializer.
+    pub fn replace_expr(&self, editor: &SyntaxEditor, expr: ast::Expr) {
+        if self.name_ref().is_some() {
+            if let Some(prev) = self.expr() {
+                editor.replace(prev.syntax(), expr.syntax());
+            }
+        } else if let Some(ast::Expr::PathExpr(path_expr)) = self.expr()
+            && let Some(path) = path_expr.path()
+            && let Some(name_ref) = path.as_single_name_ref()
+        {
+            // shorthand `{ x }` → expand to `{ x: expr }`
+            let new_field = editor
+                .make()
+                .record_expr_field(editor.make().name_ref(&name_ref.text()), Some(expr));
+            editor.replace(self.syntax(), new_field.syntax());
+        }
+    }
+}
+
 #[test]
 fn test_increase_indent() {
     let arm_list = {
@@ -285,3 +388,29 @@
         }"
     );
 }
+
+#[test]
+fn split_prefix_inserts_self() {
+    check_split_prefix("use foo;", "foo::{self}");
+}
+
+#[test]
+fn split_prefix_preserves_rename() {
+    check_split_prefix("use foo as bar;", "foo::{self as bar}");
+}
+
+#[test]
+fn split_prefix_wraps_glob() {
+    check_split_prefix("use foo::*;", "foo::{*}");
+}
+
+#[cfg(test)]
+fn check_split_prefix(before: &str, expected: &str) {
+    let source = crate::SourceFile::parse(before, parser::Edition::CURRENT).tree();
+    let use_tree = source.syntax().descendants().find_map(ast::UseTree::cast).unwrap();
+    let (editor, use_tree) = SyntaxEditor::with_ast_node(&use_tree);
+    let prefix = use_tree.path().unwrap();
+    use_tree.split_prefix_with_editor(&editor, &prefix);
+    let edit = editor.finish();
+    assert_eq!(edit.new_root().to_string(), expected);
+}
diff --git a/crates/syntax/src/ast/edit_in_place.rs b/crates/syntax/src/ast/edit_in_place.rs
deleted file mode 100644
index 4a8c9d4..0000000
--- a/crates/syntax/src/ast/edit_in_place.rs
+++ /dev/null
@@ -1,318 +0,0 @@
-//! Structural editing for ast.
-
-use std::iter::{empty, once, successors};
-
-use parser::T;
-
-use crate::{
-    AstNode, AstToken, Direction,
-    algo::{self, neighbor},
-    ast::{self, make, syntax_factory::SyntaxFactory},
-    syntax_editor::SyntaxEditor,
-    ted,
-};
-
-use super::HasName;
-
-impl ast::GenericParamList {
-    /// Constructs a matching [`ast::GenericArgList`]
-    pub fn to_generic_args(&self, make: &SyntaxFactory) -> ast::GenericArgList {
-        let args = self.generic_params().filter_map(|param| match param {
-            ast::GenericParam::LifetimeParam(it) => {
-                Some(ast::GenericArg::LifetimeArg(make.lifetime_arg(it.lifetime()?)))
-            }
-            ast::GenericParam::TypeParam(it) => {
-                Some(ast::GenericArg::TypeArg(make.type_arg(make.ty_name(it.name()?))))
-            }
-            ast::GenericParam::ConstParam(it) => {
-                // Name-only const params get parsed as `TypeArg`s
-                Some(ast::GenericArg::TypeArg(make.type_arg(make.ty_name(it.name()?))))
-            }
-        });
-
-        make::generic_arg_list(args)
-    }
-}
-
-pub trait Removable: AstNode {
-    fn remove(&self);
-}
-
-impl Removable for ast::UseTree {
-    fn remove(&self) {
-        for dir in [Direction::Next, Direction::Prev] {
-            if let Some(next_use_tree) = neighbor(self, dir) {
-                let separators = self
-                    .syntax()
-                    .siblings_with_tokens(dir)
-                    .skip(1)
-                    .take_while(|it| it.as_node() != Some(next_use_tree.syntax()));
-                ted::remove_all_iter(separators);
-                break;
-            }
-        }
-        ted::remove(self.syntax());
-    }
-}
-
-impl ast::UseTree {
-    /// Editor variant of UseTree remove
-    fn remove_with_editor(&self, editor: &SyntaxEditor) {
-        for dir in [Direction::Next, Direction::Prev] {
-            if let Some(next_use_tree) = neighbor(self, dir) {
-                let separators = self
-                    .syntax()
-                    .siblings_with_tokens(dir)
-                    .skip(1)
-                    .take_while(|it| it.as_node() != Some(next_use_tree.syntax()));
-                for separator in separators {
-                    editor.delete(separator);
-                }
-                break;
-            }
-        }
-        editor.delete(self.syntax());
-    }
-
-    /// Deletes the usetree node represented by the input. Recursively removes parents, including use nodes that become empty.
-    pub fn remove_recursive(self, editor: &SyntaxEditor) {
-        let parent = self.syntax().parent();
-
-        if let Some(u) = parent.clone().and_then(ast::Use::cast) {
-            u.remove(editor);
-        } else if let Some(u) = parent.and_then(ast::UseTreeList::cast) {
-            if u.use_trees().nth(1).is_none()
-                || u.use_trees().all(|use_tree| {
-                    use_tree.syntax() == self.syntax() || editor.deleted(use_tree.syntax())
-                })
-            {
-                u.parent_use_tree().remove_recursive(editor);
-                return;
-            }
-            self.remove_with_editor(editor);
-            u.remove_unnecessary_braces(editor);
-        }
-    }
-
-    pub fn get_or_create_use_tree_list(&self) -> ast::UseTreeList {
-        match self.use_tree_list() {
-            Some(it) => it,
-            None => {
-                let position = ted::Position::last_child_of(self.syntax());
-                let use_tree_list = make::use_tree_list(empty()).clone_for_update();
-                let mut elements = Vec::with_capacity(2);
-                if self.coloncolon_token().is_none() {
-                    elements.push(make::token(T![::]).into());
-                }
-                elements.push(use_tree_list.syntax().clone().into());
-                ted::insert_all_raw(position, elements);
-                use_tree_list
-            }
-        }
-    }
-
-    /// Splits off the given prefix, making it the path component of the use tree,
-    /// appending the rest of the path to all UseTreeList items.
-    ///
-    /// # Examples
-    ///
-    /// `prefix$0::suffix` -> `prefix::{suffix}`
-    ///
-    /// `prefix$0` -> `prefix::{self}`
-    ///
-    /// `prefix$0::*` -> `prefix::{*}`
-    pub fn split_prefix(&self, prefix: &ast::Path) {
-        debug_assert_eq!(self.path(), Some(prefix.top_path()));
-        let path = self.path().unwrap();
-        if &path == prefix && self.use_tree_list().is_none() {
-            if self.star_token().is_some() {
-                // path$0::* -> *
-                if let Some(a) = self.coloncolon_token() {
-                    ted::remove(a)
-                }
-                ted::remove(prefix.syntax());
-            } else {
-                // path$0 -> self
-                let self_suffix =
-                    make::path_unqualified(make::path_segment_self()).clone_for_update();
-                ted::replace(path.syntax(), self_suffix.syntax());
-            }
-        } else if split_path_prefix(prefix).is_none() {
-            return;
-        }
-        // At this point, prefix path is detached; _self_ use tree has suffix path.
-        // Next, transform 'suffix' use tree into 'prefix::{suffix}'
-        let subtree = self.clone_subtree().clone_for_update();
-        ted::remove_all_iter(self.syntax().children_with_tokens());
-        ted::insert(ted::Position::first_child_of(self.syntax()), prefix.syntax());
-        self.get_or_create_use_tree_list().add_use_tree(subtree);
-
-        fn split_path_prefix(prefix: &ast::Path) -> Option<()> {
-            let parent = prefix.parent_path()?;
-            let segment = parent.segment()?;
-            if algo::has_errors(segment.syntax()) {
-                return None;
-            }
-            for p in successors(parent.parent_path(), |it| it.parent_path()) {
-                p.segment()?;
-            }
-            if let Some(a) = prefix.parent_path().and_then(|p| p.coloncolon_token()) {
-                ted::remove(a)
-            }
-            ted::remove(prefix.syntax());
-            Some(())
-        }
-    }
-
-    /// Editor variant of `split_prefix`
-    pub fn split_prefix_with_editor(&self, editor: &SyntaxEditor, prefix: &ast::Path) {
-        debug_assert_eq!(self.path(), Some(prefix.top_path()));
-
-        let make = editor.make();
-        let path = self.path().unwrap();
-        let suffix = if path == *prefix && self.use_tree_list().is_none() {
-            if self.star_token().is_some() {
-                make.use_tree_glob()
-            } else {
-                let self_path = make.path_unqualified(make.path_segment_self());
-                make.use_tree(self_path, None, None, false)
-            }
-        } else {
-            let suffix_segments = path.segments().skip(prefix.segments().count());
-            let suffix_path = make.path_from_segments(suffix_segments, false);
-            make.use_tree(
-                suffix_path,
-                self.use_tree_list(),
-                self.rename(),
-                self.star_token().is_some(),
-            )
-        };
-        let use_tree_list = make.use_tree_list(once(suffix));
-        let new_use_tree = make.use_tree(prefix.clone(), Some(use_tree_list), None, false);
-
-        editor.replace(self.syntax(), new_use_tree.syntax());
-    }
-
-    /// Wraps the use tree in use tree list with no top level path (if it isn't already).
-    ///
-    /// # Examples
-    ///
-    /// `foo::bar` -> `{foo::bar}`
-    ///
-    /// `{foo::bar}` -> `{foo::bar}`
-    pub fn wrap_in_tree_list(&self) -> Option<()> {
-        if self.use_tree_list().is_some()
-            && self.path().is_none()
-            && self.star_token().is_none()
-            && self.rename().is_none()
-        {
-            return None;
-        }
-        let subtree = self.clone_subtree().clone_for_update();
-        ted::remove_all_iter(self.syntax().children_with_tokens());
-        ted::append_child(
-            self.syntax(),
-            make::use_tree_list(once(subtree)).clone_for_update().syntax(),
-        );
-        Some(())
-    }
-}
-
-impl ast::UseTreeList {
-    pub fn add_use_tree(&self, use_tree: ast::UseTree) {
-        let (position, elements) = match self.use_trees().last() {
-            Some(last_tree) => (
-                ted::Position::after(last_tree.syntax()),
-                vec![
-                    make::token(T![,]).into(),
-                    make::tokens::single_space().into(),
-                    use_tree.syntax.into(),
-                ],
-            ),
-            None => {
-                let position = match self.l_curly_token() {
-                    Some(l_curly) => ted::Position::after(l_curly),
-                    None => ted::Position::last_child_of(self.syntax()),
-                };
-                (position, vec![use_tree.syntax.into()])
-            }
-        };
-        ted::insert_all_raw(position, elements);
-    }
-}
-
-impl ast::Use {
-    fn remove(&self, editor: &SyntaxEditor) {
-        let make = editor.make();
-        let next_ws = self
-            .syntax()
-            .next_sibling_or_token()
-            .and_then(|it| it.into_token())
-            .and_then(ast::Whitespace::cast);
-        if let Some(next_ws) = next_ws {
-            let ws_text = next_ws.syntax().text();
-            if let Some(rest) = ws_text.strip_prefix('\n') {
-                let next_use_removed = next_ws
-                    .syntax()
-                    .next_sibling_or_token()
-                    .and_then(|it| it.into_node())
-                    .and_then(ast::Use::cast)
-                    .and_then(|use_| use_.use_tree())
-                    .is_some_and(|use_tree| editor.deleted(use_tree.syntax()));
-                if rest.is_empty() || next_use_removed {
-                    editor.delete(next_ws.syntax());
-                } else {
-                    editor.replace(next_ws.syntax(), make.whitespace(rest));
-                }
-            }
-        }
-        let prev_ws = self
-            .syntax()
-            .prev_sibling_or_token()
-            .and_then(|it| it.into_token())
-            .and_then(ast::Whitespace::cast);
-        if let Some(prev_ws) = prev_ws {
-            let ws_text = prev_ws.syntax().text();
-            let prev_newline = ws_text.rfind('\n').map(|x| x + 1).unwrap_or(0);
-            let rest = &ws_text[0..prev_newline];
-            if rest.is_empty() {
-                editor.delete(prev_ws.syntax());
-            } else {
-                editor.replace(prev_ws.syntax(), make.whitespace(rest));
-            }
-        }
-
-        editor.delete(self.syntax());
-    }
-}
-
-impl ast::Impl {
-    pub fn get_or_create_assoc_item_list(&self) -> ast::AssocItemList {
-        if self.assoc_item_list().is_none() {
-            let assoc_item_list = make::assoc_item_list(None).clone_for_update();
-            ted::append_child(self.syntax(), assoc_item_list.syntax());
-        }
-        self.assoc_item_list().unwrap()
-    }
-}
-
-impl ast::RecordExprField {
-    /// This will either replace the initializer, or in the case that this is a shorthand convert
-    /// the initializer into the name ref and insert the expr as the new initializer.
-    pub fn replace_expr(&self, editor: &SyntaxEditor, expr: ast::Expr) {
-        if self.name_ref().is_some() {
-            if let Some(prev) = self.expr() {
-                editor.replace(prev.syntax(), expr.syntax());
-            }
-        } else if let Some(ast::Expr::PathExpr(path_expr)) = self.expr()
-            && let Some(path) = path_expr.path()
-            && let Some(name_ref) = path.as_single_name_ref()
-        {
-            // shorthand `{ x }` → expand to `{ x: expr }`
-            let new_field = editor
-                .make()
-                .record_expr_field(editor.make().name_ref(&name_ref.text()), Some(expr));
-            editor.replace(self.syntax(), new_field.syntax());
-        }
-    }
-}
diff --git a/crates/syntax/src/ast/prec.rs b/crates/syntax/src/ast/prec.rs
index 8411275..2a50d23 100644
--- a/crates/syntax/src/ast/prec.rs
+++ b/crates/syntax/src/ast/prec.rs
@@ -264,6 +264,14 @@
             return false;
         }
 
+        // Special-case `cond && <let-chain>`
+        if let ast::Expr::BinExpr(parent) = parent
+            && parent.op_kind() == Some(ast::BinaryOp::LogicOp(ast::LogicOp::And))
+            && self.contains_let_expr()
+        {
+            return false;
+        }
+
         let (left, right, inv) = match self.is_ordered_before_parent_in_place_of(parent, place_of) {
             true => (self, parent, false),
             false => (parent, self, true),
@@ -551,4 +559,19 @@
             ForExpr(_) | IfExpr(_) | MatchExpr(_) | WhileExpr(_) | IncludeBytesExpr(_) => true,
         }
     }
+
+    fn contains_let_expr(&self) -> bool {
+        use Expr::*;
+
+        match self {
+            LetExpr(_) => true,
+            BinExpr(e) => {
+                // if we find something other than a `&&`, then this can't be a let chain
+                e.op_kind() == Some(ast::BinaryOp::LogicOp(ast::LogicOp::And))
+                    && (e.lhs().is_none_or(|it| it.contains_let_expr())
+                        || e.rhs().is_none_or(|it| it.contains_let_expr()))
+            }
+            _ => false,
+        }
+    }
 }
diff --git a/crates/syntax/src/ast/syntax_factory/constructors.rs b/crates/syntax/src/ast/syntax_factory/constructors.rs
index 2f7eab2..7f9cd1f 100644
--- a/crates/syntax/src/ast/syntax_factory/constructors.rs
+++ b/crates/syntax/src/ast/syntax_factory/constructors.rs
@@ -4,8 +4,8 @@
 use crate::{
     AstNode, Edition, NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken,
     ast::{
-        self, HasArgList, HasAttrs, HasGenericArgs, HasGenericParams, HasLoopBody, HasName,
-        HasTypeBounds, HasVisibility, Lifetime, Param, RangeItem, make,
+        self, HasArgList, HasAttrs, HasGenericArgs, HasGenericParams, HasLoopBody, HasModuleItem,
+        HasName, HasTypeBounds, HasVisibility, Lifetime, Param, RangeItem, make,
     },
     syntax_editor::SyntaxMappingBuilder,
 };
@@ -2015,6 +2015,35 @@
         make::assoc_item_list(None).clone_for_update()
     }
 
+    pub fn item_list(&self, items: impl IntoIterator<Item = ast::Item>) -> ast::ItemList {
+        let (items, input) = iterator_input(items);
+        let items_vec: Vec<_> = items.into_iter().collect();
+        let ast = make::item_list(Some(items_vec)).clone_for_update();
+
+        if let Some(mut mapping) = self.mappings() {
+            let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone());
+            builder.map_children(input, ast.items().map(|item: ast::Item| item.syntax().clone()));
+            builder.finish(&mut mapping);
+        }
+
+        ast
+    }
+
+    pub fn mod_(&self, name: ast::Name, body: Option<ast::ItemList>) -> ast::Module {
+        let ast = make::mod_(name.clone(), body.clone()).clone_for_update();
+
+        if let Some(mut mapping) = self.mappings() {
+            let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone());
+            builder.map_node(name.syntax().clone(), ast.name().unwrap().syntax().clone());
+            if let Some(body) = body {
+                builder.map_node(body.syntax().clone(), ast.item_list().unwrap().syntax().clone());
+            }
+            builder.finish(&mut mapping);
+        }
+
+        ast
+    }
+
     pub fn attr_outer(&self, meta: ast::Meta) -> ast::Attr {
         let ast = make::attr_outer(meta.clone()).clone_for_update();
 
diff --git a/crates/syntax/src/lib.rs b/crates/syntax/src/lib.rs
index cda3e69..924e72e 100644
--- a/crates/syntax/src/lib.rs
+++ b/crates/syntax/src/lib.rs
@@ -39,7 +39,6 @@
 pub mod fuzz;
 pub mod hacks;
 pub mod syntax_editor;
-pub mod ted;
 pub mod utils;
 
 use std::{marker::PhantomData, ops::Range};
diff --git a/crates/syntax/src/syntax_editor/edits.rs b/crates/syntax/src/syntax_editor/edits.rs
index f2b979e..9fab871 100644
--- a/crates/syntax/src/syntax_editor/edits.rs
+++ b/crates/syntax/src/syntax_editor/edits.rs
@@ -459,13 +459,35 @@
         if let Some(next_ws) = next_ws {
             let ws_text = next_ws.syntax().text();
             if let Some(rest) = ws_text.strip_prefix('\n') {
-                if rest.is_empty() {
+                let next_use_removed = next_ws
+                    .syntax()
+                    .next_sibling_or_token()
+                    .and_then(|it| it.into_node())
+                    .and_then(ast::Use::cast)
+                    .and_then(|use_| use_.use_tree())
+                    .is_some_and(|use_tree| editor.deleted(use_tree.syntax()));
+                if rest.is_empty() || next_use_removed {
                     editor.delete(next_ws.syntax());
                 } else {
                     editor.replace(next_ws.syntax(), make.whitespace(rest));
                 }
             }
         }
+        let prev_ws = self
+            .syntax()
+            .prev_sibling_or_token()
+            .and_then(|it| it.into_token())
+            .and_then(ast::Whitespace::cast);
+        if let Some(prev_ws) = prev_ws {
+            let ws_text = prev_ws.syntax().text();
+            let prev_newline = ws_text.rfind('\n').map(|x| x + 1).unwrap_or(0);
+            let rest = &ws_text[0..prev_newline];
+            if rest.is_empty() {
+                editor.delete(prev_ws.syntax());
+            } else {
+                editor.replace(prev_ws.syntax(), make.whitespace(rest));
+            }
+        }
 
         editor.delete(self.syntax());
     }
diff --git a/crates/syntax/src/ted.rs b/crates/syntax/src/ted.rs
deleted file mode 100644
index 5c28647..0000000
--- a/crates/syntax/src/ted.rs
+++ /dev/null
@@ -1,227 +0,0 @@
-//! Primitive tree editor, ed for trees.
-//!
-//! The `_raw`-suffixed functions insert elements as is, unsuffixed versions fix
-//! up elements around the edges.
-use std::{mem, ops::RangeInclusive};
-
-use parser::T;
-use rowan::TextSize;
-
-use crate::{
-    SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken,
-    ast::{self, AstNode, edit::IndentLevel, make},
-};
-
-/// Utility trait to allow calling `ted` functions with references or owned
-/// nodes. Do not use outside of this module.
-pub trait Element {
-    fn syntax_element(self) -> SyntaxElement;
-}
-
-impl<E: Element + Clone> Element for &'_ E {
-    fn syntax_element(self) -> SyntaxElement {
-        self.clone().syntax_element()
-    }
-}
-impl Element for SyntaxElement {
-    fn syntax_element(self) -> SyntaxElement {
-        self
-    }
-}
-impl Element for SyntaxNode {
-    fn syntax_element(self) -> SyntaxElement {
-        self.into()
-    }
-}
-impl Element for SyntaxToken {
-    fn syntax_element(self) -> SyntaxElement {
-        self.into()
-    }
-}
-
-#[derive(Debug)]
-pub struct Position {
-    repr: PositionRepr,
-}
-
-#[derive(Debug)]
-enum PositionRepr {
-    FirstChild(SyntaxNode),
-    After(SyntaxElement),
-}
-
-impl Position {
-    pub fn after(elem: impl Element) -> Position {
-        let repr = PositionRepr::After(elem.syntax_element());
-        Position { repr }
-    }
-    pub fn before(elem: impl Element) -> Position {
-        let elem = elem.syntax_element();
-        let repr = match elem.prev_sibling_or_token() {
-            Some(it) => PositionRepr::After(it),
-            None => PositionRepr::FirstChild(elem.parent().unwrap()),
-        };
-        Position { repr }
-    }
-    pub fn first_child_of(node: &(impl Into<SyntaxNode> + Clone)) -> Position {
-        let repr = PositionRepr::FirstChild(node.clone().into());
-        Position { repr }
-    }
-    pub fn last_child_of(node: &(impl Into<SyntaxNode> + Clone)) -> Position {
-        let node = node.clone().into();
-        let repr = match node.last_child_or_token() {
-            Some(it) => PositionRepr::After(it),
-            None => PositionRepr::FirstChild(node),
-        };
-        Position { repr }
-    }
-    pub fn offset(&self) -> TextSize {
-        match &self.repr {
-            PositionRepr::FirstChild(node) => node.text_range().start(),
-            PositionRepr::After(elem) => elem.text_range().end(),
-        }
-    }
-}
-
-pub fn insert(position: Position, elem: impl Element) {
-    insert_all(position, vec![elem.syntax_element()]);
-}
-pub fn insert_raw(position: Position, elem: impl Element) {
-    insert_all_raw(position, vec![elem.syntax_element()]);
-}
-pub fn insert_all(position: Position, mut elements: Vec<SyntaxElement>) {
-    if let Some(first) = elements.first()
-        && let Some(ws) = ws_before(&position, first)
-    {
-        elements.insert(0, ws.into());
-    }
-    if let Some(last) = elements.last()
-        && let Some(ws) = ws_after(&position, last)
-    {
-        elements.push(ws.into());
-    }
-    insert_all_raw(position, elements);
-}
-pub fn insert_all_raw(position: Position, elements: Vec<SyntaxElement>) {
-    let (parent, index) = match position.repr {
-        PositionRepr::FirstChild(parent) => (parent, 0),
-        PositionRepr::After(child) => (child.parent().unwrap(), child.index() + 1),
-    };
-    parent.splice_children(index..index, elements);
-}
-
-pub fn remove(elem: impl Element) {
-    elem.syntax_element().detach();
-}
-pub fn remove_all(range: RangeInclusive<SyntaxElement>) {
-    replace_all(range, Vec::new());
-}
-pub fn remove_all_iter(range: impl IntoIterator<Item = SyntaxElement>) {
-    let mut it = range.into_iter();
-    if let Some(mut first) = it.next() {
-        match it.last() {
-            Some(mut last) => {
-                if first.index() > last.index() {
-                    mem::swap(&mut first, &mut last);
-                }
-                remove_all(first..=last);
-            }
-            None => remove(first),
-        }
-    }
-}
-
-pub fn replace(old: impl Element, new: impl Element) {
-    replace_with_many(old, vec![new.syntax_element()]);
-}
-pub fn replace_with_many(old: impl Element, new: Vec<SyntaxElement>) {
-    let old = old.syntax_element();
-    replace_all(old.clone()..=old, new);
-}
-pub fn replace_all(range: RangeInclusive<SyntaxElement>, new: Vec<SyntaxElement>) {
-    let start = range.start().index();
-    let end = range.end().index();
-    let parent = range.start().parent().unwrap();
-    parent.splice_children(start..end + 1, new);
-}
-
-pub fn append_child(node: &(impl Into<SyntaxNode> + Clone), child: impl Element) {
-    let position = Position::last_child_of(node);
-    insert(position, child);
-}
-pub fn append_child_raw(node: &(impl Into<SyntaxNode> + Clone), child: impl Element) {
-    let position = Position::last_child_of(node);
-    insert_raw(position, child);
-}
-
-pub fn prepend_child(node: &(impl Into<SyntaxNode> + Clone), child: impl Element) {
-    let position = Position::first_child_of(node);
-    insert(position, child);
-}
-
-fn ws_before(position: &Position, new: &SyntaxElement) -> Option<SyntaxToken> {
-    let prev = match &position.repr {
-        PositionRepr::FirstChild(_) => return None,
-        PositionRepr::After(it) => it,
-    };
-
-    if prev.kind() == T!['{']
-        && new.kind() == SyntaxKind::USE
-        && let Some(item_list) = prev.parent().and_then(ast::ItemList::cast)
-    {
-        let mut indent = IndentLevel::from_element(&item_list.syntax().clone().into());
-        indent.0 += 1;
-        return Some(make::tokens::whitespace(&format!("\n{indent}")));
-    }
-
-    if prev.kind() == T!['{']
-        && ast::Stmt::can_cast(new.kind())
-        && let Some(stmt_list) = prev.parent().and_then(ast::StmtList::cast)
-    {
-        let mut indent = IndentLevel::from_element(&stmt_list.syntax().clone().into());
-        indent.0 += 1;
-        return Some(make::tokens::whitespace(&format!("\n{indent}")));
-    }
-
-    ws_between(prev, new)
-}
-fn ws_after(position: &Position, new: &SyntaxElement) -> Option<SyntaxToken> {
-    let next = match &position.repr {
-        PositionRepr::FirstChild(parent) => parent.first_child_or_token()?,
-        PositionRepr::After(sibling) => sibling.next_sibling_or_token()?,
-    };
-    ws_between(new, &next)
-}
-fn ws_between(left: &SyntaxElement, right: &SyntaxElement) -> Option<SyntaxToken> {
-    if left.kind() == SyntaxKind::WHITESPACE || right.kind() == SyntaxKind::WHITESPACE {
-        return None;
-    }
-    if right.kind() == T![;] || right.kind() == T![,] {
-        return None;
-    }
-    if left.kind() == T![<] || right.kind() == T![>] {
-        return None;
-    }
-    if left.kind() == T![&] && right.kind() == SyntaxKind::LIFETIME {
-        return None;
-    }
-    if right.kind() == SyntaxKind::GENERIC_ARG_LIST {
-        return None;
-    }
-
-    if right.kind() == SyntaxKind::USE {
-        let mut indent = IndentLevel::from_element(left);
-        if left.kind() == SyntaxKind::USE {
-            indent.0 = IndentLevel::from_element(right).0.max(indent.0);
-        }
-        return Some(make::tokens::whitespace(&format!("\n{indent}")));
-    }
-    if left.kind() == SyntaxKind::ATTR {
-        let mut indent = IndentLevel::from_element(right);
-        if right.kind() == SyntaxKind::ATTR {
-            indent.0 = IndentLevel::from_element(left).0.max(indent.0);
-        }
-        return Some(make::tokens::whitespace(&format!("\n{indent}")));
-    }
-    Some(make::tokens::single_space())
-}
diff --git a/crates/toolchain/Cargo.toml b/crates/toolchain/Cargo.toml
index f561c1c..d0d5840 100644
--- a/crates/toolchain/Cargo.toml
+++ b/crates/toolchain/Cargo.toml
@@ -13,7 +13,6 @@
 doctest = false
 
 [dependencies]
-home = "0.5.11"
 camino.workspace = true
 
 [lints]
diff --git a/crates/toolchain/src/lib.rs b/crates/toolchain/src/lib.rs
index 3931988..ea08f7a 100644
--- a/crates/toolchain/src/lib.rs
+++ b/crates/toolchain/src/lib.rs
@@ -119,7 +119,7 @@
         return Utf8PathBuf::try_from(PathBuf::from(path)).ok();
     }
 
-    if let Some(mut path) = home::home_dir() {
+    if let Some(mut path) = env::home_dir() {
         path.push(".cargo");
         return Utf8PathBuf::try_from(path).ok();
     }
diff --git a/docs/book/src/configuration_generated.md b/docs/book/src/configuration_generated.md
index 7bbb9e0..76b626b 100644
--- a/docs/book/src/configuration_generated.md
+++ b/docs/book/src/configuration_generated.md
@@ -446,9 +446,12 @@
 For traits the type "methods" can be used to only exclude the methods but not the trait
 itself.
 
-For modules the type "subItems" can be used to only exclude the all items in it but not the module
+For modules the type "sub_items" can be used to only exclude the all items in it but not the module
 itself. This does not include items defined in nested modules.
 
+For enums the type "variants" can be used to only exclude the all variants in it but not the enum
+itself.
+
 This setting also inherits `#rust-analyzer.completion.excludeTraits#`.
 
 
diff --git a/docs/book/src/contributing/README.md b/docs/book/src/contributing/README.md
index bb2b608..d3e2acd 100644
--- a/docs/book/src/contributing/README.md
+++ b/docs/book/src/contributing/README.md
@@ -206,13 +206,6 @@
 cargo run --release -p rust-analyzer -- analysis-stats ../chalk/
 ```
 
-For measuring time of incremental analysis, use either of these:
-
-```bash
-cargo run --release -p rust-analyzer -- analysis-bench ../chalk/ --highlight ../chalk/chalk-engine/src/logic.rs
-cargo run --release -p rust-analyzer -- analysis-bench ../chalk/ --complete ../chalk/chalk-engine/src/logic.rs:94:0
-```
-
 Look for `fn benchmark_xxx` tests for a quick way to reproduce performance problems.
 
 ## Release Process
diff --git a/editors/code/package.json b/editors/code/package.json
index 53c5a29..13d3228 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -215,6 +215,11 @@
                 "category": "rust-analyzer"
             },
             {
+                "command": "rust-analyzer.newProject",
+                "title": "Create New Project...",
+                "category": "rust-analyzer"
+            },
+            {
                 "command": "rust-analyzer.rebuildProcMacros",
                 "title": "Rebuild proc macros and build scripts",
                 "category": "rust-analyzer"
@@ -491,6 +496,23 @@
                         "markdownDescription": "Do not start rust-analyzer server when the extension is activated.",
                         "default": false,
                         "type": "boolean"
+                    },
+                    "rust-analyzer.projectCreation.openAfterCreate": {
+                        "markdownDescription": "Control what happens after `rust-analyzer: Create New Project...` finishes creating a Cargo project.",
+                        "default": "ask",
+                        "enum": [
+                            "ask",
+                            "open",
+                            "openNewWindow",
+                            "addToWorkspace"
+                        ],
+                        "enumDescriptions": [
+                            "Prompt for how to open the new project.",
+                            "Open the new project in the current window.",
+                            "Open the new project in a new window.",
+                            "Add the new project to the current workspace, or open it if no workspace is open."
+                        ],
+                        "type": "string"
                     }
                 }
             },
@@ -1333,7 +1355,7 @@
                 "title": "Completion",
                 "properties": {
                     "rust-analyzer.completion.autoimport.exclude": {
-                        "markdownDescription": "A list of full paths to items to exclude from auto-importing completions.\n\nTraits in this list won't have their methods suggested in completions unless the trait\nis in scope.\n\nYou can either specify a string path which defaults to type \"always\" or use the more\nverbose form `{ \"path\": \"path::to::item\", type: \"always\" }`.\n\nFor traits the type \"methods\" can be used to only exclude the methods but not the trait\nitself.\n\nFor modules the type \"subItems\" can be used to only exclude the all items in it but not the module\nitself. This does not include items defined in nested modules.\n\nThis setting also inherits `#rust-analyzer.completion.excludeTraits#`.",
+                        "markdownDescription": "A list of full paths to items to exclude from auto-importing completions.\n\nTraits in this list won't have their methods suggested in completions unless the trait\nis in scope.\n\nYou can either specify a string path which defaults to type \"always\" or use the more\nverbose form `{ \"path\": \"path::to::item\", type: \"always\" }`.\n\nFor traits the type \"methods\" can be used to only exclude the methods but not the trait\nitself.\n\nFor modules the type \"sub_items\" can be used to only exclude the all items in it but not the module\nitself. This does not include items defined in nested modules.\n\nFor enums the type \"variants\" can be used to only exclude the all variants in it but not the enum\nitself.\n\nThis setting also inherits `#rust-analyzer.completion.excludeTraits#`.",
                         "default": [
                             {
                                 "path": "core::borrow::Borrow",
@@ -1361,7 +1383,8 @@
                                             "enum": [
                                                 "always",
                                                 "methods",
-                                                "subItems"
+                                                "sub_items",
+                                                "variants"
                                             ],
                                             "enumDescriptions": [
                                                 "Do not show this item or its methods (if it is a trait) in auto-import completions.",
@@ -3812,6 +3835,9 @@
                     "when": "inRustProject"
                 },
                 {
+                    "command": "rust-analyzer.newProject"
+                },
+                {
                     "command": "rust-analyzer.reloadWorkspace",
                     "when": "inRustProject"
                 },
@@ -3930,6 +3956,13 @@
                 }
             ]
         },
+        "viewsWelcome": [
+            {
+                "view": "explorer",
+                "contents": "Create a new Rust project.\n[Create Rust Project](command:rust-analyzer.newProject)",
+                "when": "workspaceFolderCount == 0"
+            }
+        ],
         "viewsContainers": {
             "activitybar": [
                 {
@@ -3956,6 +3989,14 @@
                 "description": "A brief introduction to get started with rust-analyzer. Learn about key features and resources to help you get the most out of the extension.",
                 "steps": [
                     {
+                        "id": "create-project",
+                        "title": "Create a Rust project",
+                        "description": "Start a new Cargo binary or library project from VS Code.\n\n[Create a Rust Project](command:rust-analyzer.newProject)",
+                        "media": {
+                            "markdown": "./walkthrough-create-project.md"
+                        }
+                    },
+                    {
                         "id": "setup",
                         "title": "Useful Setup Tips",
                         "description": "There are a couple of things you might want to configure upfront to your tastes. We'll name a few here but be sure to check out the docs linked below!\n\n**Marking library sources as readonly**\n\nAdding the snippet on the right to your settings.json will mark all Rust library sources as readonly.\n\n**Check on Save**\n\nBy default, rust-analyzer will run ``cargo check`` on your codebase when you save a file, rendering diagnostics emitted by ``cargo check`` within your code. This can potentially collide with other ``cargo`` commands running concurrently, blocking them from running for a certain amount of time. In these cases it is recommended to disable the ``rust-analyzer.checkOnSave`` configuration and running the ``rust-analyzer: Run flycheck`` command on-demand instead.",
diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts
index 9c8b707..5514319 100644
--- a/editors/code/src/commands.ts
+++ b/editors/code/src/commands.ts
@@ -33,6 +33,7 @@
 import type { SyntaxElement } from "./syntax_tree_provider";
 
 export * from "./run";
+export { newProject } from "./new_project";
 
 export function analyzerStatus(ctx: CtxInit): Cmd {
     const tdcp = new (class implements vscode.TextDocumentContentProvider {
diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts
index d65f011..0f783c7 100644
--- a/editors/code/src/config.ts
+++ b/editors/code/src/config.ts
@@ -447,6 +447,10 @@
         return this.get<boolean>("initializeStopped");
     }
 
+    get projectCreationOpenAfterCreate() {
+        return this.get<string>("projectCreation.openAfterCreate");
+    }
+
     get askBeforeUpdateTest() {
         return this.get<boolean>("runnables.askBeforeUpdateTest");
     }
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts
index 6567bcd..7136314 100644
--- a/editors/code/src/main.ts
+++ b/editors/code/src/main.ts
@@ -161,6 +161,12 @@
         memoryUsage: { enabled: commands.memoryUsage },
         reloadWorkspace: { enabled: commands.reloadWorkspace },
         rebuildProcMacros: { enabled: commands.rebuildProcMacros },
+        newProject: {
+            // Project creation is a pure VS Code-side workflow and should stay available even in
+            // empty windows before rust-analyzer has started or a Rust workspace exists.
+            enabled: commands.newProject,
+            disabled: commands.newProject,
+        },
         matchingBrace: { enabled: commands.matchingBrace },
         joinLines: { enabled: commands.joinLines },
         parentModule: { enabled: commands.parentModule },
diff --git a/editors/code/src/new_project.ts b/editors/code/src/new_project.ts
new file mode 100644
index 0000000..f5f6d86
--- /dev/null
+++ b/editors/code/src/new_project.ts
@@ -0,0 +1,311 @@
+import * as vscode from "vscode";
+
+import type { Ctx, Cmd } from "./ctx";
+import * as ra from "./lsp_ext";
+import { cargoPath } from "./toolchain";
+import { log, spawnAsync } from "./util";
+
+type NewProjectKind = "bin" | "lib";
+type NewProjectOpenAction = "open" | "openNewWindow" | "addToWorkspace";
+
+type NewProjectTemplate = {
+    detail: string;
+    id: NewProjectKind;
+    label: string;
+};
+
+type NewProjectCargo = {
+    cargo: string;
+    cargoEnv: NodeJS.ProcessEnv;
+};
+
+const NEW_PROJECT_TEMPLATES: readonly NewProjectTemplate[] = [
+    {
+        id: "bin",
+        label: "Binary Application",
+        detail: "Create a Cargo binary package (`cargo new --bin`)",
+    },
+    {
+        id: "lib",
+        label: "Library",
+        detail: "Create a Cargo library package (`cargo new --lib`)",
+    },
+] as const;
+
+export function newProject(ctx: Ctx): Cmd {
+    return async () => {
+        const cargo = await resolveNewProjectCargo(ctx);
+
+        const selectedKind = await promptForNewProjectTemplate();
+        if (!selectedKind) {
+            return;
+        }
+
+        const parentFolder = await promptForNewProjectParentFolder();
+        if (!parentFolder) {
+            return;
+        }
+
+        const projectName = await promptForNewProjectName(parentFolder);
+        if (!projectName) {
+            return;
+        }
+
+        if (!(await createNewProject(cargo, parentFolder, selectedKind, projectName))) {
+            return;
+        }
+
+        const projectUri = vscode.Uri.joinPath(parentFolder, projectName);
+        const defaultAction = determineNewProjectOpenAction(
+            ctx.config.projectCreationOpenAfterCreate,
+            Boolean(vscode.workspace.workspaceFolders?.length),
+        );
+        const action =
+            defaultAction === "ask"
+                ? await promptForNewProjectOpenAction(
+                      projectName,
+                      Boolean(vscode.workspace.workspaceFolders?.length),
+                  )
+                : defaultAction;
+
+        if (action) {
+            await executeNewProjectOpenAction(ctx, action, projectUri);
+        }
+    };
+}
+
+async function resolveNewProjectCargo(ctx: Ctx): Promise<NewProjectCargo> {
+    // Use the same effective environment rust-analyzer uses elsewhere so project creation sees
+    // toolchain wrappers, PATH overrides, and CARGO_HOME changes from configuration.
+    const cargoEnv = { ...process.env, ...ctx.config.serverExtraEnv };
+    return { cargo: await cargoPath(cargoEnv), cargoEnv };
+}
+
+async function promptForNewProjectTemplate(): Promise<NewProjectKind | undefined> {
+    const selected = await vscode.window.showQuickPick(NEW_PROJECT_TEMPLATES, {
+        placeHolder: "Select a Rust project kind",
+    });
+    return selected?.id;
+}
+
+async function promptForNewProjectParentFolder(): Promise<vscode.Uri | undefined> {
+    const selectedFolder = await vscode.window.showOpenDialog({
+        title: "Select the parent folder for the new Rust project",
+        openLabel: "Select parent folder",
+        canSelectFiles: false,
+        canSelectFolders: true,
+        canSelectMany: false,
+    });
+    if (!selectedFolder?.length) {
+        return undefined;
+    }
+    return selectedFolder[0];
+}
+
+const CARGO_MANIFEST_NAME_PATTERN = /^[\p{Alphabetic}\p{Number}_-]+$/u;
+
+// Keep local validation focused on stable checks that can be reported in the input box, then let
+// `cargo new` remain the source of truth for package-name-specific identifier and keyword rules.
+export function validateNewProjectName(
+    value: string,
+    existingNames: readonly string[],
+): string | undefined {
+    const trimmedValue = value.trim();
+    if (trimmedValue.length === 0) {
+        return "Project name cannot be empty.";
+    }
+    if (trimmedValue.includes("/") || trimmedValue.includes("\\")) {
+        return "Project name cannot contain '/' or '\\' characters.";
+    }
+    if (trimmedValue === "." || trimmedValue === "..") {
+        return "Project name cannot be '.' or '..'.";
+    }
+    if (!CARGO_MANIFEST_NAME_PATTERN.test(trimmedValue)) {
+        return "Project name can contain only alphanumeric characters, '-' or '_'.";
+    }
+    if (existingNames.includes(trimmedValue)) {
+        return "A file or folder with this name already exists.";
+    }
+    return undefined;
+}
+
+async function promptForNewProjectName(parentFolder: vscode.Uri): Promise<string | undefined> {
+    let existingNames: string[] = [];
+    try {
+        const entries = await vscode.workspace.fs.readDirectory(parentFolder);
+        existingNames = entries.map(([name]) => name);
+    } catch (error) {
+        log.error("Failed to read project parent folder", error);
+        void vscode.window.showErrorMessage("Failed to read the selected parent folder.");
+        return undefined;
+    }
+
+    const projectName = await vscode.window.showInputBox({
+        prompt: `Enter the new project name to create inside ${parentFolder.fsPath}`,
+        validateInput: async (value) => validateNewProjectName(value, existingNames),
+    });
+    return projectName?.trim();
+}
+
+export function cargoNewArgs(kind: NewProjectKind, name: string): string[] {
+    return ["new", kind === "bin" ? "--bin" : "--lib", name];
+}
+
+async function createNewProject(
+    cargo: NewProjectCargo,
+    parentFolder: vscode.Uri,
+    kind: NewProjectKind,
+    projectName: string,
+): Promise<boolean> {
+    const args = cargoNewArgs(kind, projectName);
+    const createResult = await vscode.window.withProgress(
+        {
+            location: vscode.ProgressLocation.Notification,
+            title: `Creating Rust project ${projectName}`,
+        },
+        async () =>
+            spawnAsync(cargo.cargo, args, {
+                cwd: parentFolder.fsPath,
+                env: cargo.cargoEnv,
+            }),
+    );
+
+    if (createResult.error || createResult.status !== 0) {
+        const details = formatProcessDetails(createResult);
+        await showNewProjectError("Failed to create Rust project.", details || undefined, {
+            cargo: cargo.cargo,
+            args,
+            cwd: parentFolder.fsPath,
+            error: createResult.error?.message,
+            status: createResult.status,
+            stderr: createResult.stderr || undefined,
+            stdout: createResult.stdout || undefined,
+        });
+        return false;
+    }
+
+    return true;
+}
+
+function formatProcessDetails(result: { error?: Error; stderr: string; stdout: string }): string {
+    return [result.stderr, result.stdout, result.error?.message]
+        .filter((value): value is string => Boolean(value && value.trim().length > 0))
+        .join("\n")
+        .trim();
+}
+
+async function showNewProjectError(
+    message: string,
+    details: string | undefined,
+    logContext: {
+        cargo: string;
+        args: string[];
+        cwd?: string;
+        error?: string;
+        status: number | null;
+        stderr?: string;
+        stdout?: string;
+    },
+): Promise<void> {
+    // Keep command-failure logging focused on the invocation and process output. Environment
+    // variables may contain secrets such as API keys, tokens, and credentials, so failure logs
+    // must not dump the merged env here.
+    const commandLine = [logContext.cargo, ...logContext.args].join(" ");
+    log.error(message);
+    log.error(`command: ${commandLine}`);
+    if (logContext.cwd) {
+        log.error(`cwd: ${logContext.cwd}`);
+    }
+    log.error(`exit status: ${String(logContext.status)}`);
+    if (logContext.error) {
+        log.error(`error: ${logContext.error}`);
+    }
+    if (logContext.stderr) {
+        log.error(`stderr:\n${logContext.stderr}`);
+    }
+    if (logContext.stdout) {
+        log.error(`stdout:\n${logContext.stdout}`);
+    }
+    const selection = await vscode.window.showErrorMessage(
+        details ? `${message}\n${details}` : message,
+        "Open Extension Logs",
+    );
+    if (selection === "Open Extension Logs") {
+        log.show();
+    }
+}
+
+export function determineNewProjectOpenAction(
+    configuredAction: string | undefined,
+    hasWorkspaceFolders: boolean,
+): "ask" | NewProjectOpenAction {
+    switch (configuredAction) {
+        case "open":
+        case "openNewWindow":
+            return configuredAction;
+        case "addToWorkspace":
+            // Adding to a workspace only makes sense when one is already open. Falling back to
+            // "open" keeps the setting usable in empty windows without adding another prompt path.
+            return hasWorkspaceFolders ? configuredAction : "open";
+        default:
+            return "ask";
+    }
+}
+
+async function promptForNewProjectOpenAction(
+    projectName: string,
+    hasWorkspaceFolders: boolean,
+): Promise<NewProjectOpenAction | undefined> {
+    let message = `Would you like to open ${projectName}?`;
+    const open = "Open";
+    const openNewWindow = "Open in New Window";
+    const choices = [open, openNewWindow];
+
+    const addToWorkspace = "Add to VS Code Workspace";
+    if (hasWorkspaceFolders) {
+        message = `Would you like to open ${projectName}, or add it to the current VS Code workspace?`;
+        choices.push(addToWorkspace);
+    }
+
+    const result = await vscode.window.showInformationMessage(
+        message,
+        { modal: true, detail: "The default action can be configured in settings." },
+        ...choices,
+    );
+
+    const actionMap: Record<string, NewProjectOpenAction> = {
+        [open]: "open",
+        [openNewWindow]: "openNewWindow",
+        [addToWorkspace]: "addToWorkspace",
+    };
+    return result ? actionMap[result] : undefined;
+}
+
+async function executeNewProjectOpenAction(
+    ctx: Ctx,
+    action: NewProjectOpenAction,
+    projectUri: vscode.Uri,
+): Promise<void> {
+    if (action === "open") {
+        await vscode.commands.executeCommand("vscode.openFolder", projectUri, {
+            forceReuseWindow: true,
+        });
+        return;
+    }
+
+    if (action === "openNewWindow") {
+        await vscode.commands.executeCommand("vscode.openFolder", projectUri, {
+            forceNewWindow: true,
+        });
+        return;
+    }
+
+    const index = vscode.workspace.workspaceFolders?.length ?? 0;
+    vscode.workspace.updateWorkspaceFolders(index, 0, { uri: projectUri });
+    // Reuse the existing workspace window when requested, but nudge rust-analyzer afterwards so
+    // the newly added Cargo project is discovered immediately instead of waiting for a later
+    // background refresh.
+    if (ctx.client?.isRunning()) {
+        await ctx.client.sendRequest(ra.reloadWorkspace);
+    }
+}
diff --git a/editors/code/src/toolchain.ts b/editors/code/src/toolchain.ts
index 76946d1..6ae365c 100644
--- a/editors/code/src/toolchain.ts
+++ b/editors/code/src/toolchain.ts
@@ -3,7 +3,7 @@
 import * as path from "path";
 import * as readline from "readline";
 import * as vscode from "vscode";
-import { Env, log, memoizeAsync, unwrapUndefinable } from "./util";
+import { Env, isWindows, log, memoizeAsync, unwrapUndefinable } from "./util";
 import type { CargoRunnableArgs } from "./lsp_ext";
 
 interface CompilationArtifact {
@@ -160,9 +160,46 @@
     if (env?.["RUSTC_TOOLCHAIN"]) {
         return Promise.resolve("cargo");
     }
+    if (env) {
+        return getPathForExecutableWithEnv("cargo", env);
+    }
     return getPathForExecutable("cargo");
 }
 
+/**
+ * Resolves an executable using an explicitly supplied environment instead of the VS Code host
+ * process environment.
+ *
+ * Some extension call sites already construct the exact env they will use for spawning, so path
+ * resolution needs to honor that same `PATH`/`CARGO_HOME` view to avoid launching a different
+ * toolchain than the one that was resolved.
+ */
+async function getPathForExecutableWithEnv(
+    executableName: "cargo" | "rustc" | "rustup",
+    env: Env,
+): Promise<string> {
+    const envVar = getEnvVar(env, executableName.toUpperCase());
+    if (envVar) {
+        return envVar;
+    }
+
+    if (await lookupInPath(executableName, getEnvVar(env, "PATH") ?? "")) {
+        return executableName;
+    }
+
+    const cargoHome = getCargoHomeFromPath(getEnvVar(env, "CARGO_HOME"));
+    if (cargoHome) {
+        for (const candidate of executableCandidates(executableName)) {
+            const standardPath = vscode.Uri.joinPath(cargoHome, "bin", candidate);
+            if (await isFileAtUri(standardPath)) {
+                return standardPath.fsPath;
+            }
+        }
+    }
+
+    return executableName;
+}
+
 /** Mirrors `toolchain::get_path_for_executable()` implementation */
 const getPathForExecutable = memoizeAsync(
     // We apply caching to decrease file-system interactions
@@ -172,23 +209,22 @@
             if (envVar) return envVar;
         }
 
-        if (await lookupInPath(executableName)) return executableName;
+        if (await lookupInPath(executableName, process.env["PATH"] ?? "")) return executableName;
 
         const cargoHome = getCargoHome();
         if (cargoHome) {
-            const standardPath = vscode.Uri.joinPath(cargoHome, "bin", executableName);
-            if (await isFileAtUri(standardPath)) return standardPath.fsPath;
+            for (const candidate of executableCandidates(executableName)) {
+                const standardPath = vscode.Uri.joinPath(cargoHome, "bin", candidate);
+                if (await isFileAtUri(standardPath)) return standardPath.fsPath;
+            }
         }
         return executableName;
     },
 );
 
-async function lookupInPath(exec: string): Promise<boolean> {
-    const paths = process.env["PATH"] ?? "";
-
+async function lookupInPath(exec: string, paths: string): Promise<boolean> {
     const candidates = paths.split(path.delimiter).flatMap((dirInPath) => {
-        const candidate = path.join(dirInPath, exec);
-        return os.type() === "Windows_NT" ? [candidate, `${candidate}.exe`] : [candidate];
+        return executableCandidates(exec).map((candidate) => path.join(dirInPath, candidate));
     });
 
     for await (const isFile of candidates.map(isFileAtPath)) {
@@ -199,8 +235,28 @@
     return false;
 }
 
+function executableCandidates(executableName: string): string[] {
+    // Keep the extension-side probe aligned with `crates/toolchain::probe_for_binary()`, which
+    // checks both the bare executable name and the platform suffix such as `.exe` on Windows.
+    // That matters for both PATH lookups and `$CARGO_HOME/bin/<tool>` fallbacks.
+    return isWindows ? [executableName, `${executableName}.exe`] : [executableName];
+}
+
+function getEnvVar(env: Env, name: string): string | undefined {
+    if (!isWindows) {
+        return env[name];
+    }
+
+    const foldedName = name.toLowerCase();
+    return Object.entries(env).find(([key]) => key.toLowerCase() === foldedName)?.[1];
+}
+
 function getCargoHome(): vscode.Uri | null {
     const envVar = process.env["CARGO_HOME"];
+    return getCargoHomeFromPath(envVar);
+}
+
+function getCargoHomeFromPath(envVar: string | undefined): vscode.Uri | null {
     if (envVar) return vscode.Uri.file(envVar);
 
     try {
diff --git a/editors/code/src/util.ts b/editors/code/src/util.ts
index 05b4750..ddeec81 100644
--- a/editors/code/src/util.ts
+++ b/editors/code/src/util.ts
@@ -22,6 +22,10 @@
         log: true,
     });
 
+    show(): void {
+        this.output.show(true);
+    }
+
     trace(...messages: [unknown, ...unknown[]]): void {
         this.output.trace(this.stringify(messages));
     }
@@ -40,7 +44,7 @@
 
     error(...messages: [unknown, ...unknown[]]): void {
         this.output.error(this.stringify(messages));
-        this.output.show(true);
+        this.show();
     }
 
     private stringify(messages: unknown[]): string {
diff --git a/editors/code/tests/unit/commands.test.ts b/editors/code/tests/unit/commands.test.ts
new file mode 100644
index 0000000..900bb27
--- /dev/null
+++ b/editors/code/tests/unit/commands.test.ts
@@ -0,0 +1,80 @@
+import * as assert from "node:assert/strict";
+
+import {
+    cargoNewArgs,
+    determineNewProjectOpenAction,
+    validateNewProjectName,
+} from "../../src/new_project";
+import type { Context } from ".";
+
+export async function getTests(ctx: Context) {
+    await ctx.suite("New project command", (suite) => {
+        suite.addTest("rejects empty project name", async () => {
+            assert.equal(validateNewProjectName("", []), "Project name cannot be empty.");
+            assert.equal(validateNewProjectName("   ", []), "Project name cannot be empty.");
+        });
+
+        suite.addTest("rejects dot project names", async () => {
+            assert.equal(validateNewProjectName(".", []), "Project name cannot be '.' or '..'.");
+            assert.equal(validateNewProjectName("..", []), "Project name cannot be '.' or '..'.");
+        });
+
+        suite.addTest("rejects path separators", async () => {
+            assert.equal(
+                validateNewProjectName("foo/bar", []),
+                "Project name cannot contain '/' or '\\' characters.",
+            );
+            assert.equal(
+                validateNewProjectName("foo\\bar", []),
+                "Project name cannot contain '/' or '\\' characters.",
+            );
+        });
+
+        suite.addTest("rejects invalid Cargo package name characters", async () => {
+            assert.equal(
+                validateNewProjectName("foo.bar", []),
+                "Project name can contain only alphanumeric characters, '-' or '_'.",
+            );
+            assert.equal(
+                validateNewProjectName("foo bar", []),
+                "Project name can contain only alphanumeric characters, '-' or '_'.",
+            );
+            assert.equal(
+                validateNewProjectName("foo+bar", []),
+                "Project name can contain only alphanumeric characters, '-' or '_'.",
+            );
+        });
+
+        suite.addTest("rejects existing child folder collisions", async () => {
+            assert.equal(
+                validateNewProjectName("demo", ["demo"]),
+                "A file or folder with this name already exists.",
+            );
+        });
+
+        suite.addTest("accepts a normal project name", async () => {
+            assert.equal(validateNewProjectName("demo-project", []), undefined);
+        });
+
+        suite.addTest("resolves addToWorkspace fallback without workspace", async () => {
+            assert.equal(determineNewProjectOpenAction("addToWorkspace", false), "open");
+        });
+
+        suite.addTest("keeps addToWorkspace when workspace exists", async () => {
+            assert.equal(determineNewProjectOpenAction("addToWorkspace", true), "addToWorkspace");
+        });
+
+        suite.addTest("defaults to ask for unknown values", async () => {
+            assert.equal(determineNewProjectOpenAction(undefined, true), "ask");
+            assert.equal(determineNewProjectOpenAction("ask", true), "ask");
+        });
+
+        suite.addTest("builds binary cargo args", async () => {
+            assert.deepEqual(cargoNewArgs("bin", "demo"), ["new", "--bin", "demo"]);
+        });
+
+        suite.addTest("builds library cargo args", async () => {
+            assert.deepEqual(cargoNewArgs("lib", "demo"), ["new", "--lib", "demo"]);
+        });
+    });
+}
diff --git a/editors/code/tests/unit/launch_config.test.ts b/editors/code/tests/unit/launch_config.test.ts
index eeb8608..8da62cd 100644
--- a/editors/code/tests/unit/launch_config.test.ts
+++ b/editors/code/tests/unit/launch_config.test.ts
@@ -1,5 +1,9 @@
 import * as assert from "assert";
-import { Cargo } from "../../src/toolchain";
+import * as os from "os";
+import * as path from "path";
+import { mkdtemp, mkdir, rm, writeFile } from "fs/promises";
+import { Cargo, cargoPath } from "../../src/toolchain";
+import { isWindows, normalizeDriveLetter } from "../../src/util";
 import type { Context } from ".";
 
 export async function getTests(ctx: Context) {
@@ -96,4 +100,47 @@
             assert.notDeepStrictEqual(args.filter, undefined);
         });
     });
+
+    await ctx.suite("Toolchain resolution", (suite) => {
+        suite.addTest("prefers explicit CARGO from provided env", async () => {
+            const explicitCargo = path.join(os.tmpdir(), "custom-cargo");
+            assert.strictEqual(await cargoPath({ CARGO: explicitCargo }), explicitCargo);
+        });
+
+        suite.addTest("resolves cargo from provided PATH", async () => {
+            const tempDir = await mkdtemp(path.join(os.tmpdir(), "ra-cargo-path-"));
+            try {
+                const cargoBinary = path.join(tempDir, isWindows ? "cargo.exe" : "cargo");
+                await writeFile(cargoBinary, "");
+
+                const pathKey = isWindows ? "Path" : "PATH";
+                assert.strictEqual(await cargoPath({ [pathKey]: tempDir }), "cargo");
+            } finally {
+                await rm(tempDir, { recursive: true, force: true });
+            }
+        });
+
+        suite.addTest("resolves cargo from provided CARGO_HOME", async () => {
+            const cargoHome = await mkdtemp(path.join(os.tmpdir(), "ra-cargo-home-"));
+            try {
+                const binDir = path.join(cargoHome, "bin");
+                await mkdir(binDir);
+                const cargoBinary = path.join(binDir, isWindows ? "cargo.exe" : "cargo");
+                await writeFile(cargoBinary, "");
+
+                assert.strictEqual(
+                    normalizeComparablePath(await cargoPath({ PATH: "", CARGO_HOME: cargoHome })),
+                    normalizeComparablePath(cargoBinary),
+                );
+            } finally {
+                await rm(cargoHome, { recursive: true, force: true });
+            }
+        });
+    });
+}
+
+function normalizeComparablePath(filePath: string): string {
+    // Windows path comparisons should ignore drive-letter casing because `fsPath`
+    // may normalize it differently while still pointing to the same file.
+    return normalizeDriveLetter(filePath, isWindows);
 }
diff --git a/editors/code/walkthrough-create-project.md b/editors/code/walkthrough-create-project.md
new file mode 100644
index 0000000..34c04cd
--- /dev/null
+++ b/editors/code/walkthrough-create-project.md
@@ -0,0 +1,9 @@
+# Create a New Cargo Project
+
+Create a new Cargo project without leaving VS Code.
+
+The command lets you choose the package kind, parent folder, project name, and
+how the new project should be opened afterward.
+
+The project is created with Cargo, so the generated files and defaults match
+the normal `cargo new` experience.
diff --git a/rust-version b/rust-version
index 387bd8e..5db47ca 100644
--- a/rust-version
+++ b/rust-version
@@ -1 +1 @@
-029c9e18dd1f4668e1d42bb187c1c263dfe20093
+942ac9ce4116d4ea784c9882659372b34978b1f8