Fix rust-analyzer proc-macro merge for k8-opt-exec paths (#4140)

Written by AI, reviewed by human.

Fixes #4139.

## Summary

`consolidate_crate_specs` prefers proc-macro dylib paths that look like
exec outputs. The previous marker matched paths with `-opt-exec-`, but
Bazel can also emit exec paths like `bazel-out/k8-opt-exec/bin/...`.

This updates the marker to match both forms and adds a regression test
for the unsuffixed `k8-opt-exec/bin` path shape.

## Testing

```sh
bazel test --cache_test_results=no //tools/rust_analyzer:gen_rust_project_lib_test
```
diff --git a/tools/rust_analyzer/aquery.rs b/tools/rust_analyzer/aquery.rs
index 9a9371d..829a19d 100644
--- a/tools/rust_analyzer/aquery.rs
+++ b/tools/rust_analyzer/aquery.rs
@@ -99,7 +99,7 @@
             // generated crate-spec in both the fastbuild and opt-exec configuration.
             // Prefer proc macro paths with an opt-exec component in the path.
             if let Some(dylib_path) = spec.proc_macro_dylib_path.as_ref() {
-                const OPT_PATH_COMPONENT: &str = "-opt-exec-";
+                const OPT_PATH_COMPONENT: &str = "-opt-exec";
                 if dylib_path.contains(OPT_PATH_COMPONENT) {
                     existing.proc_macro_dylib_path.replace(dylib_path.clone());
                 }
@@ -508,7 +508,7 @@
 
     #[test]
     fn consolidate_proc_macro_prefer_exec() {
-        // proc macro crates should prefer the -opt-exec- path which is always generated
+        // proc macro crates should prefer the -opt-exec path which is always generated
         // during builds where it is used, while the fastbuild version would only be built
         // when explicitly building that target.
         let crate_specs = vec![
@@ -581,6 +581,71 @@
     }
 
     #[test]
+    fn consolidate_proc_macro_prefer_unsuffixed_exec() {
+        let crate_specs = vec![
+            CrateSpec {
+                aliases: BTreeMap::new(),
+                crate_id: "ID-myproc_macro.rs".into(),
+                display_name: "myproc_macro".into(),
+                edition: "2018".into(),
+                root_module: "myproc_macro.rs".into(),
+                is_workspace_member: true,
+                deps: BTreeSet::new(),
+                proc_macro_dylib_path: None,
+                source: None,
+                cfg: vec!["test".into(), "debug_assertions".into()],
+                env: BTreeMap::new(),
+                target: "wasm32-unknown-unknown".into(),
+                crate_type: CrateType::ProcMacro,
+                is_test: false,
+                build: None,
+            },
+            CrateSpec {
+                aliases: BTreeMap::new(),
+                crate_id: "ID-myproc_macro.rs".into(),
+                display_name: "myproc_macro".into(),
+                edition: "2018".into(),
+                root_module: "myproc_macro.rs".into(),
+                is_workspace_member: true,
+                deps: BTreeSet::new(),
+                proc_macro_dylib_path: Some(
+                    "bazel-out/k8-opt-exec/bin/myproc_macro/libmyproc_macro-12345.so".into(),
+                ),
+                source: None,
+                cfg: vec!["test".into(), "debug_assertions".into()],
+                env: BTreeMap::new(),
+                target: "x86_64-unknown-linux-gnu".into(),
+                crate_type: CrateType::ProcMacro,
+                is_test: false,
+                build: None,
+            },
+        ];
+
+        assert_eq!(
+            consolidate_crate_specs(crate_specs).unwrap(),
+            BTreeSet::from([CrateSpec {
+                aliases: BTreeMap::new(),
+                crate_id: "ID-myproc_macro.rs".into(),
+                display_name: "myproc_macro".into(),
+                edition: "2018".into(),
+                root_module: "myproc_macro.rs".into(),
+                is_workspace_member: true,
+                deps: BTreeSet::new(),
+                proc_macro_dylib_path: Some(
+                    "bazel-out/k8-opt-exec/bin/myproc_macro/libmyproc_macro-12345.so".into(),
+                ),
+                source: None,
+                cfg: vec!["test".into(), "debug_assertions".into()],
+                env: BTreeMap::new(),
+                target: "wasm32-unknown-unknown".into(),
+                crate_type: CrateType::ProcMacro,
+                is_test: false,
+                build: None,
+            }])
+        );
+    }
+
+    #[test]
     fn consolidate_spec_with_aliases() {
         let crate_specs = vec![
             CrateSpec {