Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bpo-33041: Fixed bytecode generation for "async for" with a complex target. #6052

Merged
merged 1 commit into from
Mar 10, 2018
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
65 changes: 65 additions & 0 deletions Lib/test/test_coroutines.py
Original file line number Diff line number Diff line change
Expand Up @@ -1949,6 +1949,71 @@ async def func(): pass
support.gc_collect()
self.assertIn("was never awaited", stderr.getvalue())

def test_for_assign_raising_stop_async_iteration(self):
class BadTarget:
def __setitem__(self, key, value):
raise StopAsyncIteration(42)
tgt = BadTarget()
async def source():
yield 10

async def run_for():
with self.assertRaises(StopAsyncIteration) as cm:
async for tgt[0] in source():
pass
self.assertEqual(cm.exception.args, (42,))
return 'end'
self.assertEqual(run_async(run_for()), ([], 'end'))

async def run_list():
with self.assertRaises(StopAsyncIteration) as cm:
return [0 async for tgt[0] in source()]
self.assertEqual(cm.exception.args, (42,))
return 'end'
self.assertEqual(run_async(run_list()), ([], 'end'))

async def run_gen():
gen = (0 async for tgt[0] in source())
a = gen.asend(None)
with self.assertRaises(RuntimeError) as cm:
await a
self.assertIsInstance(cm.exception.__cause__, StopAsyncIteration)
self.assertEqual(cm.exception.__cause__.args, (42,))
return 'end'
self.assertEqual(run_async(run_gen()), ([], 'end'))

def test_for_assign_raising_stop_async_iteration_2(self):
class BadIterable:
def __iter__(self):
raise StopAsyncIteration(42)
async def badpairs():
yield BadIterable()

async def run_for():
with self.assertRaises(StopAsyncIteration) as cm:
async for i, j in badpairs():
pass
self.assertEqual(cm.exception.args, (42,))
return 'end'
self.assertEqual(run_async(run_for()), ([], 'end'))

async def run_list():
with self.assertRaises(StopAsyncIteration) as cm:
return [0 async for i, j in badpairs()]
self.assertEqual(cm.exception.args, (42,))
return 'end'
self.assertEqual(run_async(run_list()), ([], 'end'))

async def run_gen():
gen = (0 async for i, j in badpairs())
a = gen.asend(None)
with self.assertRaises(RuntimeError) as cm:
await a
self.assertIsInstance(cm.exception.__cause__, StopAsyncIteration)
self.assertEqual(cm.exception.__cause__.args, (42,))
return 'end'
self.assertEqual(run_async(run_gen()), ([], 'end'))


class CoroAsyncIOCompatTest(unittest.TestCase):

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fixed bytecode generation for "async for" with a complex target. A
StopAsyncIteration raised on assigning or unpacking will be now propagated
instead of stopping the iteration.
4 changes: 2 additions & 2 deletions Python/compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -2473,8 +2473,8 @@ compiler_async_for(struct compiler *c, stmt_ty s)
ADDOP(c, GET_ANEXT);
ADDOP_O(c, LOAD_CONST, Py_None, consts);
ADDOP(c, YIELD_FROM);
VISIT(c, expr, s->v.AsyncFor.target);
ADDOP(c, POP_BLOCK); /* for SETUP_FINALLY */
VISIT(c, expr, s->v.AsyncFor.target);
compiler_pop_fblock(c, EXCEPT, try);
ADDOP_JREL(c, JUMP_FORWARD, after_try);

Expand Down Expand Up @@ -4060,8 +4060,8 @@ compiler_async_comprehension_generator(struct compiler *c,
ADDOP(c, GET_ANEXT);
ADDOP_O(c, LOAD_CONST, Py_None, consts);
ADDOP(c, YIELD_FROM);
VISIT(c, expr, gen->target);
ADDOP(c, POP_BLOCK);
VISIT(c, expr, gen->target);
compiler_pop_fblock(c, EXCEPT, try);
ADDOP_JREL(c, JUMP_FORWARD, after_try);

Expand Down