From 7b80a92ab55542af0bad83978a295ee7d97a6b99 Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Wed, 26 Aug 2026 12:26:48 +0100 Subject: [PATCH 1/2] Add tests for async comprehensions in class and module scopes. --- Lib/test/test_builtin.py | 15 +++++++++- Lib/test/test_coroutines.py | 58 +++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index 1d2c105ac047e1..fa4cdd3784f168 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -542,6 +542,9 @@ 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]''', # gh-121637: Make sure we correctly handle the case where the # async code is optimized away '''assert not await sleep(0); a = 1''', @@ -610,7 +613,17 @@ 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] + ''', ] for mode, code_sample in product(modes, code_samples): source = dedent(code_sample) diff --git a/Lib/test/test_coroutines.py b/Lib/test/test_coroutines.py index 9d415238876c8f..b29c1c97382999 100644 --- a/Lib/test/test_coroutines.py +++ b/Lib/test/test_coroutines.py @@ -407,6 +407,64 @@ async def bar(): with self.subTest(code=code), self.assertRaises(SyntaxError): compile(code, "", "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]", + ] + for code in allowed: + with self.subTest(code=code): + compile(code, "", "exec") + + # Generator expressions with await are async genexps and may appear + # outside async functions. + 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)", + ]: + with self.subTest(code=code): + compile(code, "", "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]", + ] + 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") + def test_badsyntax_2(self): samples = [ """def foo(): From 52882e4e5b1c4ef56f58c17395427d26576081bb Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Wed, 26 Aug 2026 12:58:55 +0100 Subject: [PATCH 2/2] more tests --- Lib/test/test_builtin.py | 10 ++++++++ Lib/test/test_coroutines.py | 48 ++++++++++++++++++++++++++++++++++++- 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index fa4cdd3784f168..294355698c8683 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -545,6 +545,7 @@ async def sleep(delay, result=None): '''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''', @@ -624,6 +625,15 @@ async def __aexit__(self, *exc_info): 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) diff --git a/Lib/test/test_coroutines.py b/Lib/test/test_coroutines.py index b29c1c97382999..91606f10f0463d 100644 --- a/Lib/test/test_coroutines.py +++ b/Lib/test/test_coroutines.py @@ -1,3 +1,4 @@ +import ast import contextlib import copy import inspect @@ -418,17 +419,26 @@ def test_async_comprehension_scope(self): "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, "", "exec") # Generator expressions with await are async genexps and may appear - # outside async functions. + # 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, "", "exec") @@ -453,6 +463,22 @@ def test_async_comprehension_scope(self): "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): @@ -465,6 +491,26 @@ def test_async_comprehension_scope(self): 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, "", "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, "", "exec", flags=flags) + def test_badsyntax_2(self): samples = [ """def foo():