[red-knot] Move InstanceType to its own submodule (#17525)

This commit is contained in:
Alex Waygood 2025-04-22 12:34:46 +01:00 committed by GitHub
parent d2b20f7367
commit ae6fde152c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 577 additions and 528 deletions

View file

@ -47,9 +47,10 @@ pub(crate) use crate::types::narrow::infer_narrowing_constraint;
use crate::types::signatures::{Parameter, ParameterForm, Parameters};
use crate::{Db, FxOrderSet, Module, Program};
pub(crate) use class::{
Class, ClassLiteralType, ClassType, GenericAlias, GenericClass, InstanceType, KnownClass,
KnownInstanceType, NonGenericClass,
Class, ClassLiteralType, ClassType, GenericAlias, GenericClass, KnownClass, NonGenericClass,
};
pub(crate) use instance::InstanceType;
pub(crate) use known_instance::KnownInstanceType;
mod builder;
mod call;
@ -60,6 +61,8 @@ mod diagnostic;
mod display;
mod generics;
mod infer;
mod instance;
mod known_instance;
mod mro;
mod narrow;
mod signatures;
@ -462,7 +465,8 @@ pub enum Type<'db> {
GenericAlias(GenericAlias<'db>),
/// The set of all class objects that are subclasses of the given class (C), spelled `type[C]`.
SubclassOf(SubclassOfType<'db>),
/// The set of Python objects with the given class in their __class__'s method resolution order
/// The set of Python objects with the given class in their __class__'s method resolution order.
/// Construct this variant using the `Type::instance` constructor function.
Instance(InstanceType<'db>),
/// A single Python object that requires special treatment in the type system
KnownInstance(KnownInstanceType<'db>),
@ -527,17 +531,20 @@ impl<'db> Type<'db> {
fn is_none(&self, db: &'db dyn Db) -> bool {
self.into_instance()
.is_some_and(|instance| instance.class.is_known(db, KnownClass::NoneType))
.is_some_and(|instance| instance.class().is_known(db, KnownClass::NoneType))
}
pub fn is_notimplemented(&self, db: &'db dyn Db) -> bool {
self.into_instance()
.is_some_and(|instance| instance.class.is_known(db, KnownClass::NotImplementedType))
self.into_instance().is_some_and(|instance| {
instance
.class()
.is_known(db, KnownClass::NotImplementedType)
})
}
pub fn is_object(&self, db: &'db dyn Db) -> bool {
self.into_instance()
.is_some_and(|instance| instance.class.is_object(db))
.is_some_and(|instance| instance.class().is_object(db))
}
pub const fn is_todo(&self) -> bool {
@ -689,10 +696,6 @@ impl<'db> Type<'db> {
)
}
pub const fn is_instance(&self) -> bool {
matches!(self, Type::Instance(..))
}
pub const fn is_property_instance(&self) -> bool {
matches!(self, Type::PropertyInstance(..))
}
@ -793,13 +796,6 @@ impl<'db> Type<'db> {
.expect("Expected a Type::IntLiteral variant")
}
pub const fn into_instance(self) -> Option<InstanceType<'db>> {
match self {
Type::Instance(instance_type) => Some(instance_type),
_ => None,
}
}
pub const fn into_known_instance(self) -> Option<KnownInstanceType<'db>> {
match self {
Type::KnownInstance(known_instance) => Some(known_instance),
@ -828,10 +824,6 @@ impl<'db> Type<'db> {
matches!(self, Type::LiteralString)
}
pub const fn instance(class: ClassType<'db>) -> Self {
Self::Instance(InstanceType { class })
}
pub fn string_literal(db: &'db dyn Db, string: &str) -> Self {
Self::StringLiteral(StringLiteralType::new(db, string))
}
@ -963,7 +955,7 @@ impl<'db> Type<'db> {
(_, Type::Never) => false,
// Everything is a subtype of `object`.
(_, Type::Instance(InstanceType { class })) if class.is_object(db) => true,
(_, Type::Instance(instance)) if instance.class().is_object(db) => true,
// A fully static typevar is always a subtype of itself, and is never a subtype of any
// other typevar, since there is no guarantee that they will be specialized to the same
@ -1285,7 +1277,7 @@ impl<'db> Type<'db> {
// All types are assignable to `object`.
// TODO this special case might be removable once the below cases are comprehensive
(_, Type::Instance(InstanceType { class })) if class.is_object(db) => true,
(_, Type::Instance(instance)) if instance.class().is_object(db) => true,
// A typevar is always assignable to itself, and is never assignable to any other
// typevar, since there is no guarantee that they will be specialized to the same
@ -1440,13 +1432,13 @@ impl<'db> Type<'db> {
// TODO: This is a workaround to avoid false positives (e.g. when checking function calls
// with `SupportsIndex` parameters), which should be removed when we understand protocols.
(lhs, Type::Instance(InstanceType { class }))
if class.is_known(db, KnownClass::SupportsIndex) =>
(lhs, Type::Instance(instance))
if instance.class().is_known(db, KnownClass::SupportsIndex) =>
{
match lhs {
Type::Instance(InstanceType { class })
Type::Instance(instance)
if matches!(
class.known(db),
instance.class().known(db),
Some(KnownClass::Int | KnownClass::SupportsIndex)
) =>
{
@ -1458,9 +1450,7 @@ impl<'db> Type<'db> {
}
// TODO: ditto for avoiding false positives when checking function calls with `Sized` parameters.
(lhs, Type::Instance(InstanceType { class }))
if class.is_known(db, KnownClass::Sized) =>
{
(lhs, Type::Instance(instance)) if instance.class().is_known(db, KnownClass::Sized) => {
matches!(
lhs.to_meta_type(db).member(db, "__len__"),
SymbolAndQualifiers {
@ -1771,9 +1761,9 @@ impl<'db> Type<'db> {
.is_disjoint_from(db, other),
},
(Type::KnownInstance(known_instance), Type::Instance(InstanceType { class }))
| (Type::Instance(InstanceType { class }), Type::KnownInstance(known_instance)) => {
!known_instance.is_instance_of(db, class)
(Type::KnownInstance(known_instance), Type::Instance(instance))
| (Type::Instance(instance), Type::KnownInstance(known_instance)) => {
!known_instance.is_instance_of(db, instance.class())
}
(known_instance_ty @ Type::KnownInstance(_), Type::Tuple(_))
@ -1781,20 +1771,20 @@ impl<'db> Type<'db> {
known_instance_ty.is_disjoint_from(db, KnownClass::Tuple.to_instance(db))
}
(Type::BooleanLiteral(..), Type::Instance(InstanceType { class }))
| (Type::Instance(InstanceType { class }), Type::BooleanLiteral(..)) => {
(Type::BooleanLiteral(..), Type::Instance(instance))
| (Type::Instance(instance), Type::BooleanLiteral(..)) => {
// A `Type::BooleanLiteral()` must be an instance of exactly `bool`
// (it cannot be an instance of a `bool` subclass)
!KnownClass::Bool.is_subclass_of(db, class)
!KnownClass::Bool.is_subclass_of(db, instance.class())
}
(Type::BooleanLiteral(..), _) | (_, Type::BooleanLiteral(..)) => true,
(Type::IntLiteral(..), Type::Instance(InstanceType { class }))
| (Type::Instance(InstanceType { class }), Type::IntLiteral(..)) => {
(Type::IntLiteral(..), Type::Instance(instance))
| (Type::Instance(instance), Type::IntLiteral(..)) => {
// A `Type::IntLiteral()` must be an instance of exactly `int`
// (it cannot be an instance of an `int` subclass)
!KnownClass::Int.is_subclass_of(db, class)
!KnownClass::Int.is_subclass_of(db, instance.class())
}
(Type::IntLiteral(..), _) | (_, Type::IntLiteral(..)) => true,
@ -1802,34 +1792,28 @@ impl<'db> Type<'db> {
(Type::StringLiteral(..), Type::LiteralString)
| (Type::LiteralString, Type::StringLiteral(..)) => false,
(
Type::StringLiteral(..) | Type::LiteralString,
Type::Instance(InstanceType { class }),
)
| (
Type::Instance(InstanceType { class }),
Type::StringLiteral(..) | Type::LiteralString,
) => {
(Type::StringLiteral(..) | Type::LiteralString, Type::Instance(instance))
| (Type::Instance(instance), Type::StringLiteral(..) | Type::LiteralString) => {
// A `Type::StringLiteral()` or a `Type::LiteralString` must be an instance of exactly `str`
// (it cannot be an instance of a `str` subclass)
!KnownClass::Str.is_subclass_of(db, class)
!KnownClass::Str.is_subclass_of(db, instance.class())
}
(Type::LiteralString, Type::LiteralString) => false,
(Type::LiteralString, _) | (_, Type::LiteralString) => true,
(Type::BytesLiteral(..), Type::Instance(InstanceType { class }))
| (Type::Instance(InstanceType { class }), Type::BytesLiteral(..)) => {
(Type::BytesLiteral(..), Type::Instance(instance))
| (Type::Instance(instance), Type::BytesLiteral(..)) => {
// A `Type::BytesLiteral()` must be an instance of exactly `bytes`
// (it cannot be an instance of a `bytes` subclass)
!KnownClass::Bytes.is_subclass_of(db, class)
!KnownClass::Bytes.is_subclass_of(db, instance.class())
}
(Type::SliceLiteral(..), Type::Instance(InstanceType { class }))
| (Type::Instance(InstanceType { class }), Type::SliceLiteral(..)) => {
(Type::SliceLiteral(..), Type::Instance(instance))
| (Type::Instance(instance), Type::SliceLiteral(..)) => {
// A `Type::SliceLiteral` must be an instance of exactly `slice`
// (it cannot be an instance of a `slice` subclass)
!KnownClass::Slice.is_subclass_of(db, class)
!KnownClass::Slice.is_subclass_of(db, instance.class())
}
// A class-literal type `X` is always disjoint from an instance type `Y`,
@ -1844,11 +1828,11 @@ impl<'db> Type<'db> {
.metaclass_instance_type(db)
.is_subtype_of(db, instance),
(Type::FunctionLiteral(..), Type::Instance(InstanceType { class }))
| (Type::Instance(InstanceType { class }), Type::FunctionLiteral(..)) => {
(Type::FunctionLiteral(..), Type::Instance(instance))
| (Type::Instance(instance), Type::FunctionLiteral(..)) => {
// A `Type::FunctionLiteral()` must be an instance of exactly `types.FunctionType`
// (it cannot be an instance of a `types.FunctionType` subclass)
!KnownClass::FunctionType.is_subclass_of(db, class)
!KnownClass::FunctionType.is_subclass_of(db, instance.class())
}
(Type::BoundMethod(_), other) | (other, Type::BoundMethod(_)) => KnownClass::MethodType
@ -1907,13 +1891,7 @@ impl<'db> Type<'db> {
other.is_disjoint_from(db, KnownClass::ModuleType.to_instance(db))
}
(
Type::Instance(InstanceType { class: left_class }),
Type::Instance(InstanceType { class: right_class }),
) => {
(left_class.is_final(db) && !left_class.is_subclass_of(db, right_class))
|| (right_class.is_final(db) && !right_class.is_subclass_of(db, left_class))
}
(Type::Instance(left), Type::Instance(right)) => left.is_disjoint_from(db, right),
(Type::Tuple(tuple), Type::Tuple(other_tuple)) => {
let self_elements = tuple.elements(db);
@ -2092,9 +2070,7 @@ impl<'db> Type<'db> {
false
}
Type::DataclassDecorator(_) | Type::DataclassTransformer(_) => false,
Type::Instance(InstanceType { class }) => {
class.known(db).is_some_and(KnownClass::is_singleton)
}
Type::Instance(instance) => instance.is_singleton(db),
Type::PropertyInstance(_) => false,
Type::Tuple(..) => {
// The empty tuple is a singleton on CPython and PyPy, but not on other Python
@ -2166,9 +2142,7 @@ impl<'db> Type<'db> {
.iter()
.all(|elem| elem.is_single_valued(db)),
Type::Instance(InstanceType { class }) => {
class.known(db).is_some_and(KnownClass::is_single_valued)
}
Type::Instance(instance) => instance.is_single_valued(db),
Type::BoundSuper(_) => {
// At runtime two super instances never compare equal, even if their arguments are identical.
@ -2309,7 +2283,7 @@ impl<'db> Type<'db> {
// We eagerly normalize type[object], i.e. Type::SubclassOf(object) to `type`, i.e. Type::Instance(type).
// So looking up a name in the MRO of `Type::Instance(type)` is equivalent to looking up the name in the
// MRO of the class `object`.
Type::Instance(InstanceType { class }) if class.is_known(db, KnownClass::Type) => {
Type::Instance(instance) if instance.class().is_known(db, KnownClass::Type) => {
KnownClass::Object
.to_class_literal(db)
.find_name_in_mro_with_policy(db, name, policy)
@ -2399,7 +2373,7 @@ impl<'db> Type<'db> {
Type::Dynamic(_) | Type::Never => Symbol::bound(self).into(),
Type::Instance(InstanceType { class }) => class.instance_member(db, name),
Type::Instance(instance) => instance.class().instance_member(db, name),
Type::FunctionLiteral(_) => KnownClass::FunctionType
.to_instance(db)
@ -2840,9 +2814,9 @@ impl<'db> Type<'db> {
.to_instance(db)
.member_lookup_with_policy(db, name, policy),
Type::Instance(InstanceType { class })
Type::Instance(instance)
if matches!(name.as_str(), "major" | "minor")
&& class.is_known(db, KnownClass::VersionInfo) =>
&& instance.class().is_known(db, KnownClass::VersionInfo) =>
{
let python_version = Program::get(db).python_version(db);
let segment = if name == "major" {
@ -2912,10 +2886,11 @@ impl<'db> Type<'db> {
// attributes on the original type. But in typeshed its return type is `Any`.
// It will need a special handling, so it remember the origin type to properly
// resolve the attribute.
if self.into_instance().is_some_and(|instance| {
instance.class.is_known(db, KnownClass::ModuleType)
|| instance.class.is_known(db, KnownClass::GenericAlias)
}) {
if matches!(
self.into_instance()
.and_then(|instance| instance.class().known(db)),
Some(KnownClass::ModuleType | KnownClass::GenericAlias)
) {
return Symbol::Unbound.into();
}
@ -3173,7 +3148,7 @@ impl<'db> Type<'db> {
}
},
Type::Instance(InstanceType { class }) => match class.known(db) {
Type::Instance(instance) => match instance.class().known(db) {
Some(known_class) => known_class.bool(),
None => try_dunder_bool()?,
},
@ -4561,7 +4536,7 @@ impl<'db> Type<'db> {
Type::Dynamic(_) => Ok(*self),
Type::Instance(InstanceType { class }) => match class.known(db) {
Type::Instance(instance) => match instance.class().known(db) {
Some(KnownClass::TypeVar) => Ok(todo_type!(
"Support for `typing.TypeVar` instances in type expressions"
)),
@ -4637,8 +4612,8 @@ 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 }) => SubclassOfType::from(db, *class),
Type::KnownInstance(known_instance) => known_instance.class().to_class_literal(db),
Type::Instance(instance) => instance.to_meta_type(db),
Type::KnownInstance(known_instance) => known_instance.to_meta_type(db),
Type::PropertyInstance(_) => KnownClass::Property.to_class_literal(db),
Type::Union(union) => union.map(db, |ty| ty.to_meta_type(db)),
Type::BooleanLiteral(_) => KnownClass::Bool.to_class_literal(db),
@ -4872,7 +4847,9 @@ impl<'db> Type<'db> {
Some(TypeDefinition::Class(class_literal.definition(db)))
}
Self::GenericAlias(alias) => Some(TypeDefinition::Class(alias.definition(db))),
Self::Instance(instance) => Some(TypeDefinition::Class(instance.class.definition(db))),
Self::Instance(instance) => {
Some(TypeDefinition::Class(instance.class().definition(db)))
}
Self::KnownInstance(instance) => match instance {
KnownInstanceType::TypeVar(var) => {
Some(TypeDefinition::TypeVar(var.definition(db)))
@ -7198,7 +7175,7 @@ impl<'db> SuperOwnerKind<'db> {
match self {
SuperOwnerKind::Dynamic(dynamic) => Either::Left(ClassBase::Dynamic(dynamic).mro(db)),
SuperOwnerKind::Class(class) => Either::Right(class.iter_mro(db)),
SuperOwnerKind::Instance(instance) => Either::Right(instance.class.iter_mro(db)),
SuperOwnerKind::Instance(instance) => Either::Right(instance.class().iter_mro(db)),
}
}
@ -7214,7 +7191,7 @@ impl<'db> SuperOwnerKind<'db> {
match self {
SuperOwnerKind::Dynamic(_) => None,
SuperOwnerKind::Class(class) => Some(class),
SuperOwnerKind::Instance(instance) => Some(instance.class),
SuperOwnerKind::Instance(instance) => Some(instance.class()),
}
}
@ -7385,35 +7362,38 @@ impl<'db> BoundSuperType<'db> {
policy: MemberLookupPolicy,
) -> SymbolAndQualifiers<'db> {
let owner = self.owner(db);
match owner {
SuperOwnerKind::Dynamic(_) => owner
.into_type()
.find_name_in_mro_with_policy(db, name, policy)
.expect("Calling `find_name_in_mro` on dynamic type should return `Some`"),
SuperOwnerKind::Class(class) | SuperOwnerKind::Instance(InstanceType { class }) => {
let (class_literal, _) = class.class_literal(db);
// TODO properly support super() with generic types
// * requires a fix for https://github.com/astral-sh/ruff/issues/17432
// * also requires understanding how we should handle cases like this:
// ```python
// b_int: B[int]
// b_unknown: B
//
// super(B, b_int)
// super(B[int], b_unknown)
// ```
match class_literal {
ClassLiteralType::Generic(_) => {
Symbol::bound(todo_type!("super in generic class")).into()
}
ClassLiteralType::NonGeneric(_) => class_literal.class_member_from_mro(
db,
name,
policy,
self.skip_until_after_pivot(db, owner.iter_mro(db)),
),
}
let class = match owner {
SuperOwnerKind::Dynamic(_) => {
return owner
.into_type()
.find_name_in_mro_with_policy(db, name, policy)
.expect("Calling `find_name_in_mro` on dynamic type should return `Some`")
}
SuperOwnerKind::Class(class) => *class,
SuperOwnerKind::Instance(instance) => instance.class(),
};
let (class_literal, _) = class.class_literal(db);
// TODO properly support super() with generic types
// * requires a fix for https://github.com/astral-sh/ruff/issues/17432
// * also requires understanding how we should handle cases like this:
// ```python
// b_int: B[int]
// b_unknown: B
//
// super(B, b_int)
// super(B[int], b_unknown)
// ```
match class_literal {
ClassLiteralType::Generic(_) => {
Symbol::bound(todo_type!("super in generic class")).into()
}
ClassLiteralType::NonGeneric(_) => class_literal.class_member_from_mro(
db,
name,
policy,
self.skip_until_after_pivot(db, owner.iter_mro(db)),
),
}
}
}

View file

@ -493,7 +493,7 @@ impl<'db> InnerIntersectionBuilder<'db> {
_ => {
let known_instance = new_positive
.into_instance()
.and_then(|instance| instance.class.known(db));
.and_then(|instance| instance.class().known(db));
if known_instance == Some(KnownClass::Object) {
// `object & T` -> `T`; it is always redundant to add `object` to an intersection
@ -513,7 +513,7 @@ impl<'db> InnerIntersectionBuilder<'db> {
new_positive = Type::BooleanLiteral(false);
}
Type::Instance(instance)
if instance.class.is_known(db, KnownClass::Bool) =>
if instance.class().is_known(db, KnownClass::Bool) =>
{
match new_positive {
// `bool & AlwaysTruthy` -> `Literal[True]`
@ -607,7 +607,7 @@ impl<'db> InnerIntersectionBuilder<'db> {
self.positive
.iter()
.filter_map(|ty| ty.into_instance())
.filter_map(|instance| instance.class.known(db))
.filter_map(|instance| instance.class().known(db))
.any(KnownClass::is_bool)
};
@ -623,7 +623,7 @@ impl<'db> InnerIntersectionBuilder<'db> {
Type::Never => {
// Adding ~Never to an intersection is a no-op.
}
Type::Instance(instance) if instance.class.is_object(db) => {
Type::Instance(instance) if instance.class().is_object(db) => {
// Adding ~object to an intersection results in Never.
*self = Self::default();
self.positive.insert(Type::Never);

View file

@ -4,13 +4,15 @@ use std::sync::{LazyLock, Mutex};
use super::{
class_base::ClassBase, infer_expression_type, infer_unpack_types, IntersectionBuilder,
KnownFunction, MemberLookupPolicy, Mro, MroError, MroIterator, SubclassOfType, Truthiness,
Type, TypeAliasType, TypeQualifiers, TypeVarInstance,
Type, TypeQualifiers,
};
use crate::semantic_index::definition::Definition;
use crate::semantic_index::DeclarationWithConstraint;
use crate::types::generics::{GenericContext, Specialization};
use crate::types::signatures::{Parameter, Parameters};
use crate::types::{CallableType, DataclassParams, DataclassTransformerParams, Signature};
use crate::types::{
CallableType, DataclassParams, DataclassTransformerParams, KnownInstanceType, Signature,
};
use crate::{
module_resolver::file_to_module,
semantic_index::{
@ -1690,40 +1692,6 @@ impl InheritanceCycle {
}
}
/// 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> {
pub class: ClassType<'db>,
}
impl<'db> InstanceType<'db> {
pub(super) fn is_subtype_of(self, db: &'db dyn Db, other: InstanceType<'db>) -> bool {
// N.B. The subclass relation is fully static
self.class.is_subclass_of(db, other.class)
}
pub(super) fn is_equivalent_to(self, db: &'db dyn Db, other: InstanceType<'db>) -> bool {
self.class.is_equivalent_to(db, other.class)
}
pub(super) fn is_assignable_to(self, db: &'db dyn Db, other: InstanceType<'db>) -> bool {
self.class.is_assignable_to(db, other.class)
}
pub(super) fn is_gradual_equivalent_to(
self,
db: &'db dyn Db,
other: InstanceType<'db>,
) -> bool {
self.class.is_gradual_equivalent_to(db, other.class)
}
}
impl<'db> From<InstanceType<'db>> for Type<'db> {
fn from(value: InstanceType<'db>) -> Self {
Self::Instance(value)
}
}
/// Non-exhaustive enumeration of known classes (e.g. `builtins.int`, `typing.Any`, ...) to allow
/// for easier syntax when interacting with very common classes.
///
@ -2447,357 +2415,6 @@ impl<'db> KnownClassLookupError<'db> {
}
}
/// Enumeration of specific runtime that are special enough to be considered their own type.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Update)]
pub enum KnownInstanceType<'db> {
/// The symbol `typing.Annotated` (which can also be found as `typing_extensions.Annotated`)
Annotated,
/// The symbol `typing.Literal` (which can also be found as `typing_extensions.Literal`)
Literal,
/// The symbol `typing.LiteralString` (which can also be found as `typing_extensions.LiteralString`)
LiteralString,
/// The symbol `typing.Optional` (which can also be found as `typing_extensions.Optional`)
Optional,
/// The symbol `typing.Union` (which can also be found as `typing_extensions.Union`)
Union,
/// The symbol `typing.NoReturn` (which can also be found as `typing_extensions.NoReturn`)
NoReturn,
/// The symbol `typing.Never` available since 3.11 (which can also be found as `typing_extensions.Never`)
Never,
/// The symbol `typing.Any` (which can also be found as `typing_extensions.Any`)
/// This is not used since typeshed switched to representing `Any` as a class; now we use
/// `KnownClass::Any` instead. But we still support the old `Any = object()` representation, at
/// least for now. TODO maybe remove?
Any,
/// The symbol `typing.Tuple` (which can also be found as `typing_extensions.Tuple`)
Tuple,
/// The symbol `typing.List` (which can also be found as `typing_extensions.List`)
List,
/// The symbol `typing.Dict` (which can also be found as `typing_extensions.Dict`)
Dict,
/// The symbol `typing.Set` (which can also be found as `typing_extensions.Set`)
Set,
/// The symbol `typing.FrozenSet` (which can also be found as `typing_extensions.FrozenSet`)
FrozenSet,
/// The symbol `typing.ChainMap` (which can also be found as `typing_extensions.ChainMap`)
ChainMap,
/// The symbol `typing.Counter` (which can also be found as `typing_extensions.Counter`)
Counter,
/// The symbol `typing.DefaultDict` (which can also be found as `typing_extensions.DefaultDict`)
DefaultDict,
/// The symbol `typing.Deque` (which can also be found as `typing_extensions.Deque`)
Deque,
/// The symbol `typing.OrderedDict` (which can also be found as `typing_extensions.OrderedDict`)
OrderedDict,
/// The symbol `typing.Protocol` (which can also be found as `typing_extensions.Protocol`)
Protocol,
/// The symbol `typing.Generic` (which can also be found as `typing_extensions.Generic`)
Generic,
/// The symbol `typing.Type` (which can also be found as `typing_extensions.Type`)
Type,
/// A single instance of `typing.TypeVar`
TypeVar(TypeVarInstance<'db>),
/// A single instance of `typing.TypeAliasType` (PEP 695 type alias)
TypeAliasType(TypeAliasType<'db>),
/// The symbol `knot_extensions.Unknown`
Unknown,
/// The symbol `knot_extensions.AlwaysTruthy`
AlwaysTruthy,
/// The symbol `knot_extensions.AlwaysFalsy`
AlwaysFalsy,
/// The symbol `knot_extensions.Not`
Not,
/// The symbol `knot_extensions.Intersection`
Intersection,
/// The symbol `knot_extensions.TypeOf`
TypeOf,
/// The symbol `knot_extensions.CallableTypeOf`
CallableTypeOf,
// Various special forms, special aliases and type qualifiers that we don't yet understand
// (all currently inferred as TODO in most contexts):
TypingSelf,
Final,
ClassVar,
Callable,
Concatenate,
Unpack,
Required,
NotRequired,
TypeAlias,
TypeGuard,
TypeIs,
ReadOnly,
// TODO: fill this enum out with more special forms, etc.
}
impl<'db> KnownInstanceType<'db> {
/// Evaluate the known instance in boolean context
pub(crate) const fn bool(self) -> Truthiness {
match self {
Self::Annotated
| Self::Literal
| Self::LiteralString
| Self::Optional
| Self::TypeVar(_)
| Self::Union
| Self::NoReturn
| Self::Never
| Self::Any
| Self::Tuple
| Self::Type
| Self::TypingSelf
| Self::Final
| Self::ClassVar
| Self::Callable
| Self::Concatenate
| Self::Unpack
| Self::Required
| Self::NotRequired
| Self::TypeAlias
| Self::TypeGuard
| Self::TypeIs
| Self::List
| Self::Dict
| Self::DefaultDict
| Self::Set
| Self::FrozenSet
| Self::Counter
| Self::Deque
| Self::ChainMap
| Self::OrderedDict
| Self::Protocol
| Self::Generic
| Self::ReadOnly
| Self::TypeAliasType(_)
| Self::Unknown
| Self::AlwaysTruthy
| Self::AlwaysFalsy
| Self::Not
| Self::Intersection
| Self::TypeOf
| Self::CallableTypeOf => Truthiness::AlwaysTrue,
}
}
/// Return the repr of the symbol at runtime
pub(crate) fn repr(self, db: &'db dyn Db) -> &'db str {
match self {
Self::Annotated => "typing.Annotated",
Self::Literal => "typing.Literal",
Self::LiteralString => "typing.LiteralString",
Self::Optional => "typing.Optional",
Self::Union => "typing.Union",
Self::NoReturn => "typing.NoReturn",
Self::Never => "typing.Never",
Self::Any => "typing.Any",
Self::Tuple => "typing.Tuple",
Self::Type => "typing.Type",
Self::TypingSelf => "typing.Self",
Self::Final => "typing.Final",
Self::ClassVar => "typing.ClassVar",
Self::Callable => "typing.Callable",
Self::Concatenate => "typing.Concatenate",
Self::Unpack => "typing.Unpack",
Self::Required => "typing.Required",
Self::NotRequired => "typing.NotRequired",
Self::TypeAlias => "typing.TypeAlias",
Self::TypeGuard => "typing.TypeGuard",
Self::TypeIs => "typing.TypeIs",
Self::List => "typing.List",
Self::Dict => "typing.Dict",
Self::DefaultDict => "typing.DefaultDict",
Self::Set => "typing.Set",
Self::FrozenSet => "typing.FrozenSet",
Self::Counter => "typing.Counter",
Self::Deque => "typing.Deque",
Self::ChainMap => "typing.ChainMap",
Self::OrderedDict => "typing.OrderedDict",
Self::Protocol => "typing.Protocol",
Self::Generic => "typing.Generic",
Self::ReadOnly => "typing.ReadOnly",
Self::TypeVar(typevar) => typevar.name(db),
Self::TypeAliasType(_) => "typing.TypeAliasType",
Self::Unknown => "knot_extensions.Unknown",
Self::AlwaysTruthy => "knot_extensions.AlwaysTruthy",
Self::AlwaysFalsy => "knot_extensions.AlwaysFalsy",
Self::Not => "knot_extensions.Not",
Self::Intersection => "knot_extensions.Intersection",
Self::TypeOf => "knot_extensions.TypeOf",
Self::CallableTypeOf => "knot_extensions.CallableTypeOf",
}
}
/// Return the [`KnownClass`] which this symbol is an instance of
pub(crate) const fn class(self) -> KnownClass {
match self {
Self::Annotated => KnownClass::SpecialForm,
Self::Literal => KnownClass::SpecialForm,
Self::LiteralString => KnownClass::SpecialForm,
Self::Optional => KnownClass::SpecialForm,
Self::Union => KnownClass::SpecialForm,
Self::NoReturn => KnownClass::SpecialForm,
Self::Never => KnownClass::SpecialForm,
Self::Any => KnownClass::Object,
Self::Tuple => KnownClass::SpecialForm,
Self::Type => KnownClass::SpecialForm,
Self::TypingSelf => KnownClass::SpecialForm,
Self::Final => KnownClass::SpecialForm,
Self::ClassVar => KnownClass::SpecialForm,
Self::Callable => KnownClass::SpecialForm,
Self::Concatenate => KnownClass::SpecialForm,
Self::Unpack => KnownClass::SpecialForm,
Self::Required => KnownClass::SpecialForm,
Self::NotRequired => KnownClass::SpecialForm,
Self::TypeAlias => KnownClass::SpecialForm,
Self::TypeGuard => KnownClass::SpecialForm,
Self::TypeIs => KnownClass::SpecialForm,
Self::ReadOnly => KnownClass::SpecialForm,
Self::List => KnownClass::StdlibAlias,
Self::Dict => KnownClass::StdlibAlias,
Self::DefaultDict => KnownClass::StdlibAlias,
Self::Set => KnownClass::StdlibAlias,
Self::FrozenSet => KnownClass::StdlibAlias,
Self::Counter => KnownClass::StdlibAlias,
Self::Deque => KnownClass::StdlibAlias,
Self::ChainMap => KnownClass::StdlibAlias,
Self::OrderedDict => KnownClass::StdlibAlias,
Self::Protocol => KnownClass::SpecialForm, // actually `_ProtocolMeta` at runtime but this is what typeshed says
Self::Generic => KnownClass::SpecialForm, // actually `type` at runtime but this is what typeshed says
Self::TypeVar(_) => KnownClass::TypeVar,
Self::TypeAliasType(_) => KnownClass::TypeAliasType,
Self::TypeOf => KnownClass::SpecialForm,
Self::Not => KnownClass::SpecialForm,
Self::Intersection => KnownClass::SpecialForm,
Self::CallableTypeOf => KnownClass::SpecialForm,
Self::Unknown => KnownClass::Object,
Self::AlwaysTruthy => KnownClass::Object,
Self::AlwaysFalsy => KnownClass::Object,
}
}
/// Return the instance type which this type is a subtype of.
///
/// For example, the symbol `typing.Literal` is an instance of `typing._SpecialForm`,
/// so `KnownInstanceType::Literal.instance_fallback(db)`
/// returns `Type::Instance(InstanceType { class: <typing._SpecialForm> })`.
pub(super) fn instance_fallback(self, db: &dyn Db) -> Type {
self.class().to_instance(db)
}
/// Return `true` if this symbol is an instance of `class`.
pub(super) fn is_instance_of(self, db: &'db dyn Db, class: ClassType<'db>) -> bool {
self.class().is_subclass_of(db, class)
}
pub(super) fn try_from_file_and_name(
db: &'db dyn Db,
file: File,
symbol_name: &str,
) -> Option<Self> {
let candidate = match symbol_name {
"Any" => Self::Any,
"ClassVar" => Self::ClassVar,
"Deque" => Self::Deque,
"List" => Self::List,
"Dict" => Self::Dict,
"DefaultDict" => Self::DefaultDict,
"Set" => Self::Set,
"FrozenSet" => Self::FrozenSet,
"Counter" => Self::Counter,
"ChainMap" => Self::ChainMap,
"OrderedDict" => Self::OrderedDict,
"Generic" => Self::Generic,
"Protocol" => Self::Protocol,
"Optional" => Self::Optional,
"Union" => Self::Union,
"NoReturn" => Self::NoReturn,
"Tuple" => Self::Tuple,
"Type" => Self::Type,
"Callable" => Self::Callable,
"Annotated" => Self::Annotated,
"Literal" => Self::Literal,
"Never" => Self::Never,
"Self" => Self::TypingSelf,
"Final" => Self::Final,
"Unpack" => Self::Unpack,
"Required" => Self::Required,
"TypeAlias" => Self::TypeAlias,
"TypeGuard" => Self::TypeGuard,
"TypeIs" => Self::TypeIs,
"ReadOnly" => Self::ReadOnly,
"Concatenate" => Self::Concatenate,
"NotRequired" => Self::NotRequired,
"LiteralString" => Self::LiteralString,
"Unknown" => Self::Unknown,
"AlwaysTruthy" => Self::AlwaysTruthy,
"AlwaysFalsy" => Self::AlwaysFalsy,
"Not" => Self::Not,
"Intersection" => Self::Intersection,
"TypeOf" => Self::TypeOf,
"CallableTypeOf" => Self::CallableTypeOf,
_ => return None,
};
candidate
.check_module(file_to_module(db, file)?.known()?)
.then_some(candidate)
}
/// Return `true` if `module` is a module from which this `KnownInstance` variant can validly originate.
///
/// Most variants can only exist in one module, which is the same as `self.class().canonical_module()`.
/// Some variants could validly be defined in either `typing` or `typing_extensions`, however.
pub(super) fn check_module(self, module: KnownModule) -> bool {
match self {
Self::Any
| Self::ClassVar
| Self::Deque
| Self::List
| Self::Dict
| Self::DefaultDict
| Self::Set
| Self::FrozenSet
| Self::Counter
| Self::ChainMap
| Self::OrderedDict
| Self::Optional
| Self::Union
| Self::NoReturn
| Self::Tuple
| Self::Type
| Self::Generic
| Self::Callable => module.is_typing(),
Self::Annotated
| Self::Protocol
| Self::Literal
| Self::LiteralString
| Self::Never
| Self::TypingSelf
| Self::Final
| Self::Concatenate
| Self::Unpack
| Self::Required
| Self::NotRequired
| Self::TypeAlias
| Self::TypeGuard
| Self::TypeIs
| Self::ReadOnly
| Self::TypeAliasType(_)
| Self::TypeVar(_) => {
matches!(module, KnownModule::Typing | KnownModule::TypingExtensions)
}
Self::Unknown
| Self::AlwaysTruthy
| Self::AlwaysFalsy
| Self::Not
| Self::Intersection
| Self::TypeOf
| Self::CallableTypeOf => module.is_knot_extensions(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, salsa::Update)]
pub(super) struct MetaclassError<'db> {
kind: MetaclassErrorKind<'db>,

View file

@ -10,9 +10,9 @@ use crate::types::class::{ClassType, GenericAlias, GenericClass};
use crate::types::generics::{GenericContext, Specialization};
use crate::types::signatures::{Parameter, Parameters, Signature};
use crate::types::{
FunctionSignature, InstanceType, IntersectionType, KnownClass, MethodWrapperKind,
StringLiteralType, SubclassOfInner, Type, TypeVarBoundOrConstraints, TypeVarInstance,
UnionType, WrapperDescriptorKind,
FunctionSignature, IntersectionType, KnownClass, MethodWrapperKind, StringLiteralType,
SubclassOfInner, Type, TypeVarBoundOrConstraints, TypeVarInstance, UnionType,
WrapperDescriptorKind,
};
use crate::Db;
use rustc_hash::FxHashMap;
@ -73,7 +73,7 @@ impl Display for DisplayRepresentation<'_> {
match self.ty {
Type::Dynamic(dynamic) => dynamic.fmt(f),
Type::Never => f.write_str("Never"),
Type::Instance(InstanceType { class }) => match (class, class.known(self.db)) {
Type::Instance(instance) => match (instance.class(), instance.class().known(self.db)) {
(_, Some(KnownClass::NoneType)) => f.write_str("None"),
(_, Some(KnownClass::NoDefaultType)) => f.write_str("NoDefault"),
(ClassType::NonGeneric(class), _) => f.write_str(&class.class(self.db).name),

View file

@ -1041,7 +1041,7 @@ impl<'db> TypeInferenceBuilder<'db> {
Type::BooleanLiteral(_) | Type::IntLiteral(_) => {}
Type::Instance(instance)
if matches!(
instance.class.known(self.db()),
instance.class().known(self.db()),
Some(KnownClass::Float | KnownClass::Int | KnownClass::Bool)
) => {}
_ => return false,
@ -2475,7 +2475,7 @@ impl<'db> TypeInferenceBuilder<'db> {
}
// Super instances do not allow attribute assignment
Type::Instance(instance) if instance.class.is_known(db, KnownClass::Super) => {
Type::Instance(instance) if instance.class().is_known(db, KnownClass::Super) => {
if emit_diagnostics {
self.context.report_lint_old(
&INVALID_ASSIGNMENT,
@ -2991,7 +2991,10 @@ impl<'db> TypeInferenceBuilder<'db> {
// Handle various singletons.
if let Type::Instance(instance) = declared_ty.inner_type() {
if instance.class.is_known(self.db(), KnownClass::SpecialForm) {
if instance
.class()
.is_known(self.db(), KnownClass::SpecialForm)
{
if let Some(name_expr) = target.as_name_expr() {
if let Some(known_instance) = KnownInstanceType::try_from_file_and_name(
self.db(),
@ -5780,7 +5783,9 @@ impl<'db> TypeInferenceBuilder<'db> {
range,
),
(Type::Tuple(_), Type::Instance(instance))
if instance.class.is_known(self.db(), KnownClass::VersionInfo) =>
if instance
.class()
.is_known(self.db(), KnownClass::VersionInfo) =>
{
self.infer_binary_type_comparison(
left,
@ -5790,7 +5795,9 @@ impl<'db> TypeInferenceBuilder<'db> {
)
}
(Type::Instance(instance), Type::Tuple(_))
if instance.class.is_known(self.db(), KnownClass::VersionInfo) =>
if instance
.class()
.is_known(self.db(), KnownClass::VersionInfo) =>
{
self.infer_binary_type_comparison(
Type::version_info_tuple(self.db()),
@ -6168,12 +6175,16 @@ impl<'db> TypeInferenceBuilder<'db> {
(
Type::Instance(instance),
Type::IntLiteral(_) | Type::BooleanLiteral(_) | Type::SliceLiteral(_),
) if instance.class.is_known(self.db(), KnownClass::VersionInfo) => self
.infer_subscript_expression_types(
) if instance
.class()
.is_known(self.db(), KnownClass::VersionInfo) =>
{
self.infer_subscript_expression_types(
value_node,
Type::version_info_tuple(self.db()),
slice_ty,
),
)
}
// Ex) Given `("a", "b", "c", "d")[1]`, return `"b"`
(Type::Tuple(tuple_ty), Type::IntLiteral(int)) if i32::try_from(int).is_ok() => {
@ -6459,7 +6470,7 @@ impl<'db> TypeInferenceBuilder<'db> {
},
Some(Type::BooleanLiteral(b)) => SliceArg::Arg(Some(i32::from(b))),
Some(Type::Instance(instance))
if instance.class.is_known(self.db(), KnownClass::NoneType) =>
if instance.class().is_known(self.db(), KnownClass::NoneType) =>
{
SliceArg::Arg(None)
}

View file

@ -0,0 +1,73 @@
//! Instance types: both nominal and structural.
use super::{ClassType, KnownClass, SubclassOfType, Type};
use crate::Db;
impl<'db> Type<'db> {
pub(crate) const fn instance(class: ClassType<'db>) -> Self {
Self::Instance(InstanceType { class })
}
pub(crate) const fn into_instance(self) -> Option<InstanceType<'db>> {
match self {
Type::Instance(instance_type) => Some(instance_type),
_ => None,
}
}
}
/// A type representing the set of runtime objects which are instances of a certain nominal class.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, salsa::Update)]
pub struct InstanceType<'db> {
// Keep this field private, so that the only way of constructing `InstanceType` instances
// is through the `Type::instance` constructor function.
class: ClassType<'db>,
}
impl<'db> InstanceType<'db> {
pub(super) fn class(self) -> ClassType<'db> {
self.class
}
pub(super) fn is_subtype_of(self, db: &'db dyn Db, other: Self) -> bool {
// N.B. The subclass relation is fully static
self.class.is_subclass_of(db, other.class)
}
pub(super) fn is_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool {
self.class.is_equivalent_to(db, other.class)
}
pub(super) fn is_assignable_to(self, db: &'db dyn Db, other: Self) -> bool {
self.class.is_assignable_to(db, other.class)
}
pub(super) fn is_disjoint_from(self, db: &'db dyn Db, other: Self) -> bool {
(self.class.is_final(db) && !self.class.is_subclass_of(db, other.class))
|| (other.class.is_final(db) && !other.class.is_subclass_of(db, self.class))
}
pub(super) fn is_gradual_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool {
self.class.is_gradual_equivalent_to(db, other.class)
}
pub(super) fn is_singleton(self, db: &'db dyn Db) -> bool {
self.class.known(db).is_some_and(KnownClass::is_singleton)
}
pub(super) fn is_single_valued(self, db: &'db dyn Db) -> bool {
self.class
.known(db)
.is_some_and(KnownClass::is_single_valued)
}
pub(super) fn to_meta_type(self, db: &'db dyn Db) -> Type<'db> {
SubclassOfType::from(db, self.class)
}
}
impl<'db> From<InstanceType<'db>> for Type<'db> {
fn from(value: InstanceType<'db>) -> Self {
Self::Instance(value)
}
}

View file

@ -0,0 +1,372 @@
//! The `KnownInstance` type.
//!
//! Despite its name, this is quite a different type from [`super::InstanceType`].
//! For the vast majority of instance-types in Python, we cannot say how many possible
//! inhabitants there are or could be of that type at runtime. Each variant of the
//! [`KnownInstanceType`] enum, however, represents a specific runtime symbol
//! that requires heavy special-casing in the type system. Thus any one `KnownInstance`
//! variant can only be inhabited by one or two specific objects at runtime with
//! locations that are known in advance.
use super::{class::KnownClass, ClassType, Truthiness, Type, TypeAliasType, TypeVarInstance};
use crate::db::Db;
use crate::module_resolver::{file_to_module, KnownModule};
use ruff_db::files::File;
/// Enumeration of specific runtime symbols that are special enough
/// that they can each be considered to inhabit a unique type.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Update)]
pub enum KnownInstanceType<'db> {
/// The symbol `typing.Annotated` (which can also be found as `typing_extensions.Annotated`)
Annotated,
/// The symbol `typing.Literal` (which can also be found as `typing_extensions.Literal`)
Literal,
/// The symbol `typing.LiteralString` (which can also be found as `typing_extensions.LiteralString`)
LiteralString,
/// The symbol `typing.Optional` (which can also be found as `typing_extensions.Optional`)
Optional,
/// The symbol `typing.Union` (which can also be found as `typing_extensions.Union`)
Union,
/// The symbol `typing.NoReturn` (which can also be found as `typing_extensions.NoReturn`)
NoReturn,
/// The symbol `typing.Never` available since 3.11 (which can also be found as `typing_extensions.Never`)
Never,
/// The symbol `typing.Any` (which can also be found as `typing_extensions.Any`)
/// This is not used since typeshed switched to representing `Any` as a class; now we use
/// `KnownClass::Any` instead. But we still support the old `Any = object()` representation, at
/// least for now. TODO maybe remove?
Any,
/// The symbol `typing.Tuple` (which can also be found as `typing_extensions.Tuple`)
Tuple,
/// The symbol `typing.List` (which can also be found as `typing_extensions.List`)
List,
/// The symbol `typing.Dict` (which can also be found as `typing_extensions.Dict`)
Dict,
/// The symbol `typing.Set` (which can also be found as `typing_extensions.Set`)
Set,
/// The symbol `typing.FrozenSet` (which can also be found as `typing_extensions.FrozenSet`)
FrozenSet,
/// The symbol `typing.ChainMap` (which can also be found as `typing_extensions.ChainMap`)
ChainMap,
/// The symbol `typing.Counter` (which can also be found as `typing_extensions.Counter`)
Counter,
/// The symbol `typing.DefaultDict` (which can also be found as `typing_extensions.DefaultDict`)
DefaultDict,
/// The symbol `typing.Deque` (which can also be found as `typing_extensions.Deque`)
Deque,
/// The symbol `typing.OrderedDict` (which can also be found as `typing_extensions.OrderedDict`)
OrderedDict,
/// The symbol `typing.Protocol` (which can also be found as `typing_extensions.Protocol`)
Protocol,
/// The symbol `typing.Generic` (which can also be found as `typing_extensions.Generic`)
Generic,
/// The symbol `typing.Type` (which can also be found as `typing_extensions.Type`)
Type,
/// A single instance of `typing.TypeVar`
TypeVar(TypeVarInstance<'db>),
/// A single instance of `typing.TypeAliasType` (PEP 695 type alias)
TypeAliasType(TypeAliasType<'db>),
/// The symbol `knot_extensions.Unknown`
Unknown,
/// The symbol `knot_extensions.AlwaysTruthy`
AlwaysTruthy,
/// The symbol `knot_extensions.AlwaysFalsy`
AlwaysFalsy,
/// The symbol `knot_extensions.Not`
Not,
/// The symbol `knot_extensions.Intersection`
Intersection,
/// The symbol `knot_extensions.TypeOf`
TypeOf,
/// The symbol `knot_extensions.CallableTypeOf`
CallableTypeOf,
/// The symbol `typing.Callable`
/// (which can also be found as `typing_extensions.Callable` or as `collections.abc.Callable`)
Callable,
// Various special forms, special aliases and type qualifiers that we don't yet understand
// (all currently inferred as TODO in most contexts):
TypingSelf,
Final,
ClassVar,
Concatenate,
Unpack,
Required,
NotRequired,
TypeAlias,
TypeGuard,
TypeIs,
ReadOnly,
// TODO: fill this enum out with more special forms, etc.
}
impl<'db> KnownInstanceType<'db> {
/// Evaluate the known instance in boolean context
pub(crate) const fn bool(self) -> Truthiness {
match self {
Self::Annotated
| Self::Literal
| Self::LiteralString
| Self::Optional
| Self::TypeVar(_)
| Self::Union
| Self::NoReturn
| Self::Never
| Self::Any
| Self::Tuple
| Self::Type
| Self::TypingSelf
| Self::Final
| Self::ClassVar
| Self::Callable
| Self::Concatenate
| Self::Unpack
| Self::Required
| Self::NotRequired
| Self::TypeAlias
| Self::TypeGuard
| Self::TypeIs
| Self::List
| Self::Dict
| Self::DefaultDict
| Self::Set
| Self::FrozenSet
| Self::Counter
| Self::Deque
| Self::ChainMap
| Self::OrderedDict
| Self::Protocol
| Self::Generic
| Self::ReadOnly
| Self::TypeAliasType(_)
| Self::Unknown
| Self::AlwaysTruthy
| Self::AlwaysFalsy
| Self::Not
| Self::Intersection
| Self::TypeOf
| Self::CallableTypeOf => Truthiness::AlwaysTrue,
}
}
/// Return the repr of the symbol at runtime
pub(crate) fn repr(self, db: &'db dyn Db) -> &'db str {
match self {
Self::Annotated => "typing.Annotated",
Self::Literal => "typing.Literal",
Self::LiteralString => "typing.LiteralString",
Self::Optional => "typing.Optional",
Self::Union => "typing.Union",
Self::NoReturn => "typing.NoReturn",
Self::Never => "typing.Never",
Self::Any => "typing.Any",
Self::Tuple => "typing.Tuple",
Self::Type => "typing.Type",
Self::TypingSelf => "typing.Self",
Self::Final => "typing.Final",
Self::ClassVar => "typing.ClassVar",
Self::Callable => "typing.Callable",
Self::Concatenate => "typing.Concatenate",
Self::Unpack => "typing.Unpack",
Self::Required => "typing.Required",
Self::NotRequired => "typing.NotRequired",
Self::TypeAlias => "typing.TypeAlias",
Self::TypeGuard => "typing.TypeGuard",
Self::TypeIs => "typing.TypeIs",
Self::List => "typing.List",
Self::Dict => "typing.Dict",
Self::DefaultDict => "typing.DefaultDict",
Self::Set => "typing.Set",
Self::FrozenSet => "typing.FrozenSet",
Self::Counter => "typing.Counter",
Self::Deque => "typing.Deque",
Self::ChainMap => "typing.ChainMap",
Self::OrderedDict => "typing.OrderedDict",
Self::Protocol => "typing.Protocol",
Self::Generic => "typing.Generic",
Self::ReadOnly => "typing.ReadOnly",
Self::TypeVar(typevar) => typevar.name(db),
Self::TypeAliasType(_) => "typing.TypeAliasType",
Self::Unknown => "knot_extensions.Unknown",
Self::AlwaysTruthy => "knot_extensions.AlwaysTruthy",
Self::AlwaysFalsy => "knot_extensions.AlwaysFalsy",
Self::Not => "knot_extensions.Not",
Self::Intersection => "knot_extensions.Intersection",
Self::TypeOf => "knot_extensions.TypeOf",
Self::CallableTypeOf => "knot_extensions.CallableTypeOf",
}
}
/// Return the [`KnownClass`] which this symbol is an instance of
pub(crate) const fn class(self) -> KnownClass {
match self {
Self::Annotated => KnownClass::SpecialForm,
Self::Literal => KnownClass::SpecialForm,
Self::LiteralString => KnownClass::SpecialForm,
Self::Optional => KnownClass::SpecialForm,
Self::Union => KnownClass::SpecialForm,
Self::NoReturn => KnownClass::SpecialForm,
Self::Never => KnownClass::SpecialForm,
Self::Any => KnownClass::Object,
Self::Tuple => KnownClass::SpecialForm,
Self::Type => KnownClass::SpecialForm,
Self::TypingSelf => KnownClass::SpecialForm,
Self::Final => KnownClass::SpecialForm,
Self::ClassVar => KnownClass::SpecialForm,
Self::Callable => KnownClass::SpecialForm,
Self::Concatenate => KnownClass::SpecialForm,
Self::Unpack => KnownClass::SpecialForm,
Self::Required => KnownClass::SpecialForm,
Self::NotRequired => KnownClass::SpecialForm,
Self::TypeAlias => KnownClass::SpecialForm,
Self::TypeGuard => KnownClass::SpecialForm,
Self::TypeIs => KnownClass::SpecialForm,
Self::ReadOnly => KnownClass::SpecialForm,
Self::List => KnownClass::StdlibAlias,
Self::Dict => KnownClass::StdlibAlias,
Self::DefaultDict => KnownClass::StdlibAlias,
Self::Set => KnownClass::StdlibAlias,
Self::FrozenSet => KnownClass::StdlibAlias,
Self::Counter => KnownClass::StdlibAlias,
Self::Deque => KnownClass::StdlibAlias,
Self::ChainMap => KnownClass::StdlibAlias,
Self::OrderedDict => KnownClass::StdlibAlias,
Self::Protocol => KnownClass::SpecialForm, // actually `_ProtocolMeta` at runtime but this is what typeshed says
Self::Generic => KnownClass::SpecialForm, // actually `type` at runtime but this is what typeshed says
Self::TypeVar(_) => KnownClass::TypeVar,
Self::TypeAliasType(_) => KnownClass::TypeAliasType,
Self::TypeOf => KnownClass::SpecialForm,
Self::Not => KnownClass::SpecialForm,
Self::Intersection => KnownClass::SpecialForm,
Self::CallableTypeOf => KnownClass::SpecialForm,
Self::Unknown => KnownClass::Object,
Self::AlwaysTruthy => KnownClass::Object,
Self::AlwaysFalsy => KnownClass::Object,
}
}
/// Return the instance type which this type is a subtype of.
///
/// For example, the symbol `typing.Literal` is an instance of `typing._SpecialForm`,
/// so `KnownInstanceType::Literal.instance_fallback(db)`
/// returns `Type::Instance(InstanceType { class: <typing._SpecialForm> })`.
pub(super) fn instance_fallback(self, db: &dyn Db) -> Type {
self.class().to_instance(db)
}
/// Return `true` if this symbol is an instance of `class`.
pub(super) fn is_instance_of(self, db: &'db dyn Db, class: ClassType<'db>) -> bool {
self.class().is_subclass_of(db, class)
}
pub(super) fn try_from_file_and_name(
db: &'db dyn Db,
file: File,
symbol_name: &str,
) -> Option<Self> {
let candidate = match symbol_name {
"Any" => Self::Any,
"ClassVar" => Self::ClassVar,
"Deque" => Self::Deque,
"List" => Self::List,
"Dict" => Self::Dict,
"DefaultDict" => Self::DefaultDict,
"Set" => Self::Set,
"FrozenSet" => Self::FrozenSet,
"Counter" => Self::Counter,
"ChainMap" => Self::ChainMap,
"OrderedDict" => Self::OrderedDict,
"Generic" => Self::Generic,
"Protocol" => Self::Protocol,
"Optional" => Self::Optional,
"Union" => Self::Union,
"NoReturn" => Self::NoReturn,
"Tuple" => Self::Tuple,
"Type" => Self::Type,
"Callable" => Self::Callable,
"Annotated" => Self::Annotated,
"Literal" => Self::Literal,
"Never" => Self::Never,
"Self" => Self::TypingSelf,
"Final" => Self::Final,
"Unpack" => Self::Unpack,
"Required" => Self::Required,
"TypeAlias" => Self::TypeAlias,
"TypeGuard" => Self::TypeGuard,
"TypeIs" => Self::TypeIs,
"ReadOnly" => Self::ReadOnly,
"Concatenate" => Self::Concatenate,
"NotRequired" => Self::NotRequired,
"LiteralString" => Self::LiteralString,
"Unknown" => Self::Unknown,
"AlwaysTruthy" => Self::AlwaysTruthy,
"AlwaysFalsy" => Self::AlwaysFalsy,
"Not" => Self::Not,
"Intersection" => Self::Intersection,
"TypeOf" => Self::TypeOf,
"CallableTypeOf" => Self::CallableTypeOf,
_ => return None,
};
candidate
.check_module(file_to_module(db, file)?.known()?)
.then_some(candidate)
}
/// Return `true` if `module` is a module from which this `KnownInstance` variant can validly originate.
///
/// Most variants can only exist in one module, which is the same as `self.class().canonical_module()`.
/// Some variants could validly be defined in either `typing` or `typing_extensions`, however.
pub(super) fn check_module(self, module: KnownModule) -> bool {
match self {
Self::Any
| Self::ClassVar
| Self::Deque
| Self::List
| Self::Dict
| Self::DefaultDict
| Self::Set
| Self::FrozenSet
| Self::Counter
| Self::ChainMap
| Self::OrderedDict
| Self::Optional
| Self::Union
| Self::NoReturn
| Self::Tuple
| Self::Type
| Self::Generic
| Self::Callable => module.is_typing(),
Self::Annotated
| Self::Protocol
| Self::Literal
| Self::LiteralString
| Self::Never
| Self::TypingSelf
| Self::Final
| Self::Concatenate
| Self::Unpack
| Self::Required
| Self::NotRequired
| Self::TypeAlias
| Self::TypeGuard
| Self::TypeIs
| Self::ReadOnly
| Self::TypeAliasType(_)
| Self::TypeVar(_) => {
matches!(module, KnownModule::Typing | KnownModule::TypingExtensions)
}
Self::Unknown
| Self::AlwaysTruthy
| Self::AlwaysFalsy
| Self::Not
| Self::Intersection
| Self::TypeOf
| Self::CallableTypeOf => module.is_knot_extensions(),
}
}
pub(super) fn to_meta_type(self, db: &'db dyn Db) -> Type<'db> {
self.class().to_class_literal(db)
}
}

View file

@ -3,8 +3,8 @@ use std::cmp::Ordering;
use crate::db::Db;
use super::{
class_base::ClassBase, subclass_of::SubclassOfInner, DynamicType, InstanceType,
KnownInstanceType, SuperOwnerKind, TodoType, Type,
class_base::ClassBase, subclass_of::SubclassOfInner, DynamicType, KnownInstanceType,
SuperOwnerKind, TodoType, Type,
};
/// Return an [`Ordering`] that describes the canonical order in which two types should appear
@ -126,10 +126,7 @@ pub(super) fn union_or_intersection_elements_ordering<'db>(
(Type::SubclassOf(_), _) => Ordering::Less,
(_, Type::SubclassOf(_)) => Ordering::Greater,
(
Type::Instance(InstanceType { class: left }),
Type::Instance(InstanceType { class: right }),
) => left.cmp(right),
(Type::Instance(left), Type::Instance(right)) => left.class().cmp(&right.class()),
(Type::Instance(_), _) => Ordering::Less,
(_, Type::Instance(_)) => Ordering::Greater,
@ -161,10 +158,9 @@ pub(super) fn union_or_intersection_elements_ordering<'db>(
(SuperOwnerKind::Class(left), SuperOwnerKind::Class(right)) => left.cmp(right),
(SuperOwnerKind::Class(_), _) => Ordering::Less,
(_, SuperOwnerKind::Class(_)) => Ordering::Greater,
(
SuperOwnerKind::Instance(InstanceType { class: left }),
SuperOwnerKind::Instance(InstanceType { class: right }),
) => left.cmp(right),
(SuperOwnerKind::Instance(left), SuperOwnerKind::Instance(right)) => {
left.class().cmp(&right.class())
}
(SuperOwnerKind::Instance(_), _) => Ordering::Less,
(_, SuperOwnerKind::Instance(_)) => Ordering::Greater,
(SuperOwnerKind::Dynamic(left), SuperOwnerKind::Dynamic(right)) => {