https://github.com/python/cpython/commit/aa7084e013244628430b72b4108d5c3e4a098123
commit: aa7084e013244628430b72b4108d5c3e4a098123
branch: 3.14
author: Miss Islington (bot) <[email protected]>
committer: iritkatriel <[email protected]>
date: 2026-09-01T18:18:19Z
summary:

[3.14] Add tests for async comprehensions in different scopes (GH-156412) 
(#156795)

Add tests for async comprehensions in different scopes (GH-156412)
(cherry picked from commit 42de93a62e711b695439c818a6a7f41eb2761c45)

Co-authored-by: Irit Katriel <[email protected]>

files:
M Lib/test/test_builtin.py
M Lib/test/test_coroutines.py

diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py
index 54043eac2909169..6166eee8b92596e 100644
--- a/Lib/test/test_builtin.py
+++ b/Lib/test/test_builtin.py
@@ -527,6 +527,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''',
@@ -595,7 +599,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)
diff --git a/Lib/test/test_coroutines.py b/Lib/test/test_coroutines.py
index a515e0f5ca9b5f4..4f306a820d770d5 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
@@ -407,6 +408,118 @@ 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, bases, and genexp iterables 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",
+            "async def f():\n    (x for x in [await y for y in z])",
+            "async def f():\n    (x for x in [y async for y in z])",
+        ]
+        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 body 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, bases, and genexp iterables run in the enclosing scope.
+            "(x for x in [await y for y in z])",
+            "(x for x in [y async for y in z])",
+            "def f():\n    (x for x in [await y for y in z])",
+            "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]}'",
+            "(x for x in [await y for y in z])",
+            "(x for x in [y async for y in z])",
+        ]:
+            with self.subTest(code=code, tla=True):
+                compile(code, "<test>", "exec", flags=flags)
+        still_invalid = [
+            "lambda: [await x for x in y]",
+            "def f():\n    (x for x in [await y for y in z])",
+            "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():

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to