Skip to content

Commit e68ecef

Browse files
Infer Coroutine for unannotated async defs (#21651)
Fixes #12776 Add an extra check for unannotated async defs when checking function type. `async def foo()` is now treated like `async def foo() -> Any`
1 parent 7a45d25 commit e68ecef

6 files changed

Lines changed: 53 additions & 8 deletions

File tree

mypy/checker.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8129,7 +8129,20 @@ def iterable_item_type(self, it: ProperType, context: Context) -> Type:
81298129
return self.analyze_iterable_item_type_without_expression(it, context)[1]
81308130

81318131
def function_type(self, func: FuncBase) -> FunctionLike:
8132-
return function_type(func, self.named_type("builtins.function"))
8132+
typ = function_type(func, self.named_type("builtins.function"))
8133+
if (
8134+
isinstance(func, FuncItem)
8135+
and func.is_coroutine
8136+
and not func.is_async_generator
8137+
and func.type is None
8138+
and isinstance(typ, CallableType)
8139+
):
8140+
any_type = AnyType(TypeOfAny.special_form)
8141+
ret_type = self.named_generic_type(
8142+
"typing.Coroutine", [any_type, any_type, typ.ret_type]
8143+
)
8144+
return typ.copy_modified(ret_type=ret_type)
8145+
return typ
81338146

81348147
def push_type_map(self, type_map: TypeMap, *, from_assignment: bool = True) -> None:
81358148
if is_unreachable_map(type_map):

mypy/checker_shared.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
ArgKind,
1717
Context,
1818
Expression,
19+
FuncBase,
1920
FuncItem,
2021
LambdaExpr,
2122
MypyFile,
@@ -28,6 +29,7 @@
2829
from mypy.plugin import CheckerPluginInterface, Plugin
2930
from mypy.types import (
3031
CallableType,
32+
FunctionLike,
3133
Instance,
3234
LiteralValue,
3335
Overloaded,
@@ -149,6 +151,10 @@ def expr_checker(self) -> ExpressionCheckerSharedApi:
149151
def named_type(self, name: str) -> Instance:
150152
raise NotImplementedError
151153

154+
@abstractmethod
155+
def function_type(self, func: FuncBase) -> FunctionLike:
156+
raise NotImplementedError
157+
152158
@abstractmethod
153159
def lookup_typeinfo(self, fullname: str) -> TypeInfo:
154160
raise NotImplementedError

mypy/checkexpr.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,6 @@
150150
false_only,
151151
fixup_partial_type,
152152
freeze_all_type_vars,
153-
function_type,
154153
get_all_type_vars,
155154
get_type_vars,
156155
is_literal_type_like,
@@ -415,7 +414,7 @@ def analyze_static_reference(
415414
if isinstance(node, (Var, Decorator, OverloadedFuncDef)):
416415
return node.type or AnyType(TypeOfAny.special_form)
417416
elif isinstance(node, FuncDef):
418-
return function_type(node, self.named_type("builtins.function"))
417+
return self.chk.function_type(node)
419418
elif isinstance(node, TypeInfo):
420419
# Reference to a type object.
421420
if node.typeddict_type:

mypy/checkmember.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@
4747
bind_self,
4848
erase_to_bound,
4949
freeze_all_type_vars,
50-
function_type,
5150
get_all_type_vars,
5251
make_simplified_union,
5352
supported_self_type,
@@ -360,7 +359,7 @@ def analyze_instance_member_access(
360359
if mx.is_lvalue and not mx.suppress_errors:
361360
mx.msg.cant_assign_to_method(mx.context)
362361
if not isinstance(method, OverloadedFuncDef):
363-
signature = function_type(method, mx.named_type("builtins.function"))
362+
signature = mx.chk.function_type(method)
364363
else:
365364
if method.type is None:
366365
# Overloads may be not ready if they are decorated. Handle this in same
@@ -1325,7 +1324,7 @@ def analyze_class_attribute_access(
13251324
return AnyType(TypeOfAny.from_error)
13261325
else:
13271326
assert isinstance(node.node, SYMBOL_FUNCBASE_TYPES)
1328-
typ = function_type(node.node, mx.named_type("builtins.function"))
1327+
typ = mx.chk.function_type(node.node)
13291328
# Note: if we are accessing class method on class object, the cls argument is bound.
13301329
# Annotated and/or explicit class methods go through other code paths above, for
13311330
# unannotated implicit class methods we do this here.
@@ -1490,7 +1489,7 @@ def analyze_decorator_or_funcbase_access(
14901489
"""
14911490
if isinstance(defn, Decorator):
14921491
return analyze_var(name, defn.var, itype, mx)
1493-
typ = function_type(defn, mx.chk.named_type("builtins.function"))
1492+
typ = mx.chk.function_type(defn)
14941493
if isinstance(defn, (FuncDef, OverloadedFuncDef)) and defn.is_trivial_self:
14951494
return bind_self_fast(typ, mx.self_type)
14961495
typ = check_self_arg(typ, mx.self_type, defn.is_class, mx.context, name, mx.msg)

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -839,6 +839,34 @@ def bar() -> None:
839839
[builtins fixtures/async_await.pyi]
840840
[typing fixtures/typing-async.pyi]
841841

842+
[case testUntypedAsyncFunctionAndMethodReturnCoroutine]
843+
# flags: --show-error-codes
844+
async def foo():
845+
pass
846+
847+
class C:
848+
async def method(self):
849+
pass
850+
851+
def f(c: C) -> None:
852+
a = foo()
853+
b = c.method()
854+
reveal_type(foo) # N: Revealed type is "def () -> typing.Coroutine[Any, Any, Any]"
855+
reveal_type(a) # E: Value of type "Coroutine[Any, Any, Any]" must be used [unused-coroutine] \
856+
# N: Are you missing an await? \
857+
# N: Revealed type is "typing.Coroutine[Any, Any, Any]"
858+
foo() # E: Value of type "Coroutine[Any, Any, Any]" must be used [unused-coroutine] \
859+
# N: Are you missing an await?
860+
reveal_type(c.method) # N: Revealed type is "def () -> typing.Coroutine[Any, Any, Any]"
861+
reveal_type(b) # E: Value of type "Coroutine[Any, Any, Any]" must be used [unused-coroutine] \
862+
# N: Are you missing an await? \
863+
# N: Revealed type is "typing.Coroutine[Any, Any, Any]"
864+
c.method() # E: Value of type "Coroutine[Any, Any, Any]" must be used [unused-coroutine] \
865+
# N: Are you missing an await?
866+
867+
[builtins fixtures/async_await.pyi]
868+
[typing fixtures/typing-async.pyi]
869+
842870
[case testAsyncForOutsideCoroutine]
843871
async def g():
844872
yield 0

test-data/unit/check-flags.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ async def f(): # E: Function is missing a return type annotation \
5959
# N: Use "-> None" if function does not return a value
6060
pass
6161
[builtins fixtures/async_await.pyi]
62-
[typing fixtures/typing-medium.pyi]
62+
[typing fixtures/typing-async.pyi]
6363

6464
[case testAsyncUnannotatedArgument]
6565
# flags: --disallow-untyped-defs

0 commit comments

Comments
 (0)