From e81a5ce945a60ad3cd308612cef36c24c80b9f00 Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Mon, 4 Aug 2025 02:01:11 +0000 Subject: [PATCH 01/39] feat: cache len for iterating over immutable types --- mypyc/irbuild/for_helpers.py | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index 5cf89f579ec4..2c7cdae9c6f5 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -792,10 +792,23 @@ def init(self, expr_reg: Value, target_type: RType, reverse: bool) -> None: # for the for-loop. If we are inside of a generator function, spill these into the # environment class. self.expr_target = builder.maybe_spill(expr_reg) + if ( + is_tuple_rprimitive(expr_reg.type) + or is_str_rprimitive(expr_reg.type) + or is_bytes_rprimitive(expr_reg.type) + or is_frozenset_rprimitive(expr_reg.type) + ): + self.length_reg = builder.maybe_spill(self.load_len(self.expr_target)) + else: + self.length_reg = None if not reverse: index_reg: Value = Integer(0, c_pyssize_t_rprimitive) else: - index_reg = builder.builder.int_sub(self.load_len(self.expr_target), 1) + if self.length_reg is not None: + len_val = builder.read(self.length_reg) + else: + len_val = self.load_len(self.expr_target) + index_reg = builder.builder.int_sub(len_val, 1) self.index_target = builder.maybe_spill_assignable(index_reg) self.target_type = target_type @@ -814,9 +827,13 @@ def gen_condition(self) -> None: second_check = BasicBlock() builder.add_bool_branch(comparison, second_check, self.loop_exit) builder.activate_block(second_check) - # For compatibility with python semantics we recalculate the length - # at every iteration. - len_reg = self.load_len(self.expr_target) + if self.length_reg is None: + # For compatibility with python semantics we recalculate the length + # at every iteration. + len_reg = self.load_len(self.expr_target) + else: + # (unless input is immutable type). + len_reg = builder.read(self.length_reg, line) comparison = builder.binary_op(builder.read(self.index_target, line), len_reg, "<", line) builder.add_bool_branch(comparison, self.body_block, self.loop_exit) From 804c3b50cfd3f439e580406a1df33146bc461e08 Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Mon, 4 Aug 2025 02:01:11 +0000 Subject: [PATCH 02/39] fix: tests --- mypyc/irbuild/for_helpers.py | 2 ++ mypyc/irbuild/specialize.py | 2 +- mypyc/test-data/irbuild-generics.test | 14 ++++---- mypyc/test-data/irbuild-tuple.test | 48 ++++++++++++++------------- 4 files changed, 35 insertions(+), 31 deletions(-) diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index 2c7cdae9c6f5..50fcd15025bc 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -46,8 +46,10 @@ bool_rprimitive, c_pyssize_t_rprimitive, int_rprimitive, + is_bytes_rprimitive, is_dict_rprimitive, is_fixed_width_rtype, + is_frozenset_rprimitive, is_list_rprimitive, is_sequence_rprimitive, is_short_int_rprimitive, diff --git a/mypyc/irbuild/specialize.py b/mypyc/irbuild/specialize.py index 3015640fb3fd..8e04354b1dd1 100644 --- a/mypyc/irbuild/specialize.py +++ b/mypyc/irbuild/specialize.py @@ -221,7 +221,7 @@ def translate_len(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value else: borrow = False obj = builder.accept(arg, can_borrow=borrow) - return builder.builtin_len(obj, expr.line) + return builder.builder.builtin_len(obj, expr.line) return None diff --git a/mypyc/test-data/irbuild-generics.test b/mypyc/test-data/irbuild-generics.test index d39d47e397a1..038a92e0cd73 100644 --- a/mypyc/test-data/irbuild-generics.test +++ b/mypyc/test-data/irbuild-generics.test @@ -721,18 +721,18 @@ L0: r0 = __mypyc_self__.__mypyc_env__ r1 = var_object_size args r2 = PyList_New(r1) - r3 = 0 + r3 = var_object_size args + r4 = 0 L1: - r4 = var_object_size args - r5 = r3 < r4 :: signed + r5 = r4 < r3 :: signed if r5 goto L2 else goto L4 :: bool L2: - r6 = CPySequenceTuple_GetItemUnsafe(args, r3) + r6 = CPySequenceTuple_GetItemUnsafe(args, r4) x = r6 - CPyList_SetItemUnsafe(r2, r3, x) + CPyList_SetItemUnsafe(r2, r4, x) L3: - r7 = r3 + 1 - r3 = r7 + r7 = r4 + 1 + r4 = r7 goto L1 L4: can_listcomp = r2 diff --git a/mypyc/test-data/irbuild-tuple.test b/mypyc/test-data/irbuild-tuple.test index 5c5ec27b1882..cfcfcd0a9bb3 100644 --- a/mypyc/test-data/irbuild-tuple.test +++ b/mypyc/test-data/irbuild-tuple.test @@ -133,18 +133,18 @@ def f(xs): r4, x :: str r5 :: native_int L0: - r0 = 0 + r0 = var_object_size xs + r1 = 0 L1: - r1 = var_object_size xs - r2 = r0 < r1 :: signed + r2 = r1 < r0 :: signed if r2 goto L2 else goto L4 :: bool L2: - r3 = CPySequenceTuple_GetItemUnsafe(xs, r0) + r3 = CPySequenceTuple_GetItemUnsafe(xs, r1) r4 = cast(str, r3) x = r4 L3: - r5 = r0 + 1 - r0 = r5 + r5 = r1 + 1 + r1 = r5 goto L1 L4: return 1 @@ -291,8 +291,10 @@ def test(): r1 :: native_int r2 :: bit r3 :: tuple - r4, r5 :: native_int - r6, r7 :: bit + r4 :: native_int + r5 :: bit + r6 :: native_int + r7 :: bit r8, x, r9 :: str r10 :: native_int a :: tuple @@ -302,20 +304,20 @@ L0: r1 = CPyStr_Size_size_t(source) r2 = r1 >= 0 :: signed r3 = PyTuple_New(r1) - r4 = 0 + r4 = CPyStr_Size_size_t(source) + r5 = r4 >= 0 :: signed + r6 = 0 L1: - r5 = CPyStr_Size_size_t(source) - r6 = r5 >= 0 :: signed - r7 = r4 < r5 :: signed + r7 = r6 < r4 :: signed if r7 goto L2 else goto L4 :: bool L2: - r8 = CPyStr_GetItemUnsafe(source, r4) + r8 = CPyStr_GetItemUnsafe(source, r6) x = r8 r9 = f2(x) - CPySequenceTuple_SetItemUnsafe(r3, r4, r9) + CPySequenceTuple_SetItemUnsafe(r3, r6, r9) L3: - r10 = r4 + 1 - r4 = r10 + r10 = r6 + 1 + r6 = r10 goto L1 L4: a = r3 @@ -349,21 +351,21 @@ def test(source): L0: r0 = var_object_size source r1 = PyTuple_New(r0) - r2 = 0 + r2 = var_object_size source + r3 = 0 L1: - r3 = var_object_size source - r4 = r2 < r3 :: signed + r4 = r3 < r2 :: signed if r4 goto L2 else goto L4 :: bool L2: - r5 = CPySequenceTuple_GetItemUnsafe(source, r2) + r5 = CPySequenceTuple_GetItemUnsafe(source, r3) r6 = unbox(bool, r5) x = r6 r7 = f(x) r8 = box(bool, r7) - CPySequenceTuple_SetItemUnsafe(r1, r2, r8) + CPySequenceTuple_SetItemUnsafe(r1, r3, r8) L3: - r9 = r2 + 1 - r2 = r9 + r9 = r3 + 1 + r3 = r9 goto L1 L4: a = r1 From 33d64b3d85d1b60123c44961b9890c367e0fdd18 Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Mon, 4 Aug 2025 02:01:11 +0000 Subject: [PATCH 03/39] fix: include bytes as sequence and fix test --- mypyc/ir/rtypes.py | 5 ++- mypyc/irbuild/builder.py | 7 +++- mypyc/irbuild/for_helpers.py | 8 ++-- mypyc/irbuild/specialize.py | 2 +- mypyc/test-data/fixtures/tuple.pyi | 9 ++++ mypyc/test-data/irbuild-tuple.test | 66 ++++++++++++++++++++++++++++++ 6 files changed, 90 insertions(+), 7 deletions(-) create mode 100644 mypyc/test-data/fixtures/tuple.pyi diff --git a/mypyc/ir/rtypes.py b/mypyc/ir/rtypes.py index c0871bba258c..13c946564f26 100644 --- a/mypyc/ir/rtypes.py +++ b/mypyc/ir/rtypes.py @@ -628,7 +628,10 @@ def is_range_rprimitive(rtype: RType) -> bool: def is_sequence_rprimitive(rtype: RType) -> bool: return isinstance(rtype, RPrimitive) and ( - is_list_rprimitive(rtype) or is_tuple_rprimitive(rtype) or is_str_rprimitive(rtype) + is_list_rprimitive(rtype) + or is_tuple_rprimitive(rtype) + or is_str_rprimitive(rtype) + or is_bytes_rprimitive(rtype) ) diff --git a/mypyc/irbuild/builder.py b/mypyc/irbuild/builder.py index ec3c1b1b1f3c..96697fb614e6 100644 --- a/mypyc/irbuild/builder.py +++ b/mypyc/irbuild/builder.py @@ -91,6 +91,7 @@ RType, RUnion, bitmap_rprimitive, + bytes_rprimitive, c_pyssize_t_rprimitive, dict_rprimitive, int_rprimitive, @@ -958,8 +959,12 @@ def get_sequence_type_from_type(self, target_type: Type) -> RType: elif isinstance(target_type, Instance): if target_type.type.fullname == "builtins.str": return str_rprimitive - else: + elif target_type.type.fullname == "builtins.bytes": + return bytes_rprimitive + try: return self.type_to_rtype(target_type.args[0]) + except IndexError: + raise ValueError(f"{target_type!r} is not a valid sequence.") from None # This elif-blocks are needed for iterating over classes derived from NamedTuple. elif isinstance(target_type, TypeVarLikeType): return self.get_sequence_type_from_type(target_type.upper_bound) diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index 50fcd15025bc..b807bf62db7e 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -207,9 +207,9 @@ def sequence_from_generator_preallocate_helper( there is no condition list in the generator and only one original sequence with one index is allowed. - e.g. (1) tuple(f(x) for x in a_list/a_tuple) - (2) list(f(x) for x in a_list/a_tuple) - (3) [f(x) for x in a_list/a_tuple] + e.g. (1) tuple(f(x) for x in a_list/a_tuple/a_str/a_bytes) + (2) list(f(x) for x in a_list/a_tuple/a_str/a_bytes) + (3) [f(x) for x in a_list/a_tuple/a_str/a_bytes] RTuple as an original sequence is not supported yet. Args: @@ -226,7 +226,7 @@ def sequence_from_generator_preallocate_helper( """ if len(gen.sequences) == 1 and len(gen.indices) == 1 and len(gen.condlists[0]) == 0: rtype = builder.node_type(gen.sequences[0]) - if is_list_rprimitive(rtype) or is_tuple_rprimitive(rtype) or is_str_rprimitive(rtype): + if is_sequence_rprimitive(rtype): sequence = builder.accept(gen.sequences[0]) length = builder.builder.builtin_len(sequence, gen.line, use_pyssize_t=True) target_op = empty_op_llbuilder(length, gen.line) diff --git a/mypyc/irbuild/specialize.py b/mypyc/irbuild/specialize.py index 8e04354b1dd1..9595b3b33091 100644 --- a/mypyc/irbuild/specialize.py +++ b/mypyc/irbuild/specialize.py @@ -288,7 +288,7 @@ def translate_tuple_from_generator_call( """Special case for simplest tuple creation from a generator. For example: - tuple(f(x) for x in some_list/some_tuple/some_str) + tuple(f(x) for x in some_list/some_tuple/some_str/some_bytes) 'translate_safe_generator_call()' would take care of other cases if this fails. """ diff --git a/mypyc/test-data/fixtures/tuple.pyi b/mypyc/test-data/fixtures/tuple.pyi new file mode 100644 index 000000000000..6ea10b472101 --- /dev/null +++ b/mypyc/test-data/fixtures/tuple.pyi @@ -0,0 +1,9 @@ +from typing import Iterator + +class bytes: + def __iter__(self) -> Iterator[int]: pass +class dict: pass +class int: + def __add__(self, other: int) -> int: pass +class str: pass +class tuple: pass diff --git a/mypyc/test-data/irbuild-tuple.test b/mypyc/test-data/irbuild-tuple.test index cfcfcd0a9bb3..7e18e5296eb4 100644 --- a/mypyc/test-data/irbuild-tuple.test +++ b/mypyc/test-data/irbuild-tuple.test @@ -323,6 +323,72 @@ L4: a = r3 return 1 +[case testTupleBuiltFromBytes] +def f2(val: int) -> int: + return val + 2 + +def test() -> None: + source = b"abc" + a = tuple(f2(x) for x in source) + +[builtins fixtures/tuple.pyi] +[out] +def f2(val): + val, r0 :: int +L0: + r0 = CPyTagged_Add(val, 4) + return r0 +def test(): + r0, source :: bytes + r1 :: native_int + r2 :: tuple + r3, r4 :: native_int + r5, r6, r7 :: bit + r8, r9, r10, r11 :: int + r12 :: object + r13, x, r14 :: int + r15 :: object + r16 :: native_int + a :: object +L0: + r0 = b'abc' + source = r0 + r1 = var_object_size source + r2 = PyTuple_New(r1) + r3 = var_object_size source + r4 = 0 +L1: + r5 = r4 < r3 :: signed + if r5 goto L2 else goto L8 :: bool +L2: + r6 = r4 <= 4611686018427387903 :: signed + if r6 goto L3 else goto L4 :: bool +L3: + r7 = r4 >= -4611686018427387904 :: signed + if r7 goto L5 else goto L4 :: bool +L4: + r8 = CPyTagged_FromInt64(r4) + r9 = r8 + goto L6 +L5: + r10 = r4 << 1 + r9 = r10 +L6: + r11 = CPyBytes_GetItem(source, r9) + r12 = box(int, r11) + r13 = unbox(int, r12) + x = r13 + r14 = f2(x) + r15 = box(int, r14) + CPySequenceTuple_SetItemUnsafe(r2, r4, r15) +L7: + r16 = r4 + 1 + r4 = r16 + goto L1 +L8: + a = r2 + return 1 + [case testTupleBuiltFromVariableLengthTuple] from typing import Tuple From 34c2244434a90f6222714fcb694734903085e4c7 Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Mon, 4 Aug 2025 02:01:11 +0000 Subject: [PATCH 04/39] rtypes --- mypyc/ir/rtypes.py | 62 +++++++++++++++++++++++----------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/mypyc/ir/rtypes.py b/mypyc/ir/rtypes.py index 13c946564f26..5d19d46b4aa8 100644 --- a/mypyc/ir/rtypes.py +++ b/mypyc/ir/rtypes.py @@ -192,7 +192,7 @@ def may_be_immortal(self) -> bool: def serialize(self) -> str: return "void" - def __eq__(self, other: object) -> bool: + def __eq__(self, other: object) -> TypeGuard[RVoid]: return isinstance(other, RVoid) def __hash__(self) -> int: @@ -279,7 +279,7 @@ def serialize(self) -> str: def __repr__(self) -> str: return "" % self.name - def __eq__(self, other: object) -> bool: + def __eq__(self, other: object) -> TypeGuard[RPrimitive]: return isinstance(other, RPrimitive) and other.name == self.name def __hash__(self) -> int: @@ -513,15 +513,15 @@ def __hash__(self) -> int: range_rprimitive: Final = RPrimitive("builtins.range", is_unboxed=False, is_refcounted=True) -def is_tagged(rtype: RType) -> bool: +def is_tagged(rtype: RType) -> TypeGuard[RPrimitive]: return rtype is int_rprimitive or rtype is short_int_rprimitive -def is_int_rprimitive(rtype: RType) -> bool: +def is_int_rprimitive(rtype: RType) -> TypeGuard[RPrimitive]: return rtype is int_rprimitive -def is_short_int_rprimitive(rtype: RType) -> bool: +def is_short_int_rprimitive(rtype: RType) -> TypeGuard[RPrimitive]: return rtype is short_int_rprimitive @@ -535,7 +535,7 @@ def is_int32_rprimitive(rtype: RType) -> TypeGuard[RPrimitive]: ) -def is_int64_rprimitive(rtype: RType) -> bool: +def is_int64_rprimitive(rtype: RType) -> TypeGuard[RPrimitive]: return rtype is int64_rprimitive or ( rtype is c_pyssize_t_rprimitive and rtype._ctype == "int64_t" ) @@ -554,79 +554,79 @@ def is_uint8_rprimitive(rtype: RType) -> TypeGuard[RPrimitive]: return rtype is uint8_rprimitive -def is_uint32_rprimitive(rtype: RType) -> bool: +def is_uint32_rprimitive(rtype: RType) -> TypeGuard[RPrimitive]: return rtype is uint32_rprimitive -def is_uint64_rprimitive(rtype: RType) -> bool: +def is_uint64_rprimitive(rtype: RType) -> TypeGuard[RPrimitive]: return rtype is uint64_rprimitive -def is_c_py_ssize_t_rprimitive(rtype: RType) -> bool: +def is_c_py_ssize_t_rprimitive(rtype: RType) -> TypeGuard[RPrimitive]: return rtype is c_pyssize_t_rprimitive -def is_pointer_rprimitive(rtype: RType) -> bool: +def is_pointer_rprimitive(rtype: RType) -> TypeGuard[RPrimitive]: return rtype is pointer_rprimitive -def is_float_rprimitive(rtype: RType) -> bool: +def is_float_rprimitive(rtype: RType) -> TypeGuard[RPrimitive]: return isinstance(rtype, RPrimitive) and rtype.name == "builtins.float" -def is_bool_rprimitive(rtype: RType) -> bool: +def is_bool_rprimitive(rtype: RType) -> TypeGuard[RPrimitive]: return isinstance(rtype, RPrimitive) and rtype.name == "builtins.bool" -def is_bit_rprimitive(rtype: RType) -> bool: +def is_bit_rprimitive(rtype: RType) -> TypeGuard[RPrimitive]: return isinstance(rtype, RPrimitive) and rtype.name == "bit" -def is_bool_or_bit_rprimitive(rtype: RType) -> bool: +def is_bool_or_bit_rprimitive(rtype: RType) -> TypeGuard[RPrimitive]: return is_bool_rprimitive(rtype) or is_bit_rprimitive(rtype) -def is_object_rprimitive(rtype: RType) -> bool: +def is_object_rprimitive(rtype: RType) -> TypeGuard[RPrimitive]: return isinstance(rtype, RPrimitive) and rtype.name == "builtins.object" -def is_none_rprimitive(rtype: RType) -> bool: +def is_none_rprimitive(rtype: RType) -> TypeGuard[RPrimitive]: return isinstance(rtype, RPrimitive) and rtype.name == "builtins.None" -def is_list_rprimitive(rtype: RType) -> bool: +def is_list_rprimitive(rtype: RType) -> TypeGuard[RPrimitive]: return isinstance(rtype, RPrimitive) and rtype.name == "builtins.list" -def is_dict_rprimitive(rtype: RType) -> bool: +def is_dict_rprimitive(rtype: RType) -> TypeGuard[RPrimitive]: return isinstance(rtype, RPrimitive) and rtype.name == "builtins.dict" -def is_set_rprimitive(rtype: RType) -> bool: +def is_set_rprimitive(rtype: RType) -> TypeGuard[RPrimitive]: return isinstance(rtype, RPrimitive) and rtype.name == "builtins.set" -def is_frozenset_rprimitive(rtype: RType) -> bool: +def is_frozenset_rprimitive(rtype: RType) -> TypeGuard[RPrimitive]: return isinstance(rtype, RPrimitive) and rtype.name == "builtins.frozenset" -def is_str_rprimitive(rtype: RType) -> bool: +def is_str_rprimitive(rtype: RType) -> TypeGuard[RPrimitive]: return isinstance(rtype, RPrimitive) and rtype.name == "builtins.str" -def is_bytes_rprimitive(rtype: RType) -> bool: +def is_bytes_rprimitive(rtype: RType) -> TypeGuard[RPrimitive]: return isinstance(rtype, RPrimitive) and rtype.name == "builtins.bytes" -def is_tuple_rprimitive(rtype: RType) -> bool: +def is_tuple_rprimitive(rtype: RType) -> TypeGuard[RPrimitive]: return isinstance(rtype, RPrimitive) and rtype.name == "builtins.tuple" -def is_range_rprimitive(rtype: RType) -> bool: +def is_range_rprimitive(rtype: RType) -> TypeGuard[RPrimitive]: return isinstance(rtype, RPrimitive) and rtype.name == "builtins.range" -def is_sequence_rprimitive(rtype: RType) -> bool: +def is_sequence_rprimitive(rtype: RType) -> TypeGuard[RPrimitive]: return isinstance(rtype, RPrimitive) and ( is_list_rprimitive(rtype) or is_tuple_rprimitive(rtype) @@ -720,7 +720,7 @@ def __str__(self) -> str: def __repr__(self) -> str: return "" % ", ".join(repr(typ) for typ in self.types) - def __eq__(self, other: object) -> bool: + def __eq__(self, other: object) -> TypeGuard[RTuple]: return isinstance(other, RTuple) and self.types == other.types def __hash__(self) -> int: @@ -853,7 +853,7 @@ def __repr__(self) -> str: ", ".join(name + ":" + repr(typ) for name, typ in zip(self.names, self.types)), ) - def __eq__(self, other: object) -> bool: + def __eq__(self, other: object) -> TypeGuard[RStruct]: return ( isinstance(other, RStruct) and self.name == other.name @@ -923,7 +923,7 @@ def attr_type(self, name: str) -> RType: def __repr__(self) -> str: return "" % self.name - def __eq__(self, other: object) -> bool: + def __eq__(self, other: object) -> TypeGuard[RInstance]: return isinstance(other, RInstance) and other.name == self.name def __hash__(self) -> int: @@ -977,7 +977,7 @@ def __str__(self) -> str: return "union[%s]" % ", ".join(str(item) for item in self.items) # We compare based on the set because order in a union doesn't matter - def __eq__(self, other: object) -> bool: + def __eq__(self, other: object) -> TypeGuard[RUnion]: return isinstance(other, RUnion) and self.items_set == other.items_set def __hash__(self) -> int: @@ -1019,7 +1019,7 @@ def optional_value_type(rtype: RType) -> RType | None: return None -def is_optional_type(rtype: RType) -> bool: +def is_optional_type(rtype: RType) -> TypeGuard[RUnion]: """Is rtype an optional type with exactly two union items?""" return optional_value_type(rtype) is not None @@ -1051,7 +1051,7 @@ def __str__(self) -> str: def __repr__(self) -> str: return f"" - def __eq__(self, other: object) -> bool: + def __eq__(self, other: object) -> TypeGuard[RArray]: return ( isinstance(other, RArray) and self.item_type == other.item_type From 58b18eeec8960fe409ac5a940f8afc53d5fded10 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Mon, 4 Aug 2025 02:01:11 +0000 Subject: [PATCH 05/39] 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 b807bf62db7e..7835c9cfdf43 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, Optional from mypy.nodes import ( ARG_POS, @@ -787,6 +787,8 @@ class ForSequence(ForGenerator): Supports iterating in both forward and reverse. """ + length_reg: Optional[Value] + def init(self, expr_reg: Value, target_type: RType, reverse: bool) -> None: builder = self.builder self.reverse = reverse @@ -798,7 +800,6 @@ def init(self, expr_reg: Value, target_type: RType, reverse: bool) -> None: is_tuple_rprimitive(expr_reg.type) or is_str_rprimitive(expr_reg.type) or is_bytes_rprimitive(expr_reg.type) - or is_frozenset_rprimitive(expr_reg.type) ): self.length_reg = builder.maybe_spill(self.load_len(self.expr_target)) else: From aef2732168de860cf96156a488ee061f65f64b49 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 4 Aug 2025 02:01:11 +0000 Subject: [PATCH 06/39] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypyc/irbuild/for_helpers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index 7835c9cfdf43..9405f17a00f4 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -49,7 +49,6 @@ is_bytes_rprimitive, is_dict_rprimitive, is_fixed_width_rtype, - is_frozenset_rprimitive, is_list_rprimitive, is_sequence_rprimitive, is_short_int_rprimitive, From 3bd549ca6c8b625d648574f596c6759c8718d3fb Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Mon, 4 Aug 2025 02:01:11 +0000 Subject: [PATCH 07/39] Update for_helpers.py --- 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 9405f17a00f4..d1abbe1023fd 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, Optional +from typing import Callable, ClassVar from mypy.nodes import ( ARG_POS, @@ -786,7 +786,7 @@ class ForSequence(ForGenerator): Supports iterating in both forward and reverse. """ - length_reg: Optional[Value] + length_reg: Value | AssignmentTarget | None def init(self, expr_reg: Value, target_type: RType, reverse: bool) -> None: builder = self.builder From 48a367109f587679bb7a9845f1830f86e3218197 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Mon, 4 Aug 2025 02:01:11 +0000 Subject: [PATCH 08/39] 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 9595b3b33091..748cda1256a7 100644 --- a/mypyc/irbuild/specialize.py +++ b/mypyc/irbuild/specialize.py @@ -221,7 +221,7 @@ def translate_len(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value else: borrow = False obj = builder.accept(arg, can_borrow=borrow) - return builder.builder.builtin_len(obj, expr.line) + return builder.builtin_len(obj, expr.line) return None From 3f7ba012808783ebfedd7e73fc75434b9af2b4c2 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Mon, 4 Aug 2025 02:01:11 +0000 Subject: [PATCH 09/39] Update irbuild-tuple.test --- mypyc/test-data/irbuild-tuple.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypyc/test-data/irbuild-tuple.test b/mypyc/test-data/irbuild-tuple.test index 7e18e5296eb4..02aa4da1ea21 100644 --- a/mypyc/test-data/irbuild-tuple.test +++ b/mypyc/test-data/irbuild-tuple.test @@ -331,7 +331,7 @@ def test() -> None: source = b"abc" a = tuple(f2(x) for x in source) -[builtins fixtures/tuple.pyi] +[builtins fixtures/ir.pyi] [out] def f2(val): val, r0 :: int From 74b89d86e13e2cf1a9c4f838c682f093c49253d9 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Mon, 4 Aug 2025 02:01:11 +0000 Subject: [PATCH 10/39] Delete mypyc/test-data/fixtures/tuple.pyi --- mypyc/test-data/fixtures/tuple.pyi | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 mypyc/test-data/fixtures/tuple.pyi diff --git a/mypyc/test-data/fixtures/tuple.pyi b/mypyc/test-data/fixtures/tuple.pyi deleted file mode 100644 index 6ea10b472101..000000000000 --- a/mypyc/test-data/fixtures/tuple.pyi +++ /dev/null @@ -1,9 +0,0 @@ -from typing import Iterator - -class bytes: - def __iter__(self) -> Iterator[int]: pass -class dict: pass -class int: - def __add__(self, other: int) -> int: pass -class str: pass -class tuple: pass From 05b6f53e42051cc0fd8adbfcce66feb0de10de53 Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Mon, 4 Aug 2025 02:01:11 +0000 Subject: [PATCH 11/39] feat: len only once --- mypyc/ir/rtypes.py | 9 ++ mypyc/irbuild/for_helpers.py | 60 +++++++++-- mypyc/test-data/fixtures/ir.py | 1 + mypyc/test-data/irbuild-generics.test | 138 +++++++++++++------------- mypyc/test-data/irbuild-tuple.test | 119 +++++++++++----------- 5 files changed, 185 insertions(+), 142 deletions(-) diff --git a/mypyc/ir/rtypes.py b/mypyc/ir/rtypes.py index 5d19d46b4aa8..3c2fbfec1035 100644 --- a/mypyc/ir/rtypes.py +++ b/mypyc/ir/rtypes.py @@ -635,6 +635,15 @@ def is_sequence_rprimitive(rtype: RType) -> TypeGuard[RPrimitive]: ) +def is_immutable_rprimitive(rtype: RType) -> TypeGuard[RPrimitive]: + return ( + is_str_rprimitive(rtype) + or is_bytes_rprimitive(rtype) + or is_tuple_rprimitive(rtype) + or is_frozenset_rprimitive(rtype) + ) + + class TupleNameVisitor(RTypeVisitor[str]): """Produce a tuple name based on the concrete representations of types.""" diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index d1abbe1023fd..2601d25680a2 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -11,15 +11,19 @@ from mypy.nodes import ( ARG_POS, + BytesExpr, CallExpr, DictionaryComprehension, Expression, GeneratorExpr, + ListExpr, Lvalue, MemberExpr, NameExpr, + Optional, RefExpr, SetExpr, + StrExpr, TupleExpr, TypeAlias, ) @@ -46,9 +50,9 @@ bool_rprimitive, c_pyssize_t_rprimitive, int_rprimitive, - is_bytes_rprimitive, is_dict_rprimitive, is_fixed_width_rtype, + is_immutable_rprimitive, is_list_rprimitive, is_sequence_rprimitive, is_short_int_rprimitive, @@ -152,6 +156,7 @@ def for_loop_helper_with_index( expr_reg: Value, body_insts: Callable[[Value], None], line: int, + length: Value, ) -> None: """Generate IR for a sequence iteration. @@ -172,7 +177,7 @@ def for_loop_helper_with_index( exit_block = BasicBlock() condition_block = BasicBlock() - for_gen = ForSequence(builder, index, body_block, exit_block, line, False) + for_gen = ForSequence(builder, index, body_block, exit_block, line, False, length) for_gen.init(expr_reg, target_type, reverse=False) builder.push_loop_stack(step_block, exit_block) @@ -227,7 +232,7 @@ def sequence_from_generator_preallocate_helper( rtype = builder.node_type(gen.sequences[0]) if is_sequence_rprimitive(rtype): sequence = builder.accept(gen.sequences[0]) - length = builder.builder.builtin_len(sequence, gen.line, use_pyssize_t=True) + length = get_expr_length_value(builder, gen.sequences[0], sequence, gen.line, use_pyssize_t=True) target_op = empty_op_llbuilder(length, gen.line) def set_item(item_index: Value) -> None: @@ -235,7 +240,7 @@ def set_item(item_index: Value) -> None: builder.call_c(set_item_op, [target_op, item_index, e], gen.line) for_loop_helper_with_index( - builder, gen.indices[0], gen.sequences[0], sequence, set_item, gen.line + builder, gen.indices[0], gen.sequences[0], sequence, set_item, gen.line, length ) return target_op @@ -788,20 +793,34 @@ class ForSequence(ForGenerator): length_reg: Value | AssignmentTarget | None + def __init__( + self, + builder: IRBuilder, + index: Lvalue, + body_block: BasicBlock, + loop_exit: BasicBlock, + line: int, + nested: bool, + length: Value | None = None, + ) -> None: + super().__init__(builder, index, body_block, loop_exit, line, nested) + self.length = length + """A Value representing the length of the sequence, if known.""" + def init(self, expr_reg: Value, target_type: RType, reverse: bool) -> None: + assert is_sequence_rprimitive(expr_reg.type), expr_reg builder = self.builder self.reverse = reverse # Define target to contain the expression, along with the index that will be used # for the for-loop. If we are inside of a generator function, spill these into the # environment class. self.expr_target = builder.maybe_spill(expr_reg) - if ( - is_tuple_rprimitive(expr_reg.type) - or is_str_rprimitive(expr_reg.type) - or is_bytes_rprimitive(expr_reg.type) - ): - self.length_reg = builder.maybe_spill(self.load_len(self.expr_target)) + if is_immutable_rprimitive(expr_reg.type): + # If the expression is an immutable type, we can load the length just once. + self.length_reg = builder.maybe_spill(self.length or self.load_len(self.expr_target)) else: + # Otherwise, even if the length is known, we must recalculate the length + # at every iteration for compatibility with python semantics. self.length_reg = None if not reverse: index_reg: Value = Integer(0, c_pyssize_t_rprimitive) @@ -1166,3 +1185,24 @@ def gen_step(self) -> None: def gen_cleanup(self) -> None: for gen in self.gens: gen.gen_cleanup() + + +def get_expr_length(expr: Expression) -> Optional[int]: + if isinstance(expr, (StrExpr, BytesExpr)): + return len(expr.value) + elif isinstance(expr, (ListExpr, TupleExpr)): + if all(get_expr_length(i) is not None for i in expr.items): + return len(expr.items) + # TODO: extend this, unrolling should come with a good performance boost + return None + + +def get_expr_length_value(builder: IRBuilder, expr: Expression, expr_reg, line: int, use_pyssize_t: bool) -> Value: + rtype = builder.node_type(expr) + assert is_sequence_rprimitive(rtype), rtype + length = get_expr_length(expr) + if length is None: + # We cannot compute the length at compile time, so we will fetch it. + return builder.builder.builtin_len(expr_reg, line, use_pyssize_t=use_pyssize_t) + # The expression result is known at compile time, so we can use a constant. + return Integer(length, c_pyssize_t_rprimitive if use_pyssize_t else short_int_rprimitive) diff --git a/mypyc/test-data/fixtures/ir.py b/mypyc/test-data/fixtures/ir.py index 76afc1ea58cc..661ae50fd5f3 100644 --- a/mypyc/test-data/fixtures/ir.py +++ b/mypyc/test-data/fixtures/ir.py @@ -172,6 +172,7 @@ def __getitem__(self, i: int) -> int: ... def __getitem__(self, i: slice) -> bytes: ... def join(self, x: Iterable[object]) -> bytes: ... def decode(self, x: str=..., y: str=...) -> str: ... + def __iter__(self) -> Iterator[int]: ... class bytearray: @overload diff --git a/mypyc/test-data/irbuild-generics.test b/mypyc/test-data/irbuild-generics.test index 038a92e0cd73..57de6c071fa0 100644 --- a/mypyc/test-data/irbuild-generics.test +++ b/mypyc/test-data/irbuild-generics.test @@ -689,94 +689,93 @@ def inner_deco_obj.__call__(__mypyc_self__, args, kwargs): r0 :: __main__.deco_env r1 :: native_int r2 :: list - r3, r4 :: native_int - r5 :: bit - r6, x :: object - r7 :: native_int + r3 :: native_int + r4 :: bit + r5, x :: object + r6 :: native_int can_listcomp :: list - r8 :: dict - r9 :: short_int - r10 :: native_int - r11 :: object - r12 :: tuple[bool, short_int, object, object] - r13 :: short_int - r14 :: bool - r15, r16 :: object - r17, k :: str + r7 :: dict + r8 :: short_int + r9 :: native_int + r10 :: object + r11 :: tuple[bool, short_int, object, object] + r12 :: short_int + r13 :: bool + r14, r15 :: object + r16, k :: str v :: object - r18 :: i32 - r19, r20, r21 :: bit + r17 :: i32 + r18, r19, r20 :: bit can_dictcomp :: dict - r22, can_iter, r23, can_use_keys, r24, can_use_values :: list - r25 :: object - r26 :: list - r27 :: object - r28 :: dict - r29 :: i32 - r30 :: bit - r31 :: tuple - r32 :: object - r33 :: int + r21, can_iter, r22, can_use_keys, r23, can_use_values :: list + r24 :: object + r25 :: list + r26 :: object + r27 :: dict + r28 :: i32 + r29 :: bit + r30 :: tuple + r31 :: object + r32 :: int L0: r0 = __mypyc_self__.__mypyc_env__ r1 = var_object_size args r2 = PyList_New(r1) - r3 = var_object_size args - r4 = 0 + r3 = 0 L1: - r5 = r4 < r3 :: signed - if r5 goto L2 else goto L4 :: bool + r4 = r3 < r1 :: signed + if r4 goto L2 else goto L4 :: bool L2: - r6 = CPySequenceTuple_GetItemUnsafe(args, r4) - x = r6 - CPyList_SetItemUnsafe(r2, r4, x) + r5 = CPySequenceTuple_GetItemUnsafe(args, r3) + x = r5 + CPyList_SetItemUnsafe(r2, r3, x) L3: - r7 = r4 + 1 - r4 = r7 + r6 = r3 + 1 + r3 = r6 goto L1 L4: can_listcomp = r2 - r8 = PyDict_New() - r9 = 0 - r10 = PyDict_Size(kwargs) - r11 = CPyDict_GetItemsIter(kwargs) + r7 = PyDict_New() + r8 = 0 + r9 = PyDict_Size(kwargs) + r10 = CPyDict_GetItemsIter(kwargs) L5: - r12 = CPyDict_NextItem(r11, r9) - r13 = r12[1] - r9 = r13 - r14 = r12[0] - if r14 goto L6 else goto L8 :: bool + r11 = CPyDict_NextItem(r10, r8) + r12 = r11[1] + r8 = r12 + r13 = r11[0] + if r13 goto L6 else goto L8 :: bool L6: - r15 = r12[2] - r16 = r12[3] - r17 = cast(str, r15) - k = r17 - v = r16 - r18 = CPyDict_SetItem(r8, k, v) - r19 = r18 >= 0 :: signed + r14 = r11[2] + r15 = r11[3] + r16 = cast(str, r14) + k = r16 + v = r15 + r17 = CPyDict_SetItem(r7, k, v) + r18 = r17 >= 0 :: signed L7: - r20 = CPyDict_CheckSize(kwargs, r10) + r19 = CPyDict_CheckSize(kwargs, r9) goto L5 L8: - r21 = CPy_NoErrOccurred() + r20 = CPy_NoErrOccurred() L9: - can_dictcomp = r8 - r22 = PySequence_List(kwargs) - can_iter = r22 - r23 = CPyDict_Keys(kwargs) - can_use_keys = r23 - r24 = CPyDict_Values(kwargs) - can_use_values = r24 - r25 = r0.func - r26 = PyList_New(0) - r27 = CPyList_Extend(r26, args) - r28 = PyDict_New() - r29 = CPyDict_UpdateInDisplay(r28, kwargs) - r30 = r29 >= 0 :: signed - r31 = PyList_AsTuple(r26) - r32 = PyObject_Call(r25, r31, r28) - r33 = unbox(int, r32) - return r33 + can_dictcomp = r7 + r21 = PySequence_List(kwargs) + can_iter = r21 + r22 = CPyDict_Keys(kwargs) + can_use_keys = r22 + r23 = CPyDict_Values(kwargs) + can_use_values = r23 + r24 = r0.func + r25 = PyList_New(0) + r26 = CPyList_Extend(r25, args) + r27 = PyDict_New() + r28 = CPyDict_UpdateInDisplay(r27, kwargs) + r29 = r28 >= 0 :: signed + r30 = PyList_AsTuple(r25) + r31 = PyObject_Call(r24, r30, r27) + r32 = unbox(int, r31) + return r32 def deco(func): func :: object r0 :: __main__.deco_env @@ -795,3 +794,4 @@ def f(x): x :: int L0: return x + diff --git a/mypyc/test-data/irbuild-tuple.test b/mypyc/test-data/irbuild-tuple.test index 02aa4da1ea21..bab39dc1b141 100644 --- a/mypyc/test-data/irbuild-tuple.test +++ b/mypyc/test-data/irbuild-tuple.test @@ -293,10 +293,8 @@ def test(): r3 :: tuple r4 :: native_int r5 :: bit - r6 :: native_int - r7 :: bit - r8, x, r9 :: str - r10 :: native_int + r6, x, r7 :: str + r8 :: native_int a :: tuple L0: r0 = 'abc' @@ -304,20 +302,18 @@ L0: r1 = CPyStr_Size_size_t(source) r2 = r1 >= 0 :: signed r3 = PyTuple_New(r1) - r4 = CPyStr_Size_size_t(source) - r5 = r4 >= 0 :: signed - r6 = 0 + r4 = 0 L1: - r7 = r6 < r4 :: signed - if r7 goto L2 else goto L4 :: bool + r5 = r4 < r1 :: signed + if r5 goto L2 else goto L4 :: bool L2: - r8 = CPyStr_GetItemUnsafe(source, r6) - x = r8 - r9 = f2(x) - CPySequenceTuple_SetItemUnsafe(r3, r6, r9) + r6 = CPyStr_GetItemUnsafe(source, r4) + x = r6 + r7 = f2(x) + CPySequenceTuple_SetItemUnsafe(r3, r4, r7) L3: - r10 = r6 + 1 - r6 = r10 + r8 = r4 + 1 + r4 = r8 goto L1 L4: a = r3 @@ -331,7 +327,6 @@ def test() -> None: source = b"abc" a = tuple(f2(x) for x in source) -[builtins fixtures/ir.pyi] [out] def f2(val): val, r0 :: int @@ -342,48 +337,47 @@ def test(): r0, source :: bytes r1 :: native_int r2 :: tuple - r3, r4 :: native_int - r5, r6, r7 :: bit - r8, r9, r10, r11 :: int - r12 :: object - r13, x, r14 :: int - r15 :: object - r16 :: native_int - a :: object + r3 :: native_int + r4, r5, r6 :: bit + r7, r8, r9, r10 :: int + r11 :: object + r12, x, r13 :: int + r14 :: object + r15 :: native_int + a :: tuple L0: r0 = b'abc' source = r0 r1 = var_object_size source r2 = PyTuple_New(r1) - r3 = var_object_size source - r4 = 0 + r3 = 0 L1: - r5 = r4 < r3 :: signed - if r5 goto L2 else goto L8 :: bool + r4 = r3 < r1 :: signed + if r4 goto L2 else goto L8 :: bool L2: - r6 = r4 <= 4611686018427387903 :: signed - if r6 goto L3 else goto L4 :: bool + r5 = r3 <= 4611686018427387903 :: signed + if r5 goto L3 else goto L4 :: bool L3: - r7 = r4 >= -4611686018427387904 :: signed - if r7 goto L5 else goto L4 :: bool + r6 = r3 >= -4611686018427387904 :: signed + if r6 goto L5 else goto L4 :: bool L4: - r8 = CPyTagged_FromInt64(r4) - r9 = r8 + r7 = CPyTagged_FromInt64(r3) + r8 = r7 goto L6 L5: - r10 = r4 << 1 - r9 = r10 + r9 = r3 << 1 + r8 = r9 L6: - r11 = CPyBytes_GetItem(source, r9) - r12 = box(int, r11) - r13 = unbox(int, r12) - x = r13 - r14 = f2(x) - r15 = box(int, r14) - CPySequenceTuple_SetItemUnsafe(r2, r4, r15) + r10 = CPyBytes_GetItem(source, r8) + r11 = box(int, r10) + r12 = unbox(int, r11) + x = r12 + r13 = f2(x) + r14 = box(int, r13) + CPySequenceTuple_SetItemUnsafe(r2, r3, r14) L7: - r16 = r4 + 1 - r4 = r16 + r15 = r3 + 1 + r3 = r15 goto L1 L8: a = r2 @@ -407,31 +401,30 @@ def test(source): source :: tuple r0 :: native_int r1 :: tuple - r2, r3 :: native_int - r4 :: bit - r5 :: object - r6, x, r7 :: bool - r8 :: object - r9 :: native_int + r2 :: native_int + r3 :: bit + r4 :: object + r5, x, r6 :: bool + r7 :: object + r8 :: native_int a :: tuple L0: r0 = var_object_size source r1 = PyTuple_New(r0) - r2 = var_object_size source - r3 = 0 + r2 = 0 L1: - r4 = r3 < r2 :: signed - if r4 goto L2 else goto L4 :: bool + r3 = r2 < r0 :: signed + if r3 goto L2 else goto L4 :: bool L2: - r5 = CPySequenceTuple_GetItemUnsafe(source, r3) - r6 = unbox(bool, r5) - x = r6 - r7 = f(x) - r8 = box(bool, r7) - CPySequenceTuple_SetItemUnsafe(r1, r3, r8) + r4 = CPySequenceTuple_GetItemUnsafe(source, r2) + r5 = unbox(bool, r4) + x = r5 + r6 = f(x) + r7 = box(bool, r6) + CPySequenceTuple_SetItemUnsafe(r1, r2, r7) L3: - r9 = r3 + 1 - r3 = r9 + r8 = r2 + 1 + r2 = r8 goto L1 L4: a = r1 From 4c955544240883e80a2b6817991265f6f53388fb Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 4 Aug 2025 02:01:11 +0000 Subject: [PATCH 12/39] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypyc/irbuild/for_helpers.py | 8 ++++++-- mypyc/test-data/irbuild-generics.test | 1 - 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index 2601d25680a2..a92ac21206fc 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -232,7 +232,9 @@ def sequence_from_generator_preallocate_helper( rtype = builder.node_type(gen.sequences[0]) if is_sequence_rprimitive(rtype): sequence = builder.accept(gen.sequences[0]) - length = get_expr_length_value(builder, gen.sequences[0], sequence, gen.line, use_pyssize_t=True) + length = get_expr_length_value( + builder, gen.sequences[0], sequence, gen.line, use_pyssize_t=True + ) target_op = empty_op_llbuilder(length, gen.line) def set_item(item_index: Value) -> None: @@ -1197,7 +1199,9 @@ def get_expr_length(expr: Expression) -> Optional[int]: return None -def get_expr_length_value(builder: IRBuilder, expr: Expression, expr_reg, line: int, use_pyssize_t: bool) -> Value: +def get_expr_length_value( + builder: IRBuilder, expr: Expression, expr_reg, line: int, use_pyssize_t: bool +) -> Value: rtype = builder.node_type(expr) assert is_sequence_rprimitive(rtype), rtype length = get_expr_length(expr) diff --git a/mypyc/test-data/irbuild-generics.test b/mypyc/test-data/irbuild-generics.test index 57de6c071fa0..061fdada287c 100644 --- a/mypyc/test-data/irbuild-generics.test +++ b/mypyc/test-data/irbuild-generics.test @@ -794,4 +794,3 @@ def f(x): x :: int L0: return x - From 83ee9aa864109728328a1dc62f34123cfdc68093 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Mon, 4 Aug 2025 02:01:11 +0000 Subject: [PATCH 13/39] Update for_helpers.py --- mypyc/irbuild/for_helpers.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index a92ac21206fc..07bf876a01d1 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, Optional, from mypy.nodes import ( ARG_POS, @@ -20,7 +20,6 @@ Lvalue, MemberExpr, NameExpr, - Optional, RefExpr, SetExpr, StrExpr, @@ -1200,7 +1199,7 @@ def get_expr_length(expr: Expression) -> Optional[int]: def get_expr_length_value( - builder: IRBuilder, expr: Expression, expr_reg, line: int, use_pyssize_t: bool + builder: IRBuilder, expr: Expression, expr_reg: Value, line: int, use_pyssize_t: bool ) -> Value: rtype = builder.node_type(expr) assert is_sequence_rprimitive(rtype), rtype From d8dd01a12e78b6edb3c64382ec83b493a859f9ba Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Mon, 4 Aug 2025 02:01:11 +0000 Subject: [PATCH 14/39] 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 07bf876a01d1..482ea3504e73 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, Optional, +from typing import Callable, ClassVar, Optional from mypy.nodes import ( ARG_POS, From b16e0237f2598c9125cd7c119908b8e0d1544aeb Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Mon, 4 Aug 2025 02:01:11 +0000 Subject: [PATCH 15/39] Update for_helpers.py --- 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 482ea3504e73..f624a8b074af 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, Optional +from typing import Callable, ClassVar from mypy.nodes import ( ARG_POS, @@ -1188,7 +1188,7 @@ def gen_cleanup(self) -> None: gen.gen_cleanup() -def get_expr_length(expr: Expression) -> Optional[int]: +def get_expr_length(expr: Expression) -> int | None: if isinstance(expr, (StrExpr, BytesExpr)): return len(expr.value) elif isinstance(expr, (ListExpr, TupleExpr)): From 8f5f59ed738ebbc9cda8daa257566f85ee42c0e1 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Mon, 4 Aug 2025 02:01:11 +0000 Subject: [PATCH 16/39] Update for_helpers.py --- mypyc/irbuild/for_helpers.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index f624a8b074af..a7167187af60 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -1192,8 +1192,15 @@ def get_expr_length(expr: Expression) -> int | None: if isinstance(expr, (StrExpr, BytesExpr)): return len(expr.value) elif isinstance(expr, (ListExpr, TupleExpr)): - if all(get_expr_length(i) is not None for i in expr.items): - return len(expr.items) + # if there are no star expressions, or we know the length of them, + # we know the length of the expression + stars = [get_expr_length(i, context) for i in expr.items if isinstance(i, StarExpr)] + if None not in stars: + other = sum(not isinstance(i, StarExpr) for i in expr.items) + return other + sum(stars) + elif isinstance(expr, StarExpr): + # star expression needs some extra logic but that can come later, this is good for now + pass # TODO: extend this, unrolling should come with a good performance boost return None From a0f2ac08454f94117d362a5c5a51a2e7315afcae Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Mon, 4 Aug 2025 02:01:11 +0000 Subject: [PATCH 17/39] Update for_helpers.py --- mypyc/irbuild/for_helpers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index a7167187af60..eca0516f0ff4 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -22,6 +22,7 @@ NameExpr, RefExpr, SetExpr, + StarExpr, StrExpr, TupleExpr, TypeAlias, @@ -1194,7 +1195,7 @@ def get_expr_length(expr: Expression) -> int | None: elif isinstance(expr, (ListExpr, TupleExpr)): # if there are no star expressions, or we know the length of them, # we know the length of the expression - stars = [get_expr_length(i, context) for i in expr.items if isinstance(i, StarExpr)] + stars = [get_expr_length(i) for i in expr.items if isinstance(i, StarExpr)] if None not in stars: other = sum(not isinstance(i, StarExpr) for i in expr.items) return other + sum(stars) From 47f1d3faaeefb1dbfc9e860007f3e312d73687e3 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Mon, 4 Aug 2025 02:01:11 +0000 Subject: [PATCH 18/39] 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 eca0516f0ff4..57f9b0959ff3 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -1198,7 +1198,7 @@ def get_expr_length(expr: Expression) -> int | None: stars = [get_expr_length(i) for i in expr.items if isinstance(i, StarExpr)] if None not in stars: other = sum(not isinstance(i, StarExpr) for i in expr.items) - return other + sum(stars) + return other + sum(stars) # type: ignore [arg-type] elif isinstance(expr, StarExpr): # star expression needs some extra logic but that can come later, this is good for now pass From bf7520ae7f68f8db79747b3ed542f6f3783fec16 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Mon, 4 Aug 2025 02:01:11 +0000 Subject: [PATCH 19/39] Update irbuild-tuple.test --- mypyc/test-data/irbuild-tuple.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypyc/test-data/irbuild-tuple.test b/mypyc/test-data/irbuild-tuple.test index bab39dc1b141..2a46caabed8f 100644 --- a/mypyc/test-data/irbuild-tuple.test +++ b/mypyc/test-data/irbuild-tuple.test @@ -319,7 +319,7 @@ L4: a = r3 return 1 -[case testTupleBuiltFromBytes] +[case testTupleBuiltFromBytes_64bit] def f2(val: int) -> int: return val + 2 From 558b1afcdf6d49dcd36890f9505f3109768390c8 Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Mon, 4 Aug 2025 02:01:11 +0000 Subject: [PATCH 20/39] feat: handle star expr --- 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 57f9b0959ff3..a69b6554d065 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -1201,7 +1201,7 @@ def get_expr_length(expr: Expression) -> int | None: return other + sum(stars) # type: ignore [arg-type] elif isinstance(expr, StarExpr): # star expression needs some extra logic but that can come later, this is good for now - pass + return get_expr_length(expr.expr) # TODO: extend this, unrolling should come with a good performance boost return None From 24c67d3e63010c485f676f5dc894a50b7eb8fe90 Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Mon, 4 Aug 2025 02:01:11 +0000 Subject: [PATCH 21/39] feat(test): test final and list --- mypyc/test-data/irbuild-lists.test | 229 +++++++++++++++++++++++++++++ mypyc/test-data/irbuild-tuple.test | 225 ++++++++++++++++++++++++++++ 2 files changed, 454 insertions(+) diff --git a/mypyc/test-data/irbuild-lists.test b/mypyc/test-data/irbuild-lists.test index 06120e077af9..ffd101f7e2c2 100644 --- a/mypyc/test-data/irbuild-lists.test +++ b/mypyc/test-data/irbuild-lists.test @@ -572,3 +572,232 @@ def sort_iterable(a): L0: r0 = CPySequence_Sort(a) return 1 + +[case testListBuiltFromStr] +def f2(val: str) -> str: + return val + "f2" + +def test() -> None: + source = "abc" + a = [f2(x) for x in source] +[out] +def f2(val): + val, r0, r1 :: str +L0: + r0 = 'f2' + r1 = PyUnicode_Concat(val, r0) + return r1 +def test(): + r0, source :: str + r1 :: native_int + r2 :: bit + r3 :: list + r4 :: native_int + r5 :: bit + r6, x, r7 :: str + r8 :: native_int + a :: list +L0: + r0 = 'abc' + source = r0 + r1 = CPyStr_Size_size_t(source) + r2 = r1 >= 0 :: signed + r3 = PyList_New(r1) + r4 = 0 +L1: + r5 = r4 < r1 :: signed + if r5 goto L2 else goto L4 :: bool +L2: + r6 = CPyStr_GetItemUnsafe(source, r4) + x = r6 + r7 = f2(x) + CPyList_SetItemUnsafe(r3, r4, r7) +L3: + r8 = r4 + 1 + r4 = r8 + goto L1 +L4: + a = r3 + return 1 + +[case testListBuiltFromFinalStr] +from typing import Final + +source: Final = "abc" + +def f2(val: str) -> str: + return val + "f2" + +def test() -> None: + a = [f2(x) for x in source] +[out] +def f2(val): + val, r0, r1 :: str +L0: + r0 = 'f2' + r1 = PyUnicode_Concat(val, r0) + return r1 +def test(): + r0 :: str + r1 :: list + r2 :: native_int + r3 :: bit + r4, x, r5 :: str + r6 :: native_int + a :: list +L0: + r0 = 'abc' + r1 = PyList_New(3) + r2 = 0 +L1: + r3 = r2 < 3 :: signed + if r3 goto L2 else goto L4 :: bool +L2: + r4 = CPyStr_GetItemUnsafe(r0, r2) + x = r4 + r5 = f2(x) + CPyList_SetItemUnsafe(r1, r2, r5) +L3: + r6 = r2 + 1 + r2 = r6 + goto L1 +L4: + a = r1 + return 1 + +[case testListBuiltFromBytes_64bit] +def f2(val: int) -> int: + return val + 2 + +def test() -> None: + source = b"abc" + a = [f2(x) for x in source] + +[out] +def f2(val): + val, r0 :: int +L0: + r0 = CPyTagged_Add(val, 4) + return r0 +def test(): + r0, source :: bytes + r1 :: native_int + r2 :: list + r3 :: native_int + r4, r5, r6 :: bit + r7, r8, r9, r10 :: int + r11 :: object + r12, x, r13 :: int + r14 :: object + r15 :: native_int + a :: list +L0: + r0 = b'abc' + source = r0 + r1 = var_object_size source + r2 = PyList_New(r1) + r3 = 0 +L1: + r4 = r3 < r1 :: signed + if r4 goto L2 else goto L8 :: bool +L2: + r5 = r3 <= 4611686018427387903 :: signed + if r5 goto L3 else goto L4 :: bool +L3: + r6 = r3 >= -4611686018427387904 :: signed + if r6 goto L5 else goto L4 :: bool +L4: + r7 = CPyTagged_FromInt64(r3) + r8 = r7 + goto L6 +L5: + r9 = r3 << 1 + r8 = r9 +L6: + r10 = CPyBytes_GetItem(source, r8) + r11 = box(int, r10) + r12 = unbox(int, r11) + x = r12 + r13 = f2(x) + r14 = box(int, r13) + CPyList_SetItemUnsafe(r2, r3, r14) +L7: + r15 = r3 + 1 + r3 = r15 + goto L1 +L8: + a = r2 + return 1 + +[case testListBuiltFromFinalBytes_64bit] +from typing import Final + +source: Final = b"abc" + +def f2(val: int) -> int: + return val + 2 + +def test() -> None: + a = [f2(x) for x in source] + +[out] +def f2(val): + val, r0 :: int +L0: + r0 = CPyTagged_Add(val, 4) + return r0 +def test(): + r0 :: bytes + r1 :: bool + r2 :: native_int + r3 :: list + r4 :: native_int + r5, r6, r7 :: bit + r8, r9, r10, r11 :: int + r12 :: object + r13, x, r14 :: int + r15 :: object + r16 :: native_int + a :: list +L0: + r0 = __main__.source :: static + if is_error(r0) goto L1 else goto L2 +L1: + r1 = raise NameError('value for final name "source" was not set') + unreachable +L2: + r2 = var_object_size r0 + r3 = PyList_New(r2) + r4 = 0 +L3: + r5 = r4 < r2 :: signed + if r5 goto L4 else goto L10 :: bool +L4: + r6 = r4 <= 4611686018427387903 :: signed + if r6 goto L5 else goto L6 :: bool +L5: + r7 = r4 >= -4611686018427387904 :: signed + if r7 goto L7 else goto L6 :: bool +L6: + r8 = CPyTagged_FromInt64(r4) + r9 = r8 + goto L8 +L7: + r10 = r4 << 1 + r9 = r10 +L8: + r11 = CPyBytes_GetItem(r0, r9) + r12 = box(int, r11) + r13 = unbox(int, r12) + x = r13 + r14 = f2(x) + r15 = box(int, r14) + CPyList_SetItemUnsafe(r3, r4, r15) +L9: + r16 = r4 + 1 + r4 = r16 + goto L3 +L10: + a = r3 + return 1 + diff --git a/mypyc/test-data/irbuild-tuple.test b/mypyc/test-data/irbuild-tuple.test index 2a46caabed8f..d5697c79202c 100644 --- a/mypyc/test-data/irbuild-tuple.test +++ b/mypyc/test-data/irbuild-tuple.test @@ -319,6 +319,51 @@ L4: a = r3 return 1 +[case testTupleBuiltFromFinalStr] +from typing import Final + +source: Final = "abc" + +def f2(val: str) -> str: + return val + "f2" + +def test() -> None: + a = tuple(f2(x) for x in source) +[out] +def f2(val): + val, r0, r1 :: str +L0: + r0 = 'f2' + r1 = PyUnicode_Concat(val, r0) + return r1 +def test(): + r0 :: str + r1 :: tuple + r2 :: native_int + r3 :: bit + r4, x, r5 :: str + r6 :: native_int + a :: tuple +L0: + r0 = 'abc' + r1 = PyTuple_New(3) + r2 = 0 +L1: + r3 = r2 < 3 :: signed + if r3 goto L2 else goto L4 :: bool +L2: + r4 = CPyStr_GetItemUnsafe(r0, r2) + x = r4 + r5 = f2(x) + CPySequenceTuple_SetItemUnsafe(r1, r2, r5) +L3: + r6 = r2 + 1 + r2 = r6 + goto L1 +L4: + a = r1 + return 1 + [case testTupleBuiltFromBytes_64bit] def f2(val: int) -> int: return val + 2 @@ -383,6 +428,186 @@ L8: a = r2 return 1 +[case testTupleBuiltFromFinalBytes_64bit] +from typing import Final + +source: Final = b"abc" + +def f2(val: int) -> int: + return val + 2 + +def test() -> None: + a = tuple(f2(x) for x in source) + +[out] +def f2(val): + val, r0 :: int +L0: + r0 = CPyTagged_Add(val, 4) + return r0 +def test(): + r0 :: bytes + r1 :: bool + r2 :: native_int + r3 :: tuple + r4 :: native_int + r5, r6, r7 :: bit + r8, r9, r10, r11 :: int + r12 :: object + r13, x, r14 :: int + r15 :: object + r16 :: native_int + a :: tuple +L0: + r0 = __main__.source :: static + if is_error(r0) goto L1 else goto L2 +L1: + r1 = raise NameError('value for final name "source" was not set') + unreachable +L2: + r2 = var_object_size r0 + r3 = PyTuple_New(r2) + r4 = 0 +L3: + r5 = r4 < r2 :: signed + if r5 goto L4 else goto L10 :: bool +L4: + r6 = r4 <= 4611686018427387903 :: signed + if r6 goto L5 else goto L6 :: bool +L5: + r7 = r4 >= -4611686018427387904 :: signed + if r7 goto L7 else goto L6 :: bool +L6: + r8 = CPyTagged_FromInt64(r4) + r9 = r8 + goto L8 +L7: + r10 = r4 << 1 + r9 = r10 +L8: + r11 = CPyBytes_GetItem(r0, r9) + r12 = box(int, r11) + r13 = unbox(int, r12) + x = r13 + r14 = f2(x) + r15 = box(int, r14) + CPySequenceTuple_SetItemUnsafe(r3, r4, r15) +L9: + r16 = r4 + 1 + r4 = r16 + goto L3 +L10: + a = r3 + return 1 + +[case testTupleBuiltFromFixedLengthTuple] +def f(val: int) -> bool: + return val % 2 == 0 + +def test() -> None: + source = (1, 2, 3) + a = tuple(f(x) for x in source) +[out] +def f(val): + val, r0 :: int + r1 :: bit +L0: + r0 = CPyTagged_Remainder(val, 4) + r1 = int_eq r0, 0 + return r1 +def test(): + r0, source :: tuple[int, int, int] + r1 :: list + r2, r3, r4 :: object + r5, x :: int + r6 :: bool + r7 :: object + r8 :: i32 + r9, r10 :: bit + r11, a :: tuple +L0: + r0 = (2, 4, 6) + source = r0 + r1 = PyList_New(0) + r2 = box(tuple[int, int, int], source) + r3 = PyObject_GetIter(r2) +L1: + r4 = PyIter_Next(r3) + if is_error(r4) goto L4 else goto L2 +L2: + r5 = unbox(int, r4) + x = r5 + r6 = f(x) + r7 = box(bool, r6) + r8 = PyList_Append(r1, r7) + r9 = r8 >= 0 :: signed +L3: + goto L1 +L4: + r10 = CPy_NoErrOccurred() +L5: + r11 = PyList_AsTuple(r1) + a = r11 + return 1 + +[case testTupleBuiltFromFinalFixedLengthTuple] +from typing import Final + +source: Final = (1, 2, 3) + +def f(val: int) -> bool: + return val % 2 == 0 + +def test() -> None: + a = tuple(f(x) for x in source) +[out] +def f(val): + val, r0 :: int + r1 :: bit +L0: + r0 = CPyTagged_Remainder(val, 4) + r1 = int_eq r0, 0 + return r1 +def test(): + r0 :: list + r1 :: tuple[int, int, int] + r2 :: bool + r3, r4, r5 :: object + r6, x :: int + r7 :: bool + r8 :: object + r9 :: i32 + r10, r11 :: bit + r12, a :: tuple +L0: + r0 = PyList_New(0) + r1 = __main__.source :: static + if is_error(r1) goto L1 else goto L2 +L1: + r2 = raise NameError('value for final name "source" was not set') + unreachable +L2: + r3 = box(tuple[int, int, int], r1) + r4 = PyObject_GetIter(r3) +L3: + r5 = PyIter_Next(r4) + if is_error(r5) goto L6 else goto L4 +L4: + r6 = unbox(int, r5) + x = r6 + r7 = f(x) + r8 = box(bool, r7) + r9 = PyList_Append(r0, r8) + r10 = r9 >= 0 :: signed +L5: + goto L3 +L6: + r11 = CPy_NoErrOccurred() +L7: + r12 = PyList_AsTuple(r0) + a = r12 + return 1 + [case testTupleBuiltFromVariableLengthTuple] from typing import Tuple From f09ef04c6a96382fd3ccac16b353b2fc4e7a8782 Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Mon, 4 Aug 2025 02:01:11 +0000 Subject: [PATCH 22/39] feat: propagate length of final strings --- mypyc/irbuild/for_helpers.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index a69b6554d065..839ad254d784 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -26,6 +26,7 @@ StrExpr, TupleExpr, TypeAlias, + Var, ) from mypyc.ir.ops import ( ERR_NEVER, @@ -1202,6 +1203,14 @@ def get_expr_length(expr: Expression) -> int | None: elif isinstance(expr, StarExpr): # star expression needs some extra logic but that can come later, this is good for now return get_expr_length(expr.expr) + elif ( + isinstance(expr, RefExpr) + and isinstance(expr.node, Var) + and expr.node.is_final + and isinstance(expr.node.final_value, str) + and expr.node.has_explicit_value + ): + return len(expr.node.final_value) # TODO: extend this, unrolling should come with a good performance boost return None From 0a0508b575a28fe16181e3a3e471f1f7787da59f Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Mon, 4 Aug 2025 02:01:11 +0000 Subject: [PATCH 23/39] feat(test): test containers from literal exprs --- mypyc/test-data/irbuild-lists.test | 101 ++++++++++++++++++++++++++++ mypyc/test-data/irbuild-tuple.test | 102 +++++++++++++++++++++++++++++ 2 files changed, 203 insertions(+) diff --git a/mypyc/test-data/irbuild-lists.test b/mypyc/test-data/irbuild-lists.test index ffd101f7e2c2..2abe4fdaef27 100644 --- a/mypyc/test-data/irbuild-lists.test +++ b/mypyc/test-data/irbuild-lists.test @@ -620,6 +620,47 @@ L4: a = r3 return 1 +[case testListBuiltFromStrExpr] +def f2(val: str) -> str: + return val + "f2" + +def test() -> None: + a = [f2(x) for x in "abc"] +[out] +def f2(val): + val, r0, r1 :: str +L0: + r0 = 'f2' + r1 = PyUnicode_Concat(val, r0) + return r1 +def test(): + r0 :: str + r1 :: list + r2 :: native_int + r3 :: bit + r4, x, r5 :: str + r6 :: native_int + a :: list +L0: + r0 = 'abc' + r1 = PyList_New(3) + r2 = 0 +L1: + r3 = r2 < 3 :: signed + if r3 goto L2 else goto L4 :: bool +L2: + r4 = CPyStr_GetItemUnsafe(r0, r2) + x = r4 + r5 = f2(x) + CPyList_SetItemUnsafe(r1, r2, r5) +L3: + r6 = r2 + 1 + r2 = r6 + goto L1 +L4: + a = r1 + return 1 + [case testListBuiltFromFinalStr] from typing import Final @@ -729,6 +770,66 @@ L8: a = r2 return 1 +[case testListBuiltFromBytesExpr_64bit] +def f2(val: int) -> int: + return val + 2 + +def test() -> None: + a = [f2(x) for x in b"abc"] + +[out] +def f2(val): + val, r0 :: int +L0: + r0 = CPyTagged_Add(val, 4) + return r0 +def test(): + r0 :: bytes + r1 :: list + r2 :: native_int + r3, r4, r5 :: bit + r6, r7, r8, r9 :: int + r10 :: object + r11, x, r12 :: int + r13 :: object + r14 :: native_int + a :: list +L0: + r0 = b'abc' + r1 = PyList_New(3) + r2 = 0 +L1: + r3 = r2 < 3 :: signed + if r3 goto L2 else goto L8 :: bool +L2: + r4 = r2 <= 4611686018427387903 :: signed + if r4 goto L3 else goto L4 :: bool +L3: + r5 = r2 >= -4611686018427387904 :: signed + if r5 goto L5 else goto L4 :: bool +L4: + r6 = CPyTagged_FromInt64(r2) + r7 = r6 + goto L6 +L5: + r8 = r2 << 1 + r7 = r8 +L6: + r9 = CPyBytes_GetItem(r0, r7) + r10 = box(int, r9) + r11 = unbox(int, r10) + x = r11 + r12 = f2(x) + r13 = box(int, r12) + CPyList_SetItemUnsafe(r1, r2, r13) +L7: + r14 = r2 + 1 + r2 = r14 + goto L1 +L8: + a = r1 + return 1 + [case testListBuiltFromFinalBytes_64bit] from typing import Final diff --git a/mypyc/test-data/irbuild-tuple.test b/mypyc/test-data/irbuild-tuple.test index d5697c79202c..f4a8dea2262a 100644 --- a/mypyc/test-data/irbuild-tuple.test +++ b/mypyc/test-data/irbuild-tuple.test @@ -319,6 +319,48 @@ L4: a = r3 return 1 +[case testTupleBuiltFromStrExpr] +def f2(val: str) -> str: + return val + "f2" + +def test() -> None: + a = tuple(f2(x) for x in "abc") + +[out] +def f2(val): + val, r0, r1 :: str +L0: + r0 = 'f2' + r1 = PyUnicode_Concat(val, r0) + return r1 +def test(): + r0 :: str + r1 :: tuple + r2 :: native_int + r3 :: bit + r4, x, r5 :: str + r6 :: native_int + a :: tuple +L0: + r0 = 'abc' + r1 = PyTuple_New(3) + r2 = 0 +L1: + r3 = r2 < 3 :: signed + if r3 goto L2 else goto L4 :: bool +L2: + r4 = CPyStr_GetItemUnsafe(r0, r2) + x = r4 + r5 = f2(x) + CPySequenceTuple_SetItemUnsafe(r1, r2, r5) +L3: + r6 = r2 + 1 + r2 = r6 + goto L1 +L4: + a = r1 + return 1 + [case testTupleBuiltFromFinalStr] from typing import Final @@ -428,6 +470,66 @@ L8: a = r2 return 1 +[case testTupleBuiltFromBytesExpr_64bit] +def f2(val: int) -> int: + return val + 2 + +def test() -> None: + a = tuple(f2(x) for x in b"abc") + +[out] +def f2(val): + val, r0 :: int +L0: + r0 = CPyTagged_Add(val, 4) + return r0 +def test(): + r0 :: bytes + r1 :: tuple + r2 :: native_int + r3, r4, r5 :: bit + r6, r7, r8, r9 :: int + r10 :: object + r11, x, r12 :: int + r13 :: object + r14 :: native_int + a :: tuple +L0: + r0 = b'abc' + r1 = PyTuple_New(3) + r2 = 0 +L1: + r3 = r2 < 3 :: signed + if r3 goto L2 else goto L8 :: bool +L2: + r4 = r2 <= 4611686018427387903 :: signed + if r4 goto L3 else goto L4 :: bool +L3: + r5 = r2 >= -4611686018427387904 :: signed + if r5 goto L5 else goto L4 :: bool +L4: + r6 = CPyTagged_FromInt64(r2) + r7 = r6 + goto L6 +L5: + r8 = r2 << 1 + r7 = r8 +L6: + r9 = CPyBytes_GetItem(r0, r7) + r10 = box(int, r9) + r11 = unbox(int, r10) + x = r11 + r12 = f2(x) + r13 = box(int, r12) + CPySequenceTuple_SetItemUnsafe(r1, r2, r13) +L7: + r14 = r2 + 1 + r2 = r14 + goto L1 +L8: + a = r1 + return 1 + [case testTupleBuiltFromFinalBytes_64bit] from typing import Final From ed4239b663730b5913e7a1674d16ef9ad47b8064 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 4 Aug 2025 02:01:11 +0000 Subject: [PATCH 24/39] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypyc/test-data/irbuild-lists.test | 1 - 1 file changed, 1 deletion(-) diff --git a/mypyc/test-data/irbuild-lists.test b/mypyc/test-data/irbuild-lists.test index 2abe4fdaef27..c9763b098efa 100644 --- a/mypyc/test-data/irbuild-lists.test +++ b/mypyc/test-data/irbuild-lists.test @@ -901,4 +901,3 @@ L9: L10: a = r3 return 1 - From 7eb67a301133853d5d08c8dddfbec8aeae032529 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Mon, 4 Aug 2025 02:01:11 +0000 Subject: [PATCH 25/39] 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 839ad254d784..4ece96540238 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -1211,7 +1211,7 @@ def get_expr_length(expr: Expression) -> int | None: and expr.node.has_explicit_value ): return len(expr.node.final_value) - # TODO: extend this, unrolling should come with a good performance boost + # TODO: extend this, passing length of listcomp and genexp should have worthwhile performance boost return None From e9f0451b8be2ee75b64da9c635de26dbf2f679c3 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Mon, 4 Aug 2025 02:01:11 +0000 Subject: [PATCH 26/39] Update for_helpers.py --- mypyc/irbuild/for_helpers.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index 4ece96540238..fbc008e0275a 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -1211,7 +1211,10 @@ def get_expr_length(expr: Expression) -> int | None: and expr.node.has_explicit_value ): return len(expr.node.final_value) - # TODO: extend this, passing length of listcomp and genexp should have worthwhile performance boost + # TODO: extend this, passing length of listcomp and genexp should have worthwhile + # performance boost and can be (sometimes) figured out pretty easily. set and dict + # comps *can* be done as well but will need special logic to consider the possibility + # of key conflicts return None From 537c8afbafbcbf87aaf010e6de447195e1ee7c2c Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Mon, 4 Aug 2025 02:01:11 +0000 Subject: [PATCH 27/39] feat(test): test stars --- mypyc/test-data/irbuild-lists.test | 67 ++++++++++++++++++++++++++++++ mypyc/test-data/irbuild-tuple.test | 67 ++++++++++++++++++++++++++++++ 2 files changed, 134 insertions(+) diff --git a/mypyc/test-data/irbuild-lists.test b/mypyc/test-data/irbuild-lists.test index c9763b098efa..2f5b3b39319e 100644 --- a/mypyc/test-data/irbuild-lists.test +++ b/mypyc/test-data/irbuild-lists.test @@ -901,3 +901,70 @@ L9: L10: a = r3 return 1 + +[case testListBuiltFromStars] +from typing import Final + +abc: Final = "abc" + +def test() -> None: + a = [str(x) for x in [*abc, *"def", *b"ghi", ("j", "k"), *("l", "m", "n")]] + +[out] +def test(): + r0, r1 :: str + r2 :: bytes + r3, r4 :: str + r5 :: tuple[str, str] + r6, r7, r8 :: str + r9 :: tuple[str, str, str] + r10 :: list + r11, r12, r13, r14 :: object + r15 :: i32 + r16 :: bit + r17, r18 :: object + r19 :: list + r20, r21 :: native_int + r22 :: bit + r23, x :: object + r24 :: str + r25 :: native_int + a :: list +L0: + r0 = 'abc' + r1 = 'def' + r2 = b'ghi' + r3 = 'j' + r4 = 'k' + r5 = (r3, r4) + r6 = 'l' + r7 = 'm' + r8 = 'n' + r9 = (r6, r7, r8) + r10 = PyList_New(0) + r11 = CPyList_Extend(r10, r0) + r12 = CPyList_Extend(r10, r1) + r13 = CPyList_Extend(r10, r2) + r14 = box(tuple[str, str], r5) + r15 = PyList_Append(r10, r14) + r16 = r15 >= 0 :: signed + r17 = box(tuple[str, str, str], r9) + r18 = CPyList_Extend(r10, r17) + r19 = PyList_New(13) + r20 = 0 +L1: + r21 = var_object_size r10 + r22 = r20 < r21 :: signed + if r22 goto L2 else goto L4 :: bool +L2: + r23 = list_get_item_unsafe r10, r20 + x = r23 + r24 = PyObject_Str(x) + CPyList_SetItemUnsafe(r19, r20, r24) +L3: + r25 = r20 + 1 + r20 = r25 + goto L1 +L4: + a = r19 + return 1 diff --git a/mypyc/test-data/irbuild-tuple.test b/mypyc/test-data/irbuild-tuple.test index f4a8dea2262a..f1087440ffc7 100644 --- a/mypyc/test-data/irbuild-tuple.test +++ b/mypyc/test-data/irbuild-tuple.test @@ -757,6 +757,73 @@ L4: a = r1 return 1 +[case testTupleBuiltFromStars] +from typing import Final + +abc: Final = "abc" + +def test() -> None: + a = tuple(str(x) for x in [*abc, *"def", *b"ghi", ("j", "k"), *("l", "m", "n")]) + +[out] +def test(): + r0, r1 :: str + r2 :: bytes + r3, r4 :: str + r5 :: tuple[str, str] + r6, r7, r8 :: str + r9 :: tuple[str, str, str] + r10 :: list + r11, r12, r13, r14 :: object + r15 :: i32 + r16 :: bit + r17, r18 :: object + r19 :: tuple + r20, r21 :: native_int + r22 :: bit + r23, x :: object + r24 :: str + r25 :: native_int + a :: tuple +L0: + r0 = 'abc' + r1 = 'def' + r2 = b'ghi' + r3 = 'j' + r4 = 'k' + r5 = (r3, r4) + r6 = 'l' + r7 = 'm' + r8 = 'n' + r9 = (r6, r7, r8) + r10 = PyList_New(0) + r11 = CPyList_Extend(r10, r0) + r12 = CPyList_Extend(r10, r1) + r13 = CPyList_Extend(r10, r2) + r14 = box(tuple[str, str], r5) + r15 = PyList_Append(r10, r14) + r16 = r15 >= 0 :: signed + r17 = box(tuple[str, str, str], r9) + r18 = CPyList_Extend(r10, r17) + r19 = PyTuple_New(13) + r20 = 0 +L1: + r21 = var_object_size r10 + r22 = r20 < r21 :: signed + if r22 goto L2 else goto L4 :: bool +L2: + r23 = list_get_item_unsafe r10, r20 + x = r23 + r24 = PyObject_Str(x) + CPySequenceTuple_SetItemUnsafe(r19, r20, r24) +L3: + r25 = r20 + 1 + r20 = r25 + goto L1 +L4: + a = r19 + return 1 + [case testTupleAdd] from typing import Tuple def f(a: Tuple[int, ...], b: Tuple[int, ...]) -> None: From 888b9717ef5c923e1c345bd61302eeba5307fa54 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Mon, 4 Aug 2025 00:36:46 -0400 Subject: [PATCH 28/39] Update for_helpers.py --- mypyc/irbuild/for_helpers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index fbc008e0275a..e6fc46da5215 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -1201,7 +1201,6 @@ def get_expr_length(expr: Expression) -> int | None: other = sum(not isinstance(i, StarExpr) for i in expr.items) return other + sum(stars) # type: ignore [arg-type] elif isinstance(expr, StarExpr): - # star expression needs some extra logic but that can come later, this is good for now return get_expr_length(expr.expr) elif ( isinstance(expr, RefExpr) From 2b0cb4b4871f984c440a7d6c614a30937a87e83b Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Thu, 7 Aug 2025 12:52:14 -0400 Subject: [PATCH 29/39] 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 e6fc46da5215..531107f00972 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -1213,7 +1213,7 @@ def get_expr_length(expr: Expression) -> int | None: # TODO: extend this, passing length of listcomp and genexp should have worthwhile # performance boost and can be (sometimes) figured out pretty easily. set and dict # comps *can* be done as well but will need special logic to consider the possibility - # of key conflicts + # of key conflicts. Range, enumerate, zip are all simple logic. return None From 62dbc1138c9a03d96b4ec0b4880b315c126d9c6e Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Thu, 14 Aug 2025 12:27:03 -0400 Subject: [PATCH 30/39] Update for_helpers.py --- mypyc/irbuild/for_helpers.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index e338fb1e5024..666f51590ce6 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -178,8 +178,8 @@ def for_loop_helper_with_index( exit_block = BasicBlock() condition_block = BasicBlock() - for_gen = ForSequence(builder, index, body_block, exit_block, line, False, length) - for_gen.init(expr_reg, target_type, reverse=False) + for_gen = ForSequence(builder, index, body_block, exit_block, line, False) + for_gen.init(expr_reg, target_type, reverse=False, length=length) builder.push_loop_stack(step_block, exit_block) @@ -796,9 +796,11 @@ class ForSequence(ForGenerator): length_reg: Value | AssignmentTarget | None - def init(self, expr_reg: Value, target_type: RType, reverse: bool) -> None: + def init(self, expr_reg: Value, target_type: RType, reverse: bool, length: Value | None = None) -> None: assert is_sequence_rprimitive(expr_reg.type), expr_reg builder = self.builder + # Record a Value indicating the length of the sequence, if known at compile time. + self.length = length self.reverse = reverse # Define target to contain the expression, along with the index that will be used # for the for-loop. If we are inside of a generator function, spill these into the From 99e44d37e7658328ab4658775990ab43d245f77e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 14 Aug 2025 16:31:09 +0000 Subject: [PATCH 31/39] [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 666f51590ce6..5edee6cb4df4 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -796,7 +796,9 @@ class ForSequence(ForGenerator): length_reg: Value | AssignmentTarget | None - def init(self, expr_reg: Value, target_type: RType, reverse: bool, length: Value | None = None) -> None: + def init( + self, expr_reg: Value, target_type: RType, reverse: bool, length: Value | None = None + ) -> None: assert is_sequence_rprimitive(expr_reg.type), expr_reg builder = self.builder # Record a Value indicating the length of the sequence, if known at compile time. From b19ad911bad1e6c9aee10ce13c54027a065d79e8 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Thu, 14 Aug 2025 12:46:01 -0400 Subject: [PATCH 32/39] Update irbuild-tuple.test --- mypyc/test-data/irbuild-tuple.test | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mypyc/test-data/irbuild-tuple.test b/mypyc/test-data/irbuild-tuple.test index 01853c5be37f..88d251b79b1c 100644 --- a/mypyc/test-data/irbuild-tuple.test +++ b/mypyc/test-data/irbuild-tuple.test @@ -742,8 +742,7 @@ def test(source): L0: r0 = var_object_size source r1 = PyTuple_New(r0) - r2 = var_object_size source - r3 = 0 + r2 = 0 L1: r3 = r2 < r0 :: signed if r3 goto L2 else goto L4 :: bool From e2742e924540cd03adafbd734f8260d647cab7ec Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Thu, 14 Aug 2025 12:48:48 -0400 Subject: [PATCH 33/39] Update irbuild-tuple.test --- mypyc/test-data/irbuild-tuple.test | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/mypyc/test-data/irbuild-tuple.test b/mypyc/test-data/irbuild-tuple.test index 88d251b79b1c..fa52b22e699c 100644 --- a/mypyc/test-data/irbuild-tuple.test +++ b/mypyc/test-data/irbuild-tuple.test @@ -304,20 +304,18 @@ L0: r1 = CPyStr_Size_size_t(source) r2 = r1 >= 0 :: signed r3 = PyTuple_New(r1) - r4 = CPyStr_Size_size_t(source) - r5 = r4 >= 0 :: signed - r6 = 0 + r4 = 0 L1: - r7 = r6 < r4 :: signed - if r7 goto L2 else goto L4 :: bool + r5 = r4 < r1 :: signed + if r5 goto L2 else goto L4 :: bool L2: - r8 = CPyStr_GetItemUnsafe(source, r6) - x = r8 - r9 = f2(x) - CPySequenceTuple_SetItemUnsafe(r3, r6, r9) + r6 = CPyStr_GetItemUnsafe(source, r4) + x = r6 + r7 = f2(x) + CPySequenceTuple_SetItemUnsafe(r3, r4, r7) L3: - r10 = r6 + 1 - r6 = r10 + r8 = r4 + 1 + r4 = r8 goto L1 L4: a = r3 From b9317bcc9587ac9e213d6f7d1ac753f3a4664dd2 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Thu, 14 Aug 2025 12:50:19 -0400 Subject: [PATCH 34/39] Update irbuild-generics.test --- mypyc/test-data/irbuild-generics.test | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/mypyc/test-data/irbuild-generics.test b/mypyc/test-data/irbuild-generics.test index 06e742fc2d75..cf957faccc82 100644 --- a/mypyc/test-data/irbuild-generics.test +++ b/mypyc/test-data/irbuild-generics.test @@ -711,18 +711,17 @@ L0: r0 = __mypyc_self__.__mypyc_env__ r1 = var_object_size args r2 = PyList_New(r1) - r3 = var_object_size args - r4 = 0 + r3 = 0 L1: - r5 = r4 < r3 :: signed - if r5 goto L2 else goto L4 :: bool + r4 = r3 < r1 :: signed + if r4 goto L2 else goto L4 :: bool L2: - r6 = CPySequenceTuple_GetItemUnsafe(args, r4) - x = r6 - CPyList_SetItemUnsafe(r2, r4, x) + r5 = CPySequenceTuple_GetItemUnsafe(args, r3) + x = r5 + CPyList_SetItemUnsafe(r2, r3, x) L3: - r7 = r4 + 1 - r4 = r7 + r6 = r3 + 1 + r3 = r6 goto L1 L4: can_listcomp = r2 From 0869a2f5563df242019731c84253b1f70c84c290 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Fri, 15 Aug 2025 22:47:17 -0400 Subject: [PATCH 35/39] Update irbuild-tuple.test --- mypyc/test-data/irbuild-tuple.test | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/mypyc/test-data/irbuild-tuple.test b/mypyc/test-data/irbuild-tuple.test index fa52b22e699c..f2666f32bc32 100644 --- a/mypyc/test-data/irbuild-tuple.test +++ b/mypyc/test-data/irbuild-tuple.test @@ -293,10 +293,9 @@ def test(): r3 :: tuple r4 :: native_int r5 :: bit - r6 :: native_int + r6, x, r7 :: str r7 :: bit - r8, x, r9 :: str - r10 :: native_int + r8 :: native_int a :: tuple L0: r0 = 'abc' From bec2e9298ab2091a19a0155f930d6ac2d842fdd5 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Tue, 19 Aug 2025 13:41:43 -0400 Subject: [PATCH 36/39] Update irbuild-tuple.test --- mypyc/test-data/irbuild-tuple.test | 1 - 1 file changed, 1 deletion(-) diff --git a/mypyc/test-data/irbuild-tuple.test b/mypyc/test-data/irbuild-tuple.test index f2666f32bc32..f1087440ffc7 100644 --- a/mypyc/test-data/irbuild-tuple.test +++ b/mypyc/test-data/irbuild-tuple.test @@ -294,7 +294,6 @@ def test(): r4 :: native_int r5 :: bit r6, x, r7 :: str - r7 :: bit r8 :: native_int a :: tuple L0: From 79e10096101b0122cf27354efba2a71254c92e72 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Tue, 19 Aug 2025 13:42:25 -0400 Subject: [PATCH 37/39] Update irbuild-generics.test --- mypyc/test-data/irbuild-generics.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypyc/test-data/irbuild-generics.test b/mypyc/test-data/irbuild-generics.test index 91b7b5c4f457..1f10041ba3b5 100644 --- a/mypyc/test-data/irbuild-generics.test +++ b/mypyc/test-data/irbuild-generics.test @@ -741,7 +741,7 @@ L6: r16 = cast(str, r14) k = r16 v = r15 - r17 = CPyDict_SetItem(r7, k, v) + r17 = PyDict_SetItem(r7, k, v) r18 = r17 >= 0 :: signed L7: r19 = CPyDict_CheckSize(kwargs, r9) From 625929f4349f74a1bafcb2b619d7b89e4455b945 Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Mon, 8 Sep 2025 18:26:29 +0000 Subject: [PATCH 38/39] add run tests --- mypyc/test-data/run-lists.test | 6 ++++++ mypyc/test-data/run-tuples.test | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/mypyc/test-data/run-lists.test b/mypyc/test-data/run-lists.test index 1569579c1156..40ca1b6e005f 100644 --- a/mypyc/test-data/run-lists.test +++ b/mypyc/test-data/run-lists.test @@ -479,6 +479,8 @@ def test_in_operator_various_cases() -> None: assert list_in_mixed(type) [case testListBuiltFromGenerator] +from typing import Final +abc: Final = "abc" def test_from_gen() -> None: source_a = ["a", "b", "c"] a = list(x + "f2" for x in source_a) @@ -498,6 +500,10 @@ def test_from_gen() -> None: source_str = "abcd" f = list("str:" + x for x in source_str) assert f == ["str:a", "str:b", "str:c", "str:d"] +def test_known_length() -> None: + # not built from generator but doesnt need its own test either + built = [str(x) for x in [*abc, *"def", *b"ghi", ("j", "k"), *("l", "m", "n")]] + assert built == ['a', 'b', 'c', 'd', 'e', 'f', '103', '104', '105', "('j', 'k')", 'l', 'm', 'n'] [case testNext] from typing import List diff --git a/mypyc/test-data/run-tuples.test b/mypyc/test-data/run-tuples.test index f5e1733d429b..ae88bae680ca 100644 --- a/mypyc/test-data/run-tuples.test +++ b/mypyc/test-data/run-tuples.test @@ -270,6 +270,11 @@ def test_slicing() -> None: def f8(val: int) -> bool: return val % 2 == 0 +abc: Final = "abc" + +def known_length() -> tuple[str, ...]: + return tuple(str(x) for x in [*abc, *"def", *b"ghi", ("j", "k"), *("l", "m", "n")]) + def test_sequence_generator() -> None: source_list = [1, 2, 3] a = tuple(f8(x) for x in source_list) @@ -287,6 +292,8 @@ def test_sequence_generator() -> None: b = tuple('s:' + x for x in source_str) assert b == ('s:a', 's:b', 's:b', 's:c') + assert known_length() == ['a', 'b', 'c', 'd', 'e', 'f', '103', '104', '105', "('j', 'k')", 'l', 'm', 'n'] + TUPLE: Final[Tuple[str, ...]] = ('x', 'y') def test_final_boxed_tuple() -> None: From 44244e09a6606395aff86b8da76a522b01944185 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Mon, 8 Sep 2025 14:27:36 -0400 Subject: [PATCH 39/39] Update run-tuples.test --- mypyc/test-data/run-tuples.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypyc/test-data/run-tuples.test b/mypyc/test-data/run-tuples.test index ae88bae680ca..e2e8358bb43e 100644 --- a/mypyc/test-data/run-tuples.test +++ b/mypyc/test-data/run-tuples.test @@ -292,7 +292,7 @@ def test_sequence_generator() -> None: b = tuple('s:' + x for x in source_str) assert b == ('s:a', 's:b', 's:b', 's:c') - assert known_length() == ['a', 'b', 'c', 'd', 'e', 'f', '103', '104', '105', "('j', 'k')", 'l', 'm', 'n'] + assert known_length() == ('a', 'b', 'c', 'd', 'e', 'f', '103', '104', '105', "('j', 'k')", 'l', 'm', 'n') TUPLE: Final[Tuple[str, ...]] = ('x', 'y')