Closed
Description
Version: 1.6.2 (also current main, 6560052)
Summary
Using an enum only through its variants, in an expression or a match pattern (mode::Mode::A, Mode::B => …), records no reference to the enum. A type annotation (fn takes(_m: Mode)) does. Code that constructs or matches variants without naming the type in a signature (tests, handlers, call sites) is therefore invisible to callers and impact analysis for the enum.
Reproduction
Run with CODEGRAPH=/path/to/codegraph bash repro.sh (creates and deletes a temporary project):
set -euo pipefail
CODEGRAPH="${CODEGRAPH:-codegraph}"
export DO_NOT_TRACK=1 CODEGRAPH_TELEMETRY=0 CODEGRAPH_NO_UPDATE_CHECK=1
proj="$(mktemp -d)"
trap 'rm -rf "$proj"' EXIT
index_project() { (cd "$proj" && git init -q && "$CODEGRAPH" init . >/dev/null 2>&1); }
callers_of() { (cd "$proj" && "$CODEGRAPH" callers "$1" 2>&1); }
mkdir -p "$proj/src"
printf '[package]\nname = "repro"\nversion = "0.1.0"\nedition = "2021"\n' >"$proj/Cargo.toml"
cat >"$proj/src/mode.rs" <<'T'
pub enum Mode {
A,
B,
}
pub fn takes(_m: Mode) {}
T
cat >"$proj/src/variant_use.rs" <<'T'
use crate::mode;
pub fn code(flag: bool) -> u8 {
let m = if flag { mode::Mode::A } else { mode::Mode::B };
match m {
mode::Mode::A => 1,
mode::Mode::B => 2,
}
}
T
cat >"$proj/src/main.rs" <<'T'
mod mode;
mod variant_use;
fn main() {
mode::takes(mode::Mode::A);
let _ = variant_use::code(true);
}
T
index_project
echo "== callers Mode (expected: takes, code in variant_use.rs, main)"; callers_of ModeActual
Callers of "Mode" (1):
function takes [references]
src/mode.rs:6
Expected
code (src/variant_use.rs) and main (src/main.rs) are also listed as referencing Mode.
Metadata
Metadata
Assignees
Labels
No labels
Activity
Phase 4: resolution and extraction accuracy
fix(rust): a variant path references the enum that declares it (#2328)
fix(rust): a variant path references the enum that declares it (#2328)
fix(rust): a variant path references the enum that declares it (#2328)
fix(rust): a variant path references the enum that declares it (#2328) (
colbymchenry commentedon Oct 5, 2026
Thanks @mg-mg-mg for the report and the self-contained repro script.
Fixed on
mainin #2354, building on #2330 by @danusha2345. Rust wasn't in the pass that turnsType::VALUE-style reads into references, so a variant path that isn't a call or a struct literal reached no extractor at all. Now a variant path, written as a value (mode::Mode::A), in a match pattern (Mode::B =>,Mode::C(x) =>,Mode::D { .. } =>), or asSelf::Ainside the enum's ownimpl, references the enum. It resolves through the same module scoping as a type annotation, and it only lands on an enum that actually declares that variant. So ripgrep'sMatch::Nonereaches theignorecrate's enum, not astruct Matchelsewhere.What doesn't count: standard-library variants like
Some/None/Ok/Err,Option::SomeorOrdering::Less, even if your project has its ownOptionorOrdering; associated functions likeFoo::new(), whose call still links the function; and associated consts likeLimits::MAX.On your script,
callers Modenow liststakes,codeandmain. On ripgrep, alacritty and tokio this added 822, 917 and 665 links and removed none. Released builds parse Rust in the native parser, which has the same change.Not covered yet: variants used bare after
use Mode::*, and paths inside macros.This ships in the next release. Re-index after upgrading (
codegraph index) to pick it up. You and @danusha2345 are credited in the changelog.