mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-21 15:52:34 +00:00
[ty] Diagnostics for async context managers (#19704)
## Summary Implements diagnostics for async context managers. Fixes https://github.com/astral-sh/ty/issues/918. ## Test Plan Mdtests have been added.
This commit is contained in:
parent
78e5fe0a51
commit
934fd37d2b
8 changed files with 341 additions and 121 deletions
|
@ -0,0 +1,41 @@
|
|||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: async.md - Async with statements - Accidental use of async `async with`
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/with/async.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | class Manager:
|
||||
2 | def __enter__(self): ...
|
||||
3 | def __exit__(self, *args): ...
|
||||
4 |
|
||||
5 | async def main():
|
||||
6 | # error: [invalid-context-manager] "Object of type `Manager` cannot be used with `async with` because it does not implement `__aenter__` and `__aexit__`"
|
||||
7 | async with Manager():
|
||||
8 | ...
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-context-manager]: Object of type `Manager` cannot be used with `async with` because it does not implement `__aenter__` and `__aexit__`
|
||||
--> src/mdtest_snippet.py:7:16
|
||||
|
|
||||
5 | async def main():
|
||||
6 | # error: [invalid-context-manager] "Object of type `Manager` cannot be used with `async with` because it does not implement `__aent…
|
||||
7 | async with Manager():
|
||||
| ^^^^^^^^^
|
||||
8 | ...
|
||||
|
|
||||
info: Objects of type `Manager` can be used as sync context managers
|
||||
info: Consider using `with` here
|
||||
info: rule `invalid-context-manager` is enabled by default
|
||||
|
||||
```
|
|
@ -12,27 +12,13 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/with/sync.md
|
|||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | class Manager:
|
||||
2 | async def __aenter__(self): ...
|
||||
3 | async def __aexit__(self, *args): ...
|
||||
4 |
|
||||
5 | # error: [invalid-context-manager] "Object of type `Manager` cannot be used with `with` because it does not implement `__enter__` and `__exit__`"
|
||||
6 | with Manager():
|
||||
7 | ...
|
||||
8 | class Manager:
|
||||
9 | async def __aenter__(self): ...
|
||||
10 | async def __aexit__(self, typ: str, exc, traceback): ...
|
||||
11 |
|
||||
12 | # error: [invalid-context-manager] "Object of type `Manager` cannot be used with `with` because it does not implement `__enter__` and `__exit__`"
|
||||
13 | with Manager():
|
||||
14 | ...
|
||||
15 | class Manager:
|
||||
16 | async def __aenter__(self, wrong_extra_arg): ...
|
||||
17 | async def __aexit__(self, typ, exc, traceback, wrong_extra_arg): ...
|
||||
18 |
|
||||
19 | # error: [invalid-context-manager] "Object of type `Manager` cannot be used with `with` because it does not implement `__enter__` and `__exit__`"
|
||||
20 | with Manager():
|
||||
21 | ...
|
||||
1 | class Manager:
|
||||
2 | async def __aenter__(self): ...
|
||||
3 | async def __aexit__(self, *args): ...
|
||||
4 |
|
||||
5 | # error: [invalid-context-manager] "Object of type `Manager` cannot be used with `with` because it does not implement `__enter__` and `__exit__`"
|
||||
6 | with Manager():
|
||||
7 | ...
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
@ -45,41 +31,9 @@ error[invalid-context-manager]: Object of type `Manager` cannot be used with `wi
|
|||
6 | with Manager():
|
||||
| ^^^^^^^^^
|
||||
7 | ...
|
||||
8 | class Manager:
|
||||
|
|
||||
info: Objects of type `Manager` can be used as async context managers
|
||||
info: Consider using `async with` here
|
||||
info: rule `invalid-context-manager` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-context-manager]: Object of type `Manager` cannot be used with `with` because it does not implement `__enter__` and `__exit__`
|
||||
--> src/mdtest_snippet.py:13:6
|
||||
|
|
||||
12 | # error: [invalid-context-manager] "Object of type `Manager` cannot be used with `with` because it does not implement `__enter__` and …
|
||||
13 | with Manager():
|
||||
| ^^^^^^^^^
|
||||
14 | ...
|
||||
15 | class Manager:
|
||||
|
|
||||
info: Objects of type `Manager` can be used as async context managers
|
||||
info: Consider using `async with` here
|
||||
info: rule `invalid-context-manager` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-context-manager]: Object of type `Manager` cannot be used with `with` because it does not implement `__enter__` and `__exit__`
|
||||
--> src/mdtest_snippet.py:20:6
|
||||
|
|
||||
19 | # error: [invalid-context-manager] "Object of type `Manager` cannot be used with `with` because it does not implement `__enter__` and …
|
||||
20 | with Manager():
|
||||
| ^^^^^^^^^
|
||||
21 | ...
|
||||
|
|
||||
info: Objects of type `Manager` can be used as async context managers
|
||||
info: Consider using `async with` here
|
||||
info: rule `invalid-context-manager` is enabled by default
|
||||
|
||||
```
|
||||
|
|
|
@ -35,6 +35,171 @@ async def test():
|
|||
reveal_type(y) # revealed: str
|
||||
```
|
||||
|
||||
## Context manager without an `__aenter__` or `__aexit__` method
|
||||
|
||||
```py
|
||||
class Manager: ...
|
||||
|
||||
async def main():
|
||||
# error: [invalid-context-manager] "Object of type `Manager` cannot be used with `async with` because it does not implement `__aenter__` and `__aexit__`"
|
||||
async with Manager():
|
||||
...
|
||||
```
|
||||
|
||||
## Context manager without an `__aenter__` method
|
||||
|
||||
```py
|
||||
class Manager:
|
||||
async def __aexit__(self, exc_tpe, exc_value, traceback): ...
|
||||
|
||||
async def main():
|
||||
# error: [invalid-context-manager] "Object of type `Manager` cannot be used with `async with` because it does not implement `__aenter__`"
|
||||
async with Manager():
|
||||
...
|
||||
```
|
||||
|
||||
## Context manager without an `__aexit__` method
|
||||
|
||||
```py
|
||||
class Manager:
|
||||
async def __aenter__(self): ...
|
||||
|
||||
async def main():
|
||||
# error: [invalid-context-manager] "Object of type `Manager` cannot be used with `async with` because it does not implement `__aexit__`"
|
||||
async with Manager():
|
||||
...
|
||||
```
|
||||
|
||||
## Context manager with non-callable `__aenter__` attribute
|
||||
|
||||
```py
|
||||
class Manager:
|
||||
__aenter__: int = 42
|
||||
|
||||
async def __aexit__(self, exc_tpe, exc_value, traceback): ...
|
||||
|
||||
async def main():
|
||||
# error: [invalid-context-manager] "Object of type `Manager` cannot be used with `async with` because it does not correctly implement `__aenter__`"
|
||||
async with Manager():
|
||||
...
|
||||
```
|
||||
|
||||
## Context manager with non-callable `__aexit__` attribute
|
||||
|
||||
```py
|
||||
from typing_extensions import Self
|
||||
|
||||
class Manager:
|
||||
def __aenter__(self) -> Self:
|
||||
return self
|
||||
__aexit__: int = 32
|
||||
|
||||
async def main():
|
||||
# error: [invalid-context-manager] "Object of type `Manager` cannot be used with `async with` because it does not correctly implement `__aexit__`"
|
||||
async with Manager():
|
||||
...
|
||||
```
|
||||
|
||||
## Context expression with possibly-unbound union variants
|
||||
|
||||
```py
|
||||
async def _(flag: bool):
|
||||
class Manager1:
|
||||
def __aenter__(self) -> str:
|
||||
return "foo"
|
||||
|
||||
def __aexit__(self, exc_type, exc_value, traceback): ...
|
||||
|
||||
class NotAContextManager: ...
|
||||
context_expr = Manager1() if flag else NotAContextManager()
|
||||
|
||||
# error: [invalid-context-manager] "Object of type `Manager1 | NotAContextManager` cannot be used with `async with` because the methods `__aenter__` and `__aexit__` are possibly unbound"
|
||||
async with context_expr as f:
|
||||
reveal_type(f) # revealed: str
|
||||
```
|
||||
|
||||
## Context expression with "sometimes" callable `__aenter__` method
|
||||
|
||||
```py
|
||||
async def _(flag: bool):
|
||||
class Manager:
|
||||
if flag:
|
||||
async def __aenter__(self) -> str:
|
||||
return "abcd"
|
||||
|
||||
async def __exit__(self, *args): ...
|
||||
|
||||
# error: [invalid-context-manager] "Object of type `Manager` cannot be used with `async with` because the method `__aenter__` is possibly unbound"
|
||||
async with Manager() as f:
|
||||
reveal_type(f) # revealed: CoroutineType[Any, Any, str]
|
||||
```
|
||||
|
||||
## Invalid `__aenter__` signature
|
||||
|
||||
```py
|
||||
class Manager:
|
||||
async def __aenter__() -> str:
|
||||
return "foo"
|
||||
|
||||
async def __aexit__(self, exc_type, exc_value, traceback): ...
|
||||
|
||||
async def main():
|
||||
context_expr = Manager()
|
||||
|
||||
# error: [invalid-context-manager] "Object of type `Manager` cannot be used with `async with` because it does not correctly implement `__aenter__`"
|
||||
async with context_expr as f:
|
||||
reveal_type(f) # revealed: CoroutineType[Any, Any, str]
|
||||
```
|
||||
|
||||
## Accidental use of async `async with`
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
If a asynchronous `async with` statement is used on a type with `__enter__` and `__exit__`, we show
|
||||
a diagnostic hint that the user might have intended to use `with` instead.
|
||||
|
||||
```py
|
||||
class Manager:
|
||||
def __enter__(self): ...
|
||||
def __exit__(self, *args): ...
|
||||
|
||||
async def main():
|
||||
# error: [invalid-context-manager] "Object of type `Manager` cannot be used with `async with` because it does not implement `__aenter__` and `__aexit__`"
|
||||
async with Manager():
|
||||
...
|
||||
```
|
||||
|
||||
## Incorrect signatures
|
||||
|
||||
The sub-diagnostic is also provided if the signatures of `__enter__` and `__exit__` do not match the
|
||||
expected signatures for a context manager:
|
||||
|
||||
```py
|
||||
class Manager:
|
||||
def __enter__(self): ...
|
||||
def __exit__(self, typ: str, exc, traceback): ...
|
||||
|
||||
async def main():
|
||||
# error: [invalid-context-manager] "Object of type `Manager` cannot be used with `async with` because it does not implement `__aenter__` and `__aexit__`"
|
||||
async with Manager():
|
||||
...
|
||||
```
|
||||
|
||||
## Incorrect number of arguments
|
||||
|
||||
Similarly, we also show the hint if the functions have the wrong number of arguments:
|
||||
|
||||
```py
|
||||
class Manager:
|
||||
def __enter__(self, wrong_extra_arg): ...
|
||||
def __exit__(self, typ, exc, traceback, wrong_extra_arg): ...
|
||||
|
||||
async def main():
|
||||
# error: [invalid-context-manager] "Object of type `Manager` cannot be used with `async with` because it does not implement `__aenter__` and `__aexit__`"
|
||||
async with Manager():
|
||||
...
|
||||
```
|
||||
|
||||
## `@asynccontextmanager`
|
||||
|
||||
```py
|
||||
|
|
|
@ -155,7 +155,7 @@ with context_expr as f:
|
|||
<!-- snapshot-diagnostics -->
|
||||
|
||||
If a synchronous `with` statement is used on a type with `__aenter__` and `__aexit__`, we show a
|
||||
diagnostic hint that the user might have intended to use `asnyc with` instead.
|
||||
diagnostic hint that the user might have intended to use `async with` instead.
|
||||
|
||||
```py
|
||||
class Manager:
|
||||
|
@ -167,6 +167,8 @@ with Manager():
|
|||
...
|
||||
```
|
||||
|
||||
## Incorrect signatures
|
||||
|
||||
The sub-diagnostic is also provided if the signatures of `__aenter__` and `__aexit__` do not match
|
||||
the expected signatures for a context manager:
|
||||
|
||||
|
@ -180,6 +182,8 @@ with Manager():
|
|||
...
|
||||
```
|
||||
|
||||
## Incorrect number of arguments
|
||||
|
||||
Similarly, we also show the hint if the functions have the wrong number of arguments:
|
||||
|
||||
```py
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue