Repository: rust-analyzer/rust-analyzer
Pull request #5614 (2020-07-30 20:20:10)

Use split_once polyfill


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_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 1bc5d4832d..bd50056c68 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_delim;
+use stdx::split_once;
 
 #[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_delim(s, '=') {
+        let res = match split_once(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 741a2a951c..f16e35d86b 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::{BenchWhat, Position, Verbosity};
+use rust_analyzer::cli::{AnalysisStatsCmd, BenchCmd, BenchWhat, Position, Verbosity};
 use vfs::AbsPathBuf;
 
 pub(crate) struct Args {
@@ -24,23 +24,8 @@ pub(crate) enum Command {
     Highlight {
         rainbow: bool,
     },
-    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,
-    },
+    AnalysisStats(AnalysisStatsCmd),
+    Bench(BenchCmd),
     Diagnostics {
         path: PathBuf,
         load_output_dirs: bool,
@@ -199,7 +184,7 @@ ARGS:
                     trailing.pop().unwrap().into()
                 };
 
-                Command::Stats {
+                Command::AnalysisStats(AnalysisStatsCmd {
                     randomize,
                     parallel,
                     memory_usage,
@@ -208,7 +193,7 @@ ARGS:
                     path,
                     load_output_dirs,
                     with_proc_macro,
-                }
+                })
             }
             "analysis-bench" => {
                 if matches.contains(["-h", "--help"]) {
@@ -256,7 +241,13 @@ 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 { memory_usage, path, what, load_output_dirs, with_proc_macro }
+                Command::Bench(BenchCmd {
+                    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 a473c91651..ff8234495f 100644
--- a/crates/rust-analyzer/src/bin/main.rs
+++ b/crates/rust-analyzer/src/bin/main.rs
@@ -33,36 +33,8 @@ 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::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::AnalysisStats(cmd) => cmd.run(args.verbosity)?,
+        args::Command::Bench(cmd) => cmd.run(args.verbosity)?,
         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 a9b9c8923b..1034d11bd8 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::{analysis_bench, BenchWhat, Position};
-pub use analysis_stats::analysis_stats;
+pub use analysis_bench::{BenchCmd, BenchWhat, Position};
+pub use analysis_stats::AnalysisStatsCmd;
 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 076184ad6e..c54ee5f4de 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::Path, str::FromStr, sync::Arc, time::Instant};
+use std::{env, path::PathBuf, str::FromStr, sync::Arc, time::Instant};
 
-use anyhow::{format_err, Result};
+use anyhow::{bail, format_err, Result};
 use ra_db::{
     salsa::{Database, Durability},
     FileId,
@@ -15,6 +15,14 @@ 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),
@@ -30,85 +38,80 @@ pub struct Position {
 impl FromStr for Position {
     type Err = anyhow::Error;
     fn from_str(s: &str) -> Result<Self> {
-        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()? })
+        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),
+        }
     }
 }
 
-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..]))
-}
+impl BenchCmd {
+    pub fn run(self, verbosity: Verbosity) -> Result<()> {
+        ra_prof::init();
 
-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 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))?
         };
-        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(..));
 
-            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();
+        match &self.what {
+            BenchWhat::Highlight { .. } => {
                 let res = do_work(&mut host, file_id, |analysis| {
-                    analysis.completions(&options, file_position)
+                    analysis.diagnostics(file_id, true).unwrap();
+                    analysis.highlight_as_html(file_id, false).unwrap()
                 });
                 if verbosity.is_verbose() {
-                    println!("\n{:#?}", res);
+                    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);
+            }
+            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);
+                    }
                 }
             }
         }
-    }
 
-    if memory_usage {
-        print_memory_usage(host, vfs);
-    }
+        if self.memory_usage {
+            print_memory_usage(host, vfs);
+        }
 
-    Ok(())
+        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 a270eb481c..721d41a58f 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::Path,
+    path::PathBuf,
     time::{SystemTime, UNIX_EPOCH},
 };
 
@@ -39,273 +39,280 @@ impl<DB: ParallelDatabase> Clone for Snap<salsa::Snapshot<DB>> {
     }
 }
 
-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)
-    };
+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,
+}
 
-    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());
+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)
+        };
 
-    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 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 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);
-        }
-    }
+        let mut analysis_sw = self.stop_watch();
+        let mut num_crates = 0;
+        let mut visited_modules = FxHashSet::default();
+        let mut visit_queue = Vec::new();
 
-    if randomize {
-        shuffle(&mut rng, &mut visit_queue);
-    }
+        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);
+            }
+        }
 
-    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));
+        if self.randomize {
+            shuffle(&mut rng, &mut visit_queue);
+        }
 
