mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-24 19:30:10 +08:00
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:
co-authored by
Claude Opus 5.5
parent
6c49bfa667
commit
b5553e238a
@@ -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()
|
||||
},
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user