Skip to content

Commit

Permalink
[red-knot] Small simplifications to Type::is_subtype_of and `Type::…
Browse files Browse the repository at this point in the history
…is_disjoint_from` (#15622)

## Summary

This PR generalizes some of the logic we have in `Type::is_subtype_of`
and `Type::is_disjoint_from` so that we fallback to the instance type of
the metaclass more often in `Type::ClassLiteral` and `Type::SubclassOf`
branches. This simplifies the code (we end up with one less branch in
`is_subtype_of`, and we can remove a helper method that's no longer
used), makes the code more robust (any fixes made to subtyping or
disjointness of instance types will automatically improve our
understanding of subtyping/disjointness for class-literal types and
`type[]` types) and more elegantly expresses the type-system invariants
encoded in these branches.

## Test Plan

No new tests added (it's a pure refactor, adding no new functionality).
All existing tests pass, however, including the property tests.
  • Loading branch information
AlexWaygood authored Jan 22, 2025
1 parent 8a8240b commit fbb06fe
Showing 1 changed file with 23 additions and 38 deletions.
61 changes: 23 additions & 38 deletions crates/red_knot_python_semantic/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -963,33 +963,27 @@ impl<'db> Type<'db> {
// `Literal[str]` is a subtype of `type` because the `str` class object is an instance of its metaclass `type`.
// `Literal[abc.ABC]` is a subtype of `abc.ABCMeta` because the `abc.ABC` class object
// is an instance of its metaclass `abc.ABCMeta`.
(
Type::ClassLiteral(ClassLiteralType { class: self_class }),
Type::Instance(InstanceType {
class: target_class,
}),
) => self_class.is_instance_of(db, target_class),
(Type::ClassLiteral(ClassLiteralType { class }), _) => class
.metaclass(db)
.to_instance(db)
.is_subtype_of(db, target),

// `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(subclass_of_ty),
Type::Instance(InstanceType {
class: target_class,
}),
) => subclass_of_ty
// is an instance of `enum.EnumMeta`. `type[Any]` and `type[Unknown]` do not participate in subtyping,
// however, as they are not fully static types.
(Type::SubclassOf(subclass_of_ty), _) => subclass_of_ty
.subclass_of()
.into_class()
.is_some_and(|subclass_class| subclass_class.is_instance_of(db, target_class)),

// Other than the cases enumerated above, `type[]` and class-literal types just delegate to `Instance("type")`
(Type::SubclassOf(_) | Type::ClassLiteral(_), _) => {
KnownClass::Type.to_instance(db).is_subtype_of(db, target)
}
.is_some_and(|class| {
class
.metaclass(db)
.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
Expand Down Expand Up @@ -1382,14 +1376,16 @@ impl<'db> Type<'db> {
!KnownClass::Slice.is_subclass_of(db, class)
}

(
Type::ClassLiteral(ClassLiteralType { class: class_a }),
Type::Instance(InstanceType { class: class_b }),
)
| (
Type::Instance(InstanceType { class: class_b }),
Type::ClassLiteral(ClassLiteralType { class: class_a }),
) => !class_a.is_instance_of(db, class_b),
// A class-literal type `X` is always disjoint from an instance type `Y`,
// unless the type expressing "all instances of `Z`" is a subtype of of `Y`,
// where `Z` is `X`'s metaclass.
(Type::ClassLiteral(ClassLiteralType { class }), instance @ Type::Instance(_))
| (instance @ Type::Instance(_), Type::ClassLiteral(ClassLiteralType { class })) => {
!class
.metaclass(db)
.to_instance(db)
.is_subtype_of(db, instance)
}

(Type::FunctionLiteral(..), Type::Instance(InstanceType { class }))
| (Type::Instance(InstanceType { class }), Type::FunctionLiteral(..)) => {
Expand Down Expand Up @@ -3857,17 +3853,6 @@ 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
Expand Down

0 comments on commit fbb06fe

Please sign in to comment.