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):
|
||||
reveal_type(t) # revealed: type[A] & type[B]
|
||||
else:
|
||||
reveal_type(t) # revealed: type[object] & ~type[A]
|
||||
reveal_type(t) # revealed: type & ~type[A]
|
||||
```
|
||||
|
||||
### Handling of `None`
|
||||
|
|
|
@ -142,3 +142,25 @@ class Foo(type[int]): ...
|
|||
# TODO: should be `tuple[Literal[Foo], Literal[type], 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
|
||||
def f(x: type[object]):
|
||||
reveal_type(x) # revealed: type[object]
|
||||
# TODO: bound method types
|
||||
reveal_type(x.__repr__) # revealed: Literal[__repr__]
|
||||
reveal_type(x) # revealed: type
|
||||
reveal_type(x.__repr__) # revealed: @Todo(instance attributes)
|
||||
|
||||
class A: ...
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ pub(crate) use self::infer::{
|
|||
};
|
||||
pub use self::narrow::KnownConstraintFunction;
|
||||
pub(crate) use self::signatures::Signature;
|
||||
pub use self::subclass_of::SubclassOfType;
|
||||
use crate::module_name::ModuleName;
|
||||
use crate::module_resolver::{file_to_module, resolve_module, KnownModule};
|
||||
use crate::semantic_index::ast_ids::HasScopedExpressionId;
|
||||
|
@ -49,6 +50,7 @@ mod narrow;
|
|||
mod signatures;
|
||||
mod slots;
|
||||
mod string_annotation;
|
||||
mod subclass_of;
|
||||
mod unpacker;
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -693,14 +695,6 @@ impl<'db> Type<'db> {
|
|||
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 {
|
||||
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).
|
||||
(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)
|
||||
}
|
||||
// `Literal[<class 'C'>]` is a subtype of `type[B]` if `C` is a subclass of `B`,
|
||||
// since `type[B]` describes all possible runtime subclasses of the class object `B`.
|
||||
(
|
||||
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]`?
|
||||
(Type::SubclassOf(self_subclass_ty), Type::SubclassOf(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
|
||||
// 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`.
|
||||
|
@ -904,16 +913,19 @@ impl<'db> Type<'db> {
|
|||
// 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::SubclassOf(subclass_of_ty),
|
||||
Type::Instance(InstanceType {
|
||||
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")`
|
||||
(Type::SubclassOf(_), _) => KnownClass::Type.to_instance(db).is_subtype_of(db, target),
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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
|
||||
|
@ -922,19 +934,6 @@ impl<'db> Type<'db> {
|
|||
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`,
|
||||
// which means that all instances of `bool` are also instances of `int`
|
||||
(Type::Instance(self_instance), Type::Instance(target_instance)) => {
|
||||
|
@ -975,30 +974,28 @@ impl<'db> Type<'db> {
|
|||
},
|
||||
)
|
||||
}
|
||||
(
|
||||
Type::SubclassOf(SubclassOfType {
|
||||
base: ClassBase::Any | ClassBase::Todo(_) | ClassBase::Unknown,
|
||||
}),
|
||||
Type::SubclassOf(_),
|
||||
) => true,
|
||||
(
|
||||
Type::SubclassOf(SubclassOfType {
|
||||
base: ClassBase::Any | ClassBase::Todo(_) | ClassBase::Unknown,
|
||||
}),
|
||||
Type::Instance(_),
|
||||
) if target.is_assignable_to(db, KnownClass::Type.to_instance(db)) => true,
|
||||
(
|
||||
Type::Instance(_),
|
||||
Type::SubclassOf(SubclassOfType {
|
||||
base: ClassBase::Any | ClassBase::Todo(_) | ClassBase::Unknown,
|
||||
}),
|
||||
) if self.is_assignable_to(db, KnownClass::Type.to_instance(db)) => true,
|
||||
(
|
||||
Type::ClassLiteral(_) | Type::SubclassOf(_),
|
||||
Type::SubclassOf(SubclassOfType {
|
||||
base: ClassBase::Any | ClassBase::Todo(_) | ClassBase::Unknown,
|
||||
}),
|
||||
) => true,
|
||||
(Type::SubclassOf(subclass_of_ty), Type::SubclassOf(_))
|
||||
if subclass_of_ty.is_dynamic() =>
|
||||
{
|
||||
true
|
||||
}
|
||||
(Type::SubclassOf(subclass_of_ty), Type::Instance(_))
|
||||
if subclass_of_ty.is_dynamic()
|
||||
&& target.is_assignable_to(db, KnownClass::Type.to_instance(db)) =>
|
||||
{
|
||||
true
|
||||
}
|
||||
(Type::Instance(_), Type::SubclassOf(subclass_of_ty))
|
||||
if subclass_of_ty.is_dynamic()
|
||||
&& 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() =>
|
||||
{
|
||||
true
|
||||
}
|
||||
// TODO other types containing gradual forms (e.g. generics containing Any/Unknown)
|
||||
_ => self.is_subtype_of(db, target),
|
||||
}
|
||||
|
@ -1014,33 +1011,9 @@ impl<'db> Type<'db> {
|
|||
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
|
||||
// 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
|
||||
// representation.
|
||||
self == other
|
||||
|
@ -1140,17 +1113,16 @@ impl<'db> Type<'db> {
|
|||
) => true,
|
||||
|
||||
(
|
||||
Type::SubclassOf(SubclassOfType {
|
||||
base: ClassBase::Class(class_a),
|
||||
}),
|
||||
Type::SubclassOf(subclass_of_ty),
|
||||
Type::ClassLiteral(ClassLiteralType { class: class_b }),
|
||||
)
|
||||
| (
|
||||
Type::ClassLiteral(ClassLiteralType { class: class_b }),
|
||||
Type::SubclassOf(SubclassOfType {
|
||||
base: ClassBase::Class(class_a),
|
||||
}),
|
||||
) => !class_b.is_subclass_of(db, class_a),
|
||||
Type::SubclassOf(subclass_of_ty),
|
||||
) => match subclass_of_ty.subclass_of() {
|
||||
ClassBase::Any | ClassBase::Todo(_) | ClassBase::Unknown => false,
|
||||
ClassBase::Class(class_a) => !class_b.is_subclass_of(db, class_a),
|
||||
},
|
||||
|
||||
(Type::SubclassOf(_), Type::SubclassOf(_)) => false,
|
||||
|
||||
|
@ -1198,10 +1170,10 @@ impl<'db> Type<'db> {
|
|||
}
|
||||
|
||||
(Type::SubclassOf(_), other) | (other, Type::SubclassOf(_)) => {
|
||||
// TODO: Once we have support for final classes, we can determine disjointness in more cases
|
||||
// here. However, note that it might be better to turn `Type::SubclassOf('FinalClass')` into
|
||||
// `Type::ClassLiteral('FinalClass')` during construction, instead of adding special cases for
|
||||
// final classes inside `Type::SubclassOf` everywhere.
|
||||
// TODO we could do better here: if both variants are `SubclassOf` and they have different "solid bases",
|
||||
// multiple inheritance between the two is impossible, so they are disjoint.
|
||||
//
|
||||
// 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))
|
||||
}
|
||||
|
||||
|
@ -1357,7 +1329,7 @@ impl<'db> Type<'db> {
|
|||
| Type::KnownInstance(_)
|
||||
| Type::AlwaysFalsy
|
||||
| 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(_) => {
|
||||
// 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.
|
||||
|
@ -1421,11 +1393,8 @@ impl<'db> Type<'db> {
|
|||
// are both of type Literal[345], for example.
|
||||
false
|
||||
}
|
||||
Type::SubclassOf(..) => {
|
||||
// TODO once we have support for final classes, we can return `true` for some
|
||||
// cases: type[C] is a singleton if C is final.
|
||||
false
|
||||
}
|
||||
// We eagerly transform `SubclassOf` to `ClassLiteral` for final types, so `SubclassOf` is never a singleton.
|
||||
Type::SubclassOf(..) => false,
|
||||
Type::BooleanLiteral(_)
|
||||
| Type::FunctionLiteral(..)
|
||||
| Type::ClassLiteral(..)
|
||||
|
@ -1678,7 +1647,8 @@ impl<'db> Type<'db> {
|
|||
Type::ClassLiteral(ClassLiteralType { class }) => {
|
||||
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()
|
||||
.map(|class| Type::class_literal(class).bool(db))
|
||||
.unwrap_or(Truthiness::Ambiguous),
|
||||
|
@ -1972,11 +1942,11 @@ impl<'db> Type<'db> {
|
|||
Type::Unknown => Type::Unknown,
|
||||
Type::Never => Type::Never,
|
||||
Type::ClassLiteral(ClassLiteralType { class }) => Type::instance(*class),
|
||||
Type::SubclassOf(SubclassOfType { base }) => match base {
|
||||
ClassBase::Class(class) => Type::instance(*class),
|
||||
Type::SubclassOf(subclass_of_ty) => match subclass_of_ty.subclass_of() {
|
||||
ClassBase::Class(class) => Type::instance(class),
|
||||
ClassBase::Any => Type::Any,
|
||||
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::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> {
|
||||
match self {
|
||||
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::Union(union) => union.map(db, |ty| ty.to_meta_type(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::Tuple(_) => KnownClass::Tuple.to_class_literal(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::Class(class) => Type::subclass_of_base(
|
||||
ClassBase::Class(class) => SubclassOfType::from(
|
||||
db,
|
||||
ClassBase::try_from_ty(db, class.metaclass(db)).unwrap_or(ClassBase::Unknown),
|
||||
),
|
||||
},
|
||||
|
||||
Type::StringLiteral(_) | Type::LiteralString => KnownClass::Str.to_class_literal(db),
|
||||
Type::Any => Type::subclass_of_base(ClassBase::Any),
|
||||
Type::Unknown => Type::subclass_of_base(ClassBase::Unknown),
|
||||
Type::Any => SubclassOfType::subclass_of_any(),
|
||||
Type::Unknown => SubclassOfType::subclass_of_unknown(),
|
||||
// TODO intersections
|
||||
Type::Intersection(_) => Type::subclass_of_base(
|
||||
Type::Intersection(_) => SubclassOfType::from(
|
||||
db,
|
||||
ClassBase::try_from_ty(db, todo_type!("Intersection meta-type"))
|
||||
.expect("Type::Todo should be a valid ClassBase"),
|
||||
),
|
||||
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> {
|
||||
self.to_class_literal(db)
|
||||
.into_class_literal()
|
||||
.map(|ClassLiteralType { class }| Type::subclass_of(class))
|
||||
.unwrap_or(Type::subclass_of_base(ClassBase::Unknown))
|
||||
.map(|ClassLiteralType { class }| SubclassOfType::from(db, class))
|
||||
.unwrap_or_else(SubclassOfType::subclass_of_unknown)
|
||||
}
|
||||
|
||||
/// 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)
|
||||
}
|
||||
|
||||
/// Return `true` if this symbol is an instance of `class`.
|
||||
pub fn is_instance_of(self, db: &'db dyn Db, class: Class<'db>) -> bool {
|
||||
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.
|
||||
pub(crate) fn metaclass(self, db: &'db dyn Db) -> Type<'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.
|
||||
|
@ -3372,7 +3345,7 @@ impl<'db> Class<'db> {
|
|||
// We emit diagnostics for cyclic class definitions elsewhere.
|
||||
// Avoid attempting to infer the metaclass if the class is cyclically defined:
|
||||
// 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);
|
||||
|
@ -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.
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, salsa::Update)]
|
||||
pub struct InstanceType<'db> {
|
||||
|
@ -3849,15 +3794,17 @@ pub(crate) mod tests {
|
|||
let elements = tys.into_iter().map(|ty| ty.into_type(db));
|
||||
TupleType::from_elements(db, elements)
|
||||
}
|
||||
Ty::SubclassOfAny => Type::subclass_of_base(ClassBase::Any),
|
||||
Ty::SubclassOfUnknown => Type::subclass_of_base(ClassBase::Unknown),
|
||||
Ty::SubclassOfBuiltinClass(s) => Type::subclass_of(
|
||||
Ty::SubclassOfAny => SubclassOfType::subclass_of_any(),
|
||||
Ty::SubclassOfUnknown => SubclassOfType::subclass_of_unknown(),
|
||||
Ty::SubclassOfBuiltinClass(s) => SubclassOfType::from(
|
||||
db,
|
||||
builtins_symbol(db, s)
|
||||
.expect_type()
|
||||
.expect_class_literal()
|
||||
.class,
|
||||
),
|
||||
Ty::SubclassOfAbcClass(s) => Type::subclass_of(
|
||||
Ty::SubclassOfAbcClass(s) => SubclassOfType::from(
|
||||
db,
|
||||
known_module_symbol(db, KnownModule::Abc, s)
|
||||
.expect_type()
|
||||
.expect_class_literal()
|
||||
|
@ -4019,6 +3966,7 @@ pub(crate) mod tests {
|
|||
#[test_case(Ty::AlwaysFalsy, Ty::BuiltinInstance("object"))]
|
||||
#[test_case(Ty::Never, Ty::AlwaysTruthy)]
|
||||
#[test_case(Ty::Never, Ty::AlwaysFalsy)]
|
||||
#[test_case(Ty::BuiltinClassLiteral("bool"), Ty::SubclassOfBuiltinClass("int"))]
|
||||
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)));
|
||||
|
@ -4089,11 +4037,12 @@ pub(crate) mod tests {
|
|||
assert!(literal_derived.is_class_literal());
|
||||
|
||||
// `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_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_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_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_b = Type::subclass_of(literal_b.expect_class_literal().class);
|
||||
let subclass_of_a = SubclassOfType::from(&db, literal_a.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
|
||||
assert!(literal_a.is_disjoint_from(&db, literal_b));
|
||||
|
@ -4353,6 +4302,7 @@ pub(crate) mod tests {
|
|||
#[test_case(Ty::None)]
|
||||
#[test_case(Ty::BooleanLiteral(true))]
|
||||
#[test_case(Ty::BooleanLiteral(false))]
|
||||
#[test_case(Ty::SubclassOfBuiltinClass("bool"))] // a `@final` class
|
||||
fn is_singleton(from: Ty) {
|
||||
let db = setup_db();
|
||||
|
||||
|
|
|
@ -18,6 +18,13 @@ pub enum 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 {
|
||||
struct Display<'db> {
|
||||
base: ClassBase<'db>,
|
||||
|
|
|
@ -8,8 +8,8 @@ use ruff_python_literal::escape::AsciiEscape;
|
|||
|
||||
use crate::types::class_base::ClassBase;
|
||||
use crate::types::{
|
||||
ClassLiteralType, InstanceType, IntersectionType, KnownClass, StringLiteralType,
|
||||
SubclassOfType, Type, UnionType,
|
||||
ClassLiteralType, InstanceType, IntersectionType, KnownClass, StringLiteralType, Type,
|
||||
UnionType,
|
||||
};
|
||||
use crate::Db;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
@ -84,16 +84,14 @@ impl Display for DisplayRepresentation<'_> {
|
|||
}
|
||||
// TODO functions and classes should display using a fully qualified name
|
||||
Type::ClassLiteral(ClassLiteralType { class }) => f.write_str(class.name(self.db)),
|
||||
Type::SubclassOf(SubclassOfType {
|
||||
base: ClassBase::Class(class),
|
||||
}) => {
|
||||
Type::SubclassOf(subclass_of_ty) => match subclass_of_ty.subclass_of() {
|
||||
// Only show the bare class name here; ClassBase::display would render this as
|
||||
// type[<class 'Foo'>] instead of type[Foo].
|
||||
write!(f, "type[{}]", class.name(self.db))
|
||||
}
|
||||
Type::SubclassOf(SubclassOfType { base }) => {
|
||||
ClassBase::Class(class) => write!(f, "type[{}]", class.name(self.db)),
|
||||
base @ (ClassBase::Any | ClassBase::Todo(_) | ClassBase::Unknown) => {
|
||||
write!(f, "type[{}]", base.display(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::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::SemanticIndex;
|
||||
use crate::stdlib::builtins_module_scope;
|
||||
use crate::types::class_base::ClassBase;
|
||||
use crate::types::diagnostic::{
|
||||
report_invalid_assignment, report_unresolved_module, TypeCheckDiagnostics, CALL_NON_CALLABLE,
|
||||
CALL_POSSIBLY_UNBOUND_METHOD, CONFLICTING_DECLARATIONS, CONFLICTING_METACLASS,
|
||||
|
@ -65,7 +64,7 @@ use crate::types::{
|
|||
typing_extensions_symbol, Boundness, CallDunderResult, Class, ClassLiteralType, FunctionType,
|
||||
InstanceType, IntersectionBuilder, IntersectionType, IterationOutcome, KnownClass,
|
||||
KnownFunction, KnownInstanceType, MetaclassCandidate, MetaclassErrorKind, SliceLiteralType,
|
||||
Symbol, Truthiness, TupleType, Type, TypeAliasType, TypeArrayDisplay,
|
||||
SubclassOfType, Symbol, Truthiness, TupleType, Type, TypeAliasType, TypeArrayDisplay,
|
||||
TypeVarBoundOrConstraints, TypeVarInstance, UnionBuilder, UnionType,
|
||||
};
|
||||
use crate::unpack::Unpack;
|
||||
|
@ -4903,9 +4902,11 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
ast::Expr::Name(_) | ast::Expr::Attribute(_) => {
|
||||
let name_ty = self.infer_expression(slice);
|
||||
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::subclass_of_base(ClassBase::Any)
|
||||
SubclassOfType::subclass_of_any()
|
||||
}
|
||||
_ => 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_table;
|
||||
use crate::types::{
|
||||
infer_expression_types, ClassBase, ClassLiteralType, IntersectionBuilder, KnownClass,
|
||||
KnownFunction, SubclassOfType, Truthiness, Type, UnionBuilder,
|
||||
infer_expression_types, ClassLiteralType, IntersectionBuilder, KnownClass, KnownFunction,
|
||||
SubclassOfType, Truthiness, Type, UnionBuilder,
|
||||
};
|
||||
use crate::Db;
|
||||
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
|
||||
/// 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>> {
|
||||
let constraint_fn = |class| match self {
|
||||
KnownConstraintFunction::IsInstance => Type::instance(class),
|
||||
KnownConstraintFunction::IsSubclass => SubclassOfType::from(db, class),
|
||||
};
|
||||
|
||||
match classinfo {
|
||||
Type::Tuple(tuple) => {
|
||||
let mut builder = UnionBuilder::new(db);
|
||||
|
@ -105,13 +110,10 @@ impl KnownConstraintFunction {
|
|||
}
|
||||
Some(builder.build())
|
||||
}
|
||||
Type::ClassLiteral(ClassLiteralType { class })
|
||||
| Type::SubclassOf(SubclassOfType {
|
||||
base: ClassBase::Class(class),
|
||||
}) => Some(match self {
|
||||
KnownConstraintFunction::IsInstance => Type::instance(class),
|
||||
KnownConstraintFunction::IsSubclass => Type::subclass_of(class),
|
||||
}),
|
||||
Type::ClassLiteral(ClassLiteralType { class }) => Some(constraint_fn(class)),
|
||||
Type::SubclassOf(subclass_of_ty) => {
|
||||
subclass_of_ty.subclass_of().into_class().map(constraint_fn)
|
||||
}
|
||||
_ => 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