diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 67a2486b778b1d..7ca7c2a44a2759 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -1674,7 +1674,7 @@ impl<'db> Type<'db> { ( Type::NonInferableTypeVar(lhs_bound_typevar), Type::NonInferableTypeVar(rhs_bound_typevar), - ) if lhs_bound_typevar.is_identical_to(db, rhs_bound_typevar) => { + ) if lhs_bound_typevar.identity(db) == rhs_bound_typevar.identity(db) => { ConstraintSet::from(true) } @@ -2443,7 +2443,9 @@ impl<'db> Type<'db> { ( Type::NonInferableTypeVar(self_bound_typevar), Type::NonInferableTypeVar(other_bound_typevar), - ) if self_bound_typevar == other_bound_typevar => ConstraintSet::from(false), + ) if self_bound_typevar.identity(db) == other_bound_typevar.identity(db) => { + ConstraintSet::from(false) + } (tvar @ Type::NonInferableTypeVar(_), Type::Intersection(intersection)) | (Type::Intersection(intersection), tvar @ Type::NonInferableTypeVar(_)) @@ -7789,7 +7791,32 @@ pub enum TypeVarKind { TypingSelf, } -/// A type variable that has not been bound to a generic context yet. +/// The identity of a type variable. +/// +/// This represents the core identity of a typevar, independent of its bounds or constraints. Two +/// typevars have the same identity if they represent the same logical typevar, even if their +/// bounds have been materialized differently. +/// +/// # Ordering +/// Ordering is based on the identity's salsa-assigned id and not on its values. +/// The id may change between runs, or when the identity was garbage collected and recreated. +#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)] +#[derive(PartialOrd, Ord)] +pub struct TypeVarIdentity<'db> { + /// The name of this TypeVar (e.g. `T`) + #[returns(ref)] + pub(crate) name: ast::name::Name, + + /// The type var's definition (None if synthesized) + pub(crate) definition: Option>, + + /// The kind of typevar (PEP 695, Legacy, or TypingSelf) + pub(crate) kind: TypeVarKind, +} + +impl get_size2::GetSize for TypeVarIdentity<'_> {} + +/// A specific instance of a type variable that has not been bound to a generic context yet. /// /// This is usually not the type that you want; if you are working with a typevar, in a generic /// context, which might be specialized to a concrete type, you want [`BoundTypeVarInstance`]. This @@ -7828,12 +7855,8 @@ pub enum TypeVarKind { #[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)] #[derive(PartialOrd, Ord)] pub struct TypeVarInstance<'db> { - /// The name of this TypeVar (e.g. `T`) - #[returns(ref)] - name: ast::name::Name, - - /// The type var's definition (None if synthesized) - pub definition: Option>, + /// The identity of this typevar + pub(crate) identity: TypeVarIdentity<'db>, /// The upper bound or constraint on the type of this TypeVar, if any. Don't use this field /// directly; use the `bound_or_constraints` (or `upper_bound` and `constraints`) methods @@ -7846,14 +7869,6 @@ pub struct TypeVarInstance<'db> { /// The default type for this TypeVar, if any. Don't use this field directly, use the /// `default_type` method instead (to evaluate any lazy default). _default: Option>, - - pub kind: TypeVarKind, - - /// If this typevar was transformed from another typevar via `mark_typevars_inferable`, this - /// records the identity of the "original" typevar, so we can recognize them as the same - /// typevar in `bind_typevar`. TODO: this (and the `is_identical_to` methods) should be - /// removable once we remove `mark_typevars_inferable`. - pub(crate) original: Option>, } // The Salsa heap is tracked separately. @@ -7899,6 +7914,18 @@ impl<'db> TypeVarInstance<'db> { BoundTypeVarInstance::new(db, self, BindingContext::Definition(binding_context)) } + pub(crate) fn name(self, db: &'db dyn Db) -> &'db ast::name::Name { + self.identity(db).name(db) + } + + pub(crate) fn definition(self, db: &'db dyn Db) -> Option> { + self.identity(db).definition(db) + } + + pub(crate) fn kind(self, db: &'db dyn Db) -> TypeVarKind { + self.identity(db).kind(db) + } + pub(crate) fn is_self(self, db: &'db dyn Db) -> bool { matches!(self.kind(db), TypeVarKind::TypingSelf) } @@ -7942,8 +7969,7 @@ impl<'db> TypeVarInstance<'db> { pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &NormalizedVisitor<'db>) -> Self { Self::new( db, - self.name(db), - self.definition(db), + self.identity(db), self._bound_or_constraints(db) .and_then(|bound_or_constraints| match bound_or_constraints { TypeVarBoundOrConstraintsEvaluation::Eager(bound_or_constraints) => { @@ -7963,8 +7989,6 @@ impl<'db> TypeVarInstance<'db> { .lazy_default(db) .map(|ty| ty.normalized_impl(db, visitor).into()), }), - self.kind(db), - self.original(db), ) } @@ -7976,8 +8000,7 @@ impl<'db> TypeVarInstance<'db> { ) -> Self { Self::new( db, - self.name(db), - self.definition(db), + self.identity(db), self._bound_or_constraints(db) .and_then(|bound_or_constraints| match bound_or_constraints { TypeVarBoundOrConstraintsEvaluation::Eager(bound_or_constraints) => Some( @@ -8009,8 +8032,6 @@ impl<'db> TypeVarInstance<'db> { .lazy_default(db) .map(|ty| ty.materialize(db, materialization_kind, visitor).into()), }), - self.kind(db), - self.original(db), ) } @@ -8047,33 +8068,15 @@ impl<'db> TypeVarInstance<'db> { }), }); - // Ensure that we only modify the `original` field if we are going to modify one or both of - // `_bound_or_constraints` and `_default`; don't trigger creation of a new - // `TypeVarInstance` unnecessarily. - let new_original = if new_bound_or_constraints == self._bound_or_constraints(db) - && new_default == self._default(db) - { - self.original(db) - } else { - Some(self) - }; - Self::new( db, - self.name(db), - self.definition(db), + self.identity(db), new_bound_or_constraints, self.explicit_variance(db), new_default, - self.kind(db), - new_original, ) } - fn is_identical_to(self, db: &'db dyn Db, other: Self) -> bool { - self == other || (self.original(db) == Some(other) || other.original(db) == Some(self)) - } - fn to_instance(self, db: &'db dyn Db) -> Option { let bound_or_constraints = match self.bound_or_constraints(db)? { TypeVarBoundOrConstraints::UpperBound(upper_bound) => { @@ -8083,15 +8086,18 @@ impl<'db> TypeVarInstance<'db> { TypeVarBoundOrConstraints::Constraints(constraints.to_instance(db)?.into_union()?) } }; - Some(Self::new( + let identity = TypeVarIdentity::new( db, Name::new(format!("{}'instance", self.name(db))), - None, + None, // definition + self.kind(db), + ); + Some(Self::new( + db, + identity, Some(bound_or_constraints.into()), self.explicit_variance(db), - None, - self.kind(db), - self.original(db), + None, // _default )) } @@ -8222,6 +8228,18 @@ impl<'db> BindingContext<'db> { } } +/// The identity of a bound type variable. +/// +/// This identifies a specific binding of a typevar to a context (e.g., `T@ClassC` vs `T@FunctionF`), +/// independent of the typevar's bounds or constraints. Two bound typevars have the same identity +/// if they represent the same logical typevar bound in the same context, even if their bounds +/// have been materialized differently. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, get_size2::GetSize)] +pub struct BoundTypeVarIdentity<'db> { + pub(crate) identity: TypeVarIdentity<'db>, + pub(crate) binding_context: BindingContext<'db>, +} + /// A type variable that has been bound to a generic context, and which can be specialized to a /// concrete type. #[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)] @@ -8235,6 +8253,17 @@ pub struct BoundTypeVarInstance<'db> { impl get_size2::GetSize for BoundTypeVarInstance<'_> {} impl<'db> BoundTypeVarInstance<'db> { + /// Get the identity of this bound typevar. + /// + /// This is used for comparing whether two bound typevars represent the same logical typevar, + /// regardless of e.g. differences in their bounds or constraints due to materialization. + pub(crate) fn identity(self, db: &'db dyn Db) -> BoundTypeVarIdentity<'db> { + BoundTypeVarIdentity { + identity: self.typevar(db).identity(db), + binding_context: self.binding_context(db), + } + } + /// Create a new PEP 695 type variable that can be used in signatures /// of synthetic generic functions. pub(crate) fn synthetic( @@ -8242,20 +8271,20 @@ impl<'db> BoundTypeVarInstance<'db> { name: &'static str, variance: TypeVarVariance, ) -> Self { - Self::new( + let identity = TypeVarIdentity::new( db, - TypeVarInstance::new( - db, - Name::new_static(name), - None, // definition - None, // _bound_or_constraints - Some(variance), - None, // _default - TypeVarKind::Pep695, - None, - ), - BindingContext::Synthetic, - ) + Name::new_static(name), + None, // definition + TypeVarKind::Pep695, + ); + let typevar = TypeVarInstance::new( + db, + identity, + None, // _bound_or_constraints + Some(variance), + None, // _default + ); + Self::new(db, typevar, BindingContext::Synthetic) } /// Create a new synthetic `Self` type variable with the given upper bound. @@ -8264,32 +8293,20 @@ impl<'db> BoundTypeVarInstance<'db> { upper_bound: Type<'db>, binding_context: BindingContext<'db>, ) -> Self { - Self::new( + let identity = TypeVarIdentity::new( db, - TypeVarInstance::new( - db, - Name::new_static("Self"), - None, - Some(TypeVarBoundOrConstraints::UpperBound(upper_bound).into()), - Some(TypeVarVariance::Invariant), - None, - TypeVarKind::TypingSelf, - None, - ), - binding_context, - ) - } - - pub(crate) fn is_identical_to(self, db: &'db dyn Db, other: Self) -> bool { - if self == other { - return true; - } - - if self.binding_context(db) != other.binding_context(db) { - return false; - } - - self.typevar(db).is_identical_to(db, other.typevar(db)) + Name::new_static("Self"), + None, // definition + TypeVarKind::TypingSelf, + ); + let typevar = TypeVarInstance::new( + db, + identity, + Some(TypeVarBoundOrConstraints::UpperBound(upper_bound).into()), + Some(TypeVarVariance::Invariant), + None, // _default + ); + Self::new(db, typevar, binding_context) } pub(crate) fn variance_with_polarity( diff --git a/crates/ty_python_semantic/src/types/constraints.rs b/crates/ty_python_semantic/src/types/constraints.rs index 1db37c3e56341e..3d2b23c09f8454 100644 --- a/crates/ty_python_semantic/src/types/constraints.rs +++ b/crates/ty_python_semantic/src/types/constraints.rs @@ -60,7 +60,7 @@ use itertools::Itertools; use rustc_hash::FxHashSet; use crate::Db; -use crate::types::{BoundTypeVarInstance, IntersectionType, Type, UnionType}; +use crate::types::{BoundTypeVarIdentity, IntersectionType, Type, UnionType}; /// An extension trait for building constraint sets from [`Option`] values. pub(crate) trait OptionConstraintsExtension { @@ -223,7 +223,7 @@ impl<'db> ConstraintSet<'db> { pub(crate) fn range( db: &'db dyn Db, lower: Type<'db>, - typevar: BoundTypeVarInstance<'db>, + typevar: BoundTypeVarIdentity<'db>, upper: Type<'db>, ) -> Self { let lower = lower.bottom_materialization(db); @@ -236,7 +236,7 @@ impl<'db> ConstraintSet<'db> { pub(crate) fn negated_range( db: &'db dyn Db, lower: Type<'db>, - typevar: BoundTypeVarInstance<'db>, + typevar: BoundTypeVarIdentity<'db>, upper: Type<'db>, ) -> Self { Self::range(db, lower, typevar, upper).negate(db) @@ -258,7 +258,7 @@ impl From for ConstraintSet<'_> { #[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)] #[derive(PartialOrd, Ord)] pub(crate) struct ConstrainedTypeVar<'db> { - typevar: BoundTypeVarInstance<'db>, + typevar: BoundTypeVarIdentity<'db>, lower: Type<'db>, upper: Type<'db>, } @@ -274,7 +274,7 @@ impl<'db> ConstrainedTypeVar<'db> { fn new_node( db: &'db dyn Db, lower: Type<'db>, - typevar: BoundTypeVarInstance<'db>, + typevar: BoundTypeVarIdentity<'db>, upper: Type<'db>, ) -> Node<'db> { debug_assert_eq!(lower, lower.bottom_materialization(db)); diff --git a/crates/ty_python_semantic/src/types/display.rs b/crates/ty_python_semantic/src/types/display.rs index 0a25401fe932cf..debde1a54c5c8a 100644 --- a/crates/ty_python_semantic/src/types/display.rs +++ b/crates/ty_python_semantic/src/types/display.rs @@ -24,7 +24,7 @@ use crate::types::signatures::{CallableSignature, Parameter, Parameters, Signatu use crate::types::tuple::TupleSpec; use crate::types::visitor::TypeVisitor; use crate::types::{ - BoundTypeVarInstance, CallableType, IntersectionType, KnownBoundMethodType, KnownClass, + BoundTypeVarIdentity, CallableType, IntersectionType, KnownBoundMethodType, KnownClass, MaterializationKind, Protocol, ProtocolInstanceType, StringLiteralType, SubclassOfInner, Type, UnionType, WrapperDescriptorKind, visitor, }; @@ -561,7 +561,7 @@ impl Display for DisplayRepresentation<'_> { literal_name = enum_literal.name(self.db) ), Type::NonInferableTypeVar(bound_typevar) | Type::TypeVar(bound_typevar) => { - bound_typevar.display(self.db).fmt(f) + bound_typevar.identity(self.db).display(self.db).fmt(f) } Type::AlwaysTruthy => f.write_str("AlwaysTruthy"), Type::AlwaysFalsy => f.write_str("AlwaysFalsy"), @@ -598,24 +598,24 @@ impl Display for DisplayRepresentation<'_> { } } -impl<'db> BoundTypeVarInstance<'db> { +impl<'db> BoundTypeVarIdentity<'db> { pub(crate) fn display(self, db: &'db dyn Db) -> impl Display { - DisplayBoundTypeVarInstance { - bound_typevar: self, + DisplayBoundTypeVarIdentity { + bound_typevar_identity: self, db, } } } -struct DisplayBoundTypeVarInstance<'db> { - bound_typevar: BoundTypeVarInstance<'db>, +struct DisplayBoundTypeVarIdentity<'db> { + bound_typevar_identity: BoundTypeVarIdentity<'db>, db: &'db dyn Db, } -impl Display for DisplayBoundTypeVarInstance<'_> { +impl Display for DisplayBoundTypeVarIdentity<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.write_str(self.bound_typevar.typevar(self.db).name(self.db))?; - if let Some(binding_context) = self.bound_typevar.binding_context(self.db).name(self.db) { + f.write_str(self.bound_typevar_identity.identity.name(self.db))?; + if let Some(binding_context) = self.bound_typevar_identity.binding_context.name(self.db) { write!(f, "@{binding_context}")?; } Ok(()) diff --git a/crates/ty_python_semantic/src/types/function.rs b/crates/ty_python_semantic/src/types/function.rs index 027642f5429eb9..f7cce4e03a869b 100644 --- a/crates/ty_python_semantic/src/types/function.rs +++ b/crates/ty_python_semantic/src/types/function.rs @@ -1730,7 +1730,7 @@ impl KnownFunction { return; }; - let constraints = ConstraintSet::range(db, *lower, *typevar, *upper); + let constraints = ConstraintSet::range(db, *lower, typevar.identity(db), *upper); let tracked = TrackedConstraintSet::new(db, constraints); overload.set_return_type(Type::KnownInstance(KnownInstanceType::ConstraintSet( tracked, @@ -1747,7 +1747,8 @@ impl KnownFunction { return; }; - let constraints = ConstraintSet::negated_range(db, *lower, *typevar, *upper); + let constraints = + ConstraintSet::negated_range(db, *lower, typevar.identity(db), *upper); let tracked = TrackedConstraintSet::new(db, constraints); overload.set_return_type(Type::KnownInstance(KnownInstanceType::ConstraintSet( tracked, diff --git a/crates/ty_python_semantic/src/types/generics.rs b/crates/ty_python_semantic/src/types/generics.rs index 0db16268a300c5..afb3cc45cedbad 100644 --- a/crates/ty_python_semantic/src/types/generics.rs +++ b/crates/ty_python_semantic/src/types/generics.rs @@ -14,11 +14,11 @@ use crate::types::instance::{Protocol, ProtocolInstanceType}; use crate::types::signatures::{Parameter, Parameters, Signature}; use crate::types::tuple::{TupleSpec, TupleType, walk_tuple_type}; use crate::types::{ - ApplyTypeMappingVisitor, BoundTypeVarInstance, ClassLiteral, FindLegacyTypeVarsVisitor, - HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, KnownClass, KnownInstanceType, - MaterializationKind, NormalizedVisitor, Type, TypeContext, TypeMapping, TypeRelation, - TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind, TypeVarVariance, UnionType, - binding_type, declaration_type, + ApplyTypeMappingVisitor, BoundTypeVarIdentity, BoundTypeVarInstance, ClassLiteral, + FindLegacyTypeVarsVisitor, HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, + KnownClass, KnownInstanceType, MaterializationKind, NormalizedVisitor, Type, TypeContext, + TypeMapping, TypeRelation, TypeVarBoundOrConstraints, TypeVarIdentity, TypeVarInstance, + TypeVarKind, TypeVarVariance, UnionType, binding_type, declaration_type, }; use crate::{Db, FxOrderMap, FxOrderSet}; @@ -109,24 +109,25 @@ pub(crate) fn typing_self<'db>( ) -> Option> { let index = semantic_index(db, scope_id.file(db)); - let typevar = TypeVarInstance::new( + let identity = TypeVarIdentity::new( db, ast::name::Name::new_static("Self"), Some(class.definition(db)), - Some( - TypeVarBoundOrConstraints::UpperBound(Type::instance( - db, - class.identity_specialization(db, typevar_to_type), - )) - .into(), - ), + TypeVarKind::TypingSelf, + ); + let bounds = TypeVarBoundOrConstraints::UpperBound(Type::instance( + db, + class.identity_specialization(db, typevar_to_type), + )); + let typevar = TypeVarInstance::new( + db, + identity, + Some(bounds.into()), // According to the [spec], we can consider `Self` // equivalent to an invariant type variable // [spec]: https://typing.python.org/en/latest/spec/generics.html#self Some(TypeVarVariance::Invariant), None, - TypeVarKind::TypingSelf, - None, ); bind_typevar( @@ -139,12 +140,20 @@ pub(crate) fn typing_self<'db>( .map(typevar_to_type) } -#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, get_size2::GetSize)] -pub struct GenericContextTypeVarOptions { +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, get_size2::GetSize)] +pub struct GenericContextTypeVar<'db> { + bound_typevar: BoundTypeVarInstance<'db>, should_promote_literals: bool, } -impl GenericContextTypeVarOptions { +impl<'db> GenericContextTypeVar<'db> { + fn new(bound_typevar: BoundTypeVarInstance<'db>) -> Self { + Self { + bound_typevar, + should_promote_literals: false, + } + } + fn promote_literals(mut self) -> Self { self.should_promote_literals = true; self @@ -160,7 +169,7 @@ impl GenericContextTypeVarOptions { #[derive(PartialOrd, Ord)] pub struct GenericContext<'db> { #[returns(ref)] - variables_inner: FxOrderMap, GenericContextTypeVarOptions>, + variables_inner: FxOrderMap, GenericContextTypeVar<'db>>, } pub(super) fn walk_generic_context<'db, V: super::visitor::TypeVisitor<'db> + ?Sized>( @@ -179,9 +188,15 @@ impl get_size2::GetSize for GenericContext<'_> {} impl<'db> GenericContext<'db> { fn from_variables( db: &'db dyn Db, - variables: impl IntoIterator, GenericContextTypeVarOptions)>, + variables: impl IntoIterator>, ) -> Self { - Self::new_internal(db, variables.into_iter().collect::>()) + Self::new_internal( + db, + variables + .into_iter() + .map(|variable| (variable.bound_typevar.identity(db), variable)) + .collect::>(), + ) } /// Creates a generic context from a list of PEP-695 type parameters. @@ -203,12 +218,7 @@ impl<'db> GenericContext<'db> { db: &'db dyn Db, type_params: impl IntoIterator>, ) -> Self { - Self::from_variables( - db, - type_params - .into_iter() - .map(|bound_typevar| (bound_typevar, GenericContextTypeVarOptions::default())), - ) + Self::from_variables(db, type_params.into_iter().map(GenericContextTypeVar::new)) } /// Returns a copy of this generic context where we will promote literal types in any inferred @@ -217,8 +227,8 @@ impl<'db> GenericContext<'db> { Self::from_variables( db, self.variables_inner(db) - .iter() - .map(|(bound_typevar, options)| (*bound_typevar, options.promote_literals())), + .values() + .map(|variable| variable.promote_literals()), ) } @@ -228,9 +238,9 @@ impl<'db> GenericContext<'db> { Self::from_variables( db, self.variables_inner(db) - .iter() - .chain(other.variables_inner(db).iter()) - .map(|(bound_typevar, options)| (*bound_typevar, *options)), + .values() + .chain(other.variables_inner(db).values()) + .copied(), ) } @@ -238,7 +248,9 @@ impl<'db> GenericContext<'db> { self, db: &'db dyn Db, ) -> impl ExactSizeIterator> + Clone { - self.variables_inner(db).keys().copied() + self.variables_inner(db) + .values() + .map(|variable| variable.bound_typevar) } fn variable_from_type_param( @@ -411,7 +423,7 @@ impl<'db> GenericContext<'db> { pub(crate) fn is_subset_of(self, db: &'db dyn Db, other: GenericContext<'db>) -> bool { let other_variables = other.variables_inner(db); self.variables(db) - .all(|bound_typevar| other_variables.contains_key(&bound_typevar)) + .all(|bound_typevar| other_variables.contains_key(&bound_typevar.identity(db))) } pub(crate) fn binds_named_typevar( @@ -428,8 +440,9 @@ impl<'db> GenericContext<'db> { db: &'db dyn Db, typevar: TypeVarInstance<'db>, ) -> Option> { - self.variables(db) - .find(|self_bound_typevar| self_bound_typevar.typevar(db).is_identical_to(db, typevar)) + self.variables(db).find(|self_bound_typevar| { + self_bound_typevar.typevar(db).identity(db) == typevar.identity(db) + }) } /// Creates a specialization of this generic context. Panics if the length of `types` does not @@ -513,7 +526,7 @@ impl<'db> GenericContext<'db> { } fn heap_size( - (variables,): &(FxOrderMap, GenericContextTypeVarOptions>,), + (variables,): &(FxOrderMap, GenericContextTypeVar<'db>>,), ) -> usize { ruff_memory_usage::order_map_heap_size(variables) } @@ -765,7 +778,7 @@ impl<'db> Specialization<'db> { let restricted_variables = generic_context.variables(db); let restricted_types: Option> = restricted_variables .map(|variable| { - let index = self_variables.get_index_of(&variable)?; + let index = self_variables.get_index_of(&variable.identity(db))?; self_types.get(index).copied() }) .collect(); @@ -793,7 +806,7 @@ impl<'db> Specialization<'db> { let index = self .generic_context(db) .variables_inner(db) - .get_index_of(&bound_typevar)?; + .get_index_of(&bound_typevar.identity(db))?; self.types(db).get(index).copied() } @@ -1146,7 +1159,7 @@ impl<'db> PartialSpecialization<'_, 'db> { let index = self .generic_context .variables_inner(db) - .get_index_of(&bound_typevar)?; + .get_index_of(&bound_typevar.identity(db))?; self.types.get(index).copied() } } @@ -1155,7 +1168,7 @@ impl<'db> PartialSpecialization<'_, 'db> { /// specialization of a generic function. pub(crate) struct SpecializationBuilder<'db> { db: &'db dyn Db, - types: FxHashMap, Type<'db>>, + types: FxHashMap, Type<'db>>, } impl<'db> SpecializationBuilder<'db> { @@ -1175,20 +1188,21 @@ impl<'db> SpecializationBuilder<'db> { .annotation .and_then(|annotation| annotation.specialization_of(self.db, None)); - let types = (generic_context.variables_inner(self.db).iter()).map(|(variable, options)| { - let mut ty = self.types.get(variable).copied(); - - // When inferring a specialization for a generic class typevar from a constructor call, - // promote any typevars that are inferred as a literal to the corresponding instance type. - if options.should_promote_literals { - let tcx = tcx_specialization - .and_then(|specialization| specialization.get(self.db, *variable)); - - ty = ty.map(|ty| ty.promote_literals(self.db, TypeContext::new(tcx))); - } + let types = + (generic_context.variables_inner(self.db).iter()).map(|(identity, variable)| { + let mut ty = self.types.get(identity).copied(); + + // When inferring a specialization for a generic class typevar from a constructor call, + // promote any typevars that are inferred as a literal to the corresponding instance type. + if variable.should_promote_literals { + let tcx = tcx_specialization.and_then(|specialization| { + specialization.get(self.db, variable.bound_typevar) + }); + ty = ty.map(|ty| ty.promote_literals(self.db, TypeContext::new(tcx))); + } - ty - }); + ty + }); // TODO Infer the tuple spec for a tuple type generic_context.specialize_partial(self.db, types) @@ -1196,7 +1210,7 @@ impl<'db> SpecializationBuilder<'db> { fn add_type_mapping(&mut self, bound_typevar: BoundTypeVarInstance<'db>, ty: Type<'db>) { self.types - .entry(bound_typevar) + .entry(bound_typevar.identity(self.db)) .and_modify(|existing| { *existing = UnionType::from_elements(self.db, [*existing, ty]); }) diff --git a/crates/ty_python_semantic/src/types/infer/builder.rs b/crates/ty_python_semantic/src/types/infer/builder.rs index bb1898f905def3..538d170ae60a38 100644 --- a/crates/ty_python_semantic/src/types/infer/builder.rs +++ b/crates/ty_python_semantic/src/types/infer/builder.rs @@ -94,8 +94,9 @@ use crate::types::{ MemberLookupPolicy, MetaclassCandidate, PEP695TypeAliasType, Parameter, ParameterForm, Parameters, SpecialFormType, SubclassOfType, TrackedConstraintSet, Truthiness, Type, TypeAliasType, TypeAndQualifiers, TypeContext, TypeQualifiers, - TypeVarBoundOrConstraintsEvaluation, TypeVarDefaultEvaluation, TypeVarInstance, TypeVarKind, - TypeVarVariance, TypedDictType, UnionBuilder, UnionType, binding_type, todo_type, + TypeVarBoundOrConstraintsEvaluation, TypeVarDefaultEvaluation, TypeVarIdentity, + TypeVarInstance, TypeVarKind, TypeVarVariance, TypedDictType, UnionBuilder, UnionType, + binding_type, todo_type, }; use crate::types::{ClassBase, add_inferred_python_version_hint_to_diagnostic}; use crate::unpack::{EvaluationMode, UnpackPosition}; @@ -3001,15 +3002,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { if bound_or_constraint.is_some() || default.is_some() { self.deferred.insert(definition); } + let identity = + TypeVarIdentity::new(self.db(), &name.id, Some(definition), TypeVarKind::Pep695); let ty = Type::KnownInstance(KnownInstanceType::TypeVar(TypeVarInstance::new( self.db(), - &name.id, - Some(definition), + identity, bound_or_constraint, - None, + None, // explicit_variance default.as_deref().map(|_| TypeVarDefaultEvaluation::Lazy), - TypeVarKind::Pep695, - None, ))); self.add_declaration_with_binding( node.into(), @@ -4340,15 +4340,13 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { self.deferred.insert(definition); } + let identity = TypeVarIdentity::new(db, target_name, Some(definition), TypeVarKind::Legacy); Type::KnownInstance(KnownInstanceType::TypeVar(TypeVarInstance::new( db, - target_name, - Some(definition), + identity, bound_or_constraints, Some(variance), default, - TypeVarKind::Legacy, - None, ))) }