diff --git a/crates/ty_python_semantic/resources/mdtest/type_of/generics.md b/crates/ty_python_semantic/resources/mdtest/type_of/generics.md index ee998bbb1b..3c8f157dad 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_of/generics.md +++ b/crates/ty_python_semantic/resources/mdtest/type_of/generics.md @@ -227,6 +227,17 @@ def _[T: (int | str, int)](_: T): static_assert(not is_disjoint_from(type[int], type[T])) ``` +```py +class X[T]: + value: T + + def get(self) -> T: + return self.value + +def _[T](x: X[type[T]]): + reveal_type(x.get()) # revealed: type[T@_] +``` + ## Generic Type Inference ```py diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 7c65a4715e..e64d3802d5 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -2105,33 +2105,52 @@ impl<'db> Type<'db> { }) .is_never_satisfied(db) => { - // TODO: The repetition here isn't great, but we really need the fallthrough logic, - // where this arm only engages if it returns true. - let this_instance = Type::TypeVar(subclass_of.into_type_var().unwrap()); - target.to_instance(db).when_some_and(|other_instance| { - this_instance.has_relation_to_impl( - db, - other_instance, - inferable, - relation, - relation_visitor, - disjointness_visitor, - ) - }) + // TODO: The repetition here isn't great, but we need the fallthrough logic. + subclass_of + .into_type_var() + .zip(target.to_instance(db)) + .when_some_and(|(this_instance, other_instance)| { + Type::TypeVar(this_instance).has_relation_to_impl( + db, + other_instance, + inferable, + relation, + relation_visitor, + disjointness_visitor, + ) + }) } - (_, Type::SubclassOf(subclass_of)) if subclass_of.is_type_var() => { - let other_instance = Type::TypeVar(subclass_of.into_type_var().unwrap()); - self.to_instance(db).when_some_and(|this_instance| { - this_instance.has_relation_to_impl( - db, - other_instance, - inferable, - relation, - relation_visitor, - disjointness_visitor, - ) - }) + (_, Type::SubclassOf(subclass_of)) + if !subclass_of + .into_type_var() + .zip(self.to_instance(db)) + .when_some_and(|(other_instance, this_instance)| { + this_instance.has_relation_to_impl( + db, + Type::TypeVar(other_instance), + inferable, + relation, + relation_visitor, + disjointness_visitor, + ) + }) + .is_never_satisfied(db) => + { + // TODO: The repetition here isn't great, but we need the fallthrough logic. + subclass_of + .into_type_var() + .zip(self.to_instance(db)) + .when_some_and(|(other_instance, this_instance)| { + this_instance.has_relation_to_impl( + db, + Type::TypeVar(other_instance), + inferable, + relation, + relation_visitor, + disjointness_visitor, + ) + }) } // A fully static typevar is a subtype of its upper bound, and to something similar to @@ -2656,7 +2675,9 @@ impl<'db> Type<'db> { disjointness_visitor, ), - (Type::SubclassOf(subclass_of), _) if subclass_of.is_type_var() => { + (Type::SubclassOf(subclass_of), _) | (_, Type::SubclassOf(subclass_of)) + if subclass_of.is_type_var() => + { ConstraintSet::from(false) } @@ -3116,33 +3137,33 @@ impl<'db> Type<'db> { // `type[T]` is disjoint from a class object `A` if every instance of `T` is disjoint from an instance of `A`. (Type::SubclassOf(subclass_of), other) | (other, Type::SubclassOf(subclass_of)) - if subclass_of.is_type_var() - && (other.to_instance(db).is_some() - || other.as_typevar().is_some_and(|type_var| { - type_var.typevar(db).bound_or_constraints(db).is_none() - })) => + if !subclass_of + .into_type_var() + .zip(other.to_instance(db)) + .when_none_or(|(this_instance, other_instance)| { + Type::TypeVar(this_instance).is_disjoint_from_impl( + db, + other_instance, + inferable, + disjointness_visitor, + relation_visitor, + ) + }) + .is_always_satisfied(db) => { - let this_instance = Type::TypeVar(subclass_of.into_type_var().unwrap()); - let other_instance = match other { - // An unbounded typevar `U` may have instances of type `object` if specialized to - // an instance of `type`. - Type::TypeVar(typevar) - if typevar.typevar(db).bound_or_constraints(db).is_none() => - { - Some(Type::object()) - } - _ => other.to_instance(db), - }; - - other_instance.when_none_or(|other_instance| { - this_instance.is_disjoint_from_impl( - db, - other_instance, - inferable, - disjointness_visitor, - relation_visitor, - ) - }) + // TODO: The repetition here isn't great, but we need the fallthrough logic. + subclass_of + .into_type_var() + .zip(other.to_instance(db)) + .when_none_or(|(this_instance, other_instance)| { + Type::TypeVar(this_instance).is_disjoint_from_impl( + db, + other_instance, + inferable, + disjointness_visitor, + relation_visitor, + ) + }) } // A typevar is never disjoint from itself, since all occurrences of the typevar must