mirror of https://github.com/astral-sh/ruff
add failing tests
This commit is contained in:
parent
8623176dc5
commit
671923bb6b
|
|
@ -341,4 +341,93 @@ def mutually_constrained[T, U]():
|
||||||
static_assert(not given_int.implies_subtype_of(Invariant[T], Invariant[str]))
|
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
|
[subtyping]: https://typing.python.org/en/latest/spec/concepts.html#subtype-supertype-and-type-equivalence
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,10 @@
|
||||||
# Assignable-to relation
|
# 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).
|
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
|
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
|
(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]))
|
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
|
## Generics
|
||||||
|
|
||||||
### Assignability of generic types parameterized by gradual types
|
### Assignability of generic types parameterized by gradual types
|
||||||
|
|
|
||||||
|
|
@ -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]))
|
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 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
|
[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
|
[special case for float and complex]: https://typing.python.org/en/latest/spec/special-types.html#special-cases-for-float-and-complex
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue