Skip to content

Commit 0c82fb9

Browse files
committed
fix: added await-outside-coroutine checks also to list and dict comprehensions
1 parent e7ef1f6 commit 0c82fb9

File tree

3 files changed

+35
-17
lines changed

3 files changed

+35
-17
lines changed

mypy/message_registry.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,8 @@ def with_additional_msg(self, info: str) -> ErrorMessage:
7979

8080
ASYNC_FOR_OUTSIDE_COROUTINE: Final = '"async for" outside async function'
8181
ASYNC_WITH_OUTSIDE_COROUTINE: Final = '"async with" outside async function'
82-
83-
AWAIT_WITH_OUTSIDE_COROUTINE: Final = '"await" outside coroutine ("async def")'
82+
AWAIT_OUTSIDE_FUNCTION: Final = '"await" outside function'
83+
AWAIT_OUTSIDE_COROUTINE: Final = '"await" outside coroutine ("async def")'
8484

8585
INCOMPATIBLE_TYPES_IN_YIELD: Final = ErrorMessage('Incompatible types in "yield"')
8686
INCOMPATIBLE_TYPES_IN_YIELD_FROM: Final = ErrorMessage('Incompatible types in "yield from"')

mypy/semanal.py

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6074,6 +6074,18 @@ def visit_type_application(self, expr: TypeApplication) -> None:
60746074
if analyzed is not None:
60756075
expr.types[i] = analyzed
60766076

6077+
def _check_await_outside_coroutine(self, expr: ListComprehension | SetComprehension | DictionaryComprehension) -> None:
6078+
if not has_await_expression(expr):
6079+
return
6080+
6081+
if not self.is_func_scope() or not self.function_stack[-1].is_coroutine:
6082+
self.fail(
6083+
message_registry.AWAIT_OUTSIDE_COROUTINE,
6084+
expr,
6085+
code=codes.AWAIT_NOT_ASYNC,
6086+
serious=True,
6087+
)
6088+
60776089
def visit_list_comprehension(self, expr: ListComprehension) -> None:
60786090
if any(expr.generator.is_async):
60796091
if not self.is_func_scope() or not self.function_stack[-1].is_coroutine:
@@ -6083,14 +6095,8 @@ def visit_list_comprehension(self, expr: ListComprehension) -> None:
60836095
code=codes.SYNTAX,
60846096
serious=True,
60856097
)
6086-
elif has_await_expression(expr):
6087-
if not self.is_func_scope() or not self.function_stack[-1].is_coroutine:
6088-
self.fail(
6089-
message_registry.AWAIT_WITH_OUTSIDE_COROUTINE,
6090-
expr,
6091-
code=codes.AWAIT_NOT_ASYNC,
6092-
serious=True,
6093-
)
6098+
6099+
self._check_await_outside_coroutine(expr)
60946100

60956101
expr.generator.accept(self)
60966102

@@ -6099,6 +6105,8 @@ def visit_set_comprehension(self, expr: SetComprehension) -> None:
60996105
if not self.is_func_scope() or not self.function_stack[-1].is_coroutine:
61006106
self.fail(message_registry.ASYNC_FOR_OUTSIDE_COROUTINE, expr, code=codes.SYNTAX)
61016107

6108+
self._check_await_outside_coroutine(expr)
6109+
61026110
expr.generator.accept(self)
61036111

61046112
def visit_dictionary_comprehension(self, expr: DictionaryComprehension) -> None:
@@ -6111,6 +6119,8 @@ def visit_dictionary_comprehension(self, expr: DictionaryComprehension) -> None:
61116119
serious=True,
61126120
)
61136121

6122+
self._check_await_outside_coroutine(expr)
6123+
61146124
with self.enter(expr):
61156125
self.analyze_comp_for(expr)
61166126
expr.key.accept(self)
@@ -6185,13 +6195,10 @@ def visit_await_expr(self, expr: AwaitExpr) -> None:
61856195
# We check both because is_function_scope() returns True inside comprehensions.
61866196
# This is not a blocker, because some enviroments (like ipython)
61876197
# support top level awaits.
6188-
self.fail('"await" outside function', expr, serious=True, code=codes.TOP_LEVEL_AWAIT)
6189-
elif (
6190-
not self.function_stack[-1].is_coroutine
6191-
and self.scope_stack[-1] != SCOPE_COMPREHENSION
6192-
):
6198+
self.fail(message_registry.AWAIT_OUTSIDE_FUNCTION, expr, serious=True, code=codes.TOP_LEVEL_AWAIT)
6199+
elif not self.function_stack[-1].is_coroutine and self.scope_stack[-1] != SCOPE_COMPREHENSION:
61936200
self.fail(
6194-
message_registry.AWAIT_WITH_OUTSIDE_COROUTINE,
6201+
message_registry.AWAIT_OUTSIDE_COROUTINE,
61956202
expr,
61966203
serious=True,
61976204
code=codes.AWAIT_NOT_ASYNC,

test-data/unit/check-async-await.test

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1085,12 +1085,23 @@ class Launcher(P):
10851085
[builtins fixtures/async_await.pyi]
10861086
[typing fixtures/typing-async.pyi]
10871087

1088-
[case testAwaitInsideGeneratorExpr]
1088+
[case testAwaitOutsideCoroutine]
10891089
def foo():
10901090
yield 0
10911091

10921092
def bar():
10931093
(await x for x in foo())
10941094

1095+
(await x for x in xs) # OK
1096+
[await x for x in xs] # E: "await" outside coroutine ("async def")
1097+
{await x for x in xs} # E: "await" outside coroutine ("async def")
1098+
{0: await x for x in xs} # E: "await" outside coroutine ("async def")
1099+
{await x: 0 for x in xs} # E: "await" outside coroutine ("async def")
1100+
1101+
(x for x in await xs) # E: "await" outside coroutine ("async def")
1102+
[x for x in await xs] # E: "await" outside coroutine ("async def")
1103+
{x for x in await xs} # E: "await" outside coroutine ("async def")
1104+
{x: 0 for x in await xs} # E: "await" outside coroutine ("async def")
1105+
10951106
[builtins fixtures/async_await.pyi]
10961107
[typing fixtures/typing-async.pyi]

0 commit comments

Comments
 (0)