Repository navigation
fix(rust): a variant path references the enum that declares it (#2328) - #2354
Merged
Merged
Conversation
colbymchenry
force-pushed
the
fix/2328-rust-enum-variant-refs
branch
2 times, most recently
from
October 5, 2026 21:52
7c72016 to
bf05b13
Compare
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
force-pushed
the
fix/2328-rust-enum-variant-refs
branch
from
October 5, 2026 21:57
bf05b13 to
ae15d33
Compare
This was referenced Oct 5, 2026
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.
Summary
Issue #2328: a Rust enum used only through its variants (
mode::Mode::Ain an expression,mode::Mode::A => 1/Mode::B => …in amatch, aMode::C(x)orMode::D { .. }pattern) recorded no reference to the enum. Code that builds or matches variants was missing fromcodegraph callers Modeand from impact. Only a type annotation (fn takes(_m: Mode)) produced a reference.With this branch the issue's repro lists
takes,code(src/variant_use.rs) andmain(src/main.rs) as callers ofMode, through both the native kernel and the wasm extractor.Cause
Rust was not part of the static-member pass. That pass turns
Enum.value/Type::CONSTreads intoreferencesedges for Java, C#, Kotlin, Swift, Scala, Dart, PHP and C++. A Rust path to a variant reaches no other extractor unless it is a call or a struct literal.Fix
Extraction (wasm extractor and native kernel, mirrored). A path whose last segment is capitalized emits a
referencesref named by its receiver. The receiver is the segment before the member;Selfis read as the impl's type, and inside a trait the path emits nothing. The ref sits at the receiver, so amode::prefix scopes it exactly as it scopes a type annotation. This covers:|andif letpatterns.map(Mode::C))A call's callee (
Mode::C(1),Foo::new()), a struct literal, the prefix of a longer path, ausetree, and a lowercase receiver or member emit nothing.Resolution (
isRustNameInScope). A reference writtenName::VariantorSelf::Variantlands only on an enum that declares that variant. It goes through the same Rust scoping as any other path: module path,use, glob, prelude, and std/dependency imports. As a result:Match::Nonereachesignore's enum, not the matcher'sstruct Match.Ordering::Less,cmp::Ordering::Greater,Option::Some,Option::None) never reach a same-named project enum.Limits::MAX,Mode::ALL,Self::ALL,Foo::new()) reference no type.Self::Xis read as the impl's type, never through a path written elsewhere on its line.Relation to #2330 (@danusha2345)
This adapts #2330. The extraction half (both walkers) and most of the tests come from it, and the commit credits the author.
What changed:
Self::Xskips the "path written elsewhere on the line" lookup.Node::parent(), which walks down from the tree's root, for every node of every function body. With the native kernel on tokio's 808 files, Rust extraction went from 2.1 s to 3.9 s (interleaved, same dist, min of 5 passes). With this change it is within noise of main (2.1–2.6 s vs 2.1–2.4 s). The wasm path showed no measurable difference either way.EXTRACTION_VERSIONbump. It is already 28 on main.docs/design/rust-lang-kernel-port-checklist.mdno longer says Rust is outside the static-member pass.Verification
__tests__/rust-enum-variant-reference.test.tshas 10 tests. Its fixtures are real temp Cargo projects indexed into SQLite. It covers:Self::Ain the enum's file and in another fileCODEGRAPH_KERNEL=0), in kernel mode with main's kernel binary, and with this TypeScript over main's kernel binary, so the kernel half is exercised. The 4 that pass on main are guards against new false edges.kernel-rustlang-parityhas a new case with every shape plus a non-ASCII column and CRLF. It also asserts the refs are emitted, so the parity check is not vacuous. It passes underCODEGRAPH_KERNEL_EXPECT=1.scripts/kernel-parity.mjsover ripgrep + alacritty + tokio: 1004/1006 files byte-parity, 0 diffs. The 2 parse-error files defer to wasm by policy.npx tsc --noEmitis clean andnpm run buildsucceeds.function-ref,git-index-currencyandsync.syncandgit-index-currencyalso failed alone, with 5 s git timeouts on this loaded Windows box. They failed the same way on main then.Validation
Native kernel loaded. Edges are keyed by source QN / kind / target QN / file / line, comparing main to this branch.
references → enumreferences → enumreferences → enumName::VariantorSelf::Variant, and the target enum declares that variant. There are no std enum targets.Self::edges, all correct.use. The one exception is below.stress-test/examples/simple_echo_tcp.rsoneshot::error::TryRecvError::Emptylinks to mpsc'sTryRecvError. The existing path rule checks only the last module segment (error). Type annotations share that rule. The next line,oneshot::error::TryRecvError::Closed, gets no edge, because mpsc's enum has noClosed.codegraph initmedians were 21.9 s vs 20.5 s, with main's own runs spanning 18.7–25.5 s on this shared box. Resolution time summed over workers was 27.7 s vs 31.2 s.Not in this PR
use Mode::*/use Mode::Aand used bare (A =>). There is no path to read the enum from.matches!(m, Mode::A)).-> Mode) through the Rust framework resolver's PascalCase struct heuristic, because ausebinding is not tied to its module. This is pre-existing; fix(rust): resolve bare calls through exact use bindings #2308 works on module-awareusebindings for calls.Thanks @mg-mg-mg for the report and @danusha2345 for #2330.
🤖 Generated with Claude Code