Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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

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

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 @@ -2686,3 +2686,40 @@ L0:
r6 = PyObject_VectorcallMethod(r3, r5, 9223372036854775812, 0)
keep_alive r2, self, key, val
return 1

[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*
keep_alive x
r3 :: r2 == r0
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 doesn't look quite right

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