[red-knot] Consider two instance types disjoint if the underlying classes have disjoint metaclasses (#17545)

This commit is contained in:
Alex Waygood 2025-04-22 15:14:10 +01:00 committed by GitHub
parent 775815ef22
commit 6bdffc3cbf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 41 additions and 4 deletions

View file

@ -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): ...

View file

@ -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 {