Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -830,6 +830,42 @@ static_assert(not is_subtype_of(object, Any))
static_assert(is_subtype_of(int, Any | int))
static_assert(is_subtype_of(Intersection[Any, int], int))
static_assert(not is_subtype_of(tuple[int, int], tuple[int, Any]))

class Covariant[T]:
def get(self) -> T:
raise NotImplementedError

static_assert(not is_subtype_of(Covariant[Any], Covariant[Any]))
static_assert(not is_subtype_of(Covariant[Any], Covariant[int]))
static_assert(not is_subtype_of(Covariant[int], Covariant[Any]))
static_assert(is_subtype_of(Covariant[Any], Covariant[object]))
static_assert(not is_subtype_of(Covariant[object], Covariant[Any]))

class Contravariant[T]:
def receive(self, input: T): ...

static_assert(not is_subtype_of(Contravariant[Any], Contravariant[Any]))
static_assert(not is_subtype_of(Contravariant[Any], Contravariant[int]))
static_assert(not is_subtype_of(Contravariant[int], Contravariant[Any]))
static_assert(not is_subtype_of(Contravariant[Any], Contravariant[object]))
static_assert(is_subtype_of(Contravariant[object], Contravariant[Any]))

class Invariant[T]:
mutable_attribute: T

static_assert(not is_subtype_of(Invariant[Any], Invariant[Any]))
static_assert(not is_subtype_of(Invariant[Any], Invariant[int]))
static_assert(not is_subtype_of(Invariant[int], Invariant[Any]))
static_assert(not is_subtype_of(Invariant[Any], Invariant[object]))
static_assert(not is_subtype_of(Invariant[object], Invariant[Any]))

class Bivariant[T]: ...

static_assert(is_subtype_of(Bivariant[Any], Bivariant[Any]))
static_assert(is_subtype_of(Bivariant[Any], Bivariant[int]))
static_assert(is_subtype_of(Bivariant[int], Bivariant[Any]))
static_assert(is_subtype_of(Bivariant[Any], Bivariant[object]))
static_assert(is_subtype_of(Bivariant[object], Bivariant[Any]))
```

The same for `Unknown`:
Expand Down
26 changes: 15 additions & 11 deletions crates/ty_python_semantic/src/types/generics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -602,18 +602,22 @@ fn has_relation_in_invariant_position<'db>(
base_mat,
visitor,
),
// Subtyping between invariant type parameters without a top/bottom materialization involved
// is equivalence
(None, None, TypeRelation::Subtyping) => derived_type.when_equivalent_to(db, *base_type),
(None, None, TypeRelation::Assignability) => derived_type
.has_relation_to_impl(db, *base_type, TypeRelation::Assignability, visitor)
// Subtyping between invariant type parameters without a top/bottom materialization necessitates
// checking the subtyping relation both ways: `A` must be a subtype of `B` *and* `B` must be a
// subtype of `A`. The same applies to assignability.
//
// For subtyping between fully static types, this is the same as equivalence. However, we cannot
// use `is_equivalent_to` (or `when_equivalent_to`) here, because we (correctly) understand
// `list[Any]` as being equivalent to `list[Any]`, but we don't want `list[Any]` to be
// considered a subtype of `list[Any]`. For assignability, we would have the opposite issue if
// we simply checked for equivalence here: `Foo[Any]` should be considered assignable to
// `Foo[list[Any]]` even if `Foo` is invariant, and even though `Any` is not equivalent to
// `list[Any]`, because `Any` is assignable to `list[Any]` and `list[Any]` is assignable to
// `Any`.
(None, None, relation) => derived_type
.has_relation_to_impl(db, *base_type, relation, visitor)
.and(db, || {
base_type.has_relation_to_impl(
db,
*derived_type,
TypeRelation::Assignability,
visitor,
)
base_type.has_relation_to_impl(db, *derived_type, relation, visitor)
}),
// For gradual types, A <: B (subtyping) is defined as Top[A] <: Bottom[B]
(None, Some(base_mat), TypeRelation::Subtyping) => is_subtype_in_invariant_position(
Expand Down
Loading