Skip to content

A Rust #[cfg(test)] function is unreachable from its bare name in the by-name index #2369

Description

@BobbieBarker

This issue and its PR were written by an AI agent working on my behalf. I certify the DCO sign-off on every commit and answer for the change.

A Rust function carrying #[cfg(test)] is absent from the by-name index, so no bare callee resolves to it.

cbm_registry_add takes the symbol's name and discards it ((void)name;), deriving the bare-name lookup key from the QN's last dot segment instead. For most grammars the two agree. For this one they do not: rust_cfg_qualified_name mints the twin as proj.lib.add#cfg(test), and simple_name() splits on . and :: with no # handling, so the derived key is the whole string add#cfg(test).

Nothing is ever looked up under that string. Every by-name read keys through simple_name() of the callee as written, and a callee is written add. So the function is registered, occupies a bucket, and is unreachable.

Reproducer, at the registry's own level:

cbm_registry_t *r = cbm_registry_new();
cbm_registry_add(r, "add", "proj.lib.add#cfg(test)", "Function");
cbm_resolution_t res = cbm_registry_resolve(r, "add", "proj.lib.caller", NULL, NULL, 0);
/* res.qualified_name is NULL; expected "proj.lib.add#cfg(test)" */

The visible effect is on a Rust crate's test code: a #[cfg(test)] helper called by name from another test module gets no inbound CALLS edge, and fan_in on it is 0 whatever calls it.

The fix I have keys the index on the derived segment exactly as today, and takes a second key from the passed name only when the derived segment carries a #. Every grammar that mints no # stays byte-identical, and you can check that from the keys themselves: a passed name differing from the derived key by anything other than a fence is a key no simple_name()-based lookup can reach, so adding it would only widen the bucket the scorer walks.

Two things change what the fix has to cover.

cbm_label_is_registry_symbol gates all three production callers and admits neither Module nor Section, so a file's Module node and a Markdown heading never reach this function. Among the dotted-name shapes only HCL, TOML and INI do, under label Class, and those are why the derived key stays unconditional: instance = aws_instance.web.id reaches an HCL block through the tail web, so re-keying on the passed name alone would drop that edge.

The second key puts the cfg twin into the same bare-name bucket as the real function. candidate_score applies REG_TEST_PENALTY to a candidate whose QN spells test, so the real add outranks add#cfg(test). That protection is a substring test on the QN, so it does not reach a #[test] function whose cfg predicate says something else, and Rust's inline #[cfg(test)] mod tests puts no test segment in the path either.

Both costs are now measured, on ripgrep 14.1.1, tokio 1.40.0 and rust-analyzer 2024-09-30. Bucket width holds: the largest bucket is unchanged on all three, no bucket crosses REG_MAX_CANDIDATES, and the count of lookups that bail on the cap is identical. Resolution moves at 54 call sites: 47 onto the function the source actually calls, 6 onto a cfg-fenced #[test] function that outscores the real callee, and 1 onto nothing. Figures and the six regressions are in #2370.

Raised separately from #2312 at the maintainer's request, as its own defect.

Activity

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

    parsing/qualityGraph extraction bugs, false positives, missing edges

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions