Repository: rust-lang/rust-analyzer
Pull request #17063 (2024-04-14 06:21:13)

fix: Fix inlay hint resolution being broken
So, things broke because we now store a hash (u64) in the resolution payload, but javascript and hence JSON only support integers of up to 53 bits (anything beyond gets truncated in various ways) which caused almost all hashes to always differ when resolving them. This masks the hash to 53 bits to work around that.

Fixes https://github.com/rust-lang/rust-analyzer/issues/16962

Veykril: @bors r+
bors: :pushpin: Commit 8e2d23b0dbb38d194ff43bd42020717699a15c52 has been approved by `Veykril`

It is now in the [queue](https://bors.rust-lang.org/homu/queue/rust-analyzer) for this repository.

<!-- @bors r=Veykril 8e2d23b0dbb38d194ff43bd42020717699a15c52 -->
<!-- homu: {"type":"Approved","sha":"8e2d23b0dbb38d194ff43bd42020717699a15c52","approver":"Veykril","queue":"https://bors.rust-lang.org/homu/queue/rust-analyzer"} -->
bors: :hourglass: Testing commit 8e2d23b0dbb38d194ff43bd42020717699a15c52 with merge adab94559f031f806cd2cf3022c15c90e2ca60ee...
<!-- homu: {"type":"BuildStarted","head_sha":"8e2d23b0dbb38d194ff43bd42020717699a15c52","merge_sha":"adab94559f031f806cd2cf3022c15c90e2ca60ee"} -->

Patch:
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index a10345a706..08ad10c297 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -91,7 +91,7 @@ jobs:
         run: cargo build --quiet ${{ env.USE_SYSROOT_ABI }}
 
       - name: Test
-        if: matrix.os == 'ubuntu-latest' || matrix.os == 'windows-latest' || github.event_name == 'push'
+        if: matrix.os == 'ubuntu-latest' || github.event_name == 'push'
         run: cargo test ${{ env.USE_SYSROOT_ABI }} -- --nocapture --quiet
 
       - name: Switch to stable toolchain
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
index 11014338d7..3ad7a37ec1 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -95,7 +95,7 @@ jobs:
         run: sudo apt-get install gcc-arm-linux-gnueabihf
 
       - name: Dist
-        run: cargo xtask dist --client-patch-version ${{ github.run_number }}
+        run: cargo xtask dist --jemalloc --client-patch-version ${{ github.run_number }}
 
       - run: npm ci
         working-directory: editors/code
@@ -159,7 +159,7 @@ jobs:
           fetch-depth: ${{ env.FETCH_DEPTH }}
 
       - name: Dist
-        run: cargo xtask dist --client-patch-version ${{ github.run_number }}
+        run: cargo xtask dist --jemalloc --client-patch-version ${{ github.run_number }}
 
       - run: npm ci
         working-directory: editors/code
diff --git a/crates/flycheck/src/command.rs b/crates/flycheck/src/command.rs
index 8ba7018316..091146a001 100644
--- a/crates/flycheck/src/command.rs
+++ b/crates/flycheck/src/command.rs
@@ -4,13 +4,12 @@
 use std::{
     ffi::OsString,
     fmt, io,
-    marker::PhantomData,
     path::PathBuf,
     process::{ChildStderr, ChildStdout, Command, Stdio},
 };
 
 use command_group::{CommandGroup, GroupChild};
-use crossbeam_channel::Sender;
+use crossbeam_channel::{unbounded, Receiver, Sender};
 use stdx::process::streaming_output;
 
 /// Cargo output is structured as a one JSON per line. This trait abstracts parsing one line of
@@ -100,10 +99,10 @@ pub(crate) struct CommandHandle<T> {
     /// a read syscall dropping and therefore terminating the process is our best option.
     child: JodGroupChild,
     thread: stdx::thread::JoinHandle<io::Result<(bool, String)>>,
+    pub(crate) receiver: Receiver<T>,
     program: OsString,
     arguments: Vec<OsString>,
     current_dir: Option<PathBuf>,
-    _phantom: PhantomData<T>,
 }
 
 impl<T> fmt::Debug for CommandHandle<T> {
@@ -117,7 +116,7 @@ impl<T> fmt::Debug for CommandHandle<T> {
 }
 
 impl<T: ParseFromLine> CommandHandle<T> {
-    pub(crate) fn spawn(mut command: Command, sender: Sender<T>) -> std::io::Result<Self> {
+    pub(crate) fn spawn(mut command: Command) -> std::io::Result<Self> {
         command.stdout(Stdio::piped()).stderr(Stdio::piped()).stdin(Stdio::null());
         let mut child = command.group_spawn().map(JodGroupChild)?;
 
@@ -128,12 +127,13 @@ impl<T: ParseFromLine> CommandHandle<T> {
         let stdout = child.0.inner().stdout.take().unwrap();
         let stderr = child.0.inner().stderr.take().unwrap();
 
+        let (sender, receiver) = unbounded();
         let actor = CargoActor::<T>::new(sender, stdout, stderr);
         let thread = stdx::thread::Builder::new(stdx::thread::ThreadIntent::Worker)
             .name("CommandHandle".to_owned())
             .spawn(move || actor.run())
             .expect("failed to spawn thread");
-        Ok(CommandHandle { program, arguments, current_dir, child, thread, _phantom: PhantomData })
+        Ok(CommandHandle { program, arguments, current_dir, child, thread, receiver })
     }
 
     pub(crate) fn cancel(mut self) {
diff --git a/crates/flycheck/src/lib.rs b/crates/flycheck/src/lib.rs
index 5dfaaf7742..4ee86954ac 100644
--- a/crates/flycheck/src/lib.rs
+++ b/crates/flycheck/src/lib.rs
@@ -41,50 +41,19 @@ pub enum InvocationLocation {
     Workspace,
 }
 
-#[derive(Clone, Debug, PartialEq, Eq)]
-pub struct CargoOptions {
-    pub target_triples: Vec<String>,
-    pub all_targets: bool,
-    pub no_default_features: bool,
-    pub all_features: bool,
-    pub features: Vec<String>,
-    pub extra_args: Vec<String>,
-    pub extra_env: FxHashMap<String, String>,
-    pub target_dir: Option<Utf8PathBuf>,
-}
-
-impl CargoOptions {
-    fn apply_on_command(&self, cmd: &mut Command) {
-        for target in &self.target_triples {
-            cmd.args(["--target", target.as_str()]);
-        }
-        if self.all_targets {
-            cmd.arg("--all-targets");
-        }
-        if self.all_features {
-            cmd.arg("--all-features");
-        } else {
-            if self.no_default_features {
-                cmd.arg("--no-default-features");
-            }
-            if !self.features.is_empty() {
-                cmd.arg("--features");
-                cmd.arg(self.features.join(" "));
-            }
-        }
-        if let Some(target_dir) = &self.target_dir {
-            cmd.arg("--target-dir").arg(target_dir);
-        }
-        cmd.envs(&self.extra_env);
-    }
-}
-
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub enum FlycheckConfig {
     CargoCommand {
         command: String,
-        options: CargoOptions,
+        target_triples: Vec<String>,
+        all_targets: bool,
+        no_default_features: bool,
+        all_features: bool,
+        features: Vec<String>,
+        extra_args: Vec<String>,
+        extra_env: FxHashMap<String, String>,
         ansi_color_output: bool,
+        target_dir: Option<Utf8PathBuf>,
     },
     CustomCommand {
         command: String,
@@ -215,8 +184,6 @@ struct FlycheckActor {
     /// have to wrap sub-processes output handling in a thread and pass messages
     /// back over a channel.
     command_handle: Option<CommandHandle<CargoCheckMessage>>,
-    /// The receiver side of the channel mentioned above.
-    command_receiver: Option<Receiver<CargoCheckMessage>>,
 }
 
 enum Event {
@@ -242,7 +209,6 @@ impl FlycheckActor {
             sysroot_root,
             root: workspace_root,
             command_handle: None,
-            command_receiver: None,
         }
     }
 
@@ -251,13 +217,14 @@ impl FlycheckActor {
     }
 
     fn next_event(&self, inbox: &Receiver<StateChange>) -> Option<Event> {
+        let check_chan = self.command_handle.as_ref().map(|cargo| &cargo.receiver);
         if let Ok(msg) = inbox.try_recv() {
             // give restarts a preference so check outputs don't block a restart or stop
             return Some(Event::RequestStateChange(msg));
         }
         select! {
             recv(inbox) -> msg => msg.ok().map(Event::RequestStateChange),
-            recv(self.command_receiver.as_ref().unwrap_or(&never())) -> msg => Some(Event::CheckEvent(msg.ok())),
+            recv(check_chan.unwrap_or(&never())) -> msg => Some(Event::CheckEvent(msg.ok())),
         }
     }
 
@@ -286,12 +253,10 @@ impl FlycheckActor {
                     let formatted_command = format!("{:?}", command);
 
                     tracing::debug!(?command, "will restart flycheck");
-                    let (sender, receiver) = unbounded();
-                    match CommandHandle::spawn(command, sender) {
+                    match CommandHandle::spawn(command) {
                         Ok(command_handle) => {
-                            tracing::debug!(command = formatted_command, "did restart flycheck");
+                            tracing::debug!(command = formatted_command, "did  restart flycheck");
                             self.command_handle = Some(command_handle);
-                            self.command_receiver = Some(receiver);
                             self.report_progress(Progress::DidStart);
                         }
                         Err(error) => {
@@ -307,15 +272,13 @@ impl FlycheckActor {
 
                     // Watcher finished
                     let command_handle = self.command_handle.take().unwrap();
-                    self.command_receiver.take();
                     let formatted_handle = format!("{:?}", command_handle);
 
                     let res = command_handle.join();
-                    if let Err(error) = &res {
+                    if res.is_err() {
                         tracing::error!(
-                            "Flycheck failed to run the following command: {}, error={}",
-                            formatted_handle,
-                            error
+                            "Flycheck failed to run the following command: {}",
+                            formatted_handle
                         );
                     }
                     self.report_progress(Progress::DidFinish(res));
@@ -369,7 +332,18 @@ impl FlycheckActor {
         saved_file: Option<&AbsPath>,
     ) -> Option<Command> {
         let (mut cmd, args) = match &self.config {
-            FlycheckConfig::CargoCommand { command, options, ansi_color_output } => {
+            FlycheckConfig::CargoCommand {
+                command,
+                target_triples,
+                no_default_features,
+                all_targets,
+                all_features,
+                extra_args,
+                features,
+                extra_env,
+                ansi_color_output,
+                target_dir,
+            } => {
                 let mut cmd = Command::new(Tool::Cargo.path());
                 if let Some(sysroot_root) = &self.sysroot_root {
                     cmd.env("RUSTUP_TOOLCHAIN", AsRef::<std::path::Path>::as_ref(sysroot_root));
@@ -391,8 +365,28 @@ impl FlycheckActor {
                 cmd.arg("--manifest-path");
                 cmd.arg(self.root.join("Cargo.toml"));
 
-                options.apply_on_command(&mut cmd);
-                (cmd, options.extra_args.clone())
+                for target in target_triples {
+                    cmd.args(["--target", target.as_str()]);
+                }
+                if *all_targets {
+                    cmd.arg("--all-targets");
+                }
+                if *all_features {
+                    cmd.arg("--all-features");
+                } else {
+                    if *no_default_features {
+                        cmd.arg("--no-default-features");
+                    }
+                    if !features.is_empty() {
+                        cmd.arg("--features");
+                        cmd.arg(features.join(" "));
+                    }
+                }
+                if let Some(target_dir) = target_dir {
+                    cmd.arg("--target-dir").arg(target_dir);
+                }
+                cmd.envs(extra_env);
+                (cmd, extra_args.clone())
             }
             FlycheckConfig::CustomCommand {
                 command,
diff --git a/crates/ide-assists/src/handlers/generate_function.rs b/crates/ide-assists/src/handlers/generate_function.rs
index db94a21a6d..ff051fa870 100644
--- a/crates/ide-assists/src/handlers/generate_function.rs
+++ b/crates/ide-assists/src/handlers/generate_function.rs
@@ -175,7 +175,8 @@ fn add_func_to_accumulator(
         edit.edit_file(file);
 
         let target = function_builder.target.clone();
-        let func = function_builder.render(ctx.config.snippet_cap, edit);
+        let function_template = function_builder.render();
+        let func = function_template.to_ast(ctx.config.snippet_cap, edit);
 
         if let Some(name) = adt_name {
             let name = make::ty_path(make::ext::ident_path(&format!("{}", name.display(ctx.db()))));
@@ -204,6 +205,37 @@ fn get_adt_source(
     find_struct_impl(ctx, &adt_source, &[fn_name.to_owned()]).map(|impl_| (impl_, range.file_id))
 }
 
+struct FunctionTemplate {
+    fn_def: ast::Fn,
+    ret_type: Option<ast::RetType>,
+    should_focus_return_type: bool,
+    tail_expr: ast::Expr,
+}
+
+impl FunctionTemplate {
+    fn to_ast(&self, cap: Option<SnippetCap>, edit: &mut SourceChangeBuilder) -> ast::Fn {
+        let Self { fn_def, ret_type, should_focus_return_type, tail_expr } = self;
+
+        if let Some(cap) = cap {
+            if *should_focus_return_type {
+                // Focus the return type if there is one
+                match ret_type {
+                    Some(ret_type) => {
+                        edit.add_placeholder_snippet(cap, ret_type.clone());
+                    }
+                    None => {
+                        edit.add_placeholder_snippet(cap, tail_expr.clone());
+                    }
+                }
+            } else {
+                edit.add_placeholder_snippet(cap, tail_expr.clone());
+            }
+        }
+
+        fn_def.clone()
+    }
+}
+
 struct FunctionBuilder {
     target: GeneratedFunctionTarget,
     fn_name: ast::Name,
@@ -307,7 +339,7 @@ impl FunctionBuilder {
         })
     }
 
-    fn render(self, cap: Option<SnippetCap>, edit: &mut SourceChangeBuilder) -> ast::Fn {
+    fn render(self) -> FunctionTemplate {
         let placeholder_expr = make::ext::expr_todo();
         let fn_body = make::block_expr(vec![], Some(placeholder_expr));
         let visibility = match self.visibility {
@@ -329,31 +361,17 @@ impl FunctionBuilder {
         )
         .clone_for_update();
 
-        let ret_type = fn_def.ret_type();
-        // PANIC: we guarantee we always create a function body with a tail expr
-        let tail_expr = fn_def
-            .body()
-            .expect("generated function should have a body")
-            .tail_expr()
-            .expect("function body should have a tail expression");
-
-        if let Some(cap) = cap {
-            if self.should_focus_return_type {
-                // Focus the return type if there is one
-                match ret_type {
-                    Some(ret_type) => {
-                        edit.add_placeholder_snippet(cap, ret_type.clone());
-                    }
-                    None => {
-                        edit.add_placeholder_snippet(cap, tail_expr.clone());
-                    }
-                }
-            } else {
-                edit.add_placeholder_snippet(cap, tail_expr.clone());
-            }
+        FunctionTemplate {
+            ret_type: fn_def.ret_type(),
+            // PANIC: we guarantee we always create a function body with a tail expr
+            tail_expr: fn_def
+                .body()
+                .expect("generated function should have a body")
+                .tail_expr()
+                .expect("function body should have a tail expression"),
+            should_focus_return_type: self.should_focus_return_type,
+            fn_def,
         }
-
-        fn_def
     }
 }
 
diff --git a/crates/ide-completion/src/completions/postfix.rs b/crates/ide-completion/src/completions/postfix.rs
index c6e243b31a..ae061c8ce3 100644
--- a/crates/ide-completion/src/completions/postfix.rs
+++ b/crates/ide-completion/src/completions/postfix.rs
@@ -336,8 +336,7 @@ fn build_postfix_snippet_builder<'ctx>(
     ) -> impl Fn(&str, &str, &str) -> Builder + 'ctx {
         move |label, detail, snippet| {
             let edit = TextEdit::replace(delete_range, snippet.to_owned());
-            let mut item =
-                CompletionItem::new(CompletionItemKind::Snippet, ctx.source_range(), label);
+            let mut item = CompletionItem::new(CompletionItemKind::Snippet, delete_range, label);
             item.detail(detail).snippet_edit(cap, edit);
             let postfix_match = if ctx.original_token.text() == label {
                 cov_mark::hit!(postfix_exact_match_is_high_priority);
diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs
index dda38ce77e..15eecd1b54 100644
--- a/crates/ide/src/inlay_hints.rs
+++ b/crates/ide/src/inlay_hints.rs
@@ -1,6 +1,5 @@
 use std::{
     fmt::{self, Write},
-    hash::{BuildHasher, BuildHasherDefault},
     mem::take,
 };
 
@@ -9,7 +8,7 @@ use hir::{
     known, ClosureStyle, HasVisibility, HirDisplay, HirDisplayError, HirWrite, ModuleDef,
     ModuleDefId, Semantics,
 };
-use ide_db::{base_db::FileRange, famous_defs::FamousDefs, FxHasher, RootDatabase};
+use ide_db::{base_db::FileRange, famous_defs::FamousDefs, RootDatabase};
 use itertools::Itertools;
 use smallvec::{smallvec, SmallVec};
 use stdx::never;
@@ -495,6 +494,7 @@ pub(crate) fn inlay_hints_resolve(
     position: TextSize,
     hash: u64,
     config: &InlayHintsConfig,
+    hasher: impl Fn(&InlayHint) -> u64,
 ) -> Option<InlayHint> {
     let _p = tracing::span!(tracing::Level::INFO, "inlay_hints").entered();
     let sema = Semantics::new(db);
@@ -506,20 +506,16 @@ pub(crate) fn inlay_hints_resolve(
     let mut acc = Vec::new();
 
     let hints = |node| hints(&mut acc, &famous_defs, config, file_id, node);
-    match file.token_at_offset(position).left_biased() {
-        Some(token) => {
-            if let Some(parent_block) = token.parent_ancestors().find_map(ast::BlockExpr::cast) {
-                parent_block.syntax().descendants().for_each(hints)
-            } else if let Some(parent_item) = token.parent_ancestors().find_map(ast::Item::cast) {
-                parent_item.syntax().descendants().for_each(hints)
-            } else {
-                return None;
-            }
-        }
-        None => return None,
+    let token = file.token_at_offset(position).left_biased()?;
+    if let Some(parent_block) = token.parent_ancestors().find_map(ast::BlockExpr::cast) {
+        parent_block.syntax().descendants().for_each(hints)
+    } else if let Some(parent_item) = token.parent_ancestors().find_map(ast::Item::cast) {
+        parent_item.syntax().descendants().for_each(hints)
+    } else {
+        return None;
     }
 
-    acc.into_iter().find(|hint| BuildHasherDefault::<FxHasher>::default().hash_one(hint) == hash)
+    acc.into_iter().find(|hint| hasher(hint) == hash)
 }
 
 fn hints(
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index e13060e4d7..5f5af20ac5 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -58,6 +58,8 @@ mod view_item_tree;
 mod view_memory_layout;
 mod view_mir;
 
+use std::panic::UnwindSafe;
+
 use cfg::CfgOptions;
 use fetch_crates::CrateInfo;
 use hir::ChangeWithProcMacros;
@@ -428,8 +430,11 @@ impl Analysis {
         file_id: FileId,
         position: TextSize,
         hash: u64,
+        hasher: impl Fn(&InlayHint) -> u64 + Send + UnwindSafe,
     ) -> Cancellable<Option<InlayHint>> {
-        self.with_db(|db| inlay_hints::inlay_hints_resolve(db, file_id, position, hash, config))
+        self.with_db(|db| {
+            inlay_hints::inlay_hints_resolve(db, file_id, position, hash, config, hasher)
+        })
     }
 
     /// Returns the set of folding ranges.
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 5890af17eb..80f024184a 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -10,7 +10,7 @@
 use std::{fmt, iter, ops::Not};
 
 use cfg::{CfgAtom, CfgDiff};
-use flycheck::{CargoOptions, FlycheckConfig};
+use flycheck::FlycheckConfig;
 use ide::{
     AssistConfig, CallableSnippets, CompletionConfig, DiagnosticsConfig, ExprFillDefaultMode,
     HighlightConfig, HighlightRelatedConfig, HoverConfig, HoverDocFormat, InlayFieldsToResolve,
@@ -1364,22 +1364,6 @@ impl Config {
         self.data.check_workspace
     }
 
-    pub fn cargo_test_options(&self) -> CargoOptions {
-        CargoOptions {
-            target_triples: self.data.cargo_target.clone().into_iter().collect(),
-            all_targets: false,
-            no_default_features: self.data.cargo_noDefaultFeatures,
-            all_features: matches!(self.data.cargo_features, CargoFeaturesDef::All),
-            features: match self.data.cargo_features.clone() {
-                CargoFeaturesDef::All => vec![],
-                CargoFeaturesDef::Selected(it) => it,
-            },
-            extra_args: self.extra_args().clone(),
-            extra_env: self.extra_env().clone(),
-            target_dir: self.target_dir_from_config(),
-        }
-    }
-
     pub fn flycheck(&self) -> FlycheckConfig {
         match &self.data.check_overrideCommand {
             Some(args) if !args.is_empty() => {
@@ -1405,39 +1389,37 @@ impl Config {
             }
             Some(_) | None => FlycheckConfig::CargoCommand {
                 command: self.data.check_command.clone(),
-                options: CargoOptions {
-                    target_triples: self
-                        .data
-                        .check_targets
-                        .clone()
-                        .and_then(|targets| match &targets.0[..] {
-                            [] => None,
-                            targets => Some(targets.into()),
-                        })
-                        .unwrap_or_else(|| self.data.cargo_target.clone().into_iter().collect()),
-                    all_targets: self.data.check_allTargets.unwrap_or(self.data.cargo_allTargets),
-                    no_default_features: self
-                        .data
-                        .check_noDefaultFeatures
-                        .unwrap_or(self.data.cargo_noDefaultFeatures),
-                    all_features: matches!(
-                        self.data.check_features.as_ref().unwrap_or(&self.data.cargo_features),
-                        CargoFeaturesDef::All
-                    ),
-                    features: match self
-                        .data
-                        .check_features
-                        .clone()
-                        .unwrap_or_else(|| self.data.cargo_features.clone())
-                    {
-                        CargoFeaturesDef::All => vec![],
-                        CargoFeaturesDef::Selected(it) => it,
-                    },
-                    extra_args: self.check_extra_args(),
-                    extra_env: self.check_extra_env(),
-                    target_dir: self.target_dir_from_config(),
+                target_triples: self
+                    .data
+                    .check_targets
+                    .clone()
+                    .and_then(|targets| match &targets.0[..] {
+                        [] => None,
+                        targets => Some(targets.into()),
+                    })
+                    .unwrap_or_else(|| self.data.cargo_target.clone().into_iter().collect()),
+                all_targets: self.data.check_allTargets.unwrap_or(self.data.cargo_allTargets),
+                no_default_features: self
+                    .data
+                    .check_noDefaultFeatures
+                    .unwrap_or(self.data.cargo_noDefaultFeatures),
+                all_features: matches!(
+                    self.data.check_features.as_ref().unwrap_or(&self.data.cargo_features),
+                    CargoFeaturesDef::All
+                ),
+                features: match self
+                    .data
+                    .check_features
+                    .clone()
+                    .unwrap_or_else(|| self.data.cargo_features.clone())
+                {
+                    CargoFeaturesDef::All => vec![],
+                    CargoFeaturesDef::Selected(it) => it,
                 },
+                extra_args: self.check_extra_args(),
+                extra_env: self.check_extra_env(),
                 ansi_color_output: self.color_diagnostic_output(),
+                target_dir: self.target_dir_from_config(),
             },
         }
     }
@@ -2790,7 +2772,7 @@ mod tests {
             .unwrap();
         assert_eq!(config.data.cargo_targetDir, None);
         assert!(
-            matches!(config.flycheck(), FlycheckConfig::CargoCommand { options, .. } if options.target_dir.is_none())
+            matches!(config.flycheck(), FlycheckConfig::CargoCommand { target_dir, .. } if target_dir.is_none())
         );
     }
 
@@ -2809,7 +2791,7 @@ mod tests {
             .unwrap();
         assert_eq!(config.data.cargo_targetDir, Some(TargetDirectory::UseSubdirectory(true)));
         assert!(
-            matches!(config.flycheck(), FlycheckConfig::CargoCommand { options, .. } if options.target_dir == Some(Utf8PathBuf::from("target/rust-analyzer")))
+            matches!(config.flycheck(), FlycheckConfig::CargoCommand { target_dir, .. } if target_dir == Some(Utf8PathBuf::from("target/rust-analyzer")))
         );
     }
 
@@ -2831,7 +2813,7 @@ mod tests {
             Some(TargetDirectory::Directory(Utf8PathBuf::from("other_folder")))
         );
         assert!(
-            matches!(config.flycheck(), FlycheckConfig::CargoCommand { options, .. } if options.target_dir == Some(Utf8PathBuf::from("other_folder")))
+            matches!(config.flycheck(), FlycheckConfig::CargoCommand { target_dir, .. } if target_dir == Some(Utf8PathBuf::from("other_folder")))
         );
     }
 }
diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs
index a0101af309..8516ffa0df 100644
--- a/crates/rust-analyzer/src/global_state.rs
+++ b/crates/rust-analyzer/src/global_state.rs
@@ -86,10 +86,7 @@ pub(crate) struct GlobalState {
     pub(crate) last_flycheck_error: Option<String>,
 
     // Test explorer
-    pub(crate) test_run_session: Option<Vec<flycheck::CargoTestHandle>>,
-    pub(crate) test_run_sender: Sender<flycheck::CargoTestMessage>,
-    pub(crate) test_run_receiver: Receiver<flycheck::CargoTestMessage>,
-    pub(crate) test_run_remaining_jobs: usize,
+    pub(crate) test_run_session: Option<flycheck::CargoTestHandle>,
 
     // VFS
     pub(crate) loader: Handle<Box<dyn vfs::loader::Handle>, Receiver<vfs::loader::Message>>,
@@ -194,7 +191,6 @@ impl GlobalState {
             analysis_host.update_lru_capacities(capacities);
         }
         let (flycheck_sender, flycheck_receiver) = unbounded();
-        let (test_run_sender, test_run_receiver) = unbounded();
         let mut this = GlobalState {
             sender,
             req_queue: ReqQueue::default(),
@@ -223,9 +219,6 @@ impl GlobalState {
             last_flycheck_error: None,
 
             test_run_session: None,
-            test_run_sender,
-            test_run_receiver,
-            test_run_remaining_jobs: 0,
 
             vfs: Arc::new(RwLock::new((vfs::Vfs::default(), IntMap::default()))),
             vfs_config_version: 0,
diff --git a/crates/rust-analyzer/src/handlers/request.rs b/crates/rust-analyzer/src/handlers/request.rs
index e5c070bf77..a9e84de8a4 100644
--- a/crates/rust-analyzer/src/handlers/request.rs
+++ b/crates/rust-analyzer/src/handlers/request.rs
@@ -219,28 +219,14 @@ pub(crate) fn handle_run_test(
             .unwrap_or_default(),
         None => "".to_owned(),
     };
-    let test_path = if lca.is_empty() {
-        None
+    let handle = if lca.is_empty() {
+        flycheck::CargoTestHandle::new(None)
     } else if let Some((_, path)) = lca.split_once("::") {
-        Some(path)
+        flycheck::CargoTestHandle::new(Some(path))
     } else {
-        None
+        flycheck::CargoTestHandle::new(None)
     };
-    let mut handles = vec![];
-    for ws in &*state.workspaces {
-        if let ProjectWorkspace::Cargo { cargo, .. } = ws {
-            let handle = flycheck::CargoTestHandle::new(
-                test_path,
-                state.config.cargo_test_options(),
-                cargo.workspace_root(),
-                state.test_run_sender.clone(),
-            )?;
-            handles.push(handle);
-        }
-    }
-    // Each process send finished signal twice, once for stdout and once for stderr
-    state.test_run_remaining_jobs = 2 * handles.len();
-    state.test_run_session = Some(handles);
+    state.test_run_session = Some(handle?);
     Ok(())
 }
 
@@ -1522,11 +1508,18 @@ pub(crate) fn handle_inlay_hints_resolve(
         file_id,
         hint_position,
         resolve_data.hash,
+        |hint| {
+            std::hash::BuildHasher::hash_one(
+                &std::hash::BuildHasherDefault::<ide_db::FxHasher>::default(),
+                hint,
+            )
+            // json only supports numbers up to 2^53 - 1 as integers, so mask the rest
+            & ((1 << 53) - 1)
+        },
     )?;
 
-    let mut resolved_hints = resolve_hints
-        .into_iter()
-        .filter_map(|it| {
+    Ok(resolve_hints
+        .and_then(|it| {
             to_proto::inlay_hint(
                 &snap,
                 &forced_resolve_inlay_hints_config.fields_to_resolve,
@@ -1537,13 +1530,8 @@ pub(crate) fn handle_inlay_hints_resolve(
             .ok()
         })
         .filter(|hint| hint.position == original_hint.position)
-        .filter(|hint| hint.kind == original_hint.kind);
-    if let Some(resolved_hint) = resolved_hints.next() {
-        if resolved_hints.next().is_none() {
-            return Ok(resolved_hint);
-        }
-    }
-    Ok(original_hint)
+        .filter(|hint| hint.kind == original_hint.kind)
+        .unwrap_or(original_hint))
 }
 
 pub(crate) fn handle_call_hierarchy_prepare(
diff --git a/crates/rust-analyzer/src/lsp/ext.rs b/crates/rust-analyzer/src/lsp/ext.rs
index eac982f1b2..1ef49b5c11 100644
--- a/crates/rust-analyzer/src/lsp/ext.rs
+++ b/crates/rust-analyzer/src/lsp/ext.rs
@@ -463,13 +463,6 @@ pub struct TestInfo {
     pub runnable: Runnable,
 }
 
-#[derive(Serialize, Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-pub struct InlayHintsParams {
-    pub text_document: TextDocumentIdentifier,
-    pub range: Option<lsp_types::Range>,
-}
-
 pub enum Ssr {}
 
 impl Request for Ssr {
diff --git a/crates/rust-analyzer/src/lsp/to_proto.rs b/crates/rust-analyzer/src/lsp/to_proto.rs
index d8bb12528b..04a5486e94 100644
--- a/crates/rust-analyzer/src/lsp/to_proto.rs
+++ b/crates/rust-analyzer/src/lsp/to_proto.rs
@@ -453,6 +453,8 @@ pub(crate) fn inlay_hint(
             &std::hash::BuildHasherDefault::<FxHasher>::default(),
             &inlay_hint,
         )
+        // json only supports numbers up to 2^53 - 1 as integers, so mask the rest
+         & ((1 << 53) - 1)
     });
 
     let mut something_to_resolve = false;
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index ac270bc3c5..9459bd7c5d 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -7,7 +7,7 @@ use std::{
 };
 
 use always_assert::always;
-use crossbeam_channel::{select, Receiver};
+use crossbeam_channel::{never, select, Receiver};
 use ide_db::base_db::{SourceDatabase, SourceDatabaseExt, VfsPath};
 use lsp_server::{Connection, Notification, Request};
 use lsp_types::{notification::Notification as _, TextDocumentIdentifier};
@@ -220,7 +220,7 @@ impl GlobalState {
             recv(self.flycheck_receiver) -> task =>
                 Some(Event::Flycheck(task.unwrap())),
 
-            recv(self.test_run_receiver) -> task =>
+            recv(self.test_run_session.as_ref().map(|s| s.receiver()).unwrap_or(&never())) -> task =>
                 Some(Event::TestResult(task.unwrap())),
 
         }
@@ -337,7 +337,9 @@ impl GlobalState {
                         .entered();
                 self.handle_cargo_test_msg(message);
                 // Coalesce many test result event into a single loop turn
-                while let Ok(message) = self.test_run_receiver.try_recv() {
+                while let Some(message) =
+                    self.test_run_session.as_ref().and_then(|r| r.receiver().try_recv().ok())
+                {
                     self.handle_cargo_test_msg(message);
                 }
             }
@@ -790,11 +792,8 @@ impl GlobalState {
             }
             flycheck::CargoTestMessage::Suite => (),
             flycheck::CargoTestMessage::Finished => {
-                self.test_run_remaining_jobs = self.test_run_remaining_jobs.saturating_sub(1);
-                if self.test_run_remaining_jobs == 0 {
-                    self.send_notification::<lsp_ext::EndRunTest>(());
-                    self.test_run_session = None;
-                }
+                self.send_notification::<lsp_ext::EndRunTest>(());
+                self.test_run_session = None;
             }
             flycheck::CargoTestMessage::Custom { text } => {
                 self.send_notification::<lsp_ext::AppendOutputToRunTest>(text);
diff --git a/crates/vfs/src/file_set.rs b/crates/vfs/src/file_set.rs
index d7d283c3eb..7eeb10d544 100644
--- a/crates/vfs/src/file_set.rs
+++ b/crates/vfs/src/file_set.rs
@@ -132,10 +132,6 @@ impl FileSetConfig {
     ///
     /// `scratch_space` is used as a buffer and will be entirely replaced.
     fn classify(&self, path: &VfsPath, scratch_space: &mut Vec<u8>) -> usize {
-        // `path` is a file, but r-a only cares about the containing directory. We don't
-        // want `/foo/bar_baz.rs` to be attributed to source root directory `/foo/bar`.
-        let path = path.parent().unwrap_or_else(|| path.clone());
-
         scratch_space.clear();
         path.encode(scratch_space);
         let automaton = PrefixOf::new(scratch_space.as_slice());
diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md
index 939b1819c7..1b7534a549 100644
--- a/docs/dev/lsp-extensions.md
+++ b/docs/dev/lsp-extensions.md
@@ -1,5 +1,5 @@
 <!---
-lsp/ext.rs hash: 223f48a89a5126a0
+lsp/ext.rs hash: 4aacf4cca1c9ff5e
 
 If you need to change the above hash to make the test pass, please check if you
 need to adjust this doc as well and ping this issue:
@@ -444,7 +444,7 @@ interface DiscoverTestResults {
     // For each file which its uri is in this list, the response
     // contains all tests that are located in this file, and
     // client should remove old tests not included in the response.
-    scopeFile: lc.TextDocumentIdentifier[] | undefined;    
+    scopeFile: lc.TextDocumentIdentifier[] | undefined;
 }
 ```
 
diff --git a/docs/user/manual.adoc b/docs/user/manual.adoc
index 6521b6d8b3..8bc11fd481 100644
--- a/docs/user/manual.adoc
+++ b/docs/user/manual.adoc
@@ -210,10 +210,7 @@ While the sandbox can be disabled for some directories, `/usr/bin` will always b
 This prevents access to the system's C compiler, a system-wide installation of Rust, or any other libraries you might want to link to.
 Some compilers and libraries can be acquired as Flatpak SDKs, such as `org.freedesktop.Sdk.Extension.rust-stable` or `org.freedesktop.Sdk.Extension.llvm15`.
 
-If you use a Flatpak SDK for Rust, it must be in your `PATH`:
-
- * install the SDK extensions with `flatpak install org.freedesktop.Sdk.Extension.{llvm15,rust-stable}//23.08`
- * enable SDK extensions in the editor with the environment variable `FLATPAK_ENABLE_SDK_EXT=llvm15,rust-stable` (this can be done using flatseal or `flatpak override`)
+If you use a Flatpak SDK for Rust, there should be no extra steps necessary.
 
 If you want to use Flatpak in combination with `rustup`, the following steps might help:
 
diff --git a/editors/code/language-configuration.json b/editors/code/language-configuration.json
index 6619d0c85c..bdae0e6ba9 100644
--- a/editors/code/language-configuration.json
+++ b/editors/code/language-configuration.json
@@ -19,8 +19,7 @@
         { "open": "(", "close": ")" },
         { "open": "\"", "close": "\"", "notIn": ["string"] },
         { "open": "/*", "close": " */", "notIn": ["string"] },
-        { "open": "`", "close": "`", "notIn": ["string"] },
-        { "open": "```", "close": "```", "notIn": ["string"] }
+        { "open": "`", "close": "`", "notIn": ["string"] }
     ],
     "autoCloseBefore": ";:.,=}])> \n\t",
     "surroundingPairs": [
@@ -30,8 +29,7 @@
         ["<", ">"],
         ["\"", "\""],
         ["'", "'"],
-        ["`", "`"],
-        ["```", "```"]
+        ["`", "`"]
     ],
     "indentationRules": {
         "increaseIndentPattern": "^.*\\{[^}\"']*$|^.*\\([^\\)\"']*$",


Test patch:
diff --git a/crates/flycheck/src/test_runner.rs b/crates/flycheck/src/test_runner.rs
index c136dd1366..9f761c9ead 100644
--- a/crates/flycheck/src/test_runner.rs
+++ b/crates/flycheck/src/test_runner.rs
@@ -3,15 +3,11 @@
 
 use std::process::Command;
 
-use crossbeam_channel::Sender;
-use paths::AbsPath;
+use crossbeam_channel::Receiver;
 use serde::Deserialize;
 use toolchain::Tool;
 
-use crate::{
-    command::{CommandHandle, ParseFromLine},
-    CargoOptions,
-};
+use crate::command::{CommandHandle, ParseFromLine};
 
 #[derive(Debug, Deserialize)]
 #[serde(tag = "event", rename_all = "camelCase")]
@@ -55,34 +51,30 @@ impl ParseFromLine for CargoTestMessage {
 
 #[derive(Debug)]
 pub struct CargoTestHandle {
-    _handle: CommandHandle<CargoTestMessage>,
+    handle: CommandHandle<CargoTestMessage>,
 }
 
 // Example of a cargo test command:
 // cargo test --workspace --no-fail-fast -- module::func -Z unstable-options --format=json
 
 impl CargoTestHandle {
-    pub fn new(
-        path: Option<&str>,
-        options: CargoOptions,
-        root: &AbsPath,
-        sender: Sender<CargoTestMessage>,
-    ) -> std::io::Result<Self> {
+    pub fn new(path: Option<&str>) -> std::io::Result<Self> {
         let mut cmd = Command::new(Tool::Cargo.path());
         cmd.env("RUSTC_BOOTSTRAP", "1");
         cmd.arg("test");
         cmd.arg("--workspace");
         // --no-fail-fast is needed to ensure that all requested tests will run
         cmd.arg("--no-fail-fast");
-        cmd.arg("--manifest-path");
-        cmd.arg(root.join("Cargo.toml"));
-        options.apply_on_command(&mut cmd);
         cmd.arg("--");
         if let Some(path) = path {
             cmd.arg(path);
         }
         cmd.args(["-Z", "unstable-options"]);
         cmd.arg("--format=json");
-        Ok(Self { _handle: CommandHandle::spawn(cmd, sender)? })
+        Ok(Self { handle: CommandHandle::spawn(cmd)? })
+    }
+
+    pub fn receiver(&self) -> &Receiver<CargoTestMessage> {
+        &self.handle.receiver
     }
 }
diff --git a/crates/rust-analyzer/tests/slow-tests/main.rs b/crates/rust-analyzer/tests/slow-tests/main.rs
index 439b006977..8c982b4687 100644
--- a/crates/rust-analyzer/tests/slow-tests/main.rs
+++ b/crates/rust-analyzer/tests/slow-tests/main.rs
@@ -23,12 +23,12 @@ use lsp_types::{
     notification::DidOpenTextDocument,
     request::{
         CodeActionRequest, Completion, Formatting, GotoTypeDefinition, HoverRequest,
-        WillRenameFiles, WorkspaceSymbolRequest,
+        InlayHintRequest, InlayHintResolveRequest, WillRenameFiles, WorkspaceSymbolRequest,
     },
     CodeActionContext, CodeActionParams, CompletionParams, DidOpenTextDocumentParams,
     DocumentFormattingParams, FileRename, FormattingOptions, GotoDefinitionParams, HoverParams,
-    PartialResultParams, Position, Range, RenameFilesParams, TextDocumentItem,
-    TextDocumentPositionParams, WorkDoneProgressParams,
+    InlayHint, InlayHintLabel, InlayHintParams, PartialResultParams, Position, Range,
+    RenameFilesParams, TextDocumentItem, TextDocumentPositionParams, WorkDoneProgressParams,
 };
 use rust_analyzer::lsp::ext::{OnEnter, Runnables, RunnablesParams, UnindexedProject};
 use serde_json::json;
@@ -75,6 +75,48 @@ use std::collections::Spam;
     assert!(res.to_string().contains("HashMap"));
 }
 
+#[test]
+fn resolves_inlay_hints() {
+    if skip_slow_tests() {
+        return;
+    }
+
+    let server = Project::with_fixture(
+        r#"
+//- /Cargo.toml
+[package]
+name = "foo"
+version = "0.0.0"
+
+//- /src/lib.rs
+struct Foo;
+fn f() {
+    let x = Foo;
+}
+"#,
+    )
+    .server()
+    .wait_until_workspace_is_loaded();
+
+    let res = server.send_request::<InlayHintRequest>(InlayHintParams {
+        range: Range::new(Position::new(0, 0), Position::new(3, 1)),
+        text_document: server.doc_id("src/lib.rs"),
+        work_done_progress_params: WorkDoneProgressParams::default(),
+    });
+    let mut hints = serde_json::from_value::<Option<Vec<InlayHint>>>(res).unwrap().unwrap();
+    let hint = hints.pop().unwrap();
+    assert!(hint.data.is_some());
+    assert!(
+        matches!(&hint.label, InlayHintLabel::LabelParts(parts) if parts[1].location.is_none())
+    );
+    let res = server.send_request::<InlayHintResolveRequest>(hint);
+    let hint = serde_json::from_value::<InlayHint>(res).unwrap();
+    assert!(hint.data.is_none());
+    assert!(
+        matches!(&hint.label, InlayHintLabel::LabelParts(parts) if parts[1].location.is_some())
+    );
+}
+
 #[test]
 fn test_runnables_project() {
     if skip_slow_tests() {
diff --git a/crates/rust-analyzer/tests/slow-tests/support.rs b/crates/rust-analyzer/tests/slow-tests/support.rs
index 8bbe6ff372..c6778bf656 100644
--- a/crates/rust-analyzer/tests/slow-tests/support.rs
+++ b/crates/rust-analyzer/tests/slow-tests/support.rs
@@ -159,6 +159,18 @@ impl Project<'_> {
                         content_format: Some(vec![lsp_types::MarkupKind::Markdown]),
                         ..Default::default()
                     }),
+                    inlay_hint: Some(lsp_types::InlayHintClientCapabilities {
+                        resolve_support: Some(lsp_types::InlayHintResolveClientCapabilities {
+                            properties: vec![
+                                "textEdits".to_owned(),
+                                "tooltip".to_owned(),
+                                "label.tooltip".to_owned(),
+                                "label.location".to_owned(),
+                                "label.command".to_owned(),
+                            ],
+                        }),
+                        ..Default::default()
+                    }),
                     ..Default::default()
                 }),
                 window: Some(lsp_types::WindowClientCapabilities {
diff --git a/crates/vfs/src/file_set/tests.rs b/crates/vfs/src/file_set/tests.rs
index 3cdb60dcb2..2146df185d 100644
--- a/crates/vfs/src/file_set/tests.rs
+++ b/crates/vfs/src/file_set/tests.rs
@@ -40,26 +40,3 @@ fn name_prefix() {
     let partition = file_set.partition(&vfs).into_iter().map(|it| it.len()).collect::<Vec<_>>();
     assert_eq!(partition, vec![1, 1, 0]);
 }
-
-/// Ensure that we don't consider `/foo/bar_baz.rs` to be in the
-/// `/foo/bar/` root.
-#[test]
-fn name_prefix_partially_matches() {
-    let mut file_set = FileSetConfig::builder();
-    file_set.add_file_set(vec![VfsPath::new_virtual_path("/foo".into())]);
-    file_set.add_file_set(vec![VfsPath::new_virtual_path("/foo/bar".into())]);
-    let file_set = file_set.build();
-
-    let mut vfs = Vfs::default();
-
-    // These two are both in /foo.
-    vfs.set_file_contents(VfsPath::new_virtual_path("/foo/lib.rs".into()), Some(Vec::new()));
-    vfs.set_file_contents(VfsPath::new_virtual_path("/foo/bar_baz.rs".into()), Some(Vec::new()));
-
-    // Only this file is in /foo/bar.
-    vfs.set_file_contents(VfsPath::new_virtual_path("/foo/bar/biz.rs".into()), Some(Vec::new()));
-
-    let partition = file_set.partition(&vfs).into_iter().map(|it| it.len()).collect::<Vec<_>>();
-
-    assert_eq!(partition, vec![2, 1, 0]);
-}