Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
191 changes: 104 additions & 87 deletions crates/ty_python_semantic/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down Expand Up @@ -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(_))
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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) => {
Expand All @@ -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),
)
}

Expand All @@ -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(
Expand Down Expand Up @@ -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),
)
}

Expand Down Expand Up @@ -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) => {
Expand All @@ -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
))
}

Expand Down Expand Up @@ -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)]
Expand All @@ -8235,27 +8253,38 @@ 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(
db: &'db dyn 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.
Expand All @@ -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(
Expand Down
10 changes: 5 additions & 5 deletions crates/ty_python_semantic/src/types/constraints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand Down Expand Up @@ -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);
Expand All @@ -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)
Expand All @@ -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>,
}
Expand All @@ -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));
Expand Down
Loading
Loading