From b3f3eab09e887f8dbc8dee19d2c5983e66cb7d12 Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Tue, 12 Aug 2025 02:15:57 +0000 Subject: [PATCH 01/29] [mypyc] feat: ForFilter generator helper for builtins.filter --- mypyc/irbuild/for_helpers.py | 52 ++++++++++++++++++++++ mypyc/test-data/irbuild-basic.test | 70 ++++++++++++++++++++++++++++++ mypyc/test-data/run-loops.test | 30 +++++++++++++ 3 files changed, 152 insertions(+) diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index 5cf89f579ec4..99f51339f912 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -490,6 +490,16 @@ def make_for_loop_generator( for_list = ForSequence(builder, index, body_block, loop_exit, line, nested) for_list.init(expr_reg, target_type, reverse=True) return for_list + + elif ( + expr.callee.fullname == "builtins.filter" + and len(expr.args) == 2 + and all(k == ARG_POS for k in expr.arg_kinds) + ): + for_filter = ForFilter(builder, index, body_block, loop_exit, line, nested) + for_filter.init(index, expr.args[0], expr.args[1]) + return for_filter + if isinstance(expr, CallExpr) and isinstance(expr.callee, MemberExpr) and not expr.args: # Special cases for dictionary iterator methods, like dict.items(). rtype = builder.node_type(expr.callee.expr) @@ -1147,3 +1157,45 @@ def gen_step(self) -> None: def gen_cleanup(self) -> None: for gen in self.gens: gen.gen_cleanup() + + +class ForFilter(ForGenerator): + """Generate optimized IR for a for loop over filter(f, iterable).""" + + def need_cleanup(self) -> bool: + # The wrapped for loops might need cleanup. We might generate a + # redundant cleanup block, but that's okay. + return True + + def init(self, index: Lvalue, func: Expression, iterable: Expression) -> None: + self.filter_func = self.builder.accept(func) + self.iterable = iterable + self.index = index + + self.gen = make_for_loop_generator( + self.builder, self.index, self.iterable, self.body_block, self.loop_exit, self.line, is_async=False, nested=True + ) + + def gen_condition(self) -> None: + builder = self.builder + line = self.line + # First, get the next item from the sub-generator + self.gen.gen_condition() + # Now, filter: only enter the body if func(item) is truthy + filter_block = BasicBlock() + builder.activate_block(filter_block) + self.gen.begin_body() + item = builder.read(builder.get_assignment_target(self.index), line) + # TODO: implement logic to handle c calls of native functions + result = builder.py_call(self.filter_func, [item], line) + builder.add_bool_branch(result, self.body_block, self.loop_exit) + + def begin_body(self) -> None: + # The item is already assigned to self.index by the sub-generator. + pass + + def gen_step(self) -> None: + self.gen.gen_step() + + def gen_cleanup(self) -> None: + self.gen.gen_cleanup() diff --git a/mypyc/test-data/irbuild-basic.test b/mypyc/test-data/irbuild-basic.test index 4a7d315ec836..46db61125f5b 100644 --- a/mypyc/test-data/irbuild-basic.test +++ b/mypyc/test-data/irbuild-basic.test @@ -3546,3 +3546,73 @@ L0: r2 = PyObject_Vectorcall(r1, 0, 0, 0) r3 = box(None, 1) return r3 + +[case testForFilter] +def f(x: int) -> bool: + return bool(x % 2) +def g(a: list[int]) -> int: + s = 0 + for x in filter(f, a): + s += x + return s +[out] +def f(x): + x, r0 :: int + r1 :: bit +L0: + r0 = CPyTagged_Remainder(x, 4) + r1 = r0 != 0 + return r1 +def g(a): + a :: list + s :: int + r0 :: dict + r1 :: str + r2 :: object + r3, r4 :: native_int + r5 :: bit + r6 :: object + r7, x :: int + r8 :: object + r9 :: object[1] + r10 :: object_ptr + r11 :: object + r12 :: i32 + r13 :: bit + r14 :: bool + r15 :: int + r16 :: native_int +L0: + s = 0 + r0 = __main__.globals :: static + r1 = 'f' + r2 = CPyDict_GetItem(r0, r1) + r3 = 0 +L1: + r4 = var_object_size a + r5 = r3 < r4 :: signed + if r5 goto L3 else goto L5 :: bool +L2: + r6 = list_get_item_unsafe a, r3 + r7 = unbox(int, r6) + x = r7 + r8 = box(int, x) + r9 = [r8] + r10 = load_address r9 + r11 = PyObject_Vectorcall(r2, r10, 1, 0) + keep_alive r8 + r12 = PyObject_IsTrue(r11) + r13 = r12 >= 0 :: signed + r14 = truncate r12: i32 to builtins.bool + if r14 goto L3 else goto L5 :: bool +L3: + r15 = CPyTagged_Add(s, x) + s = r15 +L4: + r16 = r3 + 1 + r3 = r16 + goto L1 +L5: +L6: + return s + diff --git a/mypyc/test-data/run-loops.test b/mypyc/test-data/run-loops.test index 3cbb07297e6e..3f4de2f4c9cc 100644 --- a/mypyc/test-data/run-loops.test +++ b/mypyc/test-data/run-loops.test @@ -571,3 +571,33 @@ print([x for x in native.Vector2(4, -5.2)]) [out] Vector2(x=-2, y=3.1) \[4, -5.2] + +[case testRunForFilter] +def f(a: list[int]) -> int: + s = 0 + for x in filter(lambda x: x % 2 == 0, a): + s += x + return s + +print(f([1, 2, 3, 4, 5, 6])) +print(f([1, 3, 5])) +print(f([])) + +[out] +12 +0 +0 + +[case testRunForFilterEdgeCases] +def f(a: list[int]) -> int: + s = 0 + for x in filter(lambda x: x > 10, a): + s += x + return s + +print(f([5, 15, 25])) +print(f([])) + +[out] +40 +0 From 67818c616d5c8ef15ae93821b821e11e47eba798 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 02:18:18 +0000 Subject: [PATCH 02/29] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypyc/irbuild/for_helpers.py | 13 ++++++++++--- mypyc/test-data/irbuild-basic.test | 1 - 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index 99f51339f912..1b824e2d1ddd 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -490,7 +490,7 @@ def make_for_loop_generator( for_list = ForSequence(builder, index, body_block, loop_exit, line, nested) for_list.init(expr_reg, target_type, reverse=True) return for_list - + elif ( expr.callee.fullname == "builtins.filter" and len(expr.args) == 2 @@ -499,7 +499,7 @@ def make_for_loop_generator( for_filter = ForFilter(builder, index, body_block, loop_exit, line, nested) for_filter.init(index, expr.args[0], expr.args[1]) return for_filter - + if isinstance(expr, CallExpr) and isinstance(expr.callee, MemberExpr) and not expr.args: # Special cases for dictionary iterator methods, like dict.items(). rtype = builder.node_type(expr.callee.expr) @@ -1173,7 +1173,14 @@ def init(self, index: Lvalue, func: Expression, iterable: Expression) -> None: self.index = index self.gen = make_for_loop_generator( - self.builder, self.index, self.iterable, self.body_block, self.loop_exit, self.line, is_async=False, nested=True + self.builder, + self.index, + self.iterable, + self.body_block, + self.loop_exit, + self.line, + is_async=False, + nested=True, ) def gen_condition(self) -> None: diff --git a/mypyc/test-data/irbuild-basic.test b/mypyc/test-data/irbuild-basic.test index 46db61125f5b..4fb9869c9bf0 100644 --- a/mypyc/test-data/irbuild-basic.test +++ b/mypyc/test-data/irbuild-basic.test @@ -3615,4 +3615,3 @@ L4: L5: L6: return s - From 74b7a6ed2e958d349eb1b9e68bf26fcd3f41ecfd Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Tue, 12 Aug 2025 03:19:42 +0000 Subject: [PATCH 03/29] fix: add filter to ir fixtures --- mypyc/test-data/fixtures/ir.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/mypyc/test-data/fixtures/ir.py b/mypyc/test-data/fixtures/ir.py index 76afc1ea58cc..cddd05ff5fe9 100644 --- a/mypyc/test-data/fixtures/ir.py +++ b/mypyc/test-data/fixtures/ir.py @@ -7,6 +7,8 @@ overload, Mapping, Union, Callable, Sequence, FrozenSet, Protocol ) +from typing_extensions import Self + _T = TypeVar('_T') T_co = TypeVar('T_co', covariant=True) T_contra = TypeVar('T_contra', contravariant=True) @@ -405,3 +407,22 @@ class classmethod: pass class staticmethod: pass NotImplemented: Any = ... + +_T1 = TypeVar("_T1") +_T2 = TypeVar("_T2") + +class map(Generic[_S]): + @overload + def __new__(cls, func: Callable[[_T1], _S], iterable: Iterable[_T1], /) -> Self: ... + @overload + def __new__(cls, func: Callable[[_T1, _T2], _S], iterable: Iterable[_T1], iter2: Iterable[_T2], /) -> Self: ... + def __iter__(self) -> Self: ... + def __next__(self) -> _S: ... + +class filter(Generic[_T]): + @overload + def __new__(cls, function: None, iterable: Iterable[_T | None], /) -> Self: ... + @overload + def __new__(cls, function: Callable[[_T], Any], iterable: Iterable[_T], /) -> Self: ... + def __iter__(self) -> Self: ... + def __next__(self) -> _T: ... From eeb09ab88211fc212220b50799671500a56a25ec Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Tue, 12 Aug 2025 03:19:42 +0000 Subject: [PATCH 04/29] fix: run tests --- mypyc/irbuild/for_helpers.py | 24 ++++++++++++++---------- mypyc/test-data/irbuild-basic.test | 10 ++++++---- mypyc/test-data/run-loops.test | 4 ++++ 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index 1b824e2d1ddd..c71a7c276510 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -1184,22 +1184,26 @@ def init(self, index: Lvalue, func: Expression, iterable: Expression) -> None: ) def gen_condition(self) -> None: - builder = self.builder - line = self.line - # First, get the next item from the sub-generator self.gen.gen_condition() - # Now, filter: only enter the body if func(item) is truthy - filter_block = BasicBlock() - builder.activate_block(filter_block) + + def begin_body(self) -> None: + # 1. Assign the next item to the loop variable self.gen.begin_body() + + # 2. Call the filter function + builder = self.builder + line = self.line item = builder.read(builder.get_assignment_target(self.index), line) # TODO: implement logic to handle c calls of native functions result = builder.py_call(self.filter_func, [item], line) - builder.add_bool_branch(result, self.body_block, self.loop_exit) - def begin_body(self) -> None: - # The item is already assigned to self.index by the sub-generator. - pass + # Now, filter: only enter the body if func(item) is truthy + cont_block, rest_block = BasicBlock(), BasicBlock() + builder.add_bool_branch(result, rest_block, cont_block) + builder.activate_block(cont_block) + builder.nonlocal_control[-1].gen_continue(builder, line) + builder.goto_and_activate(rest_block) + # At this point, the rest of the loop body (user code) will be emitted def gen_step(self) -> None: self.gen.gen_step() diff --git a/mypyc/test-data/irbuild-basic.test b/mypyc/test-data/irbuild-basic.test index 4fb9869c9bf0..fe27d3f02486 100644 --- a/mypyc/test-data/irbuild-basic.test +++ b/mypyc/test-data/irbuild-basic.test @@ -3591,7 +3591,7 @@ L0: L1: r4 = var_object_size a r5 = r3 < r4 :: signed - if r5 goto L3 else goto L5 :: bool + if r5 goto L2 else goto L6 :: bool L2: r6 = list_get_item_unsafe a, r3 r7 = unbox(int, r6) @@ -3604,14 +3604,16 @@ L2: r12 = PyObject_IsTrue(r11) r13 = r12 >= 0 :: signed r14 = truncate r12: i32 to builtins.bool - if r14 goto L3 else goto L5 :: bool + if r14 goto L4 else goto L3 :: bool L3: + goto L5 +L4: r15 = CPyTagged_Add(s, x) s = r15 -L4: +L5: r16 = r3 + 1 r3 = r16 goto L1 -L5: L6: +L7: return s diff --git a/mypyc/test-data/run-loops.test b/mypyc/test-data/run-loops.test index 3f4de2f4c9cc..755ba04a6fbd 100644 --- a/mypyc/test-data/run-loops.test +++ b/mypyc/test-data/run-loops.test @@ -579,6 +579,8 @@ def f(a: list[int]) -> int: s += x return s +[file driver.py] +from native import f print(f([1, 2, 3, 4, 5, 6])) print(f([1, 3, 5])) print(f([])) @@ -595,6 +597,8 @@ def f(a: list[int]) -> int: s += x return s +[file driver.py] +from native import f print(f([5, 15, 25])) print(f([])) From ddc13b8c647e8b2875f8af4579ce8d378496f210 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 03:21:09 +0000 Subject: [PATCH 05/29] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypyc/test-data/fixtures/ir.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypyc/test-data/fixtures/ir.py b/mypyc/test-data/fixtures/ir.py index cddd05ff5fe9..010aa8814756 100644 --- a/mypyc/test-data/fixtures/ir.py +++ b/mypyc/test-data/fixtures/ir.py @@ -418,7 +418,7 @@ def __new__(cls, func: Callable[[_T1], _S], iterable: Iterable[_T1], /) -> Self: def __new__(cls, func: Callable[[_T1, _T2], _S], iterable: Iterable[_T1], iter2: Iterable[_T2], /) -> Self: ... def __iter__(self) -> Self: ... def __next__(self) -> _S: ... - + class filter(Generic[_T]): @overload def __new__(cls, function: None, iterable: Iterable[_T | None], /) -> Self: ... From fc12ceab55f4c224d91d62a9f7a0bceff4b7a22f Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Tue, 12 Aug 2025 03:58:07 +0000 Subject: [PATCH 06/29] test with None --- mypyc/irbuild/for_helpers.py | 13 +++++++-- mypyc/test-data/irbuild-basic.test | 44 ++++++++++++++++++++++++++++++ mypyc/test-data/run-loops.test | 14 ++++++++++ 3 files changed, 68 insertions(+), 3 deletions(-) diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index c71a7c276510..2a61dbbdc345 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -22,6 +22,7 @@ SetExpr, TupleExpr, TypeAlias, + Var, ) from mypyc.ir.ops import ( ERR_NEVER, @@ -1168,7 +1169,10 @@ def need_cleanup(self) -> bool: return True def init(self, index: Lvalue, func: Expression, iterable: Expression) -> None: - self.filter_func = self.builder.accept(func) + if isinstance(func, NameExpr) and isinstance(func.node, Var) and func.node.fullname == "builtins.None": + self.filter_func = None + else: + self.filter_func = self.builder.accept(func) self.iterable = iterable self.index = index @@ -1194,8 +1198,11 @@ def begin_body(self) -> None: builder = self.builder line = self.line item = builder.read(builder.get_assignment_target(self.index), line) - # TODO: implement logic to handle c calls of native functions - result = builder.py_call(self.filter_func, [item], line) + if self.filter_func is None: + result = item + else: + # TODO: implement logic to handle c calls of native functions + result = builder.py_call(self.filter_func, [item], line) # Now, filter: only enter the body if func(item) is truthy cont_block, rest_block = BasicBlock(), BasicBlock() diff --git a/mypyc/test-data/irbuild-basic.test b/mypyc/test-data/irbuild-basic.test index fe27d3f02486..5d257f0adac2 100644 --- a/mypyc/test-data/irbuild-basic.test +++ b/mypyc/test-data/irbuild-basic.test @@ -3617,3 +3617,47 @@ L5: L6: L7: return s + +[case testForFilterNone] +def f(a: list[int]) -> int: + c = 0 + for x in filter(None, a): + c += 1 + return 0 + +[out] +def f(a): + a :: list + c :: int + r0, r1 :: native_int + r2 :: bit + r3 :: object + r4, x :: int + r5 :: bit + r6 :: int + r7 :: native_int +L0: + c = 0 + r0 = 0 +L1: + r1 = var_object_size a + r2 = r0 < r1 :: signed + if r2 goto L2 else goto L6 :: bool +L2: + r3 = list_get_item_unsafe a, r0 + r4 = unbox(int, r3) + x = r4 + r5 = x != 0 + if r5 goto L4 else goto L3 :: bool +L3: + goto L5 +L4: + r6 = CPyTagged_Add(c, 2) + c = r6 +L5: + r7 = r0 + 1 + r0 = r7 + goto L1 +L6: +L7: + return 0 diff --git a/mypyc/test-data/run-loops.test b/mypyc/test-data/run-loops.test index 755ba04a6fbd..497b9254eda9 100644 --- a/mypyc/test-data/run-loops.test +++ b/mypyc/test-data/run-loops.test @@ -590,6 +590,20 @@ print(f([])) 0 0 +[case testRunForFilterNone] +def f(a: list[int]) -> int: + c = 0 + for x in filter(None, a): + c += 1 + return c + +[file driver.py] +from native import f +print(f([0, 1, 2, 3, 4, 5, 6])) + +[out] +6 + [case testRunForFilterEdgeCases] def f(a: list[int]) -> int: s = 0 From 54ad04e6ada62aacf88f83808c7642bc13040a5e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 03:59:44 +0000 Subject: [PATCH 07/29] [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, 5 insertions(+), 1 deletion(-) diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index 2a61dbbdc345..185e56700033 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -1169,7 +1169,11 @@ def need_cleanup(self) -> bool: return True def init(self, index: Lvalue, func: Expression, iterable: Expression) -> None: - if isinstance(func, NameExpr) and isinstance(func.node, Var) and func.node.fullname == "builtins.None": + if ( + isinstance(func, NameExpr) + and isinstance(func.node, Var) + and func.node.fullname == "builtins.None" + ): self.filter_func = None else: self.filter_func = self.builder.accept(func) From eae92092c66ec40049f43272454ea2052087f538 Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Tue, 12 Aug 2025 15:06:09 +0000 Subject: [PATCH 08/29] IR cases for testing C calls --- mypyc/test-data/irbuild-basic.test | 142 ++++++++++++++++++++++++++++- 1 file changed, 141 insertions(+), 1 deletion(-) diff --git a/mypyc/test-data/irbuild-basic.test b/mypyc/test-data/irbuild-basic.test index 5d257f0adac2..5b2a634cbf43 100644 --- a/mypyc/test-data/irbuild-basic.test +++ b/mypyc/test-data/irbuild-basic.test @@ -3547,7 +3547,7 @@ L0: r3 = box(None, 1) return r3 -[case testForFilter] +[case testForFilterBool] def f(x: int) -> bool: return bool(x % 2) def g(a: list[int]) -> int: @@ -3618,6 +3618,146 @@ L6: L7: return s +[case testForFilterInt] +def f(x: int) -> int: + return x % 2 +def g(a: list[int]) -> int: + s = 0 + for x in filter(f, a): + s += x + return s +[out] +def f(x): + x, r0 :: int +L0: + r0 = CPyTagged_Remainder(x, 4) + return r0 +def g(a): + a :: list + s :: int + r0 :: dict + r1 :: str + r2 :: object + r3, r4 :: native_int + r5 :: bit + r6 :: object + r7, x :: int + r8 :: object + r9 :: object[1] + r10 :: object_ptr + r11 :: object + r12 :: i32 + r13 :: bit + r14 :: bool + r15 :: int + r16 :: native_int +L0: + s = 0 + r0 = __main__.globals :: static + r1 = 'f' + r2 = CPyDict_GetItem(r0, r1) + r3 = 0 +L1: + r4 = var_object_size a + r5 = r3 < r4 :: signed + if r5 goto L2 else goto L6 :: bool +L2: + r6 = list_get_item_unsafe a, r3 + r7 = unbox(int, r6) + x = r7 + r8 = box(int, x) + r9 = [r8] + r10 = load_address r9 + r11 = PyObject_Vectorcall(r2, r10, 1, 0) + keep_alive r8 + r12 = PyObject_IsTrue(r11) + r13 = r12 >= 0 :: signed + r14 = truncate r12: i32 to builtins.bool + if r14 goto L4 else goto L3 :: bool +L3: + goto L5 +L4: + r15 = CPyTagged_Add(s, x) + s = r15 +L5: + r16 = r3 + 1 + r3 = r16 + goto L1 +L6: +L7: + return s + +[case testForFilterStr] +def f(x: int) -> str: + return str(x % 2) +def g(a: list[int]) -> int: + s = 0 + for x in filter(f, a): + s += x + return s +[out] +def f(x): + x, r0 :: int + r1 :: str +L0: + r0 = CPyTagged_Remainder(x, 4) + r1 = CPyTagged_Str(r0) + return r1 +def g(a): + a :: list + s :: int + r0 :: dict + r1 :: str + r2 :: object + r3, r4 :: native_int + r5 :: bit + r6 :: object + r7, x :: int + r8 :: object + r9 :: object[1] + r10 :: object_ptr + r11 :: object + r12 :: i32 + r13 :: bit + r14 :: bool + r15 :: int + r16 :: native_int +L0: + s = 0 + r0 = __main__.globals :: static + r1 = 'f' + r2 = CPyDict_GetItem(r0, r1) + r3 = 0 +L1: + r4 = var_object_size a + r5 = r3 < r4 :: signed + if r5 goto L2 else goto L6 :: bool +L2: + r6 = list_get_item_unsafe a, r3 + r7 = unbox(int, r6) + x = r7 + r8 = box(int, x) + r9 = [r8] + r10 = load_address r9 + r11 = PyObject_Vectorcall(r2, r10, 1, 0) + keep_alive r8 + r12 = PyObject_IsTrue(r11) + r13 = r12 >= 0 :: signed + r14 = truncate r12: i32 to builtins.bool + if r14 goto L4 else goto L3 :: bool +L3: + goto L5 +L4: + r15 = CPyTagged_Add(s, x) + s = r15 +L5: + r16 = r3 + 1 + r3 = r16 + goto L1 +L6: +L7: + return s + [case testForFilterNone] def f(a: list[int]) -> int: c = 0 From 9941d54d895ceb7d13ebef4e6dd0e28f93142ac5 Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Tue, 12 Aug 2025 16:34:42 +0000 Subject: [PATCH 09/29] feat: handle native calls and primitive ops --- mypyc/irbuild/for_helpers.py | 20 ++++-- mypyc/test-data/irbuild-basic.test | 98 ++++++++++-------------------- 2 files changed, 46 insertions(+), 72 deletions(-) diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index 2a61dbbdc345..03b6d1c74004 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -1169,10 +1169,11 @@ def need_cleanup(self) -> bool: return True def init(self, index: Lvalue, func: Expression, iterable: Expression) -> None: + self.filter_func_def = func if isinstance(func, NameExpr) and isinstance(func.node, Var) and func.node.fullname == "builtins.None": - self.filter_func = None + self.filter_func_val = None else: - self.filter_func = self.builder.accept(func) + self.filter_func_val = self.builder.accept(func) self.iterable = iterable self.index = index @@ -1198,11 +1199,20 @@ def begin_body(self) -> None: builder = self.builder line = self.line item = builder.read(builder.get_assignment_target(self.index), line) - if self.filter_func is None: + + if self.filter_func_val is None: result = item else: - # TODO: implement logic to handle c calls of native functions - result = builder.py_call(self.filter_func, [item], line) + fake_call_expr = CallExpr( + self.filter_func_def, + [ + # this is unused in call_refexpr_with_args which is good because + # we don't have an Expression to put here. But it works just fine. + ], + [ARG_POS], + [None], + ) + result = builder.call_refexpr_with_args(fake_call_expr, self.filter_func_def, [item]) # Now, filter: only enter the body if func(item) is truthy cont_block, rest_block = BasicBlock(), BasicBlock() diff --git a/mypyc/test-data/irbuild-basic.test b/mypyc/test-data/irbuild-basic.test index 5b2a634cbf43..cd8a992e0b01 100644 --- a/mypyc/test-data/irbuild-basic.test +++ b/mypyc/test-data/irbuild-basic.test @@ -3573,15 +3573,9 @@ def g(a): r5 :: bit r6 :: object r7, x :: int - r8 :: object - r9 :: object[1] - r10 :: object_ptr - r11 :: object - r12 :: i32 - r13 :: bit - r14 :: bool - r15 :: int - r16 :: native_int + r8 :: bool + r9 :: int + r10 :: native_int L0: s = 0 r0 = __main__.globals :: static @@ -3596,23 +3590,16 @@ L2: r6 = list_get_item_unsafe a, r3 r7 = unbox(int, r6) x = r7 - r8 = box(int, x) - r9 = [r8] - r10 = load_address r9 - r11 = PyObject_Vectorcall(r2, r10, 1, 0) - keep_alive r8 - r12 = PyObject_IsTrue(r11) - r13 = r12 >= 0 :: signed - r14 = truncate r12: i32 to builtins.bool - if r14 goto L4 else goto L3 :: bool + r8 = f(x) + if r8 goto L4 else goto L3 :: bool L3: goto L5 L4: - r15 = CPyTagged_Add(s, x) - s = r15 + r9 = CPyTagged_Add(s, x) + s = r9 L5: - r16 = r3 + 1 - r3 = r16 + r10 = r3 + 1 + r3 = r10 goto L1 L6: L7: @@ -3641,16 +3628,10 @@ def g(a): r3, r4 :: native_int r5 :: bit r6 :: object - r7, x :: int - r8 :: object - r9 :: object[1] - r10 :: object_ptr - r11 :: object - r12 :: i32 - r13 :: bit - r14 :: bool - r15 :: int - r16 :: native_int + r7, x, r8 :: int + r9 :: bit + r10 :: int + r11 :: native_int L0: s = 0 r0 = __main__.globals :: static @@ -3665,23 +3646,17 @@ L2: r6 = list_get_item_unsafe a, r3 r7 = unbox(int, r6) x = r7 - r8 = box(int, x) - r9 = [r8] - r10 = load_address r9 - r11 = PyObject_Vectorcall(r2, r10, 1, 0) - keep_alive r8 - r12 = PyObject_IsTrue(r11) - r13 = r12 >= 0 :: signed - r14 = truncate r12: i32 to builtins.bool - if r14 goto L4 else goto L3 :: bool + r8 = f(x) + r9 = r8 != 0 + if r9 goto L4 else goto L3 :: bool L3: goto L5 L4: - r15 = CPyTagged_Add(s, x) - s = r15 + r10 = CPyTagged_Add(s, x) + s = r10 L5: - r16 = r3 + 1 - r3 = r16 + r11 = r3 + 1 + r3 = r11 goto L1 L6: L7: @@ -3713,15 +3688,10 @@ def g(a): r5 :: bit r6 :: object r7, x :: int - r8 :: object - r9 :: object[1] - r10 :: object_ptr - r11 :: object - r12 :: i32 - r13 :: bit - r14 :: bool - r15 :: int - r16 :: native_int + r8 :: str + r9 :: bit + r10 :: int + r11 :: native_int L0: s = 0 r0 = __main__.globals :: static @@ -3736,23 +3706,17 @@ L2: r6 = list_get_item_unsafe a, r3 r7 = unbox(int, r6) x = r7 - r8 = box(int, x) - r9 = [r8] - r10 = load_address r9 - r11 = PyObject_Vectorcall(r2, r10, 1, 0) - keep_alive r8 - r12 = PyObject_IsTrue(r11) - r13 = r12 >= 0 :: signed - r14 = truncate r12: i32 to builtins.bool - if r14 goto L4 else goto L3 :: bool + r8 = f(x) + r9 = CPyStr_IsTrue(r8) + if r9 goto L4 else goto L3 :: bool L3: goto L5 L4: - r15 = CPyTagged_Add(s, x) - s = r15 + r10 = CPyTagged_Add(s, x) + s = r10 L5: - r16 = r3 + 1 - r3 = r16 + r11 = r3 + 1 + r3 = r11 goto L1 L6: L7: From 5237f0b0f6ee567fd27ff5d332150174f41bd134 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 16:36:51 +0000 Subject: [PATCH 10/29] [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, 5 insertions(+), 1 deletion(-) diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index 03b6d1c74004..b38abdb8cf6b 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -1170,7 +1170,11 @@ def need_cleanup(self) -> bool: def init(self, index: Lvalue, func: Expression, iterable: Expression) -> None: self.filter_func_def = func - if isinstance(func, NameExpr) and isinstance(func.node, Var) and func.node.fullname == "builtins.None": + if ( + isinstance(func, NameExpr) + and isinstance(func.node, Var) + and func.node.fullname == "builtins.None" + ): self.filter_func_val = None else: self.filter_func_val = self.builder.accept(func) From d68b833802f5455ec8d5df0cbdca19aeb6d768c7 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Tue, 12 Aug 2025 12:39:32 -0400 Subject: [PATCH 11/29] Update run-loops.test --- mypyc/test-data/run-loops.test | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/mypyc/test-data/run-loops.test b/mypyc/test-data/run-loops.test index 497b9254eda9..29acaa2c91f6 100644 --- a/mypyc/test-data/run-loops.test +++ b/mypyc/test-data/run-loops.test @@ -604,6 +604,36 @@ print(f([0, 1, 2, 3, 4, 5, 6])) [out] 6 +[case testRunForFilterNative] +def f(x: int) -> int: + return x % 2 +def g(a: list[int]) -> int: + c = 0 + for x in filter(f, a): + c += 1 + return c + +[file driver.py] +from native import g +print(g([0, 1, 2, 3, 4, 5, 6])) + +[out] +3 + +[case testRunForFilterPrimitiveOp] +def f(a: list[list[int]]) -> int: + c = 0 + for x in filter(len, a): + c += 1 + return c + +[file driver.py] +from native import f +print(f([[], [0, 1], [], [], [2, 3, 4], [5, 6]])) + +[out] +3 + [case testRunForFilterEdgeCases] def f(a: list[int]) -> int: s = 0 From c9680dc93803a5ccf170a755889ca8c65039be0d Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Tue, 12 Aug 2025 12:56:41 -0400 Subject: [PATCH 12/29] Update for_helpers.py --- mypyc/irbuild/for_helpers.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index b38abdb8cf6b..36c8fa6687c2 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -1164,9 +1164,7 @@ class ForFilter(ForGenerator): """Generate optimized IR for a for loop over filter(f, iterable).""" def need_cleanup(self) -> bool: - # The wrapped for loops might need cleanup. We might generate a - # redundant cleanup block, but that's okay. - return True + return self.gen.need_cleanup() def init(self, index: Lvalue, func: Expression, iterable: Expression) -> None: self.filter_func_def = func From 5bf4b220dff27380cdfda965fd15e2735dd3984f Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Tue, 12 Aug 2025 17:01:50 +0000 Subject: [PATCH 13/29] test primitive op --- mypyc/test-data/irbuild-basic.test | 58 ++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/mypyc/test-data/irbuild-basic.test b/mypyc/test-data/irbuild-basic.test index cd8a992e0b01..8caa06a1011d 100644 --- a/mypyc/test-data/irbuild-basic.test +++ b/mypyc/test-data/irbuild-basic.test @@ -3722,6 +3722,64 @@ L6: L7: return s +[case testForFilterPrimitiveOp] +def f(a: list[list[int]]) -> int: + s = 0 + for x in filter(len, a): + s += 1 + return s +[out] +def f(a): + x, r0 :: int + r1 :: str +L0: + r0 = CPyTagged_Remainder(x, 4) + r1 = CPyTagged_Str(r0) + return r1 +def g(a): + a :: list + s :: int + r0 :: dict + r1 :: str + r2 :: object + r3, r4 :: native_int + r5 :: bit + r6 :: object + r7, x :: int + r8 :: str + r9 :: bit + r10 :: int + r11 :: native_int +L0: + s = 0 + r0 = __main__.globals :: static + r1 = 'f' + r2 = CPyDict_GetItem(r0, r1) + r3 = 0 +L1: + r4 = var_object_size a + r5 = r3 < r4 :: signed + if r5 goto L2 else goto L6 :: bool +L2: + r6 = list_get_item_unsafe a, r3 + r7 = unbox(int, r6) + x = r7 + r8 = f(x) + r9 = CPyStr_IsTrue(r8) + if r9 goto L4 else goto L3 :: bool +L3: + goto L5 +L4: + r10 = CPyTagged_Add(s, x) + s = r10 +L5: + r11 = r3 + 1 + r3 = r11 + goto L1 +L6: +L7: + return s + [case testForFilterNone] def f(a: list[int]) -> int: c = 0 From c39bb4a5a7a923e7720a7d2d82ea70b7ec623768 Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Tue, 12 Aug 2025 17:28:21 +0000 Subject: [PATCH 14/29] feat: use speciailizers --- mypyc/irbuild/for_helpers.py | 13 +++++---- mypyc/test-data/irbuild-basic.test | 43 +++++++++++++----------------- 2 files changed, 27 insertions(+), 29 deletions(-) diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index 03b6d1c74004..da415fd20285 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -1205,14 +1205,17 @@ def begin_body(self) -> None: else: fake_call_expr = CallExpr( self.filter_func_def, - [ - # this is unused in call_refexpr_with_args which is good because - # we don't have an Expression to put here. But it works just fine. - ], + [self.index], [ARG_POS], [None], ) - result = builder.call_refexpr_with_args(fake_call_expr, self.filter_func_def, [item]) + + builder.types[fake_call_expr] = builder.types[self.index] + + # I put this here to prevent a circular import + from mypyc.irbuild.expression import transform_call_expr + + result = transform_call_expr(builder, fake_call_expr) # Now, filter: only enter the body if func(item) is truthy cont_block, rest_block = BasicBlock(), BasicBlock() diff --git a/mypyc/test-data/irbuild-basic.test b/mypyc/test-data/irbuild-basic.test index 8caa06a1011d..730bb458a692 100644 --- a/mypyc/test-data/irbuild-basic.test +++ b/mypyc/test-data/irbuild-basic.test @@ -3730,31 +3730,25 @@ def f(a: list[list[int]]) -> int: return s [out] def f(a): - x, r0 :: int - r1 :: str -L0: - r0 = CPyTagged_Remainder(x, 4) - r1 = CPyTagged_Str(r0) - return r1 -def g(a): a :: list s :: int - r0 :: dict + r0 :: object r1 :: str r2 :: object r3, r4 :: native_int r5 :: bit r6 :: object - r7, x :: int - r8 :: str - r9 :: bit - r10 :: int - r11 :: native_int + r7, x :: list + r8 :: native_int + r9 :: short_int + r10 :: bit + r11 :: int + r12 :: native_int L0: s = 0 - r0 = __main__.globals :: static - r1 = 'f' - r2 = CPyDict_GetItem(r0, r1) + r0 = builtins :: module + r1 = 'len' + r2 = CPyObject_GetAttr(r0, r1) r3 = 0 L1: r4 = var_object_size a @@ -3762,19 +3756,20 @@ L1: if r5 goto L2 else goto L6 :: bool L2: r6 = list_get_item_unsafe a, r3 - r7 = unbox(int, r6) + r7 = cast(list, r6) x = r7 - r8 = f(x) - r9 = CPyStr_IsTrue(r8) - if r9 goto L4 else goto L3 :: bool + r8 = var_object_size x + r9 = r8 << 1 + r10 = r9 != 0 + if r10 goto L4 else goto L3 :: bool L3: goto L5 L4: - r10 = CPyTagged_Add(s, x) - s = r10 + r11 = CPyTagged_Add(s, 2) + s = r11 L5: - r11 = r3 + 1 - r3 = r11 + r12 = r3 + 1 + r3 = r12 goto L1 L6: L7: From 9dceb9a821886c236e82b1f07fb228cae67e1604 Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Tue, 12 Aug 2025 17:29:19 +0000 Subject: [PATCH 15/29] Revert "Update for_helpers.py" This reverts commit c9680dc93803a5ccf170a755889ca8c65039be0d. --- 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 694fd26e1aa9..00d04e760622 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -1164,7 +1164,9 @@ class ForFilter(ForGenerator): """Generate optimized IR for a for loop over filter(f, iterable).""" def need_cleanup(self) -> bool: - return self.gen.need_cleanup() + # The wrapped for loops might need cleanup. We might generate a + # redundant cleanup block, but that's okay. + return True def init(self, index: Lvalue, func: Expression, iterable: Expression) -> None: self.filter_func_def = func From 7c8053fdff48d6a2b4b39066c4bd6f3fcf41bdbf Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 17:30:04 +0000 Subject: [PATCH 16/29] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypyc/irbuild/for_helpers.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index 00d04e760622..0cf9fde66f39 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -1207,12 +1207,7 @@ def begin_body(self) -> None: if self.filter_func_val is None: result = item else: - fake_call_expr = CallExpr( - self.filter_func_def, - [self.index], - [ARG_POS], - [None], - ) + fake_call_expr = CallExpr(self.filter_func_def, [self.index], [ARG_POS], [None]) builder.types[fake_call_expr] = builder.types[self.index] From 8aff832fdbdecd002bbd39e40e89dfa7c42b9e60 Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Tue, 12 Aug 2025 19:24:04 +0000 Subject: [PATCH 17/29] add to docs --- mypyc/doc/native_operations.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/mypyc/doc/native_operations.rst b/mypyc/doc/native_operations.rst index 3255dbedd98a..c8f8cf3329ff 100644 --- a/mypyc/doc/native_operations.rst +++ b/mypyc/doc/native_operations.rst @@ -37,6 +37,7 @@ Functions * ``slice(start, stop, step)`` * ``globals()`` * ``sorted(obj)`` +* ``filter(fn, iterable)`` Method decorators ----------------- From cec1a5d9637deafaba53ae21c6cefe85395b8bbc Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Wed, 13 Aug 2025 03:59:10 -0400 Subject: [PATCH 18/29] Update for_helpers.py --- mypyc/irbuild/for_helpers.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index 0cf9fde66f39..71b3e1d8c047 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -1209,8 +1209,6 @@ def begin_body(self) -> None: else: fake_call_expr = CallExpr(self.filter_func_def, [self.index], [ARG_POS], [None]) - builder.types[fake_call_expr] = builder.types[self.index] - # I put this here to prevent a circular import from mypyc.irbuild.expression import transform_call_expr From 572793cd20d6aec307a2a48ef8ca977dc83a27fe Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Thu, 14 Aug 2025 03:00:11 -0400 Subject: [PATCH 19/29] Update native_operations.rst --- mypyc/doc/native_operations.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mypyc/doc/native_operations.rst b/mypyc/doc/native_operations.rst index c8f8cf3329ff..4487bf8df121 100644 --- a/mypyc/doc/native_operations.rst +++ b/mypyc/doc/native_operations.rst @@ -37,7 +37,6 @@ Functions * ``slice(start, stop, step)`` * ``globals()`` * ``sorted(obj)`` -* ``filter(fn, iterable)`` Method decorators ----------------- @@ -55,3 +54,5 @@ These variants of statements have custom implementations: * ``for ... in seq:`` (for loop over a sequence) * ``for ... in enumerate(...):`` * ``for ... in zip(...):`` +* ``for ... in filter(...):`` +* ``for ... in itertools.filterfalse(...):`` From dbbbb579f774d20541d2d6e843e89c1f5a7eec9f Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Sat, 16 Aug 2025 10:40:34 -0400 Subject: [PATCH 20/29] 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 58bd74b271c9..1afdcee27026 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -1229,9 +1229,10 @@ def begin_body(self) -> None: fake_call_expr = CallExpr(self.filter_func_def, [self.index], [ARG_POS], [None]) # I put this here to prevent a circular import - from mypyc.irbuild.expression import transform_call_expr + #from mypyc.irbuild.expression import transform_call_expr - result = transform_call_expr(builder, fake_call_expr) + #result = transform_call_expr(builder, fake_call_expr) + resulr = builder.accept(fake_call_expr) # Now, filter: only enter the body if func(item) is truthy cont_block, rest_block = BasicBlock(), BasicBlock() From 0bc1d26c1ffd204724c64950813c686bf712e306 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 16 Aug 2025 14:41:52 +0000 Subject: [PATCH 21/29] [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, 2 insertions(+), 2 deletions(-) diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index 1afdcee27026..e9761c0cb1a8 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -1229,9 +1229,9 @@ def begin_body(self) -> None: fake_call_expr = CallExpr(self.filter_func_def, [self.index], [ARG_POS], [None]) # I put this here to prevent a circular import - #from mypyc.irbuild.expression import transform_call_expr + # from mypyc.irbuild.expression import transform_call_expr - #result = transform_call_expr(builder, fake_call_expr) + # result = transform_call_expr(builder, fake_call_expr) resulr = builder.accept(fake_call_expr) # Now, filter: only enter the body if func(item) is truthy From 7d56fa9d0ceb46cc8438cf6ff0ed4654b77d7935 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Sat, 16 Aug 2025 10:46:25 -0400 Subject: [PATCH 22/29] 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 e9761c0cb1a8..386c543a3c22 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -1232,7 +1232,7 @@ def begin_body(self) -> None: # from mypyc.irbuild.expression import transform_call_expr # result = transform_call_expr(builder, fake_call_expr) - resulr = builder.accept(fake_call_expr) + result = builder.accept(fake_call_expr) # Now, filter: only enter the body if func(item) is truthy cont_block, rest_block = BasicBlock(), BasicBlock() From 11b04c353845efabb3632f53b9b5e21bd81b87fd Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Sun, 7 Sep 2025 01:49:55 -0400 Subject: [PATCH 23/29] Update ir.py --- mypyc/test-data/fixtures/ir.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/mypyc/test-data/fixtures/ir.py b/mypyc/test-data/fixtures/ir.py index cf9325c3b2c3..77602cc26103 100644 --- a/mypyc/test-data/fixtures/ir.py +++ b/mypyc/test-data/fixtures/ir.py @@ -416,14 +416,6 @@ class staticmethod: pass _T1 = TypeVar("_T1") _T2 = TypeVar("_T2") -class map(Generic[_S]): - @overload - def __new__(cls, func: Callable[[_T1], _S], iterable: Iterable[_T1], /) -> Self: ... - @overload - def __new__(cls, func: Callable[[_T1, _T2], _S], iterable: Iterable[_T1], iter2: Iterable[_T2], /) -> Self: ... - def __iter__(self) -> Self: ... - def __next__(self) -> _S: ... - class filter(Generic[_T]): @overload def __new__(cls, function: None, iterable: Iterable[_T | None], /) -> Self: ... From fa54df2b29a80e7eeb1e9004e26b21c08b0b9f9e Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Sun, 7 Sep 2025 01:50:23 -0400 Subject: [PATCH 24/29] Update ir.py --- mypyc/test-data/fixtures/ir.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/mypyc/test-data/fixtures/ir.py b/mypyc/test-data/fixtures/ir.py index 77602cc26103..4ac24f8834b1 100644 --- a/mypyc/test-data/fixtures/ir.py +++ b/mypyc/test-data/fixtures/ir.py @@ -413,9 +413,6 @@ class staticmethod: pass NotImplemented: Any = ... -_T1 = TypeVar("_T1") -_T2 = TypeVar("_T2") - class filter(Generic[_T]): @overload def __new__(cls, function: None, iterable: Iterable[_T | None], /) -> Self: ... From d2edf7b484b904616f43f64888a99edc27f82f30 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Sun, 7 Sep 2025 01:50:58 -0400 Subject: [PATCH 25/29] Update native_operations.rst --- mypyc/doc/native_operations.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/mypyc/doc/native_operations.rst b/mypyc/doc/native_operations.rst index 4487bf8df121..3a6afba7872c 100644 --- a/mypyc/doc/native_operations.rst +++ b/mypyc/doc/native_operations.rst @@ -55,4 +55,3 @@ These variants of statements have custom implementations: * ``for ... in enumerate(...):`` * ``for ... in zip(...):`` * ``for ... in filter(...):`` -* ``for ... in itertools.filterfalse(...):`` From 715ce4694506fe2d88892462524583cef7a8d0ad Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Sun, 7 Sep 2025 01:58:15 -0400 Subject: [PATCH 26/29] refactor run tests --- mypyc/test-data/run-loops.test | 81 ++++++++++------------------------ 1 file changed, 23 insertions(+), 58 deletions(-) diff --git a/mypyc/test-data/run-loops.test b/mypyc/test-data/run-loops.test index 29acaa2c91f6..f701e1f18e98 100644 --- a/mypyc/test-data/run-loops.test +++ b/mypyc/test-data/run-loops.test @@ -578,74 +578,39 @@ def f(a: list[int]) -> int: for x in filter(lambda x: x % 2 == 0, a): s += x return s - -[file driver.py] -from native import f -print(f([1, 2, 3, 4, 5, 6])) -print(f([1, 3, 5])) -print(f([])) - -[out] -12 -0 -0 - -[case testRunForFilterNone] -def f(a: list[int]) -> int: +def g(a: list[int]) -> int: + s = 0 + for x in filter(lambda x: x > 10, a): + s += x + return s +def with_none(a: list[int]) -> int: c = 0 for x in filter(None, a): c += 1 return c - -[file driver.py] -from native import f -print(f([0, 1, 2, 3, 4, 5, 6])) - -[out] -6 - -[case testRunForFilterNative] -def f(x: int) -> int: +def native_func(x: int) -> int: return x % 2 -def g(a: list[int]) -> int: +def with_native_func(a: list[int]) -> int: c = 0 - for x in filter(f, a): + for x in filter(native_func, a): c += 1 return c - -[file driver.py] -from native import g -print(g([0, 1, 2, 3, 4, 5, 6])) - -[out] -3 - -[case testRunForFilterPrimitiveOp] -def f(a: list[list[int]]) -> int: +def with_primitive(a: list[list[int]]) -> int: c = 0 for x in filter(len, a): c += 1 return c -[file driver.py] -from native import f -print(f([[], [0, 1], [], [], [2, 3, 4], [5, 6]])) - -[out] -3 - -[case testRunForFilterEdgeCases] -def f(a: list[int]) -> int: - s = 0 - for x in filter(lambda x: x > 10, a): - s += x - return s - -[file driver.py] -from native import f -print(f([5, 15, 25])) -print(f([])) - -[out] -40 -0 +def test_run_for_filter() -> None: + assert f([1, 2, 3, 4, 5, 6]) == 12 + assert f([1, 3, 5]) == 0 + assert f([]) == 0 +def test_run_for_filter_with_none() -> None: + assert with_none([0, 1, 2, 3, 4, 5, 6]) == 6 +def test_run_for_filter_with_native_func() -> None: + assert with_native_func([0, 1, 2, 3, 4, 5, 6]) == 3 +def test_run_for_filter_with_primitive() -> None: + assert with_primitive([[], [0, 1], [], [], [2, 3, 4], [5, 6]]) == 3 +def test_run_for_filter_edge_cases() -> None: + assert g([5, 15, 25]) == 40 + assert g([]) == 0 From a3b65b3263d4f81e716ef6faade10fe582c436d5 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Mon, 8 Sep 2025 10:23:57 -0400 Subject: [PATCH 27/29] Update mypyc/test-data/fixtures/ir.py oh, whoops! Co-authored-by: Brian Schubert --- mypyc/test-data/fixtures/ir.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/mypyc/test-data/fixtures/ir.py b/mypyc/test-data/fixtures/ir.py index 4ac24f8834b1..ee8ec6d131f3 100644 --- a/mypyc/test-data/fixtures/ir.py +++ b/mypyc/test-data/fixtures/ir.py @@ -7,8 +7,6 @@ overload, Mapping, Union, Callable, Sequence, FrozenSet, Protocol ) -from typing_extensions import Self - _T = TypeVar('_T') T_co = TypeVar('T_co', covariant=True) T_contra = TypeVar('T_contra', contravariant=True) From 1197e4e7fdd7d516691dff211b3b3519a5a9c026 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Sun, 28 Sep 2025 15:43:40 -0400 Subject: [PATCH 28/29] fix ir generaton --- mypyc/irbuild/for_helpers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index 698502941b8d..4905c7bf442d 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -1242,8 +1242,8 @@ def begin_body(self) -> None: # I put this here to prevent a circular import # from mypyc.irbuild.expression import transform_call_expr - # result = transform_call_expr(builder, fake_call_expr) - result = builder.accept(fake_call_expr) + result = transform_call_expr(builder, fake_call_expr) + # result = builder.accept(fake_call_expr) # Now, filter: only enter the body if func(item) is truthy cont_block, rest_block = BasicBlock(), BasicBlock() From 75f61e3b86640ad087cd33a4d6da8de6a7bb22f5 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Sun, 28 Sep 2025 15:46:26 -0400 Subject: [PATCH 29/29] 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 4905c7bf442d..4078b6238191 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -1240,7 +1240,7 @@ def begin_body(self) -> None: fake_call_expr = CallExpr(self.filter_func_def, [self.index], [ARG_POS], [None]) # I put this here to prevent a circular import - # from mypyc.irbuild.expression import transform_call_expr + from mypyc.irbuild.expression import transform_call_expr result = transform_call_expr(builder, fake_call_expr) # result = builder.accept(fake_call_expr)