mirror of https://github.com/astral-sh/ruff
[red-knot] Make `is_subtype_of` exhaustive (#14924)
This commit is contained in:
parent
9798556eb5
commit
4b2b126b9f
|
|
@ -615,104 +615,52 @@ impl<'db> Type<'db> {
|
|||
///
|
||||
/// [subtype of]: https://typing.readthedocs.io/en/latest/spec/concepts.html#subtype-supertype-and-type-equivalence
|
||||
pub(crate) fn is_subtype_of(self, db: &'db dyn Db, target: Type<'db>) -> bool {
|
||||
// Two equivalent types are always subtypes of each other.
|
||||
//
|
||||
// "Equivalent to" here means that the two types are both fully static
|
||||
// and describe exactly the same set of possible runtime objects.
|
||||
// For example, `int` is a subtype of `int` because `int` and `int` are equivalent to each other.
|
||||
// Equally, `type[object]` is a subtype of `type`,
|
||||
// because the former type expresses "all subclasses of `object`"
|
||||
// while the latter expresses "all instances of `type`",
|
||||
// and these are exactly the same set of objects at runtime.
|
||||
if self.is_equivalent_to(db, target) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Non-fully-static types do not participate in subtyping.
|
||||
//
|
||||
// Type `A` can only be a subtype of type `B` if the set of possible runtime objects
|
||||
// that `A` represents is a subset of the set of possible runtime objects that `B` represents.
|
||||
// But the set of objects described by a non-fully-static type is (either partially or wholly) unknown,
|
||||
// so the question is simply unanswerable for non-fully-static types.
|
||||
if !self.is_fully_static(db) || !target.is_fully_static(db) {
|
||||
return false;
|
||||
}
|
||||
|
||||
match (self, target) {
|
||||
// We should have handled these immediately above.
|
||||
(Type::Any | Type::Unknown | Type::Todo(_), _)
|
||||
| (_, Type::Any | Type::Unknown | Type::Todo(_)) => {
|
||||
unreachable!("Non-fully-static types do not participate in subtyping!")
|
||||
}
|
||||
|
||||
// `Never` is the bottom type, the empty set.
|
||||
// It is a subtype of all other fully static types.
|
||||
// No other fully static type is a subtype of `Never`.
|
||||
(Type::Never, _) => true,
|
||||
(_, Type::Never) => false,
|
||||
(_, Type::Instance(InstanceType { class }))
|
||||
if class.is_known(db, KnownClass::Object) =>
|
||||
{
|
||||
true
|
||||
}
|
||||
(Type::Instance(InstanceType { class }), _)
|
||||
if class.is_known(db, KnownClass::Object) =>
|
||||
{
|
||||
false
|
||||
}
|
||||
(Type::BooleanLiteral(_), Type::Instance(InstanceType { class }))
|
||||
if matches!(class.known(db), Some(KnownClass::Bool | KnownClass::Int)) =>
|
||||
{
|
||||
true
|
||||
}
|
||||
(Type::IntLiteral(_), Type::Instance(InstanceType { class }))
|
||||
if class.is_known(db, KnownClass::Int) =>
|
||||
{
|
||||
true
|
||||
}
|
||||
(Type::StringLiteral(_), Type::LiteralString) => true,
|
||||
(
|
||||
Type::StringLiteral(_) | Type::LiteralString,
|
||||
Type::Instance(InstanceType { class }),
|
||||
) if class.is_known(db, KnownClass::Str) => true,
|
||||
(Type::BytesLiteral(_), Type::Instance(InstanceType { class }))
|
||||
if class.is_known(db, KnownClass::Bytes) =>
|
||||
{
|
||||
true
|
||||
}
|
||||
(Type::Tuple(self_tuple), Type::Tuple(target_tuple)) => {
|
||||
let self_elements = self_tuple.elements(db);
|
||||
let target_elements = target_tuple.elements(db);
|
||||
self_elements.len() == target_elements.len()
|
||||
&& self_elements.iter().zip(target_elements).all(
|
||||
|(self_element, target_element)| {
|
||||
self_element.is_subtype_of(db, *target_element)
|
||||
},
|
||||
)
|
||||
}
|
||||
(
|
||||
Type::ClassLiteral(ClassLiteralType { class: self_class }),
|
||||
Type::SubclassOf(SubclassOfType {
|
||||
base: ClassBase::Class(target_class),
|
||||
}),
|
||||
) => self_class.is_subclass_of(db, target_class),
|
||||
(
|
||||
Type::Instance(_),
|
||||
Type::SubclassOf(SubclassOfType {
|
||||
base: ClassBase::Class(target_class),
|
||||
}),
|
||||
) if target_class.is_known(db, KnownClass::Object) => {
|
||||
self.is_subtype_of(db, KnownClass::Type.to_instance(db))
|
||||
}
|
||||
(
|
||||
Type::SubclassOf(SubclassOfType {
|
||||
base: ClassBase::Class(self_class),
|
||||
}),
|
||||
Type::SubclassOf(SubclassOfType {
|
||||
base: ClassBase::Class(target_class),
|
||||
}),
|
||||
) => self_class.is_subclass_of(db, target_class),
|
||||
// C ⊆ type
|
||||
// type[C] ⊆ type
|
||||
// Though note that this works regardless of which metaclass C has, not just for type.
|
||||
(
|
||||
Type::ClassLiteral(ClassLiteralType { class: self_class })
|
||||
| Type::SubclassOf(SubclassOfType {
|
||||
base: ClassBase::Class(self_class),
|
||||
}),
|
||||
Type::Instance(InstanceType {
|
||||
class: target_class,
|
||||
}),
|
||||
) if self_class
|
||||
.metaclass(db)
|
||||
.into_class_literal()
|
||||
.map(|meta| meta.class.is_subclass_of(db, target_class))
|
||||
.unwrap_or(false) =>
|
||||
{
|
||||
true
|
||||
}
|
||||
(Type::Union(union), ty) => union
|
||||
|
||||
(Type::Union(union), _) => union
|
||||
.elements(db)
|
||||
.iter()
|
||||
.all(|&elem_ty| elem_ty.is_subtype_of(db, ty)),
|
||||
(ty, Type::Union(union)) => union
|
||||
.all(|&elem_ty| elem_ty.is_subtype_of(db, target)),
|
||||
|
||||
(_, Type::Union(union)) => union
|
||||
.elements(db)
|
||||
.iter()
|
||||
.any(|&elem_ty| ty.is_subtype_of(db, elem_ty)),
|
||||
.any(|&elem_ty| self.is_subtype_of(db, elem_ty)),
|
||||
|
||||
(Type::Intersection(self_intersection), Type::Intersection(target_intersection)) => {
|
||||
// Check that all target positive values are covered in self positive values
|
||||
target_intersection
|
||||
|
|
@ -735,30 +683,168 @@ impl<'db> Type<'db> {
|
|||
target_neg_elem.is_subtype_of(db, self_neg_elem)
|
||||
// Is target negative value is disjoint from a self positive value?
|
||||
}) || self_intersection.positive(db).iter().any(|&self_pos_elem| {
|
||||
target_neg_elem.is_disjoint_from(db, self_pos_elem)
|
||||
self_pos_elem.is_disjoint_from(db, target_neg_elem)
|
||||
})
|
||||
})
|
||||
}
|
||||
(Type::Intersection(intersection), ty) => intersection
|
||||
|
||||
(Type::Intersection(intersection), _) => intersection
|
||||
.positive(db)
|
||||
.iter()
|
||||
.any(|&elem_ty| elem_ty.is_subtype_of(db, ty)),
|
||||
(ty, Type::Intersection(intersection)) => {
|
||||
.any(|&elem_ty| elem_ty.is_subtype_of(db, target)),
|
||||
|
||||
(_, Type::Intersection(intersection)) => {
|
||||
intersection
|
||||
.positive(db)
|
||||
.iter()
|
||||
.all(|&pos_ty| ty.is_subtype_of(db, pos_ty))
|
||||
.all(|&pos_ty| self.is_subtype_of(db, pos_ty))
|
||||
&& intersection
|
||||
.negative(db)
|
||||
.iter()
|
||||
.all(|&neg_ty| neg_ty.is_disjoint_from(db, ty))
|
||||
.all(|&neg_ty| self.is_disjoint_from(db, neg_ty))
|
||||
}
|
||||
|
||||
// All `StringLiteral` types are a subtype of `LiteralString`.
|
||||
(Type::StringLiteral(_), Type::LiteralString) => true,
|
||||
|
||||
// Except for the special `LiteralString` case above,
|
||||
// most `Literal` types delegate to their instance fallbacks
|
||||
// unless `self` is exactly equivalent to `target` (handled above)
|
||||
(Type::StringLiteral(_) | Type::LiteralString, _) => {
|
||||
KnownClass::Str.to_instance(db).is_subtype_of(db, target)
|
||||
}
|
||||
(Type::BooleanLiteral(_), _) => {
|
||||
KnownClass::Bool.to_instance(db).is_subtype_of(db, target)
|
||||
}
|
||||
(Type::IntLiteral(_), _) => KnownClass::Int.to_instance(db).is_subtype_of(db, target),
|
||||
(Type::BytesLiteral(_), _) => {
|
||||
KnownClass::Bytes.to_instance(db).is_subtype_of(db, target)
|
||||
}
|
||||
(Type::ModuleLiteral(_), _) => KnownClass::ModuleType
|
||||
.to_instance(db)
|
||||
.is_subtype_of(db, target),
|
||||
(Type::SliceLiteral(_), _) => {
|
||||
KnownClass::Slice.to_instance(db).is_subtype_of(db, target)
|
||||
}
|
||||
|
||||
// A `FunctionLiteral` type is a single-valued type like the other literals handled above,
|
||||
// so it also, for now, just delegates to its instance fallback.
|
||||
// This will change in a way similar to the `LiteralString`/`StringLiteral()` case above
|
||||
// when we add support for `typing.Callable`.
|
||||
(Type::FunctionLiteral(_), _) => KnownClass::FunctionType
|
||||
.to_instance(db)
|
||||
.is_subtype_of(db, target),
|
||||
|
||||
// A fully static heterogenous tuple type `A` is a subtype of a fully static heterogeneous tuple type `B`
|
||||
// iff the two tuple types have the same number of elements and each element-type in `A` is a subtype
|
||||
// of the element-type at the same index in `B`. (Now say that 5 times fast.)
|
||||
//
|
||||
// For example: `tuple[bool, bool]` is a subtype of `tuple[int, int]`,
|
||||
// but `tuple[bool, bool, bool]` is not a subtype of `tuple[int, int]`
|
||||
(Type::Tuple(self_tuple), Type::Tuple(target_tuple)) => {
|
||||
let self_elements = self_tuple.elements(db);
|
||||
let target_elements = target_tuple.elements(db);
|
||||
self_elements.len() == target_elements.len()
|
||||
&& self_elements.iter().zip(target_elements).all(
|
||||
|(self_element, target_element)| {
|
||||
self_element.is_subtype_of(db, *target_element)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// Other than the special tuple-to-tuple case handled, above,
|
||||
// tuple subtyping delegates to `Instance(tuple)` in the same way as the literal types.
|
||||
//
|
||||
// All heterogenous tuple types are subtypes of `Instance(<tuple>)`:
|
||||
// `Instance(<some class T>)` expresses "the set of all possible instances of the class `T`";
|
||||
// consequently, `Instance(<tuple>)` expresses "the set of all possible instances of the class `tuple`".
|
||||
// This type can be spelled in type annotations as `tuple[object, ...]` (since `tuple` is covariant).
|
||||
//
|
||||
// Note that this is not the same type as the type spelled in type annotations as `tuple`;
|
||||
// as that type is equivalent to `type[Any, ...]` (and therefore not a fully static type).
|
||||
(Type::Tuple(_), _) => KnownClass::Tuple.to_instance(db).is_subtype_of(db, target),
|
||||
|
||||
// `Type::ClassLiteral` always delegates to `Type::SubclassOf`:
|
||||
(Type::ClassLiteral(ClassLiteralType { class }), _) => {
|
||||
Type::subclass_of(class).is_subtype_of(db, target)
|
||||
}
|
||||
|
||||
// As the `unreachable!()` message says, non-fully-static `SubclassOf` types such as
|
||||
// `type[Any]`,` type[Unknown]` and `type[Todo]` should all be handled right at the top of this function.
|
||||
(
|
||||
Type::SubclassOf(SubclassOfType {
|
||||
base: ClassBase::Any | ClassBase::Unknown | ClassBase::Todo(_),
|
||||
}),
|
||||
_,
|
||||
)
|
||||
| (
|
||||
_,
|
||||
Type::SubclassOf(SubclassOfType {
|
||||
base: ClassBase::Any | ClassBase::Unknown | ClassBase::Todo(_),
|
||||
}),
|
||||
) => unreachable!(
|
||||
"Non-fully-static types should be handled at the top of this function!"
|
||||
),
|
||||
|
||||
// For example, `type[bool]` describes all possible runtime subclasses of the class `bool`,
|
||||
// and `type[int]` describes all possible runtime subclasses of the class `int`.
|
||||
// The first set is a subset of the second set, because `bool` is itself a subclass of `int`.
|
||||
(
|
||||
Type::SubclassOf(SubclassOfType {
|
||||
base: ClassBase::Class(self_class),
|
||||
}),
|
||||
Type::SubclassOf(SubclassOfType {
|
||||
base: ClassBase::Class(target_class),
|
||||
}),
|
||||
) => self_class.is_subclass_of(db, target_class),
|
||||
|
||||
// `type[str]` (== `SubclassOf("str")` in red-knot) describes all possible runtime subclasses
|
||||
// of the class object `str`. It is a subtype of `type` (== `Instance("type")`) because `str`
|
||||
// is an instance of `type`, and so all possible subclasses of `str` will also be instances of `type`.
|
||||
//
|
||||
// Similarly `type[enum.Enum]` is a subtype of `enum.EnumMeta` because `enum.Enum`
|
||||
// is an instance of `enum.EnumMeta`.
|
||||
(
|
||||
Type::SubclassOf(SubclassOfType {
|
||||
base: ClassBase::Class(self_class),
|
||||
}),
|
||||
Type::Instance(InstanceType {
|
||||
class: target_class,
|
||||
}),
|
||||
) => self_class.is_instance_of(db, target_class),
|
||||
|
||||
// Other than the cases enumerated above, `type[]` just delegates to `Instance("type")`
|
||||
(Type::SubclassOf(_), _) => KnownClass::Type.to_instance(db).is_subtype_of(db, target),
|
||||
|
||||
// For example: `Type::KnownInstance(KnownInstanceType::Type)` is a subtype of `Type::Instance(_SpecialForm)`,
|
||||
// because `Type::KnownInstance(KnownInstanceType::Type)` is a set with exactly one runtime value in it
|
||||
// (the symbol `typing.Type`), and that symbol is known to be an instance of `typing._SpecialForm` at runtime.
|
||||
(Type::KnownInstance(left), right) => {
|
||||
left.instance_fallback(db).is_subtype_of(db, right)
|
||||
}
|
||||
(Type::Instance(left), Type::Instance(right)) => left.is_instance_of(db, right.class),
|
||||
// TODO
|
||||
_ => false,
|
||||
|
||||
// For example, `abc.ABCMeta` (== `Instance("abc.ABCMeta")`) is a subtype of `type[object]`
|
||||
// (== `SubclassOf("object")`) because (since `abc.ABCMeta` subclasses `type`) all instances of `ABCMeta`
|
||||
// are instances of `type`, and `type[object]` represents the set of all subclasses of `object`,
|
||||
// which is exactly equal to the set of all instances of `type`.
|
||||
(
|
||||
Type::Instance(_),
|
||||
Type::SubclassOf(SubclassOfType {
|
||||
base: ClassBase::Class(target_class),
|
||||
}),
|
||||
) if target_class.is_known(db, KnownClass::Object) => {
|
||||
self.is_subtype_of(db, KnownClass::Type.to_instance(db))
|
||||
}
|
||||
|
||||
// `bool` is a subtype of `int`, because `bool` subclasses `int`,
|
||||
// which means that all instances of `bool` are also instances of `int`
|
||||
(Type::Instance(self_instance), Type::Instance(target_instance)) => {
|
||||
self_instance.is_subtype_of(db, target_instance)
|
||||
}
|
||||
|
||||
// Other than the special cases enumerated above,
|
||||
// `Instance` types are never subtypes of any other variants
|
||||
(Type::Instance(_), _) => false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -924,6 +1010,8 @@ impl<'db> Type<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
// any single-valued type is disjoint from another single-valued type
|
||||
// iff the two types are nonequal
|
||||
(
|
||||
left @ (Type::BooleanLiteral(..)
|
||||
| Type::IntLiteral(..)
|
||||
|
|
@ -932,7 +1020,8 @@ impl<'db> Type<'db> {
|
|||
| Type::SliceLiteral(..)
|
||||
| Type::FunctionLiteral(..)
|
||||
| Type::ModuleLiteral(..)
|
||||
| Type::ClassLiteral(..)),
|
||||
| Type::ClassLiteral(..)
|
||||
| Type::KnownInstance(..)),
|
||||
right @ (Type::BooleanLiteral(..)
|
||||
| Type::IntLiteral(..)
|
||||
| Type::StringLiteral(..)
|
||||
|
|
@ -940,9 +1029,38 @@ impl<'db> Type<'db> {
|
|||
| Type::SliceLiteral(..)
|
||||
| Type::FunctionLiteral(..)
|
||||
| Type::ModuleLiteral(..)
|
||||
| Type::ClassLiteral(..)),
|
||||
| Type::ClassLiteral(..)
|
||||
| Type::KnownInstance(..)),
|
||||
) => left != right,
|
||||
|
||||
// One tuple type can be a subtype of another tuple type,
|
||||
// but we know for sure that any given tuple type is disjoint from all single-valued types
|
||||
(
|
||||
Type::Tuple(..),
|
||||
Type::ClassLiteral(..)
|
||||
| Type::ModuleLiteral(..)
|
||||
| Type::BooleanLiteral(..)
|
||||
| Type::BytesLiteral(..)
|
||||
| Type::FunctionLiteral(..)
|
||||
| Type::IntLiteral(..)
|
||||
| Type::SliceLiteral(..)
|
||||
| Type::StringLiteral(..)
|
||||
| Type::LiteralString,
|
||||
) => true,
|
||||
|
||||
(
|
||||
Type::ClassLiteral(..)
|
||||
| Type::ModuleLiteral(..)
|
||||
| Type::BooleanLiteral(..)
|
||||
| Type::BytesLiteral(..)
|
||||
| Type::FunctionLiteral(..)
|
||||
| Type::IntLiteral(..)
|
||||
| Type::SliceLiteral(..)
|
||||
| Type::StringLiteral(..)
|
||||
| Type::LiteralString,
|
||||
Type::Tuple(..),
|
||||
) => true,
|
||||
|
||||
(
|
||||
Type::SubclassOf(SubclassOfType {
|
||||
base: ClassBase::Class(class_a),
|
||||
|
|
@ -955,10 +1073,13 @@ impl<'db> Type<'db> {
|
|||
base: ClassBase::Class(class_a),
|
||||
}),
|
||||
) => !class_b.is_subclass_of(db, class_a),
|
||||
|
||||
(Type::SubclassOf(_), Type::SubclassOf(_)) => false,
|
||||
|
||||
(Type::SubclassOf(_), Type::Instance(_)) | (Type::Instance(_), Type::SubclassOf(_)) => {
|
||||
false
|
||||
}
|
||||
|
||||
(
|
||||
Type::SubclassOf(_),
|
||||
Type::BooleanLiteral(..)
|
||||
|
|
@ -979,6 +1100,7 @@ impl<'db> Type<'db> {
|
|||
| Type::ModuleLiteral(..),
|
||||
Type::SubclassOf(_),
|
||||
) => true,
|
||||
|
||||
(Type::SubclassOf(_), _) | (_, Type::SubclassOf(_)) => {
|
||||
// TODO: Once we have support for final classes, we can determine disjointness in some cases
|
||||
// here. However, note that it might be better to turn `Type::SubclassOf('FinalClass')` into
|
||||
|
|
@ -986,13 +1108,14 @@ impl<'db> Type<'db> {
|
|||
// final classes inside `Type::SubclassOf` everywhere.
|
||||
false
|
||||
}
|
||||
(Type::KnownInstance(left), Type::KnownInstance(right)) => left != right,
|
||||
|
||||
(Type::KnownInstance(left), right) => {
|
||||
left.instance_fallback(db).is_disjoint_from(db, right)
|
||||
}
|
||||
(left, Type::KnownInstance(right)) => {
|
||||
left.is_disjoint_from(db, right.instance_fallback(db))
|
||||
}
|
||||
|
||||
(
|
||||
Type::Instance(InstanceType { class: class_none }),
|
||||
Type::Instance(InstanceType { class: class_other }),
|
||||
|
|
@ -1004,6 +1127,7 @@ impl<'db> Type<'db> {
|
|||
class_other.known(db),
|
||||
Some(KnownClass::NoneType | KnownClass::Object)
|
||||
),
|
||||
|
||||
(Type::Instance(InstanceType { class: class_none }), _)
|
||||
| (_, Type::Instance(InstanceType { class: class_none }))
|
||||
if class_none.is_known(db, KnownClass::NoneType) =>
|
||||
|
|
@ -1030,7 +1154,6 @@ impl<'db> Type<'db> {
|
|||
| (Type::Instance(InstanceType { class }), Type::StringLiteral(..)) => {
|
||||
!matches!(class.known(db), Some(KnownClass::Str | KnownClass::Object))
|
||||
}
|
||||
(Type::StringLiteral(..), _) | (_, Type::StringLiteral(..)) => true,
|
||||
|
||||
(Type::LiteralString, Type::LiteralString) => false,
|
||||
(Type::LiteralString, Type::Instance(InstanceType { class }))
|
||||
|
|
@ -1044,14 +1167,12 @@ impl<'db> Type<'db> {
|
|||
class.known(db),
|
||||
Some(KnownClass::Bytes | KnownClass::Object)
|
||||
),
|
||||
(Type::BytesLiteral(..), _) | (_, Type::BytesLiteral(..)) => true,
|
||||
|
||||
(Type::SliceLiteral(..), Type::Instance(InstanceType { class }))
|
||||
| (Type::Instance(InstanceType { class }), Type::SliceLiteral(..)) => !matches!(
|
||||
class.known(db),
|
||||
Some(KnownClass::Slice | KnownClass::Object)
|
||||
),
|
||||
(Type::SliceLiteral(..), _) | (_, Type::SliceLiteral(..)) => true,
|
||||
|
||||
(Type::ClassLiteral(..), Type::Instance(InstanceType { class }))
|
||||
| (Type::Instance(InstanceType { class }), Type::ClassLiteral(..)) => {
|
||||
|
|
@ -1079,29 +1200,26 @@ impl<'db> Type<'db> {
|
|||
false
|
||||
}
|
||||
|
||||
(Type::Tuple(tuple), other) | (other, Type::Tuple(tuple)) => {
|
||||
if let Type::Tuple(other_tuple) = other {
|
||||
if tuple.len(db) == other_tuple.len(db) {
|
||||
tuple
|
||||
.elements(db)
|
||||
.iter()
|
||||
.zip(other_tuple.elements(db))
|
||||
.any(|(e1, e2)| e1.is_disjoint_from(db, *e2))
|
||||
} else {
|
||||
true
|
||||
}
|
||||
} else {
|
||||
// We can not be sure if the tuple is disjoint from 'other' because:
|
||||
// - 'other' might be the homogeneous arbitrary-length tuple type
|
||||
// tuple[T, ...] (which we don't have support for yet); if all of
|
||||
// our element types are not disjoint with T, this is not disjoint
|
||||
// - 'other' might be a user subtype of tuple, which, if generic
|
||||
// over the same or compatible *Ts, would overlap with tuple.
|
||||
//
|
||||
// TODO: add checks for the above cases once we support them
|
||||
(Type::Tuple(tuple), Type::Tuple(other_tuple)) => {
|
||||
let self_elements = tuple.elements(db);
|
||||
let other_elements = other_tuple.elements(db);
|
||||
self_elements.len() != other_elements.len()
|
||||
|| self_elements
|
||||
.iter()
|
||||
.zip(other_elements)
|
||||
.any(|(e1, e2)| e1.is_disjoint_from(db, *e2))
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
(Type::Tuple(..), Type::Instance(..)) | (Type::Instance(..), Type::Tuple(..)) => {
|
||||
// We cannot be sure if the tuple is disjoint from the instance because:
|
||||
// - 'other' might be the homogeneous arbitrary-length tuple type
|
||||
// tuple[T, ...] (which we don't have support for yet); if all of
|
||||
// our element types are not disjoint with T, this is not disjoint
|
||||
// - 'other' might be a user subtype of tuple, which, if generic
|
||||
// over the same or compatible *Ts, would overlap with tuple.
|
||||
//
|
||||
// TODO: add checks for the above cases once we support them
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2779,6 +2897,17 @@ impl<'db> Class<'db> {
|
|||
self.iter_mro(db).contains(&ClassBase::Class(other))
|
||||
}
|
||||
|
||||
/// Return `true` if this class object is an instance of the class `other`.
|
||||
///
|
||||
/// A class is an instance of its metaclass; consequently,
|
||||
/// a class will only ever be an instance of another class
|
||||
/// if its metaclass is a subclass of that other class.
|
||||
fn is_instance_of(self, db: &'db dyn Db, other: Class<'db>) -> bool {
|
||||
self.metaclass(db).into_class_literal().is_some_and(
|
||||
|ClassLiteralType { class: metaclass }| metaclass.is_subclass_of(db, other),
|
||||
)
|
||||
}
|
||||
|
||||
/// Return the explicit `metaclass` of this class, if one is defined.
|
||||
///
|
||||
/// ## Note
|
||||
|
|
@ -3010,9 +3139,8 @@ pub struct InstanceType<'db> {
|
|||
}
|
||||
|
||||
impl<'db> InstanceType<'db> {
|
||||
/// Return `true` if members of this type are instances of the class `class` at runtime.
|
||||
pub fn is_instance_of(self, db: &'db dyn Db, class: Class<'db>) -> bool {
|
||||
self.class.is_subclass_of(db, class)
|
||||
fn is_subtype_of(self, db: &'db dyn Db, other: InstanceType<'db>) -> bool {
|
||||
self.class.is_subclass_of(db, other.class)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -3186,7 +3314,7 @@ pub(crate) mod tests {
|
|||
use super::*;
|
||||
use crate::db::tests::{setup_db, TestDb, TestDbBuilder};
|
||||
use crate::stdlib::typing_symbol;
|
||||
use crate::PythonVersion;
|
||||
use crate::{resolve_module, PythonVersion};
|
||||
use ruff_db::files::system_path_to_file;
|
||||
use ruff_db::parsed::parsed_module;
|
||||
use ruff_db::system::DbWithTestSystem;
|
||||
|
|
@ -3226,6 +3354,8 @@ pub(crate) mod tests {
|
|||
Tuple(Vec<Ty>),
|
||||
SubclassOfAny,
|
||||
SubclassOfBuiltinClass(&'static str),
|
||||
StdlibModule(CoreStdlibModule),
|
||||
SliceLiteral(i32, i32, i32),
|
||||
}
|
||||
|
||||
impl Ty {
|
||||
|
|
@ -3276,6 +3406,15 @@ pub(crate) mod tests {
|
|||
.expect_class_literal()
|
||||
.class,
|
||||
),
|
||||
Ty::StdlibModule(module) => {
|
||||
Type::ModuleLiteral(resolve_module(db, &module.name()).unwrap().file())
|
||||
}
|
||||
Ty::SliceLiteral(start, stop, step) => Type::SliceLiteral(SliceLiteralType::new(
|
||||
db,
|
||||
Some(start),
|
||||
Some(stop),
|
||||
Some(step),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3402,6 +3541,13 @@ pub(crate) mod tests {
|
|||
#[test_case(Ty::TypingLiteral, Ty::BuiltinInstance("object"))]
|
||||
#[test_case(Ty::AbcClassLiteral("ABC"), Ty::AbcInstance("ABCMeta"))]
|
||||
#[test_case(Ty::AbcInstance("ABCMeta"), Ty::SubclassOfBuiltinClass("object"))]
|
||||
#[test_case(Ty::Tuple(vec![Ty::BuiltinInstance("int")]), Ty::BuiltinInstance("tuple"))]
|
||||
#[test_case(Ty::BuiltinClassLiteral("str"), Ty::BuiltinInstance("type"))]
|
||||
#[test_case(
|
||||
Ty::StdlibModule(CoreStdlibModule::Typing),
|
||||
Ty::KnownClassInstance(KnownClass::ModuleType)
|
||||
)]
|
||||
#[test_case(Ty::SliceLiteral(1, 2, 3), Ty::BuiltinInstance("slice"))]
|
||||
fn is_subtype_of(from: Ty, to: Ty) {
|
||||
let db = setup_db();
|
||||
assert!(from.into_type(&db).is_subtype_of(&db, to.into_type(&db)));
|
||||
|
|
@ -3435,6 +3581,7 @@ pub(crate) mod tests {
|
|||
#[test_case(Ty::BuiltinInstance("type"), Ty::SubclassOfBuiltinClass("str"))]
|
||||
#[test_case(Ty::BuiltinClassLiteral("str"), Ty::SubclassOfAny)]
|
||||
#[test_case(Ty::AbcInstance("ABCMeta"), Ty::SubclassOfBuiltinClass("type"))]
|
||||
#[test_case(Ty::SubclassOfBuiltinClass("str"), Ty::BuiltinClassLiteral("str"))]
|
||||
fn is_not_subtype_of(from: Ty, to: Ty) {
|
||||
let db = setup_db();
|
||||
assert!(!from.into_type(&db).is_subtype_of(&db, to.into_type(&db)));
|
||||
|
|
@ -3566,6 +3713,7 @@ pub(crate) mod tests {
|
|||
#[test_case(Ty::Tuple(vec![Ty::IntLiteral(1)]), Ty::Tuple(vec![Ty::IntLiteral(2)]))]
|
||||
#[test_case(Ty::Tuple(vec![Ty::IntLiteral(1), Ty::IntLiteral(2)]), Ty::Tuple(vec![Ty::IntLiteral(1)]))]
|
||||
#[test_case(Ty::Tuple(vec![Ty::IntLiteral(1), Ty::IntLiteral(2)]), Ty::Tuple(vec![Ty::IntLiteral(1), Ty::IntLiteral(3)]))]
|
||||
#[test_case(Ty::Tuple(vec![]), Ty::BuiltinClassLiteral("object"))]
|
||||
fn is_disjoint_from(a: Ty, b: Ty) {
|
||||
let db = setup_db();
|
||||
let a = a.into_type(&db);
|
||||
|
|
|
|||
|
|
@ -3375,8 +3375,8 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
op,
|
||||
),
|
||||
|
||||
(left_ty @ Type::Instance(left), right_ty @ Type::Instance(right), op) => {
|
||||
if left != right && right.is_instance_of(self.db, left.class) {
|
||||
(Type::Instance(left), Type::Instance(right), op) => {
|
||||
if left != right && right.is_subtype_of(self.db, left) {
|
||||
let reflected_dunder = op.reflected_dunder();
|
||||
let rhs_reflected = right.class.class_member(self.db, reflected_dunder);
|
||||
if !rhs_reflected.is_unbound()
|
||||
|
|
@ -5320,7 +5320,7 @@ fn perform_rich_comparison<'db>(
|
|||
};
|
||||
|
||||
// The reflected dunder has priority if the right-hand side is a strict subclass of the left-hand side.
|
||||
if left != right && right.is_instance_of(db, left.class) {
|
||||
if left != right && right.is_subtype_of(db, left) {
|
||||
call_dunder(op.reflect(), right, left).or_else(|| call_dunder(op, left, right))
|
||||
} else {
|
||||
call_dunder(op, left, right).or_else(|| call_dunder(op.reflect(), right, left))
|
||||
|
|
|
|||
Loading…
Reference in New Issue