From 042aec8db97cc4dcaaacc69a5276e571c65b992e Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Wed, 21 Sep 2022 19:16:51 +0100 Subject: [PATCH 1/6] Make asyncio.Task covariant --- stdlib/asyncio/tasks.pyi | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/stdlib/asyncio/tasks.pyi b/stdlib/asyncio/tasks.pyi index 76755f1109c3..9c4607c4a20f 100644 --- a/stdlib/asyncio/tasks.pyi +++ b/stdlib/asyncio/tasks.pyi @@ -36,6 +36,7 @@ __all__ = ( ) _T = TypeVar("_T") +_T_co = TypeVar("_T_co", covariant=True) _T1 = TypeVar("_T1") _T2 = TypeVar("_T2") _T3 = TypeVar("_T3") @@ -265,7 +266,7 @@ else: ) -> tuple[set[Task[_T]], set[Task[_T]]]: ... async def wait_for(fut: _FutureLike[_T], timeout: float | None, *, loop: AbstractEventLoop | None = ...) -> _T: ... -class Task(Future[_T], Generic[_T]): +class Task(Future[_T_co], Generic[_T_co]): if sys.version_info >= (3, 8): def __init__( self, From f1ae4fa1755bee9a77127c67f3ca6084d241c036 Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Wed, 21 Sep 2022 19:17:49 +0100 Subject: [PATCH 2/6] Update futures.pyi --- stdlib/asyncio/futures.pyi | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/stdlib/asyncio/futures.pyi b/stdlib/asyncio/futures.pyi index f917bd5dee98..03eb2d7eb922 100644 --- a/stdlib/asyncio/futures.pyi +++ b/stdlib/asyncio/futures.pyi @@ -23,13 +23,14 @@ else: __all__ = ("CancelledError", "TimeoutError", "InvalidStateError", "Future", "wrap_future", "isfuture") _T = TypeVar("_T") +_T_co = TypeVar("_T_co", covariant=True) # asyncio defines 'isfuture()' in base_futures.py and re-imports it in futures.py # but it leads to circular import error in pytype tool. # That's why the import order is reversed. def isfuture(obj: object) -> TypeGuard[Future[Any]]: ... -class Future(Awaitable[_T], Iterable[_T]): +class Future(Awaitable[_T_co], Iterable[_T_co]): _state: str @property def _exception(self) -> BaseException: ... From 7debf072913250023fe2e2b43a2d04842df44fa5 Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Wed, 21 Sep 2022 19:21:02 +0100 Subject: [PATCH 3/6] Update futures.pyi --- stdlib/asyncio/futures.pyi | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/stdlib/asyncio/futures.pyi b/stdlib/asyncio/futures.pyi index 03eb2d7eb922..cc0cd2568874 100644 --- a/stdlib/asyncio/futures.pyi +++ b/stdlib/asyncio/futures.pyi @@ -53,13 +53,13 @@ class Future(Awaitable[_T_co], Iterable[_T_co]): def cancelled(self) -> bool: ... def done(self) -> bool: ... - def result(self) -> _T: ... + def result(self) -> _T_co: ... def exception(self) -> BaseException | None: ... def remove_done_callback(self: Self, __fn: Callable[[Self], object]) -> int: ... - def set_result(self, __result: _T) -> None: ... + def set_result(self, __result: _T_co) -> None: ... def set_exception(self, __exception: type | BaseException) -> None: ... - def __iter__(self) -> Generator[Any, None, _T]: ... - def __await__(self) -> Generator[Any, None, _T]: ... + def __iter__(self) -> Generator[Any, None, _T_co]: ... + def __await__(self) -> Generator[Any, None, _T_co]: ... @property def _loop(self) -> AbstractEventLoop: ... if sys.version_info >= (3, 9): From d4c053ecad4092d33088a55d8b2449e04e2c82d8 Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Thu, 22 Sep 2022 17:44:47 +0100 Subject: [PATCH 4/6] Revert future --- stdlib/asyncio/futures.pyi | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/stdlib/asyncio/futures.pyi b/stdlib/asyncio/futures.pyi index cc0cd2568874..f917bd5dee98 100644 --- a/stdlib/asyncio/futures.pyi +++ b/stdlib/asyncio/futures.pyi @@ -23,14 +23,13 @@ else: __all__ = ("CancelledError", "TimeoutError", "InvalidStateError", "Future", "wrap_future", "isfuture") _T = TypeVar("_T") -_T_co = TypeVar("_T_co", covariant=True) # asyncio defines 'isfuture()' in base_futures.py and re-imports it in futures.py # but it leads to circular import error in pytype tool. # That's why the import order is reversed. def isfuture(obj: object) -> TypeGuard[Future[Any]]: ... -class Future(Awaitable[_T_co], Iterable[_T_co]): +class Future(Awaitable[_T], Iterable[_T]): _state: str @property def _exception(self) -> BaseException: ... @@ -53,13 +52,13 @@ class Future(Awaitable[_T_co], Iterable[_T_co]): def cancelled(self) -> bool: ... def done(self) -> bool: ... - def result(self) -> _T_co: ... + def result(self) -> _T: ... def exception(self) -> BaseException | None: ... def remove_done_callback(self: Self, __fn: Callable[[Self], object]) -> int: ... - def set_result(self, __result: _T_co) -> None: ... + def set_result(self, __result: _T) -> None: ... def set_exception(self, __exception: type | BaseException) -> None: ... - def __iter__(self) -> Generator[Any, None, _T_co]: ... - def __await__(self) -> Generator[Any, None, _T_co]: ... + def __iter__(self) -> Generator[Any, None, _T]: ... + def __await__(self) -> Generator[Any, None, _T]: ... @property def _loop(self) -> AbstractEventLoop: ... if sys.version_info >= (3, 9): From 7eb90536178d9b32de0237d0af5c2daf18c8ec83 Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Thu, 22 Sep 2022 17:45:45 +0100 Subject: [PATCH 5/6] Update tasks.pyi --- stdlib/asyncio/tasks.pyi | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/stdlib/asyncio/tasks.pyi b/stdlib/asyncio/tasks.pyi index 9c4607c4a20f..ce077dca6160 100644 --- a/stdlib/asyncio/tasks.pyi +++ b/stdlib/asyncio/tasks.pyi @@ -270,17 +270,17 @@ class Task(Future[_T_co], Generic[_T_co]): if sys.version_info >= (3, 8): def __init__( self, - coro: Generator[_TaskYieldType, None, _T] | Awaitable[_T], + coro: Generator[_TaskYieldType, None, _T_co] | Awaitable[_T_co], *, loop: AbstractEventLoop = ..., name: str | None = ..., ) -> None: ... else: def __init__( - self, coro: Generator[_TaskYieldType, None, _T] | Awaitable[_T], *, loop: AbstractEventLoop = ... + self, coro: Generator[_TaskYieldType, None, _T_co] | Awaitable[_T_co], *, loop: AbstractEventLoop = ... ) -> None: ... if sys.version_info >= (3, 8): - def get_coro(self) -> Generator[_TaskYieldType, None, _T] | Awaitable[_T]: ... + def get_coro(self) -> Generator[_TaskYieldType, None, _T_co] | Awaitable[_T_co]: ... def get_name(self) -> str: ... def set_name(self, __value: object) -> None: ... From 7b230fa169823e5127cbb2f4070e3fec9f651ba0 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Wed, 28 Sep 2022 14:06:22 +0100 Subject: [PATCH 6/6] Add pyright ignore --- stdlib/asyncio/tasks.pyi | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/stdlib/asyncio/tasks.pyi b/stdlib/asyncio/tasks.pyi index ce077dca6160..27d80be8d8f5 100644 --- a/stdlib/asyncio/tasks.pyi +++ b/stdlib/asyncio/tasks.pyi @@ -266,7 +266,11 @@ else: ) -> tuple[set[Task[_T]], set[Task[_T]]]: ... async def wait_for(fut: _FutureLike[_T], timeout: float | None, *, loop: AbstractEventLoop | None = ...) -> _T: ... -class Task(Future[_T_co], Generic[_T_co]): +# pyright complains that a subclass of an invariant class shouldn't be covariant. +# While this is true in general, here it's sort-of okay to have a covariant subclass, +# since the only reason why `asyncio.Future` is invariant is the `set_result()` method, +# and `asyncio.Task.set_result()` always raises. +class Task(Future[_T_co], Generic[_T_co]): # pyright: ignore[reportGeneralTypeIssues] if sys.version_info >= (3, 8): def __init__( self,