From ae6fde152cbb90e804234623c3842d5f78d9c4da Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Tue, 22 Apr 2025 12:34:46 +0100 Subject: [PATCH] [red-knot] Move `InstanceType` to its own submodule (#17525) --- crates/red_knot_python_semantic/src/types.rs | 208 +++++----- .../src/types/builder.rs | 8 +- .../src/types/class.rs | 391 +----------------- .../src/types/display.rs | 8 +- .../src/types/infer.rs | 29 +- .../src/types/instance.rs | 73 ++++ .../src/types/known_instance.rs | 372 +++++++++++++++++ .../src/types/type_ordering.rs | 16 +- 8 files changed, 577 insertions(+), 528 deletions(-) create mode 100644 crates/red_knot_python_semantic/src/types/instance.rs create mode 100644 crates/red_knot_python_semantic/src/types/known_instance.rs diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index ca20e3c55e..06b189a2d4 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -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> { - match self { - Type::Instance(instance_type) => Some(instance_type), - _ => None, - } - } - pub const fn into_known_instance(self) -> Option> { 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)), + ), } } } diff --git a/crates/red_knot_python_semantic/src/types/builder.rs b/crates/red_knot_python_semantic/src/types/builder.rs index 21171f11fb..41d194652e 100644 --- a/crates/red_knot_python_semantic/src/types/builder.rs +++ b/crates/red_knot_python_semantic/src/types/builder.rs @@ -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); diff --git a/crates/red_knot_python_semantic/src/types/class.rs b/crates/red_knot_python_semantic/src/types/class.rs index e8b92667ac..e90ef080d4 100644 --- a/crates/red_knot_python_semantic/src/types/class.rs +++ b/crates/red_knot_python_semantic/src/types/class.rs @@ -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> 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: })`. - 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 { - 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>, diff --git a/crates/red_knot_python_semantic/src/types/display.rs b/crates/red_knot_python_semantic/src/types/display.rs index 20ca22bd98..5687b6c35c 100644 --- a/crates/red_knot_python_semantic/src/types/display.rs +++ b/crates/red_knot_python_semantic/src/types/display.rs @@ -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), diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index 13e20cffd4..d2649620dc 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -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) } diff --git a/crates/red_knot_python_semantic/src/types/instance.rs b/crates/red_knot_python_semantic/src/types/instance.rs new file mode 100644 index 0000000000..1b0ec2f4fd --- /dev/null +++ b/crates/red_knot_python_semantic/src/types/instance.rs @@ -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> { + 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> for Type<'db> { + fn from(value: InstanceType<'db>) -> Self { + Self::Instance(value) + } +} diff --git a/crates/red_knot_python_semantic/src/types/known_instance.rs b/crates/red_knot_python_semantic/src/types/known_instance.rs new file mode 100644 index 0000000000..27ed7a533a --- /dev/null +++ b/crates/red_knot_python_semantic/src/types/known_instance.rs @@ -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: })`. + 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 { + 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) + } +} diff --git a/crates/red_knot_python_semantic/src/types/type_ordering.rs b/crates/red_knot_python_semantic/src/types/type_ordering.rs index 111b758bc7..8037611f51 100644 --- a/crates/red_knot_python_semantic/src/types/type_ordering.rs +++ b/crates/red_knot_python_semantic/src/types/type_ordering.rs @@ -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)) => {