test(variance): update/add variance tests for TypeGuard

This commit is contained in:
Eric Mark Martin 2025-10-19 18:32:16 -04:00
parent b610969a9f
commit 6b5f24530b
2 changed files with 43 additions and 6 deletions

View file

@ -790,6 +790,44 @@ static_assert(not is_assignable_to(C[B], C[A]))
static_assert(not is_assignable_to(C[A], C[B]))
```
## TypeGuard
`TypeGuard[T]` is covariant in `T`. The typing spec doesn't explicitly call this out, but it follows
from similar logic to invariance of `TypeIs` except without the negative case.
Formally, suppose we have types `A` and `B` with `B < A`. Take `x: object` to be the value that all
subsequent `TypeGuard`s are narrowing.
We can assign `p: TypeGuard[A] = q` where `q: TypeGuard[B]` because
- if `q` is `False`, then no constraints were learned on `x` before and none are now learned, so
nothing changes
- if `q` is `True`, then we know `x: B`. From `B < A`, we conclude `x: A`.
We _cannot_ assign `p: TypeGuard[B] = q` where `q: TypeGuard[A]` because if `q` is `True`, we would
be concluding `x: B` from `x: A`, which is an unsafe downcast.
```py
from typing import TypeGuard
from ty_extensions import is_assignable_to, is_subtype_of, static_assert
class A:
pass
class B(A):
pass
class C[T]:
def check(x: object) -> TypeGuard[T]:
# this is a bad check, but we only care about it type-checking
return False
static_assert(is_subtype_of(C[B], C[A]))
static_assert(not is_subtype_of(C[A], C[B]))
static_assert(is_assignable_to(C[B], C[A]))
static_assert(not is_assignable_to(C[A], C[B]))
```
## Type aliases
The variance of the type alias matches the variance of the value type (RHS type).

View file

@ -670,9 +670,8 @@ Fully-static `TypeGuard[...]` and `TypeIs[...]` are subtypes of `bool`.
from ty_extensions import is_subtype_of, static_assert
from typing_extensions import TypeGuard, TypeIs
# TODO: TypeGuard
# static_assert(is_subtype_of(TypeGuard[int], bool))
# static_assert(is_subtype_of(TypeGuard[int], int))
static_assert(is_subtype_of(TypeGuard[str], bool))
static_assert(is_subtype_of(TypeGuard[str], int))
static_assert(is_subtype_of(TypeIs[str], bool))
static_assert(is_subtype_of(TypeIs[str], int))
```
@ -683,12 +682,12 @@ static_assert(is_subtype_of(TypeIs[str], int))
from ty_extensions import is_equivalent_to, is_subtype_of, static_assert
from typing_extensions import TypeGuard, TypeIs
# TODO: TypeGuard
# static_assert(is_subtype_of(TypeGuard[int], TypeGuard[int]))
# static_assert(is_subtype_of(TypeGuard[bool], TypeGuard[int]))
static_assert(is_subtype_of(TypeGuard[int], TypeGuard[int]))
static_assert(is_subtype_of(TypeGuard[bool], TypeGuard[int]))
static_assert(is_subtype_of(TypeIs[int], TypeIs[int]))
static_assert(is_subtype_of(TypeIs[int], TypeIs[int]))
static_assert(is_subtype_of(TypeGuard[bool], TypeGuard[int]))
static_assert(not is_subtype_of(TypeGuard[int], TypeGuard[bool]))
static_assert(not is_subtype_of(TypeIs[bool], TypeIs[int]))
static_assert(not is_subtype_of(TypeIs[int], TypeIs[bool]))