mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-09 21:28:21 +00:00
[ty] Add hint if async context manager is used in non-async with statement (#18299)
# Summary Adds a subdiagnostic hint in the following scenario where a synchronous `with` is used with an async context manager: ```py class Manager: async def __aenter__(self): ... async def __aexit__(self, *args): ... # error: [invalid-context-manager] "Object of type `Manager` cannot be used with `with` because it does not implement `__enter__` and `__exit__`" # note: Objects of type `Manager` *can* be used as async context managers # note: Consider using `async with` here with Manager(): ... ``` closes https://github.com/astral-sh/ty/issues/508 ## Test Plan New MD snapshot tests --------- Co-authored-by: David Peter <mail@david-peter.de>
This commit is contained in:
parent
62ef96f51e
commit
1d20cf9570
3 changed files with 147 additions and 1 deletions
|
@ -0,0 +1,85 @@
|
||||||
|
---
|
||||||
|
source: crates/ty_test/src/lib.rs
|
||||||
|
expression: snapshot
|
||||||
|
---
|
||||||
|
---
|
||||||
|
mdtest name: sync.md - With statements - Accidental use of non-async `with`
|
||||||
|
mdtest path: crates/ty_python_semantic/resources/mdtest/with/sync.md
|
||||||
|
---
|
||||||
|
|
||||||
|
# Python source files
|
||||||
|
|
||||||
|
## 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 | ...
|
||||||
|
```
|
||||||
|
|
||||||
|
# Diagnostics
|
||||||
|
|
||||||
|
```
|
||||||
|
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:6:6
|
||||||
|
|
|
||||||
|
5 | # error: [invalid-context-manager] "Object of type `Manager` cannot be used with `with` because it does not implement `__enter__` and...
|
||||||
|
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__` an...
|
||||||
|
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__` an...
|
||||||
|
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
|
||||||
|
|
||||||
|
```
|
|
@ -149,3 +149,45 @@ context_expr = Manager()
|
||||||
with context_expr as f:
|
with context_expr as f:
|
||||||
reveal_type(f) # revealed: str
|
reveal_type(f) # revealed: str
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Accidental use of non-async `with`
|
||||||
|
|
||||||
|
<!-- 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.
|
||||||
|
|
||||||
|
```py
|
||||||
|
class Manager:
|
||||||
|
async def __aenter__(self): ...
|
||||||
|
async def __aexit__(self, *args): ...
|
||||||
|
|
||||||
|
# error: [invalid-context-manager] "Object of type `Manager` cannot be used with `with` because it does not implement `__enter__` and `__exit__`"
|
||||||
|
with Manager():
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
The sub-diagnostic is also provided if the signatures of `__aenter__` and `__aexit__` do not match
|
||||||
|
the expected signatures for a context manager:
|
||||||
|
|
||||||
|
```py
|
||||||
|
class Manager:
|
||||||
|
async def __aenter__(self): ...
|
||||||
|
async def __aexit__(self, typ: str, exc, traceback): ...
|
||||||
|
|
||||||
|
# error: [invalid-context-manager] "Object of type `Manager` cannot be used with `with` because it does not implement `__enter__` and `__exit__`"
|
||||||
|
with Manager():
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
Similarly, we also show the hint if the functions have the wrong number of arguments:
|
||||||
|
|
||||||
|
```py
|
||||||
|
class Manager:
|
||||||
|
async def __aenter__(self, wrong_extra_arg): ...
|
||||||
|
async def __aexit__(self, typ, exc, traceback, wrong_extra_arg): ...
|
||||||
|
|
||||||
|
# error: [invalid-context-manager] "Object of type `Manager` cannot be used with `with` because it does not implement `__enter__` and `__exit__`"
|
||||||
|
with Manager():
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
|
@ -6133,12 +6133,31 @@ impl<'db> ContextManagerError<'db> {
|
||||||
} => format_call_dunder_errors(enter_error, "__enter__", exit_error, "__exit__"),
|
} => format_call_dunder_errors(enter_error, "__enter__", exit_error, "__exit__"),
|
||||||
};
|
};
|
||||||
|
|
||||||
builder.into_diagnostic(
|
let mut diag = builder.into_diagnostic(
|
||||||
format_args!(
|
format_args!(
|
||||||
"Object of type `{context_expression}` cannot be used with `with` because {formatted_errors}",
|
"Object of type `{context_expression}` cannot be used with `with` because {formatted_errors}",
|
||||||
context_expression = context_expression_type.display(db)
|
context_expression = context_expression_type.display(db)
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// If `__aenter__` and `__aexit__` are available, the user may have intended to use `async with` instead of `with`:
|
||||||
|
if let (
|
||||||
|
Ok(_) | Err(CallDunderError::CallError(..)),
|
||||||
|
Ok(_) | Err(CallDunderError::CallError(..)),
|
||||||
|
) = (
|
||||||
|
context_expression_type.try_call_dunder(db, "__aenter__", CallArgumentTypes::none()),
|
||||||
|
context_expression_type.try_call_dunder(
|
||||||
|
db,
|
||||||
|
"__aexit__",
|
||||||
|
CallArgumentTypes::positional([Type::unknown(), Type::unknown(), Type::unknown()]),
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
diag.info(format_args!(
|
||||||
|
"Objects of type `{context_expression}` can be used as async context managers",
|
||||||
|
context_expression = context_expression_type.display(db)
|
||||||
|
));
|
||||||
|
diag.info("Consider using `async with` here");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue