diff --git a/mypyc/ir/class_ir.py b/mypyc/ir/class_ir.py index 0a56aaf5d101..487da48ef4fd 100644 --- a/mypyc/ir/class_ir.py +++ b/mypyc/ir/class_ir.py @@ -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: + 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: @@ -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: @@ -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, diff --git a/mypyc/irbuild/ll_builder.py b/mypyc/irbuild/ll_builder.py index 37f2add4abbd..9a6ace7fbb2d 100644 --- a/mypyc/irbuild/ll_builder.py +++ b/mypyc/irbuild/ll_builder.py @@ -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: diff --git a/mypyc/test-data/irbuild-classes.test b/mypyc/test-data/irbuild-classes.test index 94e89f276eeb..d90d52040394 100644 --- a/mypyc/test-data/irbuild-classes.test +++ b/mypyc/test-data/irbuild-classes.test @@ -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* + 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