Repository: python/mypy (Python)

Issue:
async for/async with are allowed in non-async functions
**To Reproduce**

```
async def b():
 for i in range(5):
  yield i

def a():
 return [x async for x in b()]
```

**Expected Behavior**

Syntax error. When trying to run with python this code gives a syntax error.

**Actual Behavior**

Passed with no errors.

**Your Environment**

- Mypy version used: 0.812
- Python version used: 3.8


Hints:
Yeah, more generally it looks like we don't enforce that async for/async with are only used in async functions.
I would like to take up this issue. It's just that I need a few doubts cleared. 

You're stating that the compiler throws an error stating async is not allowed outside an async function, but mypy doesn't do anything right? Does checking for async fall under static checking? (I am still new to the repository, sorry)
If so, can you point me to the scripts that have the relevant code for this? And also, some guidance will be appreciated, thanks!

@msullivan @Dreamsorcerer 


Patch:
diff --git a/mypy/message_registry.py b/mypy/message_registry.py
--- a/mypy/message_registry.py
+++ b/mypy/message_registry.py
@@ -61,6 +61,9 @@ def format(self, *args: object, **kwargs: object) -> "ErrorMessage":
 )
 INCOMPATIBLE_TYPES_IN_ASYNC_FOR: Final = 'Incompatible types in "async for"'
 
+ASYNC_FOR_OUTSIDE_COROUTINE: Final = '"async for" outside async function'
+ASYNC_WITH_OUTSIDE_COROUTINE: Final = '"async with" outside async function'
+
 INCOMPATIBLE_TYPES_IN_YIELD: Final = ErrorMessage('Incompatible types in "yield"')
 INCOMPATIBLE_TYPES_IN_YIELD_FROM: Final = ErrorMessage('Incompatible types in "yield from"')
 INCOMPATIBLE_TYPES_IN_STR_INTERPOLATION: Final = "Incompatible types in string interpolation"
diff --git a/mypy/semanal.py b/mypy/semanal.py
--- a/mypy/semanal.py
+++ b/mypy/semanal.py
@@ -3606,6 +3606,10 @@ def visit_while_stmt(self, s: WhileStmt) -> None:
         self.visit_block_maybe(s.else_body)
 
     def visit_for_stmt(self, s: ForStmt) -> None:
+        if s.is_async:
+            if not self.is_func_scope() or not self.function_stack[-1].is_coroutine:
+                self.fail(message_registry.ASYNC_FOR_OUTSIDE_COROUTINE, s, code=codes.SYNTAX)
+
         self.statement = s
         s.expr.accept(self)
 
@@ -3665,6 +3669,10 @@ def visit_with_stmt(self, s: WithStmt) -> None:
         self.statement = s
         types: List[Type] = []
 
+        if s.is_async:
+            if not self.is_func_scope() or not self.function_stack[-1].is_coroutine:
+                self.fail(message_registry.ASYNC_WITH_OUTSIDE_COROUTINE, s, code=codes.SYNTAX)
+
         if s.unanalyzed_type:
             assert isinstance(s.unanalyzed_type, ProperType)
             actual_targets = [t for t in s.target if t is not None]
@@ -4160,12 +4168,24 @@ def visit_type_application(self, expr: TypeApplication) -> None:
                 expr.types[i] = analyzed
 
     def visit_list_comprehension(self, expr: ListComprehension) -> None:
+        if any(expr.generator.is_async):
+            if not self.is_func_scope() or not self.function_stack[-1].is_coroutine:
+                self.fail(message_registry.ASYNC_FOR_OUTSIDE_COROUTINE, expr, code=codes.SYNTAX)
+
         expr.generator.accept(self)
 
     def visit_set_comprehension(self, expr: SetComprehension) -> None:
+        if any(expr.generator.is_async):
+            if not self.is_func_scope() or not self.function_stack[-1].is_coroutine:
+                self.fail(message_registry.ASYNC_FOR_OUTSIDE_COROUTINE, expr, code=codes.SYNTAX)
+
         expr.generator.accept(self)
 
     def visit_dictionary_comprehension(self, expr: DictionaryComprehension) -> None:
+        if any(expr.is_async):
+            if not self.is_func_scope() or not self.function_stack[-1].is_coroutine:
+                self.fail(message_registry.ASYNC_FOR_OUTSIDE_COROUTINE, expr, code=codes.SYNTAX)
+
         with self.enter(expr):
             self.analyze_comp_for(expr)
             expr.key.accept(self)


Test patch:
diff --git a/test-data/unit/check-async-await.test b/test-data/unit/check-async-await.test
--- a/test-data/unit/check-async-await.test
+++ b/test-data/unit/check-async-await.test
@@ -825,3 +825,42 @@ def bar() -> None:
 
 [builtins fixtures/async_await.pyi]
 [typing fixtures/typing-async.pyi]
+
+[case testAsyncForOutsideCoroutine]
+# flags: --python-version 3.7
+
+async def g():
+    yield 0
+
+def f() -> None:
+    [x async for x in g()] # E: "async for" outside async function
+    {x async for x in g()} # E: "async for" outside async function
+    {x: True async for x in g()} # E: "async for" outside async function
+    (x async for x in g())
+    async for x in g(): ... # E: "async for" outside async function
+
+[x async for x in g()] # E: "async for" outside async function
+{x async for x in g()} # E: "async for" outside async function
+{x: True async for x in g()} # E: "async for" outside async function
+(x async for x in g())
+async for x in g(): ... # E: "async for" outside async function
+
+[builtins fixtures/async_await.pyi]
+[typing fixtures/typing-async.pyi]
+
+[case testAsyncWithOutsideCoroutine]
+# flags: --python-version 3.7
+
+class C:
+    async def __aenter__(self): pass
+    async def __aexit__(self, x, y, z): pass
+
+def f() -> None:
+    async with C() as x: # E: "async with" outside async function
+        pass
+
+async with C() as x: # E: "async with" outside async function
+    pass
+
+[builtins fixtures/async_await.pyi]
+[typing fixtures/typing-async.pyi]
diff --git a/test-data/unit/check-default-plugin.test b/test-data/unit/check-default-plugin.test
--- a/test-data/unit/check-default-plugin.test
+++ b/test-data/unit/check-default-plugin.test
@@ -37,8 +37,9 @@ async def yield_id(item: T) -> AsyncGenerator[T, None]:
 
 reveal_type(yield_id) # N: Revealed type is "def [T] (item: T`-1) -> typing.AsyncContextManager[T`-1]"
 
-async with yield_id(1) as x:
-    reveal_type(x) # N: Revealed type is "builtins.int*"
+async def f() -> None:
+    async with yield_id(1) as x:
+        reveal_type(x) # N: Revealed type is "builtins.int*"
 [typing fixtures/typing-async.pyi]
 [builtins fixtures/tuple.pyi]