Repository: python/mypy

Issue:
mypy does not complain about async functions that do not return values
Found as part of https://github.com/python/mypy/pull/12834

In the following, you'd expect mypy to complain about assigning to `await async_nothing`, like it does with the synchronous case. Note that mypy does in fact complain about this using the old asyncio.coroutine yield from coroutines, see `testErrorAssigningCoroutineThatDontReturn` (prior to #12834).
```
def nothing() -> None:
    ...

def foo() -> None:
    x = nothing()  # E: "nothing" does not return a value

async def async_nothing() -> None:
    ...

async def async_foo() -> None:
    x = await async_nothing()
```


Discussion:

Resolving patch:
diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py
--- a/mypy/checkexpr.py
+++ b/mypy/checkexpr.py
@@ -3987,6 +3987,8 @@ def accept(self,
                 typ = self.visit_yield_from_expr(node, allow_none_return=True)
             elif allow_none_return and isinstance(node, ConditionalExpr):
                 typ = self.visit_conditional_expr(node, allow_none_return=True)
+            elif allow_none_return and isinstance(node, AwaitExpr):
+                typ = self.visit_await_expr(node, allow_none_return=True)
             else:
                 typ = node.accept(self)
         except Exception as err:
@@ -4099,15 +4101,18 @@ def visit_yield_expr(self, e: YieldExpr) -> Type:
                                    'actual type', 'expected type')
         return self.chk.get_generator_receive_type(return_type, False)
 
-    def visit_await_expr(self, e: AwaitExpr) -> Type:
+    def visit_await_expr(self, e: AwaitExpr, allow_none_return: bool = False) -> Type:
         expected_type = self.type_context[-1]
         if expected_type is not None:
             expected_type = self.chk.named_generic_type('typing.Awaitable', [expected_type])
         actual_type = get_proper_type(self.accept(e.expr, expected_type))
         if isinstance(actual_type, AnyType):
             return AnyType(TypeOfAny.from_another_any, source_any=actual_type)
-        return self.check_awaitable_expr(actual_type, e,
-                                         message_registry.INCOMPATIBLE_TYPES_IN_AWAIT)
+        ret = self.check_awaitable_expr(actual_type, e,
+                                        message_registry.INCOMPATIBLE_TYPES_IN_AWAIT)
+        if not allow_none_return and isinstance(get_proper_type(ret), NoneType):
+            self.chk.msg.does_not_return_value(None, e)
+        return ret
 
     def check_awaitable_expr(self, t: Type, ctx: Context, msg: Union[str, ErrorMessage]) -> Type:
         """Check the argument to `await` and extract the type of value.


Test patch:
diff --git a/test-data/unit/pythoneval-asyncio.test b/test-data/unit/pythoneval-asyncio.test
--- a/test-data/unit/pythoneval-asyncio.test
+++ b/test-data/unit/pythoneval-asyncio.test
@@ -242,8 +242,7 @@ Outside 42
 
 -- Errors
 
-[case testErrorAssigningCoroutineThatDontReturn-xfail]
-# https://github.com/python/mypy/issues/12837
+[case testErrorAssigningCoroutineThatDontReturn]
 from typing import Any
 import asyncio
 from asyncio import Future
@@ -262,7 +261,7 @@ try:
 finally:
     loop.close()
 [out]
-_program.py:13: error: Function does not return a value
+_program.py:11: error: Function does not return a value
 
 [case testErrorReturnIsNotTheSameType]
 from typing import Any