Skip to content

Rust: enum variant paths (Mode::A) are not references to the enum #2328

Closed

Description

@mg-mg-mg

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 Mode

Actual

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.

Activity

  1. colbymchenry commented on Oct 5, 2026

    @colbymchenry
    Owner

    Thanks @mg-mg-mg for the report and the self-contained repro script.

    Fixed on main in #2354, building on #2330 by @danusha2345. Rust wasn't in the pass that turns Type::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 as Self::A inside the enum's own impl, 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's Match::None reaches the ignore crate's enum, not a struct Match elsewhere.

    What doesn't count: standard-library variants like Some/None/Ok/Err, Option::Some or Ordering::Less, even if your project has its own Option or Ordering; associated functions like Foo::new(), whose call still links the function; and associated consts like Limits::MAX.

    On your script, callers Mode now lists takes, code and main. 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions