From 6bdffc3cbff2194afdff0e46fccb03cf6147d367 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Tue, 22 Apr 2025 15:14:10 +0100 Subject: [PATCH] [red-knot] Consider two instance types disjoint if the underlying classes have disjoint metaclasses (#17545) --- .../type_properties/is_disjoint_from.md | 20 +++++++++++++-- .../src/types/instance.rs | 25 +++++++++++++++++-- 2 files changed, 41 insertions(+), 4 deletions(-) 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 173e643f3a..981851a6c7 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 @@ -56,6 +56,19 @@ static_assert(not is_disjoint_from(FinalSubclass, A)) # ... which makes it disjoint from B1, B2: static_assert(is_disjoint_from(B1, FinalSubclass)) static_assert(is_disjoint_from(B2, FinalSubclass)) + +# Instance types can also be disjoint if they have disjoint metaclasses. +# No possible subclass of `Meta1` and `Meta2` could exist, therefore +# no possible subclass of `UsesMeta1` and `UsesMeta2` can exist: +class Meta1(type): ... +class UsesMeta1(metaclass=Meta1): ... + +@final +class Meta2(type): ... + +class UsesMeta2(metaclass=Meta2): ... + +static_assert(is_disjoint_from(UsesMeta1, UsesMeta2)) ``` ## Tuple types @@ -342,8 +355,8 @@ 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`. +By the same token, `type[T]` is disjoint from `type[S]` if `T` is `@final`, `S` is `@final`, or the +metaclass of `T` is disjoint from the metaclass of `S`. ```py from typing import final @@ -353,6 +366,9 @@ from knot_extensions import static_assert, is_disjoint_from class Meta1(type): ... class Meta2(type): ... + +static_assert(is_disjoint_from(type[Meta1], type[Meta2])) + class UsesMeta1(metaclass=Meta1): ... class UsesMeta2(metaclass=Meta2): ... diff --git a/crates/red_knot_python_semantic/src/types/instance.rs b/crates/red_knot_python_semantic/src/types/instance.rs index 1b0ec2f4fd..fb1b1deb76 100644 --- a/crates/red_knot_python_semantic/src/types/instance.rs +++ b/crates/red_knot_python_semantic/src/types/instance.rs @@ -43,8 +43,29 @@ impl<'db> InstanceType<'db> { } pub(super) fn is_disjoint_from(self, db: &'db dyn Db, other: Self) -> bool { - (self.class.is_final(db) && !self.class.is_subclass_of(db, other.class)) - || (other.class.is_final(db) && !other.class.is_subclass_of(db, self.class)) + if self.class.is_final(db) && !self.class.is_subclass_of(db, other.class) { + return true; + } + + if other.class.is_final(db) && !other.class.is_subclass_of(db, self.class) { + return true; + } + + // Check to see whether the metaclasses of `self` and `other` are disjoint. + // Avoid this check if the metaclass of either `self` or `other` is `type`, + // however, since we end up with infinite recursion in that case due to the fact + // that `type` is its own metaclass (and we know that `type` cannot be disjoint + // from any metaclass, anyway). + let type_type = KnownClass::Type.to_instance(db); + let self_metaclass = self.class.metaclass_instance_type(db); + if self_metaclass == type_type { + return false; + } + let other_metaclass = other.class.metaclass_instance_type(db); + if other_metaclass == type_type { + return false; + } + self_metaclass.is_disjoint_from(db, other_metaclass) } pub(super) fn is_gradual_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool {