bazelbuild / bazelbuild/rules_rust

rust_analyzer: rust-project.json consolidation merges distinct crates with the same crate_id

Open
#4,138 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Starlark
Stars
843
Forks
651
Avg merge
2d 18h
Merged PRs (30d)
15

Description

This issue was created with AI assistance and has been human reviewed for accuracy before filing.

**Description**
`rust-project.json` generation can merge distinct Rust crates that happen to share the same `crate_id`.

The current consolidation logic in `tools/rust_analyzer/aquery.rs::consolidate_crate_specs` uses only `CrateSpec.crate_id` as the map key. `crate_id` is derived from the crate root module path, so sharing it is intentional for some cases, such as `rust_test(crate = ":lib")`. However, the same `crate_id` can also occur for crates that should remain distinct.

Current wrong behavior:
A standalone `rust_binary` and `rust_test` using the same root file are consolidated into one rust-analyzer crate. The resulting crate can contain mixed metadata from both targets. In the repro below, the generated crate has `display_name = "app"`, but `env.KIND = "test"`, `env.CARGO_CRATE_NAME = "app_test"`, and `build.label = "//:app_test"`.

Expected behavior:
`rust-project.json` should only merge crate specs when they represent the same logical rust-analyzer crate, such as the existing `rust_test(crate = ":lib")` case. Distinct same-root standalone binary/test crates should remain separate entries.

**Reproduction steps**
Create a minimal workspace using upstream, unpatched `rules_rust`.

`MODULE.bazel`:
```starlark
module(name = "ra_crate_id_repro")

bazel_dep(name = "rules_rust", version = "0.71.3")

rust = use_extension("@rules_rust//rust:extensions.bzl", "rust")
rust.toolchain(edition = "2021")
use_repo(rust, "rust_toolchains")

register_toolchains("@rust_toolchains//:all")
```

`BUILD.bazel`:
```starlark
load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_test")

rust_binary(
name = "app",
srcs = ["main.rs"],
rustc_env = {"KIND": "binary"},
)

rust_test(
name = "app_test",
srcs = ["main.rs"],
rustc_env = {"KIND": "test"},
)
```

`main.rs`:
```rust
pub fn value() -> u8 {
1
}

fn main() {
let _ = value();
}

#[cfg(test)]
mod tests {
#[test]
fn value_is_one() {
assert_eq!(super::value(), 1);
}
}
```

First verify the targets themselves build/test successfully:
```bash
bazel test //:app_test
bazel build //:app
```

Generate `rust-project.json`:
```bash
bazel run @rules_rust//tools/rust_analyzer:gen_rust_project -- //:app //:app_test
```

Inspect the crates generated for `main.rs`:
```bash
jq '[.crates[] | select(.root_module | endswith("/main.rs"))] | {matching_crate_count: length, crates: [.[] | {display_name, env_KIND: .env.KIND, cargo_crate_name: .env.CARGO_CRATE_NAME, build_label: .build.label, build_target_kind: .build.target_kind}]}' rust-project.json
```

Actual output from the repro:
```json
{
"matching_crate_count": 1,
"crates": [
{
"display_name": "app",
"env_KIND": "test",
"cargo_crate_name": "app_test",
"build_label": "//:app_test",
"build_target_kind": "bin"
}
]
}
```

Expected output:
There should be two distinct crate entries for `main.rs`: one for `//:app` with `KIND = "binary"`, and one for `//:app_test` with `KIND = "test"`.

**Additional context**
This appears to be caused by `consolidate_crate_specs` using `spec.crate_id` alone as the identity key for consolidation.

The existing merge behavior is useful for `rust_test(crate = ":lib")`, where the test and library share a root module and should be merged so rust-analyzer test code lenses work. The problem is that same `crate_id` does not always mean same logical rust-analyzer crate.

The same identity issue can also affect target-specific crate variants when the same crate root appears under multiple target triples/configurations. A fix likely needs to distinguish intentional library/test consolidation from unrelated same-root crates and avoid collapsing target-specific variants into one crate.

Related but distinct issues:
- #1589 discusses consolidation creating dependency cycles.
- #3126 discusses generated-source consolidation behavior.

**Impact**
This can make rust-analyzer use incorrect crate metadata. In practice this can cause wrong runnables/code lenses, wrong build labels, wrong environment variables, wrong dependency/alias metadata, or incorrect target-specific analysis.

The workaround is to carry a local patch that qualifies crate IDs before consolidation for distinct same-root binary/test crates and for non-proc-macro crates appearing under multiple target triples.

**Bazel and rules_rust version**
Reproduced with:
- Bazel: `9.1.1`
- rules_rust: `0.71.3` from Bzlmod
- Also reproduced against upstream `bazelbuild/rules_rust` `main` at `46d67bc1fda4592dab7465c8152d94ae6f969cfc`

Contributor guide

Open the contributing guide

Research direction

Read tools/rust_analyzer/aquery.rs, focusing on consolidate_crate_specs and how CrateSpec.crate_id is used as the consolidation key. Reproduce the issue with the provided MODULE.bazel, BUILD.bazel, and main.rs, then run the Bazel rust-project generator and inspect the generated entries. Done means standalone //:app and //:app_test remain distinct while the existing rust_test(crate = ":lib") consolidation still works.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
build-system, tooling
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.