Skip to content

fix(python): bind a nested class base to an earlier sibling of its class body - #2695

Merged
DeusData merged 3 commits into
mainfrom
fix/py-class-body-sibling-base
Oct 19, 2026
Merged

DeusData merged 3 commits into
mainfrom
fix/py-class-body-sibling-base

Conversation

@DeusData

Copy link
Copy Markdown
Owner

What

Python evaluates a class statement's bases in the body that encloses it. In a class body, a bare base
therefore names an earlier sibling of that body before any module-level or imported class of the same
name:

# the module also imports sqlalchemy's exc module, which defines DatabaseError, IntegrityError, ...
class AsyncAdapt_asyncpg_dbapi:
    class Error(...): ...
    class DatabaseError(Error): ...
    class IntegrityError(DatabaseError): ...   # the sibling, not exc.DatabaseError

The base resolvers sent such a base through the registry's name strategies, which prefer an
import-reachable candidate. So the sibling lost as soon as the module's exc import resolved, and
sqlalchemy's whole asyncpg exception hierarchy hung on sqlalchemy.exc. Today that only happens by luck:
sqlalchemy reaches exc through a relative import, which #2680 makes resolve. Merged with #2680, 16
INHERITS edges go wrong without this change.

  • registry.c: cbm_registry_resolve_python_class_body_base looks up <enclosing class>.<base>
    exactly, requiring both the enclosing class and the sibling to be type-like. It binds the sibling only
    when it is defined earlier in the same file, using the graph-buffer node lines. A later sibling is not
    bound yet when the base is evaluated: class Early(Later) before class Later keeps the module's
    Later.
  • The sequential semantic pass (which incremental re-indexing reruns), the parallel resolver and the
    cross-LSP base resolver ask it first. A sibling needs no external-base gate, because the class body
    shadows module imports. Other languages and module-level classes are unchanged.

No index-format change.

Evidence

Measured where the regression lives: main 1346ac4 merged with #2680 (99dafc1), without and with this
change; the two trees differ by this change only. 26 Python repos (manifest v1), production builds, macOS
arm64, --repeat 2, every repeat byte-identical.

type base this PR +added -removed
INHERITS 78,608 78,608 21 21
OVERRIDE 82,559 82,559 2 2

Every other edge type is identical (2 CALLS keep their target and change attributes only). All 21
INHERITS changes are retargets, and every one is a correction:

  • sqlalchemy (15): AsyncAdapt_asyncpg_dbapi.IntegrityError, .InternalError, .DataError,
    .OperationalError and the rest now inherit their sibling nested class, not sqlalchemy.exc.*.
  • matplotlib (1): _FancyAxislineStyle.FilledArrow inherits its sibling
    _FancyAxislineStyle.SimpleArrow, not AxislineStyle.SimpleArrow.
  • ansible (4): the test-local TestGetAllSubclasses.BranchI / BranchII inherit
    TestGetAllSubclasses.Base, not ansible.playbook.base.Base.
  • pydantic (1): TestSmartUnionWithSubclass.ModelB inherits TestSmartUnionWithSubclass.ModelA, not
    TestModelClass.ModelA.
  • OVERRIDE follows: FilledArrow.__init__ overrides _FancyAxislineStyle.SimpleArrow.__init__, and
    InvalidCachedStatementError.__init__ overrides AsyncAdapt_asyncpg_dbapi.Error.__init__.

Tests

  • pipeline_python_class_body_sibling_base{,_parallel}: an asyncpg-shaped nested exception hierarchy
    whose module imports exc both as a module and by name, plus a second adapter with the same nested
    names. Each nested base binds its earlier sibling, a non-sibling base keeps its import, and a later
    sibling is never bound. Both venues, each followed by an incremental re-index. RED without the fix
    (ASSERT(ie_sib[run])).
  • Affected suites (complexity, extraction, extraction_inheritance, registry, pipeline,
    pipeline_semantic_manifest_repro, cross_repo, py_lsp, py_lsp_stress, py_lsp_scale, the go / c / php / cs
    / ts / java / kotlin / rust LSP suites, lsp_resolution_probe, edge_structural, edge_types_probe,
    convergence_probe, matrix_known_classes, parallel, incremental, integration) pass.

…ss body

The sqlalchemy asyncpg adapter re-declares the DBAPI exception hierarchy as
classes nested in one class body, each based on an earlier sibling spelled
bare (class IntegrityError(DatabaseError)). Python evaluates the bases in the
enclosing class body, so the sibling wins over same-named module classes and
over another adapter's nested namesakes; a sibling defined later is not bound
yet. Sequential and parallel pipeline, each followed by an incremental
re-index.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
sqlalchemy's asyncpg adapter imports its exc module; once that import
resolves, name guesses prefer exc's same-named classes over the earlier
sibling of the class body. The fixture now imports exc as a module too.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
…ass body

Python evaluates a class statement's bases in the body that encloses it, so
a bare base of a class nested in a class names an earlier sibling of that
body (sqlalchemy's asyncpg adapter: class IntegrityError(DatabaseError))
before any same-named module or imported class. The base resolvers ran the
registry's name strategies instead, which prefer an import-reachable
candidate, so the sibling lost as soon as the adapter's exc import resolved.

cbm_registry_resolve_python_class_body_base looks the sibling up exactly
(<enclosing class>.<base>, both type-like) and requires it to be defined
earlier in the same file, from the graph-buffer node lines: a later sibling
is not bound yet when the base is evaluated. The sequential and incremental
semantic pass, the parallel resolver and the cross-LSP base resolver ask it
first; a sibling needs no external-base gate.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
@DeusData
DeusData merged commit 90d6d64 into main Oct 19, 2026
29 checks passed
thugrock7 pushed a commit to thugrock7/codebase-memory-mcp that referenced this pull request Oct 19, 2026
…a#2680, DeusData#2690, DeusData#2695, DeusData#2696)

py_lsp.c: the module-member lookup of this branch and the C3 super() lookup of DeusData#2696 were added at the same place; both are kept.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant