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
+1
View File
@@ -217,6 +217,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- In Rust, a call like `String::from("…")`, `Vec::from([…])` or `NonNull::from(x)` no longer links to your project's own `impl From<YourType> for String` (or for `Vec`, `NonNull`, …) unless what it passes is a `YourType`. A method call on a standard-library value, like `files.iter()` or `buf.extend(…)`, links to your trait's implementation for that type only where the trait is imported. Before, every `String::from("…")` call in dioxus's CLI linked to one such conversion, and tokio's `vec.iter()` calls to an internal trait's `Vec` impl. Re-index Rust projects after upgrading.
- In Rust, a method called through `self: Pin<&mut Self>`, `self: Box<Self>`, `self: Rc<Self>` or `self: Arc<Self>`, or on a value declared `Pin<&mut …>`, now links the way Rust looks it up: to the type's own method, like `self.poll_flush(cx)` inside tokio's `impl AsyncWrite for File`, rather than to a catch-all `impl AsyncWrite for Pin<P>`, while `Pin`'s own methods, like `self.get_mut()`, stay unlinked. A type's own method now also comes before a trait's of the same name, so tokio's `Arc::from_raw(…)` calls are the standard library's rather than its internal `Link` trait's, and a method called on a tuple, like serde's `(self.ip(), self.port()).serialize(…)`, no longer links to an unrelated type's. Before, most calls made through such a `self` linked to nothing. Re-index Rust projects after upgrading.
- In Rust, a type alias, struct, enum or function declared without `pub` now links only from its own module and the modules inside it, and a name a `use` brings in from one of your workspace's crates, like `use tokio_test::task::spawn` or `use crate::error::Error`, links only to an item of that crate or of a crate it depends on. Before, a file's `use crate::Result` could link its `Result<…>` types to another module's private `type Result`, as in dioxus's CLI, and a call to an imported function could link to another crate's function of the same name, such as tokio's test `spawn(…)` calls to one in its examples, or to nothing because a private function of that name was in the way. Re-index Rust projects after upgrading.
- In Rust, a name written on its own, like an `Err(…)` call or a `Response` return type, or through a module, like `io::Error` or `use crate::de::Error`, no longer links to a `type` or `const` declared inside an `impl`, such as a `FromStr` impl's `type Err` or a `Service` impl's `type Response`: only a path like `Self::Err` names those, and they are now listed under their type, as `ValueHint::Err`. Before, serde's and ripgrep's `Ok(…)` and `Err(…)` calls linked to a serializer's or parser's `type Ok` or `type Err`, axum's handlers' `Response` to a `Service` impl's, and ripgrep's `impl Captures for RegexCaptures` to another impl's `type Captures` instead of the `Captures` trait. Re-index Rust projects after upgrading.
- In C++, every name a declaration lists now has the declaration's type, like `strings_found` in google/breakpad's `ByteBuffer symbols_found, strings_found;` or `data_` in a class's `Table *index_, *data_;`, so calls on it link to that class's methods. Before, only the first name had its type read, and a call on another name linked to nothing or to the class of an earlier variable with the same name.
## [1.6.2] - 2026-10-03
@@ -182,6 +182,22 @@ impl Render for dyn Base {
impl Base for u32 {}
// Associated items are qualified by the impl's type; one no single name
// stands for keeps the impl's spelling, whitespace collapsed.
impl Render for (u32,
u32) {
type Out = u8;
const PAIR: usize = 2;
}
impl<'a> Render for &'a [u8] {
type Out = u16;
}
impl Render for u64 {
const WIDTH: usize = 8;
}
impl Later {
fn touch(&self) {}
}
@@ -0,0 +1,228 @@
/**
* A Rust associated item — a `type` or `const` written inside an `impl` (or a
* trait) — belongs to the type, not to the module: Rust reaches it only
* through a path (`Self::Err`, `<T as FromStr>::Err`, `T::Err`), never by its
* name alone, and no module path names it. clap's and gleam's `Err(…)` calls
* went to the `type Err` of a `FromStr` impl in the same file, and axum's
* `routing/` tests' `Response`s to the `type Response` of the `Service` impl
* in `routing/mod.rs`, whose private items the modules inside it can see.
*/
import { describe, it, expect, afterAll, beforeAll } from 'vitest';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { CodeGraph } from '../src';
/** What `name`, written in `file`, links to, as `file@line` of each target, sorted. */
const targetsOf = (cg: CodeGraph, file: string, name: string, kinds: string[]): string[] => {
const out: string[] = [];
for (const node of cg.getNodesInFile(file)) {
for (const edge of cg.getOutgoingEdges(node.id)) {
if (!kinds.includes(edge.kind)) continue;
const target = cg.getNode(edge.target)!;
if (target.name === name) out.push(`${target.filePath}@${target.startLine}`);
}
}
return [...new Set(out)].sort();
};
describe('a Rust associated item', () => {
let root = '';
let cg: CodeGraph;
beforeAll(async () => {
root = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-rust-assoc-'));
const files: Record<string, string> = {
'Cargo.toml': '[package]\nname = "web"\nversion = "0.1.0"\n',
'src/lib.rs': 'pub mod de;\npub mod error;\npub mod parts;\npub mod response;\npub mod routing;\npub mod value;\npub mod value_hint;\n',
// serde declares `de::Error` in a macro, which the index doesn't read:
// the one `Error` it holds for `de/mod.rs` is an impl's.
'src/de/mod.rs': [
'macro_rules! declare_error_trait {',
' () => {',
' pub trait Error: Sized {}',
' };',
'}',
'',
'declare_error_trait!();',
'',
'pub trait SeqAccess {',
' type Error;',
'}',
'',
'pub struct Access;',
'',
'impl SeqAccess for Access {',
' type Error = String;',
'}',
'',
].join('\n'),
'src/value.rs': 'use crate::de::Error;\n\npub struct ValueError;\n\nimpl crate::de::Error for ValueError {}\n',
'src/response.rs': 'pub struct Body;\n\npub type Response<T = Body> = http::Response<T>;\n',
// The crate's own `Error`, which `parts.rs`'s `std::error::Error` is not either.
'src/error.rs': 'pub struct Error;\n',
'src/routing/mod.rs': [
'mod route;',
'#[cfg(test)]',
'mod tests;',
'',
'use crate::response::Response;',
'use std::convert::Infallible;',
'',
'pub struct Router;',
'',
'impl<B> tower::Service<http::Request<B>> for Router {',
' type Response = Response;',
' type Error = Infallible;',
'',
' fn call(&mut self, req: http::Request<B>) -> Result<Self::Response, Self::Error> {',
' todo!()',
' }',
'}',
'',
].join('\n'),
// A module inside `routing` sees its private items, but never an impl's.
'src/routing/tests.rs': 'use crate::response::Response;\n\nfn handler() -> Response {\n todo!()\n}\n',
'src/routing/route.rs': [
'use crate::response::Response;',
'',
'pub struct Route;',
'',
'impl<B> tower::Service<http::Request<B>> for Route {',
' type Response = Response;',
'',
' fn call(&mut self, req: http::Request<B>) -> Self::Response {',
' todo!()',
' }',
'}',
'',
'pub struct RouteFuture;',
'',
'impl RouteFuture {',
' pub fn new(response: Response) -> Self {',
' RouteFuture',
' }',
'}',
'',
].join('\n'),
'src/value_hint.rs': [
'use std::str::FromStr;',
'',
'pub enum ValueHint {',
' Unknown,',
' Other,',
'}',
'',
'impl FromStr for ValueHint {',
' type Err = String;',
'',
' fn from_str(s: &str) -> Result<Self, Self::Err> {',
' match s {',
' "other" => Ok(ValueHint::Other),',
' _ => Err(format!("unknown hint: {s}")),',
' }',
' }',
'}',
'',
'pub fn parse(s: &str) -> Result<ValueHint, String> {',
' if s.is_empty() {',
' return Err(String::from("empty"));',
' }',
' s.parse()',
'}',
'',
].join('\n'),
'src/parts.rs': [
'use std::convert::Infallible;',
'',
'pub trait IntoParts {',
' type Error;',
'}',
'',
'pub struct Extensions;',
'',
'impl IntoParts for Extensions {',
' type Error = Infallible;',
'}',
'',
'#[derive(Debug)]',
'pub struct HeaderError;',
'',
'impl std::error::Error for HeaderError {',
' fn source(&self) -> Option<&(dyn std::error::Error + \'static)> {',
' None',
' }',
'}',
'',
'pub struct Jar;',
'',
'pub struct JarIter;',
'',
'impl Jar {',
' pub fn iter(&self) -> impl Iterator<Item = String> + \'_ {',
' JarIter',
' }',
'}',
'',
'impl Iterator for JarIter {',
' type Item = String;',
'',
' fn next(&mut self) -> Option<Self::Item> {',
' None',
' }',
'}',
'',
].join('\n'),
};
for (const [rel, content] of Object.entries(files)) {
fs.mkdirSync(path.dirname(path.join(root, rel)), { recursive: true });
fs.writeFileSync(path.join(root, rel), content);
}
cg = await CodeGraph.init(root, { index: true });
}, 60_000);
afterAll(() => {
cg?.close();
if (root) fs.rmSync(root, { recursive: true, force: true });
});
it('is not what a bare call names: `Err(…)` is the prelude\'s, not a `FromStr` impl\'s `type Err`', () => {
expect(targetsOf(cg, 'src/value_hint.rs', 'Err', ['calls'])).toEqual([]);
});
it('is not what a bare type names, in its own file or a module inside its own', () => {
expect(targetsOf(cg, 'src/routing/tests.rs', 'Response', ['references'])).toEqual(['src/response.rs@3']);
const own = cg.getNodesInFile('src/routing/route.rs').find((n) => n.name === 'new')!;
const targets = cg.getOutgoingEdges(own.id).filter((e) => e.kind === 'references').map((e) => cg.getNode(e.target)!)
.filter((t) => t.name === 'Response').map((t) => `${t.filePath}@${t.startLine}`);
expect(targets).toEqual(['src/response.rs@3']);
});
it('is not what a module path names: `std::error::Error` is no impl\'s `type Error`', () => {
const source = cg.getNodesInFile('src/parts.rs').find((n) => n.name === 'source')!;
const targets = cg.getOutgoingEdges(source.id).map((e) => cg.getNode(e.target)!).filter((t) => t.name === 'Error');
expect(targets.map((t) => `${t.kind} ${t.filePath}@${t.startLine}`)).toEqual([]);
});
it('is not what a `use` or a path to its module names', () => {
const value = cg.getNodesInFile('src/value.rs');
const targets = value.flatMap((n) => cg.getOutgoingEdges(n.id)).map((e) => cg.getNode(e.target)!)
.filter((t) => t.name === 'Error').map((t) => `${t.kind} ${t.filePath}@${t.startLine}`);
expect(targets).toEqual([]);
});
it('is not the `Item` an `Iterator<Item = …>` bound names', () => {
const iter = cg.getNodesInFile('src/parts.rs').find((n) => n.name === 'iter')!;
const targets = cg.getOutgoingEdges(iter.id).map((e) => cg.getNode(e.target)!).filter((t) => t.name === 'Item');
expect(targets).toEqual([]);
});
it('is still what `Self::` names inside its impl', () => {
expect(targetsOf(cg, 'src/value_hint.rs', 'Err', ['references'])).toEqual(['src/value_hint.rs@9']);
const call = cg.getNodesInFile('src/routing/mod.rs').find((n) => n.name === 'call')!;
const targets = cg.getOutgoingEdges(call.id).filter((e) => e.kind === 'references').map((e) => cg.getNode(e.target)!)
.map((t) => `${t.name} ${t.filePath}@${t.startLine}`).sort();
expect(targets).toEqual(['Error src/routing/mod.rs@12', 'Response src/routing/mod.rs@11']);
expect(targetsOf(cg, 'src/parts.rs', 'Item', ['references'])).toEqual(['src/parts.rs@33']);
});
});
+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()
},
);
+18
View File
@@ -71,6 +71,24 @@ export function rustImplTypeName(typeNode: SyntaxNode | null, source: string): s
}
}
/**
* The type an item written directly in an `impl` block belongs to: `ValueHint`
* for the `type Err = String;` of `impl FromStr for ValueHint`, which is then
* `ValueHint::Err`, as the impl's methods are `ValueHint::…`. A type no single
* name stands for (`()`, `(A, B)`, `[u8]`, `u32`) is written as the impl
* writes it. Undefined for an item outside an impl. Mirrored byte-for-byte by
* the native kernel's `impl_item_owner` (codegraph-kernel/src/rustlang.rs) —
* change both.
*/
export function rustImplItemOwner(node: SyntaxNode, source: string): string | undefined {
const impl = node.parent?.type === 'declaration_list' ? node.parent.parent : null;
if (impl?.type !== 'impl_item') return undefined;
let type = getChildByField(impl, 'type');
while (type?.type === 'reference_type') type = getChildByField(type, 'type');
if (!type) return undefined;
return rustImplTypeName(type, source) ?? getNodeText(type, source).split(/[ \t\r\n]+/).filter(Boolean).join(' ');
}
export const rustExtractor: LanguageExtractor = {
// `function_signature_item` is a trait method DECLARATION (`fn render(&self);`,
// no body). Extracting it makes a trait's method set first-class, which
+8 -1
View File
@@ -24,7 +24,7 @@ import { EXTRACTORS } from './languages';
import { stripCppTemplateArgs, isCppConstructorDeclaration, cDeclaratorIdentifier, isClassLikeDefinition } from './languages/c-cpp';
import { NestedIntervals, scanCppBraceScopes, type CppBraceScopes } from './languages/cpp-brace-scopes';
import { appendCppMacroMethods } from './languages/cpp-macro-methods';
import { rustImplTypeName } from './languages/rust';
import { rustImplItemOwner, rustImplTypeName } from './languages/rust';
import { goAliasTypeNames, goEmbeddedTypeName, goInterfaceMethodTypeNames, goStructFieldTypeNames } from './languages/go';
import { dartMisparsedGenericCall, dartReceiverOf, isDartTypeName, pushDartTypeRefs } from './languages/dart';
import { LiquidExtractor } from './liquid-extractor';
@@ -3779,6 +3779,8 @@ export class TreeSitterExtractor {
// Generic fallback for other languages
// Try to find identifier children
const nameField = getChildByField(node, 'name');
// A Rust impl's associated const is its type's (`Limits::MAX`), as its methods are.
const owner = this.language === 'rust' ? rustImplItemOwner(node, this.source) : undefined;
let declared: Node | null = null;
for (let i = 0; i < node.namedChildCount; i++) {
const child = node.namedChild(i);
@@ -3791,6 +3793,7 @@ export class TreeSitterExtractor {
const created = this.createNode(kind, name, child, {
docstring,
isExported,
...(owner !== undefined ? { qualifiedName: `${owner}::${name}` } : {}),
});
if (created && nameField && child.startIndex === nameField.startIndex) {
declared = created;
@@ -3986,12 +3989,16 @@ export class TreeSitterExtractor {
return true;
}
const owner = this.language === 'rust' ? rustImplItemOwner(node, this.source) : undefined;
const typeAliasNode = this.createNode('type_alias', name, node, {
docstring,
isExported,
// A Rust alias is private to its module unless `pub` (any form): the
// resolver keeps a private one to its own module (isRustNameInScope).
...(this.language === 'rust' ? { visibility: this.extractor.getVisibility?.(node) } : {}),
// An impl's associated type is its type's (`ValueHint::Err`), named only
// through a path, never bare (isRustAssociatedItem).
...(owner !== undefined ? { qualifiedName: `${owner}::${name}` } : {}),
});
// Extract type references from the alias value (e.g., `type X = ITextModel | null`)
+4
View File
@@ -26,6 +26,7 @@ import {
arrayElementType,
tsImportedTypeFile,
reExportSource,
isRustAssociatedItem,
} from './name-matcher';
import { JS_ARRAY_METHODS } from './js-builtins';
@@ -2620,9 +2621,12 @@ function resolveRustPathReference(
const file = resolveRustModuleFile(modSegs, ref.filePath, context);
if (!file || file === ref.filePath) return null;
// A module's item, never an impl's associated one: serde's `use
// crate::de::Error` is not `de/mod.rs`'s `impl SeqAccess`'s `type Error`.
const target = context.getNodesInFile(file).find(
(n) =>
n.name === leaf &&
!isRustAssociatedItem(n) &&
(n.kind === 'function' ||
n.kind === 'struct' ||
n.kind === 'union' ||
+24
View File
@@ -8047,6 +8047,22 @@ function isRustItemVisibleFrom(candidate: Node, ref: UnresolvedRef): boolean {
return candidate.filePath === ref.filePath || ref.filePath.startsWith(`${rustModuleDir(candidate.filePath)}/`);
}
/** What a Rust `impl` declares besides its functions: `type Err = …;`, `const MAX: usize = …;`. */
const RUST_ASSOCIATED_KINDS = new Set(['type_alias', 'variable', 'constant']);
/**
* Whether a Rust item is an associated one: a `type` or `const` written in an
* `impl` or a trait. It belongs to the type, not to the module, so Rust reaches
* it only through a path — `Self::Err`, `<T as FromStr>::Err`, `T::Err` —
* never by its name alone, and no module path (`std::error::Error`) or `use`
* names it. The extractors qualify it by the impl's type (`ValueHint::Err`) or
* its trait, as they do the methods; a module's own type alias or const is
* qualified by nothing.
*/
export function isRustAssociatedItem(n: Node): boolean {
return n.language === 'rust' && RUST_ASSOCIATED_KINDS.has(n.kind) && n.qualifiedName.includes('::');
}
/** Does one of the file's globs bring in this candidate's module (or, for `use super::*`, its parent's)? */
function rustGlobCovers(uses: RustUses, candidate: Node, ref: UnresolvedRef): boolean {
if (uses.globs.has(rustModuleName(candidate.filePath))) return true;
@@ -8105,6 +8121,10 @@ export function isRustNameInScope(candidate: Node, ref: UnresolvedRef, context:
if (seg === 'Self') return candidate.kind === 'type_alias' && isInEnclosingRustImpl(candidate, ref, context);
if (isRustGenericParam(seg, ref, context)) return false;
}
// An associated item, only through its type: `Self::Err` in its impl,
// `<T as FromStr>::Err`. No module path names one: axum-core's
// `std::error::Error` is std's, not its file's `impl IntoResponseParts`'s `type Error`.
if (isRustAssociatedItem(candidate)) return seg === '' || (seg === 'Self' && isInEnclosingRustImpl(candidate, ref, context));
if ((root === 'std' || root === 'core' || root === 'alloc') && candidate.filePath !== ref.filePath) return false;
// A project crate's name re-exports as `crate::` does: `clap::Command` is clap_builder's.
if (seg === '' || seg === 'crate' || seg === 'self' || seg === 'super' || seg === 'Self' || candidate.filePath === ref.filePath ||
@@ -8115,6 +8135,10 @@ export function isRustNameInScope(candidate: Node, ref: UnresolvedRef, context:
return rustModuleName(candidate.filePath) === seg || candidate.filePath.includes(`/${seg}/`) ||
candidate.qualifiedName.split('::').includes(seg);
}
// Nor does its name alone: clap's `Err(…)` calls went to the `type Err` of
// a `FromStr` impl in the same file, axum's routing tests' `Response`s to
// the `type Response` of `routing/mod.rs`'s `impl Service`.
if (isRustAssociatedItem(candidate)) return false;
if (candidate.kind === 'enum_member') {
if (ref.referenceKind === 'references') return false;
const uses = rustUsesOf(ref.filePath, context);