Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions Lib/test/test_coroutines.py
Original file line number Diff line number Diff line change
Expand Up @@ -1892,6 +1892,36 @@ async def run_gen():
run_async(run_gen()),
([], [121]))

def test_comp_4_2(self):
async def f(it):
for i in it:
yield i

async def run_list():
return [i + 10 async for i in f(range(5)) if 0 < i < 4]
self.assertEqual(
run_async(run_list()),
([], [11, 12, 13]))

async def run_set():
return {i + 10 async for i in f(range(5)) if 0 < i < 4}
self.assertEqual(
run_async(run_set()),
([], {11, 12, 13}))

async def run_dict():
return {i + 10: i + 100 async for i in f(range(5)) if 0 < i < 4}
self.assertEqual(
run_async(run_dict()),
([], {11: 101, 12: 102, 13: 103}))

async def run_gen():
gen = (i + 10 async for i in f(range(5)) if 0 < i < 4)
return [g + 100 async for g in gen]
self.assertEqual(
run_async(run_gen()),
([], [111, 112, 113]))

def test_comp_5(self):
async def f(it):
for i in it:
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_dis.py
Original file line number Diff line number Diff line change
Expand Up @@ -597,7 +597,7 @@ async def async_def():
Argument count: 0
Kw-only arguments: 0
Number of locals: 2
Stack size: 17
Stack size: 16
Flags: OPTIMIZED, NEWLOCALS, NOFREE, COROUTINE
Constants:
0: None
Expand Down
56 changes: 56 additions & 0 deletions Lib/test/test_sys_settrace.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ async def __aenter__(self):
async def __aexit__(self, *exc_info):
self.output.append(-self.value)

async def asynciter(iterable):
"""Convert an iterable to an asynchronous iterator."""
for x in iterable:
yield x

def asyncio_run(main):
import asyncio
import asyncio.events
Expand Down Expand Up @@ -690,6 +695,23 @@ def test_jump_out_of_block_backwards(output):
output.append(6)
output.append(7)

@async_jump_test(4, 5, [3, 5])
async def test_jump_out_of_async_for_block_forwards(output):
for i in [1]:
async for i in asynciter([1, 2]):
output.append(3)
output.append(4)
output.append(5)

@async_jump_test(5, 2, [2, 4, 2, 4, 5, 6])
async def test_jump_out_of_async_for_block_backwards(output):
for i in [1]:
output.append(2)
async for i in asynciter([1]):
output.append(4)
output.append(5)
output.append(6)

@jump_test(1, 2, [3])
def test_jump_to_codeless_line(output):
output.append(1)
Expand Down Expand Up @@ -969,6 +991,17 @@ def test_jump_over_for_block_before_else(output):
output.append(7)
output.append(8)

@async_jump_test(1, 7, [7, 8])
async def test_jump_over_async_for_block_before_else(output):
output.append(1)
if not output: # always false
async for i in asynciter([3]):
output.append(4)
else:
output.append(6)
output.append(7)
output.append(8)

# The second set of 'jump' tests are for things that are not allowed:

@jump_test(2, 3, [1], (ValueError, 'after'))
Expand Down Expand Up @@ -1020,12 +1053,24 @@ def test_no_jump_forwards_into_for_block(output):
for i in 1, 2:
output.append(3)

@async_jump_test(1, 3, [], (ValueError, 'into'))
async def test_no_jump_forwards_into_async_for_block(output):
output.append(1)
async for i in asynciter([1, 2]):
output.append(3)

@jump_test(3, 2, [2, 2], (ValueError, 'into'))
def test_no_jump_backwards_into_for_block(output):
for i in 1, 2:
output.append(2)
output.append(3)

@async_jump_test(3, 2, [2, 2], (ValueError, 'into'))
async def test_no_jump_backwards_into_async_for_block(output):
async for i in asynciter([1, 2]):
output.append(2)
output.append(3)

@jump_test(2, 4, [], (ValueError, 'into'))
def test_no_jump_forwards_into_while_block(output):
i = 1
Expand Down Expand Up @@ -1165,6 +1210,17 @@ def test_no_jump_into_for_block_before_else(output):
output.append(7)
output.append(8)

@async_jump_test(7, 4, [1, 6], (ValueError, 'into'))
async def test_no_jump_into_async_for_block_before_else(output):
output.append(1)
if not output: # always false
async for i in asynciter([3]):
output.append(4)
else:
output.append(6)
output.append(7)
output.append(8)

