Skip to content
Open
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
9a358fc
[mypyc] fix: isinstance_native fast path conflict w/ subclasses
BobTheBuidler Sep 30, 2025
8446150
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 30, 2025
f84f576
handle non-native final
BobTheBuidler Sep 30, 2025
376356b
Update class_ir.py
BobTheBuidler Oct 1, 2025
c22e6a4
Update ll_builder.py
BobTheBuidler Oct 1, 2025
70cdff9
Update class_ir.py
BobTheBuidler Oct 1, 2025
9a50f54
Merge branch 'master' into fix-isinstance-native-interpreted-subclass
BobTheBuidler Oct 1, 2025
7459701
Update irbuild-classes.test
BobTheBuidler Oct 3, 2025
86e5b00
refactor
BobTheBuidler Oct 3, 2025
d8602a0
Update irbuild-classes.test
BobTheBuidler Oct 4, 2025
9fce629
Update irbuild-classes.test
BobTheBuidler Oct 4, 2025
b90961e
Update irbuild-classes.test
BobTheBuidler Oct 4, 2025
a554654
Update irbuild-classes.test
BobTheBuidler Oct 4, 2025
f514765
Update class_ir.py
BobTheBuidler Oct 4, 2025
bee44dc
Update class_ir.py
BobTheBuidler Oct 4, 2025
3158d15
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 4, 2025
71a3c96
Update class_ir.py
BobTheBuidler Oct 4, 2025
89b2e8f
Merge branch 'master' into fix-isinstance-native-interpreted-subclass
BobTheBuidler Oct 7, 2025
da41e3f
Merge branch 'master' into fix-isinstance-native-interpreted-subclass
BobTheBuidler Nov 13, 2025
997364e
Update class_ir.py
BobTheBuidler Nov 15, 2025
726e36e
Update class_ir.py
BobTheBuidler Nov 15, 2025
d8b2f2b
fix: change :: to =
BobTheBuidler Nov 16, 2025
40876a8
fix: non-native class
BobTheBuidler Nov 17, 2025
ec59e5b
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 17, 2025
cfac061
Update class_ir.py
BobTheBuidler Nov 17, 2025
9dbc600
Update ll_builder.py
BobTheBuidler Nov 17, 2025
a379621
Merge branch 'master' into fix-isinstance-native-interpreted-subclass
BobTheBuidler Nov 17, 2025
b617fed
Update class_ir.py
BobTheBuidler Nov 17, 2025
b8aadd6
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 17, 2025
f06e6b6
Update class_ir.py
BobTheBuidler Nov 17, 2025
e47b5f9
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 15 additions & 6 deletions mypyc/ir/class_ir.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,16 @@ def __repr__(self) -> str:
def fullname(self) -> str:
return f"{self.module_name}.{self.name}"

@property
def allow_interpreted_subclasses(self) -> bool:
Copy link
Contributor Author

@BobTheBuidler BobTheBuidler Oct 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not entirely sure that this is correct in terms of the rest of the codebase right now, but semantically it feels like the correct thing to do

Any class with is_ext_class=False and is_final=False will inherently allow interpreted subclasses. But this new property bricked the codebase so clearly I'm misunderstanding something about how these 3 components interact

I can remove the property, fix just this one case, but then there will be other places in the codebase where allow_interpreted_subclasses could return False for a class that actually does allow interpreted subclasses. That's the state of things currently, but should it be?

Copy link
Contributor Author

@BobTheBuidler BobTheBuidler Nov 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JukkaL this is the reason the tests currently fail, I can revert to a working commit if you agree that my above concern is not relevant

Or if it is relevant but is a non-blocker, we can continue conversation here when its more convenient: mypyc/mypyc#1148

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think allow_interpreted_subclasses is currently directly derived from @mypyc_attr(allow_interpreted_subclasses=True) , and it indicates whether a native class could have interpreted subclassses. I think this is the most natural way to do this, and we can add a comment to make it more explicit if this is unclear right now. Various parts of the code that check for whether there could be interpreted subclasses may need to check for is_ext_class separately. So instead of modifying the definition here, my suggestion is to add a check for is_ext_class and/or is_final_class in the isinstance fast path code (if required).

