Repository: rust-analyzer/rust-analyzer
Pull request #11718 (2022-03-15 19:05:48)

Fix const generic panic
fix https://github.com/rust-analyzer/rust-analyzer/pull/11688#issuecomment-1066824954


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index fab0edfe30..5d084bcf1c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2072,18 +2072,18 @@ checksum = "da260301476ad19a4733a0e930db8227a48ea04561e235a5102978145ec69fcc"
 
 [[package]]
 name = "xshell"
-version = "0.2.0"
+version = "0.1.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3332cab90be2998a2aacb6494db45344bd16dfcc43ff36c42255018c6bcc96be"
+checksum = "eaad2035244c56da05573d4d7fda5f903c60a5f35b9110e157a14a1df45a9f14"
 dependencies = [
  "xshell-macros",
 ]
 
 [[package]]
 name = "xshell-macros"
-version = "0.2.0"
+version = "0.1.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f47e54cffa76000b7641328ab3bb1e146f93a1690ab86c5909c656f49d91019c"
+checksum = "4916a4a3cad759e499a3620523bf9545cc162d7a06163727dde97ce9aaa4cf39"
 
 [[package]]
 name = "xtask"
diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs
index 7857edb521..236487d8e5 100644
--- a/crates/hir/src/semantics.rs
+++ b/crates/hir/src/semantics.rs
@@ -1327,14 +1327,14 @@ impl<'a> SemanticsScope<'a> {
                     resolver::ScopeDef::ImplSelfType(it) => ScopeDef::ImplSelfType(it.into()),
                     resolver::ScopeDef::AdtSelfType(it) => ScopeDef::AdtSelfType(it.into()),
                     resolver::ScopeDef::GenericParam(id) => ScopeDef::GenericParam(id.into()),
-                    resolver::ScopeDef::Local(pat_id) => match self.resolver.body_owner() {
-                        Some(parent) => ScopeDef::Local(Local { parent, pat_id }),
-                        None => continue,
-                    },
-                    resolver::ScopeDef::Label(label_id) => match self.resolver.body_owner() {
-                        Some(parent) => ScopeDef::Label(Label { parent, label_id }),
-                        None => continue,
-                    },
+                    resolver::ScopeDef::Local(pat_id) => {
+                        let parent = self.resolver.body_owner().unwrap();
+                        ScopeDef::Local(Local { parent, pat_id })
+                    }
+                    resolver::ScopeDef::Label(label_id) => {
+                        let parent = self.resolver.body_owner().unwrap();
+                        ScopeDef::Label(Label { parent, label_id })
+                    }
                 };
                 f(name.clone(), def)
             }
diff --git a/crates/hir_def/src/resolver.rs b/crates/hir_def/src/resolver.rs
index b8c88282d6..72856a1bfe 100644
--- a/crates/hir_def/src/resolver.rs
+++ b/crates/hir_def/src/resolver.rs
@@ -1,5 +1,5 @@
 //! Name resolution façade.
-use std::{hash::BuildHasherDefault, sync::Arc};
+use std::sync::Arc;
 
 use base_db::CrateId;
 use hir_expand::name::{name, Name};
