From 9a358fcfa8e49a8b0f78eab336a7f6733d2b30fa Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Tue, 30 Sep 2025 19:08:10 +0000 Subject: [PATCH 01/16] [mypyc] fix: isinstance_native fast path conflict w/ subclasses --- mypyc/irbuild/ll_builder.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/mypyc/irbuild/ll_builder.py b/mypyc/irbuild/ll_builder.py index 37f2add4abbd..b75eae9176d0 100644 --- a/mypyc/irbuild/ll_builder.py +++ b/mypyc/irbuild/ll_builder.py @@ -695,11 +695,15 @@ 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: + if concrete is None or len(concrete) > FAST_ISINSTANCE_MAX_SUBCLASSES + 1 or any( + # we can only take the fast path if we know + # an end user won't inherit from the class + cls.allow_interpreted_subclasses for cls in concrete + ): return self.primitive_op( fast_isinstance_op, [obj, self.get_native_type(class_ir)], line ) From 8446150825eca21c0fa39d21b59ad210e415988d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 30 Sep 2025 19:17:24 +0000 Subject: [PATCH 02/16] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypyc/irbuild/ll_builder.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/mypyc/irbuild/ll_builder.py b/mypyc/irbuild/ll_builder.py index b75eae9176d0..250fcf7f9c0f 100644 --- a/mypyc/irbuild/ll_builder.py +++ b/mypyc/irbuild/ll_builder.py @@ -699,10 +699,15 @@ def isinstance_native(self, obj: Value, class_ir: ClassIR, line: int) -> Value: 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 or any( - # we can only take the fast path if we know - # an end user won't inherit from the class - cls.allow_interpreted_subclasses for cls in concrete + if ( + concrete is None + or len(concrete) > FAST_ISINSTANCE_MAX_SUBCLASSES + 1 + or any( + # we can only take the fast path if we know + # an end user won't inherit from the class + cls.allow_interpreted_subclasses + for cls in concrete + ) ): return self.primitive_op( fast_isinstance_op, [obj, self.get_native_type(class_ir)], line From f84f576699c77e8284f81364bd73875822c507ba Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Tue, 30 Sep 2025 15:43:39 -0400 Subject: [PATCH 03/16] handle non-native final --- mypyc/irbuild/ll_builder.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mypyc/irbuild/ll_builder.py b/mypyc/irbuild/ll_builder.py index 250fcf7f9c0f..2209d0544517 100644 --- a/mypyc/irbuild/ll_builder.py +++ b/mypyc/irbuild/ll_builder.py @@ -703,9 +703,10 @@ def isinstance_native(self, obj: Value, class_ir: ClassIR, line: int) -> Value: concrete is None or len(concrete) > FAST_ISINSTANCE_MAX_SUBCLASSES + 1 or any( - # we can only take the fast path if we know - # an end user won't inherit from the class + # we can only use fast path if we know end user won't inherit from the class cls.allow_interpreted_subclasses + # a non-native class is only suitable for fast path if final + or not cls.is_ext_class and not cls.is_final_class for cls in concrete ) ): From 376356bcbefb1d2ff9d31b15656e4066ba46e18b Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Wed, 1 Oct 2025 09:41:31 -0400 Subject: [PATCH 04/16] Update class_ir.py --- mypyc/ir/class_ir.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mypyc/ir/class_ir.py b/mypyc/ir/class_ir.py index 0a56aaf5d101..8ac3231d87cf 100644 --- a/mypyc/ir/class_ir.py +++ b/mypyc/ir/class_ir.py @@ -345,6 +345,10 @@ def subclasses(self) -> set[ClassIR] | None: return None result = set(self.children) for child in self.children: + if child.allow_interpreted_subclasses: + return None + if not (child.is_ext_class or child.is_final): + return None if child.children: child_subs = child.subclasses() if child_subs is None: From c22e6a49c4415f6b3c7abd79299cdacab1f64704 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Wed, 1 Oct 2025 09:43:34 -0400 Subject: [PATCH 05/16] Update ll_builder.py --- mypyc/irbuild/ll_builder.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/mypyc/irbuild/ll_builder.py b/mypyc/irbuild/ll_builder.py index 2209d0544517..9a6ace7fbb2d 100644 --- a/mypyc/irbuild/ll_builder.py +++ b/mypyc/irbuild/ll_builder.py @@ -699,17 +699,7 @@ def isinstance_native(self, obj: Value, class_ir: ClassIR, line: int) -> Value: 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 - or any( - # we can only use fast path if we know end user won't inherit from the class - cls.allow_interpreted_subclasses - # a non-native class is only suitable for fast path if final - or not cls.is_ext_class and not cls.is_final_class - for cls in concrete - ) - ): + if concrete is None or len(concrete) > FAST_ISINSTANCE_MAX_SUBCLASSES + 1: return self.primitive_op( fast_isinstance_op, [obj, self.get_native_type(class_ir)], line ) From 70cdff9f6c2aa76feb23fd208e5c4c7a7e0709aa Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Wed, 1 Oct 2025 12:36:36 -0400 Subject: [PATCH 06/16] Update class_ir.py --- mypyc/ir/class_ir.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypyc/ir/class_ir.py b/mypyc/ir/class_ir.py index 8ac3231d87cf..ba4eaae793c8 100644 --- a/mypyc/ir/class_ir.py +++ b/mypyc/ir/class_ir.py @@ -347,7 +347,7 @@ def subclasses(self) -> set[ClassIR] | None: for child in self.children: if child.allow_interpreted_subclasses: return None - if not (child.is_ext_class or child.is_final): + if not (child.is_ext_class or child.is_final_class): return None if child.children: child_subs = child.subclasses() From 74597012f7aa95773bbd449505313ebcdf83b7d8 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Fri, 3 Oct 2025 19:45:40 -0400 Subject: [PATCH 07/16] Update irbuild-classes.test --- mypyc/test-data/irbuild-classes.test | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/mypyc/test-data/irbuild-classes.test b/mypyc/test-data/irbuild-classes.test index a98b3a7d3dcf..db6721d11a5d 100644 --- a/mypyc/test-data/irbuild-classes.test +++ b/mypyc/test-data/irbuild-classes.test @@ -2686,3 +2686,25 @@ L0: r6 = PyObject_VectorcallMethod(r3, r5, 9223372036854775812, 0) keep_alive r2, self, key, val return 1 + +[case testIsInstanceInterpretedSubclasses] +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) -> None: + return isinstance(x, InterpSubclasses) + +[out] +def isinstance_nonnative(x): + x :: Any +L0: +def isinstance_interpreted_subclasses(x): + x :: Any +L0: From 86e5b009f67eb66d88a7e63ef1c89faa2f780eaa Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Fri, 3 Oct 2025 19:49:29 -0400 Subject: [PATCH 08/16] refactor --- mypyc/ir/class_ir.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/mypyc/ir/class_ir.py b/mypyc/ir/class_ir.py index ba4eaae793c8..3aadab73432a 100644 --- a/mypyc/ir/class_ir.py +++ b/mypyc/ir/class_ir.py @@ -345,15 +345,10 @@ def subclasses(self) -> set[ClassIR] | None: return None result = set(self.children) for child in self.children: - if child.allow_interpreted_subclasses: + child_subs = child.subclasses() + if child_subs is None: return None - if not (child.is_ext_class or child.is_final_class): - return None - if child.children: - child_subs = child.subclasses() - if child_subs is None: - return None - result.update(child_subs) + result.update(child_subs) return result def concrete_subclasses(self) -> list[ClassIR] | None: From d8602a0a53eee5fad5348d10b9c6e0eccb04535e Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Fri, 3 Oct 2025 20:00:08 -0400 Subject: [PATCH 09/16] Update irbuild-classes.test --- mypyc/test-data/irbuild-classes.test | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mypyc/test-data/irbuild-classes.test b/mypyc/test-data/irbuild-classes.test index db6721d11a5d..d705356b6e77 100644 --- a/mypyc/test-data/irbuild-classes.test +++ b/mypyc/test-data/irbuild-classes.test @@ -2688,19 +2688,21 @@ L0: 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) -> None: return isinstance(x, InterpSubclasses) - [out] def isinstance_nonnative(x): x :: Any From 9fce6290a9d13daac3d2b3b6a23bb84e73b84e9d Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Fri, 3 Oct 2025 22:38:00 -0400 Subject: [PATCH 10/16] Update irbuild-classes.test --- mypyc/test-data/irbuild-classes.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypyc/test-data/irbuild-classes.test b/mypyc/test-data/irbuild-classes.test index d705356b6e77..29562674be8c 100644 --- a/mypyc/test-data/irbuild-classes.test +++ b/mypyc/test-data/irbuild-classes.test @@ -2701,7 +2701,7 @@ class InterpSubclasses: def isinstance_nonnative(x: Any) -> bool: return isinstance(x, NonNative) -def isinstance_interpreted_subclasses(x: Any) -> None: +def isinstance_interpreted_subclasses(x: Any) -> bool: return isinstance(x, InterpSubclasses) [out] def isinstance_nonnative(x): From b90961e07cdc355bb9b5e85fe7d79153654eb3e4 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Fri, 3 Oct 2025 22:51:57 -0400 Subject: [PATCH 11/16] Update irbuild-classes.test --- mypyc/test-data/irbuild-classes.test | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/mypyc/test-data/irbuild-classes.test b/mypyc/test-data/irbuild-classes.test index 29562674be8c..da37cb85843a 100644 --- a/mypyc/test-data/irbuild-classes.test +++ b/mypyc/test-data/irbuild-classes.test @@ -2705,8 +2705,21 @@ def isinstance_interpreted_subclasses(x: Any) -> bool: return isinstance(x, InterpSubclasses) [out] def isinstance_nonnative(x): - x :: Any + x, r0 :: object + r1 :: ptr + r2 :: object + r3 :: r2 == r0 + return r3 L0: + r0 = __main__.NonNative :: type + r1 = get_element_ptr x ob_type :: PyObject + r2 :: borrow load_mem r1 :: builtins.object* + r3 :: r2 == r0 + return r3 def isinstance_interpreted_subclasses(x): - x :: Any + x, r0 :: object + r1 :: bool L0: + r0 = __main__.InterpSubclasses :: type + r1 = CPy_TypeCheck(x, r0) + return r1 From a5546545fa702f612b11ffb146de010d235a05d4 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Sat, 4 Oct 2025 01:34:28 -0400 Subject: [PATCH 12/16] Update irbuild-classes.test --- mypyc/test-data/irbuild-classes.test | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mypyc/test-data/irbuild-classes.test b/mypyc/test-data/irbuild-classes.test index da37cb85843a..56cee29a5797 100644 --- a/mypyc/test-data/irbuild-classes.test +++ b/mypyc/test-data/irbuild-classes.test @@ -2708,12 +2708,12 @@ def isinstance_nonnative(x): x, r0 :: object r1 :: ptr r2 :: object - r3 :: r2 == r0 - return r3 + 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): From f514765b17bf617f7d7aa2b2ba126c313ab97c10 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Sat, 4 Oct 2025 01:45:24 -0400 Subject: [PATCH 13/16] Update class_ir.py --- mypyc/ir/class_ir.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/mypyc/ir/class_ir.py b/mypyc/ir/class_ir.py index 3aadab73432a..b8be6dbae703 100644 --- a/mypyc/ir/class_ir.py +++ b/mypyc/ir/class_ir.py @@ -237,6 +237,13 @@ def __repr__(self) -> str: def fullname(self) -> str: return f"{self.module_name}.{self.name}" + @property + def allow_interpreted_subclasses(self) -> bool: + + @allow_interpreted_subclasses.setter + def _(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: @@ -380,7 +387,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, From bee44dc035d0f3f6981de50aa44946f3718f695c Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Sat, 4 Oct 2025 01:49:45 -0400 Subject: [PATCH 14/16] Update class_ir.py --- mypyc/ir/class_ir.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mypyc/ir/class_ir.py b/mypyc/ir/class_ir.py index b8be6dbae703..f4ab9694741a 100644 --- a/mypyc/ir/class_ir.py +++ b/mypyc/ir/class_ir.py @@ -239,6 +239,7 @@ def fullname(self) -> str: @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 _(self, value: bool) -> None: From 3158d1519b731e3fb42d6705e115775d2a0e0eba Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 4 Oct 2025 05:56:41 +0000 Subject: [PATCH 15/16] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypyc/ir/class_ir.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/mypyc/ir/class_ir.py b/mypyc/ir/class_ir.py index f4ab9694741a..1aec4ed371d5 100644 --- a/mypyc/ir/class_ir.py +++ b/mypyc/ir/class_ir.py @@ -239,12 +239,14 @@ def fullname(self) -> str: @property def allow_interpreted_subclasses(self) -> bool: - return self._allow_interpreted_subclasses or not self.is_ext_class and not self.is_final_class - + return ( + self._allow_interpreted_subclasses or not self.is_ext_class and not self.is_final_class + ) + @allow_interpreted_subclasses.setter def _(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: From 71a3c968316ee16bcce1f427f8e48210a2df88b1 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Sat, 4 Oct 2025 03:08:09 -0400 Subject: [PATCH 16/16] Update class_ir.py --- mypyc/ir/class_ir.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypyc/ir/class_ir.py b/mypyc/ir/class_ir.py index 1aec4ed371d5..487da48ef4fd 100644 --- a/mypyc/ir/class_ir.py +++ b/mypyc/ir/class_ir.py @@ -244,7 +244,7 @@ def allow_interpreted_subclasses(self) -> bool: ) @allow_interpreted_subclasses.setter - def _(self, value: bool) -> None: + def allow_interpreted_subclasses(self, value: bool) -> None: self._allow_interpreted_subclasses = value def real_base(self) -> ClassIR | None: