mirror of
https://github.com/astral-sh/ruff.git
synced 2025-11-25 22:29:02 +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
|
||||
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
|
||||
`C[T]`, and two types `A` and `B`.
|
||||
For all of the examples below, we will consider typevars `T` and `U`, two generic classes using
|
||||
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
|
||||
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
|
||||
|
||||
With a covariant typevar, subtyping and assignability are in "alignment": if `A <: B`, then
|
||||
`C[A] <: C[B]`.
|
||||
With a covariant typevar, subtyping and assignability are in "alignment": if `A <: B` and `C <: D`,
|
||||
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
|
||||
`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): ...
|
||||
|
||||
T = TypeVar("T", covariant=True)
|
||||
U = TypeVar("U", covariant=True)
|
||||
|
||||
class C(Generic[T]):
|
||||
def receive(self) -> T:
|
||||
raise ValueError
|
||||
|
||||
class D(C[U]):
|
||||
pass
|
||||
|
||||
static_assert(is_assignable_to(C[B], C[A]))
|
||||
static_assert(not is_assignable_to(C[A], C[B]))
|
||||
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[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(not is_subtype_of(C[A], C[B]))
|
||||
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[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[B], C[B]))
|
||||
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[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[B], C[B]))
|
||||
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[Any], C[A]))
|
||||
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
|
||||
|
||||
With a contravariant typevar, subtyping and assignability are in "opposition": if `A <: B`, then
|
||||
`C[B] <: C[A]`.
|
||||
With a contravariant typevar, subtyping and assignability are in "opposition": if `A <: B` and
|
||||
`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
|
||||
`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): ...
|
||||
|
||||
T = TypeVar("T", contravariant=True)
|
||||
U = TypeVar("U", contravariant=True)
|
||||
|
||||
class C(Generic[T]):
|
||||
def send(self, value: T): ...
|
||||
|
||||
class D(C[U]):
|
||||
pass
|
||||
|
||||
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[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[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(is_subtype_of(C[A], C[B]))
|
||||
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[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[B], C[B]))
|
||||
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[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[B], C[B]))
|
||||
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[Any], C[A]))
|
||||
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
|
||||
|
|
@ -155,12 +231,16 @@ class A: ...
|
|||
class B(A): ...
|
||||
|
||||
T = TypeVar("T")
|
||||
U = TypeVar("U")
|
||||
|
||||
class C(Generic[T]):
|
||||
def send(self, value: T): ...
|
||||
def receive(self) -> T:
|
||||
raise ValueError
|
||||
|
||||
class D(C[U]):
|
||||
pass
|
||||
|
||||
static_assert(not is_assignable_to(C[B], C[A]))
|
||||
static_assert(not is_assignable_to(C[A], C[B]))
|
||||
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[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[A], C[B]))
|
||||
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[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[B], C[B]))
|
||||
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[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[B], C[B]))
|
||||
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[Any], C[A]))
|
||||
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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue