From 3458f365da56f32d23e1ba5df7fb77d082976071 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Tue, 12 Aug 2025 14:35:26 +0100 Subject: [PATCH] [ty] Remove Salsa interning for `TypedDictType` (#19879) --- crates/ty_python_semantic/src/types.rs | 39 ++++++++----------- crates/ty_python_semantic/src/types/class.rs | 6 +++ .../ty_python_semantic/src/types/display.rs | 4 +- .../ty_python_semantic/src/types/instance.rs | 6 +-- .../src/types/type_ordering.rs | 4 +- 5 files changed, 28 insertions(+), 31 deletions(-) diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index babb3e01c1..3e4e2e3a23 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -1015,6 +1015,12 @@ impl<'db> Type<'db> { Self::BytesLiteral(BytesLiteralType::new(db, bytes)) } + pub(crate) fn typed_dict(defining_class: impl Into>) -> Self { + Self::TypedDict(TypedDictType { + defining_class: defining_class.into(), + }) + } + #[must_use] pub fn negate(&self, db: &'db dyn Db) -> Type<'db> { IntersectionBuilder::new(db).add_negative(*self).build() @@ -5230,16 +5236,12 @@ impl<'db> Type<'db> { KnownClass::Float.to_instance(db), ], ), - _ if class.is_typed_dict(db) => { - TypedDictType::from(db, ClassType::NonGeneric(*class)) - } + _ if class.is_typed_dict(db) => Type::typed_dict(*class), _ => Type::instance(db, class.default_specialization(db)), }; Ok(ty) } - Type::GenericAlias(alias) if alias.is_typed_dict(db) => { - Ok(TypedDictType::from(db, ClassType::from(*alias))) - } + Type::GenericAlias(alias) if alias.is_typed_dict(db) => Ok(Type::typed_dict(*alias)), Type::GenericAlias(alias) => Ok(Type::instance(db, ClassType::from(*alias))), Type::SubclassOf(_) @@ -5562,7 +5564,7 @@ impl<'db> Type<'db> { Type::AlwaysTruthy | Type::AlwaysFalsy => KnownClass::Type.to_instance(db), Type::BoundSuper(_) => KnownClass::Super.to_class_literal(db), Type::ProtocolInstance(protocol) => protocol.to_meta_type(db), - Type::TypedDict(typed_dict) => SubclassOfType::from(db, typed_dict.defining_class(db)), + Type::TypedDict(typed_dict) => SubclassOfType::from(db, typed_dict.defining_class), } } @@ -5974,7 +5976,7 @@ impl<'db> Type<'db> { }, Type::TypedDict(typed_dict) => { - Some(TypeDefinition::Class(typed_dict.defining_class(db).definition(db))) + Some(TypeDefinition::Class(typed_dict.defining_class.definition(db))) } Self::Union(_) | Self::Intersection(_) => None, @@ -9022,24 +9024,16 @@ impl<'db> EnumLiteralType<'db> { /// Type that represents the set of all inhabitants (`dict` instances) that conform to /// a given `TypedDict` schema. -#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)] -#[derive(PartialOrd, Ord)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, salsa::Update, Hash, get_size2::GetSize)] pub struct TypedDictType<'db> { /// A reference to the class (inheriting from `typing.TypedDict`) that specifies the /// schema of this `TypedDict`. defining_class: ClassType<'db>, } -// The Salsa heap is tracked separately. -impl get_size2::GetSize for TypedDictType<'_> {} - impl<'db> TypedDictType<'db> { - pub(crate) fn from(db: &'db dyn Db, defining_class: ClassType<'db>) -> Type<'db> { - Type::TypedDict(Self::new(db, defining_class)) - } - pub(crate) fn items(self, db: &'db dyn Db) -> FxOrderMap> { - let (class_literal, specialization) = self.defining_class(db).class_literal(db); + let (class_literal, specialization) = self.defining_class.class_literal(db); class_literal.fields(db, specialization, CodeGeneratorKind::TypedDict) } @@ -9048,10 +9042,9 @@ impl<'db> TypedDictType<'db> { db: &'db dyn Db, type_mapping: &TypeMapping<'a, 'db>, ) -> Self { - Self::new( - db, - self.defining_class(db).apply_type_mapping(db, type_mapping), - ) + Self { + defining_class: self.defining_class.apply_type_mapping(db, type_mapping), + } } } @@ -9060,7 +9053,7 @@ fn walk_typed_dict_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>( typed_dict: TypedDictType<'db>, visitor: &V, ) { - visitor.visit_type(db, typed_dict.defining_class(db).into()); + visitor.visit_type(db, typed_dict.defining_class.into()); } #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index c69719c99f..017c9bdcd7 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -2922,6 +2922,12 @@ impl<'db> From> for Type<'db> { } } +impl<'db> From> for ClassType<'db> { + fn from(class: ClassLiteral<'db>) -> ClassType<'db> { + ClassType::NonGeneric(class) + } +} + #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, get_size2::GetSize)] pub(super) enum InheritanceCycle { /// The class is cyclically defined and is a participant in the cycle. diff --git a/crates/ty_python_semantic/src/types/display.rs b/crates/ty_python_semantic/src/types/display.rs index 544e2c066d..76cea209f5 100644 --- a/crates/ty_python_semantic/src/types/display.rs +++ b/crates/ty_python_semantic/src/types/display.rs @@ -242,9 +242,7 @@ impl Display for DisplayRepresentation<'_> { } f.write_str("]") } - Type::TypedDict(typed_dict) => { - f.write_str(typed_dict.defining_class(self.db).name(self.db)) - } + Type::TypedDict(typed_dict) => f.write_str(typed_dict.defining_class.name(self.db)), } } } diff --git a/crates/ty_python_semantic/src/types/instance.rs b/crates/ty_python_semantic/src/types/instance.rs index 7a7bc1c6ae..8b2967e744 100644 --- a/crates/ty_python_semantic/src/types/instance.rs +++ b/crates/ty_python_semantic/src/types/instance.rs @@ -11,9 +11,7 @@ use crate::types::cyclic::PairVisitor; use crate::types::enums::is_single_member_enum; use crate::types::protocol_class::walk_protocol_interface; use crate::types::tuple::{TupleSpec, TupleType}; -use crate::types::{ - ClassBase, DynamicType, TypeMapping, TypeRelation, TypeTransformer, TypedDictType, UnionType, -}; +use crate::types::{ClassBase, DynamicType, TypeMapping, TypeRelation, TypeTransformer, UnionType}; use crate::{Db, FxOrderSet, Program}; pub(super) use synthesized_protocol::SynthesizedProtocolType; @@ -34,7 +32,7 @@ impl<'db> Type<'db> { _ if class_literal.is_protocol(db) => { Self::ProtocolInstance(ProtocolInstanceType::from_class(class)) } - _ if class_literal.is_typed_dict(db) => TypedDictType::from(db, class), + _ if class_literal.is_typed_dict(db) => Type::typed_dict(class), _ => Type::non_tuple_instance(class), } } diff --git a/crates/ty_python_semantic/src/types/type_ordering.rs b/crates/ty_python_semantic/src/types/type_ordering.rs index f5d1910f25..dd8fede9b5 100644 --- a/crates/ty_python_semantic/src/types/type_ordering.rs +++ b/crates/ty_python_semantic/src/types/type_ordering.rs @@ -236,7 +236,9 @@ pub(super) fn union_or_intersection_elements_ordering<'db>( unreachable!("Two equal, normalized intersections should share the same Salsa ID") } - (Type::TypedDict(left), Type::TypedDict(right)) => left.cmp(right), + (Type::TypedDict(left), Type::TypedDict(right)) => { + left.defining_class.cmp(&right.defining_class) + } (Type::TypedDict(_), _) => Ordering::Less, (_, Type::TypedDict(_)) => Ordering::Greater, }