Repository navigation
fix(rust): a variant path references its enum (#2328) - #2330
Closed
danusha2345 wants to merge 1 commit into
Closed
danusha2345 wants to merge 1 commit into
danusha2345 wants to merge 1 commit into
Conversation
An enum used only through its variants - `mode::Mode::A` in an
expression, `Mode::B => ...` / `Mode::C(x)` / `Mode::D { .. }` in a
pattern - recorded no reference to the enum, so the functions doing it
were missing from the enum's callers and impact.
Rust joins the static-member pass the other capitalized-type languages
already run: a path whose member is capitalized (a variant or an
associated const) emits a `references` ref to its receiver, the segment
before the member, at the place it is written - the same bare-name ref a
type annotation produces, so the `mode::` / `other::` prefix scopes it
the same way. `Self::A` names the impl's type. A lowercase receiver
(module) or member (function), a call's callee and a struct literal's
name (both already linked to their member), the prefix of a longer path
and `use` trees emit nothing. Native kernel and wasm extractor change
together.
Resolution: a type read through one of its items is a type that
declares that item - an enum with that variant, or a type whose file
defines that associated const. Without it, a bare name shared across
crates (ripgrep's `Match::None`, `Error::Partial`) or a looser path
match (tokio's `scheduler::Handle::MultiThread`) landed on a same-named
struct without that member.
EXTRACTION_VERSION 27 -> 28.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
danusha2345
pushed a commit
to danusha2345/codegraph
that referenced
this pull request
Oct 3, 2026
# Conflicts: # CHANGELOG.md # src/extraction/extraction-version.ts
danusha2345
pushed a commit
to danusha2345/codegraph
that referenced
this pull request
Oct 3, 2026
colbymchenry
added a commit
that referenced
this pull request
Oct 5, 2026
Using an enum only through its variants (`mode::Mode::A` in an expression,
`mode::Mode::A => 1` / `Mode::B => ...` in a match, a `Mode::C(x)` or
`Mode::D { .. }` pattern) recorded no reference to the enum, so code that
builds or matches variants was missing from the enum's callers and impact.
Only a type annotation (`fn takes(_m: Mode)`) produced one.
Cause: Rust was not in the static-member pass, which turns `Enum.value` /
`Type::CONST` reads into references for Java, C#, Kotlin, Swift, Scala,
Dart, PHP and C++, and a path to a variant that is not a call or a struct
literal reaches no other extractor.
Fix:
- Extraction (wasm extractor and native kernel, mirrored): a path whose
last segment is capitalized emits a `references` ref named by its
receiver (the segment before it; `Self` read as the impl's type, nothing
in a trait), positioned at the receiver, so a `mode::` prefix scopes it
exactly as it scopes a type annotation. Covers value reads, match arms,
`|` / `if let` patterns, tuple- and struct-variant patterns and a variant
passed as a value. A call's callee (`Mode::C(1)`, `Foo::new()`), a struct
literal, a longer path's prefix, a `use` tree and a lowercase receiver or
member emit nothing.
- Resolution (isRustNameInScope): a reference written `Name::Variant` or
`Self::Variant` lands only on an enum that declares that variant, after
the usual Rust scoping (module path, `use`, glob, prelude, std and
dependency imports). ripgrep's `Match::None` reaches `ignore`'s enum, not
the matcher's `struct Match`; std variants (`Ordering::Less`,
`Option::Some`) never reach a same-named project enum; associated consts
and functions (`Limits::MAX`, `Mode::ALL`, `Foo::new()`) reference no
type. `Self::X` is read as the impl's type, never through a path written
elsewhere on its line.
Adapted from #2330 by @danusha2345: the extraction half (both walkers) and
most of the tests are theirs. Changed here: the resolver accepts a declared
variant only. #2330 also accepted any type whose file holds a variable or
const of the item's name; impl consts are file-level variables in the graph,
so that cannot tell a type's associated const from another type's or a
top-level one. EXTRACTION_VERSION is not bumped again (already 28 on main).
Verification: __tests__/rust-enum-variant-reference.test.ts (10 tests):
6 fail on main in wasm mode, in kernel mode, and with this TypeScript over
main's kernel binary; 10/10 pass in both modes. The kernel parity case
covers every shape and asserts the refs exist; the kernel-parity sweep over
ripgrep, alacritty and tokio is 1004/1006 files byte-parity, 0 diffs (2
parse-error files defer to wasm by policy).
Validation (native kernel loaded, main -> branch, edges keyed by
QN/kind/file/line): ripgrep +822/-0, alacritty +917/-0, tokio +665/-0, all
`references -> enum`, node counts unchanged. Every added edge's source
reads `Name::Variant` / `Self::Variant` at its column and its target enum
declares that variant; no std enum targets. New symbol->enum pairs:
ripgrep +220, alacritty +253, tokio +254; enums with no referencer before
and one now: 31 / 13 / 31. One wrong target on tokio
(`oneshot::error::TryRecvError::Empty` -> mpsc's `TryRecvError`) comes
from the existing path rule that checks only the last module segment, which
type annotations share.
Co-authored-by: danusha2345 <ewidusoc498@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
colbymchenry
added a commit
that referenced
this pull request
Oct 5, 2026
Using an enum only through its variants (`mode::Mode::A` in an expression,
`mode::Mode::A => 1` / `Mode::B => ...` in a match, a `Mode::C(x)` or
`Mode::D { .. }` pattern) recorded no reference to the enum, so code that
builds or matches variants was missing from the enum's callers and impact.
Only a type annotation (`fn takes(_m: Mode)`) produced one.
Cause: Rust was not in the static-member pass, which turns `Enum.value` /
`Type::CONST` reads into references for Java, C#, Kotlin, Swift, Scala,
Dart, PHP and C++, and a path to a variant that is not a call or a struct
literal reaches no other extractor.
Fix:
- Extraction (wasm extractor and native kernel, mirrored): a path whose
last segment is capitalized emits a `references` ref named by its
receiver (the segment before it; `Self` read as the impl's type, nothing
in a trait), positioned at the receiver, so a `mode::` prefix scopes it
exactly as it scopes a type annotation. Covers value reads, match arms,
`|` / `if let` patterns, tuple- and struct-variant patterns and a variant
passed as a value. A call's callee (`Mode::C(1)`, `Foo::new()`), a struct
literal, a longer path's prefix, a `use` tree and a lowercase receiver or
member emit nothing.
- Resolution (isRustNameInScope): a reference written `Name::Variant` or
`Self::Variant` lands only on an enum that declares that variant, after
the usual Rust scoping (module path, `use`, glob, prelude, std and
dependency imports). ripgrep's `Match::None` reaches `ignore`'s enum, not
the matcher's `struct Match`; std variants (`Ordering::Less`,
`Option::Some`) never reach a same-named project enum; associated consts
and functions (`Limits::MAX`, `Mode::ALL`, `Foo::new()`) reference no
type. `Self::X` is read as the impl's type, never through a path written
elsewhere on its line.
Adapted from #2330 by @danusha2345: the extraction half (both walkers) and
most of the tests are theirs. Changed here:
- The resolver accepts a declared variant only. #2330 also accepted any type
whose file holds a variable or const of the item's name; impl consts are
file-level variables in the graph, so that cannot tell a type's associated
const from another type's or a top-level one.
- The walkers check the node kind before looking at its parent, and the
body walk hands the parent down: #2330 called `parent()`, which walks down
from the tree's root, for every node of every body, which nearly doubled
the kernel's Rust extraction time on tokio (2.1 s -> 3.9 s); now within
noise of main.
- EXTRACTION_VERSION is not bumped again (already 28 on main).
Verification: __tests__/rust-enum-variant-reference.test.ts (10 tests):
6 fail on main in wasm mode, in kernel mode, and with this TypeScript over
main's kernel binary; 10/10 pass in both modes. The kernel parity case
covers every shape and asserts the refs exist; the kernel-parity sweep over
ripgrep, alacritty and tokio is 1004/1006 files byte-parity, 0 diffs (2
parse-error files defer to wasm by policy).
Validation (native kernel loaded, main -> branch, edges keyed by
QN/kind/file/line): ripgrep +822/-0, alacritty +917/-0, tokio +665/-0, all
`references -> enum`, node counts unchanged. Every added edge's source
reads `Name::Variant` / `Self::Variant` at its column and its target enum
declares that variant; no std enum targets. New symbol->enum pairs:
ripgrep +220, alacritty +253, tokio +254; enums with no referencer before
and one now: 31 / 13 / 31. One wrong target on tokio
(`oneshot::error::TryRecvError::Empty` -> mpsc's `TryRecvError`) comes
from the existing path rule that checks only the last module segment, which
type annotations share. Index time on tokio stays within noise of main
(interleaved runs; resolveOne time 27.7 s vs 31.2 s summed over workers).
Co-authored-by: danusha2345 <ewidusoc498@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
colbymchenry
added a commit
that referenced
this pull request
Oct 5, 2026
Using an enum only through its variants (`mode::Mode::A` in an expression,
`mode::Mode::A => 1` / `Mode::B => ...` in a match, a `Mode::C(x)` or
`Mode::D { .. }` pattern) recorded no reference to the enum, so code that
builds or matches variants was missing from the enum's callers and impact.
Only a type annotation (`fn takes(_m: Mode)`) produced one.
Cause: Rust was not in the static-member pass, which turns `Enum.value` /
`Type::CONST` reads into references for Java, C#, Kotlin, Swift, Scala,
Dart, PHP and C++, and a path to a variant that is not a call or a struct
literal reaches no other extractor.
Fix:
- Extraction (wasm extractor and native kernel, mirrored): a path whose
last segment is capitalized emits a `references` ref named by its
receiver (the segment before it; `Self` read as the impl's type, nothing
in a trait), positioned at the receiver, so a `mode::` prefix scopes it
exactly as it scopes a type annotation. Covers value reads, match arms,
`|` / `if let` patterns, tuple- and struct-variant patterns and a variant
passed as a value. A call's callee (`Mode::C(1)`, `Foo::new()`), a struct
literal, a longer path's prefix, a `use` tree and a lowercase receiver or
member emit nothing.
- Resolution (isRustNameInScope): a reference written `Name::Variant` or
`Self::Variant` lands only on an enum that declares that variant, after
the usual Rust scoping (module path, `use`, glob, prelude, std and
dependency imports). ripgrep's `Match::None` reaches `ignore`'s enum, not
the matcher's `struct Match`; std variants (`Ordering::Less`,
`Option::Some`) never reach a same-named project enum; associated consts
and functions (`Limits::MAX`, `Mode::ALL`, `Foo::new()`) reference no
type. `Self::X` is read as the impl's type, never through a path written
elsewhere on its line.
Adapted from #2330 by @danusha2345: the extraction half (both walkers) and
most of the tests are theirs. Changed here:
- The resolver accepts a declared variant only. #2330 also accepted any type
whose file holds a variable or const of the item's name; impl consts are
file-level variables in the graph, so that cannot tell a type's associated
const from another type's or a top-level one.
- The walkers check the node kind before looking at its parent, and the
body walk hands the parent down: #2330 called `parent()`, which walks down
from the tree's root, for every node of every body, which nearly doubled
the kernel's Rust extraction time on tokio (2.1 s -> 3.9 s); now within
noise of main.
- EXTRACTION_VERSION is not bumped again (already 28 on main).
Verification: __tests__/rust-enum-variant-reference.test.ts (10 tests):
6 fail on main in wasm mode, in kernel mode, and with this TypeScript over
main's kernel binary; 10/10 pass in both modes. The kernel parity case
covers every shape and asserts the refs exist; the kernel-parity sweep over
ripgrep, alacritty and tokio is 1004/1006 files byte-parity, 0 diffs (2
parse-error files defer to wasm by policy).
Validation (native kernel loaded, main -> branch, edges keyed by
QN/kind/file/line): ripgrep +822/-0, alacritty +917/-0, tokio +665/-0, all
`references -> enum`, node counts unchanged. Every added edge's source
reads `Name::Variant` / `Self::Variant` at its column and its target enum
declares that variant; no std enum targets. New symbol->enum pairs:
ripgrep +220, alacritty +253, tokio +254; enums with no referencer before
and one now: 31 / 13 / 31. One wrong target on tokio
(`oneshot::error::TryRecvError::Empty` -> mpsc's `TryRecvError`) comes
from the existing path rule that checks only the last module segment, which
type annotations share. Index time on tokio stays within noise of main
(interleaved runs; resolveOne time 27.7 s vs 31.2 s summed over workers).
Co-authored-by: danusha2345 <ewidusoc498@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
colbymchenry
added a commit
that referenced
this pull request
Oct 5, 2026
#2354) Using an enum only through its variants (`mode::Mode::A` in an expression, `mode::Mode::A => 1` / `Mode::B => ...` in a match, a `Mode::C(x)` or `Mode::D { .. }` pattern) recorded no reference to the enum, so code that builds or matches variants was missing from the enum's callers and impact. Only a type annotation (`fn takes(_m: Mode)`) produced one. Cause: Rust was not in the static-member pass, which turns `Enum.value` / `Type::CONST` reads into references for Java, C#, Kotlin, Swift, Scala, Dart, PHP and C++, and a path to a variant that is not a call or a struct literal reaches no other extractor. Fix: - Extraction (wasm extractor and native kernel, mirrored): a path whose last segment is capitalized emits a `references` ref named by its receiver (the segment before it; `Self` read as the impl's type, nothing in a trait), positioned at the receiver, so a `mode::` prefix scopes it exactly as it scopes a type annotation. Covers value reads, match arms, `|` / `if let` patterns, tuple- and struct-variant patterns and a variant passed as a value. A call's callee (`Mode::C(1)`, `Foo::new()`), a struct literal, a longer path's prefix, a `use` tree and a lowercase receiver or member emit nothing. - Resolution (isRustNameInScope): a reference written `Name::Variant` or `Self::Variant` lands only on an enum that declares that variant, after the usual Rust scoping (module path, `use`, glob, prelude, std and dependency imports). ripgrep's `Match::None` reaches `ignore`'s enum, not the matcher's `struct Match`; std variants (`Ordering::Less`, `Option::Some`) never reach a same-named project enum; associated consts and functions (`Limits::MAX`, `Mode::ALL`, `Foo::new()`) reference no type. `Self::X` is read as the impl's type, never through a path written elsewhere on its line. Adapted from #2330 by @danusha2345: the extraction half (both walkers) and most of the tests are theirs. Changed here: - The resolver accepts a declared variant only. #2330 also accepted any type whose file holds a variable or const of the item's name; impl consts are file-level variables in the graph, so that cannot tell a type's associated const from another type's or a top-level one. - The walkers check the node kind before looking at its parent, and the body walk hands the parent down: #2330 called `parent()`, which walks down from the tree's root, for every node of every body, which nearly doubled the kernel's Rust extraction time on tokio (2.1 s -> 3.9 s); now within noise of main. - EXTRACTION_VERSION is not bumped again (already 28 on main). Verification: __tests__/rust-enum-variant-reference.test.ts (10 tests): 6 fail on main in wasm mode, in kernel mode, and with this TypeScript over main's kernel binary; 10/10 pass in both modes. The kernel parity case covers every shape and asserts the refs exist; the kernel-parity sweep over ripgrep, alacritty and tokio is 1004/1006 files byte-parity, 0 diffs (2 parse-error files defer to wasm by policy). Validation (native kernel loaded, main -> branch, edges keyed by QN/kind/file/line): ripgrep +822/-0, alacritty +917/-0, tokio +665/-0, all `references -> enum`, node counts unchanged. Every added edge's source reads `Name::Variant` / `Self::Variant` at its column and its target enum declares that variant; no std enum targets. New symbol->enum pairs: ripgrep +220, alacritty +253, tokio +254; enums with no referencer before and one now: 31 / 13 / 31. One wrong target on tokio (`oneshot::error::TryRecvError::Empty` -> mpsc's `TryRecvError`) comes from the existing path rule that checks only the last module segment, which type annotations share. Index time on tokio stays within noise of main (interleaved runs; resolveOne time 27.7 s vs 31.2 s summed over workers). Co-authored-by: danusha2345 <ewidusoc498@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Owner
|
Thanks @danusha2345! This landed in #2354, which keeps your extraction code in both the TypeScript extractor and the native kernel, and most of your tests. Three changes on top. Associated consts like |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #2328. Thanks @mg-mg-mg for the report and the repro script.
Problem
A Rust enum used only through its variants records no reference to the enum. This covers
mode::Mode::Ain an expression andMode::B => …,Mode::C(x)orMode::D { .. }in a pattern. A type annotation (fn takes(_m: Mode)) does record one. So the functions that build or match on variants without naming the type in a signature were missing from the enum's callers and impact.Fix
Extraction (wasm extractor and native kernel together). Rust joins the static-member pass that the other capitalized-type languages already run (
Enum.value,Type.CONST). A path whose member is capitalized, meaning a variant or an associated const, emits areferencesref to its receiver. The receiver is the segment before the member, and the ref sits where it is written. This is the same bare-name ref a type annotation produces, so amode::/other::prefix scopes it the way it scopesfn f(_: other::Mode).Mode::A,mode::Mode::A.flip(),.map(Mode::C)), match arms,if let/|patterns, tuple-variant patterns and struct-variant patterns (Mode::D { x }).Self::Ainside an impl names the impl's type.util::take(3),u8::MAX), or a lowercase member, which is a function (Foo::newas a value).Mode::C(1),Foo::new()) and a struct literal's name (Mode::D { x: 1 }). Both are already linked to their member bycalls/instantiates, as in the other languages. SoType::new()still does not referenceType.usetrees.Resolution. In
isRustNameInScope, a type read through one of its items is a type that declares that item. That means an enum with that variant, or a type whose file defines that associated const. Without this, a bare name shared across crates landed on a same-named struct that lacks the member. On ripgrep,ignore'sMatch::NoneandError::Partialwent to the matcher'sstruct Matchand globset'sstruct Error. A loose path match did the same on tokio, wherescheduler::Handle::MultiThreadwent tocurrent_thread'sstruct Handle. With the check, the right enum is the only candidate left, or nothing links.EXTRACTION_VERSION27 → 28.Tests
__tests__/rust-enum-variant-reference.test.tsbuilds real temp Cargo projects in SQLite with no mocks:Mode::Aafteruse, with the edge's line and column;Self::Ain the enum's file and in animplin another file;termcolor::ColorChoice::AlwaysAnsithat does not reach the file's ownColorChoice;Limits::MAX);util::take(3),mode::Mode::new(), andother::Mode::Aagainstmode::Mode;Mode::C(3)/Mode::D { x }stays linked to the variant, and a variant imported withuse Mode::*and used bare gets no edge.CODEGRAPH_KERNEL_EXPECT=1) and with wasm (CODEGRAPH_KERNEL=0). With main's kernel binary under this branch's TypeScript, the native run fails the extraction cases again, so the kernel half is exercised.kernel-rustlang-parityhas a new case covering every shape above, plus a non-ASCII column and CRLF.kernel-*-parity,rust-*,resolution,extractionand the other Rust-touching suites pass in both modes.A/B (origin/main → this branch)
references→ enumInterest::READABLE,Ready::*,Semaphore::MAX_PERMITS)Column::ALL). Every struct edge targets a struct whose file defines the const.oneshot::error::TryRecvError::Emptygoes to mpsc'sTryRecvError, which also hasEmpty. The path-scope check accepts any file namederror.rsfor theerror::segment, and type annotations already have the same issue.Not in this PR
use Mode::*oruse Mode::Aand used bare (A =>). There is no path to read the enum from, and covering it would mean a ref for every capitalized identifier (None,Some, …).matches!(m, Mode::A)). tree-sitter does not parse them, and calls there are not extracted either.-> Self/&Selftype annotation still records no reference.🤖 Generated with Claude Code