fix: correct isinstance and issubclass results in compiled builds - #661
Merged
Conversation
Compiled builds returned wrong answers for isinstance and issubclass on
SQLSpec's class hierarchies, and the answer depended on which check ran
first in the process. An ordinary type check was enough to break query
execution:
isinstance(select, CreateIndex) # False, an ordinary check
isinstance(select, QueryBuilder) # False -- wrong, poisoned by the above
session.execute(select) # AttributeError: 'Select' object has
# no attribute 'sql'
mypyc builds compiled classes without running ABCMeta.__new__ for each
class, so every class in a hierarchy inherited a single shared cache
object. The first isinstance or issubclass result was written into that
shared cache and returned for every later check against any class in the
hierarchy. All 54 abstract classes across builder, core, migrations,
storage, and serializer modules shared one cache.
Abstract bases now use plain inheritance. abstractmethod is retained, so
mypy still reports "Cannot instantiate abstract class" and the type-check
gate keeps enforcing implementations. The runtime instantiation guard is
unaffected in practice because mypyc had already disabled it: with ABC in
place, QueryBuilder() constructed successfully and __abstractmethods__ was
absent. Nothing in the codebase relies on virtual subclass registration.
StatementResult no longer inherits collections.abc.Iterable, which carries
the same metaclass. It defines __iter__, so isinstance(result, Iterable)
still succeeds through the structural check.
Also corrects a cast in the migration runner that raised TypeError in
compiled builds whenever a runner had no configuration attached. cast is a
runtime type check under mypyc, and the value is legitimately None.
Reproduces on CPython 3.10 through 3.14 and is not fixed upstream.
Claude-Session: https://claude.ai/code/session_01CKBtk2CVvc3nNRAx4NJ56K
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #661 +/- ##
==========================================
+ Coverage 77.20% 77.22% +0.01%
==========================================
Files 475 475
Lines 67825 67824 -1
Branches 9322 9322
==========================================
+ Hits 52367 52375 +8
+ Misses 12046 12037 -9
Partials 3412 3412
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
cofin
force-pushed
the
fix/mypyc-compiled-runtime-casts
branch
from
July 27, 2026 20:33
7d18557 to
03b22b0
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.
Compiled builds returned wrong answers for
isinstanceandissubclasson SQLSpec's own class hierarchies, and which answer you got depended on which check happened first in the process. An ordinary type check was enough to break query execution.ABCMeta.__new__for each class, so every class in a hierarchy inherited one shared cache object. The firstisinstance/issubclassresult was written into that shared cache and then returned for every later check against any class in the hierarchy. All 54 abstract classes across the builder, core, migrations, storage, and serializer modules shared a single cache.ABC. This removes the shared cache entirely — type checks become ordinary MRO lookups.abstractmethodis retained, so mypy still reportsCannot instantiate abstract classand the type-check gate keeps enforcing implementations.ABCin place,QueryBuilder()constructed successfully and__abstractmethods__was absent. Nothing in the codebase uses virtual subclass registration.StatementResultno longer inheritscollections.abc.Iterable, which carries the same metaclass. It defines__iter__, soisinstance(result, Iterable)still succeeds through the structural check.castto a non-optional type raisedTypeError: dict object expected; got Nonewhenever a runner had no configuration attached.castis a runtime type check under mypyc and the value is legitimatelyNone.Scope
The defect reproduces in a twelve-line file with no SQLSpec code involved, on CPython 3.10, 3.11, 3.13 and 3.14, and is not fixed upstream.
@mypyc_attr(native_class=False)— the documented escape hatch — is not usable here: it crashes the compiler on classes with rich comparison methods, and emits invalid C for a non-native base with native subclasses.Interpreted behavior is unaffected; the full interpreted suite passes. Compiled builds go from 54 shared-cache classes to zero.