Repository: rust-analyzer/rust-analyzer
Pull request #5615 (2020-07-30 20:58:04)

simplify


bors r+
🤖


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 234c314067..e63dcc530f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -125,9 +125,9 @@ checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
 
 [[package]]
 name = "chalk-derive"
-version = "0.19.0"
+version = "0.18.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "654c611946ba2629c5028cb7708687af975faf2c29d731824cb294c873df4697"
+checksum = "eea3a22f0c30b2504ac4ab58934dac0d00b92a4d7788df32795cabca24c3f929"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -137,9 +137,9 @@ dependencies = [
 
 [[package]]
 name = "chalk-ir"
-version = "0.19.0"
+version = "0.18.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0a5341fbc654ca886b73b804a36aebf0e621057ccc1a68e9815b5b39b3ac9ae8"
+checksum = "fb617b643e145e3b151502799e91a9625dd5daf1cf05dc2cb821bc75ae0c9cbd"
 dependencies = [
  "chalk-derive",
  "lazy_static",
@@ -147,9 +147,9 @@ dependencies = [
 
 [[package]]
 name = "chalk-recursive"
-version = "0.19.0"
+version = "0.18.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4484807b155b5a411e6135d330295f9ba5042e2920b8712c6574ca6ea91e9ee5"
+checksum = "d280565c8eefbf9b2bc615df49c7dfd971faad37774bf65734e626fd23864bd6"
 dependencies = [
  "chalk-derive",
  "chalk-ir",
@@ -160,9 +160,9 @@ dependencies = [
 
 [[package]]
 name = "chalk-solve"
-version = "0.19.0"
+version = "0.18.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "281f82facd2538997fbe52132b1941ed213d266748215c31d15f62a8664429ad"
+checksum = "be906fbca3f3077dce0e76d9864771d0f450c946af0d86b569fb9504148a065a"
 dependencies = [
  "chalk-derive",
  "chalk-ir",
@@ -607,9 +607,9 @@ dependencies = [
 
 [[package]]
 name = "lsp-types"
-version = "0.79.0"
+version = "0.78.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f1f86677fdbe8df5f88b99131b1424e50aad27bbe3e5900d221bc414bd72e9b"
+checksum = "d2e6cf68e3492cfa2035f0382c1da1b6ab045db0320feca505b86b4f13d66c27"
 dependencies = [
  "base64",
  "bitflags",
@@ -842,9 +842,9 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
 
 [[package]]
 name = "perf-event"
-version = "0.4.4"
+version = "0.4.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "76c42ba5d85a2f4472b99f475fb60cf336d9b4c85b1ea8bb300fef2e3c7c8f89"
+checksum = "0cb38a2f363560fb3cfcb47f28848b245a41c7e0d63e0b190918b712b6bf6803"
 dependencies = [
  "libc",
  "perf-event-open-sys",
@@ -1344,9 +1344,9 @@ dependencies = [
 
 [[package]]
 name = "rustc-ap-rustc_lexer"
-version = "671.0.0"
+version = "669.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22e1221f3bfa2943c942cf8da319ab2346887f8757778c29c7f1822cd27b521f"
+checksum = "456af5f09c006cf6c22c1a433ee0232c4bb74bdc6c647a010166a47c94ed2a63"
 dependencies = [
  "unicode-xid",
 ]
diff --git a/crates/ra_hir/src/source_analyzer.rs b/crates/ra_hir/src/source_analyzer.rs
index 8f438bba0e..f2e630ef1a 100644
--- a/crates/ra_hir/src/source_analyzer.rs
+++ b/crates/ra_hir/src/source_analyzer.rs
@@ -405,8 +405,7 @@ fn scope_for_offset(
             )
         })
         .map(|(expr_range, scope)| {
-            adjust(db, scopes, source_map, expr_range, offset.file_id, offset.value)
-                .unwrap_or(*scope)
+            adjust(db, scopes, source_map, expr_range, offset).unwrap_or(*scope)
         })
 }
 
@@ -417,8 +416,7 @@ fn adjust(
     scopes: &ExprScopes,
     source_map: &BodySourceMap,
     expr_range: TextRange,
-    file_id: HirFileId,
-    offset: TextSize,
+    offset: InFile<TextSize>,
 ) -> Option<ScopeId> {
     let child_scopes = scopes
         .scope_by_expr()
@@ -426,7 +424,7 @@ fn adjust(
         .filter_map(|(id, scope)| {
             let source = source_map.expr_syntax(*id).ok()?;
             // FIXME: correctly handle macro expansion
-            if source.file_id != file_id {
+            if source.file_id != offset.file_id {
                 return None;
             }
             let root = source.file_syntax(db.upcast());
@@ -434,7 +432,7 @@ fn adjust(
             Some((node.syntax().text_range(), scope))
         })
         .filter(|&(range, _)| {
-            range.start() <= offset && expr_range.contains_range(range) && range != expr_range
+            range.start() <= offset.value && expr_range.contains_range(range) && range != expr_range
         });
 
     child_scopes
diff --git a/crates/ra_hir_ty/Cargo.toml b/crates/ra_hir_ty/Cargo.toml
index 623ce261a0..7242e2cb6d 100644
--- a/crates/ra_hir_ty/Cargo.toml
+++ b/crates/ra_hir_ty/Cargo.toml
@@ -28,9 +28,9 @@ test_utils = { path = "../test_utils" }
 
 scoped-tls = "1"
 
-chalk-solve = { version = "0.19.0" }
-chalk-ir = { version = "0.19.0" }
-chalk-recursive = { version = "0.19.0" }
+chalk-solve = { version = "0.18.0" }
+chalk-ir = { version = "0.18.0" }
+chalk-recursive = { version = "0.18.0" }
 
 [dev-dependencies]
 expect = { path = "../expect" }
diff --git a/crates/ra_hir_ty/src/traits/chalk.rs b/crates/ra_hir_ty/src/traits/chalk.rs
index 1c70653644..5298dbecf9 100644
--- a/crates/ra_hir_ty/src/traits/chalk.rs
+++ b/crates/ra_hir_ty/src/traits/chalk.rs
@@ -183,7 +183,6 @@ impl<'a> chalk_solve::RustIrDatabase<Interner> for ChalkContext<'a> {
                     .collect(),
                 1,
             ),
-            where_clauses: make_binders(vec![], 0),
         };
         let num_vars = datas.num_binders;
         Arc::new(OpaqueTyDatum { opaque_ty_id: id, bound: make_binders(bound, num_vars) })
@@ -194,6 +193,15 @@ impl<'a> chalk_solve::RustIrDatabase<Interner> for ChalkContext<'a> {
         Ty::Unknown.to_chalk(self.db)
     }
 
+    fn force_impl_for(
+        &self,
+        _well_known: rust_ir::WellKnownTrait,
+        _ty: &chalk_ir::TyData<Interner>,
+    ) -> Option<bool> {
+        // this method is mostly for rustc
+        None
+    }
+
     fn is_object_safe(&self, _trait_id: chalk_ir::TraitId<Interner>) -> bool {
         // FIXME: implement actual object safety
         true
@@ -539,13 +547,8 @@ pub(crate) fn fn_def_datum_query(
         ),
         where_clauses,
     };
-    let datum = FnDefDatum {
-        id: fn_def_id,
-        abi: (),
-        safety: chalk_ir::Safety::Safe,
-        variadic: sig.value.is_varargs,
-        binders: make_binders(bound, sig.num_binders),
-    };
+    let datum =
+        FnDefDatum { id: fn_def_id, binders: make_binders(bound, sig.num_binders), abi: () };
     Arc::new(datum)
 }
 
diff --git a/crates/ra_hir_ty/src/traits/chalk/mapping.rs b/crates/ra_hir_ty/src/traits/chalk/mapping.rs
index b3e92993d2..09d8347caa 100644
--- a/crates/ra_hir_ty/src/traits/chalk/mapping.rs
+++ b/crates/ra_hir_ty/src/traits/chalk/mapping.rs
@@ -30,16 +30,11 @@ impl ToChalk for Ty {
             Ty::Apply(apply_ty) => match apply_ty.ctor {
                 TypeCtor::Ref(m) => ref_to_chalk(db, m, apply_ty.parameters),
                 TypeCtor::Array => array_to_chalk(db, apply_ty.parameters),
-                TypeCtor::FnPtr { num_args: _, is_varargs } => {
+                TypeCtor::FnPtr { num_args: _, is_varargs: _ } => {
+                    // FIXME: handle is_varargs
                     let substitution = apply_ty.parameters.to_chalk(db).shifted_in(&Interner);
-                    chalk_ir::TyData::Function(chalk_ir::FnPointer {
-                        num_binders: 0,
-                        abi: (),
-                        safety: chalk_ir::Safety::Safe,
-                        variadic: is_varargs,
-                        substitution,
-                    })
-                    .intern(&Interner)
+                    chalk_ir::TyData::Function(chalk_ir::Fn { num_binders: 0, substitution })
+                        .intern(&Interner)
                 }
                 _ => {
                     let name = apply_ty.ctor.to_chalk(db);
@@ -123,12 +118,7 @@ impl ToChalk for Ty {
                 let parameters = from_chalk(db, opaque_ty.substitution);
                 Ty::Opaque(OpaqueTy { opaque_ty_id: impl_trait_id, parameters })
             }
-            chalk_ir::TyData::Function(chalk_ir::FnPointer {
-                num_binders,
-                variadic,
-                substitution,
-                ..
-            }) => {
+            chalk_ir::TyData::Function(chalk_ir::Fn { num_binders, substitution }) => {
                 assert_eq!(num_binders, 0);
                 let parameters: Substs = from_chalk(
                     db,
@@ -137,7 +127,7 @@ impl ToChalk for Ty {
                 Ty::Apply(ApplicationTy {
                     ctor: TypeCtor::FnPtr {
                         num_args: (parameters.len() - 1) as u16,
-                        is_varargs: variadic,
+                        is_varargs: false,
                     },
                     parameters,
                 })
diff --git a/crates/ra_project_model/src/cfg_flag.rs b/crates/ra_project_model/src/cfg_flag.rs
index bd50056c68..1bc5d4832d 100644
--- a/crates/ra_project_model/src/cfg_flag.rs
+++ b/crates/ra_project_model/src/cfg_flag.rs
@@ -4,7 +4,7 @@
 use std::str::FromStr;
 
 use ra_cfg::CfgOptions;
-use stdx::split_once;
+use stdx::split_delim;
 
 #[derive(Clone, Eq, PartialEq, Debug)]
 pub enum CfgFlag {
@@ -15,7 +15,7 @@ pub enum CfgFlag {
 impl FromStr for CfgFlag {
     type Err = String;
     fn from_str(s: &str) -> Result<Self, Self::Err> {
-        let res = match split_once(s, '=') {
+        let res = match split_delim(s, '=') {
             Some((key, value)) => {
                 if !(value.starts_with('"') && value.ends_with('"')) {
                     return Err(format!("Invalid cfg ({:?}), value should be in quotes", s));
diff --git a/crates/ra_syntax/Cargo.toml b/crates/ra_syntax/Cargo.toml
index fc4d7aa048..670f04578a 100644
--- a/crates/ra_syntax/Cargo.toml
+++ b/crates/ra_syntax/Cargo.toml
@@ -13,7 +13,7 @@ doctest = false
 [dependencies]
 itertools = "0.9.0"
 rowan = "0.10.0"
-rustc_lexer = { version = "671.0.0", package = "rustc-ap-rustc_lexer" }
+rustc_lexer = { version = "669.0.0", package = "rustc-ap-rustc_lexer" }
 rustc-hash = "1.1.0"
 arrayvec = "0.5.1"
 once_cell = "1.3.1"
diff --git a/crates/ra_syntax/src/ast/expr_ext.rs b/crates/ra_syntax/src/ast/expr_ext.rs
index f5ba872233..365f942871 100644
--- a/crates/ra_syntax/src/ast/expr_ext.rs
+++ b/crates/ra_syntax/src/ast/expr_ext.rs
@@ -333,12 +333,13 @@ impl ast::Literal {
 
         match token.kind() {
             INT_NUMBER => {
-                // FYI: there was a bug here previously, thus the if statement below is necessary.
+                // FYI: there was a bug here previously, thus an if statement bellow is necessary.
                 // The lexer treats e.g. `1f64` as an integer literal. See
                 // https://github.com/rust-analyzer/rust-analyzer/issues/1592
                 // and the comments on the linked PR.
 
                 let text = token.text();
+
                 if let suffix @ Some(_) = Self::find_suffix(&text, &FLOAT_SUFFIXES) {
                     LiteralKind::FloatNumber { suffix }
                 } else {
diff --git a/crates/rust-analyzer/Cargo.toml b/crates/rust-analyzer/Cargo.toml
index 02c1371ac0..931fc61edd 100644
--- a/crates/rust-analyzer/Cargo.toml
+++ b/crates/rust-analyzer/Cargo.toml
@@ -20,7 +20,7 @@ env_logger = { version = "0.7.1", default-features = false }
 itertools = "0.9.0"
 jod-thread = "0.1.0"
 log = "0.4.8"
-lsp-types = { version = "0.79.0", features = ["proposed"] }
+lsp-types = { version = "0.78.0", features = ["proposed"] }
 parking_lot = "0.11.0"
 pico-args = "0.3.1"
 oorandom = "11.1.2"
@@ -42,7 +42,7 @@ ra_syntax = { path = "../ra_syntax" }
 ra_text_edit = { path = "../ra_text_edit" }
 vfs = { path = "../vfs" }
 vfs-notify = { path = "../vfs-notify" }
-ra_cfg = { path = "../ra_cfg" }
+ra_cfg = { path = "../ra_cfg"}
 ra_toolchain = { path = "../ra_toolchain" }
 
 # This should only be used in CLI
diff --git a/crates/rust-analyzer/src/bin/args.rs b/crates/rust-analyzer/src/bin/args.rs
index f16e35d86b..741a2a951c 100644
--- a/crates/rust-analyzer/src/bin/args.rs
+++ b/crates/rust-analyzer/src/bin/args.rs
@@ -8,7 +8,7 @@ use std::{env, fmt::Write, path::PathBuf};
 use anyhow::{bail, Result};
 use pico_args::Arguments;
 use ra_ssr::{SsrPattern, SsrRule};
-use rust_analyzer::cli::{AnalysisStatsCmd, BenchCmd, BenchWhat, Position, Verbosity};
+use rust_analyzer::cli::{BenchWhat, Position, Verbosity};
 use vfs::AbsPathBuf;
 
 pub(crate) struct Args {
@@ -24,8 +24,23 @@ pub(crate) enum Command {
     Highlight {
         rainbow: bool,
     },
-    AnalysisStats(AnalysisStatsCmd),
-    Bench(BenchCmd),
+    Stats {
+        randomize: bool,
+        parallel: bool,
+        memory_usage: bool,
+        only: Option<String>,
+        with_deps: bool,
+        path: PathBuf,
+        load_output_dirs: bool,
+        with_proc_macro: bool,
+    },
+    Bench {
+        memory_usage: bool,
+        path: PathBuf,
+        what: BenchWhat,
+        load_output_dirs: bool,
+        with_proc_macro: bool,
+    },
     Diagnostics {
         path: PathBuf,
         load_output_dirs: bool,
@@ -184,7 +199,7 @@ ARGS:
                     trailing.pop().unwrap().into()
                 };
 
-                Command::AnalysisStats(AnalysisStatsCmd {
+                Command::Stats {
                     randomize,
                     parallel,
                     memory_usage,
@@ -193,7 +208,7 @@ ARGS:
                     path,
                     load_output_dirs,
                     with_proc_macro,
-                })
+                }
             }
             "analysis-bench" => {
                 if matches.contains(["-h", "--help"]) {
@@ -241,13 +256,7 @@ ARGS:
                 let memory_usage = matches.contains("--memory-usage");
                 let load_output_dirs = matches.contains("--load-output-dirs");
                 let with_proc_macro = matches.contains("--with-proc-macro");
-                Command::Bench(BenchCmd {
-                    memory_usage,
-                    path,
-                    what,
-                    load_output_dirs,
-                    with_proc_macro,
-                })
+                Command::Bench { memory_usage, path, what, load_output_dirs, with_proc_macro }
             }
             "diagnostics" => {
                 if matches.contains(["-h", "--help"]) {
diff --git a/crates/rust-analyzer/src/bin/main.rs b/crates/rust-analyzer/src/bin/main.rs
index ff8234495f..a473c91651 100644
--- a/crates/rust-analyzer/src/bin/main.rs
+++ b/crates/rust-analyzer/src/bin/main.rs
@@ -33,8 +33,36 @@ fn main() -> Result<()> {
         args::Command::Parse { no_dump } => cli::parse(no_dump)?,
         args::Command::Symbols => cli::symbols()?,
         args::Command::Highlight { rainbow } => cli::highlight(rainbow)?,
-        args::Command::AnalysisStats(cmd) => cmd.run(args.verbosity)?,
-        args::Command::Bench(cmd) => cmd.run(args.verbosity)?,
+        args::Command::Stats {
+            randomize,
+            parallel,
+            memory_usage,
+            only,
+            with_deps,
+            path,
+            load_output_dirs,
+            with_proc_macro,
+        } => cli::analysis_stats(
+            args.verbosity,
+            memory_usage,
+            path.as_ref(),
+            only.as_ref().map(String::as_ref),
+            with_deps,
+            randomize,
+            parallel,
+            load_output_dirs,
+            with_proc_macro,
+        )?,
+        args::Command::Bench { memory_usage, path, what, load_output_dirs, with_proc_macro } => {
+            cli::analysis_bench(
+                args.verbosity,
+                path.as_ref(),
+                what,
+                memory_usage,
+                load_output_dirs,
+                with_proc_macro,
+            )?
+        }
         args::Command::Diagnostics { path, load_output_dirs, with_proc_macro, all } => {
             cli::diagnostics(path.as_ref(), load_output_dirs, with_proc_macro, all)?
         }
diff --git a/crates/rust-analyzer/src/cli.rs b/crates/rust-analyzer/src/cli.rs
index 1034d11bd8..a9b9c8923b 100644
--- a/crates/rust-analyzer/src/cli.rs
+++ b/crates/rust-analyzer/src/cli.rs
@@ -14,8 +14,8 @@ use ra_ide::Analysis;
 use ra_prof::profile;
 use ra_syntax::{AstNode, SourceFile};
 
-pub use analysis_bench::{BenchCmd, BenchWhat, Position};
-pub use analysis_stats::AnalysisStatsCmd;
+pub use analysis_bench::{analysis_bench, BenchWhat, Position};
+pub use analysis_stats::analysis_stats;
 pub use diagnostics::diagnostics;
 pub use load_cargo::load_cargo;
 pub use ssr::{apply_ssr_rules, search_for_patterns};
diff --git a/crates/rust-analyzer/src/cli/analysis_bench.rs b/crates/rust-analyzer/src/cli/analysis_bench.rs
index c54ee5f4de..076184ad6e 100644
--- a/crates/rust-analyzer/src/cli/analysis_bench.rs
+++ b/crates/rust-analyzer/src/cli/analysis_bench.rs
@@ -1,8 +1,8 @@
 //! Benchmark operations like highlighting or goto definition.
 
-use std::{env, path::PathBuf, str::FromStr, sync::Arc, time::Instant};
+use std::{env, path::Path, str::FromStr, sync::Arc, time::Instant};
 
-use anyhow::{bail, format_err, Result};
+use anyhow::{format_err, Result};
 use ra_db::{
     salsa::{Database, Durability},
     FileId,
@@ -15,14 +15,6 @@ use crate::{
     print_memory_usage,
 };
 
-pub struct BenchCmd {
-    pub path: PathBuf,
-    pub what: BenchWhat,
-    pub memory_usage: bool,
-    pub load_output_dirs: bool,
-    pub with_proc_macro: bool,
-}
-
 pub enum BenchWhat {
     Highlight { path: AbsPathBuf },
     Complete(Position),
@@ -38,80 +30,85 @@ pub struct Position {
 impl FromStr for Position {
     type Err = anyhow::Error;
     fn from_str(s: &str) -> Result<Self> {
-        let mut split = s.rsplitn(3, ':');
-        match (split.next(), split.next(), split.next()) {
-            (Some(column), Some(line), Some(path)) => {
-                let path = env::current_dir().unwrap().join(path);
-                let path = AbsPathBuf::assert(path);
-                Ok(Position { path, line: line.parse()?, column: column.parse()? })
-            }
-            _ => bail!("position should be in file:line:column format: {:?}", s),
-        }
+        let (path_line, column) = rsplit_at_char(s, ':')?;
+        let (path, line) = rsplit_at_char(path_line, ':')?;
+        let path = env::current_dir().unwrap().join(path);
+        let path = AbsPathBuf::assert(path);
+        Ok(Position { path, line: line.parse()?, column: column.parse()? })
     }
 }
 
-impl BenchCmd {
-    pub fn run(self, verbosity: Verbosity) -> Result<()> {
-        ra_prof::init();
+fn rsplit_at_char(s: &str, c: char) -> Result<(&str, &str)> {
+    let idx = s.rfind(c).ok_or_else(|| format_err!("no `{}` in {}", c, s))?;
+    Ok((&s[..idx], &s[idx + 1..]))
+}
 
-        let start = Instant::now();
-        eprint!("loading: ");
-        let (mut host, vfs) = load_cargo(&self.path, self.load_output_dirs, self.with_proc_macro)?;
-        eprintln!("{:?}\n", start.elapsed());
-
-        let file_id = {
-            let path = match &self.what {
-                BenchWhat::Highlight { path } => path,
-                BenchWhat::Complete(pos) | BenchWhat::GotoDef(pos) => &pos.path,
-            };
-            let path = path.clone().into();
-            vfs.file_id(&path).ok_or_else(|| format_err!("Can't find {}", path))?
+pub fn analysis_bench(
+    verbosity: Verbosity,
+    path: &Path,
+    what: BenchWhat,
+    memory_usage: bool,
+    load_output_dirs: bool,
+    with_proc_macro: bool,
+) -> Result<()> {
+    ra_prof::init();
+
+    let start = Instant::now();
+    eprint!("loading: ");
+    let (mut host, vfs) = load_cargo(path, load_output_dirs, with_proc_macro)?;
+    eprintln!("{:?}\n", start.elapsed());
+
+    let file_id = {
+        let path = match &what {
+            BenchWhat::Highlight { path } => path,
+            BenchWhat::Complete(pos) | BenchWhat::GotoDef(pos) => &pos.path,
         };
+        let path = path.clone().into();
+        vfs.file_id(&path).ok_or_else(|| format_err!("Can't find {}", path))?
+    };
+
+    match &what {
+        BenchWhat::Highlight { .. } => {
+            let res = do_work(&mut host, file_id, |analysis| {
+                analysis.diagnostics(file_id, true).unwrap();
+                analysis.highlight_as_html(file_id, false).unwrap()
+            });
+            if verbosity.is_verbose() {
+                println!("\n{}", res);
+            }
+        }
+        BenchWhat::Complete(pos) | BenchWhat::GotoDef(pos) => {
+            let is_completion = matches!(what, BenchWhat::Complete(..));
 
-        match &self.what {
-            BenchWhat::Highlight { .. } => {
+            let offset = host
+                .analysis()
+                .file_line_index(file_id)?
+                .offset(LineCol { line: pos.line - 1, col_utf16: pos.column });
+            let file_position = FilePosition { file_id, offset };
+
+            if is_completion {
+                let options = CompletionConfig::default();
                 let res = do_work(&mut host, file_id, |analysis| {
-                    analysis.diagnostics(file_id, true).unwrap();
-                    analysis.highlight_as_html(file_id, false).unwrap()
+                    analysis.completions(&options, file_position)
                 });
                 if verbosity.is_verbose() {
-                    println!("\n{}", res);
+                    println!("\n{:#?}", res);
                 }
-            }
-            BenchWhat::Complete(pos) | BenchWhat::GotoDef(pos) => {
-                let is_completion = matches!(self.what, BenchWhat::Complete(..));
-
-                let offset = host
-                    .analysis()
-                    .file_line_index(file_id)?
-                    .offset(LineCol { line: pos.line - 1, col_utf16: pos.column });
-                let file_position = FilePosition { file_id, offset };
-
-                if is_completion {
-                    let options = CompletionConfig::default();
-                    let res = do_work(&mut host, file_id, |analysis| {
-                        analysis.completions(&options, file_position)
-                    });
-                    if verbosity.is_verbose() {
-                        println!("\n{:#?}", res);
-                    }
-                } else {
-                    let res = do_work(&mut host, file_id, |analysis| {
-                        analysis.goto_definition(file_position)
-                    });
-                    if verbosity.is_verbose() {
-                        println!("\n{:#?}", res);
-                    }
+            } else {
+                let res =
+                    do_work(&mut host, file_id, |analysis| analysis.goto_definition(file_position));
+                if verbosity.is_verbose() {
+                    println!("\n{:#?}", res);
                 }
             }
         }
+    }
 
-        if self.memory_usage {
-            print_memory_usage(host, vfs);
-        }
-
-        Ok(())
+    if memory_usage {
+        print_memory_usage(host, vfs);
     }
+
+    Ok(())
 }
 
 fn do_work<F: Fn(&Analysis) -> T, T>(host: &mut AnalysisHost, file_id: FileId, work: F) -> T {
diff --git a/crates/rust-analyzer/src/cli/analysis_stats.rs b/crates/rust-analyzer/src/cli/analysis_stats.rs
index 721d41a58f..a270eb481c 100644
--- a/crates/rust-analyzer/src/cli/analysis_stats.rs
+++ b/crates/rust-analyzer/src/cli/analysis_stats.rs
@@ -2,7 +2,7 @@
 //! errors.
 
 use std::{
-    path::PathBuf,
+    path::Path,
     time::{SystemTime, UNIX_EPOCH},
 };
 
@@ -39,280 +39,273 @@ impl<DB: ParallelDatabase> Clone for Snap<salsa::Snapshot<DB>> {
     }
 }
 
-pub struct AnalysisStatsCmd {
-    pub randomize: bool,
-    pub parallel: bool,
-    pub memory_usage: bool,
-    pub only: Option<String>,
-    pub with_deps: bool,
-    pub path: PathBuf,
-    pub load_output_dirs: bool,
-    pub with_proc_macro: bool,
-}
-
-impl AnalysisStatsCmd {
-    pub fn run(self, verbosity: Verbosity) -> Result<()> {
-        let mut rng = {
-            let seed = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() as u64;
-            Rand32::new(seed)
-        };
+pub fn analysis_stats(
+    verbosity: Verbosity,
+    memory_usage: bool,
+    path: &Path,
+    only: Option<&str>,
+    with_deps: bool,
+    randomize: bool,
+    parallel: bool,
+    load_output_dirs: bool,
+    with_proc_macro: bool,
+) -> Result<()> {
+    let mut rng = {
+        let seed = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() as u64;
+        Rand32::new(seed)
+    };
 
-        let mut db_load_sw = self.stop_watch();
-        let (host, vfs) = load_cargo(&self.path, self.load_output_dirs, self.with_proc_macro)?;
-        let db = host.raw_database();
-        eprintln!("Database loaded {}", db_load_sw.elapsed());
+    let mut db_load_sw = StopWatch::start().memory(memory_usage);
+    let (host, vfs) = load_cargo(path, load_output_dirs, with_proc_macro)?;
+    let db = host.raw_database();
+    eprintln!("Database loaded {}", db_load_sw.elapsed());
 
-        let mut analysis_sw = self.stop_watch();
-        let mut num_crates = 0;
-        let mut visited_modules = FxHashSet::default();
-        let mut visit_queue = Vec::new();
+    let mut analysis_sw = StopWatch::start().memory(memory_usage);
+    let mut num_crates = 0;
+    let mut visited_modules = FxHashSet::default();
+    let mut visit_queue = Vec::new();
 
-        let mut krates = Crate::all(db);
-        if self.randomize {
-            shuffle(&mut rng, &mut krates);
-        }
-        for krate in krates {
-            let module = krate.root_module(db).expect("crate without root module");
-            let file_id = module.definition_source(db).file_id;
-            let file_id = file_id.original_file(db);
-            let source_root = db.file_source_root(file_id);
-            let source_root = db.source_root(source_root);
-            if !source_root.is_library || self.with_deps {
-                num_crates += 1;
-                visit_queue.push(module);
-            }
+    let mut krates = Crate::all(db);
+    if randomize {
+        shuffle(&mut rng, &mut krates);
+    }
+    for krate in krates {
+        let module = krate.root_module(db).expect("crate without root module");
+        let file_id = module.definition_source(db).file_id;
+        let file_id = file_id.original_file(db);
+        let source_root = db.file_source_root(file_id);
+        let source_root = db.source_root(source_root);
+        if !source_root.is_library || with_deps {
+            num_crates += 1;
+            visit_queue.push(module);
         }
+    }
 
-        if self.randomize {
-            shuffle(&mut rng, &mut visit_queue);
-        }
+    if randomize {
+        shuffle(&mut rng, &mut visit_queue);
+    }
 
-        eprintln!("Crates in this dir: {}", num_crates);
-        let mut num_decls = 0;
-        let mut funcs = Vec::new();
-        while let Some(module) = visit_queue.pop() {
-            if visited_modules.insert(module) {
-                visit_queue.extend(module.children(db));
+    eprintln!("Crates in this dir: {}", num_crates);
+    let mut num_decls = 0;
+    let mut funcs = Vec::new();
+    while let Some(module) = visit_queue.pop() {
+        if visited_modules.insert(module) {
+            visit_queue.extend(module.children(db));
 
-                for decl in module.declarations(db) {
-                    num_decls += 1;
-                    if let ModuleDef::Function(f) = decl {
-                        funcs.push(f);
-                    }
+            for decl in module.declarations(db) {
+                num_decls += 1;
+                if let ModuleDef::Function(f) = decl {
+                    funcs.push(f);
                 }
+            }
 
-                for impl_def in module.impl_defs(db) {
-                    for item in impl_def.items(db) {
-                        num_decls += 1;
-                        if let AssocItem::Function(f) = item {
-                            funcs.push(f);
-                        }
+            for impl_def in module.impl_defs(db) {
+                for item in impl_def.items(db) {
+                    num_decls += 1;
+                    if let AssocItem::Function(f) = item {
+                        funcs.push(f);
                     }
                 }
             }
         }
-        eprintln!("Total modules found: {}", visited_modules.len());
-        eprintln!("Total declarations: {}", num_decls);
-        eprintln!("Total functions: {}", funcs.len());
-        eprintln!("Item Collection: {}", analysis_sw.elapsed());
+    }
+    eprintln!("Total modules found: {}", visited_modules.len());
+    eprintln!("Total declarations: {}", num_decls);
+    eprintln!("Total functions: {}", funcs.len());
+    eprintln!("Item Collection: {}", analysis_sw.elapsed());
 
-        if self.randomize {
-            shuffle(&mut rng, &mut funcs);
-        }
+    if randomize {
+        shuffle(&mut rng, &mut funcs);
+    }
 
-        let mut bar = match verbosity {
-            Verbosity::Quiet | Verbosity::Spammy => ProgressReport::hidden(),
-            _ if self.parallel => ProgressReport::hidden(),
-            _ => ProgressReport::new(funcs.len() as u64),
-        };
+    let mut bar = match verbosity {
+        Verbosity::Quiet | Verbosity::Spammy => ProgressReport::hidden(),
+        _ if parallel => ProgressReport::hidden(),
+        _ => ProgressReport::new(funcs.len() as u64),
+    };
 
-        if self.parallel {
-            let mut inference_sw = self.stop_watch();
-            let snap = Snap(db.snapshot());
-            funcs
-                .par_iter()
-                .map_with(snap, |snap, &f| {
-                    let f_id = FunctionId::from(f);
-                    snap.0.body(f_id.into());
-                    snap.0.infer(f_id.into());
-                })
-                .count();
-            eprintln!("Parallel Inference: {}", inference_sw.elapsed());
-        }
+    if parallel {
+        let mut inference_sw = StopWatch::start().memory(memory_usage);
+        let snap = Snap(db.snapshot());
+        funcs
+            .par_iter()
+            .map_with(snap, |snap, &f| {
+                let f_id = FunctionId::from(f);
+                snap.0.body(f_id.into());
+                snap.0.infer(f_id.into());
+            })
+            .count();
+        eprintln!("Parallel Inference: {}", inference_sw.elapsed());
+    }
 
-        let mut inference_sw = self.stop_watch();
-        bar.tick();
-        let mut num_exprs = 0;
-        let mut num_exprs_unknown = 0;
-        let mut num_exprs_partially_unknown = 0;
-        let mut num_type_mismatches = 0;
-        for f in funcs {
-            let name = f.name(db);
-            let full_name = f
-                .module(db)
-                .path_to_root(db)
-                .into_iter()
-                .rev()
-                .filter_map(|it| it.name(db))
-                .chain(Some(f.name(db)))
-                .join("::");
-            if let Some(only_name) = self.only.as_deref() {
-                if name.to_string() != only_name && full_name != only_name {
-                    continue;
-                }
-            }
-            let mut msg = format!("processing: {}", full_name);
-            if verbosity.is_verbose() {
-                let src = f.source(db);
-                let original_file = src.file_id.original_file(db);
-                let path = vfs.file_path(original_file);
-                let syntax_range = src.value.syntax().text_range();
-                format_to!(msg, " ({} {:?})", path, syntax_range);
+    let mut inference_sw = StopWatch::start().memory(memory_usage);
+    bar.tick();
+    let mut num_exprs = 0;
+    let mut num_exprs_unknown = 0;
+    let mut num_exprs_partially_unknown = 0;
+    let mut num_type_mismatches = 0;
+    for f in funcs {
+        let name = f.name(db);
+        let full_name = f
+            .module(db)
+            .path_to_root(db)
+            .into_iter()
+            .rev()
+            .filter_map(|it| it.name(db))
+            .chain(Some(f.name(db)))
+            .join("::");
+        if let Some(only_name) = only {
+            if name.to_string() != only_name && full_name != only_name {
+                continue;
             }
-            if verbosity.is_spammy() {
-                bar.println(msg.to_string());
+        }
+        let mut msg = format!("processing: {}", full_name);
+        if verbosity.is_verbose() {
+            let src = f.source(db);
+            let original_file = src.file_id.original_file(db);
+            let path = vfs.file_path(original_file);
+            let syntax_range = src.value.syntax().text_range();
+            format_to!(msg, " ({} {:?})", path, syntax_range);
+        }
+        if verbosity.is_spammy() {
+            bar.println(msg.to_string());
+        }
+        bar.set_message(&msg);
+        let f_id = FunctionId::from(f);
+        let body = db.body(f_id.into());
+        let inference_result = db.infer(f_id.into());
+        let (previous_exprs, previous_unknown, previous_partially_unknown) =
+            (num_exprs, num_exprs_unknown, num_exprs_partially_unknown);
+        for (expr_id, _) in body.exprs.iter() {
+            let ty = &inference_result[expr_id];
+            num_exprs += 1;
+            if let Ty::Unknown = ty {
+                num_exprs_unknown += 1;
+            } else {
+                let mut is_partially_unknown = false;
+                ty.walk(&mut |ty| {
+                    if let Ty::Unknown = ty {
+                        is_partially_unknown = true;
+                    }
+                });
+                if is_partially_unknown {
+                    num_exprs_partially_unknown += 1;
+                }
             }
-            bar.set_message(&msg);
-            let f_id = FunctionId::from(f);
-            let body = db.body(f_id.into());
-            let inference_result = db.infer(f_id.into());
-            let (previous_exprs, previous_unknown, previous_partially_unknown) =
-                (num_exprs, num_exprs_unknown, num_exprs_partially_unknown);
-            for (expr_id, _) in body.exprs.iter() {
-                let ty = &inference_result[expr_id];
-                num_exprs += 1;
-                if let Ty::Unknown = ty {
-                    num_exprs_unknown += 1;
+            if only.is_some() && verbosity.is_spammy() {
+                // in super-verbose mode for just one function, we print every single expression
+                let (_, sm) = db.body_with_source_map(f_id.into());
+                let src = sm.expr_syntax(expr_id);
+                if let Ok(src) = src {
+                    let node = {
+                        let root = db.parse_or_expand(src.file_id).unwrap();
+                        src.value.to_node(&root)
+                    };
+                    let original_file = src.file_id.original_file(db);
+                    let line_index = host.analysis().file_line_index(original_file).unwrap();
+                    let text_range = node.syntax().text_range();
+                    let (start, end) = (
+                        line_index.line_col(text_range.start()),
+                        line_index.line_col(text_range.end()),
+                    );
+                    bar.println(format!(
+                        "{}:{}-{}:{}: {}",
+                        start.line + 1,
+                        start.col_utf16,
+                        end.line + 1,
+                        end.col_utf16,
+                        ty.display(db)
+                    ));
                 } else {
-                    let mut is_partially_unknown = false;
-                    ty.walk(&mut |ty| {
-                        if let Ty::Unknown = ty {
-                            is_partially_unknown = true;
-                        }
-                    });
-                    if is_partially_unknown {
-                        num_exprs_partially_unknown += 1;
-                    }
+                    bar.println(format!("unknown location: {}", ty.display(db)));
                 }
-                if self.only.is_some() && verbosity.is_spammy() {
-                    // in super-verbose mode for just one function, we print every single expression
+            }
+            if let Some(mismatch) = inference_result.type_mismatch_for_expr(expr_id) {
+                num_type_mismatches += 1;
+                if verbosity.is_verbose() {
                     let (_, sm) = db.body_with_source_map(f_id.into());
                     let src = sm.expr_syntax(expr_id);
                     if let Ok(src) = src {
-                        let node = {
-                            let root = db.parse_or_expand(src.file_id).unwrap();
-                            src.value.to_node(&root)
-                        };
-                        let original_file = src.file_id.original_file(db);
-                        let line_index = host.analysis().file_line_index(original_file).unwrap();
-                        let text_range = node.syntax().text_range();
+                        // FIXME: it might be nice to have a function (on Analysis?) that goes from Source<T> -> (LineCol, LineCol) directly
+                        // But also, we should just turn the type mismatches into diagnostics and provide these
+                        let root = db.parse_or_expand(src.file_id).unwrap();
+                        let node = src.map(|e| e.to_node(&root).syntax().clone());
+                        let original_range = original_range(db, node.as_ref());
+                        let path = vfs.file_path(original_range.file_id);
+                        let line_index =
+                            host.analysis().file_line_index(original_range.file_id).unwrap();
+                        let text_range = original_range.range;
                         let (start, end) = (
                             line_index.line_col(text_range.start()),
                             line_index.line_col(text_range.end()),
                         );
                         bar.println(format!(
-                            "{}:{}-{}:{}: {}",
+                            "{} {}:{}-{}:{}: Expected {}, got {}",
+                            path,
                             start.line + 1,
                             start.col_utf16,
                             end.line + 1,
                             end.col_utf16,
-                            ty.display(db)
+                            mismatch.expected.display(db),
+                            mismatch.actual.display(db)
                         ));
                     } else {
-                        bar.println(format!("unknown location: {}", ty.display(db)));
-                    }
-                }
-                if let Some(mismatch) = inference_result.type_mismatch_for_expr(expr_id) {
-                    num_type_mismatches += 1;
-                    if verbosity.is_verbose() {
-                        let (_, sm) = db.body_with_source_map(f_id.into());
-                        let src = sm.expr_syntax(expr_id);
-                        if let Ok(src) = src {
-                            // FIXME: it might be nice to have a function (on Analysis?) that goes from Source<T> -> (LineCol, LineCol) directly
-                            // But also, we should just turn the type mismatches into diagnostics and provide these
-                            let root = db.parse_or_expand(src.file_id).unwrap();
-                            let node = src.map(|e| e.to_node(&root).syntax().clone());
-                            let original_range = original_range(db, node.as_ref());
-                            let path = vfs.file_path(original_range.file_id);
-                            let line_index =
-                                host.analysis().file_line_index(original_range.file_id).unwrap();
-                            let text_range = original_range.range;
-                            let (start, end) = (
-                                line_index.line_col(text_range.start()),
-                                line_index.line_col(text_range.end()),
-                            );
-                            bar.println(format!(
-                                "{} {}:{}-{}:{}: Expected {}, got {}",
-                                path,
-                                start.line + 1,
-                                start.col_utf16,
-                                end.line + 1,
-                                end.col_utf16,
-                                mismatch.expected.display(db),
-                                mismatch.actual.display(db)
-                            ));
-                        } else {
-                            bar.println(format!(
-                                "{}: Expected {}, got {}",
-                                name,
-                                mismatch.expected.display(db),
-                                mismatch.actual.display(db)
-                            ));
-                        }
+                        bar.println(format!(
+                            "{}: Expected {}, got {}",
+                            name,
+                            mismatch.expected.display(db),
+                            mismatch.actual.display(db)
+                        ));
                     }
                 }
             }
-            if verbosity.is_spammy() {
-                bar.println(format!(
-                    "In {}: {} exprs, {} unknown, {} partial",
-                    full_name,
-                    num_exprs - previous_exprs,
-                    num_exprs_unknown - previous_unknown,
-                    num_exprs_partially_unknown - previous_partially_unknown
-                ));
-            }
-            bar.inc(1);
         }
-        bar.finish_and_clear();
-        eprintln!("Total expressions: {}", num_exprs);
-        eprintln!(
-            "Expressions of unknown type: {} ({}%)",
-            num_exprs_unknown,
-            if num_exprs > 0 { num_exprs_unknown * 100 / num_exprs } else { 100 }
-        );
-        report_metric("unknown type", num_exprs_unknown, "#");
-
-        eprintln!(
-            "Expressions of partially unknown type: {} ({}%)",
-            num_exprs_partially_unknown,
-            if num_exprs > 0 { num_exprs_partially_unknown * 100 / num_exprs } else { 100 }
-        );
-
-        eprintln!("Type mismatches: {}", num_type_mismatches);
-        report_metric("type mismatches", num_type_mismatches, "#");
+        if verbosity.is_spammy() {
+            bar.println(format!(
+                "In {}: {} exprs, {} unknown, {} partial",
+                full_name,
+                num_exprs - previous_exprs,
+                num_exprs_unknown - previous_unknown,
+                num_exprs_partially_unknown - previous_partially_unknown
+            ));
+        }
+        bar.inc(1);
+    }
+    bar.finish_and_clear();
+    eprintln!("Total expressions: {}", num_exprs);
+    eprintln!(
+        "Expressions of unknown type: {} ({}%)",
+        num_exprs_unknown,
+        if num_exprs > 0 { num_exprs_unknown * 100 / num_exprs } else { 100 }
+    );
+    report_metric("unknown type", num_exprs_unknown, "#");
 
-        eprintln!("Inference: {}", inference_sw.elapsed());
+    eprintln!(
+        "Expressions of partially unknown type: {} ({}%)",
+        num_exprs_partially_unknown,
+        if num_exprs > 0 { num_exprs_partially_unknown * 100 / num_exprs } else { 100 }
+    );
 
-        let total_span = analysis_sw.elapsed();
-        eprintln!("Total: {}", total_span);
-        report_metric("total time", total_span.time.as_millis() as u64, "ms");
-        if let Some(instructions) = total_span.instructions {
-            report_metric("total instructions", instructions, "#instr");
-        }
-        if let Some(memory) = total_span.memory {
-            report_metric("total memory", memory.allocated.megabytes() as u64, "MB");
-        }
+    eprintln!("Type mismatches: {}", num_type_mismatches);
+    report_metric("type mismatches", num_type_mismatches, "#");
 
-        if self.memory_usage {
-            print_memory_usage(host, vfs);
-        }
+    eprintln!("Inference: {}", inference_sw.elapsed());
 
-        Ok(())
+    let total_span = analysis_sw.elapsed();
+    eprintln!("Total: {}", total_span);
+    report_metric("total time", total_span.time.as_millis() as u64, "ms");
+    if let Some(instructions) = total_span.instructions {
+        report_metric("total instructions", instructions, "#instr");
+    }
+    if let Some(memory) = total_span.memory {
+        report_metric("total memory", memory.allocated.megabytes() as u64, "MB");
     }
 
-    fn stop_watch(&self) -> StopWatch {
-        StopWatch::start().memory(self.memory_usage)
+    if memory_usage {
+        print_memory_usage(host, vfs);
     }
+
+    Ok(())
 }
 
 fn shuffle<T>(rng: &mut Rand32, slice: &mut [T]) {
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index e73b3a2119..62ed6880b3 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -26,7 +26,7 @@ use ra_project_model::TargetKind;
 use ra_syntax::{algo, ast, AstNode, SyntaxKind, TextRange, TextSize};
 use serde::{Deserialize, Serialize};
 use serde_json::to_value;
-use stdx::{format_to, split_once};
+use stdx::{format_to, split_delim};
 
 use crate::{
     cargo_target_spec::CargoTargetSpec,
@@ -865,7 +865,7 @@ pub(crate) fn handle_resolve_code_action(
         .map(|it| it.into_iter().filter_map(from_proto::assist_kind).collect());
 
     let assists = snap.analysis.resolved_assists(&snap.config.assist, frange)?;
-    let (id_string, index) = split_once(&params.id, ':').unwrap();
+    let (id_string, index) = split_delim(&params.id, ':').unwrap();
     let index = index.parse::<usize>().unwrap();
     let assist = &assists[index];
     assert!(assist.assist.id.0 == id_string);
diff --git a/crates/stdx/src/lib.rs b/crates/stdx/src/lib.rs
index b65875c96e..ea0e6b9499 100644
--- a/crates/stdx/src/lib.rs
+++ b/crates/stdx/src/lib.rs
@@ -109,18 +109,9 @@ pub fn replace(buf: &mut String, from: char, to: &str) {
     *buf = buf.replace(from, to)
 }
 
-// https://github.com/rust-lang/rust/issues/74773
-pub fn split_once(haystack: &str, delim: char) -> Option<(&str, &str)> {
-    let mut split = haystack.splitn(2, delim);
-    let prefix = split.next()?;
-    let suffix = split.next()?;
-    Some((prefix, suffix))
-}
-pub fn rsplit_once(haystack: &str, delim: char) -> Option<(&str, &str)> {
-    let mut split = haystack.rsplitn(2, delim);
-    let suffix = split.next()?;
-    let prefix = split.next()?;
-    Some((prefix, suffix))
+pub fn split_delim(haystack: &str, delim: char) -> Option<(&str, &str)> {
+    let idx = haystack.find(delim)?;
+    Some((&haystack[..idx], &haystack[idx + delim.len_utf8()..]))
 }
 
 pub fn trim_indent(mut text: &str) -> String {


Test patch:
diff --git a/crates/test_utils/src/fixture.rs b/crates/test_utils/src/fixture.rs
index e40b61a94a..ed764046b6 100644
--- a/crates/test_utils/src/fixture.rs
+++ b/crates/test_utils/src/fixture.rs
@@ -2,7 +2,7 @@
 //! rust-analyzer database from a single string.
 
 use rustc_hash::FxHashMap;
-use stdx::{lines_with_ends, split_once, trim_indent};
+use stdx::{lines_with_ends, split_delim, trim_indent};
 
 #[derive(Debug, Eq, PartialEq)]
 pub struct Fixture {
@@ -71,14 +71,14 @@ impl Fixture {
         let mut cfg_key_values = Vec::new();
         let mut env = FxHashMap::default();
         for component in components[1..].iter() {
-            let (key, value) = split_once(component, ':').unwrap();
+            let (key, value) = split_delim(component, ':').unwrap();
             match key {
                 "crate" => krate = Some(value.to_string()),
                 "deps" => deps = value.split(',').map(|it| it.to_string()).collect(),
                 "edition" => edition = Some(value.to_string()),
                 "cfg" => {
                     for entry in value.split(',') {
-                        match split_once(entry, '=') {
+                        match split_delim(entry, '=') {
                             Some((k, v)) => cfg_key_values.push((k.to_string(), v.to_string())),
                             None => cfg_atoms.push(entry.to_string()),
                         }
@@ -86,7 +86,7 @@ impl Fixture {
                 }
                 "env" => {
                     for key in value.split(',') {
-                        if let Some((k, v)) = split_once(key, '=') {
+                        if let Some((k, v)) = split_delim(key, '=') {
                             env.insert(k.into(), v.into());
                         }
                     }