fix(mypyc): preserve inherited class attribute defaults under separate=True#10
Merged
Merged
Conversation
georgesittas
force-pushed
the
fix/incremental-defaults-setup-chain
branch
4 times, most recently
from
May 27, 2026 12:34
cf1897f to
7c5a1c4
Compare
georgesittas
force-pushed
the
fix/incremental-defaults-setup-chain
branch
from
May 27, 2026 16:10
8c8b1b1 to
49e4169
Compare
…ss groups (python#21524) The fix for this was included in python#21369, but no dedicated test was added. This adds `testIncrementalBuiltinBaseClassConstruction` to `run-multimodule.test`: three modules compiled with `separate=True`, where step 2 changes a helper module's signature to force the caller to be recompiled while the exception module is only loaded from cache.
…thon#21547) Fixes python#21542 Under `separate=True`, when a subclass is recompiled while its parent is loaded from mypy's incremental cache, parent default-attribute assignments are silently dropped from the subclass's `__mypyc_defaults_setup`. The first read of an inherited default-attr then raises: ``` AttributeError: attribute '<name>' of '<Parent>' undefined ``` `find_attr_initializers` walks `cdef.info.mro` and reads `info.defn.defs.body` for `AssignmentStmt`s. `ClassDef.serialize` (mypy/nodes.py) does not serialize `defs`, so a cache-loaded parent has `defs = Block([])`; the MRO walk collects no parent assignments and the subclass's emitted setup leaves inherited slots in the undefined-sentinel state. This PR implements the fix discussed in the linked issue.
georgesittas
force-pushed
the
fix/incremental-defaults-setup-chain
branch
from
June 4, 2026 13:23
49e4169 to
7500bf1
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Under
separate=True,find_attr_initializerswalked the full MRO and pulled default-valueAssignmentStmtnodes out ofinfo.defn.defs.body. After mypy loads a base class from its incremental cache, that body is always empty (ClassDef.serializedeliberately doesn't serializedefs). When the subclass got recompiled but the parent stayed cache-loaded, the subclass's emitted__mypyc_defaults_setupsilently dropped every inherited initialization. Any access to an inherited attribute through compiled code then raised:That's the bug that has been intermittently breaking sqlglot's CI partial rebuilds and producing wheels that crash at runtime on simple attribute reads (e.g.
if self.ENSURE_BOOLS:insideGenerator.preprocesson a freshly-rebuilt Snowflake instance).Change
find_attr_initializers+generate_attr_defaults_initnow switch onbuilder.options.separate:separate=True: each class's__mypyc_defaults_setupsets only that class's own defaults, then calls the nearest ancestor's__mypyc_defaults_setupto fold in inherited ones (same shape as Python's__init__chain). The chain is robust to cache-loaded bases because it doesn't need their AST — only that the ancestor has amethod_declentry for__mypyc_defaults_setup, which the cachedClassIRretains.separate=False: original inline-everything behavior preserved as an optimization (the AST is always in memory, MRO walk is safe, chain call would be needless runtime overhead).Aligns with @p-sawicki's suggestion in python/mypy#21542.
Trade-off
A new instance pays one method call per inheritance level in
__mypyc_defaults_setup. On a synthetic 7-level chain with 80+ inherited attributes per leaf, instance creation went from ~104 ns → ~119 ns (14% slower). The same scenario's partial-rebuild path went from rebuilding 106 modules in ~64s → 0 modules in ~1s (no more cache invalidation cascade) and the resulting.sofiles are slightly smaller (less duplicated default-init code per subclass).Regression test
testIncrementalCrossModuleInheritedAttrDefaultsWithOverrideinrun-multimodule.test, exercised under bothTestRunSeparateandTestRunMultiFile. Verified that it fails onrelease-2.1without the fix and passes with it.