Summary
A call site can resolve to a symbol whose kind is module, and a module is not
callable in any language we index. The edge is minted at 0.85 or 0.50
confidence and points at a mod declaration.
Seen on bevy: error!( at crates/bevy_pbr/src/render/mesh.rs:2907 resolves to
crates/bevy_ecs/src/lib.rs::error, which is line 36 of that file:
There is no macro_rules! error anywhere in bevy_ecs/src/lib.rs. The real
target is tracing's logging macro, brought in by mesh.rs:85:
use tracing::{error, warn};
mesh.rs also imports from bevy_ecs (mesh.rs:24-25), so bevy_ecs's
symbols are in the merged lookup, and the module named error is the one that
answers.
Mechanism
Two things combine, and each is independently a defect.
-
_NON_CALLABLE_KINDS in packages/core/src/repowise/core/ingestion/call_resolver.py:129
is frozenset({"property"}). module is not in it, so a module symbol is
treated as a legitimate call target everywhere.
-
That set is consulted on the global-unique tier only
(call_resolver.py:1137). The import tiers above it, import_scoped at
:1109 and import_merged at :1113-1115, apply no kind filter at all, so
they can answer with a non-callable symbol even once the set is corrected.
The bevy site above goes through import_merged.
Repro
Three files, no external crates:
src/lib.rs
src/warn/mod.rs
src/user.rs
use crate::warn;
fn run() {
warn!("something happened");
}
Result on main:
calls src/user.rs::run -> src/lib.rs::warn | callee kind: module | origin: global_unique
warn! is the standard logging macro. The edge instead lands on a module
declaration. Note this repro fires on the global-unique tier, which does have
the filter, so it isolates half 1 above; the bevy site isolates half 2.
Impact
Unsized. It was found as the single wrong target in 30 rust call sites read
from source during a precision audit, so 1 in 30 on that sample and nothing
more is claimed. The first thing to measure is how many call sites across the
corpus resolve to a symbol whose kind is module, per language, since the
merged-import tier is language-agnostic and rust is unlikely to be the only one
affected.
Worth knowing: this is the same class as the fix in dc797b36, which taught
the global-unique tier that a struct field is not callable. That commit is the
model to copy, including its rule.
Done looks like
module is refused as a call target, and the refusal is applied on the import
tiers as well as the global-unique one.
Two constraints carried over from dc797b36, both load bearing:
- Filter the answer, never the pool. The refusal must sit after the
candidate has been chosen and must return rather than fall through, so the
tier can lose an edge and can never gain one. Filtering the candidate pool
before the uniqueness test re-uniquifies a name that two symbols share and
fires the tier where it used to refuse.
- Grade removals per language. A tier registered for one language family
does not share its premise elsewhere. Report what is removed per language
rather than in total.
Gate: removed edges read wrong on a sample from the population diff, controls
byte-identical on repositories with no module-kind symbols, and dead-code
findings unmoved. "Removes N, gains 0" is not sufficient on its own.
Tests: the repro above pinned as a parser or resolver test, plus one covering
the import-tier path, plus a control showing an ordinary free-function call in
the same file still resolves.
Summary
A call site can resolve to a symbol whose kind is
module, and a module is notcallable in any language we index. The edge is minted at 0.85 or 0.50
confidence and points at a
moddeclaration.Seen on bevy:
error!(atcrates/bevy_pbr/src/render/mesh.rs:2907resolves tocrates/bevy_ecs/src/lib.rs::error, which is line 36 of that file:There is no
macro_rules! erroranywhere inbevy_ecs/src/lib.rs. The realtarget is tracing's logging macro, brought in by
mesh.rs:85:mesh.rsalso imports frombevy_ecs(mesh.rs:24-25), sobevy_ecs'ssymbols are in the merged lookup, and the module named
erroris the one thatanswers.
Mechanism
Two things combine, and each is independently a defect.
_NON_CALLABLE_KINDSinpackages/core/src/repowise/core/ingestion/call_resolver.py:129is
frozenset({"property"}).moduleis not in it, so a module symbol istreated as a legitimate call target everywhere.
That set is consulted on the global-unique tier only
(
call_resolver.py:1137). The import tiers above it,import_scopedat:1109andimport_mergedat:1113-1115, apply no kind filter at all, sothey can answer with a non-callable symbol even once the set is corrected.
The bevy site above goes through
import_merged.Repro
Three files, no external crates:
src/lib.rssrc/warn/mod.rssrc/user.rsResult on
main:warn!is the standard logging macro. The edge instead lands on a moduledeclaration. Note this repro fires on the global-unique tier, which does have
the filter, so it isolates half 1 above; the bevy site isolates half 2.
Impact
Unsized. It was found as the single wrong target in 30 rust call sites read
from source during a precision audit, so 1 in 30 on that sample and nothing
more is claimed. The first thing to measure is how many call sites across the
corpus resolve to a symbol whose kind is
module, per language, since themerged-import tier is language-agnostic and rust is unlikely to be the only one
affected.
Worth knowing: this is the same class as the fix in
dc797b36, which taughtthe global-unique tier that a struct field is not callable. That commit is the
model to copy, including its rule.
Done looks like
moduleis refused as a call target, and the refusal is applied on the importtiers as well as the global-unique one.
Two constraints carried over from
dc797b36, both load bearing:candidate has been chosen and must return rather than fall through, so the
tier can lose an edge and can never gain one. Filtering the candidate pool
before the uniqueness test re-uniquifies a name that two symbols share and
fires the tier where it used to refuse.
does not share its premise elsewhere. Report what is removed per language
rather than in total.
Gate: removed edges read wrong on a sample from the population diff, controls
byte-identical on repositories with no module-kind symbols, and dead-code
findings unmoved. "Removes N, gains 0" is not sufficient on its own.
Tests: the repro above pinned as a parser or resolver test, plus one covering
the import-tier path, plus a control showing an ordinary free-function call in
the same file still resolves.