The MRE below is taken from this mypy issue. That issue and POC mypy
implementation can provide more context into this question.
Consider the following:
from contextlib import asynccontextmanager, contextmanager
from typing import AsyncIterator, Iterator, overload
@overload
@asynccontextmanager # Error here
async def test_async() -> AsyncIterator[str]: ...
@overload
@asynccontextmanager # Error here
async def test_async(val: int) -> AsyncIterator[int]: ...
@asynccontextmanager
async def test_async(val: int = 1) -> AsyncIterator[int | str]:
yield val if val != 1 else 'a'
@overload
@contextmanager
def test_sync() -> Iterator[str]: ...
@overload
@contextmanager
def test_sync(val: int) -> Iterator[int]: ...
@contextmanager
def test_sync(val: int = 1) -> Iterator[int | str]:
yield val if val != 1 else 'a'
This happens because async
functions return types are wrapped with Coroutine
unless these functions are generators, and this condition is detected by yield
presence in the body. overload
definitions have usually only trivial bodies (ellipsis or pass
) and do not contain yield
. I’d argue that “generator-ness” should be propagated from the implementation to the overloads, marking overloads as async generators if the implementation is, because other ways to make this (correct) snippet pass are counterintuitive (either removing the decorator and constructing the type manually or adding yield
to overloads bodies, I don’t see any other reasonable approach)
Type checkers survey:
mypy
: wraps withCoroutine
, error.Argument 1 to "asynccontextmanager" has incompatible type "Callable[[], Coroutine[Any, Any, AsyncIterator[int]]]"; expected "Callable[[], AsyncIterator[Never]]" [arg-type]
pyright
: same asmypy
Argument of type "() -> Coroutine[Any, Any, AsyncIterator[str]]" cannot be assigned to parameter "func" of type "(**_P@asynccontextmanager) -> AsyncIterator[_T_co@asynccontextmanager]" in function "asynccontextmanager"
pyre
: accepts, produces expected reveal_typepytype
: rejects, but also rejects trivial bodies oftest_sync
.Function contextlib.asynccontextmanager was called with the wrong arguments [wrong-arg-types]
This issue is not currently addressed in typing specification. @hauntsaninja advised to start a topic here to discuss this potential change and bring it to sync with other typecheckers if agreed upon.