Skip to content

Commit 20eb5b5

Browse files
authored
[ty] Fix subtyping of invariant generics specialized with Any (#20650)
1 parent d9473a2 commit 20eb5b5

File tree

2 files changed

+51
-11
lines changed

2 files changed

+51
-11
lines changed

crates/ty_python_semantic/resources/mdtest/type_properties/is_subtype_of.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -830,6 +830,42 @@ static_assert(not is_subtype_of(object, Any))
830830
static_assert(is_subtype_of(int, Any | int))
831831
static_assert(is_subtype_of(Intersection[Any, int], int))
832832
static_assert(not is_subtype_of(tuple[int, int], tuple[int, Any]))
833+
834+
class Covariant[T]:
835+
def get(self) -> T:
836+
raise NotImplementedError
837+
838+
static_assert(not is_subtype_of(Covariant[Any], Covariant[Any]))
839+
static_assert(not is_subtype_of(Covariant[Any], Covariant[int]))
840+
static_assert(not is_subtype_of(Covariant[int], Covariant[Any]))
841+
static_assert(is_subtype_of(Covariant[Any], Covariant[object]))
842+
static_assert(not is_subtype_of(Covariant[object], Covariant[Any]))
843+
844+
class Contravariant[T]:
845+
def receive(self, input: T): ...
846+
847+
static_assert(not is_subtype_of(Contravariant[Any], Contravariant[Any]))
848+
static_assert(not is_subtype_of(Contravariant[Any], Contravariant[int]))
849+
static_assert(not is_subtype_of(Contravariant[int], Contravariant[Any]))
850+
static_assert(not is_subtype_of(Contravariant[Any], Contravariant[object]))
851+
static_assert(is_subtype_of(Contravariant[object], Contravariant[Any]))
852+
853+
class Invariant[T]:
854+
mutable_attribute: T
855+
856+
static_assert(not is_subtype_of(Invariant[Any], Invariant[Any]))
857+
static_assert(not is_subtype_of(Invariant[Any], Invariant[int]))
858+
static_assert(not is_subtype_of(Invariant[int], Invariant[Any]))
859+
static_assert(not is_subtype_of(Invariant[Any], Invariant[object]))
860+
static_assert(not is_subtype_of(Invariant[object], Invariant[Any]))
861+
862+
class Bivariant[T]: ...
863+
864+
static_assert(is_subtype_of(Bivariant[Any], Bivariant[Any]))
865+
static_assert(is_subtype_of(Bivariant[Any], Bivariant[int]))
866+
static_assert(is_subtype_of(Bivariant[int], Bivariant[Any]))
867+
static_assert(is_subtype_of(Bivariant[Any], Bivariant[object]))
868+
static_assert(is_subtype_of(Bivariant[object], Bivariant[Any]))
833869
```
834870

835871
The same for `Unknown`:

crates/ty_python_semantic/src/types/generics.rs

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -602,18 +602,22 @@ fn has_relation_in_invariant_position<'db>(
602602
base_mat,
603603
visitor,
604604
),
605-
// Subtyping between invariant type parameters without a top/bottom materialization involved
606-
// is equivalence
607-
(None, None, TypeRelation::Subtyping) => derived_type.when_equivalent_to(db, *base_type),
608-
(None, None, TypeRelation::Assignability) => derived_type
609-
.has_relation_to_impl(db, *base_type, TypeRelation::Assignability, visitor)
605+
// Subtyping between invariant type parameters without a top/bottom materialization necessitates
606+
// checking the subtyping relation both ways: `A` must be a subtype of `B` *and* `B` must be a
607+
// subtype of `A`. The same applies to assignability.
608+
//
609+
// For subtyping between fully static types, this is the same as equivalence. However, we cannot
610+
// use `is_equivalent_to` (or `when_equivalent_to`) here, because we (correctly) understand
611+
// `list[Any]` as being equivalent to `list[Any]`, but we don't want `list[Any]` to be
612+
// considered a subtype of `list[Any]`. For assignability, we would have the opposite issue if
613+
// we simply checked for equivalence here: `Foo[Any]` should be considered assignable to
614+
// `Foo[list[Any]]` even if `Foo` is invariant, and even though `Any` is not equivalent to
615+
// `list[Any]`, because `Any` is assignable to `list[Any]` and `list[Any]` is assignable to
616+
// `Any`.
617+
(None, None, relation) => derived_type
618+
.has_relation_to_impl(db, *base_type, relation, visitor)
610619
.and(db, || {
611-
base_type.has_relation_to_impl(
612-
db,
613-
*derived_type,
614-
TypeRelation::Assignability,
615-
visitor,
616-
)
620+
base_type.has_relation_to_impl(db, *derived_type, relation, visitor)
617621
}),
618622
// For gradual types, A <: B (subtyping) is defined as Top[A] <: Bottom[B]
619623
(None, Some(base_mat), TypeRelation::Subtyping) => is_subtype_in_invariant_position(

0 commit comments

Comments
 (0)