diff --git a/crates/red_knot_python_semantic/resources/mdtest/narrow/issubclass.md b/crates/red_knot_python_semantic/resources/mdtest/narrow/issubclass.md index 119f767c38..9518c60b1c 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/narrow/issubclass.md +++ b/crates/red_knot_python_semantic/resources/mdtest/narrow/issubclass.md @@ -246,3 +246,31 @@ def _(x: type, y: type[int]): if issubclass(x, y): reveal_type(x) # revealed: type[int] ``` + +### Disjoint `type[]` types are narrowed to `Never` + +Here, `type[UsesMeta1]` and `type[UsesMeta2]` are disjoint because a common subclass of `UsesMeta1` +and `UsesMeta2` could only exist if a common subclass of their metaclasses could exist. This is +known to be impossible due to the fact that `Meta1` is marked as `@final`. + +```py +from typing import final + +@final +class Meta1(type): ... + +class Meta2(type): ... +class UsesMeta1(metaclass=Meta1): ... +class UsesMeta2(metaclass=Meta2): ... + +def _(x: type[UsesMeta1], y: type[UsesMeta2]): + if issubclass(x, y): + reveal_type(x) # revealed: Never + else: + reveal_type(x) # revealed: type[UsesMeta1] + + if issubclass(y, x): + reveal_type(y) # revealed: Never + else: + reveal_type(y) # revealed: type[UsesMeta2] +``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/type_properties/is_disjoint_from.md b/crates/red_knot_python_semantic/resources/mdtest/type_properties/is_disjoint_from.md index b6d85cf5bf..1652598acc 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/type_properties/is_disjoint_from.md +++ b/crates/red_knot_python_semantic/resources/mdtest/type_properties/is_disjoint_from.md @@ -37,3 +37,22 @@ class UsesMeta2(metaclass=Meta2): ... static_assert(not is_disjoint_from(Meta2, type[UsesMeta2])) static_assert(is_disjoint_from(Meta1, type[UsesMeta2])) ``` + +## `type[T]` versus `type[S]` + +By the same token, `type[T]` is disjoint from `type[S]` if the metaclass of `T` is disjoint from the +metaclass of `S`. + +```py +from typing import final +from knot_extensions import static_assert, is_disjoint_from + +@final +class Meta1(type): ... + +class Meta2(type): ... +class UsesMeta1(metaclass=Meta1): ... +class UsesMeta2(metaclass=Meta2): ... + +static_assert(is_disjoint_from(type[UsesMeta1], type[UsesMeta2])) +``` diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index a5ff0358d8..310d483e89 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -1276,21 +1276,6 @@ impl<'db> Type<'db> { ClassBase::Class(class_a) => !class_b.is_subclass_of(db, class_a), }, - (Type::SubclassOf(_), Type::SubclassOf(_)) => false, - - (Type::SubclassOf(subclass_of_ty), instance @ Type::Instance(_)) - | (instance @ Type::Instance(_), Type::SubclassOf(subclass_of_ty)) => { - // `type[T]` is disjoint from `S`, where `S` is an instance type, - // if `U` is disjoint from `S`, - // where `U` represents all instances of `T`'s metaclass - let metaclass_instance = subclass_of_ty - .subclass_of() - .into_class() - .map(|class| class.metaclass(db).to_instance(db)) - .unwrap_or_else(|| KnownClass::Type.to_instance(db)); - instance.is_disjoint_from(db, metaclass_instance) - } - ( Type::SubclassOf(_), Type::BooleanLiteral(..) @@ -1324,12 +1309,9 @@ impl<'db> Type<'db> { ty.bool(db).is_always_true() } - (Type::SubclassOf(_), other) | (other, Type::SubclassOf(_)) => { - // TODO we could do better here: if both variants are `SubclassOf` and they have different "solid bases", - // multiple inheritance between the two is impossible, so they are disjoint. - // - // Note that `type[<@final class>]` is eagerly simplified to `Literal[<@final class>]` by [`SubclassOfType::from`]. - other.is_disjoint_from(db, KnownClass::Type.to_instance(db)) + (Type::SubclassOf(subclass_of_ty), other) + | (other, Type::SubclassOf(subclass_of_ty)) => { + other.is_disjoint_from(db, subclass_of_ty.as_instance_type_of_metaclass(db)) } (Type::KnownInstance(known_instance), Type::Instance(InstanceType { class })) diff --git a/crates/red_knot_python_semantic/src/types/subclass_of.rs b/crates/red_knot_python_semantic/src/types/subclass_of.rs index fe5cc5cd98..6ea9817107 100644 --- a/crates/red_knot_python_semantic/src/types/subclass_of.rs +++ b/crates/red_knot_python_semantic/src/types/subclass_of.rs @@ -68,6 +68,15 @@ impl<'db> SubclassOfType<'db> { Type::from(self.subclass_of).member(db, name) } + /// A class `T` is an instance of its metaclass `U`, + /// so the type `type[T]` is a subtype of the instance type `U`. + pub(crate) fn as_instance_type_of_metaclass(&self, db: &'db dyn Db) -> Type<'db> { + match self.subclass_of { + ClassBase::Dynamic(_) => KnownClass::Type.to_instance(db), + ClassBase::Class(class) => class.metaclass(db).to_instance(db), + } + } + /// Return `true` if `self` is a subtype of `other`. /// /// This can only return `true` if `self.subclass_of` is a [`ClassBase::Class`] variant;