-            for decl in module.declarations(db) {
-                num_decls += 1;
-                if let ModuleDef::Function(f) = decl {
-                    funcs.push(f);
-                }
-            }
+        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 impl_def in module.impl_defs(db) {
-                for item in impl_def.items(db) {
+                for decl in module.declarations(db) {
                     num_decls += 1;
-                    if let AssocItem::Function(f) = item {
+                    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);
+                        }
+                    }
+                }
             }
         }
-    }
-    eprintln!("Total modules found: {}", visited_modules.len());
-    eprintln!("Total declarations: {}", num_decls);
-    eprintln!("Total functions: {}", funcs.len());
-    eprintln!("Item Collection: {}", analysis_sw.elapsed());
-
-    if randomize {
-        shuffle(&mut rng, &mut funcs);
-    }
+        eprintln!("Total modules found: {}", visited_modules.len());
+        eprintln!("Total declarations: {}", num_decls);
+        eprintln!("Total functions: {}", funcs.len());
+        eprintln!("Item Collection: {}", analysis_sw.elapsed());
 
-    let mut bar = match verbosity {
-        Verbosity::Quiet | Verbosity::Spammy => ProgressReport::hidden(),
-        _ if parallel => ProgressReport::hidden(),
-        _ => ProgressReport::new(funcs.len() as u64),
-    };
+        if self.randomize {
+            shuffle(&mut rng, &mut funcs);
+        }
 
-    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 bar = match verbosity {
+            Verbosity::Quiet | Verbosity::Spammy => ProgressReport::hidden(),
+            _ if self.parallel => ProgressReport::hidden(),
+            _ => ProgressReport::new(funcs.len() as u64),
+        };
 
-    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;
-            }
-        }
-        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 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 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;
+
+        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;
                 }
             }
-            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)
-                    ));
+            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 {
-                    bar.println(format!("unknown location: {}", ty.display(db)));
+                    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;
+                    }
                 }
-            }
-            if let Some(mismatch) = inference_result.type_mismatch_for_expr(expr_id) {
-                num_type_mismatches += 1;
-                if verbosity.is_verbose() {
+                if self.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 {
-                        // 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 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!(
-                            "{} {}:{}-{}:{}: Expected {}, got {}",
-                            path,
+                            "{}:{}-{}:{}: {}",
                             start.line + 1,
                             start.col_utf16,
                             end.line + 1,
                             end.col_utf16,
-                            mismatch.expected.display(db),
-                            mismatch.actual.display(db)
+                            ty.display(db)
                         ));
                     } else {
-                        bar.println(format!(
-                            "{}: Expected {}, got {}",
-                            name,
-                            mismatch.expected.display(db),
-                            mismatch.actual.display(db)
-                        ));
+                        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)
+                            ));
+                        }
                     }
                 }
             }
+            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);
         }
-        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, "#");
+        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!(
+            "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, "#");
+        eprintln!("Type mismatches: {}", num_type_mismatches);
+        report_metric("type mismatches", num_type_mismatches, "#");
 
-    eprintln!("Inference: {}", inference_sw.elapsed());
+        eprintln!("Inference: {}", inference_sw.elapsed());
 
-    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");
-    }
+        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");
+        }
 
-    if memory_usage {
-        print_memory_usage(host, vfs);
+        if self.memory_usage {
+            print_memory_usage(host, vfs);
+        }
+
+        Ok(())
     }
 
-    Ok(())
+    fn stop_watch(&self) -> StopWatch {
+        StopWatch::start().memory(self.memory_usage)
+    }
 }
 
 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 62ed6880b3..e73b3a2119 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_delim};
+use stdx::{format_to, split_once};
 
 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_delim(&params.id, ':').unwrap();
+    let (id_string, index) = split_once(&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 ea0e6b9499..b65875c96e 100644
--- a/crates/stdx/src/lib.rs
+++ b/crates/stdx/src/lib.rs
@@ -109,9 +109,18 @@ pub fn replace(buf: &mut String, from: char, to: &str) {
     *buf = buf.replace(from, to)
 }
 
-pub fn split_delim(haystack: &str, delim: char) -> Option<(&str, &str)> {
-    let idx = haystack.find(delim)?;
-    Some((&haystack[..idx], &haystack[idx + delim.len_utf8()..]))
+// 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 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 ed764046b6..e40b61a94a 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_delim, trim_indent};
+use stdx::{lines_with_ends, split_once, 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_delim(component, ':').unwrap();
+            let (key, value) = split_once(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_delim(entry, '=') {
+                        match split_once(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_delim(key, '=') {
+                        if let Some((k, v)) = split_once(key, '=') {
                             env.insert(k.into(), v.into());
                         }
                     }