mirror of
https://github.com/astral-sh/ruff.git
synced 2025-07-23 13:05:06 +00:00
[ty] Check base classes when determining subtyping etc for generic aliases (#17927)
#17897 added variance handling for legacy typevars — but they were only being considered when checking generic aliases of the same class: ```py class A: ... class B(A): ... class C[T]: ... static_assert(is_subtype_of(C[B], C[A])) ``` and not for generic subclasses: ```py class D[U](C[U]): ... static_assert(is_subtype_of(D[B], C[A])) ``` Now we check those too! Closes https://github.com/astral-sh/ty/issues/101
This commit is contained in:
parent
ce0800fccf
commit
2cf5cba7ff
5 changed files with 363 additions and 98 deletions
|
@ -5,8 +5,8 @@ relations. Much more detail can be found in the [spec]. To summarize, each typev
|
||||||
**covariant**, **contravariant**, **invariant**, or **bivariant**. (Note that bivariance is not
|
**covariant**, **contravariant**, **invariant**, or **bivariant**. (Note that bivariance is not
|
||||||
currently mentioned in the typing spec, but is a fourth case that we must consider.)
|
currently mentioned in the typing spec, but is a fourth case that we must consider.)
|
||||||
|
|
||||||
For all of the examples below, we will consider a typevar `T`, a generic class using that typevar
|
For all of the examples below, we will consider typevars `T` and `U`, two generic classes using
|
||||||
`C[T]`, and two types `A` and `B`.
|
those typevars `C[T]` and `D[U]`, and two types `A` and `B`.
|
||||||
|
|
||||||
(Note that dynamic types like `Any` never participate in subtyping, so `C[Any]` is neither a subtype
|
(Note that dynamic types like `Any` never participate in subtyping, so `C[Any]` is neither a subtype
|
||||||
nor supertype of any other specialization of `C`, regardless of `T`'s variance. It is, however,
|
nor supertype of any other specialization of `C`, regardless of `T`'s variance. It is, however,
|
||||||
|
@ -14,8 +14,8 @@ assignable to any specialization of `C`, regardless of variance, via materializa
|
||||||
|
|
||||||
## Covariance
|
## Covariance
|
||||||
|
|
||||||
With a covariant typevar, subtyping and assignability are in "alignment": if `A <: B`, then
|
With a covariant typevar, subtyping and assignability are in "alignment": if `A <: B` and `C <: D`,
|
||||||
`C[A] <: C[B]`.
|
then `C[A] <: C[B]` and `C[A] <: D[B]`.
|
||||||
|
|
||||||
Types that "produce" data on demand are covariant in their typevar. If you expect a sequence of
|
Types that "produce" data on demand are covariant in their typevar. If you expect a sequence of
|
||||||
`int`s, someone can safely provide a sequence of `bool`s, since each `bool` element that you would
|
`int`s, someone can safely provide a sequence of `bool`s, since each `bool` element that you would
|
||||||
|
@ -29,11 +29,15 @@ class A: ...
|
||||||
class B(A): ...
|
class B(A): ...
|
||||||
|
|
||||||
T = TypeVar("T", covariant=True)
|
T = TypeVar("T", covariant=True)
|
||||||
|
U = TypeVar("U", covariant=True)
|
||||||
|
|
||||||
class C(Generic[T]):
|
class C(Generic[T]):
|
||||||
def receive(self) -> T:
|
def receive(self) -> T:
|
||||||
raise ValueError
|
raise ValueError
|
||||||
|
|
||||||
|
class D(C[U]):
|
||||||
|
pass
|
||||||
|
|
||||||
static_assert(is_assignable_to(C[B], C[A]))
|
static_assert(is_assignable_to(C[B], C[A]))
|
||||||
static_assert(not is_assignable_to(C[A], C[B]))
|
static_assert(not is_assignable_to(C[A], C[B]))
|
||||||
static_assert(is_assignable_to(C[A], C[Any]))
|
static_assert(is_assignable_to(C[A], C[Any]))
|
||||||
|
@ -41,6 +45,13 @@ static_assert(is_assignable_to(C[B], C[Any]))
|
||||||
static_assert(is_assignable_to(C[Any], C[A]))
|
static_assert(is_assignable_to(C[Any], C[A]))
|
||||||
static_assert(is_assignable_to(C[Any], C[B]))
|
static_assert(is_assignable_to(C[Any], C[B]))
|
||||||
|
|
||||||
|
static_assert(is_assignable_to(D[B], C[A]))
|
||||||
|
static_assert(not is_assignable_to(D[A], C[B]))
|
||||||
|
static_assert(is_assignable_to(D[A], C[Any]))
|
||||||
|
static_assert(is_assignable_to(D[B], C[Any]))
|
||||||
|
static_assert(is_assignable_to(D[Any], C[A]))
|
||||||
|
static_assert(is_assignable_to(D[Any], C[B]))
|
||||||
|
|
||||||
static_assert(is_subtype_of(C[B], C[A]))
|
static_assert(is_subtype_of(C[B], C[A]))
|
||||||
static_assert(not is_subtype_of(C[A], C[B]))
|
static_assert(not is_subtype_of(C[A], C[B]))
|
||||||
static_assert(not is_subtype_of(C[A], C[Any]))
|
static_assert(not is_subtype_of(C[A], C[Any]))
|
||||||
|
@ -48,6 +59,13 @@ static_assert(not is_subtype_of(C[B], C[Any]))
|
||||||
static_assert(not is_subtype_of(C[Any], C[A]))
|
static_assert(not is_subtype_of(C[Any], C[A]))
|
||||||
static_assert(not is_subtype_of(C[Any], C[B]))
|
static_assert(not is_subtype_of(C[Any], C[B]))
|
||||||
|
|
||||||
|
static_assert(is_subtype_of(D[B], C[A]))
|
||||||
|
static_assert(not is_subtype_of(D[A], C[B]))
|
||||||
|
static_assert(not is_subtype_of(D[A], C[Any]))
|
||||||
|
static_assert(not is_subtype_of(D[B], C[Any]))
|
||||||
|
static_assert(not is_subtype_of(D[Any], C[A]))
|
||||||
|
static_assert(not is_subtype_of(D[Any], C[B]))
|
||||||
|
|
||||||
static_assert(is_equivalent_to(C[A], C[A]))
|
static_assert(is_equivalent_to(C[A], C[A]))
|
||||||
static_assert(is_equivalent_to(C[B], C[B]))
|
static_assert(is_equivalent_to(C[B], C[B]))
|
||||||
static_assert(not is_equivalent_to(C[B], C[A]))
|
static_assert(not is_equivalent_to(C[B], C[A]))
|
||||||
|
@ -57,6 +75,15 @@ static_assert(not is_equivalent_to(C[B], C[Any]))
|
||||||
static_assert(not is_equivalent_to(C[Any], C[A]))
|
static_assert(not is_equivalent_to(C[Any], C[A]))
|
||||||
static_assert(not is_equivalent_to(C[Any], C[B]))
|
static_assert(not is_equivalent_to(C[Any], C[B]))
|
||||||
|
|
||||||
|
static_assert(not is_equivalent_to(D[A], C[A]))
|
||||||
|
static_assert(not is_equivalent_to(D[B], C[B]))
|
||||||
|
static_assert(not is_equivalent_to(D[B], C[A]))
|
||||||
|
static_assert(not is_equivalent_to(D[A], C[B]))
|
||||||
|
static_assert(not is_equivalent_to(D[A], C[Any]))
|
||||||
|
static_assert(not is_equivalent_to(D[B], C[Any]))
|
||||||
|
static_assert(not is_equivalent_to(D[Any], C[A]))
|
||||||
|
static_assert(not is_equivalent_to(D[Any], C[B]))
|
||||||
|
|
||||||
static_assert(is_gradual_equivalent_to(C[A], C[A]))
|
static_assert(is_gradual_equivalent_to(C[A], C[A]))
|
||||||
static_assert(is_gradual_equivalent_to(C[B], C[B]))
|
static_assert(is_gradual_equivalent_to(C[B], C[B]))
|
||||||
static_assert(is_gradual_equivalent_to(C[Any], C[Any]))
|
static_assert(is_gradual_equivalent_to(C[Any], C[Any]))
|
||||||
|
@ -67,12 +94,23 @@ static_assert(not is_gradual_equivalent_to(C[A], C[Any]))
|
||||||
static_assert(not is_gradual_equivalent_to(C[B], C[Any]))
|
static_assert(not is_gradual_equivalent_to(C[B], C[Any]))
|
||||||
static_assert(not is_gradual_equivalent_to(C[Any], C[A]))
|
static_assert(not is_gradual_equivalent_to(C[Any], C[A]))
|
||||||
static_assert(not is_gradual_equivalent_to(C[Any], C[B]))
|
static_assert(not is_gradual_equivalent_to(C[Any], C[B]))
|
||||||
|
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[A], C[A]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[B], C[B]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[Any], C[Any]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[Any], C[Unknown]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[B], C[A]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[A], C[B]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[A], C[Any]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[B], C[Any]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[Any], C[A]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[Any], C[B]))
|
||||||
```
|
```
|
||||||
|
|
||||||
## Contravariance
|
## Contravariance
|
||||||
|
|
||||||
With a contravariant typevar, subtyping and assignability are in "opposition": if `A <: B`, then
|
With a contravariant typevar, subtyping and assignability are in "opposition": if `A <: B` and
|
||||||
`C[B] <: C[A]`.
|
`C <: D`, then `C[B] <: C[A]` and `D[B] <: C[A]`.
|
||||||
|
|
||||||
Types that "consume" data are contravariant in their typevar. If you expect a consumer that receives
|
Types that "consume" data are contravariant in their typevar. If you expect a consumer that receives
|
||||||
`bool`s, someone can safely provide a consumer that expects to receive `int`s, since each `bool`
|
`bool`s, someone can safely provide a consumer that expects to receive `int`s, since each `bool`
|
||||||
|
@ -86,10 +124,14 @@ class A: ...
|
||||||
class B(A): ...
|
class B(A): ...
|
||||||
|
|
||||||
T = TypeVar("T", contravariant=True)
|
T = TypeVar("T", contravariant=True)
|
||||||
|
U = TypeVar("U", contravariant=True)
|
||||||
|
|
||||||
class C(Generic[T]):
|
class C(Generic[T]):
|
||||||
def send(self, value: T): ...
|
def send(self, value: T): ...
|
||||||
|
|
||||||
|
class D(C[U]):
|
||||||
|
pass
|
||||||
|
|
||||||
static_assert(not is_assignable_to(C[B], C[A]))
|
static_assert(not is_assignable_to(C[B], C[A]))
|
||||||
static_assert(is_assignable_to(C[A], C[B]))
|
static_assert(is_assignable_to(C[A], C[B]))
|
||||||
static_assert(is_assignable_to(C[A], C[Any]))
|
static_assert(is_assignable_to(C[A], C[Any]))
|
||||||
|
@ -97,6 +139,13 @@ static_assert(is_assignable_to(C[B], C[Any]))
|
||||||
static_assert(is_assignable_to(C[Any], C[A]))
|
static_assert(is_assignable_to(C[Any], C[A]))
|
||||||
static_assert(is_assignable_to(C[Any], C[B]))
|
static_assert(is_assignable_to(C[Any], C[B]))
|
||||||
|
|
||||||
|
static_assert(not is_assignable_to(D[B], C[A]))
|
||||||
|
static_assert(is_assignable_to(D[A], C[B]))
|
||||||
|
static_assert(is_assignable_to(D[A], C[Any]))
|
||||||
|
static_assert(is_assignable_to(D[B], C[Any]))
|
||||||
|
static_assert(is_assignable_to(D[Any], C[A]))
|
||||||
|
static_assert(is_assignable_to(D[Any], C[B]))
|
||||||
|
|
||||||
static_assert(not is_subtype_of(C[B], C[A]))
|
static_assert(not is_subtype_of(C[B], C[A]))
|
||||||
static_assert(is_subtype_of(C[A], C[B]))
|
static_assert(is_subtype_of(C[A], C[B]))
|
||||||
static_assert(not is_subtype_of(C[A], C[Any]))
|
static_assert(not is_subtype_of(C[A], C[Any]))
|
||||||
|
@ -104,6 +153,13 @@ static_assert(not is_subtype_of(C[B], C[Any]))
|
||||||
static_assert(not is_subtype_of(C[Any], C[A]))
|
static_assert(not is_subtype_of(C[Any], C[A]))
|
||||||
static_assert(not is_subtype_of(C[Any], C[B]))
|
static_assert(not is_subtype_of(C[Any], C[B]))
|
||||||
|
|
||||||
|
static_assert(not is_subtype_of(D[B], C[A]))
|
||||||
|
static_assert(is_subtype_of(D[A], C[B]))
|
||||||
|
static_assert(not is_subtype_of(D[A], C[Any]))
|
||||||
|
static_assert(not is_subtype_of(D[B], C[Any]))
|
||||||
|
static_assert(not is_subtype_of(D[Any], C[A]))
|
||||||
|
static_assert(not is_subtype_of(D[Any], C[B]))
|
||||||
|
|
||||||
static_assert(is_equivalent_to(C[A], C[A]))
|
static_assert(is_equivalent_to(C[A], C[A]))
|
||||||
static_assert(is_equivalent_to(C[B], C[B]))
|
static_assert(is_equivalent_to(C[B], C[B]))
|
||||||
static_assert(not is_equivalent_to(C[B], C[A]))
|
static_assert(not is_equivalent_to(C[B], C[A]))
|
||||||
|
@ -113,6 +169,15 @@ static_assert(not is_equivalent_to(C[B], C[Any]))
|
||||||
static_assert(not is_equivalent_to(C[Any], C[A]))
|
static_assert(not is_equivalent_to(C[Any], C[A]))
|
||||||
static_assert(not is_equivalent_to(C[Any], C[B]))
|
static_assert(not is_equivalent_to(C[Any], C[B]))
|
||||||
|
|
||||||
|
static_assert(not is_equivalent_to(D[A], C[A]))
|
||||||
|
static_assert(not is_equivalent_to(D[B], C[B]))
|
||||||
|
static_assert(not is_equivalent_to(D[B], C[A]))
|
||||||
|
static_assert(not is_equivalent_to(D[A], C[B]))
|
||||||
|
static_assert(not is_equivalent_to(D[A], C[Any]))
|
||||||
|
static_assert(not is_equivalent_to(D[B], C[Any]))
|
||||||
|
static_assert(not is_equivalent_to(D[Any], C[A]))
|
||||||
|
static_assert(not is_equivalent_to(D[Any], C[B]))
|
||||||
|
|
||||||
static_assert(is_gradual_equivalent_to(C[A], C[A]))
|
static_assert(is_gradual_equivalent_to(C[A], C[A]))
|
||||||
static_assert(is_gradual_equivalent_to(C[B], C[B]))
|
static_assert(is_gradual_equivalent_to(C[B], C[B]))
|
||||||
static_assert(is_gradual_equivalent_to(C[Any], C[Any]))
|
static_assert(is_gradual_equivalent_to(C[Any], C[Any]))
|
||||||
|
@ -123,6 +188,17 @@ static_assert(not is_gradual_equivalent_to(C[A], C[Any]))
|
||||||
static_assert(not is_gradual_equivalent_to(C[B], C[Any]))
|
static_assert(not is_gradual_equivalent_to(C[B], C[Any]))
|
||||||
static_assert(not is_gradual_equivalent_to(C[Any], C[A]))
|
static_assert(not is_gradual_equivalent_to(C[Any], C[A]))
|
||||||
static_assert(not is_gradual_equivalent_to(C[Any], C[B]))
|
static_assert(not is_gradual_equivalent_to(C[Any], C[B]))
|
||||||
|
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[A], C[A]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[B], C[B]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[Any], C[Any]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[Any], C[Unknown]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[B], C[A]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[A], C[B]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[A], C[Any]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[B], C[Any]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[Any], C[A]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[Any], C[B]))
|
||||||
```
|
```
|
||||||
|
|
||||||
## Invariance
|
## Invariance
|
||||||
|
@ -155,12 +231,16 @@ class A: ...
|
||||||
class B(A): ...
|
class B(A): ...
|
||||||
|
|
||||||
T = TypeVar("T")
|
T = TypeVar("T")
|
||||||
|
U = TypeVar("U")
|
||||||
|
|
||||||
class C(Generic[T]):
|
class C(Generic[T]):
|
||||||
def send(self, value: T): ...
|
def send(self, value: T): ...
|
||||||
def receive(self) -> T:
|
def receive(self) -> T:
|
||||||
raise ValueError
|
raise ValueError
|
||||||
|
|
||||||
|
class D(C[U]):
|
||||||
|
pass
|
||||||
|
|
||||||
static_assert(not is_assignable_to(C[B], C[A]))
|
static_assert(not is_assignable_to(C[B], C[A]))
|
||||||
static_assert(not is_assignable_to(C[A], C[B]))
|
static_assert(not is_assignable_to(C[A], C[B]))
|
||||||
static_assert(is_assignable_to(C[A], C[Any]))
|
static_assert(is_assignable_to(C[A], C[Any]))
|
||||||
|
@ -168,6 +248,13 @@ static_assert(is_assignable_to(C[B], C[Any]))
|
||||||
static_assert(is_assignable_to(C[Any], C[A]))
|
static_assert(is_assignable_to(C[Any], C[A]))
|
||||||
static_assert(is_assignable_to(C[Any], C[B]))
|
static_assert(is_assignable_to(C[Any], C[B]))
|
||||||
|
|
||||||
|
static_assert(not is_assignable_to(D[B], C[A]))
|
||||||
|
static_assert(not is_assignable_to(D[A], C[B]))
|
||||||
|
static_assert(is_assignable_to(D[A], C[Any]))
|
||||||
|
static_assert(is_assignable_to(D[B], C[Any]))
|
||||||
|
static_assert(is_assignable_to(D[Any], C[A]))
|
||||||
|
static_assert(is_assignable_to(D[Any], C[B]))
|
||||||
|
|
||||||
static_assert(not is_subtype_of(C[B], C[A]))
|
static_assert(not is_subtype_of(C[B], C[A]))
|
||||||
static_assert(not is_subtype_of(C[A], C[B]))
|
static_assert(not is_subtype_of(C[A], C[B]))
|
||||||
static_assert(not is_subtype_of(C[A], C[Any]))
|
static_assert(not is_subtype_of(C[A], C[Any]))
|
||||||
|
@ -175,6 +262,13 @@ static_assert(not is_subtype_of(C[B], C[Any]))
|
||||||
static_assert(not is_subtype_of(C[Any], C[A]))
|
static_assert(not is_subtype_of(C[Any], C[A]))
|
||||||
static_assert(not is_subtype_of(C[Any], C[B]))
|
static_assert(not is_subtype_of(C[Any], C[B]))
|
||||||
|
|
||||||
|
static_assert(not is_subtype_of(D[B], C[A]))
|
||||||
|
static_assert(not is_subtype_of(D[A], C[B]))
|
||||||
|
static_assert(not is_subtype_of(D[A], C[Any]))
|
||||||
|
static_assert(not is_subtype_of(D[B], C[Any]))
|
||||||
|
static_assert(not is_subtype_of(D[Any], C[A]))
|
||||||
|
static_assert(not is_subtype_of(D[Any], C[B]))
|
||||||
|
|
||||||
static_assert(is_equivalent_to(C[A], C[A]))
|
static_assert(is_equivalent_to(C[A], C[A]))
|
||||||
static_assert(is_equivalent_to(C[B], C[B]))
|
static_assert(is_equivalent_to(C[B], C[B]))
|
||||||
static_assert(not is_equivalent_to(C[B], C[A]))
|
static_assert(not is_equivalent_to(C[B], C[A]))
|
||||||
|
@ -184,6 +278,15 @@ static_assert(not is_equivalent_to(C[B], C[Any]))
|
||||||
static_assert(not is_equivalent_to(C[Any], C[A]))
|
static_assert(not is_equivalent_to(C[Any], C[A]))
|
||||||
static_assert(not is_equivalent_to(C[Any], C[B]))
|
static_assert(not is_equivalent_to(C[Any], C[B]))
|
||||||
|
|
||||||
|
static_assert(not is_equivalent_to(D[A], C[A]))
|
||||||
|
static_assert(not is_equivalent_to(D[B], C[B]))
|
||||||
|
static_assert(not is_equivalent_to(D[B], C[A]))
|
||||||
|
static_assert(not is_equivalent_to(D[A], C[B]))
|
||||||
|
static_assert(not is_equivalent_to(D[A], C[Any]))
|
||||||
|
static_assert(not is_equivalent_to(D[B], C[Any]))
|
||||||
|
static_assert(not is_equivalent_to(D[Any], C[A]))
|
||||||
|
static_assert(not is_equivalent_to(D[Any], C[B]))
|
||||||
|
|
||||||
static_assert(is_gradual_equivalent_to(C[A], C[A]))
|
static_assert(is_gradual_equivalent_to(C[A], C[A]))
|
||||||
static_assert(is_gradual_equivalent_to(C[B], C[B]))
|
static_assert(is_gradual_equivalent_to(C[B], C[B]))
|
||||||
static_assert(is_gradual_equivalent_to(C[Any], C[Any]))
|
static_assert(is_gradual_equivalent_to(C[Any], C[Any]))
|
||||||
|
@ -194,6 +297,17 @@ static_assert(not is_gradual_equivalent_to(C[A], C[Any]))
|
||||||
static_assert(not is_gradual_equivalent_to(C[B], C[Any]))
|
static_assert(not is_gradual_equivalent_to(C[B], C[Any]))
|
||||||
static_assert(not is_gradual_equivalent_to(C[Any], C[A]))
|
static_assert(not is_gradual_equivalent_to(C[Any], C[A]))
|
||||||
static_assert(not is_gradual_equivalent_to(C[Any], C[B]))
|
static_assert(not is_gradual_equivalent_to(C[Any], C[B]))
|
||||||
|
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[A], C[A]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[B], C[B]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[Any], C[Any]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[Any], C[Unknown]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[B], C[A]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[A], C[B]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[A], C[Any]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[B], C[Any]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[Any], C[A]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[Any], C[B]))
|
||||||
```
|
```
|
||||||
|
|
||||||
## Bivariance
|
## Bivariance
|
||||||
|
|
|
@ -10,16 +10,17 @@ relations. Much more detail can be found in the [spec]. To summarize, each typev
|
||||||
**covariant**, **contravariant**, **invariant**, or **bivariant**. (Note that bivariance is not
|
**covariant**, **contravariant**, **invariant**, or **bivariant**. (Note that bivariance is not
|
||||||
currently mentioned in the typing spec, but is a fourth case that we must consider.)
|
currently mentioned in the typing spec, but is a fourth case that we must consider.)
|
||||||
|
|
||||||
For all of the examples below, we will consider a typevar `T`, a generic class using that typevar
|
For all of the examples below, we will consider typevars `T` and `U`, two generic classes using
|
||||||
`C[T]`, and two types `A` and `B`.
|
those typevars `C[T]` and `D[U]`, and two types `A` and `B`.
|
||||||
|
|
||||||
(Note that dynamic types like `Any` never participate in subtyping, so `C[Any]` is neither a subtype
|
(Note that dynamic types like `Any` never participate in subtyping, so `C[Any]` is neither a subtype
|
||||||
nor supertype of any other specialization of `C`, regardless of `T`'s variance.)
|
nor supertype of any other specialization of `C`, regardless of `T`'s variance. It is, however,
|
||||||
|
assignable to any specialization of `C`, regardless of variance, via materialization.)
|
||||||
|
|
||||||
## Covariance
|
## Covariance
|
||||||
|
|
||||||
With a covariant typevar, subtyping and assignability are in "alignment": if `A <: B`, then
|
With a covariant typevar, subtyping and assignability are in "alignment": if `A <: B` and `C <: D`,
|
||||||
`C[A] <: C[B]`.
|
then `C[A] <: C[B]` and `C[A] <: D[B]`.
|
||||||
|
|
||||||
Types that "produce" data on demand are covariant in their typevar. If you expect a sequence of
|
Types that "produce" data on demand are covariant in their typevar. If you expect a sequence of
|
||||||
`int`s, someone can safely provide a sequence of `bool`s, since each `bool` element that you would
|
`int`s, someone can safely provide a sequence of `bool`s, since each `bool` element that you would
|
||||||
|
@ -36,6 +37,9 @@ class C[T]:
|
||||||
def receive(self) -> T:
|
def receive(self) -> T:
|
||||||
raise ValueError
|
raise ValueError
|
||||||
|
|
||||||
|
class D[U](C[U]):
|
||||||
|
pass
|
||||||
|
|
||||||
# TODO: no error
|
# TODO: no error
|
||||||
# error: [static-assert-error]
|
# error: [static-assert-error]
|
||||||
static_assert(is_assignable_to(C[B], C[A]))
|
static_assert(is_assignable_to(C[B], C[A]))
|
||||||
|
@ -45,6 +49,15 @@ static_assert(is_assignable_to(C[B], C[Any]))
|
||||||
static_assert(is_assignable_to(C[Any], C[A]))
|
static_assert(is_assignable_to(C[Any], C[A]))
|
||||||
static_assert(is_assignable_to(C[Any], C[B]))
|
static_assert(is_assignable_to(C[Any], C[B]))
|
||||||
|
|
||||||
|
# TODO: no error
|
||||||
|
# error: [static-assert-error]
|
||||||
|
static_assert(is_assignable_to(D[B], C[A]))
|
||||||
|
static_assert(not is_assignable_to(D[A], C[B]))
|
||||||
|
static_assert(is_assignable_to(D[A], C[Any]))
|
||||||
|
static_assert(is_assignable_to(D[B], C[Any]))
|
||||||
|
static_assert(is_assignable_to(D[Any], C[A]))
|
||||||
|
static_assert(is_assignable_to(D[Any], C[B]))
|
||||||
|
|
||||||
# TODO: no error
|
# TODO: no error
|
||||||
# error: [static-assert-error]
|
# error: [static-assert-error]
|
||||||
static_assert(is_subtype_of(C[B], C[A]))
|
static_assert(is_subtype_of(C[B], C[A]))
|
||||||
|
@ -54,6 +67,15 @@ static_assert(not is_subtype_of(C[B], C[Any]))
|
||||||
static_assert(not is_subtype_of(C[Any], C[A]))
|
static_assert(not is_subtype_of(C[Any], C[A]))
|
||||||
static_assert(not is_subtype_of(C[Any], C[B]))
|
static_assert(not is_subtype_of(C[Any], C[B]))
|
||||||
|
|
||||||
|
# TODO: no error
|
||||||
|
# error: [static-assert-error]
|
||||||
|
static_assert(is_subtype_of(D[B], C[A]))
|
||||||
|
static_assert(not is_subtype_of(D[A], C[B]))
|
||||||
|
static_assert(not is_subtype_of(D[A], C[Any]))
|
||||||
|
static_assert(not is_subtype_of(D[B], C[Any]))
|
||||||
|
static_assert(not is_subtype_of(D[Any], C[A]))
|
||||||
|
static_assert(not is_subtype_of(D[Any], C[B]))
|
||||||
|
|
||||||
static_assert(is_equivalent_to(C[A], C[A]))
|
static_assert(is_equivalent_to(C[A], C[A]))
|
||||||
static_assert(is_equivalent_to(C[B], C[B]))
|
static_assert(is_equivalent_to(C[B], C[B]))
|
||||||
static_assert(not is_equivalent_to(C[B], C[A]))
|
static_assert(not is_equivalent_to(C[B], C[A]))
|
||||||
|
@ -63,6 +85,15 @@ static_assert(not is_equivalent_to(C[B], C[Any]))
|
||||||
static_assert(not is_equivalent_to(C[Any], C[A]))
|
static_assert(not is_equivalent_to(C[Any], C[A]))
|
||||||
static_assert(not is_equivalent_to(C[Any], C[B]))
|
static_assert(not is_equivalent_to(C[Any], C[B]))
|
||||||
|
|
||||||
|
static_assert(not is_equivalent_to(D[A], C[A]))
|
||||||
|
static_assert(not is_equivalent_to(D[B], C[B]))
|
||||||
|
static_assert(not is_equivalent_to(D[B], C[A]))
|
||||||
|
static_assert(not is_equivalent_to(D[A], C[B]))
|
||||||
|
static_assert(not is_equivalent_to(D[A], C[Any]))
|
||||||
|
static_assert(not is_equivalent_to(D[B], C[Any]))
|
||||||
|
static_assert(not is_equivalent_to(D[Any], C[A]))
|
||||||
|
static_assert(not is_equivalent_to(D[Any], C[B]))
|
||||||
|
|
||||||
static_assert(is_gradual_equivalent_to(C[A], C[A]))
|
static_assert(is_gradual_equivalent_to(C[A], C[A]))
|
||||||
static_assert(is_gradual_equivalent_to(C[B], C[B]))
|
static_assert(is_gradual_equivalent_to(C[B], C[B]))
|
||||||
static_assert(is_gradual_equivalent_to(C[Any], C[Any]))
|
static_assert(is_gradual_equivalent_to(C[Any], C[Any]))
|
||||||
|
@ -73,12 +104,23 @@ static_assert(not is_gradual_equivalent_to(C[A], C[Any]))
|
||||||
static_assert(not is_gradual_equivalent_to(C[B], C[Any]))
|
static_assert(not is_gradual_equivalent_to(C[B], C[Any]))
|
||||||
static_assert(not is_gradual_equivalent_to(C[Any], C[A]))
|
static_assert(not is_gradual_equivalent_to(C[Any], C[A]))
|
||||||
static_assert(not is_gradual_equivalent_to(C[Any], C[B]))
|
static_assert(not is_gradual_equivalent_to(C[Any], C[B]))
|
||||||
|
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[A], C[A]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[B], C[B]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[Any], C[Any]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[Any], C[Unknown]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[B], C[A]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[A], C[B]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[A], C[Any]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[B], C[Any]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[Any], C[A]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[Any], C[B]))
|
||||||
```
|
```
|
||||||
|
|
||||||
## Contravariance
|
## Contravariance
|
||||||
|
|
||||||
With a contravariant typevar, subtyping are assignability are in "opposition": if `A <: B`, then
|
With a contravariant typevar, subtyping and assignability are in "opposition": if `A <: B` and
|
||||||
`C[B] <: C[A]`.
|
`C <: D`, then `C[B] <: C[A]` and `D[B] <: C[A]`.
|
||||||
|
|
||||||
Types that "consume" data are contravariant in their typevar. If you expect a consumer that receives
|
Types that "consume" data are contravariant in their typevar. If you expect a consumer that receives
|
||||||
`bool`s, someone can safely provide a consumer that expects to receive `int`s, since each `bool`
|
`bool`s, someone can safely provide a consumer that expects to receive `int`s, since each `bool`
|
||||||
|
@ -94,6 +136,9 @@ class B(A): ...
|
||||||
class C[T]:
|
class C[T]:
|
||||||
def send(self, value: T): ...
|
def send(self, value: T): ...
|
||||||
|
|
||||||
|
class D[U](C[U]):
|
||||||
|
pass
|
||||||
|
|
||||||
static_assert(not is_assignable_to(C[B], C[A]))
|
static_assert(not is_assignable_to(C[B], C[A]))
|
||||||
# TODO: no error
|
# TODO: no error
|
||||||
# error: [static-assert-error]
|
# error: [static-assert-error]
|
||||||
|
@ -103,6 +148,15 @@ static_assert(is_assignable_to(C[B], C[Any]))
|
||||||
static_assert(is_assignable_to(C[Any], C[A]))
|
static_assert(is_assignable_to(C[Any], C[A]))
|
||||||
static_assert(is_assignable_to(C[Any], C[B]))
|
static_assert(is_assignable_to(C[Any], C[B]))
|
||||||
|
|
||||||
|
static_assert(not is_assignable_to(D[B], C[A]))
|
||||||
|
# TODO: no error
|
||||||
|
# error: [static-assert-error]
|
||||||
|
static_assert(is_assignable_to(D[A], C[B]))
|
||||||
|
static_assert(is_assignable_to(D[A], C[Any]))
|
||||||
|
static_assert(is_assignable_to(D[B], C[Any]))
|
||||||
|
static_assert(is_assignable_to(D[Any], C[A]))
|
||||||
|
static_assert(is_assignable_to(D[Any], C[B]))
|
||||||
|
|
||||||
static_assert(not is_subtype_of(C[B], C[A]))
|
static_assert(not is_subtype_of(C[B], C[A]))
|
||||||
# TODO: no error
|
# TODO: no error
|
||||||
# error: [static-assert-error]
|
# error: [static-assert-error]
|
||||||
|
@ -112,6 +166,15 @@ static_assert(not is_subtype_of(C[B], C[Any]))
|
||||||
static_assert(not is_subtype_of(C[Any], C[A]))
|
static_assert(not is_subtype_of(C[Any], C[A]))
|
||||||
static_assert(not is_subtype_of(C[Any], C[B]))
|
static_assert(not is_subtype_of(C[Any], C[B]))
|
||||||
|
|
||||||
|
static_assert(not is_subtype_of(D[B], C[A]))
|
||||||
|
# TODO: no error
|
||||||
|
# error: [static-assert-error]
|
||||||
|
static_assert(is_subtype_of(D[A], C[B]))
|
||||||
|
static_assert(not is_subtype_of(D[A], C[Any]))
|
||||||
|
static_assert(not is_subtype_of(D[B], C[Any]))
|
||||||
|
static_assert(not is_subtype_of(D[Any], C[A]))
|
||||||
|
static_assert(not is_subtype_of(D[Any], C[B]))
|
||||||
|
|
||||||
static_assert(is_equivalent_to(C[A], C[A]))
|
static_assert(is_equivalent_to(C[A], C[A]))
|
||||||
static_assert(is_equivalent_to(C[B], C[B]))
|
static_assert(is_equivalent_to(C[B], C[B]))
|
||||||
static_assert(not is_equivalent_to(C[B], C[A]))
|
static_assert(not is_equivalent_to(C[B], C[A]))
|
||||||
|
@ -121,6 +184,15 @@ static_assert(not is_equivalent_to(C[B], C[Any]))
|
||||||
static_assert(not is_equivalent_to(C[Any], C[A]))
|
static_assert(not is_equivalent_to(C[Any], C[A]))
|
||||||
static_assert(not is_equivalent_to(C[Any], C[B]))
|
static_assert(not is_equivalent_to(C[Any], C[B]))
|
||||||
|
|
||||||
|
static_assert(not is_equivalent_to(D[A], C[A]))
|
||||||
|
static_assert(not is_equivalent_to(D[B], C[B]))
|
||||||
|
static_assert(not is_equivalent_to(D[B], C[A]))
|
||||||
|
static_assert(not is_equivalent_to(D[A], C[B]))
|
||||||
|
static_assert(not is_equivalent_to(D[A], C[Any]))
|
||||||
|
static_assert(not is_equivalent_to(D[B], C[Any]))
|
||||||
|
static_assert(not is_equivalent_to(D[Any], C[A]))
|
||||||
|
static_assert(not is_equivalent_to(D[Any], C[B]))
|
||||||
|
|
||||||
static_assert(is_gradual_equivalent_to(C[A], C[A]))
|
static_assert(is_gradual_equivalent_to(C[A], C[A]))
|
||||||
static_assert(is_gradual_equivalent_to(C[B], C[B]))
|
static_assert(is_gradual_equivalent_to(C[B], C[B]))
|
||||||
static_assert(is_gradual_equivalent_to(C[Any], C[Any]))
|
static_assert(is_gradual_equivalent_to(C[Any], C[Any]))
|
||||||
|
@ -131,12 +203,23 @@ static_assert(not is_gradual_equivalent_to(C[A], C[Any]))
|
||||||
static_assert(not is_gradual_equivalent_to(C[B], C[Any]))
|
static_assert(not is_gradual_equivalent_to(C[B], C[Any]))
|
||||||
static_assert(not is_gradual_equivalent_to(C[Any], C[A]))
|
static_assert(not is_gradual_equivalent_to(C[Any], C[A]))
|
||||||
static_assert(not is_gradual_equivalent_to(C[Any], C[B]))
|
static_assert(not is_gradual_equivalent_to(C[Any], C[B]))
|
||||||
|
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[A], C[A]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[B], C[B]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[Any], C[Any]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[Any], C[Unknown]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[B], C[A]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[A], C[B]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[A], C[Any]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[B], C[Any]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[Any], C[A]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[Any], C[B]))
|
||||||
```
|
```
|
||||||
|
|
||||||
## Invariance
|
## Invariance
|
||||||
|
|
||||||
With an invariant typevar, _no_ specializations of the generic class are subtypes of or assignable
|
With an invariant typevar, only equivalent specializations of the generic class are subtypes of or
|
||||||
to each other.
|
assignable to each other.
|
||||||
|
|
||||||
This often occurs for types that are both producers _and_ consumers, like a mutable `list`.
|
This often occurs for types that are both producers _and_ consumers, like a mutable `list`.
|
||||||
Iterating over the elements in a list would work with a covariant typevar, just like with the
|
Iterating over the elements in a list would work with a covariant typevar, just like with the
|
||||||
|
@ -167,6 +250,9 @@ class C[T]:
|
||||||
def receive(self) -> T:
|
def receive(self) -> T:
|
||||||
raise ValueError
|
raise ValueError
|
||||||
|
|
||||||
|
class D[U](C[U]):
|
||||||
|
pass
|
||||||
|
|
||||||
static_assert(not is_assignable_to(C[B], C[A]))
|
static_assert(not is_assignable_to(C[B], C[A]))
|
||||||
static_assert(not is_assignable_to(C[A], C[B]))
|
static_assert(not is_assignable_to(C[A], C[B]))
|
||||||
static_assert(is_assignable_to(C[A], C[Any]))
|
static_assert(is_assignable_to(C[A], C[Any]))
|
||||||
|
@ -174,6 +260,13 @@ static_assert(is_assignable_to(C[B], C[Any]))
|
||||||
static_assert(is_assignable_to(C[Any], C[A]))
|
static_assert(is_assignable_to(C[Any], C[A]))
|
||||||
static_assert(is_assignable_to(C[Any], C[B]))
|
static_assert(is_assignable_to(C[Any], C[B]))
|
||||||
|
|
||||||
|
static_assert(not is_assignable_to(D[B], C[A]))
|
||||||
|
static_assert(not is_assignable_to(D[A], C[B]))
|
||||||
|
static_assert(is_assignable_to(D[A], C[Any]))
|
||||||
|
static_assert(is_assignable_to(D[B], C[Any]))
|
||||||
|
static_assert(is_assignable_to(D[Any], C[A]))
|
||||||
|
static_assert(is_assignable_to(D[Any], C[B]))
|
||||||
|
|
||||||
static_assert(not is_subtype_of(C[B], C[A]))
|
static_assert(not is_subtype_of(C[B], C[A]))
|
||||||
static_assert(not is_subtype_of(C[A], C[B]))
|
static_assert(not is_subtype_of(C[A], C[B]))
|
||||||
static_assert(not is_subtype_of(C[A], C[Any]))
|
static_assert(not is_subtype_of(C[A], C[Any]))
|
||||||
|
@ -181,6 +274,13 @@ static_assert(not is_subtype_of(C[B], C[Any]))
|
||||||
static_assert(not is_subtype_of(C[Any], C[A]))
|
static_assert(not is_subtype_of(C[Any], C[A]))
|
||||||
static_assert(not is_subtype_of(C[Any], C[B]))
|
static_assert(not is_subtype_of(C[Any], C[B]))
|
||||||
|
|
||||||
|
static_assert(not is_subtype_of(D[B], C[A]))
|
||||||
|
static_assert(not is_subtype_of(D[A], C[B]))
|
||||||
|
static_assert(not is_subtype_of(D[A], C[Any]))
|
||||||
|
static_assert(not is_subtype_of(D[B], C[Any]))
|
||||||
|
static_assert(not is_subtype_of(D[Any], C[A]))
|
||||||
|
static_assert(not is_subtype_of(D[Any], C[B]))
|
||||||
|
|
||||||
static_assert(is_equivalent_to(C[A], C[A]))
|
static_assert(is_equivalent_to(C[A], C[A]))
|
||||||
static_assert(is_equivalent_to(C[B], C[B]))
|
static_assert(is_equivalent_to(C[B], C[B]))
|
||||||
static_assert(not is_equivalent_to(C[B], C[A]))
|
static_assert(not is_equivalent_to(C[B], C[A]))
|
||||||
|
@ -190,6 +290,15 @@ static_assert(not is_equivalent_to(C[B], C[Any]))
|
||||||
static_assert(not is_equivalent_to(C[Any], C[A]))
|
static_assert(not is_equivalent_to(C[Any], C[A]))
|
||||||
static_assert(not is_equivalent_to(C[Any], C[B]))
|
static_assert(not is_equivalent_to(C[Any], C[B]))
|
||||||
|
|
||||||
|
static_assert(not is_equivalent_to(D[A], C[A]))
|
||||||
|
static_assert(not is_equivalent_to(D[B], C[B]))
|
||||||
|
static_assert(not is_equivalent_to(D[B], C[A]))
|
||||||
|
static_assert(not is_equivalent_to(D[A], C[B]))
|
||||||
|
static_assert(not is_equivalent_to(D[A], C[Any]))
|
||||||
|
static_assert(not is_equivalent_to(D[B], C[Any]))
|
||||||
|
static_assert(not is_equivalent_to(D[Any], C[A]))
|
||||||
|
static_assert(not is_equivalent_to(D[Any], C[B]))
|
||||||
|
|
||||||
static_assert(is_gradual_equivalent_to(C[A], C[A]))
|
static_assert(is_gradual_equivalent_to(C[A], C[A]))
|
||||||
static_assert(is_gradual_equivalent_to(C[B], C[B]))
|
static_assert(is_gradual_equivalent_to(C[B], C[B]))
|
||||||
static_assert(is_gradual_equivalent_to(C[Any], C[Any]))
|
static_assert(is_gradual_equivalent_to(C[Any], C[Any]))
|
||||||
|
@ -200,6 +309,17 @@ static_assert(not is_gradual_equivalent_to(C[A], C[Any]))
|
||||||
static_assert(not is_gradual_equivalent_to(C[B], C[Any]))
|
static_assert(not is_gradual_equivalent_to(C[B], C[Any]))
|
||||||
static_assert(not is_gradual_equivalent_to(C[Any], C[A]))
|
static_assert(not is_gradual_equivalent_to(C[Any], C[A]))
|
||||||
static_assert(not is_gradual_equivalent_to(C[Any], C[B]))
|
static_assert(not is_gradual_equivalent_to(C[Any], C[B]))
|
||||||
|
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[A], C[A]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[B], C[B]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[Any], C[Any]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[Any], C[Unknown]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[B], C[A]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[A], C[B]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[A], C[Any]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[B], C[Any]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[Any], C[A]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[Any], C[B]))
|
||||||
```
|
```
|
||||||
|
|
||||||
## Bivariance
|
## Bivariance
|
||||||
|
@ -222,6 +342,9 @@ class B(A): ...
|
||||||
class C[T]:
|
class C[T]:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
class D[U](C[U]):
|
||||||
|
pass
|
||||||
|
|
||||||
# TODO: no error
|
# TODO: no error
|
||||||
# error: [static-assert-error]
|
# error: [static-assert-error]
|
||||||
static_assert(is_assignable_to(C[B], C[A]))
|
static_assert(is_assignable_to(C[B], C[A]))
|
||||||
|
@ -233,6 +356,17 @@ static_assert(is_assignable_to(C[B], C[Any]))
|
||||||
static_assert(is_assignable_to(C[Any], C[A]))
|
static_assert(is_assignable_to(C[Any], C[A]))
|
||||||
static_assert(is_assignable_to(C[Any], C[B]))
|
static_assert(is_assignable_to(C[Any], C[B]))
|
||||||
|
|
||||||
|
# TODO: no error
|
||||||
|
# error: [static-assert-error]
|
||||||
|
static_assert(is_assignable_to(D[B], C[A]))
|
||||||
|
# TODO: no error
|
||||||
|
# error: [static-assert-error]
|
||||||
|
static_assert(is_assignable_to(D[A], C[B]))
|
||||||
|
static_assert(is_assignable_to(D[A], C[Any]))
|
||||||
|
static_assert(is_assignable_to(D[B], C[Any]))
|
||||||
|
static_assert(is_assignable_to(D[Any], C[A]))
|
||||||
|
static_assert(is_assignable_to(D[Any], C[B]))
|
||||||
|
|
||||||
# TODO: no error
|
# TODO: no error
|
||||||
# error: [static-assert-error]
|
# error: [static-assert-error]
|
||||||
static_assert(is_subtype_of(C[B], C[A]))
|
static_assert(is_subtype_of(C[B], C[A]))
|
||||||
|
@ -244,6 +378,17 @@ static_assert(not is_subtype_of(C[B], C[Any]))
|
||||||
static_assert(not is_subtype_of(C[Any], C[A]))
|
static_assert(not is_subtype_of(C[Any], C[A]))
|
||||||
static_assert(not is_subtype_of(C[Any], C[B]))
|
static_assert(not is_subtype_of(C[Any], C[B]))
|
||||||
|
|
||||||
|
# TODO: no error
|
||||||
|
# error: [static-assert-error]
|
||||||
|
static_assert(is_subtype_of(D[B], C[A]))
|
||||||
|
# TODO: no error
|
||||||
|
# error: [static-assert-error]
|
||||||
|
static_assert(is_subtype_of(D[A], C[B]))
|
||||||
|
static_assert(not is_subtype_of(D[A], C[Any]))
|
||||||
|
static_assert(not is_subtype_of(D[B], C[Any]))
|
||||||
|
static_assert(not is_subtype_of(D[Any], C[A]))
|
||||||
|
static_assert(not is_subtype_of(D[Any], C[B]))
|
||||||
|
|
||||||
static_assert(is_equivalent_to(C[A], C[A]))
|
static_assert(is_equivalent_to(C[A], C[A]))
|
||||||
static_assert(is_equivalent_to(C[B], C[B]))
|
static_assert(is_equivalent_to(C[B], C[B]))
|
||||||
# TODO: no error
|
# TODO: no error
|
||||||
|
@ -257,6 +402,15 @@ static_assert(not is_equivalent_to(C[B], C[Any]))
|
||||||
static_assert(not is_equivalent_to(C[Any], C[A]))
|
static_assert(not is_equivalent_to(C[Any], C[A]))
|
||||||
static_assert(not is_equivalent_to(C[Any], C[B]))
|
static_assert(not is_equivalent_to(C[Any], C[B]))
|
||||||
|
|
||||||
|
static_assert(not is_equivalent_to(D[A], C[A]))
|
||||||
|
static_assert(not is_equivalent_to(D[B], C[B]))
|
||||||
|
static_assert(not is_equivalent_to(D[B], C[A]))
|
||||||
|
static_assert(not is_equivalent_to(D[A], C[B]))
|
||||||
|
static_assert(not is_equivalent_to(D[A], C[Any]))
|
||||||
|
static_assert(not is_equivalent_to(D[B], C[Any]))
|
||||||
|
static_assert(not is_equivalent_to(D[Any], C[A]))
|
||||||
|
static_assert(not is_equivalent_to(D[Any], C[B]))
|
||||||
|
|
||||||
static_assert(is_gradual_equivalent_to(C[A], C[A]))
|
static_assert(is_gradual_equivalent_to(C[A], C[A]))
|
||||||
static_assert(is_gradual_equivalent_to(C[B], C[B]))
|
static_assert(is_gradual_equivalent_to(C[B], C[B]))
|
||||||
static_assert(is_gradual_equivalent_to(C[Any], C[Any]))
|
static_assert(is_gradual_equivalent_to(C[Any], C[Any]))
|
||||||
|
@ -279,6 +433,17 @@ static_assert(is_gradual_equivalent_to(C[Any], C[A]))
|
||||||
# TODO: no error
|
# TODO: no error
|
||||||
# error: [static-assert-error]
|
# error: [static-assert-error]
|
||||||
static_assert(is_gradual_equivalent_to(C[Any], C[B]))
|
static_assert(is_gradual_equivalent_to(C[Any], C[B]))
|
||||||
|
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[A], C[A]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[B], C[B]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[Any], C[Any]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[Any], C[Unknown]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[B], C[A]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[A], C[B]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[A], C[Any]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[B], C[Any]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[Any], C[A]))
|
||||||
|
static_assert(not is_gradual_equivalent_to(D[Any], C[B]))
|
||||||
```
|
```
|
||||||
|
|
||||||
[spec]: https://typing.python.org/en/latest/spec/generics.html#variance
|
[spec]: https://typing.python.org/en/latest/spec/generics.html#variance
|
||||||
|
|
|
@ -334,10 +334,19 @@ static_assert(is_subtype_of(TypeOf[typing], ModuleType))
|
||||||
|
|
||||||
### Slice literals
|
### Slice literals
|
||||||
|
|
||||||
|
The type of a slice literal is currently inferred as `slice`, which is a generic type whose default
|
||||||
|
specialization includes `Any`. Slice literals therefore do not participate in the subtyping
|
||||||
|
relationship.
|
||||||
|
|
||||||
|
TODO: Infer a specialized type for the slice literal
|
||||||
|
|
||||||
```py
|
```py
|
||||||
from ty_extensions import TypeOf, is_subtype_of, static_assert
|
from ty_extensions import TypeOf, is_subtype_of, static_assert
|
||||||
|
|
||||||
static_assert(is_subtype_of(TypeOf[1:2:3], slice))
|
static_assert(not is_subtype_of(TypeOf[1:2:3], slice))
|
||||||
|
# TODO: no error
|
||||||
|
# error: [static-assert-error]
|
||||||
|
static_assert(is_subtype_of(TypeOf[1:2:3], slice[int]))
|
||||||
```
|
```
|
||||||
|
|
||||||
### Special forms
|
### Special forms
|
||||||
|
|
|
@ -1515,6 +1515,10 @@ impl<'db> Type<'db> {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
(Type::SliceLiteral(_), _) => KnownClass::Slice
|
||||||
|
.to_instance(db)
|
||||||
|
.is_assignable_to(db, target),
|
||||||
|
|
||||||
(Type::FunctionLiteral(self_function_literal), Type::Callable(_)) => {
|
(Type::FunctionLiteral(self_function_literal), Type::Callable(_)) => {
|
||||||
self_function_literal
|
self_function_literal
|
||||||
.into_callable_type(db)
|
.into_callable_type(db)
|
||||||
|
|
|
@ -290,109 +290,82 @@ impl<'db> ClassType<'db> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If `self` and `other` are generic aliases of the same generic class, returns their
|
|
||||||
/// corresponding specializations.
|
|
||||||
fn compatible_specializations(
|
|
||||||
self,
|
|
||||||
db: &'db dyn Db,
|
|
||||||
other: ClassType<'db>,
|
|
||||||
) -> Option<(Specialization<'db>, Specialization<'db>)> {
|
|
||||||
match (self, other) {
|
|
||||||
(ClassType::Generic(self_generic), ClassType::Generic(other_generic)) => {
|
|
||||||
if self_generic.origin(db) == other_generic.origin(db) {
|
|
||||||
Some((
|
|
||||||
self_generic.specialization(db),
|
|
||||||
other_generic.specialization(db),
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return `true` if `other` is present in this class's MRO.
|
/// Return `true` if `other` is present in this class's MRO.
|
||||||
pub(super) fn is_subclass_of(self, db: &'db dyn Db, other: ClassType<'db>) -> bool {
|
pub(super) fn is_subclass_of(self, db: &'db dyn Db, other: ClassType<'db>) -> bool {
|
||||||
// `is_subclass_of` is checking the subtype relation, in which gradual types do not
|
self.iter_mro(db).any(|base| {
|
||||||
// participate, so we should not return `True` if we find `Any/Unknown` in the MRO.
|
match base {
|
||||||
if self.iter_mro(db).contains(&ClassBase::Class(other)) {
|
// `is_subclass_of` is checking the subtype relation, in which gradual types do not
|
||||||
return true;
|
// participate.
|
||||||
}
|
ClassBase::Dynamic(_) => false,
|
||||||
|
|
||||||
// `self` is a subclass of `other` if they are both generic aliases of the same generic
|
// Protocol and Generic are not represented by a ClassType.
|
||||||
// class, and their specializations are compatible, taking into account the variance of the
|
ClassBase::Protocol | ClassBase::Generic(_) => false,
|
||||||
// class's typevars.
|
|
||||||
if let Some((self_specialization, other_specialization)) =
|
ClassBase::Class(base) => match (base, other) {
|
||||||
self.compatible_specializations(db, other)
|
(ClassType::NonGeneric(base), ClassType::NonGeneric(other)) => base == other,
|
||||||
{
|
(ClassType::Generic(base), ClassType::Generic(other)) => {
|
||||||
if self_specialization.is_subtype_of(db, other_specialization) {
|
base.origin(db) == other.origin(db)
|
||||||
return true;
|
&& base
|
||||||
|
.specialization(db)
|
||||||
|
.is_subtype_of(db, other.specialization(db))
|
||||||
|
}
|
||||||
|
(ClassType::Generic(_), ClassType::NonGeneric(_))
|
||||||
|
| (ClassType::NonGeneric(_), ClassType::Generic(_)) => false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn is_equivalent_to(self, db: &'db dyn Db, other: ClassType<'db>) -> bool {
|
pub(super) fn is_equivalent_to(self, db: &'db dyn Db, other: ClassType<'db>) -> bool {
|
||||||
if self == other {
|
match (self, other) {
|
||||||
return true;
|
(ClassType::NonGeneric(this), ClassType::NonGeneric(other)) => this == other,
|
||||||
}
|
(ClassType::NonGeneric(_), _) | (_, ClassType::NonGeneric(_)) => false,
|
||||||
|
|
||||||
// `self` is equivalent to `other` if they are both generic aliases of the same generic
|
(ClassType::Generic(this), ClassType::Generic(other)) => {
|
||||||
// class, and their specializations are compatible, taking into account the variance of the
|
this.origin(db) == other.origin(db)
|
||||||
// class's typevars.
|
&& this
|
||||||
if let Some((self_specialization, other_specialization)) =
|
.specialization(db)
|
||||||
self.compatible_specializations(db, other)
|
.is_equivalent_to(db, other.specialization(db))
|
||||||
{
|
|
||||||
if self_specialization.is_equivalent_to(db, other_specialization) {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn is_assignable_to(self, db: &'db dyn Db, other: ClassType<'db>) -> bool {
|
pub(super) fn is_assignable_to(self, db: &'db dyn Db, other: ClassType<'db>) -> bool {
|
||||||
if self.is_subclass_of(db, other) {
|
self.iter_mro(db).any(|base| {
|
||||||
return true;
|
match base {
|
||||||
}
|
ClassBase::Dynamic(DynamicType::Any | DynamicType::Unknown) => !other.is_final(db),
|
||||||
|
ClassBase::Dynamic(_) => false,
|
||||||
|
|
||||||
// `self` is assignable to `other` if they are both generic aliases of the same generic
|
// Protocol and Generic are not represented by a ClassType.
|
||||||
// class, and their specializations are compatible, taking into account the variance of the
|
ClassBase::Protocol | ClassBase::Generic(_) => false,
|
||||||
// class's typevars.
|
|
||||||
if let Some((self_specialization, other_specialization)) =
|
ClassBase::Class(base) => match (base, other) {
|
||||||
self.compatible_specializations(db, other)
|
(ClassType::NonGeneric(base), ClassType::NonGeneric(other)) => base == other,
|
||||||
{
|
(ClassType::Generic(base), ClassType::Generic(other)) => {
|
||||||
if self_specialization.is_assignable_to(db, other_specialization) {
|
base.origin(db) == other.origin(db)
|
||||||
return true;
|
&& base
|
||||||
|
.specialization(db)
|
||||||
|
.is_assignable_to(db, other.specialization(db))
|
||||||
|
}
|
||||||
|
(ClassType::Generic(_), ClassType::NonGeneric(_))
|
||||||
|
| (ClassType::NonGeneric(_), ClassType::Generic(_)) => false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
|
||||||
if self.is_subclass_of_any_or_unknown(db) && !other.is_final(db) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn is_gradual_equivalent_to(self, db: &'db dyn Db, other: ClassType<'db>) -> bool {
|
pub(super) fn is_gradual_equivalent_to(self, db: &'db dyn Db, other: ClassType<'db>) -> bool {
|
||||||
if self == other {
|
match (self, other) {
|
||||||
return true;
|
(ClassType::NonGeneric(this), ClassType::NonGeneric(other)) => this == other,
|
||||||
}
|
(ClassType::NonGeneric(_), _) | (_, ClassType::NonGeneric(_)) => false,
|
||||||
|
|
||||||
// `self` is equivalent to `other` if they are both generic aliases of the same generic
|
(ClassType::Generic(this), ClassType::Generic(other)) => {
|
||||||
// class, and their specializations are compatible, taking into account the variance of the
|
this.origin(db) == other.origin(db)
|
||||||
// class's typevars.
|
&& this
|
||||||
if let Some((self_specialization, other_specialization)) =
|
.specialization(db)
|
||||||
self.compatible_specializations(db, other)
|
.is_gradual_equivalent_to(db, other.specialization(db))
|
||||||
{
|
|
||||||
if self_specialization.is_gradual_equivalent_to(db, other_specialization) {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the metaclass of this class, or `type[Unknown]` if the metaclass cannot be inferred.
|
/// Return the metaclass of this class, or `type[Unknown]` if the metaclass cannot be inferred.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue