diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 547ce40c703ac..3a6cf0ad734c7 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -685,8 +685,9 @@ impl<'db> Type<'db> { matches!(self, Type::LiteralString) } - pub const fn instance(class: Class<'db>) -> Self { - Self::Instance(InstanceType { class }) + pub fn instance(db: &'db dyn Db, class: Class<'db>) -> Self { + let args: Box<[Type<'db>]> = Box::new([]); + Self::Instance(InstanceType::new(db, class, args)) } pub const fn subclass_of(class: Class<'db>) -> Self { @@ -910,10 +911,8 @@ impl<'db> Type<'db> { Type::SubclassOf(SubclassOfType { base: ClassBase::Class(self_class), }), - Type::Instance(InstanceType { - class: target_class, - }), - ) => self_class.is_instance_of(db, target_class), + Type::Instance(instance), + ) => self_class.is_instance_of(db, instance.class(db)), // Other than the cases enumerated above, `type[]` just delegates to `Instance("type")` (Type::SubclassOf(_), _) => KnownClass::Type.to_instance(db).is_subtype_of(db, target), @@ -1020,18 +1019,12 @@ impl<'db> Type<'db> { // TODO: The following is a workaround that is required to unify the two different versions // of `NoneType` and `NoDefaultType` in typeshed. This should not be required anymore once // we understand `sys.version_info` branches. - if let ( - Type::Instance(InstanceType { class: self_class }), - Type::Instance(InstanceType { - class: target_class, - }), - ) = (self, other) - { - let self_known = self_class.known(db); + if let (Type::Instance(instance), Type::Instance(target_instance)) = (self, other) { + let self_known = instance.class(db).known(db); if matches!( self_known, Some(KnownClass::NoneType | KnownClass::NoDefaultType) - ) && self_known == target_class.known(db) + ) && self_known == target_instance.class(db).known(db) { return true; } @@ -1042,10 +1035,10 @@ impl<'db> Type<'db> { Type::SubclassOf(SubclassOfType { base: ClassBase::Class(object_class), }), - Type::Instance(InstanceType { class: type_class }), + Type::Instance(instance), ) | ( - Type::Instance(InstanceType { class: type_class }), + Type::Instance(instance), Type::SubclassOf(SubclassOfType { base: ClassBase::Class(object_class), }), @@ -1055,7 +1048,7 @@ impl<'db> Type<'db> { // class", so we don't need to fall through if we're not looking at instance[type] and // type[object] specifically. return object_class.is_known(db, KnownClass::Object) - && type_class.is_known(db, KnownClass::Type); + && instance.class(db).is_known(db, KnownClass::Type); } // TODO equivalent but not identical structural types, differently-ordered unions and @@ -1184,7 +1177,7 @@ impl<'db> Type<'db> { // multiply-inherit `type` or a subclass of it, and thus not be disjoint with // `type[...]`.) Until we support finality, hardcode None, which is known to be // final. - matches!(instance.class.known(db), Some(KnownClass::NoneType)) + matches!(instance.class(db).known(db), Some(KnownClass::NoneType)) } ( @@ -1235,81 +1228,81 @@ impl<'db> Type<'db> { left.is_disjoint_from(db, right.instance_fallback(db)) } - ( - Type::Instance(InstanceType { class: class_none }), - Type::Instance(InstanceType { class: class_other }), - ) - | ( - Type::Instance(InstanceType { class: class_other }), - Type::Instance(InstanceType { class: class_none }), - ) if class_none.is_known(db, KnownClass::NoneType) => !matches!( - class_other.known(db), - Some(KnownClass::NoneType | KnownClass::Object) - ), + (Type::Instance(instance_none), Type::Instance(instance_other)) + | (Type::Instance(instance_other), Type::Instance(instance_none)) + if instance_none.class(db).is_known(db, KnownClass::NoneType) => + { + !matches!( + instance_other.class(db).known(db), + Some(KnownClass::NoneType | KnownClass::Object) + ) + } - (Type::Instance(InstanceType { class: class_none }), _) - | (_, Type::Instance(InstanceType { class: class_none })) - if class_none.is_known(db, KnownClass::NoneType) => + (Type::Instance(instance_none), _) | (_, Type::Instance(instance_none)) + if instance_none.class(db).is_known(db, KnownClass::NoneType) => { true } - (Type::BooleanLiteral(..), Type::Instance(InstanceType { class })) - | (Type::Instance(InstanceType { class }), Type::BooleanLiteral(..)) => !matches!( - class.known(db), + (Type::BooleanLiteral(..), Type::Instance(instance)) + | (Type::Instance(instance), Type::BooleanLiteral(..)) => !matches!( + instance.class(db).known(db), Some(KnownClass::Bool | KnownClass::Int | KnownClass::Object) ), (Type::BooleanLiteral(..), _) | (_, Type::BooleanLiteral(..)) => true, - (Type::IntLiteral(..), Type::Instance(InstanceType { class })) - | (Type::Instance(InstanceType { class }), Type::IntLiteral(..)) => { - !matches!(class.known(db), Some(KnownClass::Int | KnownClass::Object)) - } + (Type::IntLiteral(..), Type::Instance(instance)) + | (Type::Instance(instance), Type::IntLiteral(..)) => !matches!( + instance.class(db).known(db), + Some(KnownClass::Int | KnownClass::Object) + ), (Type::IntLiteral(..), _) | (_, Type::IntLiteral(..)) => true, (Type::StringLiteral(..), Type::LiteralString) | (Type::LiteralString, Type::StringLiteral(..)) => false, - (Type::StringLiteral(..), Type::Instance(InstanceType { class })) - | (Type::Instance(InstanceType { class }), Type::StringLiteral(..)) => { - !matches!(class.known(db), Some(KnownClass::Str | KnownClass::Object)) - } + (Type::StringLiteral(..), Type::Instance(instance)) + | (Type::Instance(instance), Type::StringLiteral(..)) => !matches!( + instance.class(db).known(db), + Some(KnownClass::Str | KnownClass::Object) + ), (Type::LiteralString, Type::LiteralString) => false, - (Type::LiteralString, Type::Instance(InstanceType { class })) - | (Type::Instance(InstanceType { class }), Type::LiteralString) => { - !matches!(class.known(db), Some(KnownClass::Str | KnownClass::Object)) - } + (Type::LiteralString, Type::Instance(instance)) + | (Type::Instance(instance), Type::LiteralString) => !matches!( + instance.class(db).known(db), + Some(KnownClass::Str | KnownClass::Object) + ), (Type::LiteralString, _) | (_, Type::LiteralString) => true, - (Type::BytesLiteral(..), Type::Instance(InstanceType { class })) - | (Type::Instance(InstanceType { class }), Type::BytesLiteral(..)) => !matches!( - class.known(db), + (Type::BytesLiteral(..), Type::Instance(instance)) + | (Type::Instance(instance), Type::BytesLiteral(..)) => !matches!( + instance.class(db).known(db), Some(KnownClass::Bytes | KnownClass::Object) ), - (Type::SliceLiteral(..), Type::Instance(InstanceType { class })) - | (Type::Instance(InstanceType { class }), Type::SliceLiteral(..)) => !matches!( - class.known(db), + (Type::SliceLiteral(..), Type::Instance(instance)) + | (Type::Instance(instance), Type::SliceLiteral(..)) => !matches!( + instance.class(db).known(db), Some(KnownClass::Slice | KnownClass::Object) ), ( Type::ClassLiteral(ClassLiteralType { class: class_a }), - Type::Instance(InstanceType { class: class_b }), + Type::Instance(instance_b), ) | ( - Type::Instance(InstanceType { class: class_b }), + Type::Instance(instance_b), Type::ClassLiteral(ClassLiteralType { class: class_a }), - ) => !class_a.is_instance_of(db, class_b), + ) => !class_a.is_instance_of(db, instance_b.class(db)), - (Type::FunctionLiteral(..), Type::Instance(InstanceType { class })) - | (Type::Instance(InstanceType { class }), Type::FunctionLiteral(..)) => !matches!( - class.known(db), + (Type::FunctionLiteral(..), Type::Instance(instance)) + | (Type::Instance(instance), Type::FunctionLiteral(..)) => !matches!( + instance.class(db).known(db), Some(KnownClass::FunctionType | KnownClass::Object) ), - (Type::ModuleLiteral(..), Type::Instance(InstanceType { class })) - | (Type::Instance(InstanceType { class }), Type::ModuleLiteral(..)) => !matches!( - class.known(db), + (Type::ModuleLiteral(..), Type::Instance(instance)) + | (Type::Instance(instance), Type::ModuleLiteral(..)) => !matches!( + instance.class(db).known(db), Some(KnownClass::ModuleType | KnownClass::Object) ), @@ -1438,9 +1431,10 @@ impl<'db> Type<'db> { | Type::ClassLiteral(..) | Type::ModuleLiteral(..) | Type::KnownInstance(..) => true, - Type::Instance(InstanceType { class }) => { - class.known(db).is_some_and(KnownClass::is_singleton) - } + Type::Instance(instance) => instance + .class(db) + .known(db) + .is_some_and(KnownClass::is_singleton), Type::Tuple(..) => { // The empty tuple is a singleton on CPython and PyPy, but not on other Python // implementations such as GraalPy. Its *use* as a singleton is discouraged and @@ -1492,7 +1486,7 @@ impl<'db> Type<'db> { .iter() .all(|elem| elem.is_single_valued(db)), - Type::Instance(InstanceType { class }) => match class.known(db) { + Type::Instance(instance) => match instance.class(db).known(db) { Some( KnownClass::NoneType | KnownClass::NoDefaultType @@ -1572,8 +1566,8 @@ impl<'db> Type<'db> { Type::KnownInstance(known_instance) => known_instance.member(db, name), - Type::Instance(InstanceType { class }) => { - let ty = match (class.known(db), name) { + Type::Instance(instance) => { + let ty = match (instance.class(db).known(db), name) { (Some(KnownClass::VersionInfo), "major") => { Type::IntLiteral(Program::get(db).python_version(db).major.into()) } @@ -1691,8 +1685,8 @@ impl<'db> Type<'db> { .unwrap_or(Truthiness::Ambiguous), Type::AlwaysTruthy => Truthiness::AlwaysTrue, Type::AlwaysFalsy => Truthiness::AlwaysFalse, - instance_ty @ Type::Instance(InstanceType { class }) => { - if class.is_known(db, KnownClass::NoneType) { + instance_ty @ Type::Instance(instance) => { + if instance.class(db).is_known(db, KnownClass::NoneType) { Truthiness::AlwaysFalse } else { // We only check the `__bool__` method for truth testing, even though at @@ -1838,7 +1832,10 @@ impl<'db> Type<'db> { .first() .map(|arg| arg.bool(db).into_type(db)) .unwrap_or(Type::BooleanLiteral(false)), - _ => Type::Instance(InstanceType { class }), + _ => { + let args: Box<[Type<'db>]> = Box::new([]); + Type::Instance(InstanceType::new(db, class, args)) + } }) } @@ -1978,10 +1975,10 @@ impl<'db> Type<'db> { todo @ Type::Todo(_) => *todo, Type::Unknown => Type::Unknown, Type::Never => Type::Never, - Type::ClassLiteral(ClassLiteralType { class }) => Type::instance(*class), + Type::ClassLiteral(ClassLiteralType { class }) => Type::instance(db, *class), Type::SubclassOf(SubclassOfType { base: ClassBase::Class(class), - }) => Type::instance(*class), + }) => Type::instance(db, *class), Type::SubclassOf(_) => Type::Any, Type::Union(union) => union.map(db, |element| element.to_instance(db)), Type::Intersection(_) => todo_type!("Type::Intersection.to_instance()"), @@ -2135,7 +2132,7 @@ impl<'db> Type<'db> { pub fn to_meta_type(&self, db: &'db dyn Db) -> Type<'db> { match self { Type::Never => Type::Never, - Type::Instance(InstanceType { class }) => Type::subclass_of(*class), + Type::Instance(instance) => Type::subclass_of(instance.class(db)), Type::KnownInstance(known_instance) => known_instance.class().to_class_literal(db), Type::Union(union) => union.map(db, |ty| ty.to_meta_type(db)), Type::BooleanLiteral(_) => KnownClass::Bool.to_class_literal(db), @@ -3571,15 +3568,16 @@ impl<'db> SubclassOfType<'db> { } /// A type representing the set of runtime objects which are instances of a certain class. -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, salsa::Update)] +#[salsa::interned] pub struct InstanceType<'db> { class: Class<'db>, + args: Box<[Type<'db>]>, } impl<'db> InstanceType<'db> { 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) + self.class(db).is_subclass_of(db, other.class(db)) } } diff --git a/crates/red_knot_python_semantic/src/types/builder.rs b/crates/red_knot_python_semantic/src/types/builder.rs index 8d5a559f865e6..fa2620b58bca6 100644 --- a/crates/red_knot_python_semantic/src/types/builder.rs +++ b/crates/red_knot_python_semantic/src/types/builder.rs @@ -26,7 +26,7 @@ //! eliminate the supertype from the intersection). //! * An intersection containing two non-overlapping types should simplify to [`Type::Never`]. -use crate::types::{InstanceType, IntersectionType, KnownClass, Type, UnionType}; +use crate::types::{IntersectionType, KnownClass, Type, UnionType}; use crate::{Db, FxOrderSet}; use smallvec::SmallVec; @@ -246,8 +246,8 @@ impl<'db> InnerIntersectionBuilder<'db> { } else { // ~Literal[True] & bool = Literal[False] // ~AlwaysTruthy & bool = Literal[False] - if let Type::Instance(InstanceType { class }) = new_positive { - if class.is_known(db, KnownClass::Bool) { + if let Type::Instance(instance) = new_positive { + if instance.class(db).is_known(db, KnownClass::Bool) { if let Some(new_type) = self .negative .iter() diff --git a/crates/red_knot_python_semantic/src/types/display.rs b/crates/red_knot_python_semantic/src/types/display.rs index bec476d725b04..2f0a56e709263 100644 --- a/crates/red_knot_python_semantic/src/types/display.rs +++ b/crates/red_knot_python_semantic/src/types/display.rs @@ -8,8 +8,8 @@ use ruff_python_literal::escape::AsciiEscape; use crate::types::class_base::ClassBase; use crate::types::{ - ClassLiteralType, InstanceType, IntersectionType, KnownClass, StringLiteralType, - SubclassOfType, Type, UnionType, + ClassLiteralType, IntersectionType, KnownClass, StringLiteralType, SubclassOfType, Type, + UnionType, }; use crate::Db; use rustc_hash::FxHashMap; @@ -68,11 +68,11 @@ impl Display for DisplayRepresentation<'_> { Type::Any => f.write_str("Any"), Type::Never => f.write_str("Never"), Type::Unknown => f.write_str("Unknown"), - Type::Instance(InstanceType { class }) => { - let representation = match class.known(self.db) { + Type::Instance(instance) => { + let representation = match instance.class(self.db).known(self.db) { Some(KnownClass::NoneType) => "None", Some(KnownClass::NoDefaultType) => "NoDefault", - _ => class.name(self.db), + _ => instance.class(self.db).name(self.db), }; f.write_str(representation) } diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index 0f7a83d69eb61..df264c13680a6 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -788,9 +788,9 @@ impl<'db> TypeInferenceBuilder<'db> { fn check_division_by_zero(&mut self, expr: &ast::ExprBinOp, left: Type<'db>) { match left { Type::BooleanLiteral(_) | Type::IntLiteral(_) => {} - Type::Instance(InstanceType { class }) + Type::Instance(instance) if matches!( - class.known(self.db()), + instance.class(self.db()).known(self.db()), Some(KnownClass::Float | KnownClass::Int | KnownClass::Bool) ) => {} _ => return, @@ -1931,8 +1931,11 @@ impl<'db> TypeInferenceBuilder<'db> { ); // Handle various singletons. - if let Type::Instance(InstanceType { class }) = annotation_ty { - if class.is_known(self.db(), KnownClass::SpecialForm) { + if let Type::Instance(instance) = annotation_ty { + if instance + .class(self.db()) + .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(), @@ -1984,9 +1987,10 @@ impl<'db> TypeInferenceBuilder<'db> { self.infer_augmented_op(assignment, target_type, value_type) }) } - Type::Instance(InstanceType { class }) => { - if let Symbol::Type(class_member, boundness) = - class.class_member(self.db(), op.in_place_dunder()) + Type::Instance(instance) => { + if let Symbol::Type(class_member, boundness) = instance + .class(self.db()) + .class_member(self.db(), op.in_place_dunder()) { let call = class_member.call(self.db(), &[target_type, value_type]); let augmented_return_ty = match call @@ -3500,9 +3504,14 @@ impl<'db> TypeInferenceBuilder<'db> { (Type::Instance(left), Type::Instance(right), op) => { if left != right && right.is_subtype_of(self.db(), left) { let reflected_dunder = op.reflected_dunder(); - let rhs_reflected = right.class.class_member(self.db(), reflected_dunder); + let rhs_reflected = right + .class(self.db()) + .class_member(self.db(), reflected_dunder); if !rhs_reflected.is_unbound() - && rhs_reflected != left.class.class_member(self.db(), reflected_dunder) + && rhs_reflected + != left + .class(self.db()) + .class_member(self.db(), reflected_dunder) { return right_ty .call_dunder(self.db(), reflected_dunder, &[right_ty, left_ty]) @@ -3516,7 +3525,7 @@ impl<'db> TypeInferenceBuilder<'db> { } let call_on_left_instance = if let Symbol::Type(class_member, _) = - left.class.class_member(self.db(), op.dunder()) + left.class(self.db()).class_member(self.db(), op.dunder()) { class_member .call(self.db(), &[left_ty, right_ty]) @@ -3529,8 +3538,9 @@ impl<'db> TypeInferenceBuilder<'db> { if left == right { None } else { - if let Symbol::Type(class_member, _) = - right.class.class_member(self.db(), op.reflected_dunder()) + if let Symbol::Type(class_member, _) = right + .class(self.db()) + .class_member(self.db(), op.reflected_dunder()) { class_member .call(self.db(), &[right_ty, left_ty]) @@ -3969,13 +3979,17 @@ impl<'db> TypeInferenceBuilder<'db> { op, KnownClass::Bytes.to_instance(self.db()), ), - (Type::Tuple(_), Type::Instance(InstanceType { class })) - if class.is_known(self.db(), KnownClass::VersionInfo) => + (Type::Tuple(_), Type::Instance(instance)) + if instance + .class(self.db()) + .is_known(self.db(), KnownClass::VersionInfo) => { self.infer_binary_type_comparison(left, op, Type::version_info_tuple(self.db())) } - (Type::Instance(InstanceType { class }), Type::Tuple(_)) - if class.is_known(self.db(), KnownClass::VersionInfo) => + (Type::Instance(instance), Type::Tuple(_)) + if instance + .class(self.db()) + .is_known(self.db(), KnownClass::VersionInfo) => { self.infer_binary_type_comparison(Type::version_info_tuple(self.db()), op, right) } @@ -4187,14 +4201,18 @@ impl<'db> TypeInferenceBuilder<'db> { ) -> Type<'db> { match (value_ty, slice_ty) { ( - Type::Instance(InstanceType { class }), + Type::Instance(instance), Type::IntLiteral(_) | Type::BooleanLiteral(_) | Type::SliceLiteral(_), - ) if class.is_known(self.db(), KnownClass::VersionInfo) => self - .infer_subscript_expression_types( + ) if instance + .class(self.db()) + .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() => { @@ -4439,8 +4457,10 @@ impl<'db> TypeInferenceBuilder<'db> { Err(_) => SliceArg::Unsupported, }, Some(Type::BooleanLiteral(b)) => SliceArg::Arg(Some(i32::from(b))), - Some(Type::Instance(InstanceType { class })) - if class.is_known(self.db(), KnownClass::NoneType) => + Some(Type::Instance(instance)) + if instance + .class(self.db()) + .is_known(self.db(), KnownClass::NoneType) => { SliceArg::Arg(None) } @@ -5451,7 +5471,7 @@ fn perform_rich_comparison<'db>( let call_dunder = |op: RichCompareOperator, left: InstanceType<'db>, right: InstanceType<'db>| { - match left.class.class_member(db, op.dunder()) { + match left.class(db).class_member(db, op.dunder()) { Symbol::Type(class_member_dunder, Boundness::Bound) => class_member_dunder .call(db, &[Type::Instance(left), Type::Instance(right)]) .return_ty(db), @@ -5492,7 +5512,7 @@ fn perform_membership_test_comparison<'db>( right: InstanceType<'db>, op: MembershipTestCompareOperator, ) -> Result, CompareUnsupportedError<'db>> { - let contains_dunder = right.class.class_member(db, "__contains__"); + let contains_dunder = right.class(db).class_member(db, "__contains__"); let compare_result_opt = match contains_dunder { Symbol::Type(contains_dunder, Boundness::Bound) => { // If `__contains__` is available, it is used directly for the membership test. diff --git a/crates/red_knot_python_semantic/src/types/narrow.rs b/crates/red_knot_python_semantic/src/types/narrow.rs index ef1d6293037ba..0f9688040d4e9 100644 --- a/crates/red_knot_python_semantic/src/types/narrow.rs +++ b/crates/red_knot_python_semantic/src/types/narrow.rs @@ -93,7 +93,7 @@ fn generate_classinfo_constraint<'db, F>( to_constraint: F, ) -> Option> where - F: Fn(ClassLiteralType<'db>) -> Type<'db> + Copy, + F: Fn(&'db dyn Db, ClassLiteralType<'db>) -> Type<'db> + Copy, { match classinfo { Type::Tuple(tuple) => { @@ -103,7 +103,7 @@ where } Some(builder.build()) } - Type::ClassLiteral(class_literal_type) => Some(to_constraint(*class_literal_type)), + Type::ClassLiteral(class_literal_type) => Some(to_constraint(db, *class_literal_type)), _ => None, } } @@ -431,10 +431,12 @@ impl<'db> NarrowingConstraintsBuilder<'db> { let to_constraint = match function { KnownConstraintFunction::IsInstance => { - |class_literal: ClassLiteralType<'db>| Type::instance(class_literal.class) + |db: &'db dyn Db, class_literal: ClassLiteralType<'db>| { + Type::instance(db, class_literal.class) + } } KnownConstraintFunction::IsSubclass => { - |class_literal: ClassLiteralType<'db>| { + |_db: &'db dyn Db, class_literal: ClassLiteralType<'db>| { Type::subclass_of(class_literal.class) } }