Skip to content

Commit fa9921a

Browse files
authored
semanal: Check that async for/with is inside an async function (#12418)
* semanal: Disallow async for/with in non-async functions * Fix invalid async syntax in test
1 parent 3751e38 commit fa9921a

File tree

4 files changed

+65
-2
lines changed

4 files changed

+65
-2
lines changed

mypy/message_registry.py

+3
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ def format(self, *args: object, **kwargs: object) -> "ErrorMessage":
6161
)
6262
INCOMPATIBLE_TYPES_IN_ASYNC_FOR: Final = 'Incompatible types in "async for"'
6363

64+
ASYNC_FOR_OUTSIDE_COROUTINE: Final = '"async for" outside async function'
65+
ASYNC_WITH_OUTSIDE_COROUTINE: Final = '"async with" outside async function'
66+
6467
INCOMPATIBLE_TYPES_IN_YIELD: Final = ErrorMessage('Incompatible types in "yield"')
6568
INCOMPATIBLE_TYPES_IN_YIELD_FROM: Final = ErrorMessage('Incompatible types in "yield from"')
6669
INCOMPATIBLE_TYPES_IN_STR_INTERPOLATION: Final = "Incompatible types in string interpolation"

mypy/semanal.py

+20
Original file line numberDiff line numberDiff line change
@@ -3621,6 +3621,10 @@ def visit_while_stmt(self, s: WhileStmt) -> None:
36213621
self.visit_block_maybe(s.else_body)
36223622

36233623
def visit_for_stmt(self, s: ForStmt) -> None:
3624+
if s.is_async:
3625+
if not self.is_func_scope() or not self.function_stack[-1].is_coroutine:
3626+
self.fail(message_registry.ASYNC_FOR_OUTSIDE_COROUTINE, s, code=codes.SYNTAX)
3627+
36243628
self.statement = s
36253629
s.expr.accept(self)
36263630

@@ -3680,6 +3684,10 @@ def visit_with_stmt(self, s: WithStmt) -> None:
36803684
self.statement = s
36813685
types: List[Type] = []
36823686

3687+
if s.is_async:
3688+
if not self.is_func_scope() or not self.function_stack[-1].is_coroutine:
3689+
self.fail(message_registry.ASYNC_WITH_OUTSIDE_COROUTINE, s, code=codes.SYNTAX)
3690+
36833691
if s.unanalyzed_type:
36843692
assert isinstance(s.unanalyzed_type, ProperType)
36853693
actual_targets = [t for t in s.target if t is not None]
@@ -4175,12 +4183,24 @@ def visit_type_application(self, expr: TypeApplication) -> None:
41754183
expr.types[i] = analyzed
41764184

41774185
def visit_list_comprehension(self, expr: ListComprehension) -> None:
4186+
if any(expr.generator.is_async):
4187+
if not self.is_func_scope() or not self.function_stack[-1].is_coroutine:
4188+
self.fail(message_registry.ASYNC_FOR_OUTSIDE_COROUTINE, expr, code=codes.SYNTAX)
4189+
41784190
expr.generator.accept(self)
41794191

41804192
def visit_set_comprehension(self, expr: SetComprehension) -> None:
4193+
if any(expr.generator.is_async):
4194+
if not self.is_func_scope() or not self.function_stack[-1].is_coroutine:
4195+
self.fail(message_registry.ASYNC_FOR_OUTSIDE_COROUTINE, expr, code=codes.SYNTAX)
4196+
41814197
expr.generator.accept(self)
41824198

41834199
def visit_dictionary_comprehension(self, expr: DictionaryComprehension) -> None:
4200+
if any(expr.is_async):
4201+
if not self.is_func_scope() or not self.function_stack[-1].is_coroutine:
4202+
self.fail(message_registry.ASYNC_FOR_OUTSIDE_COROUTINE, expr, code=codes.SYNTAX)
4203+
41844204
with self.enter(expr):
41854205
self.analyze_comp_for(expr)
41864206
expr.key.accept(self)

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

+39
Original file line numberDiff line numberDiff line change
@@ -825,3 +825,42 @@ def bar() -> None:
825825

826826
[builtins fixtures/async_await.pyi]
827827
[typing fixtures/typing-async.pyi]
828+
829+
[case testAsyncForOutsideCoroutine]
830+
# flags: --python-version 3.7
831+
832+
async def g():
833+
yield 0
834+
835+
def f() -> None:
836+
[x async for x in g()] # E: "async for" outside async function
837+
{x async for x in g()} # E: "async for" outside async function
838+
{x: True async for x in g()} # E: "async for" outside async function
839+
(x async for x in g())
840+
async for x in g(): ... # E: "async for" outside async function
841+
842+
[x async for x in g()] # E: "async for" outside async function
843+
{x async for x in g()} # E: "async for" outside async function
844+
{x: True async for x in g()} # E: "async for" outside async function
845+
(x async for x in g())
846+
async for x in g(): ... # E: "async for" outside async function
847+
848+
[builtins fixtures/async_await.pyi]
849+
[typing fixtures/typing-async.pyi]
850+
851+
[case testAsyncWithOutsideCoroutine]
852+
# flags: --python-version 3.7
853+
854+
class C:
855+
async def __aenter__(self): pass
856+
async def __aexit__(self, x, y, z): pass
857+
858+
def f() -> None:
859+
async with C() as x: # E: "async with" outside async function
860+
pass
861+
862+
async with C() as x: # E: "async with" outside async function
863+
pass
864+
865+
[builtins fixtures/async_await.pyi]
866+
[typing fixtures/typing-async.pyi]

test-data/unit/check-default-plugin.test

+3-2
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,9 @@ async def yield_id(item: T) -> AsyncGenerator[T, None]:
3737

3838
reveal_type(yield_id) # N: Revealed type is "def [T] (item: T`-1) -> typing.AsyncContextManager[T`-1]"
3939

40-
async with yield_id(1) as x:
41-
reveal_type(x) # N: Revealed type is "builtins.int*"
40+
async def f() -> None:
41+
async with yield_id(1) as x:
42+
reveal_type(x) # N: Revealed type is "builtins.int*"
4243
[typing fixtures/typing-async.pyi]
4344
[builtins fixtures/tuple.pyi]
4445

0 commit comments

Comments
 (0)