From 1975746d47907f6396f0fc8a0ef66eadda69ac90 Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Mon, 29 Sep 2025 03:10:37 +0000 Subject: [PATCH 01/13] [mypyc] feat: specialize isinstance for tuple of primitive types --- mypyc/irbuild/specialize.py | 59 +++++++++++++++++++++++++++++++++---- 1 file changed, 54 insertions(+), 5 deletions(-) diff --git a/mypyc/irbuild/specialize.py b/mypyc/irbuild/specialize.py index 29820787d10c..ec7b1c1d9eac 100644 --- a/mypyc/irbuild/specialize.py +++ b/mypyc/irbuild/specialize.py @@ -31,6 +31,7 @@ RefExpr, StrExpr, SuperExpr, + SymbolNode, TupleExpr, Var, ) @@ -587,18 +588,66 @@ def translate_isinstance(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> if not (len(expr.args) == 2 and expr.arg_kinds == [ARG_POS, ARG_POS]): return None - if isinstance(expr.args[1], (RefExpr, TupleExpr)): - builder.types[expr.args[0]] = AnyType(TypeOfAny.from_error) - - irs = builder.flatten_classes(expr.args[1]) + obj_expr = expr.args[0] + type_expr = expr.args[1] + + if isinstance(type_expr, TupleExpr) and not type_expr.items: + # we can compile this case to a noop + return builder.false() + + if isinstance(type_expr, (RefExpr, TupleExpr)): + builder.types[obj_expr] = AnyType(TypeOfAny.from_error) + + irs = builder.flatten_classes(type_expr) if irs is not None: can_borrow = all( ir.is_ext_class and not ir.inherits_python and not ir.allow_interpreted_subclasses for ir in irs ) - obj = builder.accept(expr.args[0], can_borrow=can_borrow) + obj = builder.accept(obj_expr, can_borrow=can_borrow) return builder.builder.isinstance_helper(obj, irs, expr.line) + if isinstance(type_expr, TupleExpr): + nodes: list[SymbolNode | None] = [] + for item in type_expr.items: + if not isinstance(item, RefExpr): + return None + if item.node is None: + return None + if item.node.fullname not in nodes: + nodes.append(item.node.fullname) + + descs = [isinstance_primitives.get(fullname) for fullname in nodes] + + obj = builder.accept(expr.args[0]) + + retval = Register(bool_rprimitive) + pass_block = BasicBlock() + fail_block = BasicBlock() + exit_block = BasicBlock() + + # Chain the checks: if any succeed, jump to pass_block; else, continue + for i, desc in enumerate(descs): + is_last = (i == len(descs) - 1) + next_block = fail_block if is_last else BasicBlock() + builder.add_bool_branch(builder.primitive_op(desc, [obj], expr.line), pass_block, next_block) + if not is_last: + builder.activate_block(next_block) + + # If any check passed + builder.activate_block(pass_block) + builder.assign(retval, builder.true(), expr.line) + builder.goto(exit_block) + + # If all checks failed + builder.activate_block(fail_block) + builder.assign(retval, builder.false(), expr.line) + builder.goto(exit_block) + + # Return the result + builder.activate_block(exit_block) + return retval + if isinstance(expr.args[1], RefExpr): node = expr.args[1].node if node: From e23d1f11c30057a4069420fd1a155bb68a3f2b5d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 29 Sep 2025 03:14:18 +0000 Subject: [PATCH 02/13] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypyc/irbuild/specialize.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/mypyc/irbuild/specialize.py b/mypyc/irbuild/specialize.py index ec7b1c1d9eac..0cb5dc06a2c9 100644 --- a/mypyc/irbuild/specialize.py +++ b/mypyc/irbuild/specialize.py @@ -590,11 +590,11 @@ def translate_isinstance(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> obj_expr = expr.args[0] type_expr = expr.args[1] - + if isinstance(type_expr, TupleExpr) and not type_expr.items: # we can compile this case to a noop return builder.false() - + if isinstance(type_expr, (RefExpr, TupleExpr)): builder.types[obj_expr] = AnyType(TypeOfAny.from_error) @@ -618,7 +618,7 @@ def translate_isinstance(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> nodes.append(item.node.fullname) descs = [isinstance_primitives.get(fullname) for fullname in nodes] - + obj = builder.accept(expr.args[0]) retval = Register(bool_rprimitive) @@ -628,9 +628,11 @@ def translate_isinstance(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> # Chain the checks: if any succeed, jump to pass_block; else, continue for i, desc in enumerate(descs): - is_last = (i == len(descs) - 1) + is_last = i == len(descs) - 1 next_block = fail_block if is_last else BasicBlock() - builder.add_bool_branch(builder.primitive_op(desc, [obj], expr.line), pass_block, next_block) + builder.add_bool_branch( + builder.primitive_op(desc, [obj], expr.line), pass_block, next_block + ) if not is_last: builder.activate_block(next_block) From 95ee6485e61622d844d98752cd15d5d1097194d5 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Sun, 28 Sep 2025 23:18:22 -0400 Subject: [PATCH 03/13] fix mypy errs --- mypyc/irbuild/specialize.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypyc/irbuild/specialize.py b/mypyc/irbuild/specialize.py index 0cb5dc06a2c9..87bc41099ccb 100644 --- a/mypyc/irbuild/specialize.py +++ b/mypyc/irbuild/specialize.py @@ -608,7 +608,7 @@ def translate_isinstance(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> return builder.builder.isinstance_helper(obj, irs, expr.line) if isinstance(type_expr, TupleExpr): - nodes: list[SymbolNode | None] = [] + nodes: list[SymbolNode] = [] for item in type_expr.items: if not isinstance(item, RefExpr): return None From 73f62979884ca9c7cde645b74bdc9487f4594d56 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Sun, 28 Sep 2025 23:25:09 -0400 Subject: [PATCH 04/13] fix: desc is None --- mypyc/irbuild/specialize.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mypyc/irbuild/specialize.py b/mypyc/irbuild/specialize.py index 87bc41099ccb..744ea0460c9f 100644 --- a/mypyc/irbuild/specialize.py +++ b/mypyc/irbuild/specialize.py @@ -608,16 +608,16 @@ def translate_isinstance(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> return builder.builder.isinstance_helper(obj, irs, expr.line) if isinstance(type_expr, TupleExpr): - nodes: list[SymbolNode] = [] + node_names: list[str] = [] for item in type_expr.items: if not isinstance(item, RefExpr): return None - if item.node is None: + if item.node is None or item.node.fullname not in isinstance_primitives: return None - if item.node.fullname not in nodes: - nodes.append(item.node.fullname) + if item.node.fullname not in node_names: + node_names.append(item.node.fullname) - descs = [isinstance_primitives.get(fullname) for fullname in nodes] + descs = [isinstance_primitives[fullname] for fullname in node_names] obj = builder.accept(expr.args[0]) From 7d5b66a8ea994c706f1bad92b53f8bf314553712 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 29 Sep 2025 03:26:28 +0000 Subject: [PATCH 05/13] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypyc/irbuild/specialize.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mypyc/irbuild/specialize.py b/mypyc/irbuild/specialize.py index 744ea0460c9f..f2995c442e66 100644 --- a/mypyc/irbuild/specialize.py +++ b/mypyc/irbuild/specialize.py @@ -31,7 +31,6 @@ RefExpr, StrExpr, SuperExpr, - SymbolNode, TupleExpr, Var, ) From ceda7cce78b0e95ec91f08290e4551540859a4a0 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Sun, 28 Sep 2025 23:31:02 -0400 Subject: [PATCH 06/13] fix type errs --- mypyc/irbuild/specialize.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/mypyc/irbuild/specialize.py b/mypyc/irbuild/specialize.py index f2995c442e66..d280da495671 100644 --- a/mypyc/irbuild/specialize.py +++ b/mypyc/irbuild/specialize.py @@ -31,6 +31,7 @@ RefExpr, StrExpr, SuperExpr, + SymbolNode, TupleExpr, Var, ) @@ -611,14 +612,17 @@ def translate_isinstance(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> for item in type_expr.items: if not isinstance(item, RefExpr): return None - if item.node is None or item.node.fullname not in isinstance_primitives: + if item.node is None: return None if item.node.fullname not in node_names: node_names.append(item.node.fullname) - descs = [isinstance_primitives[fullname] for fullname in node_names] + descs = [isinstance_primitives.get(fullname) for fullname in node_names] + if None in descs: + # not all types are primitive types, abort + return None - obj = builder.accept(expr.args[0]) + obj = builder.accept(obj_expr) retval = Register(bool_rprimitive) pass_block = BasicBlock() @@ -649,12 +653,12 @@ def translate_isinstance(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> builder.activate_block(exit_block) return retval - if isinstance(expr.args[1], RefExpr): - node = expr.args[1].node + if isinstance(type_expr, RefExpr): + node = type_expr.node if node: desc = isinstance_primitives.get(node.fullname) if desc: - obj = builder.accept(expr.args[0]) + obj = builder.accept(obj_expr) return builder.primitive_op(desc, [obj], expr.line) return None From 628294d179910c62829b17144fdc8ac6970ad5eb Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 29 Sep 2025 03:32:21 +0000 Subject: [PATCH 07/13] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypyc/irbuild/specialize.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mypyc/irbuild/specialize.py b/mypyc/irbuild/specialize.py index d280da495671..c5d452c09701 100644 --- a/mypyc/irbuild/specialize.py +++ b/mypyc/irbuild/specialize.py @@ -31,7 +31,6 @@ RefExpr, StrExpr, SuperExpr, - SymbolNode, TupleExpr, Var, ) From 0d51ead6dc4e5c647a21fdf009d2cefd60ed3ba1 Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Mon, 29 Sep 2025 03:44:23 +0000 Subject: [PATCH 08/13] add ir test --- mypyc/test-data/irbuild-isinstance.test | 28 +++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/mypyc/test-data/irbuild-isinstance.test b/mypyc/test-data/irbuild-isinstance.test index 0df9448b819f..36a9300350bd 100644 --- a/mypyc/test-data/irbuild-isinstance.test +++ b/mypyc/test-data/irbuild-isinstance.test @@ -189,3 +189,31 @@ def is_tuple(x): L0: r0 = PyTuple_Check(x) return r0 + +[case testTupleOfPrimitives] +from typing import Any + +def is_instance(x: Any) -> bool: + return isinstance(x, (str, int, bytes)) + +[out] +def is_instance(x): + x :: object + r0, r1, r2 :: bit + r3 :: bool +L0: + r0 = PyUnicode_Check(x) + if r0 goto L3 else goto L1 :: bool +L1: + r1 = PyLong_Check(x) + if r1 goto L3 else goto L2 :: bool +L2: + r2 = PyBytes_Check(x) + if r2 goto L3 else goto L4 :: bool +L3: + r3 = 1 + goto L5 +L4: + r3 = 0 +L5: + return r3 From 8a3a1b285fe1a965f87d6666853f3d2a93fcb07b Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Sun, 28 Sep 2025 23:45:39 -0400 Subject: [PATCH 09/13] Update specialize.py --- mypyc/irbuild/specialize.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypyc/irbuild/specialize.py b/mypyc/irbuild/specialize.py index c5d452c09701..f7aa445a2eb7 100644 --- a/mypyc/irbuild/specialize.py +++ b/mypyc/irbuild/specialize.py @@ -633,7 +633,7 @@ def translate_isinstance(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> is_last = i == len(descs) - 1 next_block = fail_block if is_last else BasicBlock() builder.add_bool_branch( - builder.primitive_op(desc, [obj], expr.line), pass_block, next_block + builder.primitive_op(desc, [obj], expr.line), pass_block, next_block # type: ignore [arg-type] ) if not is_last: builder.activate_block(next_block) From 355ceecf7f9c8dbeb94846666029b694fb358cda Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Sun, 28 Sep 2025 23:47:38 -0400 Subject: [PATCH 10/13] flip flop --- mypyc/irbuild/specialize.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/mypyc/irbuild/specialize.py b/mypyc/irbuild/specialize.py index f7aa445a2eb7..2d67f71da688 100644 --- a/mypyc/irbuild/specialize.py +++ b/mypyc/irbuild/specialize.py @@ -606,7 +606,15 @@ def translate_isinstance(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> obj = builder.accept(obj_expr, can_borrow=can_borrow) return builder.builder.isinstance_helper(obj, irs, expr.line) - if isinstance(type_expr, TupleExpr): + if isinstance(type_expr, RefExpr): + node = type_expr.node + if node: + desc = isinstance_primitives.get(node.fullname) + if desc: + obj = builder.accept(obj_expr) + return builder.primitive_op(desc, [obj], expr.line) + + elif isinstance(type_expr, TupleExpr): node_names: list[str] = [] for item in type_expr.items: if not isinstance(item, RefExpr): @@ -652,14 +660,6 @@ def translate_isinstance(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> builder.activate_block(exit_block) return retval - if isinstance(type_expr, RefExpr): - node = type_expr.node - if node: - desc = isinstance_primitives.get(node.fullname) - if desc: - obj = builder.accept(obj_expr) - return builder.primitive_op(desc, [obj], expr.line) - return None From c29d5cd8140df1ffeeb14e6e56c17ac3ff9ce9ad Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Mon, 29 Sep 2025 11:22:33 -0400 Subject: [PATCH 11/13] add a variety of run test cases --- mypyc/test-data/run-misc.test | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/mypyc/test-data/run-misc.test b/mypyc/test-data/run-misc.test index 129946a4c330..1074906357ee 100644 --- a/mypyc/test-data/run-misc.test +++ b/mypyc/test-data/run-misc.test @@ -1173,3 +1173,26 @@ def test_dummy_context() -> None: with c: assert c.c == 1 assert c.c == 0 + +[case testIsInstanceTuple] +from typing import Any + +def isinstance_empty(x: Any) -> bool: + return isinstance(x, ()) +def isinstance_single(x: Any) -> bool: + return isinstance(x, (str,)) +def isinstance_multi(x: Any) -> bool: + return isinstance(x, (str, int)) + +def test_isinstance_empty() -> None: + assert isinstance_empty("a") is False + assert isinstance_empty(1) is False + assert isinstance_empty(None) is False +def test_isinstance_single() -> None: + assert isinstance_single("a") is True + assert isinstance_single(1) is False + assert isinstance_single(None) is False +def test_isinstance_multi() -> None: + assert isinstance_multi("a") is True + assert isinstance_multi(1) is True + assert isinstance_multi(None) is False From 4e1596ce24a71b9fa9096cf9ca79cc37c0aef0b8 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Mon, 29 Sep 2025 13:14:05 -0400 Subject: [PATCH 12/13] use cast --- mypyc/irbuild/specialize.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mypyc/irbuild/specialize.py b/mypyc/irbuild/specialize.py index 2d67f71da688..b921a1a5535a 100644 --- a/mypyc/irbuild/specialize.py +++ b/mypyc/irbuild/specialize.py @@ -14,7 +14,7 @@ from __future__ import annotations -from typing import Callable, Final, Optional +from typing import Callable, Final, Optional, cast from mypy.nodes import ( ARG_NAMED, @@ -40,6 +40,7 @@ Call, Extend, Integer, + PrimitiveDescription, RaiseStandardError, Register, Truncate, @@ -641,7 +642,7 @@ def translate_isinstance(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> is_last = i == len(descs) - 1 next_block = fail_block if is_last else BasicBlock() builder.add_bool_branch( - builder.primitive_op(desc, [obj], expr.line), pass_block, next_block # type: ignore [arg-type] + builder.primitive_op(cast(PrimitiveDescription, desc), [obj], expr.line), pass_block, next_block ) if not is_last: builder.activate_block(next_block) From fa7f46b5af24e61e57bd2980ffae4ab04d84d356 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 29 Sep 2025 17:15:27 +0000 Subject: [PATCH 13/13] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypyc/irbuild/specialize.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mypyc/irbuild/specialize.py b/mypyc/irbuild/specialize.py index b921a1a5535a..2e92c05802e0 100644 --- a/mypyc/irbuild/specialize.py +++ b/mypyc/irbuild/specialize.py @@ -642,7 +642,9 @@ def translate_isinstance(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> is_last = i == len(descs) - 1 next_block = fail_block if is_last else BasicBlock() builder.add_bool_branch( - builder.primitive_op(cast(PrimitiveDescription, desc), [obj], expr.line), pass_block, next_block + builder.primitive_op(cast(PrimitiveDescription, desc), [obj], expr.line), + pass_block, + next_block, ) if not is_last: builder.activate_block(next_block)