Skip to content

python/parser: an imported module-level value resolves to MODULE with no hash #1140

Description

@swapnilpaliwal-sd

Symptom

An import of a module-level VALUE carries no pointer to what it names. The parser
resolves it to resolvedTargetKind=MODULE with an EMPTY resolvedTargetHash, so nothing
downstream can reach the declaration.

# signals.py
class Signal: ...
order_placed = Signal()

# publisher.py
from signals import order_placed        # -> MODULE, hash ""

A def or a class imported the same way resolves correctly, with a real hash.

Root cause

The member lookup that runs for a from X import Y statement resolves Y against the
functions and classes declared in X. A module-level assignment is neither, so the lookup
falls through to the module itself and the target hash is left empty. The kind reported is
then MODULE, which is accurate for what was found and wrong for what was written: the
import names a member, not a module.

Census

Counted over one mid-sized public project's parse, by resolvedTargetKind:

kind rows with an empty hash
UNRESOLVED 1859 1859
MODULE 1225 1225
TYPE 817 0
FUNCTION 708 0

Every MODULE-kind row, without exception. Roughly 940 of the 1225 are value-shaped: a
lowercase member imported from a client module, such as from pkg._state import current_app. TYPE and FUNCTION never have an empty hash, so the gap is specific to the
value case rather than general to imports.

Why it matters

Any engine rule that joins two declarations through an imported module-level object stops
at the boundary. That covers a signal object, a settings singleton, a module-level
registry dict and a configured client instance. The rule cannot tell that two modules
importing the same name are talking about the same object, because neither import carries
its identity.

A concrete case is already shipped and pinned. In
graph/python/engine/framework-behavior/dispatch.dl, signal_dispatch joins a publisher
to a receiver by requiring both ends to resolve to the same binding for the signal object.
Across modules each end has its own import binding and the join produces nothing, so the
mechanism is same-module only. graph/test/python/cases/21-url-and-signal-dispatch asserts
that limitation in its golden: three modules sharing one signal produce no edge, and a fix
here will visibly move that file.

The sibling mechanisms are unaffected, which isolates the gap: task_dispatch joins on an
imported def and works across modules, because FUNCTION imports carry a hash.

What would resolve it

A member lookup that also resolves a module-level assignment target, so a value import
reports the binding it names rather than the module it came from. Failing that, any
durable identity for the member would let the engine join by it: the qualified name of the
declaring module plus the member name is already enough, and is how library linking joins
today.

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

    bugSomething isn't workingparserthe parser (parser/), transferred from AxiomCodeAI/parserpythonPython

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions