diff --git a/crates/ty_python_semantic/resources/mdtest/generics/legacy/variance.md b/crates/ty_python_semantic/resources/mdtest/generics/legacy/variance.md index 47054496a8..d66e2d913d 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/legacy/variance.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/legacy/variance.md @@ -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 diff --git a/crates/ty_python_semantic/resources/mdtest/generics/pep695/variance.md b/crates/ty_python_semantic/resources/mdtest/generics/pep695/variance.md index b4c4b9a6c2..2e5b9507c6 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/pep695/variance.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/pep695/variance.md @@ -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 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.) +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 -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 @@ -36,6 +37,9 @@ class C[T]: def receive(self) -> T: raise ValueError +class D[U](C[U]): + pass + # TODO: no error # error: [static-assert-error] 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[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 # error: [static-assert-error] 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[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[B], C[B])) 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[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])) @@ -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[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 are 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` @@ -94,6 +136,9 @@ class B(A): ... class C[T]: def send(self, value: T): ... +class D[U](C[U]): + pass + static_assert(not is_assignable_to(C[B], C[A])) # TODO: no 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[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])) # TODO: no 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[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[B], C[B])) 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[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])) @@ -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[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 -With an invariant typevar, _no_ specializations of the generic class are subtypes of or assignable -to each other. +With an invariant typevar, only equivalent specializations of the generic class are subtypes of or +assignable to each other. 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 @@ -167,6 +250,9 @@ class C[T]: def receive(self) -> T: 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[A], C[B])) 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[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])) @@ -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[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])) @@ -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[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])) @@ -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[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 @@ -222,6 +342,9 @@ class B(A): ... class C[T]: pass +class D[U](C[U]): + pass + # TODO: no error # error: [static-assert-error] 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[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 # error: [static-assert-error] 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[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[B], C[B])) # 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[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])) @@ -279,6 +433,17 @@ static_assert(is_gradual_equivalent_to(C[Any], C[A])) # TODO: no error # error: [static-assert-error] 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 diff --git a/crates/ty_python_semantic/resources/mdtest/type_properties/is_subtype_of.md b/crates/ty_python_semantic/resources/mdtest/type_properties/is_subtype_of.md index 42b2f23572..4db6466b58 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_properties/is_subtype_of.md +++ b/crates/ty_python_semantic/resources/mdtest/type_properties/is_subtype_of.md @@ -334,10 +334,19 @@ static_assert(is_subtype_of(TypeOf[typing], ModuleType)) ### 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 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 diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index bf3b04ce58..89df7ede2f 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -1515,6 +1515,10 @@ impl<'db> Type<'db> { false } + (Type::SliceLiteral(_), _) => KnownClass::Slice + .to_instance(db) + .is_assignable_to(db, target), + (Type::FunctionLiteral(self_function_literal), Type::Callable(_)) => { self_function_literal .into_callable_type(db) diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index 0cc234c59e..bc6b0dda99 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -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. 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 - // participate, so we should not return `True` if we find `Any/Unknown` in the MRO. - if self.iter_mro(db).contains(&ClassBase::Class(other)) { - return true; - } + self.iter_mro(db).any(|base| { + match base { + // `is_subclass_of` is checking the subtype relation, in which gradual types do not + // participate. + ClassBase::Dynamic(_) => false, - // `self` is a subclass of `other` if they are both generic aliases of the same generic - // class, and their specializations are compatible, taking into account the variance of the - // class's typevars. - if let Some((self_specialization, other_specialization)) = - self.compatible_specializations(db, other) - { - if self_specialization.is_subtype_of(db, other_specialization) { - return true; + // Protocol and Generic are not represented by a ClassType. + ClassBase::Protocol | ClassBase::Generic(_) => false, + + ClassBase::Class(base) => match (base, other) { + (ClassType::NonGeneric(base), ClassType::NonGeneric(other)) => base == other, + (ClassType::Generic(base), ClassType::Generic(other)) => { + base.origin(db) == other.origin(db) + && 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 { - if self == other { - return true; - } + match (self, other) { + (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 - // class, and their specializations are compatible, taking into account the variance of the - // class's typevars. - if let Some((self_specialization, other_specialization)) = - self.compatible_specializations(db, other) - { - if self_specialization.is_equivalent_to(db, other_specialization) { - return true; + (ClassType::Generic(this), ClassType::Generic(other)) => { + this.origin(db) == other.origin(db) + && this + .specialization(db) + .is_equivalent_to(db, other.specialization(db)) } } - - false } pub(super) fn is_assignable_to(self, db: &'db dyn Db, other: ClassType<'db>) -> bool { - if self.is_subclass_of(db, other) { - return true; - } + self.iter_mro(db).any(|base| { + 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 - // class, and their specializations are compatible, taking into account the variance of the - // class's typevars. - if let Some((self_specialization, other_specialization)) = - self.compatible_specializations(db, other) - { - if self_specialization.is_assignable_to(db, other_specialization) { - return true; + // Protocol and Generic are not represented by a ClassType. + ClassBase::Protocol | ClassBase::Generic(_) => false, + + ClassBase::Class(base) => match (base, other) { + (ClassType::NonGeneric(base), ClassType::NonGeneric(other)) => base == other, + (ClassType::Generic(base), ClassType::Generic(other)) => { + base.origin(db) == other.origin(db) + && 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 { - if self == other { - return true; - } + match (self, other) { + (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 - // class, and their specializations are compatible, taking into account the variance of the - // class's typevars. - if let Some((self_specialization, other_specialization)) = - self.compatible_specializations(db, other) - { - if self_specialization.is_gradual_equivalent_to(db, other_specialization) { - return true; + (ClassType::Generic(this), ClassType::Generic(other)) => { + this.origin(db) == other.origin(db) + && this + .specialization(db) + .is_gradual_equivalent_to(db, other.specialization(db)) } } - - false } /// Return the metaclass of this class, or `type[Unknown]` if the metaclass cannot be inferred.