[red-knot] Eagerly normalize type[] types (#15272)

Co-authored-by: Carl Meyer <carl@astral.sh>
This commit is contained in:
Alex Waygood 2025-01-07 12:53:07 +00:00 committed by GitHub
parent 0dc00e63f4
commit 95294e657c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 245 additions and 174 deletions

View file

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

View file

@ -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]
```

View file

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

View file

@ -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();

View file

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

View file

@ -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),

View file

@ -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"),
} }

View file

@ -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,
} }
} }

View 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)
}
}
}
}