@@ -343,7 +343,7 @@ impl Resolver {
     /// Returns a set of names available in the current scope.
     ///
     /// Note that this is a somewhat fuzzy concept -- internally, the compiler
-    /// doesn't necessary follow a strict scoping discipline. Rather, it just
+    /// doesn't necessary follow a strict scoping discipline. Rathe, it just
     /// tells for each ident what it resolves to.
     ///
     /// A good example is something like `str::from_utf8`. From scopes point of
@@ -378,13 +378,10 @@ impl Resolver {
     /// The result is ordered *roughly* from the innermost scope to the
     /// outermost: when the name is introduced in two namespaces in two scopes,
     /// we use the position of the first scope.
-    pub fn names_in_scope(
-        &self,
-        db: &dyn DefDatabase,
-    ) -> FxIndexMap<Name, SmallVec<[ScopeDef; 1]>> {
+    pub fn names_in_scope(&self, db: &dyn DefDatabase) -> IndexMap<Name, SmallVec<[ScopeDef; 1]>> {
         let mut res = ScopeNames::default();
         for scope in self.scopes() {
-            scope.process_names(&mut res, db);
+            scope.process_names(db, &mut res);
         }
         res.map
     }
@@ -469,7 +466,7 @@ impl Resolver {
     }
 }
 
-#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+#[derive(Debug, PartialEq, Eq)]
 pub enum ScopeDef {
     ModuleDef(ModuleDefId),
     Unknown,
@@ -481,7 +478,7 @@ pub enum ScopeDef {
 }
 
 impl Scope {
-    fn process_names(&self, acc: &mut ScopeNames, db: &dyn DefDatabase) {
+    fn process_names(&self, db: &dyn DefDatabase, acc: &mut ScopeNames) {
         match self {
             Scope::ModuleScope(m) => {
                 // FIXME: should we provide `self` here?
@@ -694,10 +691,9 @@ fn to_type_ns(per_ns: PerNs) -> Option<TypeNs> {
     Some(res)
 }
 
-type FxIndexMap<K, V> = IndexMap<K, V, BuildHasherDefault<rustc_hash::FxHasher>>;
 #[derive(Default)]
 struct ScopeNames {
-    map: FxIndexMap<Name, SmallVec<[ScopeDef; 1]>>,
+    map: IndexMap<Name, SmallVec<[ScopeDef; 1]>>,
 }
 
 impl ScopeNames {
diff --git a/crates/hir_ty/src/infer/expr.rs b/crates/hir_ty/src/infer/expr.rs
index e78a6377e5..ebbce33e01 100644
--- a/crates/hir_ty/src/infer/expr.rs
+++ b/crates/hir_ty/src/infer/expr.rs
@@ -1088,9 +1088,20 @@ impl<'a> InferenceContext<'a> {
                 }
             }
         };
-        let supplied_params = substs.len();
-        for _ in supplied_params..total_len {
-            substs.push(GenericArgData::Ty(self.table.new_type_var()).intern(Interner));
+        for (id, data) in def_generics.iter().skip(substs.len()) {
+            match data {
+                TypeOrConstParamData::TypeParamData(_) => {
+                    substs.push(GenericArgData::Ty(self.table.new_type_var()).intern(Interner))
+                }
+                TypeOrConstParamData::ConstParamData(_) => {
+                    substs.push(
+                        GenericArgData::Const(self.table.new_const_var(
+                            self.db.const_param_ty(ConstParamId::from_unchecked(id)),
+                        ))
+                        .intern(Interner),
+                    )
+                }
+            }
         }
         assert_eq!(substs.len(), total_len);
         Substitution::from_iter(Interner, substs)
diff --git a/crates/ide_completion/src/completions/lifetime.rs b/crates/ide_completion/src/completions/lifetime.rs
index 4fffd1aa0c..b8a50442bc 100644
--- a/crates/ide_completion/src/completions/lifetime.rs
+++ b/crates/ide_completion/src/completions/lifetime.rs
@@ -30,7 +30,7 @@ pub(crate) fn complete_lifetime(acc: &mut Completions, ctx: &CompletionContext)
     let param_lifetime = param_lifetime.as_ref().map(ast::Lifetime::text);
     let param_lifetime = param_lifetime.as_ref().map(TokenText::as_str);
 
-    ctx.process_all_names_raw(&mut |name, res| {
+    ctx.scope.process_all_names(&mut |name, res| {
         if matches!(
             res,
             ScopeDef::GenericParam(hir::GenericParam::LifetimeParam(_))
@@ -49,7 +49,7 @@ pub(crate) fn complete_label(acc: &mut Completions, ctx: &CompletionContext) {
     if !matches!(ctx.lifetime_ctx, Some(LifetimeContext::LabelRef)) {
         return;
     }
-    ctx.process_all_names_raw(&mut |name, res| {
+    ctx.scope.process_all_names(&mut |name, res| {
         if let ScopeDef::Label(_) = res {
             acc.add_label(ctx, name);
         }
diff --git a/crates/ide_completion/src/context.rs b/crates/ide_completion/src/context.rs
index 1ad233494a..a203ebd45a 100644
--- a/crates/ide_completion/src/context.rs
+++ b/crates/ide_completion/src/context.rs
@@ -12,7 +12,7 @@ use ide_db::{
     famous_defs::FamousDefs,
     RootDatabase,
 };
-use rustc_hash::{FxHashMap, FxHashSet};
+use rustc_hash::FxHashSet;
 use syntax::{
     algo::{find_node_at_offset, non_trivia_sibling},
     ast::{self, AttrKind, HasName, NameOrNameRef},
@@ -24,8 +24,8 @@ use text_edit::Indel;
 
 use crate::{
     patterns::{
-        determine_location, determine_prev_sibling, is_in_loop_body, is_in_token_of_for_loop,
-        previous_token, ImmediateLocation, ImmediatePrevSibling,
+        determine_location, determine_prev_sibling, for_is_prev2, is_in_loop_body, previous_token,
+        ImmediateLocation, ImmediatePrevSibling,
     },
     CompletionConfig,
 };
@@ -146,7 +146,7 @@ pub(crate) struct CompletionContext<'a> {
 
     pub(super) existing_derives: FxHashSet<hir::Macro>,
 
-    pub(super) locals: FxHashMap<Name, Local>,
+    pub(super) locals: Vec<(Name, Local)>,
 }
 
 impl<'a> CompletionContext<'a> {
@@ -293,10 +293,6 @@ impl<'a> CompletionContext<'a> {
         self.path_context.as_ref().and_then(|it| it.kind)
     }
 
-    pub(crate) fn is_immediately_after_macro_bang(&self) -> bool {
-        self.token.kind() == BANG && self.token.parent().map_or(false, |it| it.kind() == MACRO_CALL)
-    }
-
     /// Checks if an item is visible and not `doc(hidden)` at the completion site.
     pub(crate) fn is_visible<I>(&self, item: &I) -> Visible
     where
@@ -322,6 +318,11 @@ impl<'a> CompletionContext<'a> {
             _ => false,
         }
     }
+
+    pub(crate) fn is_immediately_after_macro_bang(&self) -> bool {
+        self.token.kind() == BANG && self.token.parent().map_or(false, |it| it.kind() == MACRO_CALL)
+    }
+
     /// Whether the given trait is an operator trait or not.
     pub(crate) fn is_ops_trait(&self, trait_: hir::Trait) -> bool {
         match trait_.attrs(self.db).lang() {
@@ -339,12 +340,7 @@ impl<'a> CompletionContext<'a> {
             }
 
             f(name, def);
-        });
-    }
-
-    pub(crate) fn process_all_names_raw(&self, f: &mut dyn FnMut(Name, ScopeDef)) {
-        let _p = profile::span("CompletionContext::process_all_names_raw");
-        self.scope.process_all_names(&mut |name, def| f(name, def));
+        })
     }
 
     fn is_visible_impl(
@@ -376,11 +372,16 @@ impl<'a> CompletionContext<'a> {
     }
 
     fn is_doc_hidden(&self, attrs: &hir::Attrs, defining_crate: hir::Crate) -> bool {
-        match self.krate {
+        let krate = match self.krate {
+            Some(it) => it,
+            None => return true,
+        };
+        if krate != defining_crate && attrs.has_doc_hidden() {
             // `doc(hidden)` items are only completed within the defining crate.
-            Some(krate) => krate != defining_crate && attrs.has_doc_hidden(),
-            None => true,
+            return true;
         }
+
+        false
     }
 }
 
@@ -412,10 +413,10 @@ impl<'a> CompletionContext<'a> {
         let scope = sema.scope_at_offset(&token.parent()?, offset);
         let krate = scope.krate();
         let module = scope.module();
-        let mut locals = FxHashMap::default();
+        let mut locals = vec![];
         scope.process_all_names(&mut |name, scope| {
             if let ScopeDef::Local(local) = scope {
-                locals.insert(name, local);
+                locals.push((name, local));
             }
         });
 
@@ -729,7 +730,7 @@ impl<'a> CompletionContext<'a> {
     ) {
         let fake_ident_token = file_with_fake_ident.token_at_offset(offset).right_biased().unwrap();
         let syntax_element = NodeOrToken::Token(fake_ident_token);
-        if is_in_token_of_for_loop(syntax_element.clone()) {
+        if for_is_prev2(syntax_element.clone()) {
             // for pat $0
             // there is nothing to complete here except `in` keyword
             // don't bother populating the context
diff --git a/crates/ide_completion/src/patterns.rs b/crates/ide_completion/src/patterns.rs
index 4536e3e6ee..8a53d6e4d4 100644
--- a/crates/ide_completion/src/patterns.rs
+++ b/crates/ide_completion/src/patterns.rs
@@ -11,7 +11,7 @@ use syntax::{
     ast::{self, HasArgList, HasLoopBody},
     match_ast, AstNode, Direction, SyntaxElement,
     SyntaxKind::*,
-    SyntaxNode, SyntaxToken, TextRange, TextSize,
+    SyntaxNode, SyntaxToken, TextRange, TextSize, T,
 };
 
 #[cfg(test)]
@@ -295,37 +295,19 @@ pub(crate) fn previous_token(element: SyntaxElement) -> Option<SyntaxToken> {
     element.into_token().and_then(previous_non_trivia_token)
 }
 
-pub(crate) fn is_in_token_of_for_loop(element: SyntaxElement) -> bool {
-    // oh my ...
-    (|| {
-        let syntax_token = element.into_token()?;
-        let range = syntax_token.text_range();
-        let for_expr = syntax_token.ancestors().find_map(ast::ForExpr::cast)?;
-
-        // check if the current token is the `in` token of a for loop
-        if let Some(token) = for_expr.in_token() {
-            return Some(syntax_token == token);
-        }
-        let pat = for_expr.pat()?;
-        if range.end() < pat.syntax().text_range().end() {
-            // if we are inside or before the pattern we can't be at the `in` token position
-            return None;
-        }
-        let next_sibl = next_non_trivia_sibling(pat.syntax().clone().into())?;
-        Some(match next_sibl {
-            // the loop body is some node, if our token is at the start we are at the `in` position,
-            // otherwise we could be in a recovered expression, we don't wanna ruin completions there
-            syntax::NodeOrToken::Node(n) => n.text_range().start() == range.start(),
-            // the loop body consists of a single token, if we are this we are certainly at the `in` token position
-            syntax::NodeOrToken::Token(t) => t == syntax_token,
-        })
-    })()
-    .unwrap_or(false)
+/// Check if the token previous to the previous one is `for`.
+/// For example, `for _ i$0` => true.
+pub(crate) fn for_is_prev2(element: SyntaxElement) -> bool {
+    element
+        .into_token()
+        .and_then(previous_non_trivia_token)
+        .and_then(previous_non_trivia_token)
+        .filter(|it| it.kind() == T![for])
+        .is_some()
 }
-
 #[test]
 fn test_for_is_prev2() {
-    check_pattern_is_applicable(r"fn __() { for i i$0 }", is_in_token_of_for_loop);
+    check_pattern_is_applicable(r"for i i$0", for_is_prev2);
 }
 
 pub(crate) fn is_in_loop_body(node: &SyntaxNode) -> bool {
@@ -347,7 +329,7 @@ pub(crate) fn is_in_loop_body(node: &SyntaxNode) -> bool {
 
 fn previous_non_trivia_token(token: SyntaxToken) -> Option<SyntaxToken> {
     let mut token = token.prev_token();
-    while let Some(inner) = token {
+    while let Some(inner) = token.clone() {
         if !inner.kind().is_trivia() {
             return Some(inner);
         } else {
@@ -357,18 +339,6 @@ fn previous_non_trivia_token(token: SyntaxToken) -> Option<SyntaxToken> {
     None
 }
 
-fn next_non_trivia_sibling(ele: SyntaxElement) -> Option<SyntaxElement> {
-    let mut e = ele.next_sibling_or_token();
-    while let Some(inner) = e {
-        if !inner.kind().is_trivia() {
-            return Some(inner);
-        } else {
-            e = inner.next_sibling_or_token();
-        }
-    }
-    None
-}
-
 #[cfg(test)]
 mod tests {
     use syntax::algo::find_node_at_offset;
diff --git a/crates/ide_db/Cargo.toml b/crates/ide_db/Cargo.toml
index c71a213c40..e00803708b 100644
--- a/crates/ide_db/Cargo.toml
+++ b/crates/ide_db/Cargo.toml
@@ -35,5 +35,5 @@ limit = { path = "../limit", version = "0.0.0" }
 [dev-dependencies]
 test_utils = { path = "../test_utils" }
 sourcegen = { path = "../sourcegen" }
-xshell = "0.2.0"
+xshell = "0.1"
 expect-test = "1.2.0-pre.1"
diff --git a/crates/rust-analyzer/Cargo.toml b/crates/rust-analyzer/Cargo.toml
index 68d728b41f..87b6cd7186 100644
--- a/crates/rust-analyzer/Cargo.toml
+++ b/crates/rust-analyzer/Cargo.toml
@@ -75,7 +75,7 @@ jemallocator = { version = "0.4.1", package = "tikv-jemallocator", optional = tr
 [dev-dependencies]
 expect-test = "1.2.0-pre.1"
 jod-thread = "0.1.0"
-xshell = "0.2.0"
+xshell = "0.1"
 
 test_utils = { path = "../test_utils" }
 sourcegen = { path = "../sourcegen" }
diff --git a/crates/sourcegen/Cargo.toml b/crates/sourcegen/Cargo.toml
index 10587d913d..9cfe181c2e 100644
--- a/crates/sourcegen/Cargo.toml
+++ b/crates/sourcegen/Cargo.toml
@@ -10,4 +10,4 @@ rust-version = "1.57"
 doctest = false
 
 [dependencies]
-xshell = "0.2.0"
+xshell = "0.1"
diff --git a/crates/sourcegen/src/lib.rs b/crates/sourcegen/src/lib.rs
index 719b35b630..398e846a14 100644
--- a/crates/sourcegen/src/lib.rs
+++ b/crates/sourcegen/src/lib.rs
@@ -11,7 +11,7 @@ use std::{
     path::{Path, PathBuf},
 };
 
-use xshell::{cmd, Shell};
+use xshell::{cmd, pushenv};
 
 pub fn list_rust_files(dir: &Path) -> Vec<PathBuf> {
     let mut res = list_files(dir);
@@ -133,8 +133,8 @@ impl fmt::Display for Location {
     }
 }
 
-fn ensure_rustfmt(sh: &Shell) {
-    let version = cmd!(sh, "rustfmt --version").read().unwrap_or_default();
+fn ensure_rustfmt() {
+    let version = cmd!("rustfmt --version").read().unwrap_or_default();
     if !version.contains("stable") {
         panic!(
             "Failed to run rustfmt from toolchain 'stable'. \
@@ -144,11 +144,10 @@ fn ensure_rustfmt(sh: &Shell) {
 }
 
 pub fn reformat(text: String) -> String {
-    let sh = Shell::new().unwrap();
-    sh.set_var("RUSTUP_TOOLCHAIN", "stable");
-    ensure_rustfmt(&sh);
+    let _e = pushenv("RUSTUP_TOOLCHAIN", "stable");
+    ensure_rustfmt();
     let rustfmt_toml = project_root().join("rustfmt.toml");
-    let mut stdout = cmd!(sh, "rustfmt --config-path {rustfmt_toml} --config fn_single_line=true")
+    let mut stdout = cmd!("rustfmt --config-path {rustfmt_toml} --config fn_single_line=true")
         .stdin(text)
         .read()
         .unwrap();
diff --git a/docs/user/manual.adoc b/docs/user/manual.adoc
index ba28f68029..e8386607a0 100644
--- a/docs/user/manual.adoc
+++ b/docs/user/manual.adoc
@@ -714,24 +714,22 @@ include::./generated_diagnostic.adoc[]
 
 ==== Color configurations
 
-It is possible to change the foreground/background color and font family/size of inlay hints.
+It is possible to change the foreground/background color of inlay hints.
 Just add this to your `settings.json`:
 
 [source,jsonc]
 ----
 {
-  "editor.inlayHints.fontFamily": "Courier New",
-  "editor.inlayHints.fontSize": 11,
-  
   "workbench.colorCustomizations": {
     // Name of the theme you are currently using
     "[Default Dark+]": {
-      "editorInlayHint.foreground": "#868686f0",
-      "editorInlayHint.background": "#3d3d3d48",
+      "rust_analyzer.inlayHints.foreground": "#868686f0",
+      "rust_analyzer.inlayHints.background": "#3d3d3d48",
 
       // Overrides for specific kinds of inlay hints
-      "editorInlayHint.typeForeground": "#fdb6fdf0",
-      "editorInlayHint.parameterForeground": "#fdb6fdf0",
+      "rust_analyzer.inlayHints.foreground.typeHints": "#fdb6fdf0",
+      "rust_analyzer.inlayHints.foreground.paramHints": "#fdb6fdf0",
+      "rust_analyzer.inlayHints.background.chainingHints": "#6b0c0c81"
     }
   }
 }
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts
index 06e5c1185d..4856079f68 100644
--- a/editors/code/src/main.ts
+++ b/editors/code/src/main.ts
@@ -269,8 +269,8 @@ function serverPath(config: Config): string | null {
 async function isNixOs(): Promise<boolean> {
     try {
         const contents = (await vscode.workspace.fs.readFile(vscode.Uri.file("/etc/os-release"))).toString();
-        const idString = contents.split('\n').find((a) => a.startsWith("ID=")) || "ID=linux";
-        return idString.indexOf("nixos") !== -1;
+        const idString = contents.split('\n').find((a) => a.startsWith("ID="));
+        return idString?.toLowerCase()?.indexOf("nixos") !== -1;
     } catch {
         return false;
     }
diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml
index d9adf88c02..0acbaf8176 100644
--- a/xtask/Cargo.toml
+++ b/xtask/Cargo.toml
@@ -10,6 +10,6 @@ rust-version = "1.57"
 anyhow = "1.0.26"
 flate2 = "1.0"
 write-json = "0.1.0"
-xshell = "0.2.0"
+xshell = "0.1"
 xflags = "0.2.1"
 # Avoid adding more dependencies to this crate
diff --git a/xtask/src/dist.rs b/xtask/src/dist.rs
index 3c58fbb76d..985829880c 100644
--- a/xtask/src/dist.rs
+++ b/xtask/src/dist.rs
@@ -5,23 +5,25 @@ use std::{
     path::{Path, PathBuf},
 };
 
+use anyhow::Result;
 use flate2::{write::GzEncoder, Compression};
-use xshell::{cmd, Shell};
+use xshell::{cmd, cp, mkdir_p, pushd, pushenv, read_file, rm_rf, write_file};
 
 use crate::{date_iso, flags, project_root};
 
 impl flags::Dist {
-    pub(crate) fn run(self, sh: &Shell) -> anyhow::Result<()> {
-        let stable = sh.var("GITHUB_REF").unwrap_or_default().as_str() == "refs/heads/release";
+    pub(crate) fn run(self) -> Result<()> {
+        let stable =
+            std::env::var("GITHUB_REF").unwrap_or_default().as_str() == "refs/heads/release";
 
         let project_root = project_root();
         let target = Target::get(&project_root);
         let dist = project_root.join("dist");
-        sh.remove_path(&dist)?;
-        sh.create_dir(&dist)?;
+        rm_rf(&dist)?;
+        mkdir_p(&dist)?;
 
         let release_channel = if stable { "stable" } else { "nightly" };
-        dist_server(sh, release_channel, &target)?;
+        dist_server(release_channel, &target)?;
 
         if let Some(patch_version) = self.client_patch_version {
             let version = if stable {
@@ -30,55 +32,50 @@ impl flags::Dist {
                 // A hack to make VS Code prefer nightly over stable.
                 format!("0.3.{}", patch_version)
             };
-            let release_tag = if stable { date_iso(sh)? } else { "nightly".to_string() };
-            dist_client(sh, &version, &release_tag, &target)?;
+            let release_tag = if stable { date_iso()? } else { "nightly".to_string() };
+            dist_client(&version, &release_tag, &target)?;
         }
         Ok(())
     }
 }
 
-fn dist_client(
-    sh: &Shell,
-    version: &str,
-    release_tag: &str,
-    target: &Target,
-) -> anyhow::Result<()> {
+fn dist_client(version: &str, release_tag: &str, target: &Target) -> Result<()> {
     let bundle_path = Path::new("editors").join("code").join("server");
-    sh.create_dir(&bundle_path)?;
-    sh.copy_file(&target.server_path, &bundle_path)?;
+    mkdir_p(&bundle_path)?;
+    cp(&target.server_path, &bundle_path)?;
     if let Some(symbols_path) = &target.symbols_path {
-        sh.copy_file(symbols_path, &bundle_path)?;
+        cp(symbols_path, &bundle_path)?;
     }
 
-    let _d = sh.push_dir("./editors/code");
+    let _d = pushd("./editors/code")?;
 
-    let mut patch = Patch::new(sh, "./package.json")?;
+    let mut patch = Patch::new("./package.json")?;
     patch
         .replace(r#""version": "0.4.0-dev""#, &format!(r#""version": "{}""#, version))
         .replace(r#""releaseTag": null"#, &format!(r#""releaseTag": "{}""#, release_tag))
         .replace(r#""$generated-start": {},"#, "")
         .replace(",\n                \"$generated-end\": {}", "")
         .replace(r#""enabledApiProposals": [],"#, r#""#);
-    patch.commit(sh)?;
+    patch.commit()?;
 
     Ok(())
 }
 
-fn dist_server(sh: &Shell, release_channel: &str, target: &Target) -> anyhow::Result<()> {
-    let _e = sh.push_env("RUST_ANALYZER_CHANNEL", release_channel);
-    let _e = sh.push_env("CARGO_PROFILE_RELEASE_LTO", "thin");
+fn dist_server(release_channel: &str, target: &Target) -> Result<()> {
+    let _e = pushenv("RUST_ANALYZER_CHANNEL", release_channel);
+    let _e = pushenv("CARGO_PROFILE_RELEASE_LTO", "thin");
 
     // Uncomment to enable debug info for releases. Note that:
     //   * debug info is split on windows and macs, so it does nothing for those platforms,
     //   * on Linux, this blows up the binary size from 8MB to 43MB, which is unreasonable.
-    // let _e = sh.push_env("CARGO_PROFILE_RELEASE_DEBUG", "1");
+    // let _e = pushenv("CARGO_PROFILE_RELEASE_DEBUG", "1");
 
     if target.name.contains("-linux-") {
         env::set_var("CC", "clang");
     }
 
     let target_name = &target.name;
-    cmd!(sh, "cargo build --manifest-path ./crates/rust-analyzer/Cargo.toml --bin rust-analyzer --target {target_name} --release").run()?;
+    cmd!("cargo build --manifest-path ./crates/rust-analyzer/Cargo.toml --bin rust-analyzer --target {target_name} --release").run()?;
 
     let dst = Path::new("dist").join(&target.artifact_name);
     gzip(&target.server_path, &dst.with_extension("gz"))?;
@@ -86,7 +83,7 @@ fn dist_server(sh: &Shell, release_channel: &str, target: &Target) -> anyhow::Re
     Ok(())
 }
 
-fn gzip(src_path: &Path, dest_path: &Path) -> anyhow::Result<()> {
+fn gzip(src_path: &Path, dest_path: &Path) -> Result<()> {
     let mut encoder = GzEncoder::new(File::create(dest_path)?, Compression::best());
     let mut input = io::BufReader::new(File::open(src_path)?);
     io::copy(&mut input, &mut encoder)?;
@@ -136,9 +133,9 @@ struct Patch {
 }
 
 impl Patch {
-    fn new(sh: &Shell, path: impl Into<PathBuf>) -> anyhow::Result<Patch> {
+    fn new(path: impl Into<PathBuf>) -> Result<Patch> {
         let path = path.into();
-        let contents = sh.read_file(&path)?;
+        let contents = read_file(&path)?;
         Ok(Patch { path, original_contents: contents.clone(), contents })
     }
 
@@ -148,8 +145,8 @@ impl Patch {
         self
     }
 
-    fn commit(&self, sh: &Shell) -> anyhow::Result<()> {
-        sh.write_file(&self.path, &self.contents)?;
+    fn commit(&self) -> Result<()> {
+        write_file(&self.path, &self.contents)?;
         Ok(())
     }
 }
diff --git a/xtask/src/install.rs b/xtask/src/install.rs
index ae978d5512..58f386be85 100644
--- a/xtask/src/install.rs
+++ b/xtask/src/install.rs
@@ -3,20 +3,20 @@
 use std::{env, path::PathBuf, str};
 
 use anyhow::{bail, format_err, Context, Result};
-use xshell::{cmd, Shell};
+use xshell::{cmd, pushd};
 
 use crate::flags;
 
 impl flags::Install {
-    pub(crate) fn run(self, sh: &Shell) -> Result<()> {
+    pub(crate) fn run(self) -> Result<()> {
         if cfg!(target_os = "macos") {
-            fix_path_for_mac(sh).context("Fix path for mac")?;
+            fix_path_for_mac().context("Fix path for mac")?;
         }
         if let Some(server) = self.server() {
-            install_server(sh, server).context("install server")?;
+            install_server(server).context("install server")?;
         }
         if let Some(client) = self.client() {
-            install_client(sh, client).context("install client")?;
+            install_client(client).context("install client")?;
         }
         Ok(())
     }
@@ -39,14 +39,15 @@ pub(crate) enum Malloc {
     Jemalloc,
 }
 
-fn fix_path_for_mac(sh: &Shell) -> Result<()> {
+fn fix_path_for_mac() -> Result<()> {
     let mut vscode_path: Vec<PathBuf> = {
         const COMMON_APP_PATH: &str =
             r"/Applications/Visual Studio Code.app/Contents/Resources/app/bin";
         const ROOT_DIR: &str = "";
-        let home_dir = sh.var("HOME").map_err(|err| {
-            format_err!("Failed getting HOME from environment with error: {}.", err)
-        })?;
+        let home_dir = match env::var("HOME") {
+            Ok(home) => home,
+            Err(e) => bail!("Failed getting HOME from environment with error: {}.", e),
+        };
 
         [ROOT_DIR, &home_dir]
             .into_iter()
@@ -57,33 +58,36 @@ fn fix_path_for_mac(sh: &Shell) -> Result<()> {
     };
 
     if !vscode_path.is_empty() {
-        let vars = sh.var_os("PATH").context("Could not get PATH variable from env.")?;
+        let vars = match env::var_os("PATH") {
+            Some(path) => path,
+            None => bail!("Could not get PATH variable from env."),
+        };
 
         let mut paths = env::split_paths(&vars).collect::<Vec<_>>();
         paths.append(&mut vscode_path);
         let new_paths = env::join_paths(paths).context("build env PATH")?;
-        sh.set_var("PATH", &new_paths);
+        env::set_var("PATH", &new_paths);
     }
 
     Ok(())
 }
 
-fn install_client(sh: &Shell, client_opt: ClientOpt) -> Result<()> {
-    let _dir = sh.push_dir("./editors/code");
+fn install_client(client_opt: ClientOpt) -> Result<()> {
+    let _dir = pushd("./editors/code");
 
     // Package extension.
     if cfg!(unix) {
-        cmd!(sh, "npm --version").run().context("`npm` is required to build the VS Code plugin")?;
-        cmd!(sh, "npm ci").run()?;
+        cmd!("npm --version").run().context("`npm` is required to build the VS Code plugin")?;
+        cmd!("npm ci").run()?;
 
-        cmd!(sh, "npm run package --scripts-prepend-node-path").run()?;
+        cmd!("npm run package --scripts-prepend-node-path").run()?;
     } else {
-        cmd!(sh, "cmd.exe /c npm --version")
+        cmd!("cmd.exe /c npm --version")
             .run()
             .context("`npm` is required to build the VS Code plugin")?;
-        cmd!(sh, "cmd.exe /c npm ci").run()?;
+        cmd!("cmd.exe /c npm ci").run()?;
 
-        cmd!(sh, "cmd.exe /c npm run package").run()?;
+        cmd!("cmd.exe /c npm run package").run()?;
     };
 
     // Find the appropriate VS Code binary.
@@ -100,9 +104,9 @@ fn install_client(sh: &Shell, client_opt: ClientOpt) -> Result<()> {
         .copied()
         .find(|&bin| {
             if cfg!(unix) {
-                cmd!(sh, "{bin} --version").read().is_ok()
+                cmd!("{bin} --version").read().is_ok()
             } else {
-                cmd!(sh, "cmd.exe /c {bin}.cmd --version").read().is_ok()
+                cmd!("cmd.exe /c {bin}.cmd --version").read().is_ok()
             }
         })
         .ok_or_else(|| {
@@ -111,11 +115,11 @@ fn install_client(sh: &Shell, client_opt: ClientOpt) -> Result<()> {
 
     // Install & verify.
     let installed_extensions = if cfg!(unix) {
-        cmd!(sh, "{code} --install-extension rust-analyzer.vsix --force").run()?;
-        cmd!(sh, "{code} --list-extensions").read()?
+        cmd!("{code} --install-extension rust-analyzer.vsix --force").run()?;
+        cmd!("{code} --list-extensions").read()?
     } else {
-        cmd!(sh, "cmd.exe /c {code}.cmd --install-extension rust-analyzer.vsix --force").run()?;
-        cmd!(sh, "cmd.exe /c {code}.cmd --list-extensions").read()?
+        cmd!("cmd.exe /c {code}.cmd --install-extension rust-analyzer.vsix --force").run()?;
+        cmd!("cmd.exe /c {code}.cmd --list-extensions").read()?
     };
 
     if !installed_extensions.contains("rust-analyzer") {
@@ -129,14 +133,14 @@ fn install_client(sh: &Shell, client_opt: ClientOpt) -> Result<()> {
     Ok(())
 }
 
-fn install_server(sh: &Shell, opts: ServerOpt) -> Result<()> {
+fn install_server(opts: ServerOpt) -> Result<()> {
     let features = match opts.malloc {
         Malloc::System => &[][..],
         Malloc::Mimalloc => &["--features", "mimalloc"],
         Malloc::Jemalloc => &["--features", "jemalloc"],
     };
 
-    let cmd = cmd!(sh, "cargo install --path crates/rust-analyzer --locked --force --features force-always-assert {features...}");
+    let cmd = cmd!("cargo install --path crates/rust-analyzer --locked --force --features force-always-assert {features...}");
     cmd.run()?;
     Ok(())
 }
diff --git a/xtask/src/main.rs b/xtask/src/main.rs
index df726dc23c..5e5401ce28 100644
--- a/xtask/src/main.rs
+++ b/xtask/src/main.rs
@@ -14,16 +14,15 @@ mod release;
 mod dist;
 mod metrics;
 
-use anyhow::bail;
+use anyhow::{bail, Result};
 use std::{
     env,
     path::{Path, PathBuf},
 };
-use xshell::{cmd, Shell};
+use xshell::{cmd, cp, pushd, pushenv};
 
-fn main() -> anyhow::Result<()> {
-    let sh = &Shell::new()?;
-    sh.change_dir(project_root());
+fn main() -> Result<()> {
+    let _d = pushd(project_root())?;
 
     let flags = flags::Xtask::from_env()?;
     match flags.subcommand {
@@ -31,21 +30,18 @@ fn main() -> anyhow::Result<()> {
             println!("{}", flags::Xtask::HELP);
             Ok(())
         }
-        flags::XtaskCmd::Install(cmd) => cmd.run(sh),
-        flags::XtaskCmd::FuzzTests(_) => run_fuzzer(sh),
-        flags::XtaskCmd::Release(cmd) => cmd.run(sh),
-        flags::XtaskCmd::Promote(cmd) => cmd.run(sh),
-        flags::XtaskCmd::Dist(cmd) => cmd.run(sh),
-        flags::XtaskCmd::Metrics(cmd) => cmd.run(sh),
+        flags::XtaskCmd::Install(cmd) => cmd.run(),
+        flags::XtaskCmd::FuzzTests(_) => run_fuzzer(),
+        flags::XtaskCmd::Release(cmd) => cmd.run(),
+        flags::XtaskCmd::Promote(cmd) => cmd.run(),
+        flags::XtaskCmd::Dist(cmd) => cmd.run(),
+        flags::XtaskCmd::Metrics(cmd) => cmd.run(),
         flags::XtaskCmd::Bb(cmd) => {
             {
-                let _d = sh.push_dir("./crates/rust-analyzer");
-                cmd!(sh, "cargo build --release --features jemalloc").run()?;
+                let _d = pushd("./crates/rust-analyzer")?;
+                cmd!("cargo build --release --features jemalloc").run()?;
             }
-            sh.copy_file(
-                "./target/release/rust-analyzer",
-                format!("./target/rust-analyzer-{}", cmd.suffix),
-            )?;
+            cp("./target/release/rust-analyzer", format!("./target/rust-analyzer-{}", cmd.suffix))?;
             Ok(())
         }
     }
@@ -61,25 +57,25 @@ fn project_root() -> PathBuf {
     .to_path_buf()
 }
 
-fn run_fuzzer(sh: &Shell) -> anyhow::Result<()> {
-    let _d = sh.push_dir("./crates/syntax");
-    let _e = sh.push_env("RUSTUP_TOOLCHAIN", "nightly");
-    if cmd!(sh, "cargo fuzz --help").read().is_err() {
-        cmd!(sh, "cargo install cargo-fuzz").run()?;
+fn run_fuzzer() -> Result<()> {
+    let _d = pushd("./crates/syntax")?;
+    let _e = pushenv("RUSTUP_TOOLCHAIN", "nightly");
+    if cmd!("cargo fuzz --help").read().is_err() {
+        cmd!("cargo install cargo-fuzz").run()?;
     };
 
     // Expecting nightly rustc
-    let out = cmd!(sh, "rustc --version").read()?;
+    let out = cmd!("rustc --version").read()?;
     if !out.contains("nightly") {
         bail!("fuzz tests require nightly rustc")
     }
 
-    cmd!(sh, "cargo fuzz run parser").run()?;
+    cmd!("cargo fuzz run parser").run()?;
     Ok(())
 }
 
-fn date_iso(sh: &Shell) -> anyhow::Result<String> {
-    let res = cmd!(sh, "date -u +%Y-%m-%d").read()?;
+fn date_iso() -> Result<String> {
+    let res = cmd!("date -u +%Y-%m-%d").read()?;
     Ok(res)
 }
 
diff --git a/xtask/src/metrics.rs b/xtask/src/metrics.rs
index b683b32e6d..e4d3e981af 100644
--- a/xtask/src/metrics.rs
+++ b/xtask/src/metrics.rs
@@ -1,103 +1,95 @@
 use std::{
     collections::BTreeMap,
-    env, fs,
+    env,
     io::Write as _,
     path::Path,
     time::{Instant, SystemTime, UNIX_EPOCH},
 };
 
-use anyhow::{bail, format_err};
-use xshell::{cmd, Shell};
+use anyhow::{bail, format_err, Result};
+use xshell::{cmd, mkdir_p, pushd, pushenv, read_file, rm_rf};
 
 use crate::flags;
 
 type Unit = String;
 
 impl flags::Metrics {
-    pub(crate) fn run(self, sh: &Shell) -> anyhow::Result<()> {
-        let mut metrics = Metrics::new(sh)?;
+    pub(crate) fn run(self) -> Result<()> {
+        let mut metrics = Metrics::new()?;
         if !self.dry_run {
-            sh.remove_path("./target/release")?;
+            rm_rf("./target/release")?;
         }
         if !Path::new("./target/rustc-perf").exists() {
-            sh.create_dir("./target/rustc-perf")?;
-            cmd!(sh, "git clone https://github.com/rust-lang/rustc-perf.git ./target/rustc-perf")
+            mkdir_p("./target/rustc-perf")?;
+            cmd!("git clone https://github.com/rust-lang/rustc-perf.git ./target/rustc-perf")
                 .run()?;
         }
         {
-            let _d = sh.push_dir("./target/rustc-perf");
+            let _d = pushd("./target/rustc-perf")?;
             let revision = &metrics.perf_revision;
-            cmd!(sh, "git reset --hard {revision}").run()?;
+            cmd!("git reset --hard {revision}").run()?;
         }
 
-        let _env = sh.push_env("RA_METRICS", "1");
+        let _env = pushenv("RA_METRICS", "1");
 
         {
             // https://github.com/rust-analyzer/rust-analyzer/issues/9997
-            let _d = sh.push_dir("target/rustc-perf/collector/benchmarks/webrender");
-            cmd!(sh, "cargo update -p url --precise 1.6.1").run()?;
+            let _d = pushd("target/rustc-perf/collector/benchmarks/webrender")?;
+            cmd!("cargo update -p url --precise 1.6.1").run()?;
         }
-        metrics.measure_build(sh)?;
-        metrics.measure_analysis_stats_self(sh)?;
-        metrics.measure_analysis_stats(sh, "ripgrep")?;
-        metrics.measure_analysis_stats(sh, "webrender")?;
-        metrics.measure_analysis_stats(sh, "diesel/diesel")?;
+        metrics.measure_build()?;
+        metrics.measure_analysis_stats_self()?;
+        metrics.measure_analysis_stats("ripgrep")?;
+        metrics.measure_analysis_stats("webrender")?;
+        metrics.measure_analysis_stats("diesel/diesel")?;
 
         if !self.dry_run {
-            let _d = sh.push_dir("target");
+            let _d = pushd("target")?;
             let metrics_token = env::var("METRICS_TOKEN").unwrap();
             cmd!(
-                sh,
                 "git clone --depth 1 https://{metrics_token}@github.com/rust-analyzer/metrics.git"
             )
             .run()?;
-            let _d = sh.push_dir("metrics");
+            let _d = pushd("metrics")?;
 
-            let mut file = fs::File::options().append(true).open("metrics.json")?;
+            let mut file = std::fs::OpenOptions::new().append(true).open("metrics.json")?;
             writeln!(file, "{}", metrics.json())?;
-            cmd!(sh, "git add .").run()?;
-            cmd!(sh, "git -c user.name=Bot -c user.email=dummy@example.com commit --message 📈")
+            cmd!("git add .").run()?;
+            cmd!("git -c user.name=Bot -c user.email=dummy@example.com commit --message 📈")
                 .run()?;
-            cmd!(sh, "git push origin master").run()?;
+            cmd!("git push origin master").run()?;
         }
-        eprintln!("{metrics:#?}");
+        eprintln!("{:#?}", metrics);
         Ok(())
     }
 }
 
 impl Metrics {
-    fn measure_build(&mut self, sh: &Shell) -> anyhow::Result<()> {
+    fn measure_build(&mut self) -> Result<()> {
         eprintln!("\nMeasuring build");
-        cmd!(sh, "cargo fetch").run()?;
+        cmd!("cargo fetch").run()?;
 
         let time = Instant::now();
-        cmd!(sh, "cargo build --release --package rust-analyzer --bin rust-analyzer").run()?;
+        cmd!("cargo build --release --package rust-analyzer --bin rust-analyzer").run()?;
         let time = time.elapsed();
         self.report("build", time.as_millis() as u64, "ms".into());
         Ok(())
     }
-    fn measure_analysis_stats_self(&mut self, sh: &Shell) -> anyhow::Result<()> {
-        self.measure_analysis_stats_path(sh, "self", ".")
+    fn measure_analysis_stats_self(&mut self) -> Result<()> {
+        self.measure_analysis_stats_path("self", ".")
     }
-    fn measure_analysis_stats(&mut self, sh: &Shell, bench: &str) -> anyhow::Result<()> {
+    fn measure_analysis_stats(&mut self, bench: &str) -> Result<()> {
         self.measure_analysis_stats_path(
-            sh,
             bench,
             &format!("./target/rustc-perf/collector/benchmarks/{}", bench),
         )
     }
-    fn measure_analysis_stats_path(
-        &mut self,
-        sh: &Shell,
-        name: &str,
-        path: &str,
-    ) -> anyhow::Result<()> {
-        eprintln!("\nMeasuring analysis-stats/{name}");
-        let output =
-            cmd!(sh, "./target/release/rust-analyzer -q analysis-stats --memory-usage {path}")
-                .read()?;
+    fn measure_analysis_stats_path(&mut self, name: &str, path: &str) -> Result<()> {
+        eprintln!("\nMeasuring analysis-stats/{}", name);
+        let output = cmd!("./target/release/rust-analyzer -q analysis-stats --memory-usage {path}")
+            .read()?;
         for (metric, value, unit) in parse_metrics(&output) {
-            self.report(&format!("analysis-stats/{name}/{metric}"), value, unit.into());
+            self.report(&format!("analysis-stats/{}/{}", name, metric), value, unit.into());
         }
         Ok(())
     }
@@ -133,10 +125,10 @@ struct Host {
 }
 
 impl Metrics {
-    fn new(sh: &Shell) -> anyhow::Result<Metrics> {
-        let host = Host::new(sh)?;
+    fn new() -> Result<Metrics> {
+        let host = Host::new()?;
         let timestamp = SystemTime::now();
-        let revision = cmd!(sh, "git rev-parse HEAD").read()?;
+        let revision = cmd!("git rev-parse HEAD").read()?;
         let perf_revision = "c52ee623e231e7690a93be88d943016968c1036b".into();
         Ok(Metrics { host, timestamp, revision, perf_revision, metrics: BTreeMap::new() })
     }
@@ -165,29 +157,28 @@ impl Metrics {
 }
 
 impl Host {
-    fn new(sh: &Shell) -> anyhow::Result<Host> {
+    fn new() -> Result<Host> {
         if cfg!(not(target_os = "linux")) {
             bail!("can only collect metrics on Linux ");
         }
 
-        let os = read_field(sh, "/etc/os-release", "PRETTY_NAME=")?.trim_matches('"').to_string();
+        let os = read_field("/etc/os-release", "PRETTY_NAME=")?.trim_matches('"').to_string();
 
-        let cpu = read_field(sh, "/proc/cpuinfo", "model name")?
-            .trim_start_matches(':')
-            .trim()
-            .to_string();
+        let cpu =
+            read_field("/proc/cpuinfo", "model name")?.trim_start_matches(':').trim().to_string();
 
-        let mem = read_field(sh, "/proc/meminfo", "MemTotal:")?;
+        let mem = read_field("/proc/meminfo", "MemTotal:")?;
 
         return Ok(Host { os, cpu, mem });
 
-        fn read_field(sh: &Shell, path: &str, field: &str) -> anyhow::Result<String> {
-            let text = sh.read_file(path)?;
+        fn read_field(path: &str, field: &str) -> Result<String> {
+            let text = read_file(path)?;
 
-            text.lines()
-                .find_map(|it| it.strip_prefix(field))
-                .map(|it| it.trim().to_string())
-                .ok_or_else(|| format_err!("can't parse {}", path))
+            let line = text
+                .lines()
+                .find(|it| it.starts_with(field))
+                .ok_or_else(|| format_err!("can't parse {}", path))?;
+            Ok(line[field.len()..].trim().to_string())
         }
     }
     fn to_json(&self, mut obj: write_json::Object<'_>) {
diff --git a/xtask/src/release.rs b/xtask/src/release.rs
index 9bd5d9d310..acb13282f6 100644
--- a/xtask/src/release.rs
+++ b/xtask/src/release.rs
@@ -1,15 +1,15 @@
 mod changelog;
 
-use xshell::{cmd, Shell};
+use xshell::{cmd, pushd, read_dir, read_file, write_file};
 
-use crate::{date_iso, flags, is_release_tag, project_root};
+use crate::{date_iso, flags, is_release_tag, project_root, Result};
 
 impl flags::Release {
-    pub(crate) fn run(self, sh: &Shell) -> anyhow::Result<()> {
+    pub(crate) fn run(self) -> Result<()> {
         if !self.dry_run {
-            cmd!(sh, "git switch release").run()?;
-            cmd!(sh, "git fetch upstream --tags --force").run()?;
-            cmd!(sh, "git reset --hard tags/nightly").run()?;
+            cmd!("git switch release").run()?;
+            cmd!("git fetch upstream --tags --force").run()?;
+            cmd!("git reset --hard tags/nightly").run()?;
             // The `release` branch sometimes has a couple of cherry-picked
             // commits for patch releases. If that's the case, just overwrite
             // it. As we are setting `release` branch to an up-to-date `nightly`
@@ -19,24 +19,24 @@ impl flags::Release {
             // commits -- they'll be kept alive by the tag. More generally, we
             // don't care about historic releases all that much, it's fine even
             // to delete old tags.
-            cmd!(sh, "git push --force").run()?;
+            cmd!("git push --force").run()?;
         }
 
         // Generates bits of manual.adoc.
-        cmd!(sh, "cargo test -p ide_assists -p ide_diagnostics -p rust-analyzer -- sourcegen_")
+        cmd!("cargo test -p ide_assists -p ide_diagnostics -p rust-analyzer -- sourcegen_")
             .run()?;
 
         let website_root = project_root().join("../rust-analyzer.github.io");
         {
-            let _dir = sh.push_dir(&website_root);
-            cmd!(sh, "git switch src").run()?;
-            cmd!(sh, "git pull").run()?;
+            let _dir = pushd(&website_root)?;
+            cmd!("git switch src").run()?;
+            cmd!("git pull").run()?;
         }
         let changelog_dir = website_root.join("./thisweek/_posts");
 
-        let today = date_iso(sh)?;
-        let commit = cmd!(sh, "git rev-parse HEAD").read()?;
-        let changelog_n = sh.read_dir(changelog_dir.as_path())?.len();
+        let today = date_iso()?;
+        let commit = cmd!("git rev-parse HEAD").read()?;
+        let changelog_n = read_dir(changelog_dir.as_path())?.len();
 
         for adoc in [
             "manual.adoc",
@@ -48,46 +48,42 @@ impl flags::Release {
             let src = project_root().join("./docs/user/").join(adoc);
             let dst = website_root.join(adoc);
 
-            let contents = sh.read_file(src)?;
-            sh.write_file(dst, contents)?;
+            let contents = read_file(src)?;
+            write_file(dst, contents)?;
         }
 
-        let tags = cmd!(sh, "git tag --list").read()?;
+        let tags = cmd!("git tag --list").read()?;
         let prev_tag = tags.lines().filter(|line| is_release_tag(line)).last().unwrap();
 
-        let contents = changelog::get_changelog(sh, changelog_n, &commit, prev_tag, &today)?;
+        let contents = changelog::get_changelog(changelog_n, &commit, prev_tag, &today)?;
         let path = changelog_dir.join(format!("{}-changelog-{}.adoc", today, changelog_n));
-        sh.write_file(&path, &contents)?;
+        write_file(&path, &contents)?;
 
         Ok(())
     }
 }
 
 impl flags::Promote {
-    pub(crate) fn run(self, sh: &Shell) -> anyhow::Result<()> {
-        let _dir = sh.push_dir("../rust-rust-analyzer");
-        cmd!(sh, "git switch master").run()?;
-        cmd!(sh, "git fetch upstream").run()?;
-        cmd!(sh, "git reset --hard upstream/master").run()?;
-        cmd!(sh, "git submodule update --recursive").run()?;
+    pub(crate) fn run(self) -> Result<()> {
+        let _dir = pushd("../rust-rust-analyzer")?;
+        cmd!("git switch master").run()?;
+        cmd!("git fetch upstream").run()?;
+        cmd!("git reset --hard upstream/master").run()?;
+        cmd!("git submodule update --recursive").run()?;
 
-        let date = date_iso(sh)?;
-        let branch = format!("rust-analyzer-{date}");
-        cmd!(sh, "git switch -c {branch}").run()?;
+        let branch = format!("rust-analyzer-{}", date_iso()?);
+        cmd!("git switch -c {branch}").run()?;
         {
-            let _dir = sh.push_dir("src/tools/rust-analyzer");
-            cmd!(sh, "git fetch origin").run()?;
-            cmd!(sh, "git reset --hard origin/release").run()?;
+            let _dir = pushd("src/tools/rust-analyzer")?;
+            cmd!("git fetch origin").run()?;
+            cmd!("git reset --hard origin/release").run()?;
         }
-        cmd!(sh, "git add src/tools/rust-analyzer").run()?;
-        cmd!(sh, "git commit -m':arrow_up: rust-analyzer'").run()?;
+        cmd!("git add src/tools/rust-analyzer").run()?;
+        cmd!("git commit -m':arrow_up: rust-analyzer'").run()?;
         if !self.dry_run {
-            cmd!(sh, "git push -u origin {branch}").run()?;
-            cmd!(
-                sh,
-                "xdg-open https://github.com/matklad/rust/pull/new/{branch}?body=r%3F%20%40ghost"
-            )
-            .run()?;
+            cmd!("git push -u origin {branch}").run()?;
+            cmd!("xdg-open https://github.com/matklad/rust/pull/new/{branch}?body=r%3F%20%40ghost")
+                .run()?;
         }
         Ok(())
     }
diff --git a/xtask/src/release/changelog.rs b/xtask/src/release/changelog.rs
index ceef9fac2d..2384a746f3 100644
--- a/xtask/src/release/changelog.rs
+++ b/xtask/src/release/changelog.rs
@@ -1,17 +1,16 @@
 use std::fmt::Write;
 use std::{env, iter};
 
-use anyhow::bail;
-use xshell::{cmd, Shell};
+use anyhow::{bail, Result};
+use xshell::cmd;
 
 pub(crate) fn get_changelog(
-    sh: &Shell,
     changelog_n: usize,
     commit: &str,
     prev_tag: &str,
     today: &str,
-) -> anyhow::Result<String> {
-    let git_log = cmd!(sh, "git log {prev_tag}..HEAD --merges --reverse").read()?;
+) -> Result<String> {
+    let git_log = cmd!("git log {prev_tag}..HEAD --merges --reverse").read()?;
     let mut features = String::new();
     let mut fixes = String::new();
     let mut internal = String::new();
@@ -31,15 +30,14 @@ pub(crate) fn get_changelog(
 
                 // we don't use an HTTPS client or JSON parser to keep the build times low
                 let pr_json =
-                    cmd!(sh, "curl -s -H {accept} -H {authorization} {pr_url}/{pr}").read()?;
-                let pr_title = cmd!(sh, "jq .title").stdin(&pr_json).read()?;
+                    cmd!("curl -s -H {accept} -H {authorization} {pr_url}/{pr}").read()?;
+                let pr_title = cmd!("jq .title").stdin(&pr_json).read()?;
                 let pr_title = unescape(&pr_title[1..pr_title.len() - 1]);
-                let pr_comment = cmd!(sh, "jq .body").stdin(pr_json).read()?;
+                let pr_comment = cmd!("jq .body").stdin(pr_json).read()?;
 
                 let comments_json =
-                    cmd!(sh, "curl -s -H {accept} -H {authorization} {pr_url}/{pr}/comments")
-                        .read()?;
-                let pr_comments = cmd!(sh, "jq .[].body").stdin(comments_json).read()?;
+                    cmd!("curl -s -H {accept} -H {authorization} {pr_url}/{pr}/comments").read()?;
+                let pr_comments = cmd!("jq .[].body").stdin(comments_json).read()?;
 
                 let l = iter::once(pr_comment.as_str())
                     .chain(pr_comments.lines())


Test patch:
diff --git a/crates/hir_ty/src/tests/regression.rs b/crates/hir_ty/src/tests/regression.rs
index 4a65b927ba..63faecbfaf 100644
--- a/crates/hir_ty/src/tests/regression.rs
+++ b/crates/hir_ty/src/tests/regression.rs
@@ -1431,3 +1431,26 @@ fn nalgebra_factorial() {
         "#,
     )
 }
+
+#[test]
+fn regression_11688_1() {
+    check_no_mismatches(
+        r#"
+        pub struct Buffer<T>(T);
+        type Writer = Buffer<u8>;
+        impl<T> Buffer<T> {
+            fn extend_from_array<const N: usize>(&mut self, xs: &[T; N]) {
+                loop {}
+            }
+        }
+        trait Encode<S> {
+            fn encode(self, w: &mut Writer, s: &mut S);
+        }
+        impl<S> Encode<S> for u8 {
+            fn encode(self, w: &mut Writer, _: &mut S) {
+                w.extend_from_array(&self.to_le_bytes());
+            }
+        }
+        "#,
+    );
+}
diff --git a/crates/ide_completion/src/tests/item.rs b/crates/ide_completion/src/tests/item.rs
index ed341b361a..1d5ddc092e 100644
--- a/crates/ide_completion/src/tests/item.rs
+++ b/crates/ide_completion/src/tests/item.rs
@@ -76,14 +76,8 @@ fn after_target_name_in_impl() {
             kw for
         "#]],
     );
-    // FIXME: This should not emit `kw for`
-    check(
-        r"impl Trait for Type $0",
-        expect![[r#"
-        kw where
-        kw for
-    "#]],
-    );
+    // FIXME: This should emit `kw where`
+    check(r"impl Trait for Type $0", expect![[r#""#]]);
 }
 
 #[test]
diff --git a/crates/ide_completion/src/tests/pattern.rs b/crates/ide_completion/src/tests/pattern.rs
index 891c1346df..7767f24632 100644
--- a/crates/ide_completion/src/tests/pattern.rs
+++ b/crates/ide_completion/src/tests/pattern.rs
@@ -92,16 +92,6 @@ fn quux() {
 "#,
         expect![[r#""#]],
     );
-    check_empty(
-        r#"
-fn foo() {
-    for &$0 in () {}
-}
-"#,
-        expect![[r#"
-            kw mut
-        "#]],
-    );
 }
 
 #[test]
diff --git a/crates/ide_db/src/tests/sourcegen_lints.rs b/crates/ide_db/src/tests/sourcegen_lints.rs
index b7db1dd8db..61c05c7ffd 100644
--- a/crates/ide_db/src/tests/sourcegen_lints.rs
+++ b/crates/ide_db/src/tests/sourcegen_lints.rs
@@ -4,20 +4,16 @@ use std::{borrow::Cow, fs, path::Path};
 use itertools::Itertools;
 use stdx::format_to;
 use test_utils::project_root;
-use xshell::{cmd, Shell};
+use xshell::cmd;
 
 /// This clones rustc repo, and so is not worth to keep up-to-date. We update
 /// manually by un-ignoring the test from time to time.
 #[test]
 #[ignore]
 fn sourcegen_lint_completions() {
-    let sh = &Shell::new().unwrap();
-
     let rust_repo = project_root().join("./target/rust");
     if !rust_repo.exists() {
-        cmd!(sh, "git clone --depth=1 https://github.com/rust-lang/rust {rust_repo}")
-            .run()
-            .unwrap();
+        cmd!("git clone --depth=1 https://github.com/rust-lang/rust {rust_repo}").run().unwrap();
     }
 
     let mut contents = String::from(
@@ -34,19 +30,16 @@ pub struct LintGroup {
 ",
     );
 
-    generate_lint_descriptor(sh, &mut contents);
+    generate_lint_descriptor(&mut contents);
     contents.push('\n');
 
     generate_feature_descriptor(&mut contents, &rust_repo.join("src/doc/unstable-book/src"));
     contents.push('\n');
 
     let lints_json = project_root().join("./target/clippy_lints.json");
-    cmd!(
-        sh,
-        "curl https://rust-lang.github.io/rust-clippy/master/lints.json --output {lints_json}"
-    )
-    .run()
-    .unwrap();
+    cmd!("curl https://rust-lang.github.io/rust-clippy/master/lints.json --output {lints_json}")
+        .run()
+        .unwrap();
     generate_descriptor_clippy(&mut contents, &lints_json);
 
     let contents = sourcegen::add_preamble("sourcegen_lints", sourcegen::reformat(contents));
@@ -55,10 +48,10 @@ pub struct LintGroup {
     sourcegen::ensure_file_contents(destination.as_path(), &contents);
 }
 
-fn generate_lint_descriptor(sh: &Shell, buf: &mut String) {
+fn generate_lint_descriptor(buf: &mut String) {
     // FIXME: rustdoc currently requires an input file for -Whelp cc https://github.com/rust-lang/rust/pull/88831
     let file = project_root().join(file!());
-    let stdout = cmd!(sh, "rustdoc -W help {file}").read().unwrap();
+    let stdout = cmd!("rustdoc -W help {file}").read().unwrap();
     let start_lints = stdout.find("----  -------  -------").unwrap();
     let start_lint_groups = stdout.find("----  ---------").unwrap();
     let start_lints_rustdoc =
diff --git a/crates/rust-analyzer/tests/slow-tests/tidy.rs b/crates/rust-analyzer/tests/slow-tests/tidy.rs
index 9e53aa847d..2b51e7fecb 100644
--- a/crates/rust-analyzer/tests/slow-tests/tidy.rs
+++ b/crates/rust-analyzer/tests/slow-tests/tidy.rs
@@ -3,15 +3,14 @@ use std::{
     path::{Path, PathBuf},
 };
 
-use xshell::{cmd, Shell};
+use xshell::{cmd, pushd, pushenv, read_file};
 
 #[test]
 fn check_code_formatting() {
-    let sh = &Shell::new().unwrap();
-    sh.change_dir(sourcegen::project_root());
-    sh.set_var("RUSTUP_TOOLCHAIN", "stable");
+    let _dir = pushd(sourcegen::project_root()).unwrap();
+    let _e = pushenv("RUSTUP_TOOLCHAIN", "stable");
 
-    let out = cmd!(sh, "rustfmt --version").read().unwrap();
+    let out = cmd!("rustfmt --version").read().unwrap();
     if !out.contains("stable") {
         panic!(
             "Failed to run rustfmt from toolchain 'stable'. \
@@ -19,27 +18,25 @@ fn check_code_formatting() {
         )
     }
 
-    let res = cmd!(sh, "cargo fmt -- --check").run();
+    let res = cmd!("cargo fmt -- --check").run();
     if res.is_err() {
-        let _ = cmd!(sh, "cargo fmt").run();
+        let _ = cmd!("cargo fmt").run();
     }
     res.unwrap()
 }
 
 #[test]
 fn check_lsp_extensions_docs() {
-    let sh = &Shell::new().unwrap();
-
     let expected_hash = {
-        let lsp_ext_rs = sh
-            .read_file(sourcegen::project_root().join("crates/rust-analyzer/src/lsp_ext.rs"))
-            .unwrap();
+        let lsp_ext_rs =
+            read_file(sourcegen::project_root().join("crates/rust-analyzer/src/lsp_ext.rs"))
+                .unwrap();
         stable_hash(lsp_ext_rs.as_str())
     };
 
     let actual_hash = {
         let lsp_extensions_md =
-            sh.read_file(sourcegen::project_root().join("docs/dev/lsp-extensions.md")).unwrap();
+            read_file(sourcegen::project_root().join("docs/dev/lsp-extensions.md")).unwrap();
         let text = lsp_extensions_md
             .lines()
             .find_map(|line| line.strip_prefix("lsp_ext.rs hash:"))
@@ -65,8 +62,6 @@ Please adjust docs/dev/lsp-extensions.md.
 
 #[test]
 fn files_are_tidy() {
-    let sh = &Shell::new().unwrap();
-
     let files = sourcegen::list_files(&sourcegen::project_root().join("crates"));
 
     let mut tidy_docs = TidyDocs::default();
@@ -75,7 +70,7 @@ fn files_are_tidy() {
         let extension = path.extension().unwrap_or_default().to_str().unwrap_or_default();
         match extension {
             "rs" => {
-                let text = sh.read_file(&path).unwrap();
+                let text = read_file(&path).unwrap();
                 check_todo(&path, &text);
                 check_dbg(&path, &text);
                 check_test_attrs(&path, &text);
@@ -85,7 +80,7 @@ fn files_are_tidy() {
                 tidy_marks.visit(&path, &text);
             }
             "toml" => {
-                let text = sh.read_file(&path).unwrap();
+                let text = read_file(&path).unwrap();
                 check_cargo_toml(&path, text);
             }
             _ => (),
@@ -144,10 +139,8 @@ fn check_cargo_toml(path: &Path, text: String) {
 
 #[test]
 fn check_merge_commits() {
-    let sh = &Shell::new().unwrap();
-
-    let bors = cmd!(sh, "git rev-list --merges --author 'bors\\[bot\\]' HEAD~19..").read().unwrap();
-    let all = cmd!(sh, "git rev-list --merges HEAD~19..").read().unwrap();
+    let bors = cmd!("git rev-list --merges --author 'bors\\[bot\\]' HEAD~19..").read().unwrap();
+    let all = cmd!("git rev-list --merges HEAD~19..").read().unwrap();
     if bors != all {
         panic!(
             "
@@ -220,8 +213,6 @@ See https://github.com/rust-lang/rust-clippy/issues/5537 for discussion.
 
 #[test]
 fn check_licenses() {
-    let sh = &Shell::new().unwrap();
-
     let expected = "
 0BSD OR MIT OR Apache-2.0
 Apache-2.0
@@ -244,7 +235,7 @@ Zlib OR Apache-2.0 OR MIT
     .filter(|it| !it.is_empty())
     .collect::<Vec<_>>();
 
-    let meta = cmd!(sh, "cargo metadata --format-version 1").read().unwrap();
+    let meta = cmd!("cargo metadata --format-version 1").read().unwrap();
     let mut licenses = meta
         .split(|c| c == ',' || c == '{' || c == '}')
         .filter(|it| it.contains(r#""license""#))