diff --git a/mypyc/ir/class_ir.py b/mypyc/ir/class_ir.py index 0a56aaf5d101..d3771511b98e 100644 --- a/mypyc/ir/class_ir.py +++ b/mypyc/ir/class_ir.py @@ -333,7 +333,11 @@ def has_method_decl(self, name: str) -> bool: return any(name in ir.method_decls for ir in self.mro) def has_no_subclasses(self) -> bool: - return self.children == [] and not self.allow_interpreted_subclasses + return ( + self.children == [] + and not self.allow_interpreted_subclasses + and (self.is_ext_class or self.is_final_class) + ) def subclasses(self) -> set[ClassIR] | None: """Return all subclasses of this class, both direct and indirect. @@ -341,15 +345,18 @@ def subclasses(self) -> set[ClassIR] | None: Return None if it is impossible to identify all subclasses, for example because we are performing separate compilation. """ - if self.children is None or self.allow_interpreted_subclasses: + if ( + (not self.is_ext_class and not self.is_final_class) + or self.allow_interpreted_subclasses + or self.children is 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: diff --git a/mypyc/irbuild/ll_builder.py b/mypyc/irbuild/ll_builder.py index fd66288dbcc5..026f3780c88e 100644 --- a/mypyc/irbuild/ll_builder.py +++ b/mypyc/irbuild/ll_builder.py @@ -697,8 +697,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 0f8ec2b094f0..e04bcb808d76 100644 --- a/mypyc/test-data/irbuild-classes.test +++ b/mypyc/test-data/irbuild-classes.test @@ -2865,3 +2865,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