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<T>, Arc<T>, RefCell<T>)
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:
-
Create a workspace with just these three files:
MODULE.bazel:
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:
load("@rules_rust//rust:defs.bzl", "rust_library")
rust_library(
name = "repro",
srcs = ["src/lib.rs"],
edition = "2024",
)
src/lib.rs:
use std::cell::RefCell;
pub struct Foo;
pub fn example() -> Option<Foo> {
let y: Option<Foo> = None;
let _z = RefCell::new(Foo);
y
}
-
bazel build //:repro — builds cleanly.
-
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/.
-
Inspect the sysroot directory referenced by the generated
rust-project.json's sysroot field
(<output_base>/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.
-
Run the generated rust-project.json through rust-analyzer directly
(no editor needed):
<sysroot>/bin/rust-analyzer analysis-stats <dir containing rust-project.json>
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
-
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<Foo>)
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 `<crate path>` has sysroot errors: sysroot at
`<sysroot>/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:
#!/bin/sh
output_base=$(bazel info output_base 2>/dev/null)
cargo_dir="$output_base/external/<rust_toolchain_repo>/bin"
export PATH="$cargo_dir:$PATH"
export RUSTC_BOOTSTRAP=1
exec "<path to the rust-analyzer binary>" "$@"
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
Description
When using
rules_rust's rust-analyzer integration (rust_analyzer_toolchain+the
rust-project.jsonproduced bydiscover_bazel_rust_project), hover andgo-to-definition are broken for any local variable/expression whose type is a
generic container from
std/alloc(e.g.Option<T>,Arc<T>,RefCell<T>)instantiated with a workspace-local
T.{unknown}instead of the real type.std/coresource (e.g. jumpinginto
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<...>) areunaffected — 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 thisdirectly — the same source file, same rust-analyzer version, loaded via a
plain Cargo project instead of the Bazel-generated
rust-project.json, andboth hover and go-to-definition work correctly there.
Reproduction steps
This reproduces in a minimal, standalone workspace — no other configuration
involved:
Create a workspace with just these three files:
MODULE.bazel:BUILD.bazel:src/lib.rs:bazel build //:repro— builds cleanly.bazel run @rules_rust//tools/rust_analyzer:setup -- helix— generates therust-analyzer integration and a
rust-project.json-producing discoverbinary under
.helix/.rules_rust_analyzer/.Inspect the sysroot directory referenced by the generated
rust-project.json'ssysrootfield(
<output_base>/external/rules_rust++rust+rust_analyzer_1.95.0_tools):it contains
rust-analyzer,rustc,rustdoc,rust-gdb,rust-gdbgui,rust-lldb— nocargobinary.Run the generated
rust-project.jsonthrough rust-analyzer directly(no editor needed):
This logs:
Opening the same project in an editor via the generated
workspace.discoverConfigintegration (we used Helix 25.07.1) shows thedownstream symptom on
src/lib.rs: hoveringy(typeOption<Foo>)displays
{unknown}instead of the real type, and go-to-definition onRefCelldoes nothing. Earlier in the session, rust-analyzer also logs:even though that sysroot directory contains a structurally valid
corecrate (
Cargo.toml+src/lib.rspresent).Additional context
-Zlockfile-pathfailure is not simply a missing-RUSTC_BOOTSTRAP/nightly-channel issue: even with
RUSTC_BOOTSTRAP=1forced, thecargothat ends up being invoked for this call reports
unknown -Z flag specified: lockfile-pathoutright — the flag isn't recognized by thatcargo build at all.
rust_analyzer_toolchain-provided sysroot directory (the one referencedby the
sysroot/sysroot_srcfields of the generatedrust-project.json)contains
rust-analyzer,rustc,rustdoc,rust-gdb,rust-gdbgui,rust-lldb— but nocargobinary.workspace.discoverConfigintegration generated bybazel run @rules_rust//tools/rust_analyzer:setup.identical source and rust-analyzer version, loaded without going through
rules_rust's discovery/rust-project.jsongeneration, does not exhibiteither symptom.
Impact
We are not aware of a workaround that stays purely within the Bazel/
rules_rusttoolchain — avoiding the issue currently seems to require makinga
cargobinary available somewhere rust-analyzer will pick it up from. Thisdoes not block using
rules_rust/rust_analyzer_toolchainoutright, but itmeaningfully 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
cargofor this call purely viaPATH— weconfirmed it does not look for one relative to the
sysrootit was given(pointing
sysrootat a directory that does contain a matchingcargohadno effect). So the only workaround we found is to make a
cargoavailable onPATHfor the rust-analyzer process itself, matched to the same version asthe
rust_analyzer_toolchain'srustc/rust-analyzer(a mismatched, e.g.system/rustup-provided,
cargoeither rejects the-Zflag outright orrequires nightly). Bazel does build such a matching
cargoas part of theregular (non-rust-analyzer)
rust_toolchain, so we pointed rust-analyzer atthat one via a small wrapper script that Helix's
commandsetting launchesinstead of the
rust-analyzerbinary directly:This is entirely external to
rules_rust's own configuration surface (thereis 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/setupcould generate a wrapperlike this itself — it already emits small executables into
.helix/.rules_rust_analyzer/(discover_bazel_rust_project.exe,flycheck.exe) and has access to both therust_analyzer_toolchain'srust-analyzerbinary and the regularrust_toolchain's matchingcargoduring 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=1unlocks all unstable features for that process,silently. Shipping it unconditionally in official tooling is a broader
escape hatch than "fix this one call."
cargo/rustcpairing happens to support-Zlockfile-path. A futurerust-analyzer release could start requiring a different unstable flag the
matched
cargodoesn't have either.disable this call) — it papers over it. A real fix likely belongs upstream
in rust-analyzer.
Bazel and rules_rust version
rust_analyzer_toolchain: 1.95.0