mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-02 06:42:02 +00:00
[red-knot] Eagerly normalize type[]
types (#15272)
Co-authored-by: Carl Meyer <carl@astral.sh>
This commit is contained in:
parent
0dc00e63f4
commit
95294e657c
9 changed files with 245 additions and 174 deletions
|
@ -90,7 +90,7 @@ def _(t: type[object]):
|
||||||
if issubclass(t, B):
|
if issubclass(t, B):
|
||||||
reveal_type(t) # revealed: type[A] & type[B]
|
reveal_type(t) # revealed: type[A] & type[B]
|
||||||
else:
|
else:
|
||||||
reveal_type(t) # revealed: type[object] & ~type[A]
|
reveal_type(t) # revealed: type & ~type[A]
|
||||||
```
|
```
|
||||||
|
|
||||||
### Handling of `None`
|
### Handling of `None`
|
||||||
|
|
|
@ -142,3 +142,25 @@ class Foo(type[int]): ...
|
||||||
# TODO: should be `tuple[Literal[Foo], Literal[type], Literal[object]]
|
# TODO: should be `tuple[Literal[Foo], Literal[type], Literal[object]]
|
||||||
reveal_type(Foo.__mro__) # revealed: tuple[Literal[Foo], Unknown, Literal[object]]
|
reveal_type(Foo.__mro__) # revealed: tuple[Literal[Foo], Unknown, Literal[object]]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## `@final` classes
|
||||||
|
|
||||||
|
`type[]` types are eagerly converted to class-literal types if a class decorated with `@final` is
|
||||||
|
used as the type argument. This applies to standard-library classes and user-defined classes:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[environment]
|
||||||
|
python-version = "3.10"
|
||||||
|
```
|
||||||
|
|
||||||
|
```py
|
||||||
|
from types import EllipsisType
|
||||||
|
from typing import final
|
||||||
|
|
||||||
|
@final
|
||||||
|
class Foo: ...
|
||||||
|
|
||||||
|
def _(x: type[Foo], y: type[EllipsisType]):
|
||||||
|
reveal_type(x) # revealed: Literal[Foo]
|
||||||
|
reveal_type(y) # revealed: Literal[EllipsisType]
|
||||||
|
```
|
||||||
|
|
|
@ -47,9 +47,8 @@ x: type = A() # error: [invalid-assignment]
|
||||||
|
|
||||||
```py
|
```py
|
||||||
def f(x: type[object]):
|
def f(x: type[object]):
|
||||||
reveal_type(x) # revealed: type[object]
|
reveal_type(x) # revealed: type
|
||||||
# TODO: bound method types
|
reveal_type(x.__repr__) # revealed: @Todo(instance attributes)
|
||||||
reveal_type(x.__repr__) # revealed: Literal[__repr__]
|
|
||||||
|
|
||||||
class A: ...
|
class A: ...
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ pub(crate) use self::infer::{
|
||||||
};
|
};
|
||||||
pub use self::narrow::KnownConstraintFunction;
|
pub use self::narrow::KnownConstraintFunction;
|
||||||
pub(crate) use self::signatures::Signature;
|
pub(crate) use self::signatures::Signature;
|
||||||
|
pub use self::subclass_of::SubclassOfType;
|
||||||
use crate::module_name::ModuleName;
|
use crate::module_name::ModuleName;
|
||||||
use crate::module_resolver::{file_to_module, resolve_module, KnownModule};
|
use crate::module_resolver::{file_to_module, resolve_module, KnownModule};
|
||||||
use crate::semantic_index::ast_ids::HasScopedExpressionId;
|
use crate::semantic_index::ast_ids::HasScopedExpressionId;
|
||||||
|
@ -49,6 +50,7 @@ mod narrow;
|
||||||
mod signatures;
|
mod signatures;
|
||||||
mod slots;
|
mod slots;
|
||||||
mod string_annotation;
|
mod string_annotation;
|
||||||
|
mod subclass_of;
|
||||||
mod unpacker;
|
mod unpacker;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -693,14 +695,6 @@ impl<'db> Type<'db> {
|
||||||
Self::Instance(InstanceType { class })
|
Self::Instance(InstanceType { class })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn subclass_of(class: Class<'db>) -> Self {
|
|
||||||
Self::subclass_of_base(ClassBase::Class(class))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn subclass_of_base(base: ClassBase<'db>) -> Self {
|
|
||||||
Self::SubclassOf(SubclassOfType { base })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn string_literal(db: &'db dyn Db, string: &str) -> Self {
|
pub fn string_literal(db: &'db dyn Db, string: &str) -> Self {
|
||||||
Self::StringLiteral(StringLiteralType::new(db, string))
|
Self::StringLiteral(StringLiteralType::new(db, string))
|
||||||
}
|
}
|
||||||
|
@ -887,16 +881,31 @@ impl<'db> Type<'db> {
|
||||||
// as that type is equivalent to `type[Any, ...]` (and therefore not a fully static type).
|
// 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::Tuple(_), _) => KnownClass::Tuple.to_instance(db).is_subtype_of(db, target),
|
||||||
|
|
||||||
// `Type::ClassLiteral` always delegates to `Type::SubclassOf`:
|
// `Literal[<class 'C'>]` is a subtype of `type[B]` if `C` is a subclass of `B`,
|
||||||
(Type::ClassLiteral(ClassLiteralType { class }), _) => {
|
// since `type[B]` describes all possible runtime subclasses of the class object `B`.
|
||||||
Type::subclass_of(class).is_subtype_of(db, target)
|
(
|
||||||
}
|
Type::ClassLiteral(ClassLiteralType { class }),
|
||||||
|
Type::SubclassOf(target_subclass_ty),
|
||||||
|
) => target_subclass_ty
|
||||||
|
.subclass_of()
|
||||||
|
.into_class()
|
||||||
|
.is_some_and(|target_class| class.is_subclass_of(db, target_class)),
|
||||||
|
|
||||||
// This branch asks: given two types `type[T]` and `type[S]`, is `type[T]` a subtype of `type[S]`?
|
// This branch asks: given two types `type[T]` and `type[S]`, is `type[T]` a subtype of `type[S]`?
|
||||||
(Type::SubclassOf(self_subclass_ty), Type::SubclassOf(target_subclass_ty)) => {
|
(Type::SubclassOf(self_subclass_ty), Type::SubclassOf(target_subclass_ty)) => {
|
||||||
self_subclass_ty.is_subtype_of(db, target_subclass_ty)
|
self_subclass_ty.is_subtype_of(db, target_subclass_ty)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// `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[str]` (== `SubclassOf("str")` in red-knot) describes all possible runtime subclasses
|
// `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`
|
// 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`.
|
// is an instance of `type`, and so all possible subclasses of `str` will also be instances of `type`.
|
||||||
|
@ -904,16 +913,19 @@ impl<'db> Type<'db> {
|
||||||
// Similarly `type[enum.Enum]` is a subtype of `enum.EnumMeta` because `enum.Enum`
|
// Similarly `type[enum.Enum]` is a subtype of `enum.EnumMeta` because `enum.Enum`
|
||||||
// is an instance of `enum.EnumMeta`.
|
// is an instance of `enum.EnumMeta`.
|
||||||
(
|
(
|
||||||
Type::SubclassOf(SubclassOfType {
|
Type::SubclassOf(subclass_of_ty),
|
||||||
base: ClassBase::Class(self_class),
|
|
||||||
}),
|
|
||||||
Type::Instance(InstanceType {
|
Type::Instance(InstanceType {
|
||||||
class: target_class,
|
class: target_class,
|
||||||
}),
|
}),
|
||||||
) => self_class.is_instance_of(db, target_class),
|
) => 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[]` just delegates to `Instance("type")`
|
// Other than the cases enumerated above, `type[]` and class-literal types just delegate to `Instance("type")`
|
||||||
(Type::SubclassOf(_), _) => KnownClass::Type.to_instance(db).is_subtype_of(db, target),
|
(Type::SubclassOf(_) | Type::ClassLiteral(_), _) => {
|
||||||
|
KnownClass::Type.to_instance(db).is_subtype_of(db, target)
|
||||||
|
}
|
||||||
|
|
||||||
// For example: `Type::KnownInstance(KnownInstanceType::Type)` is a subtype of `Type::Instance(_SpecialForm)`,
|
// 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
|
// because `Type::KnownInstance(KnownInstanceType::Type)` is a set with exactly one runtime value in it
|
||||||
|
@ -922,19 +934,6 @@ impl<'db> Type<'db> {
|
||||||
left.instance_fallback(db).is_subtype_of(db, right)
|
left.instance_fallback(db).is_subtype_of(db, right)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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`,
|
// `bool` is a subtype of `int`, because `bool` subclasses `int`,
|
||||||
// which means that all instances of `bool` are also instances of `int`
|
// which means that all instances of `bool` are also instances of `int`
|
||||||
(Type::Instance(self_instance), Type::Instance(target_instance)) => {
|
(Type::Instance(self_instance), Type::Instance(target_instance)) => {
|
||||||
|
@ -975,30 +974,28 @@ impl<'db> Type<'db> {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
(
|
(Type::SubclassOf(subclass_of_ty), Type::SubclassOf(_))
|
||||||
Type::SubclassOf(SubclassOfType {
|
if subclass_of_ty.is_dynamic() =>
|
||||||
base: ClassBase::Any | ClassBase::Todo(_) | ClassBase::Unknown,
|
{
|
||||||
}),
|
true
|
||||||
Type::SubclassOf(_),
|
}
|
||||||
) => true,
|
(Type::SubclassOf(subclass_of_ty), Type::Instance(_))
|
||||||
(
|
if subclass_of_ty.is_dynamic()
|
||||||
Type::SubclassOf(SubclassOfType {
|
&& target.is_assignable_to(db, KnownClass::Type.to_instance(db)) =>
|
||||||
base: ClassBase::Any | ClassBase::Todo(_) | ClassBase::Unknown,
|
{
|
||||||
}),
|
true
|
||||||
Type::Instance(_),
|
}
|
||||||
) if target.is_assignable_to(db, KnownClass::Type.to_instance(db)) => true,
|
(Type::Instance(_), Type::SubclassOf(subclass_of_ty))
|
||||||
(
|
if subclass_of_ty.is_dynamic()
|
||||||
Type::Instance(_),
|
&& self.is_assignable_to(db, KnownClass::Type.to_instance(db)) =>
|
||||||
Type::SubclassOf(SubclassOfType {
|
{
|
||||||
base: ClassBase::Any | ClassBase::Todo(_) | ClassBase::Unknown,
|
true
|
||||||
}),
|
}
|
||||||
) if self.is_assignable_to(db, KnownClass::Type.to_instance(db)) => true,
|
(Type::ClassLiteral(_) | Type::SubclassOf(_), Type::SubclassOf(target_subclass_of))
|
||||||
(
|
if target_subclass_of.is_dynamic() =>
|
||||||
Type::ClassLiteral(_) | Type::SubclassOf(_),
|
{
|
||||||
Type::SubclassOf(SubclassOfType {
|
true
|
||||||
base: ClassBase::Any | ClassBase::Todo(_) | ClassBase::Unknown,
|
}
|
||||||
}),
|
|
||||||
) => true,
|
|
||||||
// TODO other types containing gradual forms (e.g. generics containing Any/Unknown)
|
// TODO other types containing gradual forms (e.g. generics containing Any/Unknown)
|
||||||
_ => self.is_subtype_of(db, target),
|
_ => self.is_subtype_of(db, target),
|
||||||
}
|
}
|
||||||
|
@ -1014,33 +1011,9 @@ impl<'db> Type<'db> {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// type[object] ≡ type
|
|
||||||
if let (
|
|
||||||
Type::SubclassOf(SubclassOfType {
|
|
||||||
base: ClassBase::Class(object_class),
|
|
||||||
}),
|
|
||||||
Type::Instance(InstanceType { class: type_class }),
|
|
||||||
)
|
|
||||||
| (
|
|
||||||
Type::Instance(InstanceType { class: type_class }),
|
|
||||||
Type::SubclassOf(SubclassOfType {
|
|
||||||
base: ClassBase::Class(object_class),
|
|
||||||
}),
|
|
||||||
) = (self, other)
|
|
||||||
{
|
|
||||||
// This is the only case where "instance of a class" is equivalent to "subclass of a
|
|
||||||
// class", so we don't need to fall through if we're not looking at instance[type] and
|
|
||||||
// type[object] specifically.
|
|
||||||
return object_class.is_known(db, KnownClass::Object)
|
|
||||||
&& type_class.is_known(db, KnownClass::Type);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO equivalent but not identical structural types, differently-ordered unions and
|
// TODO equivalent but not identical structural types, differently-ordered unions and
|
||||||
// intersections, other cases?
|
// intersections, other cases?
|
||||||
|
|
||||||
// TODO: Once we have support for final classes, we can establish that
|
|
||||||
// `Type::SubclassOf('FinalClass')` is equivalent to `Type::ClassLiteral('FinalClass')`.
|
|
||||||
|
|
||||||
// For all other cases, types are equivalent iff they have the same internal
|
// For all other cases, types are equivalent iff they have the same internal
|
||||||
// representation.
|
// representation.
|
||||||
self == other
|
self == other
|
||||||
|
@ -1140,17 +1113,16 @@ impl<'db> Type<'db> {
|
||||||
) => true,
|
) => true,
|
||||||
|
|
||||||
(
|
(
|
||||||
Type::SubclassOf(SubclassOfType {
|
Type::SubclassOf(subclass_of_ty),
|
||||||
base: ClassBase::Class(class_a),
|
|
||||||
}),
|
|
||||||
Type::ClassLiteral(ClassLiteralType { class: class_b }),
|
Type::ClassLiteral(ClassLiteralType { class: class_b }),
|
||||||
)
|
)
|
||||||
| (
|
| (
|
||||||
Type::ClassLiteral(ClassLiteralType { class: class_b }),
|
Type::ClassLiteral(ClassLiteralType { class: class_b }),
|
||||||
Type::SubclassOf(SubclassOfType {
|
Type::SubclassOf(subclass_of_ty),
|
||||||
base: ClassBase::Class(class_a),
|
) => match subclass_of_ty.subclass_of() {
|
||||||
}),
|
ClassBase::Any | ClassBase::Todo(_) | ClassBase::Unknown => false,
|
||||||
) => !class_b.is_subclass_of(db, class_a),
|
ClassBase::Class(class_a) => !class_b.is_subclass_of(db, class_a),
|
||||||
|
},
|
||||||
|
|
||||||
(Type::SubclassOf(_), Type::SubclassOf(_)) => false,
|
(Type::SubclassOf(_), Type::SubclassOf(_)) => false,
|
||||||
|
|
||||||
|
@ -1198,10 +1170,10 @@ impl<'db> Type<'db> {
|
||||||
}
|
}
|
||||||
|
|
||||||
(Type::SubclassOf(_), other) | (other, Type::SubclassOf(_)) => {
|
(Type::SubclassOf(_), other) | (other, Type::SubclassOf(_)) => {
|
||||||
// TODO: Once we have support for final classes, we can determine disjointness in more cases
|
// TODO we could do better here: if both variants are `SubclassOf` and they have different "solid bases",
|
||||||
// here. However, note that it might be better to turn `Type::SubclassOf('FinalClass')` into
|
// multiple inheritance between the two is impossible, so they are disjoint.
|
||||||
// `Type::ClassLiteral('FinalClass')` during construction, instead of adding special cases for
|
//
|
||||||
// final classes inside `Type::SubclassOf` everywhere.
|
// 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))
|
other.is_disjoint_from(db, KnownClass::Type.to_instance(db))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1357,7 +1329,7 @@ impl<'db> Type<'db> {
|
||||||
| Type::KnownInstance(_)
|
| Type::KnownInstance(_)
|
||||||
| Type::AlwaysFalsy
|
| Type::AlwaysFalsy
|
||||||
| Type::AlwaysTruthy => true,
|
| Type::AlwaysTruthy => true,
|
||||||
Type::SubclassOf(SubclassOfType { base }) => matches!(base, ClassBase::Class(_)),
|
Type::SubclassOf(subclass_of_ty) => subclass_of_ty.is_fully_static(),
|
||||||
Type::ClassLiteral(_) | Type::Instance(_) => {
|
Type::ClassLiteral(_) | Type::Instance(_) => {
|
||||||
// TODO: Ideally, we would iterate over the MRO of the class, check if all
|
// TODO: Ideally, we would iterate over the MRO of the class, check if all
|
||||||
// bases are fully static, and only return `true` if that is the case.
|
// bases are fully static, and only return `true` if that is the case.
|
||||||
|
@ -1421,11 +1393,8 @@ impl<'db> Type<'db> {
|
||||||
// are both of type Literal[345], for example.
|
// are both of type Literal[345], for example.
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
Type::SubclassOf(..) => {
|
// We eagerly transform `SubclassOf` to `ClassLiteral` for final types, so `SubclassOf` is never a singleton.
|
||||||
// TODO once we have support for final classes, we can return `true` for some
|
Type::SubclassOf(..) => false,
|
||||||
// cases: type[C] is a singleton if C is final.
|
|
||||||
false
|
|
||||||
}
|
|
||||||
Type::BooleanLiteral(_)
|
Type::BooleanLiteral(_)
|
||||||
| Type::FunctionLiteral(..)
|
| Type::FunctionLiteral(..)
|
||||||
| Type::ClassLiteral(..)
|
| Type::ClassLiteral(..)
|
||||||
|
@ -1678,7 +1647,8 @@ impl<'db> Type<'db> {
|
||||||
Type::ClassLiteral(ClassLiteralType { class }) => {
|
Type::ClassLiteral(ClassLiteralType { class }) => {
|
||||||
class.metaclass(db).to_instance(db).bool(db)
|
class.metaclass(db).to_instance(db).bool(db)
|
||||||
}
|
}
|
||||||
Type::SubclassOf(SubclassOfType { base }) => base
|
Type::SubclassOf(subclass_of_ty) => subclass_of_ty
|
||||||
|
.subclass_of()
|
||||||
.into_class()
|
.into_class()
|
||||||
.map(|class| Type::class_literal(class).bool(db))
|
.map(|class| Type::class_literal(class).bool(db))
|
||||||
.unwrap_or(Truthiness::Ambiguous),
|
.unwrap_or(Truthiness::Ambiguous),
|
||||||
|
@ -1972,11 +1942,11 @@ impl<'db> Type<'db> {
|
||||||
Type::Unknown => Type::Unknown,
|
Type::Unknown => Type::Unknown,
|
||||||
Type::Never => Type::Never,
|
Type::Never => Type::Never,
|
||||||
Type::ClassLiteral(ClassLiteralType { class }) => Type::instance(*class),
|
Type::ClassLiteral(ClassLiteralType { class }) => Type::instance(*class),
|
||||||
Type::SubclassOf(SubclassOfType { base }) => match base {
|
Type::SubclassOf(subclass_of_ty) => match subclass_of_ty.subclass_of() {
|
||||||
ClassBase::Class(class) => Type::instance(*class),
|
ClassBase::Class(class) => Type::instance(class),
|
||||||
ClassBase::Any => Type::Any,
|
ClassBase::Any => Type::Any,
|
||||||
ClassBase::Unknown => Type::Unknown,
|
ClassBase::Unknown => Type::Unknown,
|
||||||
ClassBase::Todo(todo) => Type::Todo(*todo),
|
ClassBase::Todo(todo) => Type::Todo(todo),
|
||||||
},
|
},
|
||||||
Type::Union(union) => union.map(db, |element| element.to_instance(db)),
|
Type::Union(union) => union.map(db, |element| element.to_instance(db)),
|
||||||
Type::Intersection(_) => todo_type!("Type::Intersection.to_instance()"),
|
Type::Intersection(_) => todo_type!("Type::Intersection.to_instance()"),
|
||||||
|
@ -2131,7 +2101,7 @@ impl<'db> Type<'db> {
|
||||||
pub fn to_meta_type(&self, db: &'db dyn Db) -> Type<'db> {
|
pub fn to_meta_type(&self, db: &'db dyn Db) -> Type<'db> {
|
||||||
match self {
|
match self {
|
||||||
Type::Never => Type::Never,
|
Type::Never => Type::Never,
|
||||||
Type::Instance(InstanceType { class }) => Type::subclass_of(*class),
|
Type::Instance(InstanceType { class }) => SubclassOfType::from(db, *class),
|
||||||
Type::KnownInstance(known_instance) => known_instance.class().to_class_literal(db),
|
Type::KnownInstance(known_instance) => known_instance.class().to_class_literal(db),
|
||||||
Type::Union(union) => union.map(db, |ty| ty.to_meta_type(db)),
|
Type::Union(union) => union.map(db, |ty| ty.to_meta_type(db)),
|
||||||
Type::BooleanLiteral(_) => KnownClass::Bool.to_class_literal(db),
|
Type::BooleanLiteral(_) => KnownClass::Bool.to_class_literal(db),
|
||||||
|
@ -2142,23 +2112,25 @@ impl<'db> Type<'db> {
|
||||||
Type::ModuleLiteral(_) => KnownClass::ModuleType.to_class_literal(db),
|
Type::ModuleLiteral(_) => KnownClass::ModuleType.to_class_literal(db),
|
||||||
Type::Tuple(_) => KnownClass::Tuple.to_class_literal(db),
|
Type::Tuple(_) => KnownClass::Tuple.to_class_literal(db),
|
||||||
Type::ClassLiteral(ClassLiteralType { class }) => class.metaclass(db),
|
Type::ClassLiteral(ClassLiteralType { class }) => class.metaclass(db),
|
||||||
Type::SubclassOf(SubclassOfType { base }) => match base {
|
Type::SubclassOf(subclass_of_ty) => match subclass_of_ty.subclass_of() {
|
||||||
ClassBase::Any | ClassBase::Unknown | ClassBase::Todo(_) => *self,
|
ClassBase::Any | ClassBase::Unknown | ClassBase::Todo(_) => *self,
|
||||||
ClassBase::Class(class) => Type::subclass_of_base(
|
ClassBase::Class(class) => SubclassOfType::from(
|
||||||
|
db,
|
||||||
ClassBase::try_from_ty(db, class.metaclass(db)).unwrap_or(ClassBase::Unknown),
|
ClassBase::try_from_ty(db, class.metaclass(db)).unwrap_or(ClassBase::Unknown),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
Type::StringLiteral(_) | Type::LiteralString => KnownClass::Str.to_class_literal(db),
|
Type::StringLiteral(_) | Type::LiteralString => KnownClass::Str.to_class_literal(db),
|
||||||
Type::Any => Type::subclass_of_base(ClassBase::Any),
|
Type::Any => SubclassOfType::subclass_of_any(),
|
||||||
Type::Unknown => Type::subclass_of_base(ClassBase::Unknown),
|
Type::Unknown => SubclassOfType::subclass_of_unknown(),
|
||||||
// TODO intersections
|
// TODO intersections
|
||||||
Type::Intersection(_) => Type::subclass_of_base(
|
Type::Intersection(_) => SubclassOfType::from(
|
||||||
|
db,
|
||||||
ClassBase::try_from_ty(db, todo_type!("Intersection meta-type"))
|
ClassBase::try_from_ty(db, todo_type!("Intersection meta-type"))
|
||||||
.expect("Type::Todo should be a valid ClassBase"),
|
.expect("Type::Todo should be a valid ClassBase"),
|
||||||
),
|
),
|
||||||
Type::AlwaysTruthy | Type::AlwaysFalsy => KnownClass::Type.to_instance(db),
|
Type::AlwaysTruthy | Type::AlwaysFalsy => KnownClass::Type.to_instance(db),
|
||||||
Type::Todo(todo) => Type::subclass_of_base(ClassBase::Todo(*todo)),
|
Type::Todo(todo) => SubclassOfType::from(db, ClassBase::Todo(*todo)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2367,8 +2339,8 @@ impl<'db> KnownClass {
|
||||||
pub fn to_subclass_of(self, db: &'db dyn Db) -> Type<'db> {
|
pub fn to_subclass_of(self, db: &'db dyn Db) -> Type<'db> {
|
||||||
self.to_class_literal(db)
|
self.to_class_literal(db)
|
||||||
.into_class_literal()
|
.into_class_literal()
|
||||||
.map(|ClassLiteralType { class }| Type::subclass_of(class))
|
.map(|ClassLiteralType { class }| SubclassOfType::from(db, class))
|
||||||
.unwrap_or(Type::subclass_of_base(ClassBase::Unknown))
|
.unwrap_or_else(SubclassOfType::subclass_of_unknown)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return `true` if this symbol can be resolved to a class definition `class` in typeshed,
|
/// Return `true` if this symbol can be resolved to a class definition `class` in typeshed,
|
||||||
|
@ -2766,6 +2738,7 @@ impl<'db> KnownInstanceType<'db> {
|
||||||
self.class().to_instance(db)
|
self.class().to_instance(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return `true` if this symbol is an instance of `class`.
|
||||||
pub fn is_instance_of(self, db: &'db dyn Db, class: Class<'db>) -> bool {
|
pub fn is_instance_of(self, db: &'db dyn Db, class: Class<'db>) -> bool {
|
||||||
self.class().is_subclass_of(db, class)
|
self.class().is_subclass_of(db, class)
|
||||||
}
|
}
|
||||||
|
@ -3359,7 +3332,7 @@ impl<'db> Class<'db> {
|
||||||
/// Return the metaclass of this class, or `type[Unknown]` if the metaclass cannot be inferred.
|
/// Return the metaclass of this class, or `type[Unknown]` if the metaclass cannot be inferred.
|
||||||
pub(crate) fn metaclass(self, db: &'db dyn Db) -> Type<'db> {
|
pub(crate) fn metaclass(self, db: &'db dyn Db) -> Type<'db> {
|
||||||
self.try_metaclass(db)
|
self.try_metaclass(db)
|
||||||
.unwrap_or_else(|_| Type::subclass_of_base(ClassBase::Unknown))
|
.unwrap_or_else(|_| SubclassOfType::subclass_of_unknown())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the metaclass of this class, or an error if the metaclass cannot be inferred.
|
/// Return the metaclass of this class, or an error if the metaclass cannot be inferred.
|
||||||
|
@ -3372,7 +3345,7 @@ impl<'db> Class<'db> {
|
||||||
// We emit diagnostics for cyclic class definitions elsewhere.
|
// We emit diagnostics for cyclic class definitions elsewhere.
|
||||||
// Avoid attempting to infer the metaclass if the class is cyclically defined:
|
// Avoid attempting to infer the metaclass if the class is cyclically defined:
|
||||||
// it would be easy to enter an infinite loop.
|
// it would be easy to enter an infinite loop.
|
||||||
return Ok(Type::subclass_of_base(ClassBase::Unknown));
|
return Ok(SubclassOfType::subclass_of_unknown());
|
||||||
}
|
}
|
||||||
|
|
||||||
let explicit_metaclass = self.explicit_metaclass(db);
|
let explicit_metaclass = self.explicit_metaclass(db);
|
||||||
|
@ -3549,34 +3522,6 @@ impl<'db> From<ClassLiteralType<'db>> for Type<'db> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A type that represents `type[C]`, i.e. the class literal `C` and class literals that are subclasses of `C`.
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Update)]
|
|
||||||
pub struct SubclassOfType<'db> {
|
|
||||||
base: ClassBase<'db>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'db> SubclassOfType<'db> {
|
|
||||||
fn member(self, db: &'db dyn Db, name: &str) -> Symbol<'db> {
|
|
||||||
Type::from(self.base).member(db, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_subtype_of(self, db: &'db dyn Db, other: SubclassOfType<'db>) -> bool {
|
|
||||||
match (self.base, other.base) {
|
|
||||||
// Non-fully-static types do not participate in subtyping
|
|
||||||
(ClassBase::Any | ClassBase::Unknown | ClassBase::Todo(_), _)
|
|
||||||
| (_, ClassBase::Any | ClassBase::Unknown | ClassBase::Todo(_)) => false,
|
|
||||||
|
|
||||||
// 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`.
|
|
||||||
(ClassBase::Class(self_class), ClassBase::Class(other_class)) => {
|
|
||||||
// N.B. The subclass relation is fully static
|
|
||||||
self_class.is_subclass_of(db, other_class)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A type representing the set of runtime objects which are instances of a certain class.
|
/// A type representing the set of runtime objects which are instances of a certain class.
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, salsa::Update)]
|
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, salsa::Update)]
|
||||||
pub struct InstanceType<'db> {
|
pub struct InstanceType<'db> {
|
||||||
|
@ -3849,15 +3794,17 @@ pub(crate) mod tests {
|
||||||
let elements = tys.into_iter().map(|ty| ty.into_type(db));
|
let elements = tys.into_iter().map(|ty| ty.into_type(db));
|
||||||
TupleType::from_elements(db, elements)
|
TupleType::from_elements(db, elements)
|
||||||
}
|
}
|
||||||
Ty::SubclassOfAny => Type::subclass_of_base(ClassBase::Any),
|
Ty::SubclassOfAny => SubclassOfType::subclass_of_any(),
|
||||||
Ty::SubclassOfUnknown => Type::subclass_of_base(ClassBase::Unknown),
|
Ty::SubclassOfUnknown => SubclassOfType::subclass_of_unknown(),
|
||||||
Ty::SubclassOfBuiltinClass(s) => Type::subclass_of(
|
Ty::SubclassOfBuiltinClass(s) => SubclassOfType::from(
|
||||||
|
db,
|
||||||
builtins_symbol(db, s)
|
builtins_symbol(db, s)
|
||||||
.expect_type()
|
.expect_type()
|
||||||
.expect_class_literal()
|
.expect_class_literal()
|
||||||
.class,
|
.class,
|
||||||
),
|
),
|
||||||
Ty::SubclassOfAbcClass(s) => Type::subclass_of(
|
Ty::SubclassOfAbcClass(s) => SubclassOfType::from(
|
||||||
|
db,
|
||||||
known_module_symbol(db, KnownModule::Abc, s)
|
known_module_symbol(db, KnownModule::Abc, s)
|
||||||
.expect_type()
|
.expect_type()
|
||||||
.expect_class_literal()
|
.expect_class_literal()
|
||||||
|
@ -4019,6 +3966,7 @@ pub(crate) mod tests {
|
||||||
#[test_case(Ty::AlwaysFalsy, Ty::BuiltinInstance("object"))]
|
#[test_case(Ty::AlwaysFalsy, Ty::BuiltinInstance("object"))]
|
||||||
#[test_case(Ty::Never, Ty::AlwaysTruthy)]
|
#[test_case(Ty::Never, Ty::AlwaysTruthy)]
|
||||||
#[test_case(Ty::Never, Ty::AlwaysFalsy)]
|
#[test_case(Ty::Never, Ty::AlwaysFalsy)]
|
||||||
|
#[test_case(Ty::BuiltinClassLiteral("bool"), Ty::SubclassOfBuiltinClass("int"))]
|
||||||
fn is_subtype_of(from: Ty, to: Ty) {
|
fn is_subtype_of(from: Ty, to: Ty) {
|
||||||
let db = setup_db();
|
let db = setup_db();
|
||||||
assert!(from.into_type(&db).is_subtype_of(&db, to.into_type(&db)));
|
assert!(from.into_type(&db).is_subtype_of(&db, to.into_type(&db)));
|
||||||
|
@ -4089,11 +4037,12 @@ pub(crate) mod tests {
|
||||||
assert!(literal_derived.is_class_literal());
|
assert!(literal_derived.is_class_literal());
|
||||||
|
|
||||||
// `subclass_of_base` represents `Type[Base]`.
|
// `subclass_of_base` represents `Type[Base]`.
|
||||||
let subclass_of_base = Type::subclass_of(literal_base.expect_class_literal().class);
|
let subclass_of_base = SubclassOfType::from(&db, literal_base.expect_class_literal().class);
|
||||||
assert!(literal_base.is_subtype_of(&db, subclass_of_base));
|
assert!(literal_base.is_subtype_of(&db, subclass_of_base));
|
||||||
assert!(literal_derived.is_subtype_of(&db, subclass_of_base));
|
assert!(literal_derived.is_subtype_of(&db, subclass_of_base));
|
||||||
|
|
||||||
let subclass_of_derived = Type::subclass_of(literal_derived.expect_class_literal().class);
|
let subclass_of_derived =
|
||||||
|
SubclassOfType::from(&db, literal_derived.expect_class_literal().class);
|
||||||
assert!(literal_derived.is_subtype_of(&db, subclass_of_derived));
|
assert!(literal_derived.is_subtype_of(&db, subclass_of_derived));
|
||||||
assert!(!literal_base.is_subtype_of(&db, subclass_of_derived));
|
assert!(!literal_base.is_subtype_of(&db, subclass_of_derived));
|
||||||
|
|
||||||
|
@ -4274,8 +4223,8 @@ pub(crate) mod tests {
|
||||||
let literal_a = super::global_symbol(&db, module, "A").expect_type();
|
let literal_a = super::global_symbol(&db, module, "A").expect_type();
|
||||||
let literal_b = super::global_symbol(&db, module, "B").expect_type();
|
let literal_b = super::global_symbol(&db, module, "B").expect_type();
|
||||||
|
|
||||||
let subclass_of_a = Type::subclass_of(literal_a.expect_class_literal().class);
|
let subclass_of_a = SubclassOfType::from(&db, literal_a.expect_class_literal().class);
|
||||||
let subclass_of_b = Type::subclass_of(literal_b.expect_class_literal().class);
|
let subclass_of_b = SubclassOfType::from(&db, literal_b.expect_class_literal().class);
|
||||||
|
|
||||||
// Class literals are always disjoint. They are singleton types
|
// Class literals are always disjoint. They are singleton types
|
||||||
assert!(literal_a.is_disjoint_from(&db, literal_b));
|
assert!(literal_a.is_disjoint_from(&db, literal_b));
|
||||||
|
@ -4353,6 +4302,7 @@ pub(crate) mod tests {
|
||||||
#[test_case(Ty::None)]
|
#[test_case(Ty::None)]
|
||||||
#[test_case(Ty::BooleanLiteral(true))]
|
#[test_case(Ty::BooleanLiteral(true))]
|
||||||
#[test_case(Ty::BooleanLiteral(false))]
|
#[test_case(Ty::BooleanLiteral(false))]
|
||||||
|
#[test_case(Ty::SubclassOfBuiltinClass("bool"))] // a `@final` class
|
||||||
fn is_singleton(from: Ty) {
|
fn is_singleton(from: Ty) {
|
||||||
let db = setup_db();
|
let db = setup_db();
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,13 @@ pub enum ClassBase<'db> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'db> ClassBase<'db> {
|
impl<'db> ClassBase<'db> {
|
||||||
|
pub const fn is_dynamic(self) -> bool {
|
||||||
|
match self {
|
||||||
|
ClassBase::Any | ClassBase::Unknown | ClassBase::Todo(_) => true,
|
||||||
|
ClassBase::Class(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn display(self, db: &'db dyn Db) -> impl std::fmt::Display + 'db {
|
pub fn display(self, db: &'db dyn Db) -> impl std::fmt::Display + 'db {
|
||||||
struct Display<'db> {
|
struct Display<'db> {
|
||||||
base: ClassBase<'db>,
|
base: ClassBase<'db>,
|
||||||
|
|
|
@ -8,8 +8,8 @@ use ruff_python_literal::escape::AsciiEscape;
|
||||||
|
|
||||||
use crate::types::class_base::ClassBase;
|
use crate::types::class_base::ClassBase;
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
ClassLiteralType, InstanceType, IntersectionType, KnownClass, StringLiteralType,
|
ClassLiteralType, InstanceType, IntersectionType, KnownClass, StringLiteralType, Type,
|
||||||
SubclassOfType, Type, UnionType,
|
UnionType,
|
||||||
};
|
};
|
||||||
use crate::Db;
|
use crate::Db;
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
|
@ -84,16 +84,14 @@ impl Display for DisplayRepresentation<'_> {
|
||||||
}
|
}
|
||||||
// TODO functions and classes should display using a fully qualified name
|
// TODO functions and classes should display using a fully qualified name
|
||||||
Type::ClassLiteral(ClassLiteralType { class }) => f.write_str(class.name(self.db)),
|
Type::ClassLiteral(ClassLiteralType { class }) => f.write_str(class.name(self.db)),
|
||||||
Type::SubclassOf(SubclassOfType {
|
Type::SubclassOf(subclass_of_ty) => match subclass_of_ty.subclass_of() {
|
||||||
base: ClassBase::Class(class),
|
|
||||||
}) => {
|
|
||||||
// Only show the bare class name here; ClassBase::display would render this as
|
// Only show the bare class name here; ClassBase::display would render this as
|
||||||
// type[<class 'Foo'>] instead of type[Foo].
|
// type[<class 'Foo'>] instead of type[Foo].
|
||||||
write!(f, "type[{}]", class.name(self.db))
|
ClassBase::Class(class) => write!(f, "type[{}]", class.name(self.db)),
|
||||||
}
|
base @ (ClassBase::Any | ClassBase::Todo(_) | ClassBase::Unknown) => {
|
||||||
Type::SubclassOf(SubclassOfType { base }) => {
|
write!(f, "type[{}]", base.display(self.db))
|
||||||
write!(f, "type[{}]", base.display(self.db))
|
}
|
||||||
}
|
},
|
||||||
Type::KnownInstance(known_instance) => f.write_str(known_instance.repr(self.db)),
|
Type::KnownInstance(known_instance) => f.write_str(known_instance.repr(self.db)),
|
||||||
Type::FunctionLiteral(function) => f.write_str(function.name(self.db)),
|
Type::FunctionLiteral(function) => f.write_str(function.name(self.db)),
|
||||||
Type::Union(union) => union.display(self.db).fmt(f),
|
Type::Union(union) => union.display(self.db).fmt(f),
|
||||||
|
|
|
@ -49,7 +49,6 @@ use crate::semantic_index::semantic_index;
|
||||||
use crate::semantic_index::symbol::{NodeWithScopeKind, NodeWithScopeRef, ScopeId};
|
use crate::semantic_index::symbol::{NodeWithScopeKind, NodeWithScopeRef, ScopeId};
|
||||||
use crate::semantic_index::SemanticIndex;
|
use crate::semantic_index::SemanticIndex;
|
||||||
use crate::stdlib::builtins_module_scope;
|
use crate::stdlib::builtins_module_scope;
|
||||||
use crate::types::class_base::ClassBase;
|
|
||||||
use crate::types::diagnostic::{
|
use crate::types::diagnostic::{
|
||||||
report_invalid_assignment, report_unresolved_module, TypeCheckDiagnostics, CALL_NON_CALLABLE,
|
report_invalid_assignment, report_unresolved_module, TypeCheckDiagnostics, CALL_NON_CALLABLE,
|
||||||
CALL_POSSIBLY_UNBOUND_METHOD, CONFLICTING_DECLARATIONS, CONFLICTING_METACLASS,
|
CALL_POSSIBLY_UNBOUND_METHOD, CONFLICTING_DECLARATIONS, CONFLICTING_METACLASS,
|
||||||
|
@ -65,7 +64,7 @@ use crate::types::{
|
||||||
typing_extensions_symbol, Boundness, CallDunderResult, Class, ClassLiteralType, FunctionType,
|
typing_extensions_symbol, Boundness, CallDunderResult, Class, ClassLiteralType, FunctionType,
|
||||||
InstanceType, IntersectionBuilder, IntersectionType, IterationOutcome, KnownClass,
|
InstanceType, IntersectionBuilder, IntersectionType, IterationOutcome, KnownClass,
|
||||||
KnownFunction, KnownInstanceType, MetaclassCandidate, MetaclassErrorKind, SliceLiteralType,
|
KnownFunction, KnownInstanceType, MetaclassCandidate, MetaclassErrorKind, SliceLiteralType,
|
||||||
Symbol, Truthiness, TupleType, Type, TypeAliasType, TypeArrayDisplay,
|
SubclassOfType, Symbol, Truthiness, TupleType, Type, TypeAliasType, TypeArrayDisplay,
|
||||||
TypeVarBoundOrConstraints, TypeVarInstance, UnionBuilder, UnionType,
|
TypeVarBoundOrConstraints, TypeVarInstance, UnionBuilder, UnionType,
|
||||||
};
|
};
|
||||||
use crate::unpack::Unpack;
|
use crate::unpack::Unpack;
|
||||||
|
@ -4903,9 +4902,11 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
ast::Expr::Name(_) | ast::Expr::Attribute(_) => {
|
ast::Expr::Name(_) | ast::Expr::Attribute(_) => {
|
||||||
let name_ty = self.infer_expression(slice);
|
let name_ty = self.infer_expression(slice);
|
||||||
match name_ty {
|
match name_ty {
|
||||||
Type::ClassLiteral(ClassLiteralType { class }) => Type::subclass_of(class),
|
Type::ClassLiteral(ClassLiteralType { class }) => {
|
||||||
|
SubclassOfType::from(self.db(), class)
|
||||||
|
}
|
||||||
Type::KnownInstance(KnownInstanceType::Any) => {
|
Type::KnownInstance(KnownInstanceType::Any) => {
|
||||||
Type::subclass_of_base(ClassBase::Any)
|
SubclassOfType::subclass_of_any()
|
||||||
}
|
}
|
||||||
_ => todo_type!("unsupported type[X] special form"),
|
_ => todo_type!("unsupported type[X] special form"),
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,8 +7,8 @@ use crate::semantic_index::expression::Expression;
|
||||||
use crate::semantic_index::symbol::{ScopeId, ScopedSymbolId, SymbolTable};
|
use crate::semantic_index::symbol::{ScopeId, ScopedSymbolId, SymbolTable};
|
||||||
use crate::semantic_index::symbol_table;
|
use crate::semantic_index::symbol_table;
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
infer_expression_types, ClassBase, ClassLiteralType, IntersectionBuilder, KnownClass,
|
infer_expression_types, ClassLiteralType, IntersectionBuilder, KnownClass, KnownFunction,
|
||||||
KnownFunction, SubclassOfType, Truthiness, Type, UnionBuilder,
|
SubclassOfType, Truthiness, Type, UnionBuilder,
|
||||||
};
|
};
|
||||||
use crate::Db;
|
use crate::Db;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
@ -97,6 +97,11 @@ impl KnownConstraintFunction {
|
||||||
/// The `classinfo` argument can be a class literal, a tuple of (tuples of) class literals. PEP 604
|
/// The `classinfo` argument can be a class literal, a tuple of (tuples of) class literals. PEP 604
|
||||||
/// union types are not yet supported. Returns `None` if the `classinfo` argument has a wrong type.
|
/// union types are not yet supported. Returns `None` if the `classinfo` argument has a wrong type.
|
||||||
fn generate_constraint<'db>(self, db: &'db dyn Db, classinfo: Type<'db>) -> Option<Type<'db>> {
|
fn generate_constraint<'db>(self, db: &'db dyn Db, classinfo: Type<'db>) -> Option<Type<'db>> {
|
||||||
|
let constraint_fn = |class| match self {
|
||||||
|
KnownConstraintFunction::IsInstance => Type::instance(class),
|
||||||
|
KnownConstraintFunction::IsSubclass => SubclassOfType::from(db, class),
|
||||||
|
};
|
||||||
|
|
||||||
match classinfo {
|
match classinfo {
|
||||||
Type::Tuple(tuple) => {
|
Type::Tuple(tuple) => {
|
||||||
let mut builder = UnionBuilder::new(db);
|
let mut builder = UnionBuilder::new(db);
|
||||||
|
@ -105,13 +110,10 @@ impl KnownConstraintFunction {
|
||||||
}
|
}
|
||||||
Some(builder.build())
|
Some(builder.build())
|
||||||
}
|
}
|
||||||
Type::ClassLiteral(ClassLiteralType { class })
|
Type::ClassLiteral(ClassLiteralType { class }) => Some(constraint_fn(class)),
|
||||||
| Type::SubclassOf(SubclassOfType {
|
Type::SubclassOf(subclass_of_ty) => {
|
||||||
base: ClassBase::Class(class),
|
subclass_of_ty.subclass_of().into_class().map(constraint_fn)
|
||||||
}) => Some(match self {
|
}
|
||||||
KnownConstraintFunction::IsInstance => Type::instance(class),
|
|
||||||
KnownConstraintFunction::IsSubclass => Type::subclass_of(class),
|
|
||||||
}),
|
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
92
crates/red_knot_python_semantic/src/types/subclass_of.rs
Normal file
92
crates/red_knot_python_semantic/src/types/subclass_of.rs
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
use super::{ClassBase, ClassLiteralType, Db, KnownClass, Symbol, Type};
|
||||||
|
|
||||||
|
/// A type that represents `type[C]`, i.e. the class object `C` and class objects that are subclasses of `C`.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Update)]
|
||||||
|
pub struct SubclassOfType<'db> {
|
||||||
|
// Keep this field private, so that the only way of constructing the struct is through the `from` method.
|
||||||
|
subclass_of: ClassBase<'db>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'db> SubclassOfType<'db> {
|
||||||
|
/// Construct a new [`Type`] instance representing a given class object (or a given dynamic type)
|
||||||
|
/// and all possible subclasses of that class object/dynamic type.
|
||||||
|
///
|
||||||
|
/// This method does not always return a [`Type::SubclassOf`] variant.
|
||||||
|
/// If the class object is known to be a final class,
|
||||||
|
/// this method will return a [`Type::ClassLiteral`] variant; this is a more precise type.
|
||||||
|
/// If the class object is `builtins.object`, `Type::Instance(<builtins.type>)` will be returned;
|
||||||
|
/// this is no more precise, but it is exactly equivalent to `type[object]`.
|
||||||
|
///
|
||||||
|
/// The eager normalization here means that we do not need to worry elsewhere about distinguishing
|
||||||
|
/// between `@final` classes and other classes when dealing with [`Type::SubclassOf`] variants.
|
||||||
|
pub(crate) fn from(db: &'db dyn Db, subclass_of: impl Into<ClassBase<'db>>) -> Type<'db> {
|
||||||
|
let subclass_of = subclass_of.into();
|
||||||
|
match subclass_of {
|
||||||
|
ClassBase::Any | ClassBase::Unknown | ClassBase::Todo(_) => {
|
||||||
|
Type::SubclassOf(Self { subclass_of })
|
||||||
|
}
|
||||||
|
ClassBase::Class(class) => {
|
||||||
|
if class.is_final(db) {
|
||||||
|
Type::ClassLiteral(ClassLiteralType { class })
|
||||||
|
} else if class.is_known(db, KnownClass::Object) {
|
||||||
|
KnownClass::Type.to_instance(db)
|
||||||
|
} else {
|
||||||
|
Type::SubclassOf(Self { subclass_of })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return a [`Type`] instance representing the type `type[Unknown]`.
|
||||||
|
pub(crate) const fn subclass_of_unknown() -> Type<'db> {
|
||||||
|
Type::SubclassOf(SubclassOfType {
|
||||||
|
subclass_of: ClassBase::Unknown,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return a [`Type`] instance representing the type `type[Any]`.
|
||||||
|
pub(crate) const fn subclass_of_any() -> Type<'db> {
|
||||||
|
Type::SubclassOf(SubclassOfType {
|
||||||
|
subclass_of: ClassBase::Any,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the inner [`ClassBase`] value wrapped by this `SubclassOfType`.
|
||||||
|
pub(crate) const fn subclass_of(self) -> ClassBase<'db> {
|
||||||
|
self.subclass_of
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn is_dynamic(self) -> bool {
|
||||||
|
// Unpack `self` so that we're forced to update this method if any more fields are added in the future.
|
||||||
|
let Self { subclass_of } = self;
|
||||||
|
subclass_of.is_dynamic()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn is_fully_static(self) -> bool {
|
||||||
|
!self.is_dynamic()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn member(self, db: &'db dyn Db, name: &str) -> Symbol<'db> {
|
||||||
|
Type::from(self.subclass_of).member(db, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return `true` if `self` is a subtype of `other`.
|
||||||
|
///
|
||||||
|
/// This can only return `true` if `self.subclass_of` is a [`ClassBase::Class`] variant;
|
||||||
|
/// only fully static types participate in subtyping.
|
||||||
|
pub(crate) fn is_subtype_of(self, db: &'db dyn Db, other: SubclassOfType<'db>) -> bool {
|
||||||
|
match (self.subclass_of, other.subclass_of) {
|
||||||
|
// Non-fully-static types do not participate in subtyping
|
||||||
|
(ClassBase::Any | ClassBase::Unknown | ClassBase::Todo(_), _)
|
||||||
|
| (_, ClassBase::Any | ClassBase::Unknown | ClassBase::Todo(_)) => false,
|
||||||
|
|
||||||
|
// 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`.
|
||||||
|
(ClassBase::Class(self_class), ClassBase::Class(other_class)) => {
|
||||||
|
// N.B. The subclass relation is fully static
|
||||||
|
self_class.is_subclass_of(db, other_class)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue