add failing tests

This commit is contained in:
Douglas Creager 2025-11-06 15:44:11 -05:00
parent 8623176dc5
commit 671923bb6b
3 changed files with 178 additions and 0 deletions

View File

@ -341,4 +341,93 @@ def mutually_constrained[T, U]():
static_assert(not given_int.implies_subtype_of(Invariant[T], Invariant[str]))
```
## Generic callables
A generic callable can be considered equivalent to an intersection of all of its possible
specializations. That means that a generic callable is a subtype of any particular specialization.
(If someone expects a function that works with a particular specialization, it's fine to hand them
the generic callable.)
```py
from typing import Callable
from ty_extensions import CallableTypeOf, ConstraintSet, TypeOf, is_subtype_of, static_assert
def identity[T](t: T) -> T:
return t
constraints = ConstraintSet.always()
# TODO: no error
# error: [static-assert-error]
static_assert(constraints.implies_subtype_of(TypeOf[identity], Callable[[int], int]))
# TODO: no error
# error: [static-assert-error]
static_assert(constraints.implies_subtype_of(TypeOf[identity], Callable[[str], str]))
static_assert(not constraints.implies_subtype_of(TypeOf[identity], Callable[[str], int]))
# TODO: no error
# error: [static-assert-error]
static_assert(constraints.implies_subtype_of(CallableTypeOf[identity], Callable[[int], int]))
# TODO: no error
# error: [static-assert-error]
static_assert(constraints.implies_subtype_of(CallableTypeOf[identity], Callable[[str], str]))
static_assert(not constraints.implies_subtype_of(CallableTypeOf[identity], Callable[[str], int]))
```
The reverse is not true — if someone expects a generic function that can be called with any
specialization, we cannot hand them a function that only works with one specialization.
```py
static_assert(not constraints.implies_subtype_of(Callable[[int], int], TypeOf[identity]))
static_assert(not constraints.implies_subtype_of(Callable[[str], str], TypeOf[identity]))
static_assert(not constraints.implies_subtype_of(Callable[[str], int], TypeOf[identity]))
static_assert(not constraints.implies_subtype_of(Callable[[int], int], CallableTypeOf[identity]))
static_assert(not constraints.implies_subtype_of(Callable[[str], str], CallableTypeOf[identity]))
static_assert(not constraints.implies_subtype_of(Callable[[str], int], CallableTypeOf[identity]))
```
Unrelated typevars in the constraint set do not affect whether the subtyping check succeeds or
fails.
```py
def unrelated[T]():
# Note that even though this typevar is also named T, it is not the same typevar as T@identity!
constraints = ConstraintSet.range(bool, T, int)
# TODO: no error
# error: [static-assert-error]
static_assert(constraints.implies_subtype_of(TypeOf[identity], Callable[[int], int]))
# TODO: no error
# error: [static-assert-error]
static_assert(constraints.implies_subtype_of(TypeOf[identity], Callable[[str], str]))
static_assert(not constraints.implies_subtype_of(TypeOf[identity], Callable[[str], int]))
static_assert(not constraints.implies_subtype_of(Callable[[int], int], TypeOf[identity]))
static_assert(not constraints.implies_subtype_of(Callable[[str], str], TypeOf[identity]))
static_assert(not constraints.implies_subtype_of(Callable[[str], int], TypeOf[identity]))
```
The generic callable's typevar _also_ does not affect whether the subtyping check succeeds or fails!
```py
def identity2[T](t: T) -> T:
# This constraint set refers to the same typevar as the generic function types below!
constraints = ConstraintSet.range(bool, T, int)
# TODO: no error
# error: [static-assert-error]
static_assert(constraints.implies_subtype_of(TypeOf[identity2], Callable[[int], int]))
# TODO: no error
# error: [static-assert-error]
static_assert(constraints.implies_subtype_of(TypeOf[identity2], Callable[[str], str]))
static_assert(not constraints.implies_subtype_of(TypeOf[identity2], Callable[[str], int]))
static_assert(not constraints.implies_subtype_of(Callable[[int], int], TypeOf[identity2]))
static_assert(not constraints.implies_subtype_of(Callable[[str], str], TypeOf[identity2]))
static_assert(not constraints.implies_subtype_of(Callable[[str], int], TypeOf[identity2]))
return t
```
[subtyping]: https://typing.python.org/en/latest/spec/concepts.html#subtype-supertype-and-type-equivalence

View File

@ -1,5 +1,10 @@
# Assignable-to relation
```toml
[environment]
python-version = "3.12"
```
The `is_assignable_to(S, T)` relation below checks if type `S` is assignable to type `T` (target).
This allows us to check if a type `S` can be used in a context where a type `T` is expected
(function arguments, variable assignments). See the [typing documentation] for a precise definition
@ -1227,6 +1232,46 @@ from ty_extensions import static_assert, is_assignable_to
static_assert(is_assignable_to(type, Callable[..., Any]))
```
### Generic callables
A generic callable can be considered equivalent to an intersection of all of its possible
specializations. That means that a generic callable is assignable to any particular specialization.
(If someone expects a function that works with a particular specialization, it's fine to hand them
the generic callable.)
```py
from typing import Callable
from ty_extensions import CallableTypeOf, TypeOf, is_assignable_to, static_assert
def identity[T](t: T) -> T:
return t
static_assert(is_assignable_to(TypeOf[identity], Callable[[int], int]))
static_assert(is_assignable_to(TypeOf[identity], Callable[[str], str]))
# TODO: no error
# error: [static-assert-error]
static_assert(not is_assignable_to(TypeOf[identity], Callable[[str], int]))
static_assert(is_assignable_to(CallableTypeOf[identity], Callable[[int], int]))
static_assert(is_assignable_to(CallableTypeOf[identity], Callable[[str], str]))
# TODO: no error
# error: [static-assert-error]
static_assert(not is_assignable_to(CallableTypeOf[identity], Callable[[str], int]))
```
The reverse is not true — if someone expects a generic function that can be called with any
specialization, we cannot hand them a function that only works with one specialization.
```py
static_assert(not is_assignable_to(Callable[[int], int], TypeOf[identity]))
static_assert(not is_assignable_to(Callable[[str], str], TypeOf[identity]))
static_assert(not is_assignable_to(Callable[[str], int], TypeOf[identity]))
static_assert(not is_assignable_to(Callable[[int], int], CallableTypeOf[identity]))
static_assert(not is_assignable_to(Callable[[str], str], CallableTypeOf[identity]))
static_assert(not is_assignable_to(Callable[[str], int], CallableTypeOf[identity]))
```
## Generics
### Assignability of generic types parameterized by gradual types

View File

@ -2207,6 +2207,50 @@ static_assert(is_subtype_of(CallableTypeOf[overload_ab], CallableTypeOf[overload
static_assert(is_subtype_of(CallableTypeOf[overload_ba], CallableTypeOf[overload_ab]))
```
### Generic callables
A generic callable can be considered equivalent to an intersection of all of its possible
specializations. That means that a generic callable is a subtype of any particular specialization.
(If someone expects a function that works with a particular specialization, it's fine to hand them
the generic callable.)
```py
from typing import Callable
from ty_extensions import CallableTypeOf, TypeOf, is_subtype_of, static_assert
def identity[T](t: T) -> T:
return t
# TODO: no error
# error: [static-assert-error]
static_assert(is_subtype_of(TypeOf[identity], Callable[[int], int]))
# TODO: no error
# error: [static-assert-error]
static_assert(is_subtype_of(TypeOf[identity], Callable[[str], str]))
static_assert(not is_subtype_of(TypeOf[identity], Callable[[str], int]))
# TODO: no error
# error: [static-assert-error]
static_assert(is_subtype_of(CallableTypeOf[identity], Callable[[int], int]))
# TODO: no error
# error: [static-assert-error]
static_assert(is_subtype_of(CallableTypeOf[identity], Callable[[str], str]))
static_assert(not is_subtype_of(CallableTypeOf[identity], Callable[[str], int]))
```
The reverse is not true — if someone expects a generic function that can be called with any
specialization, we cannot hand them a function that only works with one specialization.
```py
static_assert(not is_subtype_of(Callable[[int], int], TypeOf[identity]))
static_assert(not is_subtype_of(Callable[[str], str], TypeOf[identity]))
static_assert(not is_subtype_of(Callable[[str], int], TypeOf[identity]))
static_assert(not is_subtype_of(Callable[[int], int], CallableTypeOf[identity]))
static_assert(not is_subtype_of(Callable[[str], str], CallableTypeOf[identity]))
static_assert(not is_subtype_of(Callable[[str], int], CallableTypeOf[identity]))
```
[gradual form]: https://typing.python.org/en/latest/spec/glossary.html#term-gradual-form
[gradual tuple]: https://typing.python.org/en/latest/spec/tuples.html#tuple-type-form
[special case for float and complex]: https://typing.python.org/en/latest/spec/special-types.html#special-cases-for-float-and-complex