Also, mypyc should generate a compile error if allow_interpreted_subclasses=True is used with a non-native class or a final class (not sure if this is checked right now).

Copy link
Contributor Author

@BobTheBuidler BobTheBuidler Nov 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah okay great that makes sense, thanks for this.

I already opened this a while back, adding a brief docstring to mypyc_attr with an attached proposal to enhance it with an Args: section, could I get a greenlight to proceed with that, adding a note about this and a short blurb on the other mypyc_attr kwargs?

Separately, I have a second PR to validate mypyc_attr arguments, if that's a welcome change I can extend it to account for this allows_interpreted_subclasses/native_class conflict

I'll get this PR fixed up tonight

return (
self._allow_interpreted_subclasses or not self.is_ext_class and not self.is_final_class
)

@allow_interpreted_subclasses.setter
def allow_interpreted_subclasses(self, value: bool) -> None:
self._allow_interpreted_subclasses = value

def real_base(self) -> ClassIR | None:
"""Return the actual concrete base class, if there is one."""
if len(self.mro) > 1 and not self.mro[1].is_trait:
Expand Down Expand Up @@ -345,11 +355,10 @@ def subclasses(self) -> set[ClassIR] | None:
return None
result = set(self.children)
for child in self.children:
if child.children:
child_subs = child.subclasses()
if child_subs is None:
return None
result.update(child_subs)
child_subs = child.subclasses()
if child_subs is None:
return None
result.update(child_subs)
return result

def concrete_subclasses(self) -> list[ClassIR] | None:
Expand Down Expand Up @@ -381,7 +390,7 @@ def serialize(self) -> JsonDict:
"is_final_class": self.is_final_class,
"inherits_python": self.inherits_python,
"has_dict": self.has_dict,
"allow_interpreted_subclasses": self.allow_interpreted_subclasses,
"allow_interpreted_subclasses": self._allow_interpreted_subclasses,
"needs_getseters": self.needs_getseters,
"_serializable": self._serializable,
"builtin_base": self.builtin_base,
Expand Down
4 changes: 2 additions & 2 deletions mypyc/irbuild/ll_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -695,8 +695,8 @@ def isinstance_native(self, obj: Value, class_ir: ClassIR, line: int) -> Value:
"""Fast isinstance() check for a native class.

If there are three or fewer concrete (non-trait) classes among the class
and all its children, use even faster type comparison checks `type(obj)
is typ`.
and all its children, and none of them allow interpreted subclasses, use
even faster type comparison checks `type(obj) is typ`.
"""
concrete = all_concrete_classes(class_ir)
if concrete is None or len(concrete) > FAST_ISINSTANCE_MAX_SUBCLASSES + 1:
Expand Down
37 changes: 37 additions & 0 deletions mypyc/test-data/irbuild-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -2852,3 +2852,40 @@ class InvalidKwarg:
@mypyc_attr(str()) # E: All "mypyc_attr" positional arguments must be string literals.
class InvalidLiteral:
pass

[case testIsInstanceInterpretedSubclasses]
from typing import Any
from mypy_extensions import mypyc_attr

@mypyc_attr(native_class=False)
class NonNative:
pass

@mypyc_attr(allow_interpreted_subclasses=True)
class InterpSubclasses:
pass

def isinstance_nonnative(x: Any) -> bool:
return isinstance(x, NonNative)
def isinstance_interpreted_subclasses(x: Any) -> bool:
return isinstance(x, InterpSubclasses)
[out]
def isinstance_nonnative(x):
x, r0 :: object
r1 :: ptr
r2 :: object
r3 :: bit
L0:
r0 = __main__.NonNative :: type
r1 = get_element_ptr x ob_type :: PyObject
r2 :: borrow load_mem r1 :: builtins.object*
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this IR will change once we address the property question

keep_alive x
r3 :: r2 == r0
return r3
def isinstance_interpreted_subclasses(x):
x, r0 :: object
r1 :: bool
L0:
r0 = __main__.InterpSubclasses :: type
r1 = CPy_TypeCheck(x, r0)
return r1
Loading