fix(rust): an impl's associated type or const no longer takes a bare name or a module path (#2707)

A `type` or `const` written inside an `impl` belongs to the type: Rust
reaches it only through a path like `Self::Err`, `<T as FromStr>::Err` or
`T::Err`, never by its name alone, and no module path or `use` names it.
Both extractors put it at file scope under its bare name, so the resolver
could not tell it from a module's own alias:
- a same-file `Err(…)` call linked to a `FromStr` impl's `type Err`, and
  serde's `Ok(…)` calls to a serializer's `type Ok`;
- axum's routing tests' `Response` linked to the `type Response` of
  `routing/mod.rs`'s `impl Service`, a private item the modules inside
  it can see;
- `io::Error` and `std::error::Error` linked to the `type Error` of an
  impl in the same file, and serde's `use crate::de::Error` to one in
  `de/mod.rs`.

Fix:
- Both extractors (rustImplItemOwner, and the kernel's impl_item_owner)
  qualify an item written directly in an impl by the impl's type, as they
  already qualify its methods: `ValueHint::Err`, `Limits::MAX`. A type no
  single name stands for keeps the impl's spelling, whitespace collapsed:
  `(A, B)::First`, `[u8]::Out`, `u64::WIDTH`.
- isRustAssociatedItem: a Rust type alias or const with a qualified name.
  Nothing else of those kinds gets one: an inline module adds no prefix,
  a function body's items aren't indexed, and a trait's default `type`
  is already its trait's.
- isRustNameInScope: such an item never matches a bare name, and matches a
  path only as `Self::X` in its own impl or as `<…>::X` (unchanged).
- resolveRustPathReference never picks one for a `crate::m::X` path.

Tests: rust-associated-item-scope.test.ts, 6 tests, on both engines; 5
fail on origin/main. The kernel parity torture fixture gains associated
items on a tuple, a slice reference and a primitive.

A/B on fresh indexes, origin/main 5530676f vs this change. Every changed
edge was triaged; no right link was lost:
- axum: 8 wrong links gone, 14 moved to axum-core's `pub type Response`.
- tokio: 46 wrong links gone.
- clap: 12 wrong links gone, 7 of them `Err(…)` calls.
- serde: 98 wrong links gone; `First<…>`/`Second<…>` now reach the
  module's aliases.
- ripgrep: 54 wrong links gone; `impl Captures for RegexCaptures` (3)
  now implements grep-matcher's `Captures` trait.
- gleam: 5 wrong links gone, 10 moved to the right target, 1 moved from
  one wrong target to another.
- dioxus: 71 wrong links gone, 40 moved to the right target, 3 moved from
  one wrong target to another.
- Controls (gin, serilog, flask, express) byte-identical; indexing time
  unchanged within noise.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-10-20 16:57:03 +00:00
committed by GitHub
co-authored by Claude Opus 5.5
parent 6c49bfa667
commit b5553e238a
8 changed files with 325 additions and 1 deletions
+26
View File
@@ -446,6 +446,26 @@ impl<'t> Walker<'t> {
None
}
/// rustImplItemOwner (languages/rust.ts) — the type an item written
/// directly in an impl block belongs to: `ValueHint` for the `type Err`
/// of `impl FromStr for ValueHint`, then `ValueHint::Err`, as the impl's
/// methods are. A type no single name stands for (`()`, `(A, B)`, `[u8]`,
/// `u32`) is written as the impl writes it. Mirrored byte-for-byte —
/// change both.
fn impl_item_owner(&self, node: Node) -> Option<String> {
let list = node.parent().filter(|p| p.kind() == "declaration_list")?;
let imp = list.parent().filter(|p| p.kind() == "impl_item")?;
let mut ty = imp.child_by_field_name("type")?;
while ty.kind() == "reference_type" {
ty = ty.child_by_field_name("type")?;
}
self.impl_type_name(Some(ty)).or_else(|| {
let words: Vec<&str> =
self.text(ty).split([' ', '\t', '\r', '\n']).filter(|w| !w.is_empty()).collect();
Some(words.join(" "))
})
}
// --- visitNode ------------------------------------------------------------
fn visit_node(&mut self, node: Node<'t>) {
@@ -691,6 +711,9 @@ impl<'t> Walker<'t> {
// Private unless `pub` (any form), as the wasm walker reads it:
// the resolver keeps a private alias to its own module.
visibility: Some(self.visibility_of(node)),
// An impl's associated type is its type's (`ValueHint::Err`),
// named only through a path, never bare.
qualified_name: self.impl_item_owner(node).map(|o| format!("{o}::{name}")),
..Extra::default()
};
self.create_node("type_alias", &name, node, extra);
@@ -703,6 +726,8 @@ impl<'t> Walker<'t> {
fn extract_variable(&mut self, node: Node<'t>) {
let docstring = preceding_docstring(node, self.src);
let name_field = node.child_by_field_name("name");
// An impl's associated const is its type's (`Limits::MAX`), as its methods are.
let owner = self.impl_item_owner(node);
let mut declared: Option<(u32, String)> = None;
for i in 0..node.named_child_count() {
let Some(child) = node.named_child(i) else { continue };
@@ -718,6 +743,7 @@ impl<'t> Walker<'t> {
Extra {
docstring: docstring.clone(),
is_exported: Some(false),
qualified_name: owner.as_ref().map(|o| format!("{o}::{name}")),
..Extra::default()
},
);