Skip to content

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

Description

@TrapsterDK

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:

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:

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:

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:

bazel test //:app_test
bazel build //:app

Generate rust-project.json:

bazel run @rules_rust//tools/rust_analyzer:gen_rust_project -- //:app //:app_test

Inspect the crates generated for main.rs:

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:

{
  "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:

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions