[ty] Add separate type for typevar "identity" (#20813)
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / ty completion evaluation (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks instrumented (ruff) (push) Blocked by required conditions
CI / benchmarks instrumented (ty) (push) Blocked by required conditions
CI / benchmarks walltime (medium|multithreaded) (push) Blocked by required conditions
CI / benchmarks walltime (small|large) (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run

As part of #20598, we added `is_identical_to` methods to
`TypeVarInstance` and `BoundTypeVarInstance`, which compare when two
typevar instances refer to "the same" underlying typevar, even if we
have forced their lazy bounds/constraints as part of marking typevars as
inferable. (Doing so results in a different salsa interned struct ID,
since we've changed the contents of the `bounds_or_constraints` field.)

It turns out that marking typevars as inferable is not the only way that
we might force lazy bounds/constraints; it also happens when we
materialize a type containing a typevar. This surfaced as ecosystem
report failures on #20677.

That means that we need a more long-term fix to this problem.
(`is_identical_to`, and its underlying `original` field, were meant to
be a temporary fix until we removed the `MarkTypeVarsInferable` type
mapping.)

This PR extracts out a separate type (`TypeVarIdentity`) that only
includes the fields that actually inform whether two typevars are "the
same". All other properties of the typevar (default, bounds/constraints,
etc) still live in `TypeVarInstance`. Call sites that care about typevar
identity can now either store just `TypeVarIdentity` (if they never need
access to those other properties), or continue to store
`TypeVarInstance` but pull out its `identity` when performing those "are
they the same typevar" comparisons. (All of this also applies
respectively to `BoundTypeVar{Identity,Instance}`.) In particular,
constraint sets now work on `BoundTypeVarIdentity`, and generic contexts
still _store_ a `BoundTypeVarInstance` (since we might need access to
defaults when specializing), but are keyed on `BoundTypeVarIdentity`.
This commit is contained in:
Douglas Creager 2025-10-13 20:09:27 -04:00 committed by GitHub
parent aba0bd568e
commit 5e08e5451d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 199 additions and 169 deletions

View file

@ -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<Definition<'db>>,
/// 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<Definition<'db>>,
/// 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<TypeVarDefaultEvaluation<'db>>,
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<TypeVarInstance<'db>>,
}
// 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<Definition<'db>> {
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<Self> {
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(

View file

@ -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<T> {
@ -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<bool> 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));

View file

@ -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(())

View file

@ -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,

View file

@ -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<Type<'db>> {
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<BoundTypeVarInstance<'db>, GenericContextTypeVarOptions>,
variables_inner: FxOrderMap<BoundTypeVarIdentity<'db>, 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<Item = (BoundTypeVarInstance<'db>, GenericContextTypeVarOptions)>,
variables: impl IntoIterator<Item = GenericContextTypeVar<'db>>,
) -> Self {
Self::new_internal(db, variables.into_iter().collect::<FxOrderMap<_, _>>())
Self::new_internal(
db,
variables
.into_iter()
.map(|variable| (variable.bound_typevar.identity(db), variable))
.collect::<FxOrderMap<_, _>>(),
)
}
/// 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<Item = BoundTypeVarInstance<'db>>,
) -> 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<Item = BoundTypeVarInstance<'db>> + 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<BoundTypeVarInstance<'db>> {
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<BoundTypeVarInstance<'db>, GenericContextTypeVarOptions>,),
(variables,): &(FxOrderMap<BoundTypeVarIdentity<'db>, 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<Box<[_]>> = 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<BoundTypeVarInstance<'db>, Type<'db>>,
types: FxHashMap<BoundTypeVarIdentity<'db>, 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();
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 options.should_promote_literals {
let tcx = tcx_specialization
.and_then(|specialization| specialization.get(self.db, *variable));
// 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.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]);
})

View file

@ -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,
)))
}