[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:
Carl Meyer 2025-05-30 12:01:51 -07:00 committed by GitHub
parent fc549bda94
commit ad024f9a09
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 53 additions and 1 deletions

View file

@ -172,4 +172,28 @@ T = TypeVar("T", covariant=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

View file

@ -746,4 +746,24 @@ def h[T: (P, None)](t: T) -> None:
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/