Skip to content

Commit 89df62c

Browse files
authored
GH-128534: Fix behavior of branch monitoring for async for (GH-130847)
* Both branches in a pair now have a common source and are included in co_branches
1 parent e5527f2 commit 89df62c

17 files changed

+235
-154
lines changed

Include/internal/pycore_magic_number.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,8 @@ Known values:
270270
Python 3.14a5 3615 (CALL_FUNCTION_EX always take a kwargs argument)
271271
Python 3.14a5 3616 (Remove BINARY_SUBSCR and family. Make them BINARY_OPs)
272272
Python 3.14a6 3617 (Branch monitoring for async for loops)
273-
Python 3.14a6 3618 (Renumber RESUME opcode from 149 to 128)
273+
Python 3.14a6 3618 (Add oparg to END_ASYNC_FOR)
274+
Python 3.14a6 3619 (Renumber RESUME opcode from 149 to 128)
274275
275276
Python 3.15 will start with 3650
276277
@@ -283,7 +284,7 @@ PC/launcher.c must also be updated.
283284
284285
*/
285286

286-
#define PYC_MAGIC_NUMBER 3618
287+
#define PYC_MAGIC_NUMBER 3619
287288
/* This is equivalent to converting PYC_MAGIC_NUMBER to 2 bytes
288289
(little-endian) and then appending b'\r\n'. */
289290
#define PYC_MAGIC_NUMBER_TOKEN \

Include/internal/pycore_opcode_metadata.h

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/opcode_ids.h

Lines changed: 59 additions & 59 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Lib/_opcode_metadata.py

Lines changed: 59 additions & 59 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Lib/dis.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
STORE_FAST_STORE_FAST = opmap['STORE_FAST_STORE_FAST']
5353
IS_OP = opmap['IS_OP']
5454
CONTAINS_OP = opmap['CONTAINS_OP']
55+
END_ASYNC_FOR = opmap['END_ASYNC_FOR']
5556

5657
CACHE = opmap["CACHE"]
5758

@@ -605,7 +606,8 @@ def get_argval_argrepr(self, op, arg, offset):
605606
argval = self.offset_from_jump_arg(op, arg, offset)
606607
lbl = self.get_label_for_offset(argval)
607608
assert lbl is not None
608-
argrepr = f"to L{lbl}"
609+
preposition = "from" if deop == END_ASYNC_FOR else "to"
610+
argrepr = f"{preposition} L{lbl}"
609611
elif deop in (LOAD_FAST_LOAD_FAST, STORE_FAST_LOAD_FAST, STORE_FAST_STORE_FAST):
610612
arg1 = arg >> 4
611613
arg2 = arg & 15
@@ -745,7 +747,8 @@ def _parse_exception_table(code):
745747

746748
def _is_backward_jump(op):
747749
return opname[op] in ('JUMP_BACKWARD',
748-
'JUMP_BACKWARD_NO_INTERRUPT')
750+
'JUMP_BACKWARD_NO_INTERRUPT',
751+
'END_ASYNC_FOR') # Not really a jump, but it has a "target"
749752

750753
def _get_instructions_bytes(code, linestarts=None, line_offset=0, co_positions=None,
751754
original_code=None, arg_resolver=None):

Lib/test/test_code.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -953,6 +953,15 @@ def with_extended_args(x):
953953
get_line_branches(with_extended_args),
954954
[(1,2,8)])
955955

956+
async def afunc():
957+
async for letter in async_iter1:
958+
2
959+
3
960+
961+
self.assertEqual(
962+
get_line_branches(afunc),
963+
[(1,1,3)])
964+
956965
if check_impl_detail(cpython=True) and ctypes is not None:
957966
py = ctypes.pythonapi
958967
freefunc = ctypes.CFUNCTYPE(None,ctypes.c_voidp)

Lib/test/test_dis.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1256,6 +1256,23 @@ def test__try_compile_no_context_exc_on_error(self):
12561256
except Exception as e:
12571257
self.assertIsNone(e.__context__)
12581258

1259+
def test_async_for_presentation(self):
1260+
1261+
async def afunc():
1262+
async for letter in async_iter1:
1263+
l2
1264+
l3
1265+
1266+
disassembly = self.get_disassembly(afunc)
1267+
for line in disassembly.split("\n"):
1268+
if "END_ASYNC_FOR" in line:
1269+
break
1270+
else:
1271+
self.fail("No END_ASYNC_FOR in disassembly of async for")
1272+
self.assertNotIn("to", line)
1273+
self.assertIn("from", line)
1274+
1275+
12591276
@staticmethod
12601277
def code_quicken(f):
12611278
_testinternalcapi = import_helper.import_module("_testinternalcapi")

0 commit comments

Comments
 (0)