def test_no_jump_to_non_integers(self):
self.run_test(no_jump_to_non_integers, 2, "Spam", [True])

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed jumping when the function contains an ``async for`` loop.
43 changes: 17 additions & 26 deletions Python/compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -2237,23 +2237,19 @@ compiler_async_for(struct compiler *c, stmt_ty s)
ADDOP(c, DUP_TOP);
ADDOP_O(c, LOAD_GLOBAL, stop_aiter_error, names);
ADDOP_I(c, COMPARE_OP, PyCmp_EXC_MATCH);
ADDOP_JABS(c, POP_JUMP_IF_FALSE, try_cleanup);

ADDOP(c, POP_TOP);
ADDOP(c, POP_TOP);
ADDOP(c, POP_TOP);
ADDOP(c, POP_EXCEPT); /* for SETUP_EXCEPT */
ADDOP(c, POP_BLOCK); /* for SETUP_LOOP */
ADDOP_JABS(c, JUMP_ABSOLUTE, after_loop_else);


compiler_use_next_block(c, try_cleanup);
ADDOP_JABS(c, POP_JUMP_IF_TRUE, try_cleanup);
ADDOP(c, END_FINALLY);

compiler_use_next_block(c, after_try);
VISIT_SEQ(c, stmt, s->v.AsyncFor.body);
ADDOP_JABS(c, JUMP_ABSOLUTE, try);

compiler_use_next_block(c, try_cleanup);
ADDOP(c, POP_TOP);
ADDOP(c, POP_TOP);
ADDOP(c, POP_TOP);
ADDOP(c, POP_EXCEPT); /* for SETUP_EXCEPT */
ADDOP(c, POP_TOP); /* for correct calculation of stack effect */
ADDOP(c, POP_BLOCK); /* for SETUP_LOOP */
compiler_pop_fblock(c, LOOP, try);

Expand Down Expand Up @@ -3750,7 +3746,7 @@ compiler_async_comprehension_generator(struct compiler *c,
_Py_IDENTIFIER(StopAsyncIteration);

comprehension_ty gen;
basicblock *anchor, *if_cleanup, *try,
basicblock *if_cleanup, *try,
*after_try, *except, *try_cleanup;
Py_ssize_t i, n;

Expand All @@ -3761,12 +3757,11 @@ compiler_async_comprehension_generator(struct compiler *c,

try = compiler_new_block(c);
after_try = compiler_new_block(c);
try_cleanup = compiler_new_block(c);
except = compiler_new_block(c);
if_cleanup = compiler_new_block(c);
anchor = compiler_new_block(c);
try_cleanup = compiler_new_block(c);

if (if_cleanup == NULL || anchor == NULL ||
if (if_cleanup == NULL ||
try == NULL || after_try == NULL ||
except == NULL || try_cleanup == NULL) {
return 0;
Expand Down Expand Up @@ -3807,16 +3802,7 @@ compiler_async_comprehension_generator(struct compiler *c,
ADDOP(c, DUP_TOP);
ADDOP_O(c, LOAD_GLOBAL, stop_aiter_error, names);
ADDOP_I(c, COMPARE_OP, PyCmp_EXC_MATCH);
ADDOP_JABS(c, POP_JUMP_IF_FALSE, try_cleanup);

ADDOP(c, POP_TOP);
ADDOP(c, POP_TOP);
ADDOP(c, POP_TOP);
ADDOP(c, POP_EXCEPT); /* for SETUP_EXCEPT */
ADDOP_JABS(c, JUMP_ABSOLUTE, anchor);


compiler_use_next_block(c, try_cleanup);
ADDOP_JABS(c, POP_JUMP_IF_TRUE, try_cleanup);
ADDOP(c, END_FINALLY);

compiler_use_next_block(c, after_try);
Expand Down Expand Up @@ -3865,7 +3851,12 @@ compiler_async_comprehension_generator(struct compiler *c,
}
compiler_use_next_block(c, if_cleanup);
ADDOP_JABS(c, JUMP_ABSOLUTE, try);
compiler_use_next_block(c, anchor);

compiler_use_next_block(c, try_cleanup);
ADDOP(c, POP_TOP);
ADDOP(c, POP_TOP);
ADDOP(c, POP_TOP);
ADDOP(c, POP_EXCEPT); /* for SETUP_EXCEPT */
ADDOP(c, POP_TOP);

return 1;
Expand Down