From 6c7684a5cc307abe680141853d88239b2f5d97de Mon Sep 17 00:00:00 2001 From: chetan22 Date: Fri, 7 May 2021 20:57:48 +0530 Subject: [PATCH 1/4] [mypyc] Match evaluation order of multiple assignment from iterable with Python (#793) Refactored to iterate rvalues first before performing any assignments. Closes mypyc/mypyc#793. --- mypyc/irbuild/builder.py | 14 ++- mypyc/test-data/irbuild-statements.test | 109 +++++++++++++++++++++--- 2 files changed, 109 insertions(+), 14 deletions(-) diff --git a/mypyc/irbuild/builder.py b/mypyc/irbuild/builder.py index 5ef8d8594c150..6cd36a01f21a6 100644 --- a/mypyc/irbuild/builder.py +++ b/mypyc/irbuild/builder.py @@ -931,7 +931,8 @@ def process_iterator_tuple_assignment( # This may be the whole lvalue list if there is no starred value split_idx = target.star_idx if target.star_idx is not None else len(target.items) - # Assign values before the first starred value + # Read values before the first starred value + values = [] for litem in target.items[:split_idx]: ritem = self.call_c(next_op, [iterator], line) error_block, ok_block = BasicBlock(), BasicBlock() @@ -947,9 +948,13 @@ def process_iterator_tuple_assignment( self.activate_block(ok_block) + values.append(ritem) + + # Assign read values to target lvalues + for litem, ritem in zip(target.items[:split_idx], values): self.assign(litem, ritem, line) - # Assign the starred value and all values after it + # Read the starred value and all values after it if target.star_idx is not None: post_star_vals = target.items[split_idx + 1 :] iter_list = self.primitive_op(to_list, [iterator], line) @@ -970,8 +975,13 @@ def process_iterator_tuple_assignment( self.activate_block(ok_block) + values = [] for litem in reversed(post_star_vals): ritem = self.primitive_op(list_pop_last, [iter_list], line) + values.append(ritem) + + # Assign the read values to target lvalues + for litem, ritem in zip(reversed(post_star_vals), values): self.assign(litem, ritem, line) # Assign the starred value diff --git a/mypyc/test-data/irbuild-statements.test b/mypyc/test-data/irbuild-statements.test index d1e9f69a59e0c..8b7d5ddb28f21 100644 --- a/mypyc/test-data/irbuild-statements.test +++ b/mypyc/test-data/irbuild-statements.test @@ -443,9 +443,9 @@ L0: def from_any(a): a, r0, r1 :: object r2 :: bool - x, r3 :: object + r3 :: object r4 :: bool - y, r5 :: object + x, y, r5 :: object r6 :: bool L0: r0 = PyObject_GetIter(a) @@ -455,13 +455,13 @@ L1: r2 = raise ValueError('not enough values to unpack') unreachable L2: - x = r1 r3 = PyIter_Next(r0) if is_error(r3) goto L3 else goto L4 L3: r4 = raise ValueError('not enough values to unpack') unreachable L4: + x = r1 y = r3 r5 = PyIter_Next(r0) if is_error(r5) goto L6 else goto L5 @@ -504,9 +504,9 @@ L0: def from_any(a): a, r0, r1 :: object r2 :: bool - r3, x :: int - r4 :: object - r5 :: bool + r3 :: object + r4 :: bool + r5, x :: int y, r6 :: object r7 :: bool L0: @@ -517,15 +517,15 @@ L1: r2 = raise ValueError('not enough values to unpack') unreachable L2: - r3 = unbox(int, r1) - x = r3 - r4 = PyIter_Next(r0) - if is_error(r4) goto L3 else goto L4 + r3 = PyIter_Next(r0) + if is_error(r3) goto L3 else goto L4 L3: - r5 = raise ValueError('not enough values to unpack') + r4 = raise ValueError('not enough values to unpack') unreachable L4: - y = r4 + r5 = unbox(int, r1) + x = r5 + y = r3 r6 = PyIter_Next(r0) if is_error(r6) goto L6 else goto L5 L5: @@ -534,6 +534,91 @@ L5: L6: return 1 +[case testStarUnpack] +from typing import Any, List, Iterator + +it: Iterator = iter(['x', 'y', 'z1', 'z2', 'z3', 'u', 'w']) + +def f(a: Any) -> None: + a.x, a.y, *a.z, a.u, a.w = it +[out] +def f(a): + a :: object + r0 :: dict + r1 :: str + r2, r3, r4 :: object + r5 :: bool + r6 :: object + r7 :: bool + r8 :: str + r9 :: int32 + r10 :: bit + r11 :: str + r12 :: int32 + r13 :: bit + r14 :: list + r15 :: ptr + r16 :: int64 + r17 :: short_int + r18 :: bit + r19 :: bool + r20, r21 :: object + r22 :: str + r23 :: int32 + r24 :: bit + r25 :: str + r26 :: int32 + r27 :: bit + r28 :: str + r29 :: int32 + r30 :: bit +L0: + r0 = __main__.globals :: static + r1 = 'it' + r2 = CPyDict_GetItem(r0, r1) + r3 = PyObject_GetIter(r2) + r4 = PyIter_Next(r3) + if is_error(r4) goto L1 else goto L2 +L1: + r5 = raise ValueError('not enough values to unpack') + unreachable +L2: + r6 = PyIter_Next(r3) + if is_error(r6) goto L3 else goto L4 +L3: + r7 = raise ValueError('not enough values to unpack') + unreachable +L4: + r8 = 'x' + r9 = PyObject_SetAttr(a, r8, r4) + r10 = r9 >= 0 :: signed + r11 = 'y' + r12 = PyObject_SetAttr(a, r11, r6) + r13 = r12 >= 0 :: signed + r14 = PySequence_List(r3) + r15 = get_element_ptr r14 ob_size :: PyVarObject + r16 = load_mem r15 :: int64* + keep_alive r14 + r17 = r16 << 1 + r18 = 4 <= r17 :: signed + if r18 goto L6 else goto L5 :: bool +L5: + r19 = raise ValueError('not enough values to unpack') + unreachable +L6: + r20 = CPyList_PopLast(r14) + r21 = CPyList_PopLast(r14) + r22 = 'w' + r23 = PyObject_SetAttr(a, r22, r20) + r24 = r23 >= 0 :: signed + r25 = 'u' + r26 = PyObject_SetAttr(a, r25, r21) + r27 = r26 >= 0 :: signed + r28 = 'z' + r29 = PyObject_SetAttr(a, r28, r14) + r30 = r29 >= 0 :: signed + return 1 + [case testMultiAssignmentNested] from typing import Tuple, Any, List From 97828d93703b4819dcd1a6f42fdf7340c79db78a Mon Sep 17 00:00:00 2001 From: chetan22 Date: Sat, 8 May 2021 12:29:44 +0530 Subject: [PATCH 2/4] changed int64 -> native_int --- mypyc/test-data/irbuild-statements.test | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mypyc/test-data/irbuild-statements.test b/mypyc/test-data/irbuild-statements.test index 8b7d5ddb28f21..697e7d25a45c2 100644 --- a/mypyc/test-data/irbuild-statements.test +++ b/mypyc/test-data/irbuild-statements.test @@ -558,7 +558,7 @@ def f(a): r13 :: bit r14 :: list r15 :: ptr - r16 :: int64 + r16 :: native_int r17 :: short_int r18 :: bit r19 :: bool @@ -597,7 +597,7 @@ L4: r13 = r12 >= 0 :: signed r14 = PySequence_List(r3) r15 = get_element_ptr r14 ob_size :: PyVarObject - r16 = load_mem r15 :: int64* + r16 = load_mem r15 :: native_int* keep_alive r14 r17 = r16 << 1 r18 = 4 <= r17 :: signed From 8d12e3daa5c530b159557c817a47046162387ac7 Mon Sep 17 00:00:00 2001 From: Chetan Khanna Date: Fri, 24 Apr 2026 00:50:31 +0530 Subject: [PATCH 3/4] defer assignment after all rhs evaluation is complete --- mypyc/irbuild/builder.py | 23 +++-- mypyc/test-data/irbuild-statements.test | 117 ++++++++++++------------ 2 files changed, 71 insertions(+), 69 deletions(-) diff --git a/mypyc/irbuild/builder.py b/mypyc/irbuild/builder.py index 6cd36a01f21a6..d2d98796f1fb5 100644 --- a/mypyc/irbuild/builder.py +++ b/mypyc/irbuild/builder.py @@ -932,7 +932,8 @@ def process_iterator_tuple_assignment( split_idx = target.star_idx if target.star_idx is not None else len(target.items) # Read values before the first starred value - values = [] + left_values = [] + right_values = [] for litem in target.items[:split_idx]: ritem = self.call_c(next_op, [iterator], line) error_block, ok_block = BasicBlock(), BasicBlock() @@ -948,11 +949,7 @@ def process_iterator_tuple_assignment( self.activate_block(ok_block) - values.append(ritem) - - # Assign read values to target lvalues - for litem, ritem in zip(target.items[:split_idx], values): - self.assign(litem, ritem, line) + left_values.append(ritem) # Read the starred value and all values after it if target.star_idx is not None: @@ -975,13 +972,15 @@ def process_iterator_tuple_assignment( self.activate_block(ok_block) - values = [] for litem in reversed(post_star_vals): ritem = self.primitive_op(list_pop_last, [iter_list], line) - values.append(ritem) + right_values.append(ritem) - # Assign the read values to target lvalues - for litem, ritem in zip(reversed(post_star_vals), values): + # Assign read values to target lvalues + for litem, ritem in zip(target.items[:split_idx], left_values): + self.assign(litem, ritem, line) + + for litem, ritem in zip(reversed(post_star_vals), right_values): self.assign(litem, ritem, line) # Assign the starred value @@ -1003,6 +1002,10 @@ def process_iterator_tuple_assignment( self.add(Unreachable()) self.activate_block(ok_block) + + # Assign read values to target lvalues + for litem, ritem in zip(target.items[:split_idx], left_values): + self.assign(litem, ritem, line) def push_loop_stack(self, continue_block: BasicBlock, break_block: BasicBlock) -> None: self.nonlocal_control.append( diff --git a/mypyc/test-data/irbuild-statements.test b/mypyc/test-data/irbuild-statements.test index 697e7d25a45c2..e4da35ccb6bee 100644 --- a/mypyc/test-data/irbuild-statements.test +++ b/mypyc/test-data/irbuild-statements.test @@ -445,8 +445,9 @@ def from_any(a): r2 :: bool r3 :: object r4 :: bool - x, y, r5 :: object + r5 :: object r6 :: bool + x, y :: object L0: r0 = PyObject_GetIter(a) r1 = PyIter_Next(r0) @@ -461,14 +462,14 @@ L3: r4 = raise ValueError('not enough values to unpack') unreachable L4: - x = r1 - y = r3 r5 = PyIter_Next(r0) if is_error(r5) goto L6 else goto L5 L5: r6 = raise ValueError('too many values to unpack') unreachable L6: + x = r1 + y = r3 return 1 [case testMultiAssignmentCoercions] @@ -506,9 +507,10 @@ def from_any(a): r2 :: bool r3 :: object r4 :: bool - r5, x :: int - y, r6 :: object - r7 :: bool + r5 :: object + r6 :: bool + r7, x :: int + y :: object L0: r0 = PyObject_GetIter(a) r1 = PyIter_Next(r0) @@ -523,15 +525,15 @@ L3: r4 = raise ValueError('not enough values to unpack') unreachable L4: - r5 = unbox(int, r1) - x = r5 - y = r3 - r6 = PyIter_Next(r0) - if is_error(r6) goto L6 else goto L5 + r5 = PyIter_Next(r0) + if is_error(r5) goto L6 else goto L5 L5: - r7 = raise ValueError('too many values to unpack') + r6 = raise ValueError('too many values to unpack') unreachable L6: + r7 = unbox(int, r1) + x = r7 + y = r3 return 1 [case testStarUnpack] @@ -550,28 +552,27 @@ def f(a): r5 :: bool r6 :: object r7 :: bool - r8 :: str - r9 :: int32 - r10 :: bit - r11 :: str - r12 :: int32 - r13 :: bit - r14 :: list - r15 :: ptr - r16 :: native_int - r17 :: short_int - r18 :: bit - r19 :: bool - r20, r21 :: object - r22 :: str - r23 :: int32 - r24 :: bit - r25 :: str - r26 :: int32 - r27 :: bit - r28 :: str - r29 :: int32 - r30 :: bit + r8 :: list + r9 :: native_int + r10 :: short_int + r11 :: bit + r12 :: bool + r13, r14 :: object + r15 :: str + r16 :: i32 + r17 :: bit + r18 :: str + r19 :: i32 + r20 :: bit + r21 :: str + r22 :: i32 + r23 :: bit + r24 :: str + r25 :: i32 + r26 :: bit + r27 :: str + r28 :: i32 + r29 :: bit L0: r0 = __main__.globals :: static r1 = 'it' @@ -589,34 +590,32 @@ L3: r7 = raise ValueError('not enough values to unpack') unreachable L4: - r8 = 'x' - r9 = PyObject_SetAttr(a, r8, r4) - r10 = r9 >= 0 :: signed - r11 = 'y' - r12 = PyObject_SetAttr(a, r11, r6) - r13 = r12 >= 0 :: signed - r14 = PySequence_List(r3) - r15 = get_element_ptr r14 ob_size :: PyVarObject - r16 = load_mem r15 :: native_int* - keep_alive r14 - r17 = r16 << 1 - r18 = 4 <= r17 :: signed - if r18 goto L6 else goto L5 :: bool + r8 = PySequence_List(r3) + r9 = var_object_size r8 + r10 = r9 << 1 + r11 = int_le 4, r10 + if r11 goto L6 else goto L5 :: bool L5: - r19 = raise ValueError('not enough values to unpack') + r12 = raise ValueError('not enough values to unpack') unreachable L6: - r20 = CPyList_PopLast(r14) - r21 = CPyList_PopLast(r14) - r22 = 'w' - r23 = PyObject_SetAttr(a, r22, r20) - r24 = r23 >= 0 :: signed - r25 = 'u' - r26 = PyObject_SetAttr(a, r25, r21) - r27 = r26 >= 0 :: signed - r28 = 'z' - r29 = PyObject_SetAttr(a, r28, r14) - r30 = r29 >= 0 :: signed + r13 = CPyList_PopLast(r8) + r14 = CPyList_PopLast(r8) + r15 = 'x' + r16 = PyObject_SetAttr(a, r15, r4) + r17 = r16 >= 0 :: signed + r18 = 'y' + r19 = PyObject_SetAttr(a, r18, r6) + r20 = r19 >= 0 :: signed + r21 = 'w' + r22 = PyObject_SetAttr(a, r21, r13) + r23 = r22 >= 0 :: signed + r24 = 'u' + r25 = PyObject_SetAttr(a, r24, r14) + r26 = r25 >= 0 :: signed + r27 = 'z' + r28 = PyObject_SetAttr(a, r27, r8) + r29 = r28 >= 0 :: signed return 1 [case testMultiAssignmentNested] From d68529fb5944a9c933d99d93bb92f3342ccfca20 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 23 Apr 2026 20:22:45 +0000 Subject: [PATCH 4/4] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypyc/irbuild/builder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypyc/irbuild/builder.py b/mypyc/irbuild/builder.py index d2d98796f1fb5..ac02ebd93ffbf 100644 --- a/mypyc/irbuild/builder.py +++ b/mypyc/irbuild/builder.py @@ -1002,7 +1002,7 @@ def process_iterator_tuple_assignment( self.add(Unreachable()) self.activate_block(ok_block) - + # Assign read values to target lvalues for litem, ritem in zip(target.items[:split_idx], left_values): self.assign(litem, ritem, line)