bazelbuild / bazelbuild/rules_rust
rust-analyzer sysroot enrichment fails: no cargo in rust_analyzer_toolchain
- Dominant language
- Starlark
- Stars
- 843
- Forks
- 651
- Avg merge
- 2d 18h
- Merged PRs (30d)
- 15
Description
**Description**
When using `rules_rust`'s rust-analyzer integration (`rust_analyzer_toolchain` +
the `rust-project.json` produced by `discover_bazel_rust_project`), hover and
go-to-definition are broken for any local variable/expression whose type is a
generic container from `std`/`alloc` (e.g. `Option`, `Arc`, `RefCell`)
instantiated with a workspace-local `T`.
- Hovering such a variable shows `{unknown}` instead of the real type.
- Go-to-definition into the corresponding `std`/`core` source (e.g. jumping
into `RefCell::new`) silently does nothing — no navigation, no error.
Plain, non-generic types (local newtypes, etc.) and types coming straight from
an already-fully-inferred function return (e.g. `anyhow::Result<...>`) are
unaffected — only generic std/alloc containers wrapping a workspace-local type
are impacted.
Expected behavior: hover and go-to-definition should work the same way they do
for identical code loaded via a normal `cargo`-based project. We verified this
directly — the same source file, same rust-analyzer version, loaded via a
plain Cargo project instead of the Bazel-generated `rust-project.json`, and
both hover and go-to-definition work correctly there.
**Reproduction steps**
This reproduces in a minimal, standalone workspace — no other configuration
involved:
1. Create a workspace with just these three files:
`MODULE.bazel`:
```python
module(name = "rules_rust_ra_repro")
bazel_dep(name = "rules_rust", version = "0.73.0")
rust = use_extension("@rules_rust//rust:extensions.bzl", "rust")
rust.toolchain(
edition = "2024",
versions = ["1.95.0"],
)
use_repo(rust, "rust_toolchains")
register_toolchains("@rust_toolchains//:all")
```
`BUILD.bazel`:
```python
load("@rules_rust//rust:defs.bzl", "rust_library")
rust_library(
name = "repro",
srcs = ["src/lib.rs"],
edition = "2024",
)
```
`src/lib.rs`:
```rust
use std::cell::RefCell;
pub struct Foo;
pub fn example() -> Option {
let y: Option = None;
let _z = RefCell::new(Foo);
y
}
```
2. `bazel build //:repro` — builds cleanly.
3. `bazel run @rules_rust//tools/rust_analyzer:setup -- helix` — generates the
rust-analyzer integration and a `rust-project.json`-producing discover
binary under `.helix/.rules_rust_analyzer/`.
4. Inspect the sysroot directory referenced by the generated
`rust-project.json`'s `sysroot` field
(`/external/rules_rust++rust+rust_analyzer_1.95.0_tools`):
it contains `rust-analyzer`, `rustc`, `rustdoc`, `rust-gdb`, `rust-gdbgui`,
`rust-lldb` — no `cargo` binary.
5. Run the generated `rust-project.json` through rust-analyzer directly
(no editor needed):
```
/bin/rust-analyzer analysis-stats
```
This logs:
```
WARN `cargo metadata` failed and returning succeeded result with
`--no-deps` error=`cargo metadata` exited with an error: error: unknown
`-Z` flag specified: lockfile-path
```
6. Opening the same project in an editor via the generated
`workspace.discoverConfig` integration (we used Helix 25.07.1) shows the
downstream symptom on `src/lib.rs`: hovering `y` (type `Option`)
displays `{unknown}` instead of the real type, and go-to-definition on
`RefCell` does nothing. Earlier in the session, rust-analyzer also logs:
```
WARN Workspace `` has sysroot errors: sysroot at
`/lib/rustlib/src/library` is missing a `core` library, try
running `rustup component add rust-src` to possibly fix this
```
even though that sysroot directory contains a structurally valid `core`
crate (`Cargo.toml` + `src/lib.rs` present).
**Additional context**
- The `-Zlockfile-path` failure is not simply a missing-`RUSTC_BOOTSTRAP`/
nightly-channel issue: even with `RUSTC_BOOTSTRAP=1` forced, the `cargo`
that ends up being invoked for this call reports `unknown -Z flag
specified: lockfile-path` outright — the flag isn't recognized by that
cargo build at all.
- The `rust_analyzer_toolchain`-provided sysroot directory (the one referenced
by the `sysroot`/`sysroot_src` fields of the generated `rust-project.json`)
contains `rust-analyzer`, `rustc`, `rustdoc`, `rust-gdb`, `rust-gdbgui`,
`rust-lldb` — but no `cargo` binary.
- Reproduces identically on rules_rust 0.71.3 and 0.73.0.
- Editor used for testing: Helix 25.07.1, via the standard
`workspace.discoverConfig` integration generated by
`bazel run @rules_rust//tools/rust_analyzer:setup`.
- Confirmed as a regression relative to a plain Cargo-based project setup:
identical source and rust-analyzer version, loaded without going through
`rules_rust`'s discovery/`rust-project.json` generation, does not exhibit
either symptom.
**Impact**
We are not aware of a workaround that stays purely within the Bazel/
`rules_rust` toolchain — avoiding the issue currently seems to require making
a `cargo` binary available somewhere rust-analyzer will pick it up from. This
does not block using `rules_rust`/`rust_analyzer_toolchain` outright, but it
meaningfully degrades the IDE experience for any code using standard generic
containers over local types — a very common pattern — for any team that
doesn't otherwise have a separate Cargo/rustup toolchain installed.
**Workaround**
rust-analyzer resolves the `cargo` for this call purely via `PATH` — we
confirmed it does not look for one relative to the `sysroot` it was given
(pointing `sysroot` at a directory that does contain a matching `cargo` had
no effect). So the only workaround we found is to make a `cargo` available on
`PATH` for the rust-analyzer process itself, matched to the same version as
the `rust_analyzer_toolchain`'s `rustc`/`rust-analyzer` (a mismatched, e.g.
system/rustup-provided, `cargo` either rejects the `-Z` flag outright or
requires nightly). Bazel does build such a matching `cargo` as part of the
regular (non-rust-analyzer) `rust_toolchain`, so we pointed rust-analyzer at
that one via a small wrapper script that Helix's `command` setting launches
instead of the `rust-analyzer` binary directly:
```sh
#!/bin/sh
output_base=$(bazel info output_base 2>/dev/null)
cargo_dir="$output_base/external//bin"
export PATH="$cargo_dir:$PATH"
export RUSTC_BOOTSTRAP=1
exec "" "$@"
```
This is entirely external to `rules_rust`'s own configuration surface (there
is no `rust-analyzer.cargo.*` setting that redirects or disables this call),
so it has to be layered on top of the generated editor integration.
In principle `discover_bazel_rust_project`/`setup` could generate a wrapper
like this itself — it already emits small executables into
`.helix/.rules_rust_analyzer/` (`discover_bazel_rust_project.exe`,
`flycheck.exe`) and has access to both the `rust_analyzer_toolchain`'s
`rust-analyzer` binary and the regular `rust_toolchain`'s matching `cargo`
during that step. We're not asking for that specifically, though — a few
reasons it may not be the right fix to bake in as default behavior:
- `RUSTC_BOOTSTRAP=1` unlocks all unstable features for that process,
silently. Shipping it unconditionally in official tooling is a broader
escape hatch than "fix this one call."
- It's inherently version-fragile: it only works because this particular
`cargo`/`rustc` pairing happens to support `-Zlockfile-path`. A future
rust-analyzer release could start requiring a different unstable flag the
matched `cargo` doesn't have either.
- It doesn't fix the actual gap (rust-analyzer having no way to redirect or
disable this call) — it papers over it. A real fix likely belongs upstream
in rust-analyzer.
**Bazel and rules_rust version**
- Bazel: 9.1.1
- rules_rust: 0.73.0 (also reproduced on 0.71.3)
- rust-analyzer version from `rust_analyzer_toolchain`: 1.95.0
Contributor guide
Research direction
Start with the generated integration from `bazel run @rules_rust//tools/rust_analyzer:setup -- helix`, then inspect `discover_bazel_rust_project`, `rust-project.json`, and the referenced sysroot. Reproduce with `rust-analyzer analysis-stats` and the provided `src/lib.rs` example. Done means the generated integration no longer fails its cargo/sysroot enrichment and hover and go-to-definition work for generic standard-library containers.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- developer-experience, tooling
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100