mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-28 21:05:08 +00:00
[ty] support callability of bound/constrained typevars (#18389)
## Summary Allow a typevar to be callable if it is bound to a callable type, or constrained to callable types. I spent some time digging into why this support didn't fall out naturally, and ultimately the reason is that we look up `__call__` on the meta type (since its a dunder), and our implementation of `Type::to_meta_type` for `Type::Callable` does not return a type with `__call__`. A more general solution here would be to have `Type::to_meta_type` for `Type::Callable` synthesize a protocol with `__call__` and return an intersection with that protocol (since for a type to be callable, we know its meta-type must have `__call__`). That solution could in principle also replace the special-case handling of `Type::Callable` itself, here in `Type::bindings`. But that more general approach would also be slower, and our protocol support isn't quite ready for that yet, and handling this directly in `Type::bindings` is really not bad. Fixes https://github.com/astral-sh/ty/issues/480 ## Test Plan Added mdtests.
This commit is contained in:
parent
fc549bda94
commit
ad024f9a09
3 changed files with 53 additions and 1 deletions
|
@ -172,4 +172,28 @@ T = TypeVar("T", covariant=cond())
|
||||||
U = TypeVar("U", contravariant=cond())
|
U = TypeVar("U", contravariant=cond())
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Callability
|
||||||
|
|
||||||
|
A typevar bound to a Callable type is callable:
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import Callable, TypeVar
|
||||||
|
|
||||||
|
T = TypeVar("T", bound=Callable[[], int])
|
||||||
|
|
||||||
|
def bound(f: T):
|
||||||
|
reveal_type(f) # revealed: T
|
||||||
|
reveal_type(f()) # revealed: int
|
||||||
|
```
|
||||||
|
|
||||||
|
Same with a constrained typevar, as long as all constraints are callable:
|
||||||
|
|
||||||
|
```py
|
||||||
|
T = TypeVar("T", Callable[[], int], Callable[[], str])
|
||||||
|
|
||||||
|
def constrained(f: T):
|
||||||
|
reveal_type(f) # revealed: T
|
||||||
|
reveal_type(f()) # revealed: int | str
|
||||||
|
```
|
||||||
|
|
||||||
[generics]: https://typing.python.org/en/latest/spec/generics.html
|
[generics]: https://typing.python.org/en/latest/spec/generics.html
|
||||||
|
|
|
@ -746,4 +746,24 @@ def h[T: (P, None)](t: T) -> None:
|
||||||
p: P = t
|
p: P = t
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Callability
|
||||||
|
|
||||||
|
A typevar bound to a Callable type is callable:
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import Callable
|
||||||
|
|
||||||
|
def bound[T: Callable[[], int]](f: T):
|
||||||
|
reveal_type(f) # revealed: T
|
||||||
|
reveal_type(f()) # revealed: int
|
||||||
|
```
|
||||||
|
|
||||||
|
Same with a constrained typevar, as long as all constraints are callable:
|
||||||
|
|
||||||
|
```py
|
||||||
|
def constrained[T: (Callable[[], int], Callable[[], str])](f: T):
|
||||||
|
reveal_type(f) # revealed: T
|
||||||
|
reveal_type(f()) # revealed: int | str
|
||||||
|
```
|
||||||
|
|
||||||
[pep 695]: https://peps.python.org/pep-0695/
|
[pep 695]: https://peps.python.org/pep-0695/
|
||||||
|
|
|
@ -3608,6 +3608,15 @@ impl<'db> Type<'db> {
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Type::TypeVar(typevar) => match typevar.bound_or_constraints(db) {
|
||||||
|
None => CallableBinding::not_callable(self).into(),
|
||||||
|
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => bound.bindings(db),
|
||||||
|
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => Bindings::from_union(
|
||||||
|
self,
|
||||||
|
constraints.elements(db).iter().map(|ty| ty.bindings(db)),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
Type::BoundMethod(bound_method) => {
|
Type::BoundMethod(bound_method) => {
|
||||||
let signature = bound_method.function(db).signature(db);
|
let signature = bound_method.function(db).signature(db);
|
||||||
CallableBinding::from_overloads(self, signature.overloads.iter().cloned())
|
CallableBinding::from_overloads(self, signature.overloads.iter().cloned())
|
||||||
|
@ -4422,7 +4431,6 @@ impl<'db> Type<'db> {
|
||||||
| Type::LiteralString
|
| Type::LiteralString
|
||||||
| Type::Tuple(_)
|
| Type::Tuple(_)
|
||||||
| Type::BoundSuper(_)
|
| Type::BoundSuper(_)
|
||||||
| Type::TypeVar(_)
|
|
||||||
| Type::ModuleLiteral(_) => CallableBinding::not_callable(self).into(),
|
| Type::ModuleLiteral(_) => CallableBinding::not_callable(self).into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue