From cf1470b9ab69d0d1419ed0a3c7513a80c8f21738 Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Tue, 21 Oct 2025 21:16:27 +0000 Subject: [PATCH 01/10] [mypyc] fix: builder crashes on 3-arg range if step isnt foldable --- mypyc/irbuild/for_helpers.py | 60 ++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 33 deletions(-) diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index 715f5432cd13..0421335a4fa4 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -454,39 +454,33 @@ def make_for_loop_generator( return for_dict if isinstance(expr, CallExpr) and isinstance(expr.callee, RefExpr): - if ( - is_range_ref(expr.callee) - and ( - len(expr.args) <= 2 - or (len(expr.args) == 3 and builder.extract_int(expr.args[2]) is not None) - ) - and set(expr.arg_kinds) == {ARG_POS} - ): - # Special case "for x in range(...)". - # We support the 3 arg form but only for int literals, since it doesn't - # seem worth the hassle of supporting dynamically determining which - # direction of comparison to do. - if len(expr.args) == 1: - start_reg: Value = Integer(0) - end_reg = builder.accept(expr.args[0]) - else: - start_reg = builder.accept(expr.args[0]) - end_reg = builder.accept(expr.args[1]) - if len(expr.args) == 3: - step = builder.extract_int(expr.args[2]) - assert step is not None - if step == 0: - builder.error("range() step can't be zero", expr.args[2].line) - else: - step = 1 - - for_range = ForRange(builder, index, body_block, loop_exit, line, nested) - for_range.init(start_reg, end_reg, step) - return for_range + num_args = len(expr.args) + + if is_range_ref(expr.callee) and set(expr.arg_kinds) == {ARG_POS}: + if num_args <= 2 or (num_args == 3 and builder.extract_int(expr.args[2]) is not None): + # Special case "for x in range(...)". + # We support the 3 arg form but only for int literals, since it doesn't + # seem worth the hassle of supporting dynamically determining which + # direction of comparison to do. + if num_args == 1: + start_reg: Value = Integer(0) + end_reg = builder.accept(expr.args[0]) + step = 1 + else: + start_reg = builder.accept(expr.args[0]) + end_reg = builder.accept(expr.args[1]) + step = 1 if num_args == 2 else builder.extract_int(expr.args[2]) + + if step: + for_range = ForRange(builder, index, body_block, loop_exit, line, nested) + for_range.init(start_reg, end_reg, step) + return for_range + + # If we could not constant fold `step`, we just fallback to calling stdlib implementation elif ( expr.callee.fullname == "builtins.enumerate" - and len(expr.args) == 1 + and num_args == 1 and expr.arg_kinds == [ARG_POS] and isinstance(index, TupleExpr) and len(index.items) == 2 @@ -500,10 +494,10 @@ def make_for_loop_generator( elif ( expr.callee.fullname == "builtins.zip" - and len(expr.args) >= 2 + and num_args >= 2 and set(expr.arg_kinds) == {ARG_POS} and isinstance(index, TupleExpr) - and len(index.items) == len(expr.args) + and len(index.items) == num_args ): # Special case "for x, y in zip(a, b)". for_zip = ForZip(builder, index, body_block, loop_exit, line, nested) @@ -512,7 +506,7 @@ def make_for_loop_generator( if ( expr.callee.fullname == "builtins.reversed" - and len(expr.args) == 1 + and num_args == 1 and expr.arg_kinds == [ARG_POS] and is_sequence_rprimitive(builder.node_type(expr.args[0])) ): From 2b0407b9ae535e5e7474193c6578d20f9b30dda9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 21 Oct 2025 21:22:41 +0000 Subject: [PATCH 02/10] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypyc/irbuild/for_helpers.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index 0421335a4fa4..7fae0129d2af 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -455,7 +455,7 @@ def make_for_loop_generator( if isinstance(expr, CallExpr) and isinstance(expr.callee, RefExpr): num_args = len(expr.args) - + if is_range_ref(expr.callee) and set(expr.arg_kinds) == {ARG_POS}: if num_args <= 2 or (num_args == 3 and builder.extract_int(expr.args[2]) is not None): # Special case "for x in range(...)". @@ -470,12 +470,12 @@ def make_for_loop_generator( start_reg = builder.accept(expr.args[0]) end_reg = builder.accept(expr.args[1]) step = 1 if num_args == 2 else builder.extract_int(expr.args[2]) - + if step: for_range = ForRange(builder, index, body_block, loop_exit, line, nested) for_range.init(start_reg, end_reg, step) return for_range - + # If we could not constant fold `step`, we just fallback to calling stdlib implementation elif ( From 9efe3779ce425a82d6deb408df9b5233812133a3 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Tue, 21 Oct 2025 17:25:50 -0400 Subject: [PATCH 03/10] Update for_helpers.py --- mypyc/irbuild/for_helpers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index 7fae0129d2af..6d372ef051ec 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -462,6 +462,7 @@ def make_for_loop_generator( # We support the 3 arg form but only for int literals, since it doesn't # seem worth the hassle of supporting dynamically determining which # direction of comparison to do. + step: int | None if num_args == 1: start_reg: Value = Integer(0) end_reg = builder.accept(expr.args[0]) From 1dbaa15bb765db7088901da15bbf958e2259e6e6 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Tue, 21 Oct 2025 17:34:46 -0400 Subject: [PATCH 04/10] Update for_helpers.py --- mypyc/irbuild/for_helpers.py | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index 6d372ef051ec..88680610c964 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -457,12 +457,12 @@ def make_for_loop_generator( num_args = len(expr.args) if is_range_ref(expr.callee) and set(expr.arg_kinds) == {ARG_POS}: - if num_args <= 2 or (num_args == 3 and builder.extract_int(expr.args[2]) is not None): - # Special case "for x in range(...)". - # We support the 3 arg form but only for int literals, since it doesn't - # seem worth the hassle of supporting dynamically determining which - # direction of comparison to do. - step: int | None + # Special case "for x in range(...)". + # NOTE We support the 3 arg form but only when `step` is constant- + # foldable, since it doesn't seem worth the hassle of supporting + # dynamically determining which direction of comparison to do. + # If we cannot constant fold `step`, we just fallback to stdlib range. + if num_args <= 2 or (num_args == 3 and builder.extract_int(expr.args[2])): if num_args == 1: start_reg: Value = Integer(0) end_reg = builder.accept(expr.args[0]) @@ -470,14 +470,11 @@ def make_for_loop_generator( else: start_reg = builder.accept(expr.args[0]) end_reg = builder.accept(expr.args[1]) - step = 1 if num_args == 2 else builder.extract_int(expr.args[2]) + step = 1 if num_args == 2 else cast(int, builder.extract_int(expr.args[2])) - if step: - for_range = ForRange(builder, index, body_block, loop_exit, line, nested) - for_range.init(start_reg, end_reg, step) - return for_range - - # If we could not constant fold `step`, we just fallback to calling stdlib implementation + for_range = ForRange(builder, index, body_block, loop_exit, line, nested) + for_range.init(start_reg, end_reg, step) + return for_range elif ( expr.callee.fullname == "builtins.enumerate" From a2669827b2dad731a2d3ae1dac02e9aa5a0ee857 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Tue, 21 Oct 2025 17:36:44 -0400 Subject: [PATCH 05/10] Update for_helpers.py --- mypyc/irbuild/for_helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index 88680610c964..7da12b6ed418 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -7,7 +7,7 @@ from __future__ import annotations -from typing import Callable, ClassVar +from typing import Callable, ClassVar, cast from mypy.nodes import ( ARG_POS, From b5d519c994674729c24118d09a29cbd1e00b5c58 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Tue, 21 Oct 2025 19:24:38 -0400 Subject: [PATCH 06/10] Update commandline.test --- mypyc/test-data/commandline.test | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/mypyc/test-data/commandline.test b/mypyc/test-data/commandline.test index 392ad3620790..05c059b33e47 100644 --- a/mypyc/test-data/commandline.test +++ b/mypyc/test-data/commandline.test @@ -168,14 +168,13 @@ from typing import Final, List, Any, AsyncIterable from mypy_extensions import trait, mypyc_attr def busted(b: bool) -> None: - for i in range(1, 10, 0): # E: range() step can't be zero - try: - if i == 5: - break # E: break inside try/finally block is unimplemented - elif i == 4: - continue # E: continue inside try/finally block is unimplemented - finally: - print('oops') + try: + if i == 5: + break # E: break inside try/finally block is unimplemented + elif i == 4: + continue # E: continue inside try/finally block is unimplemented + finally: + print('oops') print(sum([1,2,3])) From 4fda93220c499d353943ba8d01569aeb239341be Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Tue, 21 Oct 2025 19:46:02 -0400 Subject: [PATCH 07/10] Update commandline.test --- mypyc/test-data/commandline.test | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/mypyc/test-data/commandline.test b/mypyc/test-data/commandline.test index 05c059b33e47..bbcec9bd6a0a 100644 --- a/mypyc/test-data/commandline.test +++ b/mypyc/test-data/commandline.test @@ -168,13 +168,14 @@ from typing import Final, List, Any, AsyncIterable from mypy_extensions import trait, mypyc_attr def busted(b: bool) -> None: - try: - if i == 5: - break # E: break inside try/finally block is unimplemented - elif i == 4: - continue # E: continue inside try/finally block is unimplemented - finally: - print('oops') + for i in range(1, 10): + try: + if i == 5: + break # E: break inside try/finally block is unimplemented + elif i == 4: + continue # E: continue inside try/finally block is unimplemented + finally: + print('oops') print(sum([1,2,3])) From 3ad0bad933200e04f6cf424124d33fec7db2d995 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Sat, 25 Oct 2025 17:15:13 -0400 Subject: [PATCH 08/10] Update for_helpers.py --- mypyc/irbuild/for_helpers.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index 7da12b6ed418..2fe20341b81c 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -7,7 +7,7 @@ from __future__ import annotations -from typing import Callable, ClassVar, cast +from typing import Callable, ClassVar from mypy.nodes import ( ARG_POS, @@ -470,7 +470,8 @@ def make_for_loop_generator( else: start_reg = builder.accept(expr.args[0]) end_reg = builder.accept(expr.args[1]) - step = 1 if num_args == 2 else cast(int, builder.extract_int(expr.args[2])) + step = 1 if num_args == 2 else builder.extract_int(expr.args[2]) + assert isinstance(step, int), "this was validated above, the assert is for mypy" for_range = ForRange(builder, index, body_block, loop_exit, line, nested) for_range.init(start_reg, end_reg, step) From 0bb8e0e1966d631306b959a29a65ce9c8622b3c5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 25 Oct 2025 21:16:33 +0000 Subject: [PATCH 09/10] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypyc/irbuild/for_helpers.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index 2fe20341b81c..60e3bfa31d52 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -471,7 +471,9 @@ def make_for_loop_generator( start_reg = builder.accept(expr.args[0]) end_reg = builder.accept(expr.args[1]) step = 1 if num_args == 2 else builder.extract_int(expr.args[2]) - assert isinstance(step, int), "this was validated above, the assert is for mypy" + assert isinstance( + step, int + ), "this was validated above, the assert is for mypy" for_range = ForRange(builder, index, body_block, loop_exit, line, nested) for_range.init(start_reg, end_reg, step) From 57be7b856a1a6f63a6729e8f116b8f8afbe1914d Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Sat, 25 Oct 2025 18:51:18 -0400 Subject: [PATCH 10/10] Update for_helpers.py --- mypyc/irbuild/for_helpers.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index 60e3bfa31d52..62c76a116ef1 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -470,10 +470,11 @@ def make_for_loop_generator( else: start_reg = builder.accept(expr.args[0]) end_reg = builder.accept(expr.args[1]) - step = 1 if num_args == 2 else builder.extract_int(expr.args[2]) + step_ = 1 if num_args == 2 else builder.extract_int(expr.args[2]) assert isinstance( - step, int + step_, int ), "this was validated above, the assert is for mypy" + step = step_ for_range = ForRange(builder, index, body_block, loop_exit, line, nested) for_range.init(start_reg, end_reg, step)