Skip to content

expressions: __getattr__ dispatch can route to inherited Protocol stubs #114

Description

@discreteds

Problem

BaseExpressionAPI.__getattr__ (line 91 of api_base.py) uses hasattr(ns_cls, name) to check if a builder handles an attribute. Because builders inherit from their Protocol classes, protocol method stubs are visible to hasattr even if the concrete builder doesn't override them.

for ns_cls in self._FLAT_NAMESPACES:
    ns = ns_cls(self)
    if hasattr(ns_cls, name):  # ← matches inherited Protocol stubs too
        return getattr(ns, name)

If a builder inherits a protocol but forgets to implement one of its methods, the dispatch would silently bind the protocol stub (returning None or ...) instead of raising AttributeError.

Why it hasn't bitten yet

The test_protocol_alignment.py tests enforce that every protocol method has a concrete implementation on the composed ExpressionSystem. But no test validates the dispatch mechanism itself — specifically that a dispatched call hits a real implementation rather than an inherited stub.

Fix

Change dispatch to check name in ns_cls.__dict__ instead of hasattr(ns_cls, name). This only routes to methods defined directly on the concrete builder class.

Secondary concern: dir() coverage

dir(ExpressionAPI_instance) reflects __getattr__-dispatched methods only if __dir__ is overridden. Currently no __dir__ exists. This isn't a bug (no test relies on dir() for expressions), but becomes relevant when relations adopt the same pattern.

Discovered during

Codex adversarial review of the Relation API builder decomposition spec (2026-05-06).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions