Skip to content

[Bug] A call site can resolve to a module, and a module is not callable #1886

Description

@RaghavChamadiya

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:

pub mod error;

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.

  1. _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.

  2. 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

pub mod warn;

src/warn/mod.rs

pub fn helper() {}

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.

Metadata

Metadata

Assignees

Labels

bugSomething isn't workinghelp wantedExtra attention is needed

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions