mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-06 08:30:42 +00:00

## Summary - Add support for the return types of `async` functions - Add type inference for `await` expressions - Add support for `async with` / async context managers - Add support for `yield from` expressions This PR is generally lacking proper error handling in some cases (e.g. illegal `__await__` attributes). I'm planning to work on this in a follow-up. part of https://github.com/astral-sh/ty/issues/151 closes https://github.com/astral-sh/ty/issues/736 ## Ecosystem There are a lot of true positives on `prefect` which look similar to: ```diff prefect (https://github.com/PrefectHQ/prefect) + src/integrations/prefect-aws/tests/workers/test_ecs_worker.py:406:12: error[unresolved-attribute] Type `str` has no attribute `status_code` ``` This is due to a wrong return type annotation [here](e926b8c4c1/src/integrations/prefect-aws/tests/workers/test_ecs_worker.py (L355-L391)
). ```diff mitmproxy (https://github.com/mitmproxy/mitmproxy) + test/mitmproxy/addons/test_clientplayback.py:18:1: error[invalid-argument-type] Argument to function `asynccontextmanager` is incorrect: Expected `(...) -> AsyncIterator[Unknown]`, found `def tcp_server(handle_conn, **server_args) -> Unknown | tuple[str, int]` ``` [This](a4d794c59a/test/mitmproxy/addons/test_clientplayback.py (L18-L19)
) is a true positive. That function should return `AsyncIterator[Address]`, not `Address`. I looked through almost all of the other new diagnostics and they all look like known problems or true positives. ## Typing conformance The typing conformance diff looks good. ## Test Plan New Markdown tests
2 KiB
2 KiB
Async with statements
Basic async with
statement
The type of the target variable in a with
statement should be the return type from the context
manager's __aenter__
method. However, async with
statements aren't supported yet. This test
asserts that it doesn't emit any context manager-related errors.
class Target: ...
class Manager:
async def __aenter__(self) -> Target:
return Target()
async def __aexit__(self, exc_type, exc_value, traceback): ...
async def test():
async with Manager() as f:
reveal_type(f) # revealed: Target
Multiple targets
class Manager:
async def __aenter__(self) -> tuple[int, str]:
return 42, "hello"
async def __aexit__(self, exc_type, exc_value, traceback): ...
async def test():
async with Manager() as (x, y):
reveal_type(x) # revealed: int
reveal_type(y) # revealed: str
@asynccontextmanager
from contextlib import asynccontextmanager
from typing import AsyncGenerator
class Session: ...
@asynccontextmanager
async def connect() -> AsyncGenerator[Session]:
yield Session()
# TODO: this should be `() -> _AsyncGeneratorContextManager[Session, None]`
reveal_type(connect) # revealed: (...) -> _AsyncGeneratorContextManager[Unknown, None]
async def main():
async with connect() as session:
# TODO: should be `Session`
reveal_type(session) # revealed: Unknown
asyncio.timeout
[environment]
python-version = "3.11"
import asyncio
async def long_running_task():
await asyncio.sleep(5)
async def main():
async with asyncio.timeout(1):
await long_running_task()
asyncio.TaskGroup
[environment]
python-version = "3.11"
import asyncio
async def long_running_task():
await asyncio.sleep(5)
async def main():
async with asyncio.TaskGroup() as tg:
# TODO: should be `TaskGroup`
reveal_type(tg) # revealed: Unknown
tg.create_task(long_running_task())