From 384fcbd2a1e2ed49d4aa6c2818afd3e0502405de Mon Sep 17 00:00:00 2001 From: jw9829 <245427686+jw9829@users.noreply.github.com> Date: Sun, 27 Sep 2026 21:43:54 -0400 Subject: [PATCH] Preserve Task types in as_completed --- .../test_cases/asyncio/check_as_completed.py | 29 +++++++++++++++++++ stdlib/asyncio/tasks.pyi | 13 +++++++-- 2 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 stdlib/@tests/test_cases/asyncio/check_as_completed.py diff --git a/stdlib/@tests/test_cases/asyncio/check_as_completed.py b/stdlib/@tests/test_cases/asyncio/check_as_completed.py new file mode 100644 index 000000000000..92319a2a84b2 --- /dev/null +++ b/stdlib/@tests/test_cases/asyncio/check_as_completed.py @@ -0,0 +1,29 @@ +from __future__ import annotations + +import asyncio +import sys +from collections.abc import Coroutine +from typing import Any +from typing_extensions import assert_type + + +async def result() -> int: + return 42 + + +if sys.version_info >= (3, 13): + + async def check_as_completed() -> None: + task = asyncio.create_task(result()) + + async for completed_task in asyncio.as_completed([task]): + assert_type(completed_task, asyncio.Task[int]) + completed_task.cancelling() + + coroutine_for_async_iteration = result() + async for completed_future in asyncio.as_completed([coroutine_for_async_iteration]): + assert_type(completed_future, asyncio.Future[int]) + + for completed_coroutine in asyncio.as_completed([task]): + coroutine: Coroutine[Any, Any, int] = completed_coroutine + assert_type(await coroutine, int) diff --git a/stdlib/asyncio/tasks.pyi b/stdlib/asyncio/tasks.pyi index 66c31f15e6fb..67ac03304f10 100644 --- a/stdlib/asyncio/tasks.pyi +++ b/stdlib/asyncio/tasks.pyi @@ -67,6 +67,8 @@ else: _T = TypeVar("_T") _T_co = TypeVar("_T_co", covariant=True) +_T_sync_co = TypeVar("_T_sync_co", covariant=True) +_T_async_co = TypeVar("_T_async_co", covariant=True) _T1 = TypeVar("_T1") _T2 = TypeVar("_T2") _T3 = TypeVar("_T3") @@ -87,9 +89,16 @@ ALL_COMPLETED: Final = concurrent.futures.ALL_COMPLETED if sys.version_info >= (3, 13): @type_check_only - class _SyncAndAsyncIterator(Iterator[Coroutine[Any, Any, _T]], AsyncIterator[Future[_T]], Protocol[_T]): ... + class _SyncAndAsyncIterator(Iterator[_T_sync_co], AsyncIterator[_T_async_co], Protocol[_T_sync_co, _T_async_co]): ... - def as_completed(fs: Iterable[_FutureLike[_T]], *, timeout: float | None = None) -> _SyncAndAsyncIterator[_T]: ... + @overload + def as_completed( + fs: Iterable[Task[_T]], *, timeout: float | None = None + ) -> _SyncAndAsyncIterator[Coroutine[Any, Any, _T], Task[_T]]: ... + @overload + def as_completed( + fs: Iterable[_FutureLike[_T]], *, timeout: float | None = None + ) -> _SyncAndAsyncIterator[Coroutine[Any, Any, _T], Future[_T]]: ... else: def as_completed(fs: Iterable[_FutureLike[_T]], *, timeout: float | None = None) -> Iterator[Future[_T]]: ...