Skip to content
Open
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
25 changes: 24 additions & 1 deletion Lib/test/test_builtin.py
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,10 @@ async def sleep(delay, result=None):
'''a = [x async for x in (x async for x in arange(5))][1]''',
'''a, = [1 for x in {x async for x in arange(1)}]''',
'''a = [await sleep(0, x) async for x in arange(2)][1]''',
'''a = [await sleep(0, 1) for _ in [0]][0]''',
'''a = {await sleep(0, 1) for _ in [0]}.pop()''',
'''a = {0: await sleep(0, 1) for _ in [0]}[0]''',
'''a = (lambda x=[await sleep(0, 1) for _ in [0]]: x)()[0]''',
# gh-121637: Make sure we correctly handle the case where the
# async code is optimized away
'''assert not await sleep(0); a = 1''',
Expand Down Expand Up @@ -610,7 +614,26 @@ async def __aexit__(self, *exc_info):
'''def f():
async with Lock() as l:
a = 1
'''
''',
'''class C:
[await x for x in y]
''',
'''class C:
[x async for x in arange(10)]
''',
'''async def f():
class C:
[await x for x in y]
''',
'''lambda: [await x for x in y]''',
'''class C:
def f(self, x=[await y for y in z]):
pass
''',
'''type T = [await x for x in y]''',
'''async def f[T=[await x for x in y]]():
pass
''',
]
for mode, code_sample in product(modes, code_samples):
source = dedent(code_sample)
Expand Down
104 changes: 104 additions & 0 deletions Lib/test/test_coroutines.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import ast
import contextlib
import copy
import inspect
Expand Down Expand Up @@ -407,6 +408,109 @@ async def bar():
with self.subTest(code=code), self.assertRaises(SyntaxError):
compile(code, "<test>", "exec")

def test_async_comprehension_scope(self):
# List/set/dict comprehensions with await or async for are allowed
# only in async functions, or at module level with top-level await.
allowed = [
"async def f():\n [await x for x in y]",
"async def f():\n {await x for x in y}",
"async def f():\n {k: await x for k, x in y}",
"async def f():\n [x async for x in y]",
"async def f():\n {x async for x in y}",
"async def f():\n {k: x async for k, x in y}",
"async def f():\n [[await x for x in y] for y in z]",
# Defaults and bases are evaluated in the enclosing scope.
"async def outer():\n async def f(x=[await y for y in z]): pass",
"async def f():\n class C([await x for x in y]): pass",
]
for code in allowed:
with self.subTest(code=code):
compile(code, "<test>", "exec")

# Generator expressions with await are async genexps and may appear
# outside async functions. A listcomp nested in a genexp is also
# allowed (the genexp becomes an async generator).
for code in [
"(await x for x in y)",
"def f():\n (await x for x in y)",
"class C:\n (await x for x in y)",
"lambda: (await x for x in y)",
"([await x for x in y] for y in z)",
"def f():\n ([await x for x in y] for y in z)",
"class C:\n ([await x for x in y] for y in z)",
"async def f():\n ([await x for x in y] for y in z)",
]:
with self.subTest(code=code):
compile(code, "<test>", "exec")

err = "asynchronous comprehension outside of an asynchronous function"
invalid = [
"[await x for x in y]",
"{await x for x in y}",
"{k: await x for k, x in y}",
"[x async for x in y]",
"{x async for x in y}",
"{k: x async for k, x in y}",
"[[await x for x in y] for y in z]",
"[[x async for x in y] for y in z]",
"def f():\n [await x for x in y]",
"def f():\n [x async for x in y]",
"async def f():\n def g():\n [await x for x in y]",
"class C:\n [await x for x in y]",
"class C:\n {await x for x in y}",
"class C:\n {k: await x for k, x in y}",
"class C:\n [x async for x in y]",
"class C:\n [[await x for x in y] for y in z]",
"async def f():\n class C:\n x = [await y for y in z]",
"async def f():\n class C:\n x = [y async for y in z]",
# Lambdas are never async, even inside async def.
"lambda: [await x for x in y]",
"async def f():\n lambda: [await x for x in y]",
"class C:\n f = lambda: [await x for x in y]",
# Defaults and bases run in the enclosing scope.
"async def f(x=[await y for y in z]): pass",
"def f(x=[await y for y in z]): pass",
"class C:\n def f(self, x=[await y for y in z]): pass",
"class C([await x for x in y]): pass",
# Type aliases and type-parameter scopes.
"type T = [await x for x in y]",
"type T = [x async for x in y]",
"def f[T=[await x for x in y]](): pass",
"def f[T: [await x for x in y]](): pass",
"async def f[T=[await x for x in y]](): pass",
"async def f(x: [await y for y in z]): pass",
]
for code in invalid:
with self.subTest(code=code):
support.check_syntax_error(self, code, err)

support.check_syntax_error(
self, "await x", "'await' outside function")
support.check_syntax_error(
self, "class C:\n await x", "'await' outside function")
support.check_syntax_error(
self, "def f():\n await x", "'await' outside async function")

flags = ast.PyCF_ALLOW_TOP_LEVEL_AWAIT
for code in [
"[await x for x in y]",
"async def f(x=[await y for y in z]): pass",
"class C([await x for x in y]): pass",
"f'{[await x for x in y]}'",
]:
with self.subTest(code=code, tla=True):
compile(code, "<test>", "exec", flags=flags)
still_invalid = [
"lambda: [await x for x in y]",
"class C:\n def f(self, x=[await y for y in z]): pass",
"type T = [await x for x in y]",
"async def f[T=[await x for x in y]](): pass",
]
for code in still_invalid:
with self.subTest(code=code, tla=True):
with self.assertRaisesRegex(SyntaxError, err):
compile(code, "<test>", "exec", flags=flags)

def test_badsyntax_2(self):
samples = [
"""def foo():
Expand Down
Loading