From 77a62aef0bb9e394dcdd9501d3e57ad038e59f1a Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Wed, 1 Oct 2025 14:44:41 -0400 Subject: [PATCH 01/38] return constraint sets directly in function comparison --- .../ty_python_semantic/src/types/function.rs | 32 ++++++------------- .../src/types/signatures.rs | 21 ------------ 2 files changed, 9 insertions(+), 44 deletions(-) diff --git a/crates/ty_python_semantic/src/types/function.rs b/crates/ty_python_semantic/src/types/function.rs index 2f6b5858d75963..ecfa017f6e9355 100644 --- a/crates/ty_python_semantic/src/types/function.rs +++ b/crates/ty_python_semantic/src/types/function.rs @@ -896,39 +896,25 @@ impl<'db> FunctionType<'db> { db: &'db dyn Db, other: Self, relation: TypeRelation, - _visitor: &HasRelationToVisitor<'db>, + visitor: &HasRelationToVisitor<'db>, ) -> ConstraintSet<'db> { - match relation { - TypeRelation::Subtyping | TypeRelation::Redundancy => { - ConstraintSet::from(self.is_subtype_of(db, other)) - } - TypeRelation::Assignability => ConstraintSet::from(self.is_assignable_to(db, other)), - } - } - - pub(crate) fn is_subtype_of(self, db: &'db dyn Db, other: Self) -> bool { // A function type is the subtype of itself, and not of any other function type. However, // our representation of a function type includes any specialization that should be applied // to the signature. Different specializations of the same function type are only subtypes // of each other if they result in subtype signatures. - if self.normalized(db) == other.normalized(db) { - return true; + if matches!(relation, TypeRelation::Subtyping | TypeRelation::Redundancy) + && self.normalized(db) == other.normalized(db) + { + return ConstraintSet::from(true); } + if self.literal(db) != other.literal(db) { - return false; + return ConstraintSet::from(false); } + let self_signature = self.signature(db); let other_signature = other.signature(db); - self_signature.is_subtype_of(db, other_signature) - } - - pub(crate) fn is_assignable_to(self, db: &'db dyn Db, other: Self) -> bool { - // A function type is assignable to itself, and not to any other function type. However, - // our representation of a function type includes any specialization that should be applied - // to the signature. Different specializations of the same function type are only - // assignable to each other if they result in assignable signatures. - self.literal(db) == other.literal(db) - && self.signature(db).is_assignable_to(db, other.signature(db)) + self_signature.has_relation_to_impl(db, other_signature, relation, visitor) } pub(crate) fn is_equivalent_to_impl( diff --git a/crates/ty_python_semantic/src/types/signatures.rs b/crates/ty_python_semantic/src/types/signatures.rs index 41b955f4ec8d8d..b9b1cba6393f3e 100644 --- a/crates/ty_python_semantic/src/types/signatures.rs +++ b/crates/ty_python_semantic/src/types/signatures.rs @@ -173,13 +173,6 @@ impl<'db> CallableSignature<'db> { } } - /// Check whether this callable type is a subtype of another callable type. - /// - /// See [`Type::is_subtype_of`] for more details. - pub(crate) fn is_subtype_of(&self, db: &'db dyn Db, other: &Self) -> bool { - self.is_subtype_of_impl(db, other).is_always_satisfied() - } - fn is_subtype_of_impl(&self, db: &'db dyn Db, other: &Self) -> ConstraintSet<'db> { self.has_relation_to_impl( db, @@ -190,20 +183,6 @@ impl<'db> CallableSignature<'db> { ) } - /// Check whether this callable type is assignable to another callable type. - /// - /// See [`Type::is_assignable_to`] for more details. - pub(crate) fn is_assignable_to(&self, db: &'db dyn Db, other: &Self) -> bool { - self.has_relation_to_impl( - db, - other, - TypeRelation::Assignability, - &HasRelationToVisitor::default(), - &IsDisjointVisitor::default(), - ) - .is_always_satisfied() - } - pub(crate) fn has_relation_to_impl( &self, db: &'db dyn Db, From 296d485c61f57db5f031ce72c6104450e53d4d5c Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Wed, 1 Oct 2025 15:14:54 -0400 Subject: [PATCH 02/38] pass inferable typevars explicitly --- crates/ty_python_semantic/src/types.rs | 394 ++++++++++++++---- .../ty_python_semantic/src/types/call/bind.rs | 15 +- crates/ty_python_semantic/src/types/class.rs | 8 +- .../ty_python_semantic/src/types/function.rs | 22 +- .../ty_python_semantic/src/types/generics.rs | 21 +- .../ty_python_semantic/src/types/instance.rs | 19 +- .../src/types/protocol_class.rs | 21 +- .../src/types/signatures.rs | 30 +- .../src/types/subclass_of.rs | 4 + crates/ty_python_semantic/src/types/tuple.rs | 45 +- 10 files changed, 468 insertions(+), 111 deletions(-) diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index dc27519dbca7a5..b2c38560179957 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -559,21 +559,27 @@ impl<'db> PropertyInstanceType<'db> { } } - fn when_equivalent_to(self, db: &'db dyn Db, other: Self) -> ConstraintSet<'db> { - self.is_equivalent_to_impl(db, other, &IsEquivalentVisitor::default()) + fn when_equivalent_to( + self, + db: &'db dyn Db, + other: Self, + inferable: Option>, + ) -> ConstraintSet<'db> { + self.is_equivalent_to_impl(db, other, inferable, &IsEquivalentVisitor::default()) } fn is_equivalent_to_impl( self, db: &'db dyn Db, other: Self, + inferable: Option>, visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { let getter_equivalence = if let Some(getter) = self.getter(db) { let Some(other_getter) = other.getter(db) else { return ConstraintSet::from(false); }; - getter.is_equivalent_to_impl(db, other_getter, visitor) + getter.is_equivalent_to_impl(db, other_getter, inferable, visitor) } else { if other.getter(db).is_some() { return ConstraintSet::from(false); @@ -586,7 +592,7 @@ impl<'db> PropertyInstanceType<'db> { let Some(other_setter) = other.setter(db) else { return ConstraintSet::from(false); }; - setter.is_equivalent_to_impl(db, other_setter, visitor) + setter.is_equivalent_to_impl(db, other_setter, inferable, visitor) } else { if other.setter(db).is_some() { return ConstraintSet::from(false); @@ -1457,29 +1463,40 @@ impl<'db> Type<'db> { /// /// See [`TypeRelation::Subtyping`] for more details. pub(crate) fn is_subtype_of(self, db: &'db dyn Db, target: Type<'db>) -> bool { - self.when_subtype_of(db, target).is_always_satisfied() + self.when_subtype_of(db, target, None).is_always_satisfied() } - fn when_subtype_of(self, db: &'db dyn Db, target: Type<'db>) -> ConstraintSet<'db> { - self.has_relation_to(db, target, TypeRelation::Subtyping) + fn when_subtype_of( + self, + db: &'db dyn Db, + target: Type<'db>, + inferable: Option>, + ) -> ConstraintSet<'db> { + self.has_relation_to(db, target, inferable, TypeRelation::Subtyping) } /// Return true if this type is assignable to type `target`. /// /// See [`TypeRelation::Assignability`] for more details. pub(crate) fn is_assignable_to(self, db: &'db dyn Db, target: Type<'db>) -> bool { - self.when_assignable_to(db, target).is_always_satisfied() + self.when_assignable_to(db, target, None) + .is_always_satisfied() } - fn when_assignable_to(self, db: &'db dyn Db, target: Type<'db>) -> ConstraintSet<'db> { - self.has_relation_to(db, target, TypeRelation::Assignability) + fn when_assignable_to( + self, + db: &'db dyn Db, + target: Type<'db>, + inferable: Option>, + ) -> ConstraintSet<'db> { + self.has_relation_to(db, target, inferable, TypeRelation::Assignability) } /// Return `true` if it would be redundant to add `self` to a union that already contains `other`. /// /// See [`TypeRelation::Redundancy`] for more details. pub(crate) fn is_redundant_with(self, db: &'db dyn Db, other: Type<'db>) -> bool { - self.has_relation_to(db, other, TypeRelation::Redundancy) + self.has_relation_to(db, other, None, TypeRelation::Redundancy) .is_always_satisfied() } @@ -1487,11 +1504,13 @@ impl<'db> Type<'db> { self, db: &'db dyn Db, target: Type<'db>, + inferable: Option>, relation: TypeRelation, ) -> ConstraintSet<'db> { self.has_relation_to_impl( db, target, + inferable, relation, &HasRelationToVisitor::default(), &IsDisjointVisitor::default(), @@ -1502,6 +1521,7 @@ impl<'db> Type<'db> { self, db: &'db dyn Db, target: Type<'db>, + inferable: Option>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -1541,6 +1561,7 @@ impl<'db> Type<'db> { self_alias.value_type(db).has_relation_to_impl( db, target, + inferable, relation, relation_visitor, disjointness_visitor, @@ -1553,6 +1574,7 @@ impl<'db> Type<'db> { self.has_relation_to_impl( db, target_alias.value_type(db), + inferable, relation, relation_visitor, disjointness_visitor, @@ -1569,6 +1591,7 @@ impl<'db> Type<'db> { field.default_type(db).has_relation_to_impl( db, right, + inferable, relation, relation_visitor, disjointness_visitor, @@ -1651,6 +1674,7 @@ impl<'db> Type<'db> { .has_relation_to_impl( db, target, + inferable, relation, relation_visitor, disjointness_visitor, @@ -1660,6 +1684,7 @@ impl<'db> Type<'db> { constraint.has_relation_to_impl( db, target, + inferable, relation, relation_visitor, disjointness_visitor, @@ -1681,6 +1706,7 @@ impl<'db> Type<'db> { self.has_relation_to_impl( db, *constraint, + inferable, relation, relation_visitor, disjointness_visitor, @@ -1701,6 +1727,7 @@ impl<'db> Type<'db> { self.has_relation_to_impl( db, *constraint, + inferable, relation, relation_visitor, disjointness_visitor, @@ -1725,6 +1752,7 @@ impl<'db> Type<'db> { elem_ty.has_relation_to_impl( db, target, + inferable, relation, relation_visitor, disjointness_visitor, @@ -1735,6 +1763,7 @@ impl<'db> Type<'db> { self.has_relation_to_impl( db, elem_ty, + inferable, relation, relation_visitor, disjointness_visitor, @@ -1751,6 +1780,7 @@ impl<'db> Type<'db> { self.has_relation_to_impl( db, pos_ty, + inferable, relation, relation_visitor, disjointness_visitor, @@ -1782,6 +1812,7 @@ impl<'db> Type<'db> { self_ty.is_disjoint_from_impl( db, neg_ty, + inferable, disjointness_visitor, relation_visitor, ) @@ -1793,6 +1824,7 @@ impl<'db> Type<'db> { elem_ty.has_relation_to_impl( db, target, + inferable, relation, relation_visitor, disjointness_visitor, @@ -1814,6 +1846,7 @@ impl<'db> Type<'db> { .has_relation_to_impl( db, bound, + inferable, relation, relation_visitor, disjointness_visitor, @@ -1827,6 +1860,7 @@ impl<'db> Type<'db> { self.has_relation_to_impl( db, bound, + inferable, relation, relation_visitor, disjointness_visitor, @@ -1848,7 +1882,7 @@ impl<'db> Type<'db> { (left, Type::AlwaysTruthy) => ConstraintSet::from(left.bool(db).is_always_true()), // Currently, the only supertype of `AlwaysFalsy` and `AlwaysTruthy` is the universal set (object instance). (Type::AlwaysFalsy | Type::AlwaysTruthy, _) => { - target.when_equivalent_to(db, Type::object()) + target.when_equivalent_to(db, Type::object(), inferable) } // These clauses handle type variants that include function literals. A function @@ -1857,18 +1891,33 @@ impl<'db> Type<'db> { // applied to the signature. Different specializations of the same function literal are // only subtypes of each other if they result in the same signature. (Type::FunctionLiteral(self_function), Type::FunctionLiteral(target_function)) => { - self_function.has_relation_to_impl(db, target_function, relation, relation_visitor) + self_function.has_relation_to_impl( + db, + target_function, + inferable, + relation, + relation_visitor, + disjointness_visitor, + ) } (Type::BoundMethod(self_method), Type::BoundMethod(target_method)) => self_method .has_relation_to_impl( db, target_method, + inferable, relation, relation_visitor, disjointness_visitor, ), (Type::KnownBoundMethod(self_method), Type::KnownBoundMethod(target_method)) => { - self_method.has_relation_to_impl(db, target_method, relation, relation_visitor) + self_method.has_relation_to_impl( + db, + target_method, + inferable, + relation, + relation_visitor, + disjointness_visitor, + ) } // No literal type is a subtype of any other literal type, unless they are the same @@ -1897,6 +1946,7 @@ impl<'db> Type<'db> { self_callable.has_relation_to_impl( db, other_callable, + inferable, relation, relation_visitor, disjointness_visitor, @@ -1908,6 +1958,7 @@ impl<'db> Type<'db> { callable.has_relation_to_impl( db, target, + inferable, relation, relation_visitor, disjointness_visitor, @@ -1920,6 +1971,7 @@ impl<'db> Type<'db> { self.satisfies_protocol( db, protocol, + inferable, relation, relation_visitor, disjointness_visitor, @@ -1963,6 +2015,7 @@ impl<'db> Type<'db> { instance.has_relation_to_impl( db, target, + inferable, relation, relation_visitor, disjointness_visitor, @@ -1970,16 +2023,36 @@ impl<'db> Type<'db> { }), // The same reasoning applies for these special callable types: - (Type::BoundMethod(_), _) => KnownClass::MethodType - .to_instance(db) - .has_relation_to_impl(db, target, relation, relation_visitor, disjointness_visitor), - (Type::KnownBoundMethod(method), _) => method - .class() - .to_instance(db) - .has_relation_to_impl(db, target, relation, relation_visitor, disjointness_visitor), + (Type::BoundMethod(_), _) => { + KnownClass::MethodType.to_instance(db).has_relation_to_impl( + db, + target, + inferable, + relation, + relation_visitor, + disjointness_visitor, + ) + } + (Type::KnownBoundMethod(method), _) => { + method.class().to_instance(db).has_relation_to_impl( + db, + target, + inferable, + relation, + relation_visitor, + disjointness_visitor, + ) + } (Type::WrapperDescriptor(_), _) => KnownClass::WrapperDescriptorType .to_instance(db) - .has_relation_to_impl(db, target, relation, relation_visitor, disjointness_visitor), + .has_relation_to_impl( + db, + target, + inferable, + relation, + relation_visitor, + disjointness_visitor, + ), (Type::DataclassDecorator(_) | Type::DataclassTransformer(_), _) => { // TODO: Implement subtyping using an equivalent `Callable` type. @@ -1992,6 +2065,7 @@ impl<'db> Type<'db> { .has_relation_to_impl( db, right.return_type(db), + inferable, relation, relation_visitor, disjointness_visitor, @@ -2000,6 +2074,7 @@ impl<'db> Type<'db> { right.return_type(db).has_relation_to_impl( db, left.return_type(db), + inferable, relation, relation_visitor, disjointness_visitor, @@ -2010,6 +2085,7 @@ impl<'db> Type<'db> { (Type::TypeIs(_), _) => KnownClass::Bool.to_instance(db).has_relation_to_impl( db, target, + inferable, relation, relation_visitor, disjointness_visitor, @@ -2022,6 +2098,7 @@ impl<'db> Type<'db> { .has_relation_to_impl( db, target, + inferable, relation, relation_visitor, disjointness_visitor, @@ -2030,10 +2107,13 @@ impl<'db> Type<'db> { (Type::Callable(_), _) => ConstraintSet::from(false), - (Type::BoundSuper(_), Type::BoundSuper(_)) => self.when_equivalent_to(db, target), + (Type::BoundSuper(_), Type::BoundSuper(_)) => { + self.when_equivalent_to(db, target, inferable) + } (Type::BoundSuper(_), _) => KnownClass::Super.to_instance(db).has_relation_to_impl( db, target, + inferable, relation, relation_visitor, disjointness_visitor, @@ -2048,6 +2128,7 @@ impl<'db> Type<'db> { ClassType::NonGeneric(class).has_relation_to_impl( db, subclass_of_class, + inferable, relation, relation_visitor, disjointness_visitor, @@ -2061,6 +2142,7 @@ impl<'db> Type<'db> { ClassType::Generic(alias).has_relation_to_impl( db, subclass_of_class, + inferable, relation, relation_visitor, disjointness_visitor, @@ -2073,6 +2155,7 @@ impl<'db> Type<'db> { self_subclass_ty.has_relation_to_impl( db, target_subclass_ty, + inferable, relation, relation_visitor, disjointness_visitor, @@ -2082,12 +2165,26 @@ impl<'db> Type<'db> { // `Literal[str]` is a subtype of `type` because the `str` class object is an instance of its metaclass `type`. // `Literal[abc.ABC]` is a subtype of `abc.ABCMeta` because the `abc.ABC` class object // is an instance of its metaclass `abc.ABCMeta`. - (Type::ClassLiteral(class), _) => class - .metaclass_instance_type(db) - .has_relation_to_impl(db, target, relation, relation_visitor, disjointness_visitor), + (Type::ClassLiteral(class), _) => { + class.metaclass_instance_type(db).has_relation_to_impl( + db, + target, + inferable, + relation, + relation_visitor, + disjointness_visitor, + ) + } (Type::GenericAlias(alias), _) => ClassType::from(alias) .metaclass_instance_type(db) - .has_relation_to_impl(db, target, relation, relation_visitor, disjointness_visitor), + .has_relation_to_impl( + db, + target, + inferable, + relation, + relation_visitor, + disjointness_visitor, + ), // `type[Any]` is a subtype of `type[object]`, and is assignable to any `type[...]` (Type::SubclassOf(subclass_of_ty), other) if subclass_of_ty.is_dynamic() => { @@ -2096,6 +2193,7 @@ impl<'db> Type<'db> { .has_relation_to_impl( db, other, + inferable, relation, relation_visitor, disjointness_visitor, @@ -2105,6 +2203,7 @@ impl<'db> Type<'db> { other.has_relation_to_impl( db, KnownClass::Type.to_instance(db), + inferable, relation, relation_visitor, disjointness_visitor, @@ -2120,6 +2219,7 @@ impl<'db> Type<'db> { other.has_relation_to_impl( db, KnownClass::Type.to_instance(db), + inferable, relation, relation_visitor, disjointness_visitor, @@ -2138,7 +2238,14 @@ impl<'db> Type<'db> { .into_class() .map(|class| class.metaclass_instance_type(db)) .unwrap_or_else(|| KnownClass::Type.to_instance(db)) - .has_relation_to_impl(db, target, relation, relation_visitor, disjointness_visitor), + .has_relation_to_impl( + db, + target, + inferable, + relation, + relation_visitor, + disjointness_visitor, + ), // For example: `Type::SpecialForm(SpecialFormType::Type)` is a subtype of `Type::NominalInstance(_SpecialForm)`, // because `Type::SpecialForm(SpecialFormType::Type)` is a set with exactly one runtime value in it @@ -2146,6 +2253,7 @@ impl<'db> Type<'db> { (Type::SpecialForm(left), right) => left.instance_fallback(db).has_relation_to_impl( db, right, + inferable, relation, relation_visitor, disjointness_visitor, @@ -2154,6 +2262,7 @@ impl<'db> Type<'db> { (Type::KnownInstance(left), right) => left.instance_fallback(db).has_relation_to_impl( db, right, + inferable, relation, relation_visitor, disjointness_visitor, @@ -2166,6 +2275,7 @@ impl<'db> Type<'db> { self_instance.has_relation_to_impl( db, target_instance, + inferable, relation, relation_visitor, disjointness_visitor, @@ -2173,12 +2283,20 @@ impl<'db> Type<'db> { }) } - (Type::PropertyInstance(_), _) => KnownClass::Property - .to_instance(db) - .has_relation_to_impl(db, target, relation, relation_visitor, disjointness_visitor), + (Type::PropertyInstance(_), _) => { + KnownClass::Property.to_instance(db).has_relation_to_impl( + db, + target, + inferable, + relation, + relation_visitor, + disjointness_visitor, + ) + } (_, Type::PropertyInstance(_)) => self.has_relation_to_impl( db, KnownClass::Property.to_instance(db), + inferable, relation, relation_visitor, disjointness_visitor, @@ -2205,17 +2323,24 @@ impl<'db> Type<'db> { /// /// [equivalent to]: https://typing.python.org/en/latest/spec/glossary.html#term-equivalent pub(crate) fn is_equivalent_to(self, db: &'db dyn Db, other: Type<'db>) -> bool { - self.when_equivalent_to(db, other).is_always_satisfied() + self.when_equivalent_to(db, other, None) + .is_always_satisfied() } - fn when_equivalent_to(self, db: &'db dyn Db, other: Type<'db>) -> ConstraintSet<'db> { - self.is_equivalent_to_impl(db, other, &IsEquivalentVisitor::default()) + fn when_equivalent_to( + self, + db: &'db dyn Db, + other: Type<'db>, + inferable: Option>, + ) -> ConstraintSet<'db> { + self.is_equivalent_to_impl(db, other, inferable, &IsEquivalentVisitor::default()) } pub(crate) fn is_equivalent_to_impl( self, db: &'db dyn Db, other: Type<'db>, + inferable: Option>, visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { if self == other { @@ -2244,44 +2369,44 @@ impl<'db> Type<'db> { (Type::TypeAlias(self_alias), _) => { let self_alias_ty = self_alias.value_type(db).normalized(db); visitor.visit((self_alias_ty, other), || { - self_alias_ty.is_equivalent_to_impl(db, other, visitor) + self_alias_ty.is_equivalent_to_impl(db, other, inferable, visitor) }) } (_, Type::TypeAlias(other_alias)) => { let other_alias_ty = other_alias.value_type(db).normalized(db); visitor.visit((self, other_alias_ty), || { - self.is_equivalent_to_impl(db, other_alias_ty, visitor) + self.is_equivalent_to_impl(db, other_alias_ty, inferable, visitor) }) } (Type::NominalInstance(first), Type::NominalInstance(second)) => { - first.is_equivalent_to_impl(db, second, visitor) + first.is_equivalent_to_impl(db, second, inferable, visitor) } (Type::Union(first), Type::Union(second)) => { - first.is_equivalent_to_impl(db, second, visitor) + first.is_equivalent_to_impl(db, second, inferable, visitor) } (Type::Intersection(first), Type::Intersection(second)) => { - first.is_equivalent_to_impl(db, second, visitor) + first.is_equivalent_to_impl(db, second, inferable, visitor) } (Type::FunctionLiteral(self_function), Type::FunctionLiteral(target_function)) => { - self_function.is_equivalent_to_impl(db, target_function, visitor) + self_function.is_equivalent_to_impl(db, target_function, inferable, visitor) } (Type::BoundMethod(self_method), Type::BoundMethod(target_method)) => { - self_method.is_equivalent_to_impl(db, target_method, visitor) + self_method.is_equivalent_to_impl(db, target_method, inferable, visitor) } (Type::KnownBoundMethod(self_method), Type::KnownBoundMethod(target_method)) => { - self_method.is_equivalent_to_impl(db, target_method, visitor) + self_method.is_equivalent_to_impl(db, target_method, inferable, visitor) } (Type::Callable(first), Type::Callable(second)) => { - first.is_equivalent_to_impl(db, second, visitor) + first.is_equivalent_to_impl(db, second, inferable, visitor) } (Type::ProtocolInstance(first), Type::ProtocolInstance(second)) => { - first.is_equivalent_to_impl(db, second, visitor) + first.is_equivalent_to_impl(db, second, inferable, visitor) } (Type::ProtocolInstance(protocol), nominal @ Type::NominalInstance(n)) | (nominal @ Type::NominalInstance(n), Type::ProtocolInstance(protocol)) => { @@ -2298,7 +2423,7 @@ impl<'db> Type<'db> { } (Type::PropertyInstance(left), Type::PropertyInstance(right)) => { - left.is_equivalent_to_impl(db, right, visitor) + left.is_equivalent_to_impl(db, right, inferable, visitor) } _ => ConstraintSet::from(false), @@ -2321,13 +2446,20 @@ impl<'db> Type<'db> { /// This function aims to have no false positives, but might return wrong /// `false` answers in some cases. pub(crate) fn is_disjoint_from(self, db: &'db dyn Db, other: Type<'db>) -> bool { - self.when_disjoint_from(db, other).is_always_satisfied() + self.when_disjoint_from(db, other, None) + .is_always_satisfied() } - fn when_disjoint_from(self, db: &'db dyn Db, other: Type<'db>) -> ConstraintSet<'db> { + fn when_disjoint_from( + self, + db: &'db dyn Db, + other: Type<'db>, + inferable: Option>, + ) -> ConstraintSet<'db> { self.is_disjoint_from_impl( db, other, + inferable, &IsDisjointVisitor::default(), &HasRelationToVisitor::default(), ) @@ -2337,6 +2469,7 @@ impl<'db> Type<'db> { self, db: &'db dyn Db, other: Type<'db>, + inferable: Option>, disjointness_visitor: &IsDisjointVisitor<'db>, relation_visitor: &HasRelationToVisitor<'db>, ) -> ConstraintSet<'db> { @@ -2344,6 +2477,7 @@ impl<'db> Type<'db> { db: &'db dyn Db, protocol: ProtocolInstanceType<'db>, other: Type<'db>, + inferable: Option>, disjointness_visitor: &IsDisjointVisitor<'db>, relation_visitor: &HasRelationToVisitor<'db>, ) -> ConstraintSet<'db> { @@ -2356,6 +2490,7 @@ impl<'db> Type<'db> { member.has_disjoint_type_from( db, attribute_type, + inferable, disjointness_visitor, relation_visitor, ) @@ -2374,6 +2509,7 @@ impl<'db> Type<'db> { self_alias_ty.is_disjoint_from_impl( db, other, + inferable, disjointness_visitor, relation_visitor, ) @@ -2386,6 +2522,7 @@ impl<'db> Type<'db> { self.is_disjoint_from_impl( db, other_alias_ty, + inferable, disjointness_visitor, relation_visitor, ) @@ -2422,12 +2559,19 @@ impl<'db> Type<'db> { match bound_typevar.typevar(db).bound_or_constraints(db) { None => ConstraintSet::from(false), Some(TypeVarBoundOrConstraints::UpperBound(bound)) => bound - .is_disjoint_from_impl(db, other, disjointness_visitor, relation_visitor), + .is_disjoint_from_impl( + db, + other, + inferable, + disjointness_visitor, + relation_visitor, + ), Some(TypeVarBoundOrConstraints::Constraints(constraints)) => { constraints.elements(db).iter().when_all(db, |constraint| { constraint.is_disjoint_from_impl( db, other, + inferable, disjointness_visitor, relation_visitor, ) @@ -2441,7 +2585,13 @@ impl<'db> Type<'db> { (Type::Union(union), other) | (other, Type::Union(union)) => { union.elements(db).iter().when_all(db, |e| { - e.is_disjoint_from_impl(db, other, disjointness_visitor, relation_visitor) + e.is_disjoint_from_impl( + db, + other, + inferable, + disjointness_visitor, + relation_visitor, + ) }) } @@ -2457,6 +2607,7 @@ impl<'db> Type<'db> { p.is_disjoint_from_impl( db, other, + inferable, disjointness_visitor, relation_visitor, ) @@ -2466,6 +2617,7 @@ impl<'db> Type<'db> { p.is_disjoint_from_impl( db, self, + inferable, disjointness_visitor, relation_visitor, ) @@ -2484,6 +2636,7 @@ impl<'db> Type<'db> { p.is_disjoint_from_impl( db, non_intersection, + inferable, disjointness_visitor, relation_visitor, ) @@ -2494,6 +2647,7 @@ impl<'db> Type<'db> { non_intersection.has_relation_to_impl( db, neg_ty, + inferable, TypeRelation::Subtyping, relation_visitor, disjointness_visitor, @@ -2577,7 +2731,7 @@ impl<'db> Type<'db> { (Type::ProtocolInstance(left), Type::ProtocolInstance(right)) => disjointness_visitor .visit((self, other), || { - left.is_disjoint_from_impl(db, right, disjointness_visitor) + left.is_disjoint_from_impl(db, right, inferable, disjointness_visitor) }), (Type::ProtocolInstance(protocol), Type::SpecialForm(special_form)) @@ -2587,6 +2741,7 @@ impl<'db> Type<'db> { db, protocol, special_form.instance_fallback(db), + inferable, disjointness_visitor, relation_visitor, ) @@ -2600,6 +2755,7 @@ impl<'db> Type<'db> { db, protocol, known_instance.instance_fallback(db), + inferable, disjointness_visitor, relation_visitor, ) @@ -2663,6 +2819,7 @@ impl<'db> Type<'db> { db, protocol, ty, + inferable, disjointness_visitor, relation_visitor, ) @@ -2680,6 +2837,7 @@ impl<'db> Type<'db> { db, protocol, nominal, + inferable, disjointness_visitor, relation_visitor, ) @@ -2694,6 +2852,7 @@ impl<'db> Type<'db> { Place::Type(attribute_type, _) => member.has_disjoint_type_from( db, attribute_type, + inferable, disjointness_visitor, relation_visitor, ), @@ -2718,25 +2877,37 @@ impl<'db> Type<'db> { match subclass_of_ty.subclass_of() { SubclassOfInner::Dynamic(_) => ConstraintSet::from(false), SubclassOfInner::Class(class_a) => ClassType::from(alias_b) - .when_subclass_of(db, class_a) + .when_subclass_of(db, class_a, inferable) .negate(db), } } (Type::SubclassOf(left), Type::SubclassOf(right)) => { - left.is_disjoint_from_impl(db, right, disjointness_visitor) + left.is_disjoint_from_impl(db, right, inferable, disjointness_visitor) } // for `type[Any]`/`type[Unknown]`/`type[Todo]`, we know the type cannot be any larger than `type`, // so although the type is dynamic we can still determine disjointedness in some situations (Type::SubclassOf(subclass_of_ty), other) | (other, Type::SubclassOf(subclass_of_ty)) => match subclass_of_ty.subclass_of() { - SubclassOfInner::Dynamic(_) => KnownClass::Type - .to_instance(db) - .is_disjoint_from_impl(db, other, disjointness_visitor, relation_visitor), - SubclassOfInner::Class(class) => class - .metaclass_instance_type(db) - .is_disjoint_from_impl(db, other, disjointness_visitor, relation_visitor), + SubclassOfInner::Dynamic(_) => { + KnownClass::Type.to_instance(db).is_disjoint_from_impl( + db, + other, + inferable, + disjointness_visitor, + relation_visitor, + ) + } + SubclassOfInner::Class(class) => { + class.metaclass_instance_type(db).is_disjoint_from_impl( + db, + other, + inferable, + disjointness_visitor, + relation_visitor, + ) + } }, (Type::SpecialForm(special_form), Type::NominalInstance(instance)) @@ -2803,6 +2974,7 @@ impl<'db> Type<'db> { .has_relation_to_impl( db, instance, + inferable, TypeRelation::Subtyping, relation_visitor, disjointness_visitor, @@ -2817,7 +2989,7 @@ impl<'db> Type<'db> { (Type::ClassLiteral(class), instance @ Type::NominalInstance(_)) | (instance @ Type::NominalInstance(_), Type::ClassLiteral(class)) => class .metaclass_instance_type(db) - .when_subtype_of(db, instance) + .when_subtype_of(db, instance, inferable) .negate(db), (Type::GenericAlias(alias), instance @ Type::NominalInstance(_)) | (instance @ Type::NominalInstance(_), Type::GenericAlias(alias)) => { @@ -2826,6 +2998,7 @@ impl<'db> Type<'db> { .has_relation_to_impl( db, instance, + inferable, TypeRelation::Subtyping, relation_visitor, disjointness_visitor, @@ -2844,12 +3017,19 @@ impl<'db> Type<'db> { (Type::BoundMethod(_), other) | (other, Type::BoundMethod(_)) => KnownClass::MethodType .to_instance(db) - .is_disjoint_from_impl(db, other, disjointness_visitor, relation_visitor), + .is_disjoint_from_impl( + db, + other, + inferable, + disjointness_visitor, + relation_visitor, + ), (Type::KnownBoundMethod(method), other) | (other, Type::KnownBoundMethod(method)) => { method.class().to_instance(db).is_disjoint_from_impl( db, other, + inferable, disjointness_visitor, relation_visitor, ) @@ -2858,7 +3038,13 @@ impl<'db> Type<'db> { (Type::WrapperDescriptor(_), other) | (other, Type::WrapperDescriptor(_)) => { KnownClass::WrapperDescriptorType .to_instance(db) - .is_disjoint_from_impl(db, other, disjointness_visitor, relation_visitor) + .is_disjoint_from_impl( + db, + other, + inferable, + disjointness_visitor, + relation_visitor, + ) } (Type::Callable(_) | Type::FunctionLiteral(_), Type::Callable(_)) @@ -2906,6 +3092,7 @@ impl<'db> Type<'db> { .has_relation_to_impl( db, CallableType::unknown(db), + inferable, TypeRelation::Assignability, relation_visitor, disjointness_visitor, @@ -2931,6 +3118,7 @@ impl<'db> Type<'db> { other.is_disjoint_from_impl( db, KnownClass::ModuleType.to_instance(db), + inferable, disjointness_visitor, relation_visitor, ) @@ -2938,24 +3126,37 @@ impl<'db> Type<'db> { (Type::NominalInstance(left), Type::NominalInstance(right)) => disjointness_visitor .visit((self, other), || { - left.is_disjoint_from_impl(db, right, disjointness_visitor, relation_visitor) + left.is_disjoint_from_impl( + db, + right, + inferable, + disjointness_visitor, + relation_visitor, + ) }), (Type::PropertyInstance(_), other) | (other, Type::PropertyInstance(_)) => { KnownClass::Property.to_instance(db).is_disjoint_from_impl( db, other, + inferable, disjointness_visitor, relation_visitor, ) } (Type::BoundSuper(_), Type::BoundSuper(_)) => { - self.when_equivalent_to(db, other).negate(db) + self.when_equivalent_to(db, other, inferable).negate(db) + } + (Type::BoundSuper(_), other) | (other, Type::BoundSuper(_)) => { + KnownClass::Super.to_instance(db).is_disjoint_from_impl( + db, + other, + inferable, + disjointness_visitor, + relation_visitor, + ) } - (Type::BoundSuper(_), other) | (other, Type::BoundSuper(_)) => KnownClass::Super - .to_instance(db) - .is_disjoint_from_impl(db, other, disjointness_visitor, relation_visitor), } } @@ -9754,6 +9955,7 @@ impl<'db> BoundMethodType<'db> { self, db: &'db dyn Db, other: Self, + inferable: Option>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -9763,11 +9965,19 @@ impl<'db> BoundMethodType<'db> { // differently), and of the bound self parameter (taking care that parameters, including a // bound self parameter, are contravariant.) self.function(db) - .has_relation_to_impl(db, other.function(db), relation, relation_visitor) + .has_relation_to_impl( + db, + other.function(db), + inferable, + relation, + relation_visitor, + disjointness_visitor, + ) .and(db, || { other.self_instance(db).has_relation_to_impl( db, self.self_instance(db), + inferable, relation, relation_visitor, disjointness_visitor, @@ -9779,14 +9989,18 @@ impl<'db> BoundMethodType<'db> { self, db: &'db dyn Db, other: Self, + inferable: Option>, visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { self.function(db) - .is_equivalent_to_impl(db, other.function(db), visitor) + .is_equivalent_to_impl(db, other.function(db), inferable, visitor) .and(db, || { - other - .self_instance(db) - .is_equivalent_to_impl(db, self.self_instance(db), visitor) + other.self_instance(db).is_equivalent_to_impl( + db, + self.self_instance(db), + inferable, + visitor, + ) }) } } @@ -9908,6 +10122,7 @@ impl<'db> CallableType<'db> { self, db: &'db dyn Db, other: Self, + inferable: Option>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -9918,6 +10133,7 @@ impl<'db> CallableType<'db> { self.signatures(db).has_relation_to_impl( db, other.signatures(db), + inferable, relation, relation_visitor, disjointness_visitor, @@ -9931,6 +10147,7 @@ impl<'db> CallableType<'db> { self, db: &'db dyn Db, other: Self, + inferable: Option>, visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { if self == other { @@ -9939,7 +10156,7 @@ impl<'db> CallableType<'db> { ConstraintSet::from(self.is_function_like(db) == other.is_function_like(db)).and(db, || { self.signatures(db) - .is_equivalent_to_impl(db, other.signatures(db), visitor) + .is_equivalent_to_impl(db, other.signatures(db), inferable, visitor) }) } } @@ -10000,19 +10217,35 @@ impl<'db> KnownBoundMethodType<'db> { self, db: &'db dyn Db, other: Self, + inferable: Option>, relation: TypeRelation, - visitor: &HasRelationToVisitor<'db>, + relation_visitor: &HasRelationToVisitor<'db>, + disjointness_visitor: &IsDisjointVisitor<'db>, ) -> ConstraintSet<'db> { match (self, other) { ( KnownBoundMethodType::FunctionTypeDunderGet(self_function), KnownBoundMethodType::FunctionTypeDunderGet(other_function), - ) => self_function.has_relation_to_impl(db, other_function, relation, visitor), + ) => self_function.has_relation_to_impl( + db, + other_function, + inferable, + relation, + relation_visitor, + disjointness_visitor, + ), ( KnownBoundMethodType::FunctionTypeDunderCall(self_function), KnownBoundMethodType::FunctionTypeDunderCall(other_function), - ) => self_function.has_relation_to_impl(db, other_function, relation, visitor), + ) => self_function.has_relation_to_impl( + db, + other_function, + inferable, + relation, + relation_visitor, + disjointness_visitor, + ), ( KnownBoundMethodType::PropertyDunderGet(self_property), @@ -10021,7 +10254,7 @@ impl<'db> KnownBoundMethodType<'db> { | ( KnownBoundMethodType::PropertyDunderSet(self_property), KnownBoundMethodType::PropertyDunderSet(other_property), - ) => self_property.when_equivalent_to(db, other_property), + ) => self_property.when_equivalent_to(db, other_property, inferable), (KnownBoundMethodType::StrStartswith(_), KnownBoundMethodType::StrStartswith(_)) => { ConstraintSet::from(self == other) @@ -10052,18 +10285,19 @@ impl<'db> KnownBoundMethodType<'db> { self, db: &'db dyn Db, other: Self, + inferable: Option>, visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { match (self, other) { ( KnownBoundMethodType::FunctionTypeDunderGet(self_function), KnownBoundMethodType::FunctionTypeDunderGet(other_function), - ) => self_function.is_equivalent_to_impl(db, other_function, visitor), + ) => self_function.is_equivalent_to_impl(db, other_function, inferable, visitor), ( KnownBoundMethodType::FunctionTypeDunderCall(self_function), KnownBoundMethodType::FunctionTypeDunderCall(other_function), - ) => self_function.is_equivalent_to_impl(db, other_function, visitor), + ) => self_function.is_equivalent_to_impl(db, other_function, inferable, visitor), ( KnownBoundMethodType::PropertyDunderGet(self_property), @@ -10072,7 +10306,7 @@ impl<'db> KnownBoundMethodType<'db> { | ( KnownBoundMethodType::PropertyDunderSet(self_property), KnownBoundMethodType::PropertyDunderSet(other_property), - ) => self_property.is_equivalent_to_impl(db, other_property, visitor), + ) => self_property.is_equivalent_to_impl(db, other_property, inferable, visitor), (KnownBoundMethodType::StrStartswith(_), KnownBoundMethodType::StrStartswith(_)) => { ConstraintSet::from(self == other) @@ -10917,6 +11151,7 @@ impl<'db> UnionType<'db> { self, db: &'db dyn Db, other: Self, + _inferable: Option>, _visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { if self == other { @@ -11018,6 +11253,7 @@ impl<'db> IntersectionType<'db> { self, db: &'db dyn Db, other: Self, + _inferable: Option>, _visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { if self == other { diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index 7854982a7301d3..9a4556ae18f275 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -597,7 +597,7 @@ impl<'db> Bindings<'db> { Type::FunctionLiteral(function_type) => match function_type.known(db) { Some(KnownFunction::IsEquivalentTo) => { if let [Some(ty_a), Some(ty_b)] = overload.parameter_types() { - let constraints = ty_a.when_equivalent_to(db, *ty_b); + let constraints = ty_a.when_equivalent_to(db, *ty_b, None); let tracked = TrackedConstraintSet::new(db, constraints); overload.set_return_type(Type::KnownInstance( KnownInstanceType::ConstraintSet(tracked), @@ -607,7 +607,7 @@ impl<'db> Bindings<'db> { Some(KnownFunction::IsSubtypeOf) => { if let [Some(ty_a), Some(ty_b)] = overload.parameter_types() { - let constraints = ty_a.when_subtype_of(db, *ty_b); + let constraints = ty_a.when_subtype_of(db, *ty_b, None); let tracked = TrackedConstraintSet::new(db, constraints); overload.set_return_type(Type::KnownInstance( KnownInstanceType::ConstraintSet(tracked), @@ -617,7 +617,7 @@ impl<'db> Bindings<'db> { Some(KnownFunction::IsAssignableTo) => { if let [Some(ty_a), Some(ty_b)] = overload.parameter_types() { - let constraints = ty_a.when_assignable_to(db, *ty_b); + let constraints = ty_a.when_assignable_to(db, *ty_b, None); let tracked = TrackedConstraintSet::new(db, constraints); overload.set_return_type(Type::KnownInstance( KnownInstanceType::ConstraintSet(tracked), @@ -627,7 +627,7 @@ impl<'db> Bindings<'db> { Some(KnownFunction::IsDisjointFrom) => { if let [Some(ty_a), Some(ty_b)] = overload.parameter_types() { - let constraints = ty_a.when_disjoint_from(db, *ty_b); + let constraints = ty_a.when_disjoint_from(db, *ty_b, None); let tracked = TrackedConstraintSet::new(db, constraints); overload.set_return_type(Type::KnownInstance( KnownInstanceType::ConstraintSet(tracked), @@ -2590,7 +2590,12 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> { // constraint set that we get from this assignability check, instead of inferring and // building them in an earlier separate step. if argument_type - .when_assignable_to(self.db, expected_ty) + .when_assignable_to( + self.db, + expected_ty, + self.specialization + .map(|specialization| specialization.generic_context(self.db)), + ) .is_never_satisfied() { let positional = matches!(argument, Argument::Positional | Argument::Synthetic) diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index 8136f931846859..dcab9b8c6724db 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -534,17 +534,19 @@ impl<'db> ClassType<'db> { /// Return `true` if `other` is present in this class's MRO. pub(super) fn is_subclass_of(self, db: &'db dyn Db, other: ClassType<'db>) -> bool { - self.when_subclass_of(db, other).is_always_satisfied() + self.when_subclass_of(db, other, None).is_always_satisfied() } pub(super) fn when_subclass_of( self, db: &'db dyn Db, other: ClassType<'db>, + inferable: Option>, ) -> ConstraintSet<'db> { self.has_relation_to_impl( db, other, + inferable, TypeRelation::Subtyping, &HasRelationToVisitor::default(), &IsDisjointVisitor::default(), @@ -555,6 +557,7 @@ impl<'db> ClassType<'db> { self, db: &'db dyn Db, other: Self, + inferable: Option>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -580,6 +583,7 @@ impl<'db> ClassType<'db> { base.specialization(db).has_relation_to_impl( db, other.specialization(db), + inferable, relation, relation_visitor, disjointness_visitor, @@ -604,6 +608,7 @@ impl<'db> ClassType<'db> { self, db: &'db dyn Db, other: ClassType<'db>, + inferable: Option>, visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { if self == other { @@ -622,6 +627,7 @@ impl<'db> ClassType<'db> { this.specialization(db).is_equivalent_to_impl( db, other.specialization(db), + inferable, visitor, ) }) diff --git a/crates/ty_python_semantic/src/types/function.rs b/crates/ty_python_semantic/src/types/function.rs index ecfa017f6e9355..b840fe8fede032 100644 --- a/crates/ty_python_semantic/src/types/function.rs +++ b/crates/ty_python_semantic/src/types/function.rs @@ -81,9 +81,9 @@ use crate::types::visitor::any_over_type; use crate::types::{ ApplyTypeMappingVisitor, BoundMethodType, BoundTypeVarInstance, CallableType, ClassBase, ClassLiteral, ClassType, DeprecatedInstance, DynamicType, FindLegacyTypeVarsVisitor, - HasRelationToVisitor, IsEquivalentVisitor, KnownClass, KnownInstanceType, NormalizedVisitor, - SpecialFormType, TrackedConstraintSet, Truthiness, Type, TypeContext, TypeMapping, - TypeRelation, UnionBuilder, binding_type, todo_type, walk_signature, + HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, KnownClass, KnownInstanceType, + NormalizedVisitor, SpecialFormType, TrackedConstraintSet, Truthiness, Type, TypeContext, + TypeMapping, TypeRelation, UnionBuilder, binding_type, todo_type, walk_signature, }; use crate::{Db, FxOrderSet, ModuleName, resolve_module}; @@ -895,8 +895,10 @@ impl<'db> FunctionType<'db> { self, db: &'db dyn Db, other: Self, + inferable: Option>, relation: TypeRelation, - visitor: &HasRelationToVisitor<'db>, + relation_visitor: &HasRelationToVisitor<'db>, + disjointness_visitor: &IsDisjointVisitor<'db>, ) -> ConstraintSet<'db> { // A function type is the subtype of itself, and not of any other function type. However, // our representation of a function type includes any specialization that should be applied @@ -914,13 +916,21 @@ impl<'db> FunctionType<'db> { let self_signature = self.signature(db); let other_signature = other.signature(db); - self_signature.has_relation_to_impl(db, other_signature, relation, visitor) + self_signature.has_relation_to_impl( + db, + other_signature, + inferable, + relation, + relation_visitor, + disjointness_visitor, + ) } pub(crate) fn is_equivalent_to_impl( self, db: &'db dyn Db, other: Self, + inferable: Option>, visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { if self.normalized(db) == other.normalized(db) { @@ -931,7 +941,7 @@ impl<'db> FunctionType<'db> { } let self_signature = self.signature(db); let other_signature = other.signature(db); - self_signature.is_equivalent_to_impl(db, other_signature, visitor) + self_signature.is_equivalent_to_impl(db, other_signature, inferable, visitor) } pub(crate) fn find_legacy_typevars_impl( diff --git a/crates/ty_python_semantic/src/types/generics.rs b/crates/ty_python_semantic/src/types/generics.rs index eaf1c73f434cd2..61f5ca29575b5e 100644 --- a/crates/ty_python_semantic/src/types/generics.rs +++ b/crates/ty_python_semantic/src/types/generics.rs @@ -554,6 +554,7 @@ fn is_subtype_in_invariant_position<'db>( derived_materialization: MaterializationKind, base_type: &Type<'db>, base_materialization: MaterializationKind, + inferable: Option>, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, ) -> ConstraintSet<'db> { @@ -578,6 +579,7 @@ fn is_subtype_in_invariant_position<'db>( derived.has_relation_to_impl( db, base, + inferable, TypeRelation::Subtyping, relation_visitor, disjointness_visitor, @@ -630,6 +632,7 @@ fn has_relation_in_invariant_position<'db>( derived_materialization: Option, base_type: &Type<'db>, base_materialization: Option, + inferable: Option>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -643,6 +646,7 @@ fn has_relation_in_invariant_position<'db>( derived_mat, base_type, base_mat, + inferable, relation_visitor, disjointness_visitor, ), @@ -662,6 +666,7 @@ fn has_relation_in_invariant_position<'db>( .has_relation_to_impl( db, *base_type, + inferable, relation, relation_visitor, disjointness_visitor, @@ -670,6 +675,7 @@ fn has_relation_in_invariant_position<'db>( base_type.has_relation_to_impl( db, *derived_type, + inferable, relation, relation_visitor, disjointness_visitor, @@ -683,6 +689,7 @@ fn has_relation_in_invariant_position<'db>( MaterializationKind::Top, base_type, base_mat, + inferable, relation_visitor, disjointness_visitor, ) @@ -694,6 +701,7 @@ fn has_relation_in_invariant_position<'db>( derived_mat, base_type, MaterializationKind::Bottom, + inferable, relation_visitor, disjointness_visitor, ) @@ -705,6 +713,7 @@ fn has_relation_in_invariant_position<'db>( MaterializationKind::Bottom, base_type, base_mat, + inferable, relation_visitor, disjointness_visitor, ), @@ -714,6 +723,7 @@ fn has_relation_in_invariant_position<'db>( derived_mat, base_type, MaterializationKind::Top, + inferable, relation_visitor, disjointness_visitor, ), @@ -957,6 +967,7 @@ impl<'db> Specialization<'db> { self, db: &'db dyn Db, other: Self, + inferable: Option>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -971,6 +982,7 @@ impl<'db> Specialization<'db> { return self_tuple.has_relation_to_impl( db, other_tuple, + inferable, relation, relation_visitor, disjointness_visitor, @@ -998,6 +1010,7 @@ impl<'db> Specialization<'db> { self_materialization_kind, other_type, other_materialization_kind, + inferable, relation, relation_visitor, disjointness_visitor, @@ -1005,6 +1018,7 @@ impl<'db> Specialization<'db> { TypeVarVariance::Covariant => self_type.has_relation_to_impl( db, *other_type, + inferable, relation, relation_visitor, disjointness_visitor, @@ -1012,6 +1026,7 @@ impl<'db> Specialization<'db> { TypeVarVariance::Contravariant => other_type.has_relation_to_impl( db, *self_type, + inferable, relation, relation_visitor, disjointness_visitor, @@ -1030,6 +1045,7 @@ impl<'db> Specialization<'db> { self, db: &'db dyn Db, other: Specialization<'db>, + inferable: Option>, visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { if self.materialization_kind(db) != other.materialization_kind(db) { @@ -1055,7 +1071,7 @@ impl<'db> Specialization<'db> { TypeVarVariance::Invariant | TypeVarVariance::Covariant | TypeVarVariance::Contravariant => { - self_type.is_equivalent_to_impl(db, *other_type, visitor) + self_type.is_equivalent_to_impl(db, *other_type, inferable, visitor) } TypeVarVariance::Bivariant => ConstraintSet::from(true), }; @@ -1068,7 +1084,8 @@ impl<'db> Specialization<'db> { (Some(_), None) | (None, Some(_)) => return ConstraintSet::from(false), (None, None) => {} (Some(self_tuple), Some(other_tuple)) => { - let compatible = self_tuple.is_equivalent_to_impl(db, other_tuple, visitor); + let compatible = + self_tuple.is_equivalent_to_impl(db, other_tuple, inferable, visitor); if result.intersect(db, compatible).is_never_satisfied() { return result; } diff --git a/crates/ty_python_semantic/src/types/instance.rs b/crates/ty_python_semantic/src/types/instance.rs index 7d7998530e3a4a..1b2fefb5db2133 100644 --- a/crates/ty_python_semantic/src/types/instance.rs +++ b/crates/ty_python_semantic/src/types/instance.rs @@ -9,7 +9,7 @@ use crate::place::PlaceAndQualifiers; use crate::semantic_index::definition::Definition; use crate::types::constraints::{ConstraintSet, IteratorConstraintsExtension}; use crate::types::enums::is_single_member_enum; -use crate::types::generics::walk_specialization; +use crate::types::generics::{GenericContext, walk_specialization}; use crate::types::protocol_class::walk_protocol_interface; use crate::types::tuple::{TupleSpec, TupleType}; use crate::types::{ @@ -121,6 +121,7 @@ impl<'db> Type<'db> { self, db: &'db dyn Db, protocol: ProtocolInstanceType<'db>, + inferable: Option>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -129,6 +130,7 @@ impl<'db> Type<'db> { self_protocol.interface(db).has_relation_to_impl( db, protocol.interface(db), + inferable, relation, relation_visitor, disjointness_visitor, @@ -142,6 +144,7 @@ impl<'db> Type<'db> { member.is_satisfied_by( db, self, + inferable, relation, relation_visitor, disjointness_visitor, @@ -174,6 +177,7 @@ impl<'db> Type<'db> { type_to_test.has_relation_to_impl( db, Type::non_tuple_instance(db, class), + inferable, relation, relation_visitor, disjointness_visitor, @@ -364,6 +368,7 @@ impl<'db> NominalInstanceType<'db> { self, db: &'db dyn Db, other: Self, + inferable: Option>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -376,6 +381,7 @@ impl<'db> NominalInstanceType<'db> { ) => tuple1.has_relation_to_impl( db, tuple2, + inferable, relation, relation_visitor, disjointness_visitor, @@ -383,6 +389,7 @@ impl<'db> NominalInstanceType<'db> { _ => self.class(db).has_relation_to_impl( db, other.class(db), + inferable, relation, relation_visitor, disjointness_visitor, @@ -394,18 +401,19 @@ impl<'db> NominalInstanceType<'db> { self, db: &'db dyn Db, other: Self, + inferable: Option>, visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { match (self.0, other.0) { ( NominalInstanceInner::ExactTuple(tuple1), NominalInstanceInner::ExactTuple(tuple2), - ) => tuple1.is_equivalent_to_impl(db, tuple2, visitor), + ) => tuple1.is_equivalent_to_impl(db, tuple2, inferable, visitor), (NominalInstanceInner::Object, NominalInstanceInner::Object) => { ConstraintSet::from(true) } (NominalInstanceInner::NonTuple(class1), NominalInstanceInner::NonTuple(class2)) => { - class1.is_equivalent_to_impl(db, class2, visitor) + class1.is_equivalent_to_impl(db, class2, inferable, visitor) } _ => ConstraintSet::from(false), } @@ -415,6 +423,7 @@ impl<'db> NominalInstanceType<'db> { self, db: &'db dyn Db, other: Self, + inferable: Option>, disjointness_visitor: &IsDisjointVisitor<'db>, relation_visitor: &HasRelationToVisitor<'db>, ) -> ConstraintSet<'db> { @@ -427,6 +436,7 @@ impl<'db> NominalInstanceType<'db> { let compatible = self_spec.is_disjoint_from_impl( db, &other_spec, + inferable, disjointness_visitor, relation_visitor, ); @@ -640,6 +650,7 @@ impl<'db> ProtocolInstanceType<'db> { .satisfies_protocol( db, protocol, + None, TypeRelation::Subtyping, &HasRelationToVisitor::default(), &IsDisjointVisitor::default(), @@ -698,6 +709,7 @@ impl<'db> ProtocolInstanceType<'db> { self, db: &'db dyn Db, other: Self, + _inferable: Option>, _visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { if self == other { @@ -719,6 +731,7 @@ impl<'db> ProtocolInstanceType<'db> { self, _db: &'db dyn Db, _other: Self, + _inferable: Option>, _visitor: &IsDisjointVisitor<'db>, ) -> ConstraintSet<'db> { ConstraintSet::from(false) diff --git a/crates/ty_python_semantic/src/types/protocol_class.rs b/crates/ty_python_semantic/src/types/protocol_class.rs index 529824b1eab29a..660236c3e15b5a 100644 --- a/crates/ty_python_semantic/src/types/protocol_class.rs +++ b/crates/ty_python_semantic/src/types/protocol_class.rs @@ -22,6 +22,7 @@ use crate::{ constraints::{ConstraintSet, IteratorConstraintsExtension, OptionConstraintsExtension}, context::InferContext, diagnostic::report_undeclared_protocol_member, + generics::GenericContext, signatures::{Parameter, Parameters}, todo_type, }, @@ -235,6 +236,7 @@ impl<'db> ProtocolInterface<'db> { self, db: &'db dyn Db, other: Self, + inferable: Option>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -276,6 +278,7 @@ impl<'db> ProtocolInterface<'db> { our_type.has_relation_to_impl( db, Type::Callable(other_type.bind_self(db)), + inferable, relation, relation_visitor, disjointness_visitor, @@ -288,6 +291,7 @@ impl<'db> ProtocolInterface<'db> { ) => our_method.bind_self(db).has_relation_to_impl( db, other_method.bind_self(db), + inferable, relation, relation_visitor, disjointness_visitor, @@ -300,6 +304,7 @@ impl<'db> ProtocolInterface<'db> { .has_relation_to_impl( db, other_type, + inferable, relation, relation_visitor, disjointness_visitor, @@ -308,6 +313,7 @@ impl<'db> ProtocolInterface<'db> { other_type.has_relation_to_impl( db, our_type, + inferable, relation, relation_visitor, disjointness_visitor, @@ -605,6 +611,7 @@ impl<'a, 'db> ProtocolMember<'a, 'db> { &self, db: &'db dyn Db, other: Type<'db>, + inferable: Option>, disjointness_visitor: &IsDisjointVisitor<'db>, relation_visitor: &HasRelationToVisitor<'db>, ) -> ConstraintSet<'db> { @@ -613,9 +620,13 @@ impl<'a, 'db> ProtocolMember<'a, 'db> { ProtocolMemberKind::Property(_) | ProtocolMemberKind::Method(_) => { ConstraintSet::from(false) } - ProtocolMemberKind::Other(ty) => { - ty.is_disjoint_from_impl(db, other, disjointness_visitor, relation_visitor) - } + ProtocolMemberKind::Other(ty) => ty.is_disjoint_from_impl( + db, + other, + inferable, + disjointness_visitor, + relation_visitor, + ), } } @@ -625,6 +636,7 @@ impl<'a, 'db> ProtocolMember<'a, 'db> { &self, db: &'db dyn Db, other: Type<'db>, + inferable: Option>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -664,6 +676,7 @@ impl<'a, 'db> ProtocolMember<'a, 'db> { attribute_type.has_relation_to_impl( db, Type::Callable(method.bind_self(db)), + inferable, relation, relation_visitor, disjointness_visitor, @@ -684,6 +697,7 @@ impl<'a, 'db> ProtocolMember<'a, 'db> { .has_relation_to_impl( db, attribute_type, + inferable, relation, relation_visitor, disjointness_visitor, @@ -692,6 +706,7 @@ impl<'a, 'db> ProtocolMember<'a, 'db> { attribute_type.has_relation_to_impl( db, *member_type, + inferable, relation, relation_visitor, disjointness_visitor, diff --git a/crates/ty_python_semantic/src/types/signatures.rs b/crates/ty_python_semantic/src/types/signatures.rs index b9b1cba6393f3e..41e04a2f43e8d7 100644 --- a/crates/ty_python_semantic/src/types/signatures.rs +++ b/crates/ty_python_semantic/src/types/signatures.rs @@ -173,10 +173,16 @@ impl<'db> CallableSignature<'db> { } } - fn is_subtype_of_impl(&self, db: &'db dyn Db, other: &Self) -> ConstraintSet<'db> { + fn is_subtype_of_impl( + &self, + db: &'db dyn Db, + other: &Self, + inferable: Option>, + ) -> ConstraintSet<'db> { self.has_relation_to_impl( db, other, + inferable, TypeRelation::Subtyping, &HasRelationToVisitor::default(), &IsDisjointVisitor::default(), @@ -187,6 +193,7 @@ impl<'db> CallableSignature<'db> { &self, db: &'db dyn Db, other: &Self, + inferable: Option>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -195,6 +202,7 @@ impl<'db> CallableSignature<'db> { db, &self.overloads, &other.overloads, + inferable, relation, relation_visitor, disjointness_visitor, @@ -207,6 +215,7 @@ impl<'db> CallableSignature<'db> { db: &'db dyn Db, self_signatures: &[Signature<'db>], other_signatures: &[Signature<'db>], + inferable: Option>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -217,6 +226,7 @@ impl<'db> CallableSignature<'db> { self_signature.has_relation_to_impl( db, other_signature, + inferable, relation, relation_visitor, disjointness_visitor, @@ -229,6 +239,7 @@ impl<'db> CallableSignature<'db> { db, std::slice::from_ref(self_signature), other_signatures, + inferable, relation, relation_visitor, disjointness_visitor, @@ -241,6 +252,7 @@ impl<'db> CallableSignature<'db> { db, self_signatures, std::slice::from_ref(other_signature), + inferable, relation, relation_visitor, disjointness_visitor, @@ -253,6 +265,7 @@ impl<'db> CallableSignature<'db> { db, self_signatures, std::slice::from_ref(other_signature), + inferable, relation, relation_visitor, disjointness_visitor, @@ -268,20 +281,21 @@ impl<'db> CallableSignature<'db> { &self, db: &'db dyn Db, other: &Self, + inferable: Option>, visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { match (self.overloads.as_slice(), other.overloads.as_slice()) { ([self_signature], [other_signature]) => { // Common case: both callable types contain a single signature, use the custom // equivalence check instead of delegating it to the subtype check. - self_signature.is_equivalent_to_impl(db, other_signature, visitor) + self_signature.is_equivalent_to_impl(db, other_signature, inferable, visitor) } (_, _) => { if self == other { return ConstraintSet::from(true); } - self.is_subtype_of_impl(db, other) - .and(db, || other.is_subtype_of_impl(db, self)) + self.is_subtype_of_impl(db, other, inferable) + .and(db, || other.is_subtype_of_impl(db, self, inferable)) } } } @@ -590,6 +604,7 @@ impl<'db> Signature<'db> { &self, db: &'db dyn Db, other: &Signature<'db>, + inferable: Option>, visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { let mut result = ConstraintSet::from(true); @@ -597,7 +612,10 @@ impl<'db> Signature<'db> { let self_type = self_type.unwrap_or(Type::unknown()); let other_type = other_type.unwrap_or(Type::unknown()); !result - .intersect(db, self_type.is_equivalent_to_impl(db, other_type, visitor)) + .intersect( + db, + self_type.is_equivalent_to_impl(db, other_type, inferable, visitor), + ) .is_never_satisfied() }; @@ -673,6 +691,7 @@ impl<'db> Signature<'db> { &self, db: &'db dyn Db, other: &Signature<'db>, + inferable: Option>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -748,6 +767,7 @@ impl<'db> Signature<'db> { type1.has_relation_to_impl( db, type2, + inferable, relation, relation_visitor, disjointness_visitor, diff --git a/crates/ty_python_semantic/src/types/subclass_of.rs b/crates/ty_python_semantic/src/types/subclass_of.rs index c6a16620a94c3d..2f98ff8c473027 100644 --- a/crates/ty_python_semantic/src/types/subclass_of.rs +++ b/crates/ty_python_semantic/src/types/subclass_of.rs @@ -1,6 +1,7 @@ use crate::place::PlaceAndQualifiers; use crate::semantic_index::definition::Definition; use crate::types::constraints::ConstraintSet; +use crate::types::generics::GenericContext; use crate::types::variance::VarianceInferable; use crate::types::{ ApplyTypeMappingVisitor, BoundTypeVarInstance, ClassType, DynamicType, @@ -135,6 +136,7 @@ impl<'db> SubclassOfType<'db> { self, db: &'db dyn Db, other: SubclassOfType<'db>, + inferable: Option>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -157,6 +159,7 @@ impl<'db> SubclassOfType<'db> { .has_relation_to_impl( db, other_class, + inferable, relation, relation_visitor, disjointness_visitor, @@ -171,6 +174,7 @@ impl<'db> SubclassOfType<'db> { self, db: &'db dyn Db, other: Self, + _inferable: Option>, _visitor: &IsDisjointVisitor<'db>, ) -> ConstraintSet<'db> { match (self.subclass_of, other.subclass_of) { diff --git a/crates/ty_python_semantic/src/types/tuple.rs b/crates/ty_python_semantic/src/types/tuple.rs index a94a0610e72569..00826132eaa8ee 100644 --- a/crates/ty_python_semantic/src/types/tuple.rs +++ b/crates/ty_python_semantic/src/types/tuple.rs @@ -24,6 +24,7 @@ use itertools::{Either, EitherOrBoth, Itertools}; use crate::semantic_index::definition::Definition; use crate::types::class::{ClassType, KnownClass}; use crate::types::constraints::{ConstraintSet, IteratorConstraintsExtension}; +use crate::types::generics::GenericContext; use crate::types::{ ApplyTypeMappingVisitor, BoundTypeVarInstance, FindLegacyTypeVarsVisitor, HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, NormalizedVisitor, Type, TypeMapping, TypeRelation, @@ -258,6 +259,7 @@ impl<'db> TupleType<'db> { self, db: &'db dyn Db, other: Self, + inferable: Option>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -265,6 +267,7 @@ impl<'db> TupleType<'db> { self.tuple(db).has_relation_to_impl( db, other.tuple(db), + inferable, relation, relation_visitor, disjointness_visitor, @@ -275,10 +278,11 @@ impl<'db> TupleType<'db> { self, db: &'db dyn Db, other: Self, + inferable: Option>, visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { self.tuple(db) - .is_equivalent_to_impl(db, other.tuple(db), visitor) + .is_equivalent_to_impl(db, other.tuple(db), inferable, visitor) } pub(crate) fn is_single_valued(self, db: &'db dyn Db) -> bool { @@ -442,6 +446,7 @@ impl<'db> FixedLengthTuple> { &self, db: &'db dyn Db, other: &Tuple>, + inferable: Option>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -453,6 +458,7 @@ impl<'db> FixedLengthTuple> { self_ty.has_relation_to_impl( db, *other_ty, + inferable, relation, relation_visitor, disjointness_visitor, @@ -473,6 +479,7 @@ impl<'db> FixedLengthTuple> { let element_constraints = self_ty.has_relation_to_impl( db, *other_ty, + inferable, relation, relation_visitor, disjointness_visitor, @@ -491,6 +498,7 @@ impl<'db> FixedLengthTuple> { let element_constraints = self_ty.has_relation_to_impl( db, *other_ty, + inferable, relation, relation_visitor, disjointness_visitor, @@ -510,6 +518,7 @@ impl<'db> FixedLengthTuple> { self_ty.has_relation_to_impl( db, other.variable, + inferable, relation, relation_visitor, disjointness_visitor, @@ -524,13 +533,14 @@ impl<'db> FixedLengthTuple> { &self, db: &'db dyn Db, other: &Self, + inferable: Option>, visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { ConstraintSet::from(self.0.len() == other.0.len()).and(db, || { (self.0.iter()) .zip(&other.0) .when_all(db, |(self_ty, other_ty)| { - self_ty.is_equivalent_to_impl(db, *other_ty, visitor) + self_ty.is_equivalent_to_impl(db, *other_ty, inferable, visitor) }) }) } @@ -793,6 +803,7 @@ impl<'db> VariableLengthTuple> { &self, db: &'db dyn Db, other: &Tuple>, + inferable: Option>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -825,6 +836,7 @@ impl<'db> VariableLengthTuple> { let element_constraints = self_ty.has_relation_to_impl( db, other_ty, + inferable, relation, relation_visitor, disjointness_visitor, @@ -844,6 +856,7 @@ impl<'db> VariableLengthTuple> { let element_constraints = self_ty.has_relation_to_impl( db, other_ty, + inferable, relation, relation_visitor, disjointness_visitor, @@ -884,6 +897,7 @@ impl<'db> VariableLengthTuple> { EitherOrBoth::Both(self_ty, other_ty) => self_ty.has_relation_to_impl( db, other_ty, + inferable, relation, relation_visitor, disjointness_visitor, @@ -891,6 +905,7 @@ impl<'db> VariableLengthTuple> { EitherOrBoth::Left(self_ty) => self_ty.has_relation_to_impl( db, other.variable, + inferable, relation, relation_visitor, disjointness_visitor, @@ -918,6 +933,7 @@ impl<'db> VariableLengthTuple> { EitherOrBoth::Both(self_ty, other_ty) => self_ty.has_relation_to_impl( db, *other_ty, + inferable, relation, relation_visitor, disjointness_visitor, @@ -925,6 +941,7 @@ impl<'db> VariableLengthTuple> { EitherOrBoth::Left(self_ty) => self_ty.has_relation_to_impl( db, other.variable, + inferable, relation, relation_visitor, disjointness_visitor, @@ -945,6 +962,7 @@ impl<'db> VariableLengthTuple> { self.variable.has_relation_to_impl( db, other.variable, + inferable, relation, relation_visitor, disjointness_visitor, @@ -958,16 +976,17 @@ impl<'db> VariableLengthTuple> { &self, db: &'db dyn Db, other: &Self, + inferable: Option>, visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { self.variable - .is_equivalent_to_impl(db, other.variable, visitor) + .is_equivalent_to_impl(db, other.variable, inferable, visitor) .and(db, || { (self.prenormalized_prefix_elements(db, None)) .zip_longest(other.prenormalized_prefix_elements(db, None)) .when_all(db, |pair| match pair { EitherOrBoth::Both(self_ty, other_ty) => { - self_ty.is_equivalent_to_impl(db, other_ty, visitor) + self_ty.is_equivalent_to_impl(db, other_ty, inferable, visitor) } EitherOrBoth::Left(_) | EitherOrBoth::Right(_) => { ConstraintSet::from(false) @@ -979,7 +998,7 @@ impl<'db> VariableLengthTuple> { .zip_longest(other.prenormalized_suffix_elements(db, None)) .when_all(db, |pair| match pair { EitherOrBoth::Both(self_ty, other_ty) => { - self_ty.is_equivalent_to_impl(db, other_ty, visitor) + self_ty.is_equivalent_to_impl(db, other_ty, inferable, visitor) } EitherOrBoth::Left(_) | EitherOrBoth::Right(_) => { ConstraintSet::from(false) @@ -1170,6 +1189,7 @@ impl<'db> Tuple> { &self, db: &'db dyn Db, other: &Self, + inferable: Option>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -1178,6 +1198,7 @@ impl<'db> Tuple> { Tuple::Fixed(self_tuple) => self_tuple.has_relation_to_impl( db, other, + inferable, relation, relation_visitor, disjointness_visitor, @@ -1185,6 +1206,7 @@ impl<'db> Tuple> { Tuple::Variable(self_tuple) => self_tuple.has_relation_to_impl( db, other, + inferable, relation, relation_visitor, disjointness_visitor, @@ -1196,14 +1218,15 @@ impl<'db> Tuple> { &self, db: &'db dyn Db, other: &Self, + inferable: Option>, visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { match (self, other) { (Tuple::Fixed(self_tuple), Tuple::Fixed(other_tuple)) => { - self_tuple.is_equivalent_to_impl(db, other_tuple, visitor) + self_tuple.is_equivalent_to_impl(db, other_tuple, inferable, visitor) } (Tuple::Variable(self_tuple), Tuple::Variable(other_tuple)) => { - self_tuple.is_equivalent_to_impl(db, other_tuple, visitor) + self_tuple.is_equivalent_to_impl(db, other_tuple, inferable, visitor) } (Tuple::Fixed(_), Tuple::Variable(_)) | (Tuple::Variable(_), Tuple::Fixed(_)) => { ConstraintSet::from(false) @@ -1215,6 +1238,7 @@ impl<'db> Tuple> { &self, db: &'db dyn Db, other: &Self, + inferable: Option>, disjointness_visitor: &IsDisjointVisitor<'db>, relation_visitor: &HasRelationToVisitor<'db>, ) -> ConstraintSet<'db> { @@ -1234,6 +1258,7 @@ impl<'db> Tuple> { db: &'db dyn Db, a: impl IntoIterator>, b: impl IntoIterator>, + inferable: Option>, disjointness_visitor: &IsDisjointVisitor<'db>, relation_visitor: &HasRelationToVisitor<'db>, ) -> ConstraintSet<'db> @@ -1244,6 +1269,7 @@ impl<'db> Tuple> { self_element.is_disjoint_from_impl( db, *other_element, + inferable, disjointness_visitor, relation_visitor, ) @@ -1255,6 +1281,7 @@ impl<'db> Tuple> { db, self_tuple.elements(), other_tuple.elements(), + inferable, disjointness_visitor, relation_visitor, ), @@ -1266,6 +1293,7 @@ impl<'db> Tuple> { db, self_tuple.prefix_elements(), other_tuple.prefix_elements(), + inferable, disjointness_visitor, relation_visitor, ) @@ -1274,6 +1302,7 @@ impl<'db> Tuple> { db, self_tuple.suffix_elements().rev(), other_tuple.suffix_elements().rev(), + inferable, disjointness_visitor, relation_visitor, ) @@ -1284,6 +1313,7 @@ impl<'db> Tuple> { db, fixed.elements(), variable.prefix_elements(), + inferable, disjointness_visitor, relation_visitor, ) @@ -1292,6 +1322,7 @@ impl<'db> Tuple> { db, fixed.elements().rev(), variable.suffix_elements().rev(), + inferable, disjointness_visitor, relation_visitor, ) From c7b927009311f9f8f51cfe0ff30d29851c95fdb4 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Wed, 1 Oct 2025 15:54:43 -0400 Subject: [PATCH 03/38] it's a start --- crates/ty_ide/src/completion.rs | 2 +- crates/ty_ide/src/semantic_tokens.rs | 4 +- crates/ty_python_semantic/src/types.rs | 327 ++++-------------- .../ty_python_semantic/src/types/builder.rs | 3 +- crates/ty_python_semantic/src/types/class.rs | 11 +- .../src/types/class_base.rs | 1 - .../ty_python_semantic/src/types/display.rs | 4 +- .../ty_python_semantic/src/types/function.rs | 13 +- .../ty_python_semantic/src/types/generics.rs | 23 +- .../src/types/ide_support.rs | 1 - .../src/types/infer/builder.rs | 4 - crates/ty_python_semantic/src/types/narrow.rs | 18 +- .../src/types/signatures.rs | 32 +- .../src/types/type_ordering.rs | 4 - .../ty_python_semantic/src/types/visitor.rs | 7 - 15 files changed, 106 insertions(+), 348 deletions(-) diff --git a/crates/ty_ide/src/completion.rs b/crates/ty_ide/src/completion.rs index 2cec00791e48be..2fbe8aeb5b4f89 100644 --- a/crates/ty_ide/src/completion.rs +++ b/crates/ty_ide/src/completion.rs @@ -130,7 +130,7 @@ impl<'db> Completion<'db> { | Type::BytesLiteral(_) => CompletionKind::Value, Type::EnumLiteral(_) => CompletionKind::Enum, Type::ProtocolInstance(_) => CompletionKind::Interface, - Type::NonInferableTypeVar(_) | Type::TypeVar(_) => CompletionKind::TypeParameter, + Type::TypeVar(_) => CompletionKind::TypeParameter, Type::Union(union) => union .elements(db) .iter() diff --git a/crates/ty_ide/src/semantic_tokens.rs b/crates/ty_ide/src/semantic_tokens.rs index dccebf3c4b8e65..f5ea3798848573 100644 --- a/crates/ty_ide/src/semantic_tokens.rs +++ b/crates/ty_ide/src/semantic_tokens.rs @@ -336,9 +336,7 @@ impl<'db> SemanticTokenVisitor<'db> { match ty { Type::ClassLiteral(_) => (SemanticTokenType::Class, modifiers), - Type::NonInferableTypeVar(_) | Type::TypeVar(_) => { - (SemanticTokenType::TypeParameter, modifiers) - } + Type::TypeVar(_) => (SemanticTokenType::TypeParameter, modifiers), Type::FunctionLiteral(_) => { // Check if this is a method based on current scope if self.in_class_scope { diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index b2c38560179957..fd5dd5204ef922 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -757,15 +757,9 @@ pub enum Type<'db> { LiteralString, /// A bytes literal BytesLiteral(BytesLiteralType<'db>), - /// An instance of a typevar in a context where we can infer a specialization for it. (This is - /// typically the signature of a generic function, or of a constructor of a generic class.) - /// When the generic class or function binding this typevar is specialized, we will replace the - /// typevar with its specialization. + /// An instance of a typevar. When the generic class or function binding this typevar is + /// specialized, we will replace the typevar with its specialization. TypeVar(BoundTypeVarInstance<'db>), - /// An instance of a typevar where we cannot infer a specialization for it. (This is typically - /// the body of the generic function or class that binds the typevar.) In these positions, - /// properties like assignability must hold for all possible specializations. - NonInferableTypeVar(BoundTypeVarInstance<'db>), /// A bound super object like `super()` or `super(A, A())` /// This type doesn't handle an unbound super object like `super(A)`; for that we just use /// a `Type::NominalInstance` of `builtins.super`. @@ -1283,9 +1277,6 @@ impl<'db> Type<'db> { Type::TypeVar(bound_typevar) => visitor.visit(self, || { Type::TypeVar(bound_typevar.normalized_impl(db, visitor)) }), - Type::NonInferableTypeVar(bound_typevar) => visitor.visit(self, || { - Type::NonInferableTypeVar(bound_typevar.normalized_impl(db, visitor)) - }), Type::KnownInstance(known_instance) => visitor.visit(self, || { Type::KnownInstance(known_instance.normalized_impl(db, visitor)) }), @@ -1362,7 +1353,6 @@ impl<'db> Type<'db> { | Type::Union(_) | Type::Intersection(_) | Type::Callable(_) - | Type::NonInferableTypeVar(_) | Type::TypeVar(_) | Type::BoundSuper(_) | Type::TypeIs(_) @@ -1453,7 +1443,6 @@ impl<'db> Type<'db> { | Type::KnownInstance(_) | Type::PropertyInstance(_) | Type::Intersection(_) - | Type::NonInferableTypeVar(_) | Type::TypeVar(_) | Type::BoundSuper(_) => None, } @@ -1634,17 +1623,15 @@ impl<'db> Type<'db> { // However, there is one exception to this general rule: for any given typevar `T`, // `T` will always be a subtype of any union containing `T`. // A similar rule applies in reverse to intersection types. - (Type::NonInferableTypeVar(_), Type::Union(union)) - if union.elements(db).contains(&self) => - { + (Type::TypeVar(_), Type::Union(union)) if union.elements(db).contains(&self) => { ConstraintSet::from(true) } - (Type::Intersection(intersection), Type::NonInferableTypeVar(_)) + (Type::Intersection(intersection), Type::TypeVar(_)) if intersection.positive(db).contains(&target) => { ConstraintSet::from(true) } - (Type::Intersection(intersection), Type::NonInferableTypeVar(_)) + (Type::Intersection(intersection), Type::TypeVar(_)) if intersection.negative(db).contains(&target) => { ConstraintSet::from(false) @@ -1655,18 +1642,18 @@ impl<'db> Type<'db> { // // Note that this is not handled by the early return at the beginning of this method, // since subtyping between a TypeVar and an arbitrary other type cannot be guaranteed to be reflexive. - ( - Type::NonInferableTypeVar(lhs_bound_typevar), - Type::NonInferableTypeVar(rhs_bound_typevar), - ) if lhs_bound_typevar.is_identical_to(db, rhs_bound_typevar) => { + (Type::TypeVar(lhs_bound_typevar), Type::TypeVar(rhs_bound_typevar)) + if lhs_bound_typevar.is_identical_to(db, rhs_bound_typevar) => + { ConstraintSet::from(true) } // A fully static typevar is a subtype of its upper bound, and to something similar to // the union of its constraints. An unbound, unconstrained, fully static typevar has an // implicit upper bound of `object` (which is handled above). - (Type::NonInferableTypeVar(bound_typevar), _) - if bound_typevar.typevar(db).bound_or_constraints(db).is_some() => + (Type::TypeVar(bound_typevar), _) + if inferable.is_none_or(|inferable| !inferable.contains(db, bound_typevar)) + && bound_typevar.typevar(db).bound_or_constraints(db).is_some() => { match bound_typevar.typevar(db).bound_or_constraints(db) { None => unreachable!(), @@ -1697,23 +1684,24 @@ impl<'db> Type<'db> { // If the typevar is constrained, there must be multiple constraints, and the typevar // might be specialized to any one of them. However, the constraints do not have to be // disjoint, which means an lhs type might be a subtype of all of the constraints. - (_, Type::NonInferableTypeVar(bound_typevar)) - if !bound_typevar - .typevar(db) - .constraints(db) - .when_some_and(|constraints| { - constraints.iter().when_all(db, |constraint| { - self.has_relation_to_impl( - db, - *constraint, - inferable, - relation, - relation_visitor, - disjointness_visitor, - ) + (_, Type::TypeVar(bound_typevar)) + if inferable.is_none_or(|inferable| !inferable.contains(db, bound_typevar)) + && !bound_typevar + .typevar(db) + .constraints(db) + .when_some_and(|constraints| { + constraints.iter().when_all(db, |constraint| { + self.has_relation_to_impl( + db, + *constraint, + inferable, + relation, + relation_visitor, + disjointness_visitor, + ) + }) }) - }) - .is_never_satisfied() => + .is_never_satisfied() => { // TODO: The repetition here isn't great, but we really need the fallthrough logic, // where this arm only engages if it returns true (or in the world of constraints, @@ -1736,7 +1724,10 @@ impl<'db> Type<'db> { }) } - (Type::TypeVar(_), _) if relation.is_assignability() => { + (Type::TypeVar(bound_typevar), _) + if inferable.is_some_and(|inferable| inferable.contains(db, bound_typevar)) + && relation.is_assignability() => + { // The implicit lower bound of a typevar is `Never`, which means // that it is always assignable to any other type. @@ -1837,7 +1828,11 @@ impl<'db> Type<'db> { // (If the typevar is bounded, it might be specialized to a smaller type than the // bound. This is true even if the bound is a final class, since the typevar can still // be specialized to `Never`.) - (_, Type::NonInferableTypeVar(_)) => ConstraintSet::from(false), + (_, Type::TypeVar(bound_typevar)) + if inferable.is_none_or(|inferable| !inferable.contains(db, bound_typevar)) => + { + ConstraintSet::from(false) + } (_, Type::TypeVar(typevar)) if relation.is_assignability() @@ -2304,9 +2299,7 @@ impl<'db> Type<'db> { // Other than the special cases enumerated above, `Instance` types and typevars are // never subtypes of any other variants - (Type::NominalInstance(_) | Type::NonInferableTypeVar(_), _) => { - ConstraintSet::from(false) - } + (Type::NominalInstance(_), _) => ConstraintSet::from(false), } } @@ -2538,14 +2531,16 @@ impl<'db> Type<'db> { // be specialized to the same type. (This is an important difference between typevars // and `Any`!) Different typevars might be disjoint, depending on their bounds and // constraints, which are handled below. - ( - Type::NonInferableTypeVar(self_bound_typevar), - Type::NonInferableTypeVar(other_bound_typevar), - ) if self_bound_typevar == other_bound_typevar => ConstraintSet::from(false), + (Type::TypeVar(self_bound_typevar), Type::TypeVar(other_bound_typevar)) + if self_bound_typevar == other_bound_typevar => + { + ConstraintSet::from(false) + } - (tvar @ Type::NonInferableTypeVar(_), Type::Intersection(intersection)) - | (Type::Intersection(intersection), tvar @ Type::NonInferableTypeVar(_)) - if intersection.negative(db).contains(&tvar) => + (tvar @ Type::TypeVar(bound_typevar), Type::Intersection(intersection)) + | (Type::Intersection(intersection), tvar @ Type::TypeVar(bound_typevar)) + if inferable.is_none_or(|inferable| !inferable.contains(db, bound_typevar)) + && intersection.negative(db).contains(&tvar) => { ConstraintSet::from(true) } @@ -2554,8 +2549,9 @@ impl<'db> Type<'db> { // specialized to any type. A bounded typevar is not disjoint from its bound, and is // only disjoint from other types if its bound is. A constrained typevar is disjoint // from a type if all of its constraints are. - (Type::NonInferableTypeVar(bound_typevar), other) - | (other, Type::NonInferableTypeVar(bound_typevar)) => { + (Type::TypeVar(bound_typevar), other) | (other, Type::TypeVar(bound_typevar)) + if inferable.is_none_or(|inferable| !inferable.contains(db, bound_typevar)) => + { match bound_typevar.typevar(db).bound_or_constraints(db) { None => ConstraintSet::from(false), Some(TypeVarBoundOrConstraints::UpperBound(bound)) => bound @@ -3203,7 +3199,7 @@ impl<'db> Type<'db> { // the bound is a final singleton class, since it can still be specialized to `Never`. // A constrained typevar is a singleton if all of its constraints are singletons. (Note // that you cannot specialize a constrained typevar to a subtype of a constraint.) - Type::NonInferableTypeVar(bound_typevar) => { + Type::TypeVar(bound_typevar) => { match bound_typevar.typevar(db).bound_or_constraints(db) { None => false, Some(TypeVarBoundOrConstraints::UpperBound(_)) => false, @@ -3214,8 +3210,6 @@ impl<'db> Type<'db> { } } - Type::TypeVar(_) => false, - // We eagerly transform `SubclassOf` to `ClassLiteral` for final types, so `SubclassOf` is never a singleton. Type::SubclassOf(..) => false, Type::BoundSuper(..) => false, @@ -3314,7 +3308,7 @@ impl<'db> Type<'db> { // `Never`. A constrained typevar is single-valued if all of its constraints are // single-valued. (Note that you cannot specialize a constrained typevar to a subtype // of a constraint.) - Type::NonInferableTypeVar(bound_typevar) => { + Type::TypeVar(bound_typevar) => { match bound_typevar.typevar(db).bound_or_constraints(db) { None => false, Some(TypeVarBoundOrConstraints::UpperBound(_)) => false, @@ -3325,8 +3319,6 @@ impl<'db> Type<'db> { } } - Type::TypeVar(_) => false, - Type::SubclassOf(..) => { // TODO: Same comment as above for `is_singleton` false @@ -3491,7 +3483,6 @@ impl<'db> Type<'db> { | Type::LiteralString | Type::BytesLiteral(_) | Type::EnumLiteral(_) - | Type::NonInferableTypeVar(_) | Type::TypeVar(_) | Type::NominalInstance(_) | Type::ProtocolInstance(_) @@ -3602,7 +3593,7 @@ impl<'db> Type<'db> { Type::object().instance_member(db, name) } - Type::NonInferableTypeVar(bound_typevar) => { + Type::TypeVar(bound_typevar) => { match bound_typevar.typevar(db).bound_or_constraints(db) { None => Type::object().instance_member(db, name), Some(TypeVarBoundOrConstraints::UpperBound(bound)) => { @@ -3615,16 +3606,6 @@ impl<'db> Type<'db> { } } - Type::TypeVar(_) => { - debug_assert!( - false, - "should not be able to access instance member `{name}` \ - of type variable {} in inferable position", - self.display(db) - ); - Place::Unbound.into() - } - Type::IntLiteral(_) => KnownClass::Int.to_instance(db).instance_member(db, name), Type::BooleanLiteral(_) | Type::TypeIs(_) => { KnownClass::Bool.to_instance(db).instance_member(db, name) @@ -4192,7 +4173,6 @@ impl<'db> Type<'db> { | Type::BytesLiteral(..) | Type::EnumLiteral(..) | Type::LiteralString - | Type::NonInferableTypeVar(..) | Type::TypeVar(..) | Type::SpecialForm(..) | Type::KnownInstance(..) @@ -4544,7 +4524,7 @@ impl<'db> Type<'db> { } }, - Type::NonInferableTypeVar(bound_typevar) => { + Type::TypeVar(bound_typevar) => { match bound_typevar.typevar(db).bound_or_constraints(db) { None => Truthiness::Ambiguous, Some(TypeVarBoundOrConstraints::UpperBound(bound)) => { @@ -4555,7 +4535,6 @@ impl<'db> Type<'db> { } } } - Type::TypeVar(_) => Truthiness::Ambiguous, Type::NominalInstance(instance) => instance .known_class(db) @@ -4654,7 +4633,7 @@ impl<'db> Type<'db> { .into() } - Type::NonInferableTypeVar(bound_typevar) => { + Type::TypeVar(bound_typevar) => { match bound_typevar.typevar(db).bound_or_constraints(db) { None => CallableBinding::not_callable(self).into(), Some(TypeVarBoundOrConstraints::UpperBound(bound)) => bound.bindings(db), @@ -4667,15 +4646,6 @@ impl<'db> Type<'db> { } } - Type::TypeVar(_) => { - debug_assert!( - false, - "should not be able to call type variable {} in inferable position", - self.display(db) - ); - CallableBinding::not_callable(self).into() - } - Type::BoundMethod(bound_method) => { let signature = bound_method.function(db).signature(db); CallableBinding::from_overloads(self, signature.overloads.iter().cloned()) @@ -5593,18 +5563,16 @@ impl<'db> Type<'db> { Type::TypeAlias(alias) => { Some(alias.value_type(db).try_iterate_with_mode(db, mode)?) } - Type::NonInferableTypeVar(tvar) => match tvar.typevar(db).bound_or_constraints(db) { - Some(TypeVarBoundOrConstraints::UpperBound(bound)) => { - Some(bound.try_iterate_with_mode(db, mode)?) + Type::TypeVar(tvar) => { + match tvar.typevar(db).bound_or_constraints(db) { + Some(TypeVarBoundOrConstraints::UpperBound(bound)) => { + Some(bound.try_iterate_with_mode(db, mode)?) + } + // TODO: could we create a "union of tuple specs"...? + // (Same question applies to the `Type::Union()` branch lower down) + Some(TypeVarBoundOrConstraints::Constraints(_)) | None => None } - // TODO: could we create a "union of tuple specs"...? - // (Same question applies to the `Type::Union()` branch lower down) - Some(TypeVarBoundOrConstraints::Constraints(_)) | None => None }, - Type::TypeVar(_) => unreachable!( - "should not be able to iterate over type variable {} in inferable position", - self.display(db) - ), // N.B. These special cases aren't strictly necessary, they're just obvious optimizations Type::LiteralString | Type::Dynamic(_) => Some(Cow::Owned(TupleSpec::homogeneous(self))), @@ -5921,7 +5889,7 @@ impl<'db> Type<'db> { // It is important that identity_specialization specializes the class with // _inferable_ typevars, so that our specialization inference logic will // try to find a specialization for them. - Type::from(class.identity_specialization(db, &Type::TypeVar)), + Type::from(class.identity_specialization(db)), ), _ => (None, None, self), }, @@ -6074,9 +6042,6 @@ impl<'db> Type<'db> { // If there is no bound or constraints on a typevar `T`, `T: object` implicitly, which // has no instance type. Otherwise, synthesize a typevar with bound or constraints // mapped through `to_instance`. - Type::NonInferableTypeVar(bound_typevar) => { - Some(Type::NonInferableTypeVar(bound_typevar.to_instance(db)?)) - } Type::TypeVar(bound_typevar) => Some(Type::TypeVar(bound_typevar.to_instance(db)?)), Type::TypeAlias(alias) => alias.value_type(db).to_instance(db), Type::Intersection(_) => Some(todo_type!("Type::Intersection.to_instance")), @@ -6160,7 +6125,6 @@ impl<'db> Type<'db> { | Type::LiteralString | Type::ModuleLiteral(_) | Type::StringLiteral(_) - | Type::NonInferableTypeVar(_) | Type::TypeVar(_) | Type::Callable(_) | Type::BoundMethod(_) @@ -6192,7 +6156,7 @@ impl<'db> Type<'db> { typevar_binding_context, *typevar, ) - .map(Type::NonInferableTypeVar) + .map(Type::TypeVar) .unwrap_or(*self)) } KnownInstanceType::Deprecated(_) => Err(InvalidTypeExpressionError { @@ -6269,14 +6233,7 @@ impl<'db> Type<'db> { }); }; - Ok(typing_self( - db, - scope_id, - typevar_binding_context, - class, - &Type::NonInferableTypeVar, - ) - .unwrap_or(*self)) + Ok(typing_self(db, scope_id, typevar_binding_context, class).unwrap_or(*self)) } SpecialFormType::TypeAlias => Ok(Type::Dynamic(DynamicType::TodoTypeAlias)), SpecialFormType::TypedDict => Err(InvalidTypeExpressionError { @@ -6446,7 +6403,7 @@ impl<'db> Type<'db> { } Type::Callable(_) | Type::DataclassTransformer(_) => KnownClass::Type.to_instance(db), Type::ModuleLiteral(_) => KnownClass::ModuleType.to_class_literal(db), - Type::NonInferableTypeVar(bound_typevar) => { + Type::TypeVar(bound_typevar) => { match bound_typevar.typevar(db).bound_or_constraints(db) { None => KnownClass::Type.to_instance(db), Some(TypeVarBoundOrConstraints::UpperBound(bound)) => bound.to_meta_type(db), @@ -6457,7 +6414,6 @@ impl<'db> Type<'db> { } } } - Type::TypeVar(_) => KnownClass::Type.to_instance(db), Type::ClassLiteral(class) => class.metaclass(db), Type::GenericAlias(alias) => ClassType::from(alias).metaclass(db), @@ -6591,36 +6547,12 @@ impl<'db> Type<'db> { } } TypeMapping::PromoteLiterals - | TypeMapping::BindLegacyTypevars(_) - | TypeMapping::MarkTypeVarsInferable(_) => self, + | TypeMapping::BindLegacyTypevars(_) => self, TypeMapping::Materialize(materialization_kind) => { Type::TypeVar(bound_typevar.materialize_impl(db, *materialization_kind, visitor)) } } - Type::NonInferableTypeVar(bound_typevar) => match type_mapping { - TypeMapping::Specialization(specialization) => { - specialization.get(db, bound_typevar).unwrap_or(self) - } - TypeMapping::PartialSpecialization(partial) => { - partial.get(db, bound_typevar).unwrap_or(self) - } - TypeMapping::MarkTypeVarsInferable(binding_context) => { - if binding_context.is_none_or(|context| context == bound_typevar.binding_context(db)) { - Type::TypeVar(bound_typevar.mark_typevars_inferable(db, visitor)) - } else { - self - } - } - TypeMapping::PromoteLiterals - | TypeMapping::BindLegacyTypevars(_) - | TypeMapping::BindSelf(_) - | TypeMapping::ReplaceSelf { .. } - => self, - TypeMapping::Materialize(materialization_kind) => Type::NonInferableTypeVar(bound_typevar.materialize_impl(db, *materialization_kind, visitor)) - - } - Type::KnownInstance(KnownInstanceType::TypeVar(typevar)) => match type_mapping { TypeMapping::BindLegacyTypevars(binding_context) => { Type::TypeVar(BoundTypeVarInstance::new(db, typevar, *binding_context)) @@ -6630,7 +6562,6 @@ impl<'db> Type<'db> { TypeMapping::PromoteLiterals | TypeMapping::BindSelf(_) | TypeMapping::ReplaceSelf { .. } | - TypeMapping::MarkTypeVarsInferable(_) | TypeMapping::Materialize(_) => self, } @@ -6745,7 +6676,6 @@ impl<'db> Type<'db> { TypeMapping::BindLegacyTypevars(_) | TypeMapping::BindSelf(_) | TypeMapping::ReplaceSelf { .. } | - TypeMapping::MarkTypeVarsInferable(_) | TypeMapping::Materialize(_) => self, TypeMapping::PromoteLiterals => self.promote_literals_impl(db, tcx) } @@ -6756,7 +6686,6 @@ impl<'db> Type<'db> { TypeMapping::BindLegacyTypevars(_) | TypeMapping::BindSelf(_) | TypeMapping::ReplaceSelf { .. } | - TypeMapping::MarkTypeVarsInferable(_) | TypeMapping::PromoteLiterals => self, TypeMapping::Materialize(materialization_kind) => match materialization_kind { MaterializationKind::Top => Type::object(), @@ -6806,7 +6735,7 @@ impl<'db> Type<'db> { visitor: &FindLegacyTypeVarsVisitor<'db>, ) { match self { - Type::NonInferableTypeVar(bound_typevar) | Type::TypeVar(bound_typevar) => { + Type::TypeVar(bound_typevar) => { if matches!( bound_typevar.typevar(db).kind(db), TypeVarKind::Legacy | TypeVarKind::TypingSelf @@ -7038,7 +6967,6 @@ impl<'db> Type<'db> { | Self::PropertyInstance(_) | Self::BoundSuper(_) => self.to_meta_type(db).definition(db), - Self::NonInferableTypeVar(bound_typevar) | Self::TypeVar(bound_typevar) => Some(TypeDefinition::TypeVar(bound_typevar.typevar(db).definition(db)?)), Self::ProtocolInstance(protocol) => match protocol.inner { @@ -7180,9 +7108,7 @@ impl<'db> VarianceInferable<'db> for Type<'db> { Type::GenericAlias(generic_alias) => generic_alias.variance_of(db, typevar), Type::Callable(callable_type) => callable_type.signatures(db).variance_of(db, typevar), // A type variable is always covariant in itself. - Type::TypeVar(other_typevar) | Type::NonInferableTypeVar(other_typevar) - if other_typevar == typevar => - { + Type::TypeVar(other_typevar) if other_typevar == typevar => { // type variables are covariant in themselves TypeVarVariance::Covariant } @@ -7238,7 +7164,6 @@ impl<'db> VarianceInferable<'db> for Type<'db> { | Type::AlwaysTruthy | Type::BoundSuper(_) | Type::TypeVar(_) - | Type::NonInferableTypeVar(_) | Type::TypedDict(_) | Type::TypeAlias(_) => TypeVarVariance::Bivariant, }; @@ -7292,17 +7217,6 @@ pub enum TypeMapping<'a, 'db> { BindSelf(Type<'db>), /// Replaces occurrences of `typing.Self` with a new `Self` type variable with the given upper bound. ReplaceSelf { new_upper_bound: Type<'db> }, - /// Marks type variables as inferable. - /// - /// When we create the signature for a generic function, we mark its type variables as inferable. Since - /// the generic function might reference type variables from enclosing generic scopes, we include the - /// function's binding context in order to only mark those type variables as inferable that are actually - /// bound by that function. - /// - /// When the parameter is set to `None`, *all* type variables will be marked as inferable. We use this - /// variant when descending into the bounds and/or constraints, and the default value of a type variable, - /// which may include nested type variables (`Self` has a bound of `C[T]` for a generic class `C[T]`). - MarkTypeVarsInferable(Option>), /// Create the top or bottom materialization of a type. Materialize(MaterializationKind), } @@ -7319,7 +7233,6 @@ impl<'db> TypeMapping<'_, 'db> { | TypeMapping::PartialSpecialization(_) | TypeMapping::PromoteLiterals | TypeMapping::BindLegacyTypevars(_) - | TypeMapping::MarkTypeVarsInferable(_) | TypeMapping::Materialize(_) => context, TypeMapping::BindSelf(_) => GenericContext::from_typevar_instances( db, @@ -8176,62 +8089,6 @@ impl<'db> TypeVarInstance<'db> { ) } - fn mark_typevars_inferable( - self, - db: &'db dyn Db, - visitor: &ApplyTypeMappingVisitor<'db>, - ) -> Self { - // Type variables can have nested type variables in their bounds, constraints, or default value. - // When we mark a type variable as inferable, we also mark all of these nested type variables as - // inferable, so we set the parameter to `None` here. - let type_mapping = &TypeMapping::MarkTypeVarsInferable(None); - - let new_bound_or_constraints = - self._bound_or_constraints(db) - .map(|bound_or_constraints| match bound_or_constraints { - TypeVarBoundOrConstraintsEvaluation::Eager(bound_or_constraints) => { - bound_or_constraints - .mark_typevars_inferable(db, visitor) - .into() - } - TypeVarBoundOrConstraintsEvaluation::LazyUpperBound - | TypeVarBoundOrConstraintsEvaluation::LazyConstraints => bound_or_constraints, - }); - - let new_default = self._default(db).and_then(|default| match default { - TypeVarDefaultEvaluation::Eager(ty) => Some( - ty.apply_type_mapping_impl(db, type_mapping, TypeContext::default(), visitor) - .into(), - ), - TypeVarDefaultEvaluation::Lazy => self.lazy_default(db).map(|ty| { - ty.apply_type_mapping_impl(db, type_mapping, TypeContext::default(), visitor) - .into() - }), - }); - - // 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), - 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)) } @@ -8540,18 +8397,6 @@ impl<'db> BoundTypeVarInstance<'db> { ) } - fn mark_typevars_inferable( - self, - db: &'db dyn Db, - visitor: &ApplyTypeMappingVisitor<'db>, - ) -> Self { - Self::new( - db, - self.typevar(db).mark_typevars_inferable(db, visitor), - self.binding_context(db), - ) - } - fn to_instance(self, db: &'db dyn Db) -> Option { Some(Self::new( db, @@ -8657,38 +8502,6 @@ impl<'db> TypeVarBoundOrConstraints<'db> { } } } - - fn mark_typevars_inferable( - self, - db: &'db dyn Db, - visitor: &ApplyTypeMappingVisitor<'db>, - ) -> Self { - let type_mapping = &TypeMapping::MarkTypeVarsInferable(None); - - match self { - TypeVarBoundOrConstraints::UpperBound(bound) => TypeVarBoundOrConstraints::UpperBound( - bound.apply_type_mapping_impl(db, type_mapping, TypeContext::default(), visitor), - ), - TypeVarBoundOrConstraints::Constraints(constraints) => { - TypeVarBoundOrConstraints::Constraints(UnionType::new( - db, - constraints - .elements(db) - .iter() - .map(|ty| { - ty.apply_type_mapping_impl( - db, - type_mapping, - TypeContext::default(), - visitor, - ) - }) - .collect::>() - .into_boxed_slice(), - )) - } - } - } } /// Error returned if a type is not awaitable. diff --git a/crates/ty_python_semantic/src/types/builder.rs b/crates/ty_python_semantic/src/types/builder.rs index 9d101c40b2e1ea..87639ffe0ce9a9 100644 --- a/crates/ty_python_semantic/src/types/builder.rs +++ b/crates/ty_python_semantic/src/types/builder.rs @@ -1062,8 +1062,7 @@ impl<'db> InnerIntersectionBuilder<'db> { let mut positive_to_remove = SmallVec::<[usize; 1]>::new(); for (typevar_index, ty) in self.positive.iter().enumerate() { - let (Type::NonInferableTypeVar(bound_typevar) | Type::TypeVar(bound_typevar)) = ty - else { + let Type::TypeVar(bound_typevar) = ty else { continue; }; let Some(TypeVarBoundOrConstraints::Constraints(constraints)) = diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index dcab9b8c6724db..db53505c959793 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -1556,15 +1556,10 @@ impl<'db> ClassLiteral<'db> { }) } - /// Returns a specialization of this class where each typevar is mapped to itself. The second - /// parameter can be `Type::TypeVar` or `Type::NonInferableTypeVar`, depending on the use case. - pub(crate) fn identity_specialization( - self, - db: &'db dyn Db, - typevar_to_type: &impl Fn(BoundTypeVarInstance<'db>) -> Type<'db>, - ) -> ClassType<'db> { + /// Returns a specialization of this class where each typevar is mapped to itself. + pub(crate) fn identity_specialization(self, db: &'db dyn Db) -> ClassType<'db> { self.apply_specialization(db, |generic_context| { - generic_context.identity_specialization(db, typevar_to_type) + generic_context.identity_specialization(db) }) } diff --git a/crates/ty_python_semantic/src/types/class_base.rs b/crates/ty_python_semantic/src/types/class_base.rs index 204cbfb3508922..d22dbd5542f189 100644 --- a/crates/ty_python_semantic/src/types/class_base.rs +++ b/crates/ty_python_semantic/src/types/class_base.rs @@ -155,7 +155,6 @@ impl<'db> ClassBase<'db> { | Type::StringLiteral(_) | Type::LiteralString | Type::ModuleLiteral(_) - | Type::NonInferableTypeVar(_) | Type::TypeVar(_) | Type::BoundSuper(_) | Type::ProtocolInstance(_) diff --git a/crates/ty_python_semantic/src/types/display.rs b/crates/ty_python_semantic/src/types/display.rs index 1bee509e260407..1dcaf916b92029 100644 --- a/crates/ty_python_semantic/src/types/display.rs +++ b/crates/ty_python_semantic/src/types/display.rs @@ -560,9 +560,7 @@ impl Display for DisplayRepresentation<'_> { .display_with(self.db, self.settings.clone()), literal_name = enum_literal.name(self.db) ), - Type::NonInferableTypeVar(bound_typevar) | Type::TypeVar(bound_typevar) => { - bound_typevar.display(self.db).fmt(f) - } + Type::TypeVar(bound_typevar) => bound_typevar.display(self.db).fmt(f), Type::AlwaysTruthy => f.write_str("AlwaysTruthy"), Type::AlwaysFalsy => f.write_str("AlwaysFalsy"), Type::BoundSuper(bound_super) => { diff --git a/crates/ty_python_semantic/src/types/function.rs b/crates/ty_python_semantic/src/types/function.rs index b840fe8fede032..04f7d6779a1520 100644 --- a/crates/ty_python_semantic/src/types/function.rs +++ b/crates/ty_python_semantic/src/types/function.rs @@ -1057,7 +1057,6 @@ fn is_instance_truthiness<'db>( | Type::PropertyInstance(..) | Type::AlwaysTruthy | Type::AlwaysFalsy - | Type::NonInferableTypeVar(..) | Type::TypeVar(..) | Type::BoundSuper(..) | Type::TypeIs(..) @@ -1653,11 +1652,7 @@ impl KnownFunction { } KnownFunction::RangeConstraint => { - let [ - Some(lower), - Some(Type::NonInferableTypeVar(typevar)), - Some(upper), - ] = parameter_types + let [Some(lower), Some(Type::TypeVar(typevar)), Some(upper)] = parameter_types else { return; }; @@ -1670,11 +1665,7 @@ impl KnownFunction { } KnownFunction::NegatedRangeConstraint => { - let [ - Some(lower), - Some(Type::NonInferableTypeVar(typevar)), - Some(upper), - ] = parameter_types + let [Some(lower), Some(Type::TypeVar(typevar)), Some(upper)] = parameter_types else { return; }; diff --git a/crates/ty_python_semantic/src/types/generics.rs b/crates/ty_python_semantic/src/types/generics.rs index 61f5ca29575b5e..29925a83463443 100644 --- a/crates/ty_python_semantic/src/types/generics.rs +++ b/crates/ty_python_semantic/src/types/generics.rs @@ -105,7 +105,6 @@ pub(crate) fn typing_self<'db>( scope_id: ScopeId, typevar_binding_context: Option>, class: ClassLiteral<'db>, - typevar_to_type: &impl Fn(BoundTypeVarInstance<'db>) -> Type<'db>, ) -> Option> { let index = semantic_index(db, scope_id.file(db)); @@ -116,7 +115,7 @@ pub(crate) fn typing_self<'db>( Some( TypeVarBoundOrConstraints::UpperBound(Type::instance( db, - class.identity_specialization(db, typevar_to_type), + class.identity_specialization(db), )) .into(), ), @@ -136,7 +135,7 @@ pub(crate) fn typing_self<'db>( typevar_binding_context, typevar, ) - .map(typevar_to_type) + .map(Type::TypeVar) } #[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, get_size2::GetSize)] @@ -311,6 +310,14 @@ impl<'db> GenericContext<'db> { self.variables_inner(db).len() } + pub(crate) fn contains( + self, + db: &'db dyn Db, + bound_typevar: BoundTypeVarInstance<'db>, + ) -> bool { + self.variables_inner(db).contains_key(&bound_typevar) + } + pub(crate) fn signature(self, db: &'db dyn Db) -> Signature<'db> { let parameters = Parameters::new( self.variables(db) @@ -364,14 +371,8 @@ impl<'db> GenericContext<'db> { } /// Returns a specialization of this generic context where each typevar is mapped to itself. - /// The second parameter can be `Type::TypeVar` or `Type::NonInferableTypeVar`, depending on - /// the use case. - pub(crate) fn identity_specialization( - self, - db: &'db dyn Db, - typevar_to_type: &impl Fn(BoundTypeVarInstance<'db>) -> Type<'db>, - ) -> Specialization<'db> { - let types = self.variables(db).map(typevar_to_type).collect(); + pub(crate) fn identity_specialization(self, db: &'db dyn Db) -> Specialization<'db> { + let types = self.variables(db).map(Type::TypeVar).collect(); self.specialize(db, types) } diff --git a/crates/ty_python_semantic/src/types/ide_support.rs b/crates/ty_python_semantic/src/types/ide_support.rs index 1b00a41007e184..c7620e42adf7d4 100644 --- a/crates/ty_python_semantic/src/types/ide_support.rs +++ b/crates/ty_python_semantic/src/types/ide_support.rs @@ -194,7 +194,6 @@ impl<'db> AllMembers<'db> { | Type::ProtocolInstance(_) | Type::SpecialForm(_) | Type::KnownInstance(_) - | Type::NonInferableTypeVar(_) | Type::TypeVar(_) | Type::BoundSuper(_) | Type::TypeIs(_) => match ty.to_meta_type(db) { diff --git a/crates/ty_python_semantic/src/types/infer/builder.rs b/crates/ty_python_semantic/src/types/infer/builder.rs index c6b817c78962d2..717731e820f135 100644 --- a/crates/ty_python_semantic/src/types/infer/builder.rs +++ b/crates/ty_python_semantic/src/types/infer/builder.rs @@ -3529,7 +3529,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { | Type::WrapperDescriptor(_) | Type::DataclassDecorator(_) | Type::DataclassTransformer(_) - | Type::NonInferableTypeVar(..) | Type::TypeVar(..) | Type::AlwaysTruthy | Type::AlwaysFalsy @@ -7544,7 +7543,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { | Type::BytesLiteral(_) | Type::EnumLiteral(_) | Type::BoundSuper(_) - | Type::NonInferableTypeVar(_) | Type::TypeVar(_) | Type::TypeIs(_) | Type::TypedDict(_), @@ -7916,7 +7914,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { | Type::BytesLiteral(_) | Type::EnumLiteral(_) | Type::BoundSuper(_) - | Type::NonInferableTypeVar(_) | Type::TypeVar(_) | Type::TypeIs(_) | Type::TypedDict(_), @@ -7946,7 +7943,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { | Type::BytesLiteral(_) | Type::EnumLiteral(_) | Type::BoundSuper(_) - | Type::NonInferableTypeVar(_) | Type::TypeVar(_) | Type::TypeIs(_) | Type::TypedDict(_), diff --git a/crates/ty_python_semantic/src/types/narrow.rs b/crates/ty_python_semantic/src/types/narrow.rs index 2fb6157acb904b..58cf65638f5336 100644 --- a/crates/ty_python_semantic/src/types/narrow.rs +++ b/crates/ty_python_semantic/src/types/narrow.rs @@ -207,15 +207,16 @@ impl ClassInfoConstraintFunction { Type::Union(union) => { union.try_map(db, |element| self.generate_constraint(db, *element)) } - Type::NonInferableTypeVar(bound_typevar) => match bound_typevar - .typevar(db) - .bound_or_constraints(db)? - { - TypeVarBoundOrConstraints::UpperBound(bound) => self.generate_constraint(db, bound), - TypeVarBoundOrConstraints::Constraints(constraints) => { - self.generate_constraint(db, Type::Union(constraints)) + Type::TypeVar(bound_typevar) => { + match bound_typevar.typevar(db).bound_or_constraints(db)? { + TypeVarBoundOrConstraints::UpperBound(bound) => { + self.generate_constraint(db, bound) + } + TypeVarBoundOrConstraints::Constraints(constraints) => { + self.generate_constraint(db, Type::Union(constraints)) + } } - }, + } // It's not valid to use a generic alias as the second argument to `isinstance()` or `issubclass()`, // e.g. `isinstance(x, list[int])` fails at runtime. @@ -251,7 +252,6 @@ impl ClassInfoConstraintFunction { | Type::IntLiteral(_) | Type::KnownInstance(_) | Type::TypeIs(_) - | Type::TypeVar(_) | Type::WrapperDescriptor(_) | Type::DataclassTransformer(_) | Type::TypedDict(_) => None, diff --git a/crates/ty_python_semantic/src/types/signatures.rs b/crates/ty_python_semantic/src/types/signatures.rs index 41e04a2f43e8d7..ecd71147f0dcd5 100644 --- a/crates/ty_python_semantic/src/types/signatures.rs +++ b/crates/ty_python_semantic/src/types/signatures.rs @@ -425,12 +425,7 @@ impl<'db> Signature<'db> { has_implicitly_positional_first_parameter, ); let return_ty = function_node.returns.as_ref().map(|returns| { - let plain_return_ty = definition_expression_type(db, definition, returns.as_ref()) - .apply_type_mapping( - db, - &TypeMapping::MarkTypeVarsInferable(Some(definition.into())), - TypeContext::default(), - ); + let plain_return_ty = definition_expression_type(db, definition, returns.as_ref()); if function_node.is_async && !is_generator { KnownClass::CoroutineType .to_specialized_instance(db, [Type::any(), Type::any(), plain_return_ty]) @@ -1291,19 +1286,8 @@ impl<'db> Parameters<'db> { let class = nearest_enclosing_class(db, index, scope_id).unwrap(); Some( - // It looks like unnecessary work here that we create the implicit Self - // annotation using non-inferable typevars and then immediately apply - // `MarkTypeVarsInferable` to it. However, this is currently necessary to - // ensure that implicit-Self and explicit Self annotations are both treated - // the same. Marking type vars inferable will cause reification of lazy - // typevar defaults/bounds/constraints; this needs to happen for both - // implicit and explicit Self so they remain the "same" typevar. - typing_self(db, scope_id, typevar_binding_context, class, &Type::NonInferableTypeVar) - .expect("We should always find the surrounding class for an implicit self: Self annotation").apply_type_mapping( - db, - &TypeMapping::MarkTypeVarsInferable(None), - TypeContext::default() - ) + typing_self(db, scope_id, typevar_binding_context, class) + .expect("We should always find the surrounding class for an implicit self: Self annotation"), ) } else { // For methods of non-generic classes that are not otherwise generic (e.g. return `Self` or @@ -1727,13 +1711,9 @@ impl<'db> Parameter<'db> { kind: ParameterKind<'db>, ) -> Self { Self { - annotated_type: parameter.annotation().map(|annotation| { - definition_expression_type(db, definition, annotation).apply_type_mapping( - db, - &TypeMapping::MarkTypeVarsInferable(Some(definition.into())), - TypeContext::default(), - ) - }), + annotated_type: parameter + .annotation() + .map(|annotation| definition_expression_type(db, definition, annotation)), kind, form: ParameterForm::Value, inferred_annotation: false, diff --git a/crates/ty_python_semantic/src/types/type_ordering.rs b/crates/ty_python_semantic/src/types/type_ordering.rs index 3a0f1cd2511131..9324716ad386c9 100644 --- a/crates/ty_python_semantic/src/types/type_ordering.rs +++ b/crates/ty_python_semantic/src/types/type_ordering.rs @@ -142,10 +142,6 @@ pub(super) fn union_or_intersection_elements_ordering<'db>( (Type::ProtocolInstance(_), _) => Ordering::Less, (_, Type::ProtocolInstance(_)) => Ordering::Greater, - (Type::NonInferableTypeVar(left), Type::NonInferableTypeVar(right)) => left.cmp(right), - (Type::NonInferableTypeVar(_), _) => Ordering::Less, - (_, Type::NonInferableTypeVar(_)) => Ordering::Greater, - (Type::TypeVar(left), Type::TypeVar(right)) => left.cmp(right), (Type::TypeVar(_), _) => Ordering::Less, (_, Type::TypeVar(_)) => Ordering::Greater, diff --git a/crates/ty_python_semantic/src/types/visitor.rs b/crates/ty_python_semantic/src/types/visitor.rs index 9ca0f98e4c8829..8c764c00153101 100644 --- a/crates/ty_python_semantic/src/types/visitor.rs +++ b/crates/ty_python_semantic/src/types/visitor.rs @@ -121,7 +121,6 @@ pub(super) enum NonAtomicType<'db> { NominalInstance(NominalInstanceType<'db>), PropertyInstance(PropertyInstanceType<'db>), TypeIs(TypeIsType<'db>), - NonInferableTypeVar(BoundTypeVarInstance<'db>), TypeVar(BoundTypeVarInstance<'db>), ProtocolInstance(ProtocolInstanceType<'db>), TypedDict(TypedDictType<'db>), @@ -185,9 +184,6 @@ impl<'db> From> for TypeKind<'db> { Type::PropertyInstance(property) => { TypeKind::NonAtomic(NonAtomicType::PropertyInstance(property)) } - Type::NonInferableTypeVar(bound_typevar) => { - TypeKind::NonAtomic(NonAtomicType::NonInferableTypeVar(bound_typevar)) - } Type::TypeVar(bound_typevar) => { TypeKind::NonAtomic(NonAtomicType::TypeVar(bound_typevar)) } @@ -227,9 +223,6 @@ pub(super) fn walk_non_atomic_type<'db, V: TypeVisitor<'db> + ?Sized>( visitor.visit_property_instance_type(db, property); } NonAtomicType::TypeIs(type_is) => visitor.visit_typeis_type(db, type_is), - NonAtomicType::NonInferableTypeVar(bound_typevar) => { - visitor.visit_bound_type_var_type(db, bound_typevar); - } NonAtomicType::TypeVar(bound_typevar) => { visitor.visit_bound_type_var_type(db, bound_typevar); } From f5720c4d251b5dd617939f2f422ce06ce523b98d Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Wed, 1 Oct 2025 16:33:35 -0400 Subject: [PATCH 04/38] separate type for inferable --- crates/ty_python_semantic/src/types.rs | 75 +++++++------- .../ty_python_semantic/src/types/call/bind.rs | 32 +++--- crates/ty_python_semantic/src/types/class.rs | 13 ++- .../ty_python_semantic/src/types/function.rs | 6 +- .../ty_python_semantic/src/types/generics.rs | 97 ++++++++++++++++--- .../ty_python_semantic/src/types/instance.rs | 16 +-- .../src/types/protocol_class.rs | 8 +- .../src/types/signatures.rs | 16 +-- .../src/types/subclass_of.rs | 6 +- crates/ty_python_semantic/src/types/tuple.rs | 22 ++--- 10 files changed, 187 insertions(+), 104 deletions(-) diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index fd5dd5204ef922..cbd0b529d75e2a 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -52,8 +52,8 @@ use crate::types::function::{ DataclassTransformerParams, FunctionSpans, FunctionType, KnownFunction, }; use crate::types::generics::{ - GenericContext, PartialSpecialization, Specialization, bind_typevar, typing_self, - walk_generic_context, + GenericContext, InferableTypeVars, PartialSpecialization, Specialization, bind_typevar, + typing_self, walk_generic_context, }; use crate::types::infer::infer_unpack_types; use crate::types::mro::{Mro, MroError, MroIterator}; @@ -563,7 +563,7 @@ impl<'db> PropertyInstanceType<'db> { self, db: &'db dyn Db, other: Self, - inferable: Option>, + inferable: &InferableTypeVars<'db>, ) -> ConstraintSet<'db> { self.is_equivalent_to_impl(db, other, inferable, &IsEquivalentVisitor::default()) } @@ -572,7 +572,7 @@ impl<'db> PropertyInstanceType<'db> { self, db: &'db dyn Db, other: Self, - inferable: Option>, + inferable: &InferableTypeVars<'db>, visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { let getter_equivalence = if let Some(getter) = self.getter(db) { @@ -1452,14 +1452,15 @@ impl<'db> Type<'db> { /// /// See [`TypeRelation::Subtyping`] for more details. pub(crate) fn is_subtype_of(self, db: &'db dyn Db, target: Type<'db>) -> bool { - self.when_subtype_of(db, target, None).is_always_satisfied() + self.when_subtype_of(db, target, &InferableTypeVars::none()) + .is_always_satisfied() } fn when_subtype_of( self, db: &'db dyn Db, target: Type<'db>, - inferable: Option>, + inferable: &InferableTypeVars<'db>, ) -> ConstraintSet<'db> { self.has_relation_to(db, target, inferable, TypeRelation::Subtyping) } @@ -1468,7 +1469,7 @@ impl<'db> Type<'db> { /// /// See [`TypeRelation::Assignability`] for more details. pub(crate) fn is_assignable_to(self, db: &'db dyn Db, target: Type<'db>) -> bool { - self.when_assignable_to(db, target, None) + self.when_assignable_to(db, target, &InferableTypeVars::none()) .is_always_satisfied() } @@ -1476,7 +1477,7 @@ impl<'db> Type<'db> { self, db: &'db dyn Db, target: Type<'db>, - inferable: Option>, + inferable: &InferableTypeVars<'db>, ) -> ConstraintSet<'db> { self.has_relation_to(db, target, inferable, TypeRelation::Assignability) } @@ -1485,15 +1486,20 @@ impl<'db> Type<'db> { /// /// See [`TypeRelation::Redundancy`] for more details. pub(crate) fn is_redundant_with(self, db: &'db dyn Db, other: Type<'db>) -> bool { - self.has_relation_to(db, other, None, TypeRelation::Redundancy) - .is_always_satisfied() + self.has_relation_to( + db, + other, + &InferableTypeVars::none(), + TypeRelation::Redundancy, + ) + .is_always_satisfied() } fn has_relation_to( self, db: &'db dyn Db, target: Type<'db>, - inferable: Option>, + inferable: &InferableTypeVars<'db>, relation: TypeRelation, ) -> ConstraintSet<'db> { self.has_relation_to_impl( @@ -1510,7 +1516,7 @@ impl<'db> Type<'db> { self, db: &'db dyn Db, target: Type<'db>, - inferable: Option>, + inferable: &InferableTypeVars<'db>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -1652,7 +1658,7 @@ impl<'db> Type<'db> { // the union of its constraints. An unbound, unconstrained, fully static typevar has an // implicit upper bound of `object` (which is handled above). (Type::TypeVar(bound_typevar), _) - if inferable.is_none_or(|inferable| !inferable.contains(db, bound_typevar)) + if !inferable.is_inferable(bound_typevar) && bound_typevar.typevar(db).bound_or_constraints(db).is_some() => { match bound_typevar.typevar(db).bound_or_constraints(db) { @@ -1685,7 +1691,7 @@ impl<'db> Type<'db> { // might be specialized to any one of them. However, the constraints do not have to be // disjoint, which means an lhs type might be a subtype of all of the constraints. (_, Type::TypeVar(bound_typevar)) - if inferable.is_none_or(|inferable| !inferable.contains(db, bound_typevar)) + if !inferable.is_inferable(bound_typevar) && !bound_typevar .typevar(db) .constraints(db) @@ -1725,8 +1731,7 @@ impl<'db> Type<'db> { } (Type::TypeVar(bound_typevar), _) - if inferable.is_some_and(|inferable| inferable.contains(db, bound_typevar)) - && relation.is_assignability() => + if inferable.is_inferable(bound_typevar) && relation.is_assignability() => { // The implicit lower bound of a typevar is `Never`, which means // that it is always assignable to any other type. @@ -1828,9 +1833,7 @@ impl<'db> Type<'db> { // (If the typevar is bounded, it might be specialized to a smaller type than the // bound. This is true even if the bound is a final class, since the typevar can still // be specialized to `Never`.) - (_, Type::TypeVar(bound_typevar)) - if inferable.is_none_or(|inferable| !inferable.contains(db, bound_typevar)) => - { + (_, Type::TypeVar(bound_typevar)) if !inferable.is_inferable(bound_typevar) => { ConstraintSet::from(false) } @@ -2316,7 +2319,7 @@ impl<'db> Type<'db> { /// /// [equivalent to]: https://typing.python.org/en/latest/spec/glossary.html#term-equivalent pub(crate) fn is_equivalent_to(self, db: &'db dyn Db, other: Type<'db>) -> bool { - self.when_equivalent_to(db, other, None) + self.when_equivalent_to(db, other, &InferableTypeVars::none()) .is_always_satisfied() } @@ -2324,7 +2327,7 @@ impl<'db> Type<'db> { self, db: &'db dyn Db, other: Type<'db>, - inferable: Option>, + inferable: &InferableTypeVars<'db>, ) -> ConstraintSet<'db> { self.is_equivalent_to_impl(db, other, inferable, &IsEquivalentVisitor::default()) } @@ -2333,7 +2336,7 @@ impl<'db> Type<'db> { self, db: &'db dyn Db, other: Type<'db>, - inferable: Option>, + inferable: &InferableTypeVars<'db>, visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { if self == other { @@ -2439,7 +2442,7 @@ impl<'db> Type<'db> { /// This function aims to have no false positives, but might return wrong /// `false` answers in some cases. pub(crate) fn is_disjoint_from(self, db: &'db dyn Db, other: Type<'db>) -> bool { - self.when_disjoint_from(db, other, None) + self.when_disjoint_from(db, other, &InferableTypeVars::none()) .is_always_satisfied() } @@ -2447,7 +2450,7 @@ impl<'db> Type<'db> { self, db: &'db dyn Db, other: Type<'db>, - inferable: Option>, + inferable: &InferableTypeVars<'db>, ) -> ConstraintSet<'db> { self.is_disjoint_from_impl( db, @@ -2462,7 +2465,7 @@ impl<'db> Type<'db> { self, db: &'db dyn Db, other: Type<'db>, - inferable: Option>, + inferable: &InferableTypeVars<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, relation_visitor: &HasRelationToVisitor<'db>, ) -> ConstraintSet<'db> { @@ -2470,7 +2473,7 @@ impl<'db> Type<'db> { db: &'db dyn Db, protocol: ProtocolInstanceType<'db>, other: Type<'db>, - inferable: Option>, + inferable: &InferableTypeVars<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, relation_visitor: &HasRelationToVisitor<'db>, ) -> ConstraintSet<'db> { @@ -2539,7 +2542,7 @@ impl<'db> Type<'db> { (tvar @ Type::TypeVar(bound_typevar), Type::Intersection(intersection)) | (Type::Intersection(intersection), tvar @ Type::TypeVar(bound_typevar)) - if inferable.is_none_or(|inferable| !inferable.contains(db, bound_typevar)) + if !inferable.is_inferable(bound_typevar) && intersection.negative(db).contains(&tvar) => { ConstraintSet::from(true) @@ -2550,7 +2553,7 @@ impl<'db> Type<'db> { // only disjoint from other types if its bound is. A constrained typevar is disjoint // from a type if all of its constraints are. (Type::TypeVar(bound_typevar), other) | (other, Type::TypeVar(bound_typevar)) - if inferable.is_none_or(|inferable| !inferable.contains(db, bound_typevar)) => + if !inferable.is_inferable(bound_typevar) => { match bound_typevar.typevar(db).bound_or_constraints(db) { None => ConstraintSet::from(false), @@ -9768,7 +9771,7 @@ impl<'db> BoundMethodType<'db> { self, db: &'db dyn Db, other: Self, - inferable: Option>, + inferable: &InferableTypeVars<'db>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -9802,7 +9805,7 @@ impl<'db> BoundMethodType<'db> { self, db: &'db dyn Db, other: Self, - inferable: Option>, + inferable: &InferableTypeVars<'db>, visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { self.function(db) @@ -9935,7 +9938,7 @@ impl<'db> CallableType<'db> { self, db: &'db dyn Db, other: Self, - inferable: Option>, + inferable: &InferableTypeVars<'db>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -9960,7 +9963,7 @@ impl<'db> CallableType<'db> { self, db: &'db dyn Db, other: Self, - inferable: Option>, + inferable: &InferableTypeVars<'db>, visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { if self == other { @@ -10030,7 +10033,7 @@ impl<'db> KnownBoundMethodType<'db> { self, db: &'db dyn Db, other: Self, - inferable: Option>, + inferable: &InferableTypeVars<'db>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -10098,7 +10101,7 @@ impl<'db> KnownBoundMethodType<'db> { self, db: &'db dyn Db, other: Self, - inferable: Option>, + inferable: &InferableTypeVars<'db>, visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { match (self, other) { @@ -10964,7 +10967,7 @@ impl<'db> UnionType<'db> { self, db: &'db dyn Db, other: Self, - _inferable: Option>, + _inferable: &InferableTypeVars<'db>, _visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { if self == other { @@ -11066,7 +11069,7 @@ impl<'db> IntersectionType<'db> { self, db: &'db dyn Db, other: Self, - _inferable: Option>, + _inferable: &InferableTypeVars<'db>, _visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { if self == other { diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index 9a4556ae18f275..3383afa9ce139f 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -3,6 +3,7 @@ //! [signatures][crate::types::signatures], we have to handle the fact that the callable might be a //! union of types, each of which might contain multiple overloads. +use std::borrow::Cow; use std::collections::HashSet; use std::fmt; @@ -26,7 +27,9 @@ use crate::types::enums::is_enum_class; use crate::types::function::{ DataclassTransformerParams, FunctionDecorators, FunctionType, KnownFunction, OverloadLiteral, }; -use crate::types::generics::{Specialization, SpecializationBuilder, SpecializationError}; +use crate::types::generics::{ + InferableTypeVars, Specialization, SpecializationBuilder, SpecializationError, +}; use crate::types::signatures::{Parameter, ParameterForm, ParameterKind, Parameters}; use crate::types::tuple::{TupleLength, TupleType}; use crate::types::{ @@ -597,7 +600,8 @@ impl<'db> Bindings<'db> { Type::FunctionLiteral(function_type) => match function_type.known(db) { Some(KnownFunction::IsEquivalentTo) => { if let [Some(ty_a), Some(ty_b)] = overload.parameter_types() { - let constraints = ty_a.when_equivalent_to(db, *ty_b, None); + let constraints = + ty_a.when_equivalent_to(db, *ty_b, &InferableTypeVars::none()); let tracked = TrackedConstraintSet::new(db, constraints); overload.set_return_type(Type::KnownInstance( KnownInstanceType::ConstraintSet(tracked), @@ -607,7 +611,8 @@ impl<'db> Bindings<'db> { Some(KnownFunction::IsSubtypeOf) => { if let [Some(ty_a), Some(ty_b)] = overload.parameter_types() { - let constraints = ty_a.when_subtype_of(db, *ty_b, None); + let constraints = + ty_a.when_subtype_of(db, *ty_b, &InferableTypeVars::none()); let tracked = TrackedConstraintSet::new(db, constraints); overload.set_return_type(Type::KnownInstance( KnownInstanceType::ConstraintSet(tracked), @@ -617,7 +622,8 @@ impl<'db> Bindings<'db> { Some(KnownFunction::IsAssignableTo) => { if let [Some(ty_a), Some(ty_b)] = overload.parameter_types() { - let constraints = ty_a.when_assignable_to(db, *ty_b, None); + let constraints = + ty_a.when_assignable_to(db, *ty_b, &InferableTypeVars::none()); let tracked = TrackedConstraintSet::new(db, constraints); overload.set_return_type(Type::KnownInstance( KnownInstanceType::ConstraintSet(tracked), @@ -627,7 +633,8 @@ impl<'db> Bindings<'db> { Some(KnownFunction::IsDisjointFrom) => { if let [Some(ty_a), Some(ty_b)] = overload.parameter_types() { - let constraints = ty_a.when_disjoint_from(db, *ty_b, None); + let constraints = + ty_a.when_disjoint_from(db, *ty_b, &InferableTypeVars::none()); let tracked = TrackedConstraintSet::new(db, constraints); overload.set_return_type(Type::KnownInstance( KnownInstanceType::ConstraintSet(tracked), @@ -2583,6 +2590,14 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> { argument_type = argument_type.apply_specialization(self.db, specialization); expected_ty = expected_ty.apply_specialization(self.db, specialization); } + let inferable_typevars = match self.specialization { + Some(specialization) => Cow::Borrowed( + specialization + .generic_context(self.db) + .inferable_typevars(self.db), + ), + None => Cow::Owned(InferableTypeVars::none()), + }; // This is one of the few places where we want to check if there's _any_ specialization // where assignability holds; normally we want to check that assignability holds for // _all_ specializations. @@ -2590,12 +2605,7 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> { // constraint set that we get from this assignability check, instead of inferring and // building them in an earlier separate step. if argument_type - .when_assignable_to( - self.db, - expected_ty, - self.specialization - .map(|specialization| specialization.generic_context(self.db)), - ) + .when_assignable_to(self.db, expected_ty, inferable_typevars.as_ref()) .is_never_satisfied() { let positional = matches!(argument, Argument::Positional | Argument::Synthetic) diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index db53505c959793..a095b137fb98e8 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -19,7 +19,9 @@ use crate::types::context::InferContext; use crate::types::diagnostic::INVALID_TYPE_ALIAS_TYPE; use crate::types::enums::enum_metadata; use crate::types::function::{DataclassTransformerParams, KnownFunction}; -use crate::types::generics::{GenericContext, Specialization, walk_specialization}; +use crate::types::generics::{ + GenericContext, InferableTypeVars, Specialization, walk_specialization, +}; use crate::types::infer::nearest_enclosing_class; use crate::types::signatures::{CallableSignature, Parameter, Parameters, Signature}; use crate::types::tuple::{TupleSpec, TupleType}; @@ -534,14 +536,15 @@ impl<'db> ClassType<'db> { /// Return `true` if `other` is present in this class's MRO. pub(super) fn is_subclass_of(self, db: &'db dyn Db, other: ClassType<'db>) -> bool { - self.when_subclass_of(db, other, None).is_always_satisfied() + self.when_subclass_of(db, other, &InferableTypeVars::none()) + .is_always_satisfied() } pub(super) fn when_subclass_of( self, db: &'db dyn Db, other: ClassType<'db>, - inferable: Option>, + inferable: &InferableTypeVars<'db>, ) -> ConstraintSet<'db> { self.has_relation_to_impl( db, @@ -557,7 +560,7 @@ impl<'db> ClassType<'db> { self, db: &'db dyn Db, other: Self, - inferable: Option>, + inferable: &InferableTypeVars<'db>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -608,7 +611,7 @@ impl<'db> ClassType<'db> { self, db: &'db dyn Db, other: ClassType<'db>, - inferable: Option>, + inferable: &InferableTypeVars<'db>, visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { if self == other { diff --git a/crates/ty_python_semantic/src/types/function.rs b/crates/ty_python_semantic/src/types/function.rs index 04f7d6779a1520..347de29f6ad6bb 100644 --- a/crates/ty_python_semantic/src/types/function.rs +++ b/crates/ty_python_semantic/src/types/function.rs @@ -73,7 +73,7 @@ use crate::types::diagnostic::{ report_runtime_check_against_non_runtime_checkable_protocol, }; use crate::types::display::DisplaySettings; -use crate::types::generics::GenericContext; +use crate::types::generics::{GenericContext, InferableTypeVars}; use crate::types::ide_support::all_members; use crate::types::narrow::ClassInfoConstraintFunction; use crate::types::signatures::{CallableSignature, Signature}; @@ -895,7 +895,7 @@ impl<'db> FunctionType<'db> { self, db: &'db dyn Db, other: Self, - inferable: Option>, + inferable: &InferableTypeVars<'db>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -930,7 +930,7 @@ impl<'db> FunctionType<'db> { self, db: &'db dyn Db, other: Self, - inferable: Option>, + inferable: &InferableTypeVars<'db>, visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { if self.normalized(db) == other.normalized(db) { diff --git a/crates/ty_python_semantic/src/types/generics.rs b/crates/ty_python_semantic/src/types/generics.rs index 29925a83463443..0fecf42e9dcf27 100644 --- a/crates/ty_python_semantic/src/types/generics.rs +++ b/crates/ty_python_semantic/src/types/generics.rs @@ -1,26 +1,28 @@ -use crate::types::constraints::ConstraintSet; +use std::cell::RefCell; use itertools::Itertools; use ruff_python_ast as ast; -use rustc_hash::FxHashMap; +use rustc_hash::{FxHashMap, FxHashSet}; use crate::semantic_index::definition::Definition; use crate::semantic_index::scope::{FileScopeId, NodeWithScopeKind, ScopeId}; use crate::semantic_index::{SemanticIndex, semantic_index}; use crate::types::class::ClassType; use crate::types::class_base::ClassBase; +use crate::types::constraints::ConstraintSet; use crate::types::infer::infer_definition_types; 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::visitor::{NonAtomicType, TypeKind, TypeVisitor, walk_non_atomic_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, + binding_type, declaration_type, walk_bound_type_var_type, }; -use crate::{Db, FxOrderMap, FxOrderSet}; +use crate::{Db, FxIndexSet, FxOrderMap, FxOrderSet}; /// Returns an iterator of any generic context introduced by the given scope or any enclosing /// scope. @@ -138,6 +140,71 @@ pub(crate) fn typing_self<'db>( .map(Type::TypeVar) } +#[derive(Clone, Debug, Eq, PartialEq, salsa::Update)] +pub(crate) struct InferableTypeVars<'db> { + typevars: FxHashSet>, +} + +impl<'db> InferableTypeVars<'db> { + pub(crate) fn none() -> Self { + InferableTypeVars { + typevars: FxHashSet::default(), + } + } + + pub(crate) fn is_inferable(&self, bound_typevar: BoundTypeVarInstance<'db>) -> bool { + self.typevars.contains(&bound_typevar) + } + + fn from_bound_typevars( + db: &'db dyn Db, + bound_typevars: impl IntoIterator>, + ) -> Self { + struct CollectTypeVars<'db> { + typevars: RefCell>>, + seen_types: RefCell>>, + } + + impl<'db> TypeVisitor<'db> for CollectTypeVars<'db> { + fn should_visit_lazy_type_attributes(&self) -> bool { + true + } + + fn visit_bound_type_var_type( + &self, + db: &'db dyn Db, + bound_typevar: BoundTypeVarInstance<'db>, + ) { + self.typevars.borrow_mut().insert(bound_typevar); + walk_bound_type_var_type(db, bound_typevar, self); + } + + fn visit_type(&self, db: &'db dyn Db, ty: Type<'db>) { + match TypeKind::from(ty) { + TypeKind::Atomic => {} + TypeKind::NonAtomic(non_atomic_type) => { + if !self.seen_types.borrow_mut().insert(non_atomic_type) { + // If we have already seen this type, we can skip it. + return; + } + walk_non_atomic_type(db, non_atomic_type, self); + } + } + } + } + + let visitor = CollectTypeVars { + typevars: RefCell::new(FxHashSet::default()), + seen_types: RefCell::new(FxIndexSet::default()), + }; + for bound_typevar in bound_typevars { + visitor.visit_bound_type_var_type(db, bound_typevar); + } + let typevars = visitor.typevars.into_inner(); + Self { typevars } + } +} + #[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, get_size2::GetSize)] pub struct GenericContextTypeVarOptions { should_promote_literals: bool, @@ -175,6 +242,7 @@ pub(super) fn walk_generic_context<'db, V: super::visitor::TypeVisitor<'db> + ?S // The Salsa heap is tracked separately. impl get_size2::GetSize for GenericContext<'_> {} +#[salsa::tracked] impl<'db> GenericContext<'db> { fn from_variables( db: &'db dyn Db, @@ -233,6 +301,11 @@ impl<'db> GenericContext<'db> { ) } + #[salsa::tracked(returns(ref))] + pub(crate) fn inferable_typevars(self, db: &'db dyn Db) -> InferableTypeVars<'db> { + InferableTypeVars::from_bound_typevars(db, self.variables(db)) + } + pub(crate) fn variables( self, db: &'db dyn Db, @@ -310,14 +383,6 @@ impl<'db> GenericContext<'db> { self.variables_inner(db).len() } - pub(crate) fn contains( - self, - db: &'db dyn Db, - bound_typevar: BoundTypeVarInstance<'db>, - ) -> bool { - self.variables_inner(db).contains_key(&bound_typevar) - } - pub(crate) fn signature(self, db: &'db dyn Db) -> Signature<'db> { let parameters = Parameters::new( self.variables(db) @@ -555,7 +620,7 @@ fn is_subtype_in_invariant_position<'db>( derived_materialization: MaterializationKind, base_type: &Type<'db>, base_materialization: MaterializationKind, - inferable: Option>, + inferable: &InferableTypeVars<'db>, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, ) -> ConstraintSet<'db> { @@ -633,7 +698,7 @@ fn has_relation_in_invariant_position<'db>( derived_materialization: Option, base_type: &Type<'db>, base_materialization: Option, - inferable: Option>, + inferable: &InferableTypeVars<'db>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -968,7 +1033,7 @@ impl<'db> Specialization<'db> { self, db: &'db dyn Db, other: Self, - inferable: Option>, + inferable: &InferableTypeVars<'db>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -1046,7 +1111,7 @@ impl<'db> Specialization<'db> { self, db: &'db dyn Db, other: Specialization<'db>, - inferable: Option>, + inferable: &InferableTypeVars<'db>, visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { if self.materialization_kind(db) != other.materialization_kind(db) { diff --git a/crates/ty_python_semantic/src/types/instance.rs b/crates/ty_python_semantic/src/types/instance.rs index 1b2fefb5db2133..722e08ba146cb6 100644 --- a/crates/ty_python_semantic/src/types/instance.rs +++ b/crates/ty_python_semantic/src/types/instance.rs @@ -9,7 +9,7 @@ use crate::place::PlaceAndQualifiers; use crate::semantic_index::definition::Definition; use crate::types::constraints::{ConstraintSet, IteratorConstraintsExtension}; use crate::types::enums::is_single_member_enum; -use crate::types::generics::{GenericContext, walk_specialization}; +use crate::types::generics::{InferableTypeVars, walk_specialization}; use crate::types::protocol_class::walk_protocol_interface; use crate::types::tuple::{TupleSpec, TupleType}; use crate::types::{ @@ -121,7 +121,7 @@ impl<'db> Type<'db> { self, db: &'db dyn Db, protocol: ProtocolInstanceType<'db>, - inferable: Option>, + inferable: &InferableTypeVars<'db>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -368,7 +368,7 @@ impl<'db> NominalInstanceType<'db> { self, db: &'db dyn Db, other: Self, - inferable: Option>, + inferable: &InferableTypeVars<'db>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -401,7 +401,7 @@ impl<'db> NominalInstanceType<'db> { self, db: &'db dyn Db, other: Self, - inferable: Option>, + inferable: &InferableTypeVars<'db>, visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { match (self.0, other.0) { @@ -423,7 +423,7 @@ impl<'db> NominalInstanceType<'db> { self, db: &'db dyn Db, other: Self, - inferable: Option>, + inferable: &InferableTypeVars<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, relation_visitor: &HasRelationToVisitor<'db>, ) -> ConstraintSet<'db> { @@ -650,7 +650,7 @@ impl<'db> ProtocolInstanceType<'db> { .satisfies_protocol( db, protocol, - None, + &InferableTypeVars::none(), TypeRelation::Subtyping, &HasRelationToVisitor::default(), &IsDisjointVisitor::default(), @@ -709,7 +709,7 @@ impl<'db> ProtocolInstanceType<'db> { self, db: &'db dyn Db, other: Self, - _inferable: Option>, + _inferable: &InferableTypeVars<'db>, _visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { if self == other { @@ -731,7 +731,7 @@ impl<'db> ProtocolInstanceType<'db> { self, _db: &'db dyn Db, _other: Self, - _inferable: Option>, + _inferable: &InferableTypeVars<'db>, _visitor: &IsDisjointVisitor<'db>, ) -> ConstraintSet<'db> { ConstraintSet::from(false) diff --git a/crates/ty_python_semantic/src/types/protocol_class.rs b/crates/ty_python_semantic/src/types/protocol_class.rs index 660236c3e15b5a..ab3caf37f698ab 100644 --- a/crates/ty_python_semantic/src/types/protocol_class.rs +++ b/crates/ty_python_semantic/src/types/protocol_class.rs @@ -22,7 +22,7 @@ use crate::{ constraints::{ConstraintSet, IteratorConstraintsExtension, OptionConstraintsExtension}, context::InferContext, diagnostic::report_undeclared_protocol_member, - generics::GenericContext, + generics::InferableTypeVars, signatures::{Parameter, Parameters}, todo_type, }, @@ -236,7 +236,7 @@ impl<'db> ProtocolInterface<'db> { self, db: &'db dyn Db, other: Self, - inferable: Option>, + inferable: &InferableTypeVars<'db>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -611,7 +611,7 @@ impl<'a, 'db> ProtocolMember<'a, 'db> { &self, db: &'db dyn Db, other: Type<'db>, - inferable: Option>, + inferable: &InferableTypeVars<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, relation_visitor: &HasRelationToVisitor<'db>, ) -> ConstraintSet<'db> { @@ -636,7 +636,7 @@ impl<'a, 'db> ProtocolMember<'a, 'db> { &self, db: &'db dyn Db, other: Type<'db>, - inferable: Option>, + inferable: &InferableTypeVars<'db>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, diff --git a/crates/ty_python_semantic/src/types/signatures.rs b/crates/ty_python_semantic/src/types/signatures.rs index ecd71147f0dcd5..47ad8a23a7e982 100644 --- a/crates/ty_python_semantic/src/types/signatures.rs +++ b/crates/ty_python_semantic/src/types/signatures.rs @@ -23,7 +23,9 @@ use super::{ use crate::semantic_index::definition::Definition; use crate::types::constraints::{ConstraintSet, IteratorConstraintsExtension}; use crate::types::function::FunctionType; -use crate::types::generics::{GenericContext, typing_self, walk_generic_context}; +use crate::types::generics::{ + GenericContext, InferableTypeVars, typing_self, walk_generic_context, +}; use crate::types::infer::nearest_enclosing_class; use crate::types::{ ApplyTypeMappingVisitor, BoundTypeVarInstance, ClassLiteral, FindLegacyTypeVarsVisitor, @@ -177,7 +179,7 @@ impl<'db> CallableSignature<'db> { &self, db: &'db dyn Db, other: &Self, - inferable: Option>, + inferable: &InferableTypeVars<'db>, ) -> ConstraintSet<'db> { self.has_relation_to_impl( db, @@ -193,7 +195,7 @@ impl<'db> CallableSignature<'db> { &self, db: &'db dyn Db, other: &Self, - inferable: Option>, + inferable: &InferableTypeVars<'db>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -215,7 +217,7 @@ impl<'db> CallableSignature<'db> { db: &'db dyn Db, self_signatures: &[Signature<'db>], other_signatures: &[Signature<'db>], - inferable: Option>, + inferable: &InferableTypeVars<'db>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -281,7 +283,7 @@ impl<'db> CallableSignature<'db> { &self, db: &'db dyn Db, other: &Self, - inferable: Option>, + inferable: &InferableTypeVars<'db>, visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { match (self.overloads.as_slice(), other.overloads.as_slice()) { @@ -599,7 +601,7 @@ impl<'db> Signature<'db> { &self, db: &'db dyn Db, other: &Signature<'db>, - inferable: Option>, + inferable: &InferableTypeVars<'db>, visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { let mut result = ConstraintSet::from(true); @@ -686,7 +688,7 @@ impl<'db> Signature<'db> { &self, db: &'db dyn Db, other: &Signature<'db>, - inferable: Option>, + inferable: &InferableTypeVars<'db>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, diff --git a/crates/ty_python_semantic/src/types/subclass_of.rs b/crates/ty_python_semantic/src/types/subclass_of.rs index 2f98ff8c473027..d28185faaf6289 100644 --- a/crates/ty_python_semantic/src/types/subclass_of.rs +++ b/crates/ty_python_semantic/src/types/subclass_of.rs @@ -1,7 +1,7 @@ use crate::place::PlaceAndQualifiers; use crate::semantic_index::definition::Definition; use crate::types::constraints::ConstraintSet; -use crate::types::generics::GenericContext; +use crate::types::generics::InferableTypeVars; use crate::types::variance::VarianceInferable; use crate::types::{ ApplyTypeMappingVisitor, BoundTypeVarInstance, ClassType, DynamicType, @@ -136,7 +136,7 @@ impl<'db> SubclassOfType<'db> { self, db: &'db dyn Db, other: SubclassOfType<'db>, - inferable: Option>, + inferable: &InferableTypeVars<'db>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -174,7 +174,7 @@ impl<'db> SubclassOfType<'db> { self, db: &'db dyn Db, other: Self, - _inferable: Option>, + _inferable: &InferableTypeVars<'db>, _visitor: &IsDisjointVisitor<'db>, ) -> ConstraintSet<'db> { match (self.subclass_of, other.subclass_of) { diff --git a/crates/ty_python_semantic/src/types/tuple.rs b/crates/ty_python_semantic/src/types/tuple.rs index 00826132eaa8ee..f482e38c3fca2b 100644 --- a/crates/ty_python_semantic/src/types/tuple.rs +++ b/crates/ty_python_semantic/src/types/tuple.rs @@ -24,7 +24,7 @@ use itertools::{Either, EitherOrBoth, Itertools}; use crate::semantic_index::definition::Definition; use crate::types::class::{ClassType, KnownClass}; use crate::types::constraints::{ConstraintSet, IteratorConstraintsExtension}; -use crate::types::generics::GenericContext; +use crate::types::generics::InferableTypeVars; use crate::types::{ ApplyTypeMappingVisitor, BoundTypeVarInstance, FindLegacyTypeVarsVisitor, HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, NormalizedVisitor, Type, TypeMapping, TypeRelation, @@ -259,7 +259,7 @@ impl<'db> TupleType<'db> { self, db: &'db dyn Db, other: Self, - inferable: Option>, + inferable: &InferableTypeVars<'db>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -278,7 +278,7 @@ impl<'db> TupleType<'db> { self, db: &'db dyn Db, other: Self, - inferable: Option>, + inferable: &InferableTypeVars<'db>, visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { self.tuple(db) @@ -446,7 +446,7 @@ impl<'db> FixedLengthTuple> { &self, db: &'db dyn Db, other: &Tuple>, - inferable: Option>, + inferable: &InferableTypeVars<'db>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -533,7 +533,7 @@ impl<'db> FixedLengthTuple> { &self, db: &'db dyn Db, other: &Self, - inferable: Option>, + inferable: &InferableTypeVars<'db>, visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { ConstraintSet::from(self.0.len() == other.0.len()).and(db, || { @@ -803,7 +803,7 @@ impl<'db> VariableLengthTuple> { &self, db: &'db dyn Db, other: &Tuple>, - inferable: Option>, + inferable: &InferableTypeVars<'db>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -976,7 +976,7 @@ impl<'db> VariableLengthTuple> { &self, db: &'db dyn Db, other: &Self, - inferable: Option>, + inferable: &InferableTypeVars<'db>, visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { self.variable @@ -1189,7 +1189,7 @@ impl<'db> Tuple> { &self, db: &'db dyn Db, other: &Self, - inferable: Option>, + inferable: &InferableTypeVars<'db>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -1218,7 +1218,7 @@ impl<'db> Tuple> { &self, db: &'db dyn Db, other: &Self, - inferable: Option>, + inferable: &InferableTypeVars<'db>, visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { match (self, other) { @@ -1238,7 +1238,7 @@ impl<'db> Tuple> { &self, db: &'db dyn Db, other: &Self, - inferable: Option>, + inferable: &InferableTypeVars<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, relation_visitor: &HasRelationToVisitor<'db>, ) -> ConstraintSet<'db> { @@ -1258,7 +1258,7 @@ impl<'db> Tuple> { db: &'db dyn Db, a: impl IntoIterator>, b: impl IntoIterator>, - inferable: Option>, + inferable: &InferableTypeVars<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, relation_visitor: &HasRelationToVisitor<'db>, ) -> ConstraintSet<'db> From 9883c128eb96906d4aaf6f8695a0fa541452fac6 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Wed, 1 Oct 2025 16:55:31 -0400 Subject: [PATCH 05/38] track inferable when infering --- crates/ty_python_semantic/src/types/call/bind.rs | 12 +++++------- crates/ty_python_semantic/src/types/generics.rs | 14 +++++++++++--- .../ty_python_semantic/src/types/infer/builder.rs | 13 +++++++++---- 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index 3383afa9ce139f..623e9999545e50 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -2521,11 +2521,12 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> { } fn infer_specialization(&mut self) { - if self.signature.generic_context.is_none() { + let Some(generic_context) = self.signature.generic_context else { return; - } + }; - let mut builder = SpecializationBuilder::new(self.db); + let mut builder = + SpecializationBuilder::new(self.db, generic_context.inferable_typevars(self.db)); // Note that we infer the annotated type _before_ the arguments if this call is part of // an annotated assignment, to closer match the order of any unions written in the type @@ -2570,10 +2571,7 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> { } } - self.specialization = self - .signature - .generic_context - .map(|gc| builder.build(gc, *self.call_expression_tcx)); + self.specialization = Some(builder.build(generic_context, *self.call_expression_tcx)); } fn check_argument_type( diff --git a/crates/ty_python_semantic/src/types/generics.rs b/crates/ty_python_semantic/src/types/generics.rs index 0fecf42e9dcf27..c80707b8087a75 100644 --- a/crates/ty_python_semantic/src/types/generics.rs +++ b/crates/ty_python_semantic/src/types/generics.rs @@ -1206,13 +1206,15 @@ impl<'db> PartialSpecialization<'_, 'db> { /// specialization of a generic function. pub(crate) struct SpecializationBuilder<'db> { db: &'db dyn Db, + inferable: &'db InferableTypeVars<'db>, types: FxHashMap, Type<'db>>, } impl<'db> SpecializationBuilder<'db> { - pub(crate) fn new(db: &'db dyn Db) -> Self { + pub(crate) fn new(db: &'db dyn Db, inferable: &'db InferableTypeVars<'db>) -> Self { Self { db, + inferable, types: FxHashMap::default(), } } @@ -1351,7 +1353,10 @@ impl<'db> SpecializationBuilder<'db> { (Type::TypeVar(bound_typevar), ty) | (ty, Type::TypeVar(bound_typevar)) => { match bound_typevar.typevar(self.db).bound_or_constraints(self.db) { Some(TypeVarBoundOrConstraints::UpperBound(bound)) => { - if !ty.is_assignable_to(self.db, bound) { + if !ty + .when_assignable_to(self.db, bound, self.inferable) + .is_always_satisfied() + { return Err(SpecializationError::MismatchedBound { bound_typevar, argument: ty, @@ -1361,7 +1366,10 @@ impl<'db> SpecializationBuilder<'db> { } Some(TypeVarBoundOrConstraints::Constraints(constraints)) => { for constraint in constraints.elements(self.db) { - if ty.is_assignable_to(self.db, *constraint) { + if ty + .when_assignable_to(self.db, *constraint, self.inferable) + .is_always_satisfied() + { self.add_type_mapping(bound_typevar, *constraint); return Ok(()); } diff --git a/crates/ty_python_semantic/src/types/infer/builder.rs b/crates/ty_python_semantic/src/types/infer/builder.rs index 717731e820f135..d88f5bf96c9a23 100644 --- a/crates/ty_python_semantic/src/types/infer/builder.rs +++ b/crates/ty_python_semantic/src/types/infer/builder.rs @@ -5887,10 +5887,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { let elt_tys = |collection_class: KnownClass| { let class_literal = collection_class.try_to_class_literal(self.db())?; let generic_context = class_literal.generic_context(self.db())?; - Some((class_literal, generic_context.variables(self.db()))) + Some(( + class_literal, + generic_context, + generic_context.variables(self.db()), + )) }; - let Some((class_literal, elt_tys)) = elt_tys(collection_class) else { + let Some((class_literal, generic_context, elt_tys)) = elt_tys(collection_class) else { // Infer the element types without type context, and fallback to unknown for // custom typesheds. for elt in elts.flatten().flatten() { @@ -5906,7 +5910,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { .map(|specialization| specialization.types(self.db())); // Create a set of constraints to infer a precise type for `T`. - let mut builder = SpecializationBuilder::new(self.db()); + let mut builder = + SpecializationBuilder::new(self.db(), generic_context.inferable_typevars(self.db())); match annotated_elt_tys { // The annotated type acts as a constraint for `T`. @@ -5971,7 +5976,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } } - let class_type = class_literal.apply_specialization(self.db(), |generic_context| { + let class_type = class_literal.apply_specialization(self.db(), |_| { builder.build(generic_context, TypeContext::default()) }); From 4ac1f2953378a0949a11f110e428f2dd04dbf283 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Wed, 1 Oct 2025 20:01:16 -0400 Subject: [PATCH 06/38] function signature typevars are inferable --- crates/ty_python_semantic/src/types.rs | 55 +++---- .../ty_python_semantic/src/types/call/bind.rs | 21 ++- crates/ty_python_semantic/src/types/class.rs | 8 +- .../ty_python_semantic/src/types/function.rs | 4 +- .../ty_python_semantic/src/types/generics.rs | 143 ++++++++++-------- .../ty_python_semantic/src/types/instance.rs | 14 +- .../src/types/protocol_class.rs | 6 +- .../src/types/signatures.rs | 30 +++- .../src/types/subclass_of.rs | 4 +- crates/ty_python_semantic/src/types/tuple.rs | 20 +-- 10 files changed, 166 insertions(+), 139 deletions(-) diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index cbd0b529d75e2a..dc8684a184ecc3 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -563,7 +563,7 @@ impl<'db> PropertyInstanceType<'db> { self, db: &'db dyn Db, other: Self, - inferable: &InferableTypeVars<'db>, + inferable: InferableTypeVars<'_, 'db>, ) -> ConstraintSet<'db> { self.is_equivalent_to_impl(db, other, inferable, &IsEquivalentVisitor::default()) } @@ -572,7 +572,7 @@ impl<'db> PropertyInstanceType<'db> { self, db: &'db dyn Db, other: Self, - inferable: &InferableTypeVars<'db>, + inferable: InferableTypeVars<'_, 'db>, visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { let getter_equivalence = if let Some(getter) = self.getter(db) { @@ -1452,7 +1452,7 @@ impl<'db> Type<'db> { /// /// See [`TypeRelation::Subtyping`] for more details. pub(crate) fn is_subtype_of(self, db: &'db dyn Db, target: Type<'db>) -> bool { - self.when_subtype_of(db, target, &InferableTypeVars::none()) + self.when_subtype_of(db, target, InferableTypeVars::None) .is_always_satisfied() } @@ -1460,7 +1460,7 @@ impl<'db> Type<'db> { self, db: &'db dyn Db, target: Type<'db>, - inferable: &InferableTypeVars<'db>, + inferable: InferableTypeVars<'_, 'db>, ) -> ConstraintSet<'db> { self.has_relation_to(db, target, inferable, TypeRelation::Subtyping) } @@ -1469,7 +1469,7 @@ impl<'db> Type<'db> { /// /// See [`TypeRelation::Assignability`] for more details. pub(crate) fn is_assignable_to(self, db: &'db dyn Db, target: Type<'db>) -> bool { - self.when_assignable_to(db, target, &InferableTypeVars::none()) + self.when_assignable_to(db, target, InferableTypeVars::None) .is_always_satisfied() } @@ -1477,7 +1477,7 @@ impl<'db> Type<'db> { self, db: &'db dyn Db, target: Type<'db>, - inferable: &InferableTypeVars<'db>, + inferable: InferableTypeVars<'_, 'db>, ) -> ConstraintSet<'db> { self.has_relation_to(db, target, inferable, TypeRelation::Assignability) } @@ -1486,20 +1486,15 @@ impl<'db> Type<'db> { /// /// See [`TypeRelation::Redundancy`] for more details. pub(crate) fn is_redundant_with(self, db: &'db dyn Db, other: Type<'db>) -> bool { - self.has_relation_to( - db, - other, - &InferableTypeVars::none(), - TypeRelation::Redundancy, - ) - .is_always_satisfied() + self.has_relation_to(db, other, InferableTypeVars::None, TypeRelation::Redundancy) + .is_always_satisfied() } fn has_relation_to( self, db: &'db dyn Db, target: Type<'db>, - inferable: &InferableTypeVars<'db>, + inferable: InferableTypeVars<'_, 'db>, relation: TypeRelation, ) -> ConstraintSet<'db> { self.has_relation_to_impl( @@ -1516,7 +1511,7 @@ impl<'db> Type<'db> { self, db: &'db dyn Db, target: Type<'db>, - inferable: &InferableTypeVars<'db>, + inferable: InferableTypeVars<'_, 'db>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -2319,7 +2314,7 @@ impl<'db> Type<'db> { /// /// [equivalent to]: https://typing.python.org/en/latest/spec/glossary.html#term-equivalent pub(crate) fn is_equivalent_to(self, db: &'db dyn Db, other: Type<'db>) -> bool { - self.when_equivalent_to(db, other, &InferableTypeVars::none()) + self.when_equivalent_to(db, other, InferableTypeVars::None) .is_always_satisfied() } @@ -2327,7 +2322,7 @@ impl<'db> Type<'db> { self, db: &'db dyn Db, other: Type<'db>, - inferable: &InferableTypeVars<'db>, + inferable: InferableTypeVars<'_, 'db>, ) -> ConstraintSet<'db> { self.is_equivalent_to_impl(db, other, inferable, &IsEquivalentVisitor::default()) } @@ -2336,7 +2331,7 @@ impl<'db> Type<'db> { self, db: &'db dyn Db, other: Type<'db>, - inferable: &InferableTypeVars<'db>, + inferable: InferableTypeVars<'_, 'db>, visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { if self == other { @@ -2442,7 +2437,7 @@ impl<'db> Type<'db> { /// This function aims to have no false positives, but might return wrong /// `false` answers in some cases. pub(crate) fn is_disjoint_from(self, db: &'db dyn Db, other: Type<'db>) -> bool { - self.when_disjoint_from(db, other, &InferableTypeVars::none()) + self.when_disjoint_from(db, other, InferableTypeVars::None) .is_always_satisfied() } @@ -2450,7 +2445,7 @@ impl<'db> Type<'db> { self, db: &'db dyn Db, other: Type<'db>, - inferable: &InferableTypeVars<'db>, + inferable: InferableTypeVars<'_, 'db>, ) -> ConstraintSet<'db> { self.is_disjoint_from_impl( db, @@ -2465,7 +2460,7 @@ impl<'db> Type<'db> { self, db: &'db dyn Db, other: Type<'db>, - inferable: &InferableTypeVars<'db>, + inferable: InferableTypeVars<'_, 'db>, disjointness_visitor: &IsDisjointVisitor<'db>, relation_visitor: &HasRelationToVisitor<'db>, ) -> ConstraintSet<'db> { @@ -2473,7 +2468,7 @@ impl<'db> Type<'db> { db: &'db dyn Db, protocol: ProtocolInstanceType<'db>, other: Type<'db>, - inferable: &InferableTypeVars<'db>, + inferable: InferableTypeVars<'_, 'db>, disjointness_visitor: &IsDisjointVisitor<'db>, relation_visitor: &HasRelationToVisitor<'db>, ) -> ConstraintSet<'db> { @@ -9771,7 +9766,7 @@ impl<'db> BoundMethodType<'db> { self, db: &'db dyn Db, other: Self, - inferable: &InferableTypeVars<'db>, + inferable: InferableTypeVars<'_, 'db>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -9805,7 +9800,7 @@ impl<'db> BoundMethodType<'db> { self, db: &'db dyn Db, other: Self, - inferable: &InferableTypeVars<'db>, + inferable: InferableTypeVars<'_, 'db>, visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { self.function(db) @@ -9938,7 +9933,7 @@ impl<'db> CallableType<'db> { self, db: &'db dyn Db, other: Self, - inferable: &InferableTypeVars<'db>, + inferable: InferableTypeVars<'_, 'db>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -9963,7 +9958,7 @@ impl<'db> CallableType<'db> { self, db: &'db dyn Db, other: Self, - inferable: &InferableTypeVars<'db>, + inferable: InferableTypeVars<'_, 'db>, visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { if self == other { @@ -10033,7 +10028,7 @@ impl<'db> KnownBoundMethodType<'db> { self, db: &'db dyn Db, other: Self, - inferable: &InferableTypeVars<'db>, + inferable: InferableTypeVars<'_, 'db>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -10101,7 +10096,7 @@ impl<'db> KnownBoundMethodType<'db> { self, db: &'db dyn Db, other: Self, - inferable: &InferableTypeVars<'db>, + inferable: InferableTypeVars<'_, 'db>, visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { match (self, other) { @@ -10967,7 +10962,7 @@ impl<'db> UnionType<'db> { self, db: &'db dyn Db, other: Self, - _inferable: &InferableTypeVars<'db>, + _inferable: InferableTypeVars<'_, 'db>, _visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { if self == other { @@ -11069,7 +11064,7 @@ impl<'db> IntersectionType<'db> { self, db: &'db dyn Db, other: Self, - _inferable: &InferableTypeVars<'db>, + _inferable: InferableTypeVars<'_, 'db>, _visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { if self == other { diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index 623e9999545e50..ee337b0607d6bf 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -3,7 +3,6 @@ //! [signatures][crate::types::signatures], we have to handle the fact that the callable might be a //! union of types, each of which might contain multiple overloads. -use std::borrow::Cow; use std::collections::HashSet; use std::fmt; @@ -601,7 +600,7 @@ impl<'db> Bindings<'db> { Some(KnownFunction::IsEquivalentTo) => { if let [Some(ty_a), Some(ty_b)] = overload.parameter_types() { let constraints = - ty_a.when_equivalent_to(db, *ty_b, &InferableTypeVars::none()); + ty_a.when_equivalent_to(db, *ty_b, InferableTypeVars::None); let tracked = TrackedConstraintSet::new(db, constraints); overload.set_return_type(Type::KnownInstance( KnownInstanceType::ConstraintSet(tracked), @@ -612,7 +611,7 @@ impl<'db> Bindings<'db> { Some(KnownFunction::IsSubtypeOf) => { if let [Some(ty_a), Some(ty_b)] = overload.parameter_types() { let constraints = - ty_a.when_subtype_of(db, *ty_b, &InferableTypeVars::none()); + ty_a.when_subtype_of(db, *ty_b, InferableTypeVars::None); let tracked = TrackedConstraintSet::new(db, constraints); overload.set_return_type(Type::KnownInstance( KnownInstanceType::ConstraintSet(tracked), @@ -623,7 +622,7 @@ impl<'db> Bindings<'db> { Some(KnownFunction::IsAssignableTo) => { if let [Some(ty_a), Some(ty_b)] = overload.parameter_types() { let constraints = - ty_a.when_assignable_to(db, *ty_b, &InferableTypeVars::none()); + ty_a.when_assignable_to(db, *ty_b, InferableTypeVars::None); let tracked = TrackedConstraintSet::new(db, constraints); overload.set_return_type(Type::KnownInstance( KnownInstanceType::ConstraintSet(tracked), @@ -634,7 +633,7 @@ impl<'db> Bindings<'db> { Some(KnownFunction::IsDisjointFrom) => { if let [Some(ty_a), Some(ty_b)] = overload.parameter_types() { let constraints = - ty_a.when_disjoint_from(db, *ty_b, &InferableTypeVars::none()); + ty_a.when_disjoint_from(db, *ty_b, InferableTypeVars::None); let tracked = TrackedConstraintSet::new(db, constraints); overload.set_return_type(Type::KnownInstance( KnownInstanceType::ConstraintSet(tracked), @@ -2589,12 +2588,10 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> { expected_ty = expected_ty.apply_specialization(self.db, specialization); } let inferable_typevars = match self.specialization { - Some(specialization) => Cow::Borrowed( - specialization - .generic_context(self.db) - .inferable_typevars(self.db), - ), - None => Cow::Owned(InferableTypeVars::none()), + Some(specialization) => specialization + .generic_context(self.db) + .inferable_typevars(self.db), + None => InferableTypeVars::None, }; // This is one of the few places where we want to check if there's _any_ specialization // where assignability holds; normally we want to check that assignability holds for @@ -2603,7 +2600,7 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> { // constraint set that we get from this assignability check, instead of inferring and // building them in an earlier separate step. if argument_type - .when_assignable_to(self.db, expected_ty, inferable_typevars.as_ref()) + .when_assignable_to(self.db, expected_ty, inferable_typevars) .is_never_satisfied() { let positional = matches!(argument, Argument::Positional | Argument::Synthetic) diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index a095b137fb98e8..327cf2bdb44ea6 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -536,7 +536,7 @@ impl<'db> ClassType<'db> { /// Return `true` if `other` is present in this class's MRO. pub(super) fn is_subclass_of(self, db: &'db dyn Db, other: ClassType<'db>) -> bool { - self.when_subclass_of(db, other, &InferableTypeVars::none()) + self.when_subclass_of(db, other, InferableTypeVars::None) .is_always_satisfied() } @@ -544,7 +544,7 @@ impl<'db> ClassType<'db> { self, db: &'db dyn Db, other: ClassType<'db>, - inferable: &InferableTypeVars<'db>, + inferable: InferableTypeVars<'_, 'db>, ) -> ConstraintSet<'db> { self.has_relation_to_impl( db, @@ -560,7 +560,7 @@ impl<'db> ClassType<'db> { self, db: &'db dyn Db, other: Self, - inferable: &InferableTypeVars<'db>, + inferable: InferableTypeVars<'_, 'db>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -611,7 +611,7 @@ impl<'db> ClassType<'db> { self, db: &'db dyn Db, other: ClassType<'db>, - inferable: &InferableTypeVars<'db>, + inferable: InferableTypeVars<'_, 'db>, visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { if self == other { diff --git a/crates/ty_python_semantic/src/types/function.rs b/crates/ty_python_semantic/src/types/function.rs index 347de29f6ad6bb..bcf49284ca1ea3 100644 --- a/crates/ty_python_semantic/src/types/function.rs +++ b/crates/ty_python_semantic/src/types/function.rs @@ -895,7 +895,7 @@ impl<'db> FunctionType<'db> { self, db: &'db dyn Db, other: Self, - inferable: &InferableTypeVars<'db>, + inferable: InferableTypeVars<'_, 'db>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -930,7 +930,7 @@ impl<'db> FunctionType<'db> { self, db: &'db dyn Db, other: Self, - inferable: &InferableTypeVars<'db>, + inferable: InferableTypeVars<'_, 'db>, visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { if self.normalized(db) == other.normalized(db) { diff --git a/crates/ty_python_semantic/src/types/generics.rs b/crates/ty_python_semantic/src/types/generics.rs index c80707b8087a75..ee78c1b7c4cbcf 100644 --- a/crates/ty_python_semantic/src/types/generics.rs +++ b/crates/ty_python_semantic/src/types/generics.rs @@ -140,68 +140,32 @@ pub(crate) fn typing_self<'db>( .map(Type::TypeVar) } -#[derive(Clone, Debug, Eq, PartialEq, salsa::Update)] -pub(crate) struct InferableTypeVars<'db> { - typevars: FxHashSet>, +#[derive(Clone, Copy, Debug)] +pub(crate) enum InferableTypeVars<'a, 'db> { + None, + One(&'a FxHashSet>), + Two( + &'a InferableTypeVars<'a, 'db>, + &'a InferableTypeVars<'a, 'db>, + ), } -impl<'db> InferableTypeVars<'db> { - pub(crate) fn none() -> Self { - InferableTypeVars { - typevars: FxHashSet::default(), - } - } - +impl<'a, 'db> InferableTypeVars<'a, 'db> { pub(crate) fn is_inferable(&self, bound_typevar: BoundTypeVarInstance<'db>) -> bool { - self.typevars.contains(&bound_typevar) - } - - fn from_bound_typevars( - db: &'db dyn Db, - bound_typevars: impl IntoIterator>, - ) -> Self { - struct CollectTypeVars<'db> { - typevars: RefCell>>, - seen_types: RefCell>>, - } - - impl<'db> TypeVisitor<'db> for CollectTypeVars<'db> { - fn should_visit_lazy_type_attributes(&self) -> bool { - true - } - - fn visit_bound_type_var_type( - &self, - db: &'db dyn Db, - bound_typevar: BoundTypeVarInstance<'db>, - ) { - self.typevars.borrow_mut().insert(bound_typevar); - walk_bound_type_var_type(db, bound_typevar, self); - } - - fn visit_type(&self, db: &'db dyn Db, ty: Type<'db>) { - match TypeKind::from(ty) { - TypeKind::Atomic => {} - TypeKind::NonAtomic(non_atomic_type) => { - if !self.seen_types.borrow_mut().insert(non_atomic_type) { - // If we have already seen this type, we can skip it. - return; - } - walk_non_atomic_type(db, non_atomic_type, self); - } - } + match self { + InferableTypeVars::None => false, + InferableTypeVars::One(typevars) => typevars.contains(&bound_typevar), + InferableTypeVars::Two(left, right) => { + left.is_inferable(bound_typevar) || right.is_inferable(bound_typevar) } } + } - let visitor = CollectTypeVars { - typevars: RefCell::new(FxHashSet::default()), - seen_types: RefCell::new(FxIndexSet::default()), - }; - for bound_typevar in bound_typevars { - visitor.visit_bound_type_var_type(db, bound_typevar); + pub(crate) fn merge(&'a self, other: Option<&'a InferableTypeVars<'a, 'db>>) -> Self { + match other { + Some(other) => InferableTypeVars::Two(self, other), + None => *self, } - let typevars = visitor.typevars.into_inner(); - Self { typevars } } } @@ -301,9 +265,62 @@ impl<'db> GenericContext<'db> { ) } + pub(crate) fn inferable_typevars(self, db: &'db dyn Db) -> InferableTypeVars<'db, 'db> { + // The first inner function is so that salsa is caching the FxHashSet, not the + // InferableTypeVars that wraps it. (That way InferableTypeVars can contain references, and + // doesn't need to impl salsa::Update.) + InferableTypeVars::One(self.inferable_typevars_inner(db)) + } + #[salsa::tracked(returns(ref))] - pub(crate) fn inferable_typevars(self, db: &'db dyn Db) -> InferableTypeVars<'db> { - InferableTypeVars::from_bound_typevars(db, self.variables(db)) + fn inferable_typevars_inner(self, db: &'db dyn Db) -> FxHashSet> { + // The second inner function is because the salsa macros seem to not like nested structs + // and impl blocks inside the function. + self.inferable_typevars_innerer(db) + } + + fn inferable_typevars_innerer(self, db: &'db dyn Db) -> FxHashSet> { + struct CollectTypeVars<'db> { + typevars: RefCell>>, + seen_types: RefCell>>, + } + + impl<'db> TypeVisitor<'db> for CollectTypeVars<'db> { + fn should_visit_lazy_type_attributes(&self) -> bool { + true + } + + fn visit_bound_type_var_type( + &self, + db: &'db dyn Db, + bound_typevar: BoundTypeVarInstance<'db>, + ) { + self.typevars.borrow_mut().insert(bound_typevar); + walk_bound_type_var_type(db, bound_typevar, self); + } + + fn visit_type(&self, db: &'db dyn Db, ty: Type<'db>) { + match TypeKind::from(ty) { + TypeKind::Atomic => {} + TypeKind::NonAtomic(non_atomic_type) => { + if !self.seen_types.borrow_mut().insert(non_atomic_type) { + // If we have already seen this type, we can skip it. + return; + } + walk_non_atomic_type(db, non_atomic_type, self); + } + } + } + } + + let visitor = CollectTypeVars { + typevars: RefCell::new(FxHashSet::default()), + seen_types: RefCell::new(FxIndexSet::default()), + }; + for bound_typevar in self.variables(db) { + visitor.visit_bound_type_var_type(db, bound_typevar); + } + visitor.typevars.into_inner() } pub(crate) fn variables( @@ -620,7 +637,7 @@ fn is_subtype_in_invariant_position<'db>( derived_materialization: MaterializationKind, base_type: &Type<'db>, base_materialization: MaterializationKind, - inferable: &InferableTypeVars<'db>, + inferable: InferableTypeVars<'_, 'db>, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, ) -> ConstraintSet<'db> { @@ -698,7 +715,7 @@ fn has_relation_in_invariant_position<'db>( derived_materialization: Option, base_type: &Type<'db>, base_materialization: Option, - inferable: &InferableTypeVars<'db>, + inferable: InferableTypeVars<'_, 'db>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -1033,7 +1050,7 @@ impl<'db> Specialization<'db> { self, db: &'db dyn Db, other: Self, - inferable: &InferableTypeVars<'db>, + inferable: InferableTypeVars<'_, 'db>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -1111,7 +1128,7 @@ impl<'db> Specialization<'db> { self, db: &'db dyn Db, other: Specialization<'db>, - inferable: &InferableTypeVars<'db>, + inferable: InferableTypeVars<'_, 'db>, visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { if self.materialization_kind(db) != other.materialization_kind(db) { @@ -1206,12 +1223,12 @@ impl<'db> PartialSpecialization<'_, 'db> { /// specialization of a generic function. pub(crate) struct SpecializationBuilder<'db> { db: &'db dyn Db, - inferable: &'db InferableTypeVars<'db>, + inferable: InferableTypeVars<'db, 'db>, types: FxHashMap, Type<'db>>, } impl<'db> SpecializationBuilder<'db> { - pub(crate) fn new(db: &'db dyn Db, inferable: &'db InferableTypeVars<'db>) -> Self { + pub(crate) fn new(db: &'db dyn Db, inferable: InferableTypeVars<'db, 'db>) -> Self { Self { db, inferable, diff --git a/crates/ty_python_semantic/src/types/instance.rs b/crates/ty_python_semantic/src/types/instance.rs index 722e08ba146cb6..6761f4992d5edd 100644 --- a/crates/ty_python_semantic/src/types/instance.rs +++ b/crates/ty_python_semantic/src/types/instance.rs @@ -121,7 +121,7 @@ impl<'db> Type<'db> { self, db: &'db dyn Db, protocol: ProtocolInstanceType<'db>, - inferable: &InferableTypeVars<'db>, + inferable: InferableTypeVars<'_, 'db>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -368,7 +368,7 @@ impl<'db> NominalInstanceType<'db> { self, db: &'db dyn Db, other: Self, - inferable: &InferableTypeVars<'db>, + inferable: InferableTypeVars<'_, 'db>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -401,7 +401,7 @@ impl<'db> NominalInstanceType<'db> { self, db: &'db dyn Db, other: Self, - inferable: &InferableTypeVars<'db>, + inferable: InferableTypeVars<'_, 'db>, visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { match (self.0, other.0) { @@ -423,7 +423,7 @@ impl<'db> NominalInstanceType<'db> { self, db: &'db dyn Db, other: Self, - inferable: &InferableTypeVars<'db>, + inferable: InferableTypeVars<'_, 'db>, disjointness_visitor: &IsDisjointVisitor<'db>, relation_visitor: &HasRelationToVisitor<'db>, ) -> ConstraintSet<'db> { @@ -650,7 +650,7 @@ impl<'db> ProtocolInstanceType<'db> { .satisfies_protocol( db, protocol, - &InferableTypeVars::none(), + InferableTypeVars::None, TypeRelation::Subtyping, &HasRelationToVisitor::default(), &IsDisjointVisitor::default(), @@ -709,7 +709,7 @@ impl<'db> ProtocolInstanceType<'db> { self, db: &'db dyn Db, other: Self, - _inferable: &InferableTypeVars<'db>, + _inferable: InferableTypeVars<'_, 'db>, _visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { if self == other { @@ -731,7 +731,7 @@ impl<'db> ProtocolInstanceType<'db> { self, _db: &'db dyn Db, _other: Self, - _inferable: &InferableTypeVars<'db>, + _inferable: InferableTypeVars<'_, 'db>, _visitor: &IsDisjointVisitor<'db>, ) -> ConstraintSet<'db> { ConstraintSet::from(false) diff --git a/crates/ty_python_semantic/src/types/protocol_class.rs b/crates/ty_python_semantic/src/types/protocol_class.rs index ab3caf37f698ab..78bf8cc6924b2b 100644 --- a/crates/ty_python_semantic/src/types/protocol_class.rs +++ b/crates/ty_python_semantic/src/types/protocol_class.rs @@ -236,7 +236,7 @@ impl<'db> ProtocolInterface<'db> { self, db: &'db dyn Db, other: Self, - inferable: &InferableTypeVars<'db>, + inferable: InferableTypeVars<'_, 'db>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -611,7 +611,7 @@ impl<'a, 'db> ProtocolMember<'a, 'db> { &self, db: &'db dyn Db, other: Type<'db>, - inferable: &InferableTypeVars<'db>, + inferable: InferableTypeVars<'_, 'db>, disjointness_visitor: &IsDisjointVisitor<'db>, relation_visitor: &HasRelationToVisitor<'db>, ) -> ConstraintSet<'db> { @@ -636,7 +636,7 @@ impl<'a, 'db> ProtocolMember<'a, 'db> { &self, db: &'db dyn Db, other: Type<'db>, - inferable: &InferableTypeVars<'db>, + inferable: InferableTypeVars<'_, 'db>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, diff --git a/crates/ty_python_semantic/src/types/signatures.rs b/crates/ty_python_semantic/src/types/signatures.rs index 47ad8a23a7e982..5c67132b8daf24 100644 --- a/crates/ty_python_semantic/src/types/signatures.rs +++ b/crates/ty_python_semantic/src/types/signatures.rs @@ -179,7 +179,7 @@ impl<'db> CallableSignature<'db> { &self, db: &'db dyn Db, other: &Self, - inferable: &InferableTypeVars<'db>, + inferable: InferableTypeVars<'_, 'db>, ) -> ConstraintSet<'db> { self.has_relation_to_impl( db, @@ -195,7 +195,7 @@ impl<'db> CallableSignature<'db> { &self, db: &'db dyn Db, other: &Self, - inferable: &InferableTypeVars<'db>, + inferable: InferableTypeVars<'_, 'db>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -217,7 +217,7 @@ impl<'db> CallableSignature<'db> { db: &'db dyn Db, self_signatures: &[Signature<'db>], other_signatures: &[Signature<'db>], - inferable: &InferableTypeVars<'db>, + inferable: InferableTypeVars<'_, 'db>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -283,7 +283,7 @@ impl<'db> CallableSignature<'db> { &self, db: &'db dyn Db, other: &Self, - inferable: &InferableTypeVars<'db>, + inferable: InferableTypeVars<'_, 'db>, visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { match (self.overloads.as_slice(), other.overloads.as_slice()) { @@ -601,9 +601,18 @@ impl<'db> Signature<'db> { &self, db: &'db dyn Db, other: &Signature<'db>, - inferable: &InferableTypeVars<'db>, + inferable: InferableTypeVars<'_, 'db>, visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { + // The typevars in self and other should also be considered inferable when checking whether + // two signatures are equivalent. + let self_inferable = + (self.generic_context).map(|generic_context| generic_context.inferable_typevars(db)); + let other_inferable = + (other.generic_context).map(|generic_context| generic_context.inferable_typevars(db)); + let inferable = inferable.merge(self_inferable.as_ref()); + let inferable = inferable.merge(other_inferable.as_ref()); + let mut result = ConstraintSet::from(true); let mut check_types = |self_type: Option>, other_type: Option>| { let self_type = self_type.unwrap_or(Type::unknown()); @@ -688,11 +697,20 @@ impl<'db> Signature<'db> { &self, db: &'db dyn Db, other: &Signature<'db>, - inferable: &InferableTypeVars<'db>, + inferable: InferableTypeVars<'_, 'db>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, ) -> ConstraintSet<'db> { + // The typevars in self and other should also be considered inferable when checking whether + // two signatures are equivalent. + let self_inferable = + (self.generic_context).map(|generic_context| generic_context.inferable_typevars(db)); + let other_inferable = + (other.generic_context).map(|generic_context| generic_context.inferable_typevars(db)); + let inferable = inferable.merge(self_inferable.as_ref()); + let inferable = inferable.merge(other_inferable.as_ref()); + /// A helper struct to zip two slices of parameters together that provides control over the /// two iterators individually. It also keeps track of the current parameter in each /// iterator. diff --git a/crates/ty_python_semantic/src/types/subclass_of.rs b/crates/ty_python_semantic/src/types/subclass_of.rs index d28185faaf6289..db55132c1ea131 100644 --- a/crates/ty_python_semantic/src/types/subclass_of.rs +++ b/crates/ty_python_semantic/src/types/subclass_of.rs @@ -136,7 +136,7 @@ impl<'db> SubclassOfType<'db> { self, db: &'db dyn Db, other: SubclassOfType<'db>, - inferable: &InferableTypeVars<'db>, + inferable: InferableTypeVars<'_, 'db>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -174,7 +174,7 @@ impl<'db> SubclassOfType<'db> { self, db: &'db dyn Db, other: Self, - _inferable: &InferableTypeVars<'db>, + _inferable: InferableTypeVars<'_, 'db>, _visitor: &IsDisjointVisitor<'db>, ) -> ConstraintSet<'db> { match (self.subclass_of, other.subclass_of) { diff --git a/crates/ty_python_semantic/src/types/tuple.rs b/crates/ty_python_semantic/src/types/tuple.rs index f482e38c3fca2b..e70c7708a290e9 100644 --- a/crates/ty_python_semantic/src/types/tuple.rs +++ b/crates/ty_python_semantic/src/types/tuple.rs @@ -259,7 +259,7 @@ impl<'db> TupleType<'db> { self, db: &'db dyn Db, other: Self, - inferable: &InferableTypeVars<'db>, + inferable: InferableTypeVars<'_, 'db>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -278,7 +278,7 @@ impl<'db> TupleType<'db> { self, db: &'db dyn Db, other: Self, - inferable: &InferableTypeVars<'db>, + inferable: InferableTypeVars<'_, 'db>, visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { self.tuple(db) @@ -446,7 +446,7 @@ impl<'db> FixedLengthTuple> { &self, db: &'db dyn Db, other: &Tuple>, - inferable: &InferableTypeVars<'db>, + inferable: InferableTypeVars<'_, 'db>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -533,7 +533,7 @@ impl<'db> FixedLengthTuple> { &self, db: &'db dyn Db, other: &Self, - inferable: &InferableTypeVars<'db>, + inferable: InferableTypeVars<'_, 'db>, visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { ConstraintSet::from(self.0.len() == other.0.len()).and(db, || { @@ -803,7 +803,7 @@ impl<'db> VariableLengthTuple> { &self, db: &'db dyn Db, other: &Tuple>, - inferable: &InferableTypeVars<'db>, + inferable: InferableTypeVars<'_, 'db>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -976,7 +976,7 @@ impl<'db> VariableLengthTuple> { &self, db: &'db dyn Db, other: &Self, - inferable: &InferableTypeVars<'db>, + inferable: InferableTypeVars<'_, 'db>, visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { self.variable @@ -1189,7 +1189,7 @@ impl<'db> Tuple> { &self, db: &'db dyn Db, other: &Self, - inferable: &InferableTypeVars<'db>, + inferable: InferableTypeVars<'_, 'db>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -1218,7 +1218,7 @@ impl<'db> Tuple> { &self, db: &'db dyn Db, other: &Self, - inferable: &InferableTypeVars<'db>, + inferable: InferableTypeVars<'_, 'db>, visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { match (self, other) { @@ -1238,7 +1238,7 @@ impl<'db> Tuple> { &self, db: &'db dyn Db, other: &Self, - inferable: &InferableTypeVars<'db>, + inferable: InferableTypeVars<'_, 'db>, disjointness_visitor: &IsDisjointVisitor<'db>, relation_visitor: &HasRelationToVisitor<'db>, ) -> ConstraintSet<'db> { @@ -1258,7 +1258,7 @@ impl<'db> Tuple> { db: &'db dyn Db, a: impl IntoIterator>, b: impl IntoIterator>, - inferable: &InferableTypeVars<'db>, + inferable: InferableTypeVars<'_, 'db>, disjointness_visitor: &IsDisjointVisitor<'db>, relation_visitor: &HasRelationToVisitor<'db>, ) -> ConstraintSet<'db> From 5ddfc1d404a6d8fe0f4b13dd9416462abbf257f6 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Wed, 1 Oct 2025 20:02:45 -0400 Subject: [PATCH 07/38] clippy --- .../ty_python_semantic/src/types/signatures.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/ty_python_semantic/src/types/signatures.rs b/crates/ty_python_semantic/src/types/signatures.rs index 5c67132b8daf24..04d6306dc2dace 100644 --- a/crates/ty_python_semantic/src/types/signatures.rs +++ b/crates/ty_python_semantic/src/types/signatures.rs @@ -702,15 +702,6 @@ impl<'db> Signature<'db> { relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, ) -> ConstraintSet<'db> { - // The typevars in self and other should also be considered inferable when checking whether - // two signatures are equivalent. - let self_inferable = - (self.generic_context).map(|generic_context| generic_context.inferable_typevars(db)); - let other_inferable = - (other.generic_context).map(|generic_context| generic_context.inferable_typevars(db)); - let inferable = inferable.merge(self_inferable.as_ref()); - let inferable = inferable.merge(other_inferable.as_ref()); - /// A helper struct to zip two slices of parameters together that provides control over the /// two iterators individually. It also keeps track of the current parameter in each /// iterator. @@ -772,6 +763,15 @@ impl<'db> Signature<'db> { } } + // The typevars in self and other should also be considered inferable when checking whether + // two signatures are equivalent. + let self_inferable = + (self.generic_context).map(|generic_context| generic_context.inferable_typevars(db)); + let other_inferable = + (other.generic_context).map(|generic_context| generic_context.inferable_typevars(db)); + let inferable = inferable.merge(self_inferable.as_ref()); + let inferable = inferable.merge(other_inferable.as_ref()); + let mut result = ConstraintSet::from(true); let mut check_types = |type1: Option>, type2: Option>| { let type1 = type1.unwrap_or(Type::unknown()); From 7d07b84a0cab465760de36f5372d5ed7c001ebd2 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Thu, 2 Oct 2025 13:18:32 -0400 Subject: [PATCH 08/38] add display --- .../ty_python_semantic/src/types/generics.rs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/crates/ty_python_semantic/src/types/generics.rs b/crates/ty_python_semantic/src/types/generics.rs index ee78c1b7c4cbcf..d1440a133449dd 100644 --- a/crates/ty_python_semantic/src/types/generics.rs +++ b/crates/ty_python_semantic/src/types/generics.rs @@ -1,4 +1,5 @@ use std::cell::RefCell; +use std::fmt::Display; use itertools::Itertools; use ruff_python_ast as ast; @@ -167,6 +168,31 @@ impl<'a, 'db> InferableTypeVars<'a, 'db> { None => *self, } } + + // Keep this around for debugging purposes + #[expect(dead_code)] + pub(crate) fn display(&self, db: &'db dyn Db) -> impl Display { + fn find_typevars<'db>( + result: &mut FxHashSet>, + inferable: &InferableTypeVars<'_, 'db>, + ) { + match inferable { + InferableTypeVars::None => {} + InferableTypeVars::One(typevars) => result.extend(typevars.iter().copied()), + InferableTypeVars::Two(left, right) => { + find_typevars(result, left); + find_typevars(result, right); + } + } + } + + let mut typevars = FxHashSet::default(); + find_typevars(&mut typevars, self); + format!( + "[{}]", + typevars.into_iter().map(|btv| btv.display(db)).format(", ") + ) + } } #[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, get_size2::GetSize)] From 720c704b45124d67c0c1fb0f2e0439b5815bf122 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Thu, 2 Oct 2025 13:59:02 -0400 Subject: [PATCH 09/38] track inferable during calls better --- crates/ty_python_semantic/src/types/call/bind.rs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index ee337b0607d6bf..7ee0fa58286af4 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -2467,6 +2467,7 @@ struct ArgumentTypeChecker<'a, 'db> { call_expression_tcx: &'a TypeContext<'db>, errors: &'a mut Vec>, + inferable_typevars: InferableTypeVars<'db, 'db>, specialization: Option>, } @@ -2488,6 +2489,7 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> { parameter_tys, call_expression_tcx, errors, + inferable_typevars: InferableTypeVars::None, specialization: None, } } @@ -2524,8 +2526,8 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> { return; }; - let mut builder = - SpecializationBuilder::new(self.db, generic_context.inferable_typevars(self.db)); + self.inferable_typevars = generic_context.inferable_typevars(self.db); + let mut builder = SpecializationBuilder::new(self.db, self.inferable_typevars); // Note that we infer the annotated type _before_ the arguments if this call is part of // an annotated assignment, to closer match the order of any unions written in the type @@ -2587,12 +2589,6 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> { argument_type = argument_type.apply_specialization(self.db, specialization); expected_ty = expected_ty.apply_specialization(self.db, specialization); } - let inferable_typevars = match self.specialization { - Some(specialization) => specialization - .generic_context(self.db) - .inferable_typevars(self.db), - None => InferableTypeVars::None, - }; // This is one of the few places where we want to check if there's _any_ specialization // where assignability holds; normally we want to check that assignability holds for // _all_ specializations. @@ -2600,7 +2596,7 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> { // constraint set that we get from this assignability check, instead of inferring and // building them in an earlier separate step. if argument_type - .when_assignable_to(self.db, expected_ty, inferable_typevars) + .when_assignable_to(self.db, expected_ty, self.inferable_typevars) .is_never_satisfied() { let positional = matches!(argument, Argument::Positional | Argument::Synthetic) From 4a3e94ef3449a0d1726e3eb7864eabf1262ac74c Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Thu, 2 Oct 2025 14:07:24 -0400 Subject: [PATCH 10/38] use inferable here too --- crates/ty_python_semantic/src/types/generics.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/ty_python_semantic/src/types/generics.rs b/crates/ty_python_semantic/src/types/generics.rs index d1440a133449dd..3f05e66d1763e3 100644 --- a/crates/ty_python_semantic/src/types/generics.rs +++ b/crates/ty_python_semantic/src/types/generics.rs @@ -1326,7 +1326,9 @@ impl<'db> SpecializationBuilder<'db> { // without specializing `T` to `None`. if !matches!(formal, Type::ProtocolInstance(_)) && !actual.is_never() - && actual.is_subtype_of(self.db, formal) + && actual + .when_subtype_of(self.db, formal, self.inferable) + .is_always_satisfied() { return Ok(()); } From d890b410b3869e72e7ef15fc6221cfa073a20b5a Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Thu, 2 Oct 2025 16:01:24 -0400 Subject: [PATCH 11/38] more inferable --- .../ty_python_semantic/src/types/call/bind.rs | 40 +++++++++++++++---- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index 7ee0fa58286af4..4e25ec2ab6d649 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -1413,7 +1413,10 @@ impl<'db> CallableBinding<'db> { let parameter_type = overload.signature.parameters()[*parameter_index] .annotated_type() .unwrap_or(Type::unknown()); - if argument_type.is_assignable_to(db, parameter_type) { + if argument_type + .when_assignable_to(db, parameter_type, overload.inferable_typevars) + .is_always_satisfied() + { is_argument_assignable_to_any_overload = true; break 'overload; } @@ -1639,7 +1642,14 @@ impl<'db> CallableBinding<'db> { .unwrap_or(Type::unknown()); let first_parameter_type = &mut first_parameter_types[parameter_index]; if let Some(first_parameter_type) = first_parameter_type { - if !first_parameter_type.is_equivalent_to(db, current_parameter_type) { + if !first_parameter_type + .when_equivalent_to( + db, + current_parameter_type, + overload.inferable_typevars, + ) + .is_always_satisfied() + { participating_parameter_indexes.insert(parameter_index); } } else { @@ -1756,7 +1766,12 @@ impl<'db> CallableBinding<'db> { matching_overloads.all(|(_, overload)| { overload .return_type() - .is_equivalent_to(db, first_overload_return_type) + .when_equivalent_to( + db, + first_overload_return_type, + overload.inferable_typevars, + ) + .is_always_satisfied() }) } else { // No matching overload @@ -2725,7 +2740,14 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> { return; }; - if !key_type.is_assignable_to(self.db, KnownClass::Str.to_instance(self.db)) { + if !key_type + .when_assignable_to( + self.db, + KnownClass::Str.to_instance(self.db), + self.inferable_typevars, + ) + .is_always_satisfied() + { self.errors.push(BindingError::InvalidKeyType { argument_index: adjusted_argument_index, provided_ty: key_type, @@ -2760,8 +2782,8 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> { } } - fn finish(self) -> Option> { - self.specialization + fn finish(self) -> (InferableTypeVars<'db, 'db>, Option>) { + (self.inferable_typevars, self.specialization) } } @@ -2825,6 +2847,9 @@ pub(crate) struct Binding<'db> { /// Return type of the call. return_ty: Type<'db>, + /// The inferable typevars in this signature. + inferable_typevars: InferableTypeVars<'db, 'db>, + /// The specialization that was inferred from the argument types, if the callable is generic. specialization: Option>, @@ -2851,6 +2876,7 @@ impl<'db> Binding<'db> { callable_type: signature_type, signature_type, return_ty: Type::unknown(), + inferable_typevars: InferableTypeVars::None, specialization: None, argument_matches: Box::from([]), variadic_argument_matched_to_variadic_parameter: false, @@ -2922,7 +2948,7 @@ impl<'db> Binding<'db> { checker.infer_specialization(); checker.check_argument_types(); - self.specialization = checker.finish(); + (self.inferable_typevars, self.specialization) = checker.finish(); if let Some(specialization) = self.specialization { self.return_ty = self.return_ty.apply_specialization(db, specialization); } From 20f394c623c245a8f41e06b42e911a91422a32c6 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Fri, 3 Oct 2025 09:52:56 -0400 Subject: [PATCH 12/38] fix incorrect xarray ecosystem results --- crates/ty_python_semantic/src/types.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index dc8684a184ecc3..66d46e797e23f4 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -1644,7 +1644,8 @@ impl<'db> Type<'db> { // Note that this is not handled by the early return at the beginning of this method, // since subtyping between a TypeVar and an arbitrary other type cannot be guaranteed to be reflexive. (Type::TypeVar(lhs_bound_typevar), Type::TypeVar(rhs_bound_typevar)) - if lhs_bound_typevar.is_identical_to(db, rhs_bound_typevar) => + if !inferable.is_inferable(lhs_bound_typevar) + && lhs_bound_typevar.is_identical_to(db, rhs_bound_typevar) => { ConstraintSet::from(true) } From c76ba5d64c1e1ef23c5e8df314ffa6b04f272290 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Fri, 3 Oct 2025 15:06:55 -0400 Subject: [PATCH 13/38] track heap size --- crates/ty_python_semantic/src/types/generics.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ty_python_semantic/src/types/generics.rs b/crates/ty_python_semantic/src/types/generics.rs index 3f05e66d1763e3..302f2836a8772a 100644 --- a/crates/ty_python_semantic/src/types/generics.rs +++ b/crates/ty_python_semantic/src/types/generics.rs @@ -298,7 +298,7 @@ impl<'db> GenericContext<'db> { InferableTypeVars::One(self.inferable_typevars_inner(db)) } - #[salsa::tracked(returns(ref))] + #[salsa::tracked(returns(ref), heap_size=ruff_memory_usage::heap_size)] fn inferable_typevars_inner(self, db: &'db dyn Db) -> FxHashSet> { // The second inner function is because the salsa macros seem to not like nested structs // and impl blocks inside the function. From 88c388bfee435ab82e998c2f944ea1ff6d34c1d1 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Fri, 3 Oct 2025 15:12:46 -0400 Subject: [PATCH 14/38] fix fuzz panics --- .../ty_python_semantic/src/types/generics.rs | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/crates/ty_python_semantic/src/types/generics.rs b/crates/ty_python_semantic/src/types/generics.rs index 302f2836a8772a..661c70d5c0f899 100644 --- a/crates/ty_python_semantic/src/types/generics.rs +++ b/crates/ty_python_semantic/src/types/generics.rs @@ -298,7 +298,12 @@ impl<'db> GenericContext<'db> { InferableTypeVars::One(self.inferable_typevars_inner(db)) } - #[salsa::tracked(returns(ref), heap_size=ruff_memory_usage::heap_size)] + #[salsa::tracked( + returns(ref), + cycle_fn=inferable_typevars_cycle_recover, + cycle_initial=inferable_typevars_cycle_initial, + heap_size=ruff_memory_usage::heap_size, + )] fn inferable_typevars_inner(self, db: &'db dyn Db) -> FxHashSet> { // The second inner function is because the salsa macros seem to not like nested structs // and impl blocks inside the function. @@ -596,6 +601,22 @@ impl<'db> GenericContext<'db> { } } +fn inferable_typevars_cycle_recover<'db>( + _db: &'db dyn Db, + _value: &FxHashSet>, + _count: u32, + _self: GenericContext<'db>, +) -> salsa::CycleRecoveryAction>> { + salsa::CycleRecoveryAction::Iterate +} + +fn inferable_typevars_cycle_initial<'db>( + _db: &'db dyn Db, + _self: GenericContext<'db>, +) -> FxHashSet> { + FxHashSet::default() +} + #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub(super) enum LegacyGenericBase { Generic, From 5db9be89ff89c13dc7885efe76a488d12c40e39f Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Tue, 7 Oct 2025 09:06:32 -0400 Subject: [PATCH 15/38] absolutely consistent --- crates/ty_python_semantic/src/types.rs | 28 +++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 66d46e797e23f4..573b78b48fe66d 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -1624,16 +1624,20 @@ impl<'db> Type<'db> { // However, there is one exception to this general rule: for any given typevar `T`, // `T` will always be a subtype of any union containing `T`. // A similar rule applies in reverse to intersection types. - (Type::TypeVar(_), Type::Union(union)) if union.elements(db).contains(&self) => { + (Type::TypeVar(bound_typevar), Type::Union(union)) + if !inferable.is_inferable(bound_typevar) && union.elements(db).contains(&self) => + { ConstraintSet::from(true) } - (Type::Intersection(intersection), Type::TypeVar(_)) - if intersection.positive(db).contains(&target) => + (Type::Intersection(intersection), Type::TypeVar(bound_typevar)) + if !inferable.is_inferable(bound_typevar) + && intersection.positive(db).contains(&target) => { ConstraintSet::from(true) } - (Type::Intersection(intersection), Type::TypeVar(_)) - if intersection.negative(db).contains(&target) => + (Type::Intersection(intersection), Type::TypeVar(bound_typevar)) + if !inferable.is_inferable(bound_typevar) + && intersection.negative(db).contains(&target) => { ConstraintSet::from(false) } @@ -1834,7 +1838,8 @@ impl<'db> Type<'db> { } (_, Type::TypeVar(typevar)) - if relation.is_assignability() + if inferable.is_inferable(typevar) + && relation.is_assignability() && typevar.typevar(db).upper_bound(db).is_none_or(|bound| { !self .has_relation_to_impl( @@ -1863,7 +1868,11 @@ impl<'db> Type<'db> { } // TODO: Infer specializations here - (Type::TypeVar(_), _) | (_, Type::TypeVar(_)) => ConstraintSet::from(false), + (Type::TypeVar(bound_typevar), _) | (_, Type::TypeVar(bound_typevar)) + if inferable.is_inferable(bound_typevar) => + { + ConstraintSet::from(false) + } (Type::TypedDict(_), _) | (_, Type::TypedDict(_)) => { // TODO: Implement assignability and subtyping for TypedDict @@ -2298,6 +2307,11 @@ impl<'db> Type<'db> { // Other than the special cases enumerated above, `Instance` types and typevars are // never subtypes of any other variants + (Type::TypeVar(bound_typevar), _) => { + // All inferable cases should have been handled above + debug_assert!(!inferable.is_inferable(bound_typevar)); + ConstraintSet::from(false) + } (Type::NominalInstance(_), _) => ConstraintSet::from(false), } } From 53689ef80d1a9c3d97908b6054530f2791e0e1b7 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Tue, 7 Oct 2025 15:12:15 -0400 Subject: [PATCH 16/38] only infer inferable typevars --- crates/ty_python_semantic/src/types/generics.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/ty_python_semantic/src/types/generics.rs b/crates/ty_python_semantic/src/types/generics.rs index 661c70d5c0f899..47ed5742252a5e 100644 --- a/crates/ty_python_semantic/src/types/generics.rs +++ b/crates/ty_python_semantic/src/types/generics.rs @@ -1416,7 +1416,9 @@ impl<'db> SpecializationBuilder<'db> { } } - (Type::TypeVar(bound_typevar), ty) | (ty, Type::TypeVar(bound_typevar)) => { + (Type::TypeVar(bound_typevar), ty) | (ty, Type::TypeVar(bound_typevar)) + if self.inferable.is_inferable(bound_typevar) => + { match bound_typevar.typevar(self.db).bound_or_constraints(self.db) { Some(TypeVarBoundOrConstraints::UpperBound(bound)) => { if !ty From 30f322cd55763e44bb1b48ea2c3bf0cc6aae6827 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Tue, 7 Oct 2025 15:46:17 -0400 Subject: [PATCH 17/38] include inferable in snapshot --- crates/ty_python_semantic/src/types/call/bind.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index 4e25ec2ab6d649..1d29ef48d5de62 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -3042,6 +3042,7 @@ impl<'db> Binding<'db> { fn snapshot(&self) -> BindingSnapshot<'db> { BindingSnapshot { return_ty: self.return_ty, + inferable_typevars: self.inferable_typevars, specialization: self.specialization, argument_matches: self.argument_matches.clone(), parameter_tys: self.parameter_tys.clone(), @@ -3052,6 +3053,7 @@ impl<'db> Binding<'db> { fn restore(&mut self, snapshot: BindingSnapshot<'db>) { let BindingSnapshot { return_ty, + inferable_typevars, specialization, argument_matches, parameter_tys, @@ -3059,6 +3061,7 @@ impl<'db> Binding<'db> { } = snapshot; self.return_ty = return_ty; + self.inferable_typevars = inferable_typevars; self.specialization = specialization; self.argument_matches = argument_matches; self.parameter_tys = parameter_tys; @@ -3078,6 +3081,7 @@ impl<'db> Binding<'db> { /// Resets the state of this binding to its initial state. fn reset(&mut self) { self.return_ty = Type::unknown(); + self.inferable_typevars = InferableTypeVars::None; self.specialization = None; self.argument_matches = Box::from([]); self.parameter_tys = Box::from([]); @@ -3088,6 +3092,7 @@ impl<'db> Binding<'db> { #[derive(Clone, Debug)] struct BindingSnapshot<'db> { return_ty: Type<'db>, + inferable_typevars: InferableTypeVars<'db, 'db>, specialization: Option>, argument_matches: Box<[MatchedArgument<'db>]>, parameter_tys: Box<[Option>]>, @@ -3127,6 +3132,7 @@ impl<'db> CallableBindingSnapshot<'db> { // ... and update the snapshot with the current state of the binding. snapshot.return_ty = binding.return_ty; + snapshot.inferable_typevars = binding.inferable_typevars; snapshot.specialization = binding.specialization; snapshot .argument_matches From e959369f7a32e65e4c736b0c60825fe3b2975a48 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Fri, 10 Oct 2025 14:30:29 -0400 Subject: [PATCH 18/38] skip bidi for parameters with typevars --- .../ty_python_semantic/src/types/generics.rs | 8 ++++ .../src/types/infer/builder.rs | 44 ++++++++++++++++++- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/crates/ty_python_semantic/src/types/generics.rs b/crates/ty_python_semantic/src/types/generics.rs index 47ed5742252a5e..1cfe56e3a28b62 100644 --- a/crates/ty_python_semantic/src/types/generics.rs +++ b/crates/ty_python_semantic/src/types/generics.rs @@ -431,6 +431,14 @@ impl<'db> GenericContext<'db> { self.variables_inner(db).len() } + pub(crate) fn contains( + self, + db: &'db dyn Db, + bound_typevar: BoundTypeVarInstance<'db>, + ) -> bool { + self.variables_inner(db).contains_key(&bound_typevar) + } + pub(crate) fn signature(self, db: &'db dyn Db) -> Signature<'db> { let parameters = Parameters::new( self.variables(db) diff --git a/crates/ty_python_semantic/src/types/infer/builder.rs b/crates/ty_python_semantic/src/types/infer/builder.rs index d88f5bf96c9a23..7beae8d12f3e05 100644 --- a/crates/ty_python_semantic/src/types/infer/builder.rs +++ b/crates/ty_python_semantic/src/types/infer/builder.rs @@ -5357,6 +5357,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } // Retrieve the parameter type for the current argument in a given overload and its binding. + let db = self.db(); let parameter_type = |overload: &Binding<'db>, binding: &CallableBinding<'db>| { let argument_index = if binding.bound_type.is_some() { argument_index + 1 @@ -5369,7 +5370,48 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { return None; }; - overload.signature.parameters()[*parameter_index].annotated_type() + let parameter_type = + overload.signature.parameters()[*parameter_index].annotated_type()?; + + // TODO: For now, skip any parameter annotations that mention any of the overload's + // typevars. There are two issues: + // + // First, if we include those typevars in the type context that we use to infer the + // corresponding argument type, the typevars might end up appearing in the inferred + // argument type as well. As part of analyzing this call, we're going to (try to) + // infer a specialization of those typevars, and would need to substitute those + // typevars in the inferred argument type. We can't do that easily at the moment, + // since specialization inference occurs _after_ we've inferred argument types, and + // we can't _update_ an expression's inferred type after the fact. + // + // Second, certain kinds of arguments themselves have typevars that we need to + // infer specializations for. (For instance, passing the result of _another_ call + // to the argument of _this_ call, where both are calls to generic functions.) In + // that case, we want to "tie together" the typevars of the two calls so that we + // can infer their specializations at the same time — or at least, for the + // specialization of one to influence the specialization of the other. It's not yet + // clear how we're going to do that. (We might have to start inferring constraint + // sets for each expression, instead of simple types?) + // + // Regardless, for now, the expedient "solution" is to not perform bidi type + // checking for these kinds of parameters. + if let Some(generic_context) = overload.signature.generic_context { + let mentions_overload_typevars = any_over_type( + db, + parameter_type, + &|ty| { + ty.into_type_var().is_some_and(|bound_typevar| { + generic_context.contains(db, bound_typevar) + }) + }, + true, + ); + if mentions_overload_typevars { + return None; + } + } + + Some(parameter_type) }; // If there is only a single binding and overload, we can infer the argument directly with From 998fcae5cf20841781591028be4c8d078538071d Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Fri, 10 Oct 2025 15:05:26 -0400 Subject: [PATCH 19/38] clippy! --- crates/ty_python_semantic/src/types/generics.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/ty_python_semantic/src/types/generics.rs b/crates/ty_python_semantic/src/types/generics.rs index 1cfe56e3a28b62..db6fad615f56a4 100644 --- a/crates/ty_python_semantic/src/types/generics.rs +++ b/crates/ty_python_semantic/src/types/generics.rs @@ -686,6 +686,7 @@ pub(super) fn walk_specialization<'db, V: super::visitor::TypeVisitor<'db> + ?Si } } +#[expect(clippy::too_many_arguments)] fn is_subtype_in_invariant_position<'db>( db: &'db dyn Db, derived_type: &Type<'db>, From 5919ef21ebb9902ef58bf30736e36ff1ce8d46a0 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Sat, 11 Oct 2025 14:23:52 -0400 Subject: [PATCH 20/38] Implement TypeVar identity refactoring - Create TypeVarIdentity salsa-interned type with name, definition, and kind - Create BoundTypeVarIdentity for identity comparisons - Update TypeVarInstance to use identity field instead of separate fields - Update GenericContext and SpecializationBuilder to use BoundTypeVarIdentity as map keys - Remove is_identical_to methods in favor of direct identity comparison - All tests pass (271 passed, 0 failed) This ensures that typevars with the same logical identity but different materialized bounds are recognized as identical, fixing the regression where method calls on Top-materialized types fail. Generated with Claude Code assistance. --- PLAN.md | 210 ++++++++++++++++++ crates/ty_python_semantic/src/types.rs | 150 +++++++++---- .../ty_python_semantic/src/types/generics.rs | 81 ++++--- .../src/types/infer/builder.rs | 27 ++- 4 files changed, 381 insertions(+), 87 deletions(-) create mode 100644 PLAN.md diff --git a/PLAN.md b/PLAN.md new file mode 100644 index 00000000000000..b187514fdf4153 --- /dev/null +++ b/PLAN.md @@ -0,0 +1,210 @@ +# Plan: Fix TypeVar Identity Issue with Materialization + +## Problem + +When a `Top[C[T: (int, str)]]` type is used, method calls fail because the `Self` typevar in the method signature has a Bottom-materialized upper bound, while the `Self` in the class's inferable typevars set has no materialization. These are different `TypeVarInstance` objects in Salsa, causing `is_inferable()` to return false and the assignability check to fail. + +## Root Cause + +Two `TypeVarInstance` objects representing the same logical typevar (e.g., `Self` for a class) are treated as distinct when they differ only in their bounds/constraints due to materialization. This happens because: + +1. `TypeVarInstance` is salsa-interned and includes all fields (name, definition, bounds, variance, etc.) +2. When bounds are materialized, a new `TypeVarInstance` is created +3. Checks for "same typevar" use full equality, which includes the materialized bounds +4. The inferable typevars set uses `BoundTypeVarInstance` as keys, which includes the full `TypeVarInstance` + +## Solution + +Introduce a new concept of "typevar identity" that is separate from the full typevar instance. Two typevars have the same identity if they represent the same logical typevar, regardless of how their bounds have been materialized. + +## Implementation Steps + +### 1. Create `TypeVarIdentity` (salsa-interned) + +**Location**: `crates/ty_python_semantic/src/types.rs` + +**Fields** (moved from `TypeVarInstance`): +- `name: Name<'db>` - The typevar's name +- `definition: Option>` - Where the typevar was defined +- `kind: TypeVarKind` - Whether it's PEP 695, Legacy, or TypingSelf + +**Traits to implement**: +- `Debug` (via salsa) +- `Clone, Copy` (via salsa) +- `PartialEq, Eq` (via salsa) +- `Hash` (via salsa) +- `get_size2::GetSize` (via salsa) + +**Salsa attributes**: +```rust +#[salsa::interned(debug)] +pub struct TypeVarIdentity<'db> { + pub(crate) name: Name<'db>, + pub(crate) definition: Option>, + pub(crate) kind: TypeVarKind, +} +``` + +### 2. Create `BoundTypeVarIdentity` (non-interned) + +**Location**: `crates/ty_python_semantic/src/types.rs` + +**Fields**: +- `identity: TypeVarIdentity<'db>` - The typevar's identity +- `binding_context: BindingContext<'db>` - Where the typevar is bound + +**Traits to implement**: +- `Debug` +- `Clone, Copy` +- `PartialEq, Eq` +- `Hash` +- `get_size2::GetSize` + +This type identifies a specific binding of a typevar (e.g., `T@ClassC1` vs `T@FunctionF`). + +### 3. Update `TypeVarInstance` + +**Changes**: +- Add new field: `identity: TypeVarIdentity<'db>` +- Keep existing fields: `_bound_or_constraints`, `explicit_variance`, `_default`, `original` +- Remove fields moved to `TypeVarIdentity`: `name`, `definition`, `kind` + +**Constructor updates**: +- Create `TypeVarIdentity` first, then use it in `TypeVarInstance::new` +- Update all call sites that construct `TypeVarInstance` + +**Accessor methods**: +- Add forwarding methods for `name()`, `definition()`, `kind()` that delegate to `identity()` +- Keep existing methods for other fields + +### 4. Add `identity()` method to `BoundTypeVarInstance` + +**Method signature**: +```rust +pub(crate) fn identity(self, db: &'db dyn Db) -> BoundTypeVarIdentity<'db> { + BoundTypeVarIdentity { + identity: self.typevar(db).identity(db), + binding_context: self.binding_context(db), + } +} +``` + +### 5. Update `GenericContext` + +**Location**: `crates/ty_python_semantic/src/types/generics.rs` + +**Changes**: +- Change `variables_inner` from `FxOrderMap, ...>` to `FxOrderMap, ...>` +- Update `variables()` method to return `BoundTypeVarInstance` by looking up the full instance +- Update all methods that use `variables_inner` as a map key + +**Affected methods**: +- `from_typevar_instances()` - use `btv.identity(db)` as map key +- `variables()` - reconstruct `BoundTypeVarInstance` from stored identity +- `lookup()` - use identity for lookup + +### 6. Update `Specialization` + +**Location**: `crates/ty_python_semantic/src/types/generics.rs` + +**Changes**: +- The `generic_context` field already uses `GenericContext`, which will now use identities +- Verify that `types` array stays synchronized with context (might need to store parallel arrays or restructure) +- Update methods that iterate over typevars and types together + +### 7. Update `ConstraintSet` + +**Location**: `crates/ty_python_semantic/src/types.rs` (if it uses typevar keys) + +**Changes**: +- If `ConstraintSet` uses `BoundTypeVarInstance` as keys in any internal maps, update to use `BoundTypeVarIdentity` +- Search for uses of `BoundTypeVarInstance` as map keys or set elements + +### 8. Update `InferableTypeVars` + +**Location**: `crates/ty_python_semantic/src/types/generics.rs` + +**Changes**: +- Change from `FxHashSet>` to `FxHashSet>` +- Update `is_inferable()` to use `bound_typevar.identity(db)` for lookup +- Update `inferable_typevars_innerer()` to collect identities instead of full instances + +### 9. Remove `is_identical_to` methods + +**Location**: `crates/ty_python_semantic/src/types.rs` + +**Changes**: +- Remove `TypeVarInstance::is_identical_to()` method entirely +- Remove `BoundTypeVarInstance::is_identical_to()` method entirely +- Update all call sites to use direct equality comparison instead: + - `btv1.identity(db) == btv2.identity(db)` for bound typevars + - `tv1.identity(db) == tv2.identity(db)` for typevar instances +- Search for all uses of `is_identical_to` and replace with identity comparisons + +**Rationale**: With explicit identity types, we can use standard `==` comparison instead of custom methods. The identity types already implement `Eq` and `PartialEq` correctly. + +### 10. Update Display implementations + +**Location**: `crates/ty_python_semantic/src/types/display.rs` + +**Changes**: +- Update `DisplayBoundTypeVarInstance` to use `typevar.identity(db).name(db)` +- Verify all display code still works correctly + +## Testing Strategy + +### Primary Testing: Existing Test Suite +Since this branch is based on `main` (not `work`), the regression we identified doesn't exist yet in this branch. Our primary testing goal is to ensure all existing tests continue to pass after the refactoring. + +**Process**: +1. Run the full test suite in the new branch after each major step +2. Ensure no regressions are introduced by the refactoring +3. Fix any test failures that arise from the structural changes + +### Testing the Regression Fix +To verify that this change fixes the regression that would be introduced by the `work` branch changes: + +**Process**: +1. In the `work` worktree, temporarily merge the `dcreager/typevar-identity` branch: + ```bash + cd /home/dcreager/git/ruff/work + git merge dcreager/typevar-identity + ``` + +2. Build and run the test case from `/home/dcreager/Documents/scratch/ty/top.py`: + ```bash + cargo build --bin ty + target/debug/ty check /home/dcreager/Documents/scratch/ty/top.py + ``` + +3. Verify that the `invalid-argument-type` error no longer occurs for `x.method()` + +4. Revert the merge to restore the `work` branch: + ```bash + git merge --abort # or git reset --hard HEAD if merge was completed + ``` + +### Why This Approach? +- The `main` branch doesn't have the inferable typevar changes yet, so the bug doesn't manifest +- The `work` branch has the inferable changes that trigger the bug +- By merging our fix into `work`, we can test that it resolves the issue +- We revert to keep the `work` and new feature branches independent + +## Migration Notes + +- This is a significant refactoring that touches core type system code +- All places that construct `TypeVarInstance` must be updated +- All places that use `BoundTypeVarInstance` for identity/lookup must use `BoundTypeVarIdentity` +- Salsa will need to recompute caches after these changes +- Performance should be similar or slightly better (smaller identity keys for lookups) + +## Rollout + +1. Implement changes incrementally, ensuring tests pass at each step +2. Start with creating new types (`TypeVarIdentity`, `BoundTypeVarIdentity`) +3. Update `TypeVarInstance` structure +4. Update all construction sites +5. Update data structures (`GenericContext`, `InferableTypeVars`) +6. Update comparison logic +7. Run full test suite +8. Test with real-world cases including the motivating example diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index dc27519dbca7a5..1e64834615acab 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -1635,7 +1635,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) } @@ -7782,19 +7782,42 @@ pub enum TypeVarKind { /// the typevar is defined and immediately bound to a single generic context. Just like in the /// legacy case, we will create a `TypeVarInstance` and [`BoundTypeVarInstance`], and the type of /// `T` at `[1]` and `[2]` will be that `TypeVarInstance` and `BoundTypeVarInstance`, respectively. + +/// 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 type var instance's salsa-assigned id and not on its values. -/// The id may change between runs, or when the type var instance was garbage collected and recreated. +/// 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 TypeVarInstance<'db> { +pub struct TypeVarIdentity<'db> { /// The name of this TypeVar (e.g. `T`) #[returns(ref)] - name: ast::name::Name, + pub(crate) name: ast::name::Name, /// The type var's definition (None if synthesized) - pub definition: Option>, + 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 with its bounds and constraints. +/// +/// # Ordering +/// Ordering is based on the type var instance's salsa-assigned id and not on its values. +/// The id may change between runs, or when the type var instance was garbage collected and recreated. +#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)] +#[derive(PartialOrd, Ord)] +pub struct TypeVarInstance<'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 @@ -7808,8 +7831,6 @@ pub struct TypeVarInstance<'db> { /// `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 @@ -7860,6 +7881,21 @@ impl<'db> TypeVarInstance<'db> { BoundTypeVarInstance::new(db, self, BindingContext::Definition(binding_context)) } + /// Get the name of this typevar (forwarded from identity) + pub(crate) fn name(self, db: &'db dyn Db) -> &'db ast::name::Name { + self.identity(db).name(db) + } + + /// Get the definition of this typevar (forwarded from identity) + pub(crate) fn definition(self, db: &'db dyn Db) -> Option> { + self.identity(db).definition(db) + } + + /// Get the kind of this typevar (forwarded from identity) + 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) } @@ -7903,8 +7939,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) => { @@ -7924,7 +7959,6 @@ impl<'db> TypeVarInstance<'db> { .lazy_default(db) .map(|ty| ty.normalized_impl(db, visitor).into()), }), - self.kind(db), self.original(db), ) } @@ -7937,8 +7971,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( @@ -7970,7 +8003,6 @@ impl<'db> TypeVarInstance<'db> { .lazy_default(db) .map(|ty| ty.materialize(db, materialization_kind, visitor).into()), }), - self.kind(db), self.original(db), ) } @@ -8021,20 +8053,14 @@ impl<'db> TypeVarInstance<'db> { 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) => { @@ -8044,14 +8070,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), + None, // _default self.original(db), )) } @@ -8179,6 +8209,25 @@ 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)] +pub struct BoundTypeVarIdentity<'db> { + pub(crate) identity: TypeVarIdentity<'db>, + pub(crate) binding_context: BindingContext<'db>, +} + +// The Salsa heap is tracked separately for the inner types. +impl get_size2::GetSize for BoundTypeVarIdentity<'_> { + fn get_heap_size(&self) -> usize { + 0 + } +} + /// 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)] @@ -8192,6 +8241,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 differences in their bounds 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( @@ -8199,17 +8259,22 @@ impl<'db> BoundTypeVarInstance<'db> { name: &'static str, variance: TypeVarVariance, ) -> Self { + let identity = TypeVarIdentity::new( + db, + Name::new_static(name), + None, // definition + TypeVarKind::Pep695, + ); + Self::new( db, TypeVarInstance::new( db, - Name::new_static(name), - None, // definition + identity, None, // _bound_or_constraints Some(variance), None, // _default - TypeVarKind::Pep695, - None, + None, // original ), BindingContext::Synthetic, ) @@ -8221,34 +8286,27 @@ impl<'db> BoundTypeVarInstance<'db> { upper_bound: Type<'db>, binding_context: BindingContext<'db>, ) -> Self { + let identity = TypeVarIdentity::new( + db, + Name::new_static("Self"), + None, // definition + TypeVarKind::TypingSelf, + ); + Self::new( db, TypeVarInstance::new( db, - Name::new_static("Self"), - None, + identity, Some(TypeVarBoundOrConstraints::UpperBound(upper_bound).into()), Some(TypeVarVariance::Invariant), - None, - TypeVarKind::TypingSelf, - None, + None, // _default + None, // original ), 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)) - } - pub(crate) fn variance_with_polarity( self, db: &'db dyn Db, diff --git a/crates/ty_python_semantic/src/types/generics.rs b/crates/ty_python_semantic/src/types/generics.rs index eaf1c73f434cd2..42ddc3603e75ef 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,10 +109,16 @@ 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)), + TypeVarKind::TypingSelf, + ); + + let typevar = TypeVarInstance::new( + db, + identity, Some( TypeVarBoundOrConstraints::UpperBound(Type::instance( db, @@ -125,7 +131,6 @@ pub(crate) fn typing_self<'db>( // [spec]: https://typing.python.org/en/latest/spec/generics.html#self Some(TypeVarVariance::Invariant), None, - TypeVarKind::TypingSelf, None, ); @@ -160,7 +165,7 @@ impl GenericContextTypeVarOptions { #[derive(PartialOrd, Ord)] pub struct GenericContext<'db> { #[returns(ref)] - variables_inner: FxOrderMap, GenericContextTypeVarOptions>, + variables_inner: FxOrderMap, (BoundTypeVarInstance<'db>, GenericContextTypeVarOptions)>, } pub(super) fn walk_generic_context<'db, V: super::visitor::TypeVisitor<'db> + ?Sized>( @@ -181,7 +186,13 @@ impl<'db> GenericContext<'db> { db: &'db dyn Db, variables: impl IntoIterator, GenericContextTypeVarOptions)>, ) -> Self { - Self::new_internal(db, variables.into_iter().collect::>()) + Self::new_internal( + db, + variables + .into_iter() + .map(|(btv, opts)| (btv.identity(db), (btv, opts))) + .collect::>(), + ) } /// Creates a generic context from a list of PEP-695 type parameters. @@ -218,7 +229,7 @@ impl<'db> GenericContext<'db> { db, self.variables_inner(db) .iter() - .map(|(bound_typevar, options)| (*bound_typevar, options.promote_literals())), + .map(|(_, (bound_typevar, options))| (*bound_typevar, options.promote_literals())), ) } @@ -230,7 +241,7 @@ impl<'db> GenericContext<'db> { self.variables_inner(db) .iter() .chain(other.variables_inner(db).iter()) - .map(|(bound_typevar, options)| (*bound_typevar, *options)), + .map(|(_, (bound_typevar, options))| (*bound_typevar, *options)), ) } @@ -238,7 +249,7 @@ impl<'db> GenericContext<'db> { self, db: &'db dyn Db, ) -> impl ExactSizeIterator> + Clone { - self.variables_inner(db).keys().copied() + self.variables_inner(db).values().map(|(btv, _)| *btv) } fn variable_from_type_param( @@ -388,7 +399,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_typevar( @@ -396,8 +407,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 @@ -481,7 +493,10 @@ impl<'db> GenericContext<'db> { } fn heap_size( - (variables,): &(FxOrderMap, GenericContextTypeVarOptions>,), + (variables,): &(FxOrderMap< + BoundTypeVarIdentity<'db>, + (BoundTypeVarInstance<'db>, GenericContextTypeVarOptions), + >,), ) -> usize { ruff_memory_usage::order_map_heap_size(variables) } @@ -733,7 +748,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(); @@ -761,7 +776,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() } @@ -1114,7 +1129,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() } } @@ -1123,7 +1138,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> { @@ -1143,20 +1158,22 @@ 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, options))| { + 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 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))); - } + 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) @@ -1164,7 +1181,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 c6b817c78962d2..f736bf0d18f927 100644 --- a/crates/ty_python_semantic/src/types/infer/builder.rs +++ b/crates/ty_python_semantic/src/types/infer/builder.rs @@ -95,8 +95,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}; @@ -2959,15 +2960,19 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { if bound_or_constraint.is_some() || default.is_some() { self.deferred.insert(definition); } - let ty = Type::KnownInstance(KnownInstanceType::TypeVar(TypeVarInstance::new( + let identity = TypeVarIdentity::new( self.db(), &name.id, Some(definition), + TypeVarKind::Pep695, + ); + let ty = Type::KnownInstance(KnownInstanceType::TypeVar(TypeVarInstance::new( + self.db(), + identity, bound_or_constraint, - None, + None, // explicit_variance default.as_deref().map(|_| TypeVarDefaultEvaluation::Lazy), - TypeVarKind::Pep695, - None, + None, // original ))); self.add_declaration_with_binding( node.into(), @@ -4298,15 +4303,19 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { self.deferred.insert(definition); } - Type::KnownInstance(KnownInstanceType::TypeVar(TypeVarInstance::new( + let identity = TypeVarIdentity::new( db, target_name, Some(definition), + TypeVarKind::Legacy, + ); + Type::KnownInstance(KnownInstanceType::TypeVar(TypeVarInstance::new( + db, + identity, bound_or_constraints, Some(variance), default, - TypeVarKind::Legacy, - None, + None, // original ))) } From 7cc7faac46e0198e35254e61b4629e8df2b2da5c Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Sat, 11 Oct 2025 14:36:41 -0400 Subject: [PATCH 21/38] clean up claudisms --- crates/ty_python_semantic/src/types.rs | 110 +++++++----------- .../ty_python_semantic/src/types/generics.rs | 28 ++--- 2 files changed, 60 insertions(+), 78 deletions(-) diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 1e64834615acab..58f4ec350156a6 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -7750,7 +7750,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 @@ -7782,33 +7807,6 @@ pub enum TypeVarKind { /// the typevar is defined and immediately bound to a single generic context. Just like in the /// legacy case, we will create a `TypeVarInstance` and [`BoundTypeVarInstance`], and the type of /// `T` at `[1]` and `[2]` will be that `TypeVarInstance` and `BoundTypeVarInstance`, respectively. - -/// 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 with its bounds and constraints. /// /// # Ordering /// Ordering is based on the type var instance's salsa-assigned id and not on its values. @@ -7881,17 +7879,14 @@ impl<'db> TypeVarInstance<'db> { BoundTypeVarInstance::new(db, self, BindingContext::Definition(binding_context)) } - /// Get the name of this typevar (forwarded from identity) pub(crate) fn name(self, db: &'db dyn Db) -> &'db ast::name::Name { self.identity(db).name(db) } - /// Get the definition of this typevar (forwarded from identity) pub(crate) fn definition(self, db: &'db dyn Db) -> Option> { self.identity(db).definition(db) } - /// Get the kind of this typevar (forwarded from identity) pub(crate) fn kind(self, db: &'db dyn Db) -> TypeVarKind { self.identity(db).kind(db) } @@ -8215,19 +8210,12 @@ impl<'db> BindingContext<'db> { /// 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)] +#[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>, } -// The Salsa heap is tracked separately for the inner types. -impl get_size2::GetSize for BoundTypeVarIdentity<'_> { - fn get_heap_size(&self) -> usize { - 0 - } -} - /// 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)] @@ -8243,8 +8231,8 @@ 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 differences in their bounds due to materialization. + /// 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), @@ -8265,19 +8253,15 @@ impl<'db> BoundTypeVarInstance<'db> { None, // definition TypeVarKind::Pep695, ); - - Self::new( + let typevar = TypeVarInstance::new( db, - TypeVarInstance::new( - db, - identity, - None, // _bound_or_constraints - Some(variance), - None, // _default - None, // original - ), - BindingContext::Synthetic, - ) + identity, + None, // _bound_or_constraints + Some(variance), + None, // _default + None, // original + ); + Self::new(db, typevar, BindingContext::Synthetic) } /// Create a new synthetic `Self` type variable with the given upper bound. @@ -8292,19 +8276,15 @@ impl<'db> BoundTypeVarInstance<'db> { None, // definition TypeVarKind::TypingSelf, ); - - Self::new( + let typevar = TypeVarInstance::new( db, - TypeVarInstance::new( - db, - identity, - Some(TypeVarBoundOrConstraints::UpperBound(upper_bound).into()), - Some(TypeVarVariance::Invariant), - None, // _default - None, // original - ), - binding_context, - ) + identity, + Some(TypeVarBoundOrConstraints::UpperBound(upper_bound).into()), + Some(TypeVarVariance::Invariant), + None, // _default + None, // original + ); + Self::new(db, typevar, binding_context) } pub(crate) fn variance_with_polarity( diff --git a/crates/ty_python_semantic/src/types/generics.rs b/crates/ty_python_semantic/src/types/generics.rs index 42ddc3603e75ef..5ca9085fa5931f 100644 --- a/crates/ty_python_semantic/src/types/generics.rs +++ b/crates/ty_python_semantic/src/types/generics.rs @@ -115,17 +115,14 @@ pub(crate) fn typing_self<'db>( Some(class.definition(db)), TypeVarKind::TypingSelf, ); - + let bounds = TypeVarBoundOrConstraints::UpperBound(Type::instance( + db, + class.identity_specialization(db, typevar_to_type), + )); let typevar = TypeVarInstance::new( db, identity, - Some( - TypeVarBoundOrConstraints::UpperBound(Type::instance( - db, - class.identity_specialization(db, typevar_to_type), - )) - .into(), - ), + 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 @@ -165,7 +162,10 @@ impl GenericContextTypeVarOptions { #[derive(PartialOrd, Ord)] pub struct GenericContext<'db> { #[returns(ref)] - variables_inner: FxOrderMap, (BoundTypeVarInstance<'db>, GenericContextTypeVarOptions)>, + variables_inner: FxOrderMap< + BoundTypeVarIdentity<'db>, + (BoundTypeVarInstance<'db>, GenericContextTypeVarOptions), + >, } pub(super) fn walk_generic_context<'db, V: super::visitor::TypeVisitor<'db> + ?Sized>( @@ -493,10 +493,12 @@ impl<'db> GenericContext<'db> { } fn heap_size( - (variables,): &(FxOrderMap< - BoundTypeVarIdentity<'db>, - (BoundTypeVarInstance<'db>, GenericContextTypeVarOptions), - >,), + (variables,): &( + FxOrderMap< + BoundTypeVarIdentity<'db>, + (BoundTypeVarInstance<'db>, GenericContextTypeVarOptions), + >, + ), ) -> usize { ruff_memory_usage::order_map_heap_size(variables) } From 0644e0285463c381f32bdc326e8e3f43855d7fc9 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Sat, 11 Oct 2025 14:43:31 -0400 Subject: [PATCH 22/38] Refactor GenericContext to use GenericContextTypeVar struct Replace tuple value type in GenericContext.variables_inner with a proper struct. This change: - Renames GenericContextTypeVarOptions to GenericContextTypeVar - Moves BoundTypeVarInstance into the struct alongside should_promote_literals - Updates all methods that use variables_inner (promote_literals, merge, variables, heap_size, and SpecializationBuilder.build) This makes the code cleaner and more maintainable by using a proper struct instead of a tuple for the map values. --- .../ty_python_semantic/src/types/generics.rs | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/crates/ty_python_semantic/src/types/generics.rs b/crates/ty_python_semantic/src/types/generics.rs index 5ca9085fa5931f..718b197b4c958a 100644 --- a/crates/ty_python_semantic/src/types/generics.rs +++ b/crates/ty_python_semantic/src/types/generics.rs @@ -141,12 +141,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 @@ -162,10 +170,7 @@ impl GenericContextTypeVarOptions { #[derive(PartialOrd, Ord)] pub struct GenericContext<'db> { #[returns(ref)] - variables_inner: FxOrderMap< - BoundTypeVarIdentity<'db>, - (BoundTypeVarInstance<'db>, GenericContextTypeVarOptions), - >, + variables_inner: FxOrderMap, GenericContextTypeVar<'db>>, } pub(super) fn walk_generic_context<'db, V: super::visitor::TypeVisitor<'db> + ?Sized>( @@ -184,13 +189,13 @@ 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() - .map(|(btv, opts)| (btv.identity(db), (btv, opts))) + .map(|var| (var.bound_typevar.identity(db), var)) .collect::>(), ) } @@ -218,7 +223,7 @@ impl<'db> GenericContext<'db> { db, type_params .into_iter() - .map(|bound_typevar| (bound_typevar, GenericContextTypeVarOptions::default())), + .map(GenericContextTypeVar::new), ) } @@ -228,8 +233,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(|var| var.promote_literals()), ) } @@ -239,9 +244,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(), ) } @@ -249,7 +254,7 @@ impl<'db> GenericContext<'db> { self, db: &'db dyn Db, ) -> impl ExactSizeIterator> + Clone { - self.variables_inner(db).values().map(|(btv, _)| *btv) + self.variables_inner(db).values().map(|var| var.bound_typevar) } fn variable_from_type_param( @@ -493,12 +498,7 @@ impl<'db> GenericContext<'db> { } fn heap_size( - (variables,): &( - FxOrderMap< - BoundTypeVarIdentity<'db>, - (BoundTypeVarInstance<'db>, GenericContextTypeVarOptions), - >, - ), + (variables,): &(FxOrderMap, GenericContextTypeVar<'db>>,), ) -> usize { ruff_memory_usage::order_map_heap_size(variables) } @@ -1161,14 +1161,14 @@ impl<'db> SpecializationBuilder<'db> { .and_then(|annotation| annotation.specialization_of(self.db, None)); let types = (generic_context.variables_inner(self.db).iter()).map( - |(identity, (variable, options))| { + |(identity, var)| { 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 { + if var.should_promote_literals { let tcx = tcx_specialization - .and_then(|specialization| specialization.get(self.db, *variable)); + .and_then(|specialization| specialization.get(self.db, var.bound_typevar)); ty = ty.map(|ty| ty.promote_literals(self.db, TypeContext::new(tcx))); } From 782214ae162864b3f49860505e22222a91d83e34 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Sat, 11 Oct 2025 14:49:43 -0400 Subject: [PATCH 23/38] var -> variable --- .../ty_python_semantic/src/types/generics.rs | 30 ++++++++----------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/crates/ty_python_semantic/src/types/generics.rs b/crates/ty_python_semantic/src/types/generics.rs index 718b197b4c958a..3b8904d8de0d9e 100644 --- a/crates/ty_python_semantic/src/types/generics.rs +++ b/crates/ty_python_semantic/src/types/generics.rs @@ -195,7 +195,7 @@ impl<'db> GenericContext<'db> { db, variables .into_iter() - .map(|var| (var.bound_typevar.identity(db), var)) + .map(|variable| (variable.bound_typevar.identity(db), variable)) .collect::>(), ) } @@ -219,12 +219,7 @@ impl<'db> GenericContext<'db> { db: &'db dyn Db, type_params: impl IntoIterator>, ) -> Self { - Self::from_variables( - db, - type_params - .into_iter() - .map(GenericContextTypeVar::new), - ) + 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 @@ -234,7 +229,7 @@ impl<'db> GenericContext<'db> { db, self.variables_inner(db) .values() - .map(|var| var.promote_literals()), + .map(|variable| variable.promote_literals()), ) } @@ -254,7 +249,9 @@ impl<'db> GenericContext<'db> { self, db: &'db dyn Db, ) -> impl ExactSizeIterator> + Clone { - self.variables_inner(db).values().map(|var| var.bound_typevar) + self.variables_inner(db) + .values() + .map(|variable| variable.bound_typevar) } fn variable_from_type_param( @@ -1160,22 +1157,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( - |(identity, var)| { + 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 var.should_promote_literals { - let tcx = tcx_specialization - .and_then(|specialization| specialization.get(self.db, var.bound_typevar)); - + 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 - }, - ); + }); // TODO Infer the tuple spec for a tuple type generic_context.specialize_partial(self.db, types) From 2ca6c1ec44083c25b3d46c7902ead47498428671 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Sat, 11 Oct 2025 14:57:51 -0400 Subject: [PATCH 24/38] Remove unused 'original' field from TypeVarInstance The original field was used to track the identity of typevars that were transformed via mark_typevars_inferable, allowing them to be recognized as the same typevar. This was needed for the old is_identical_to methods. Since we now use TypeVarIdentity for identity comparisons and have removed the is_identical_to methods, the original field is no longer needed. It was only being passed through but never actually read or used. This simplifies TypeVarInstance::new by removing an unused parameter. --- crates/ty_python_semantic/src/types.rs | 38 ++++++------------- .../ty_python_semantic/src/types/generics.rs | 1 - .../src/types/infer/builder.rs | 2 - 3 files changed, 11 insertions(+), 30 deletions(-) diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 58f4ec350156a6..f4dd675cd44811 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -7828,12 +7828,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>, - - /// 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. @@ -7954,7 +7948,6 @@ impl<'db> TypeVarInstance<'db> { .lazy_default(db) .map(|ty| ty.normalized_impl(db, visitor).into()), }), - self.original(db), ) } @@ -7998,7 +7991,6 @@ impl<'db> TypeVarInstance<'db> { .lazy_default(db) .map(|ty| ty.materialize(db, materialization_kind, visitor).into()), }), - self.original(db), ) } @@ -8035,25 +8027,20 @@ 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) + // Don't trigger creation of a new `TypeVarInstance` unnecessarily. + if new_bound_or_constraints == self._bound_or_constraints(db) && new_default == self._default(db) { - self.original(db) + self } else { - Some(self) - }; - - Self::new( - db, - self.identity(db), - new_bound_or_constraints, - self.explicit_variance(db), - new_default, - new_original, - ) + Self::new( + db, + self.identity(db), + new_bound_or_constraints, + self.explicit_variance(db), + new_default, + ) + } } fn to_instance(self, db: &'db dyn Db) -> Option { @@ -8077,7 +8064,6 @@ impl<'db> TypeVarInstance<'db> { Some(bound_or_constraints.into()), self.explicit_variance(db), None, // _default - self.original(db), )) } @@ -8259,7 +8245,6 @@ impl<'db> BoundTypeVarInstance<'db> { None, // _bound_or_constraints Some(variance), None, // _default - None, // original ); Self::new(db, typevar, BindingContext::Synthetic) } @@ -8282,7 +8267,6 @@ impl<'db> BoundTypeVarInstance<'db> { Some(TypeVarBoundOrConstraints::UpperBound(upper_bound).into()), Some(TypeVarVariance::Invariant), None, // _default - None, // original ); Self::new(db, typevar, binding_context) } diff --git a/crates/ty_python_semantic/src/types/generics.rs b/crates/ty_python_semantic/src/types/generics.rs index 3b8904d8de0d9e..38fbc4a40a553e 100644 --- a/crates/ty_python_semantic/src/types/generics.rs +++ b/crates/ty_python_semantic/src/types/generics.rs @@ -128,7 +128,6 @@ pub(crate) fn typing_self<'db>( // [spec]: https://typing.python.org/en/latest/spec/generics.html#self Some(TypeVarVariance::Invariant), None, - None, ); bind_typevar( diff --git a/crates/ty_python_semantic/src/types/infer/builder.rs b/crates/ty_python_semantic/src/types/infer/builder.rs index f736bf0d18f927..73decdb14a542c 100644 --- a/crates/ty_python_semantic/src/types/infer/builder.rs +++ b/crates/ty_python_semantic/src/types/infer/builder.rs @@ -2972,7 +2972,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { bound_or_constraint, None, // explicit_variance default.as_deref().map(|_| TypeVarDefaultEvaluation::Lazy), - None, // original ))); self.add_declaration_with_binding( node.into(), @@ -4315,7 +4314,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { bound_or_constraints, Some(variance), default, - None, // original ))) } From e6645bb9962ced77260b4a06383269bddc061bec Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Sat, 11 Oct 2025 14:58:54 -0400 Subject: [PATCH 25/38] superfluous --- crates/ty_python_semantic/src/types.rs | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index f4dd675cd44811..9870a89ff465a0 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -8027,20 +8027,13 @@ impl<'db> TypeVarInstance<'db> { }), }); - // Don't trigger creation of a new `TypeVarInstance` unnecessarily. - if new_bound_or_constraints == self._bound_or_constraints(db) - && new_default == self._default(db) - { - self - } else { - Self::new( - db, - self.identity(db), - new_bound_or_constraints, - self.explicit_variance(db), - new_default, - ) - } + Self::new( + db, + self.identity(db), + new_bound_or_constraints, + self.explicit_variance(db), + new_default, + ) } fn to_instance(self, db: &'db dyn Db) -> Option { From 6dab7e7a236a785c7e3e1196f0880e5ced1223f7 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Sat, 11 Oct 2025 15:00:47 -0400 Subject: [PATCH 26/38] precommit --- PLAN.md | 80 ++++++++++++------- .../src/types/infer/builder.rs | 15 +--- 2 files changed, 55 insertions(+), 40 deletions(-) diff --git a/PLAN.md b/PLAN.md index b187514fdf4153..380305a9ed1009 100644 --- a/PLAN.md +++ b/PLAN.md @@ -9,9 +9,9 @@ When a `Top[C[T: (int, str)]]` type is used, method calls fail because the `Self Two `TypeVarInstance` objects representing the same logical typevar (e.g., `Self` for a class) are treated as distinct when they differ only in their bounds/constraints due to materialization. This happens because: 1. `TypeVarInstance` is salsa-interned and includes all fields (name, definition, bounds, variance, etc.) -2. When bounds are materialized, a new `TypeVarInstance` is created -3. Checks for "same typevar" use full equality, which includes the materialized bounds -4. The inferable typevars set uses `BoundTypeVarInstance` as keys, which includes the full `TypeVarInstance` +1. When bounds are materialized, a new `TypeVarInstance` is created +1. Checks for "same typevar" use full equality, which includes the materialized bounds +1. The inferable typevars set uses `BoundTypeVarInstance` as keys, which includes the full `TypeVarInstance` ## Solution @@ -24,11 +24,13 @@ Introduce a new concept of "typevar identity" that is separate from the full typ **Location**: `crates/ty_python_semantic/src/types.rs` **Fields** (moved from `TypeVarInstance`): + - `name: Name<'db>` - The typevar's name - `definition: Option>` - Where the typevar was defined - `kind: TypeVarKind` - Whether it's PEP 695, Legacy, or TypingSelf **Traits to implement**: + - `Debug` (via salsa) - `Clone, Copy` (via salsa) - `PartialEq, Eq` (via salsa) @@ -36,6 +38,7 @@ Introduce a new concept of "typevar identity" that is separate from the full typ - `get_size2::GetSize` (via salsa) **Salsa attributes**: + ```rust #[salsa::interned(debug)] pub struct TypeVarIdentity<'db> { @@ -50,10 +53,12 @@ pub struct TypeVarIdentity<'db> { **Location**: `crates/ty_python_semantic/src/types.rs` **Fields**: + - `identity: TypeVarIdentity<'db>` - The typevar's identity - `binding_context: BindingContext<'db>` - Where the typevar is bound **Traits to implement**: + - `Debug` - `Clone, Copy` - `PartialEq, Eq` @@ -65,21 +70,25 @@ This type identifies a specific binding of a typevar (e.g., `T@ClassC1` vs `T@Fu ### 3. Update `TypeVarInstance` **Changes**: + - Add new field: `identity: TypeVarIdentity<'db>` - Keep existing fields: `_bound_or_constraints`, `explicit_variance`, `_default`, `original` - Remove fields moved to `TypeVarIdentity`: `name`, `definition`, `kind` **Constructor updates**: + - Create `TypeVarIdentity` first, then use it in `TypeVarInstance::new` - Update all call sites that construct `TypeVarInstance` **Accessor methods**: + - Add forwarding methods for `name()`, `definition()`, `kind()` that delegate to `identity()` - Keep existing methods for other fields ### 4. Add `identity()` method to `BoundTypeVarInstance` **Method signature**: + ```rust pub(crate) fn identity(self, db: &'db dyn Db) -> BoundTypeVarIdentity<'db> { BoundTypeVarIdentity { @@ -94,11 +103,13 @@ pub(crate) fn identity(self, db: &'db dyn Db) -> BoundTypeVarIdentity<'db> { **Location**: `crates/ty_python_semantic/src/types/generics.rs` **Changes**: + - Change `variables_inner` from `FxOrderMap, ...>` to `FxOrderMap, ...>` - Update `variables()` method to return `BoundTypeVarInstance` by looking up the full instance - Update all methods that use `variables_inner` as a map key **Affected methods**: + - `from_typevar_instances()` - use `btv.identity(db)` as map key - `variables()` - reconstruct `BoundTypeVarInstance` from stored identity - `lookup()` - use identity for lookup @@ -108,6 +119,7 @@ pub(crate) fn identity(self, db: &'db dyn Db) -> BoundTypeVarIdentity<'db> { **Location**: `crates/ty_python_semantic/src/types/generics.rs` **Changes**: + - The `generic_context` field already uses `GenericContext`, which will now use identities - Verify that `types` array stays synchronized with context (might need to store parallel arrays or restructure) - Update methods that iterate over typevars and types together @@ -117,6 +129,7 @@ pub(crate) fn identity(self, db: &'db dyn Db) -> BoundTypeVarIdentity<'db> { **Location**: `crates/ty_python_semantic/src/types.rs` (if it uses typevar keys) **Changes**: + - If `ConstraintSet` uses `BoundTypeVarInstance` as keys in any internal maps, update to use `BoundTypeVarIdentity` - Search for uses of `BoundTypeVarInstance` as map keys or set elements @@ -125,6 +138,7 @@ pub(crate) fn identity(self, db: &'db dyn Db) -> BoundTypeVarIdentity<'db> { **Location**: `crates/ty_python_semantic/src/types/generics.rs` **Changes**: + - Change from `FxHashSet>` to `FxHashSet>` - Update `is_inferable()` to use `bound_typevar.identity(db)` for lookup - Update `inferable_typevars_innerer()` to collect identities instead of full instances @@ -134,11 +148,12 @@ pub(crate) fn identity(self, db: &'db dyn Db) -> BoundTypeVarIdentity<'db> { **Location**: `crates/ty_python_semantic/src/types.rs` **Changes**: + - Remove `TypeVarInstance::is_identical_to()` method entirely - Remove `BoundTypeVarInstance::is_identical_to()` method entirely - Update all call sites to use direct equality comparison instead: - - `btv1.identity(db) == btv2.identity(db)` for bound typevars - - `tv1.identity(db) == tv2.identity(db)` for typevar instances + - `btv1.identity(db) == btv2.identity(db)` for bound typevars + - `tv1.identity(db) == tv2.identity(db)` for typevar instances - Search for all uses of `is_identical_to` and replace with identity comparisons **Rationale**: With explicit identity types, we can use standard `==` comparison instead of custom methods. The identity types already implement `Eq` and `PartialEq` correctly. @@ -148,43 +163,52 @@ pub(crate) fn identity(self, db: &'db dyn Db) -> BoundTypeVarIdentity<'db> { **Location**: `crates/ty_python_semantic/src/types/display.rs` **Changes**: + - Update `DisplayBoundTypeVarInstance` to use `typevar.identity(db).name(db)` - Verify all display code still works correctly ## Testing Strategy ### Primary Testing: Existing Test Suite + Since this branch is based on `main` (not `work`), the regression we identified doesn't exist yet in this branch. Our primary testing goal is to ensure all existing tests continue to pass after the refactoring. **Process**: + 1. Run the full test suite in the new branch after each major step -2. Ensure no regressions are introduced by the refactoring -3. Fix any test failures that arise from the structural changes +1. Ensure no regressions are introduced by the refactoring +1. Fix any test failures that arise from the structural changes ### Testing the Regression Fix + To verify that this change fixes the regression that would be introduced by the `work` branch changes: **Process**: + 1. In the `work` worktree, temporarily merge the `dcreager/typevar-identity` branch: - ```bash - cd /home/dcreager/git/ruff/work - git merge dcreager/typevar-identity - ``` -2. Build and run the test case from `/home/dcreager/Documents/scratch/ty/top.py`: - ```bash - cargo build --bin ty - target/debug/ty check /home/dcreager/Documents/scratch/ty/top.py - ``` + ```bash + cd /home/dcreager/git/ruff/work + git merge dcreager/typevar-identity + ``` + +1. Build and run the test case from `/home/dcreager/Documents/scratch/ty/top.py`: -3. Verify that the `invalid-argument-type` error no longer occurs for `x.method()` + ```bash + cargo build --bin ty + target/debug/ty check /home/dcreager/Documents/scratch/ty/top.py + ``` -4. Revert the merge to restore the `work` branch: - ```bash - git merge --abort # or git reset --hard HEAD if merge was completed - ``` +1. Verify that the `invalid-argument-type` error no longer occurs for `x.method()` + +1. Revert the merge to restore the `work` branch: + + ```bash + git merge --abort # or git reset --hard HEAD if merge was completed + ``` ### Why This Approach? + - The `main` branch doesn't have the inferable typevar changes yet, so the bug doesn't manifest - The `work` branch has the inferable changes that trigger the bug - By merging our fix into `work`, we can test that it resolves the issue @@ -201,10 +225,10 @@ To verify that this change fixes the regression that would be introduced by the ## Rollout 1. Implement changes incrementally, ensuring tests pass at each step -2. Start with creating new types (`TypeVarIdentity`, `BoundTypeVarIdentity`) -3. Update `TypeVarInstance` structure -4. Update all construction sites -5. Update data structures (`GenericContext`, `InferableTypeVars`) -6. Update comparison logic -7. Run full test suite -8. Test with real-world cases including the motivating example +1. Start with creating new types (`TypeVarIdentity`, `BoundTypeVarIdentity`) +1. Update `TypeVarInstance` structure +1. Update all construction sites +1. Update data structures (`GenericContext`, `InferableTypeVars`) +1. Update comparison logic +1. Run full test suite +1. Test with real-world cases including the motivating example diff --git a/crates/ty_python_semantic/src/types/infer/builder.rs b/crates/ty_python_semantic/src/types/infer/builder.rs index 73decdb14a542c..74c0a008bdf05a 100644 --- a/crates/ty_python_semantic/src/types/infer/builder.rs +++ b/crates/ty_python_semantic/src/types/infer/builder.rs @@ -2960,12 +2960,8 @@ 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 identity = + TypeVarIdentity::new(self.db(), &name.id, Some(definition), TypeVarKind::Pep695); let ty = Type::KnownInstance(KnownInstanceType::TypeVar(TypeVarInstance::new( self.db(), identity, @@ -4302,12 +4298,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { self.deferred.insert(definition); } - let identity = TypeVarIdentity::new( - db, - target_name, - Some(definition), - TypeVarKind::Legacy, - ); + let identity = TypeVarIdentity::new(db, target_name, Some(definition), TypeVarKind::Legacy); Type::KnownInstance(KnownInstanceType::TypeVar(TypeVarInstance::new( db, identity, From a4dc1719502f5d73ca09a11d3e0034fa016ab2c2 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Sat, 11 Oct 2025 15:00:54 -0400 Subject: [PATCH 27/38] remove finished plan --- PLAN.md | 234 -------------------------------------------------------- 1 file changed, 234 deletions(-) delete mode 100644 PLAN.md diff --git a/PLAN.md b/PLAN.md deleted file mode 100644 index 380305a9ed1009..00000000000000 --- a/PLAN.md +++ /dev/null @@ -1,234 +0,0 @@ -# Plan: Fix TypeVar Identity Issue with Materialization - -## Problem - -When a `Top[C[T: (int, str)]]` type is used, method calls fail because the `Self` typevar in the method signature has a Bottom-materialized upper bound, while the `Self` in the class's inferable typevars set has no materialization. These are different `TypeVarInstance` objects in Salsa, causing `is_inferable()` to return false and the assignability check to fail. - -## Root Cause - -Two `TypeVarInstance` objects representing the same logical typevar (e.g., `Self` for a class) are treated as distinct when they differ only in their bounds/constraints due to materialization. This happens because: - -1. `TypeVarInstance` is salsa-interned and includes all fields (name, definition, bounds, variance, etc.) -1. When bounds are materialized, a new `TypeVarInstance` is created -1. Checks for "same typevar" use full equality, which includes the materialized bounds -1. The inferable typevars set uses `BoundTypeVarInstance` as keys, which includes the full `TypeVarInstance` - -## Solution - -Introduce a new concept of "typevar identity" that is separate from the full typevar instance. Two typevars have the same identity if they represent the same logical typevar, regardless of how their bounds have been materialized. - -## Implementation Steps - -### 1. Create `TypeVarIdentity` (salsa-interned) - -**Location**: `crates/ty_python_semantic/src/types.rs` - -**Fields** (moved from `TypeVarInstance`): - -- `name: Name<'db>` - The typevar's name -- `definition: Option>` - Where the typevar was defined -- `kind: TypeVarKind` - Whether it's PEP 695, Legacy, or TypingSelf - -**Traits to implement**: - -- `Debug` (via salsa) -- `Clone, Copy` (via salsa) -- `PartialEq, Eq` (via salsa) -- `Hash` (via salsa) -- `get_size2::GetSize` (via salsa) - -**Salsa attributes**: - -```rust -#[salsa::interned(debug)] -pub struct TypeVarIdentity<'db> { - pub(crate) name: Name<'db>, - pub(crate) definition: Option>, - pub(crate) kind: TypeVarKind, -} -``` - -### 2. Create `BoundTypeVarIdentity` (non-interned) - -**Location**: `crates/ty_python_semantic/src/types.rs` - -**Fields**: - -- `identity: TypeVarIdentity<'db>` - The typevar's identity -- `binding_context: BindingContext<'db>` - Where the typevar is bound - -**Traits to implement**: - -- `Debug` -- `Clone, Copy` -- `PartialEq, Eq` -- `Hash` -- `get_size2::GetSize` - -This type identifies a specific binding of a typevar (e.g., `T@ClassC1` vs `T@FunctionF`). - -### 3. Update `TypeVarInstance` - -**Changes**: - -- Add new field: `identity: TypeVarIdentity<'db>` -- Keep existing fields: `_bound_or_constraints`, `explicit_variance`, `_default`, `original` -- Remove fields moved to `TypeVarIdentity`: `name`, `definition`, `kind` - -**Constructor updates**: - -- Create `TypeVarIdentity` first, then use it in `TypeVarInstance::new` -- Update all call sites that construct `TypeVarInstance` - -**Accessor methods**: - -- Add forwarding methods for `name()`, `definition()`, `kind()` that delegate to `identity()` -- Keep existing methods for other fields - -### 4. Add `identity()` method to `BoundTypeVarInstance` - -**Method signature**: - -```rust -pub(crate) fn identity(self, db: &'db dyn Db) -> BoundTypeVarIdentity<'db> { - BoundTypeVarIdentity { - identity: self.typevar(db).identity(db), - binding_context: self.binding_context(db), - } -} -``` - -### 5. Update `GenericContext` - -**Location**: `crates/ty_python_semantic/src/types/generics.rs` - -**Changes**: - -- Change `variables_inner` from `FxOrderMap, ...>` to `FxOrderMap, ...>` -- Update `variables()` method to return `BoundTypeVarInstance` by looking up the full instance -- Update all methods that use `variables_inner` as a map key - -**Affected methods**: - -- `from_typevar_instances()` - use `btv.identity(db)` as map key -- `variables()` - reconstruct `BoundTypeVarInstance` from stored identity -- `lookup()` - use identity for lookup - -### 6. Update `Specialization` - -**Location**: `crates/ty_python_semantic/src/types/generics.rs` - -**Changes**: - -- The `generic_context` field already uses `GenericContext`, which will now use identities -- Verify that `types` array stays synchronized with context (might need to store parallel arrays or restructure) -- Update methods that iterate over typevars and types together - -### 7. Update `ConstraintSet` - -**Location**: `crates/ty_python_semantic/src/types.rs` (if it uses typevar keys) - -**Changes**: - -- If `ConstraintSet` uses `BoundTypeVarInstance` as keys in any internal maps, update to use `BoundTypeVarIdentity` -- Search for uses of `BoundTypeVarInstance` as map keys or set elements - -### 8. Update `InferableTypeVars` - -**Location**: `crates/ty_python_semantic/src/types/generics.rs` - -**Changes**: - -- Change from `FxHashSet>` to `FxHashSet>` -- Update `is_inferable()` to use `bound_typevar.identity(db)` for lookup -- Update `inferable_typevars_innerer()` to collect identities instead of full instances - -### 9. Remove `is_identical_to` methods - -**Location**: `crates/ty_python_semantic/src/types.rs` - -**Changes**: - -- Remove `TypeVarInstance::is_identical_to()` method entirely -- Remove `BoundTypeVarInstance::is_identical_to()` method entirely -- Update all call sites to use direct equality comparison instead: - - `btv1.identity(db) == btv2.identity(db)` for bound typevars - - `tv1.identity(db) == tv2.identity(db)` for typevar instances -- Search for all uses of `is_identical_to` and replace with identity comparisons - -**Rationale**: With explicit identity types, we can use standard `==` comparison instead of custom methods. The identity types already implement `Eq` and `PartialEq` correctly. - -### 10. Update Display implementations - -**Location**: `crates/ty_python_semantic/src/types/display.rs` - -**Changes**: - -- Update `DisplayBoundTypeVarInstance` to use `typevar.identity(db).name(db)` -- Verify all display code still works correctly - -## Testing Strategy - -### Primary Testing: Existing Test Suite - -Since this branch is based on `main` (not `work`), the regression we identified doesn't exist yet in this branch. Our primary testing goal is to ensure all existing tests continue to pass after the refactoring. - -**Process**: - -1. Run the full test suite in the new branch after each major step -1. Ensure no regressions are introduced by the refactoring -1. Fix any test failures that arise from the structural changes - -### Testing the Regression Fix - -To verify that this change fixes the regression that would be introduced by the `work` branch changes: - -**Process**: - -1. In the `work` worktree, temporarily merge the `dcreager/typevar-identity` branch: - - ```bash - cd /home/dcreager/git/ruff/work - git merge dcreager/typevar-identity - ``` - -1. Build and run the test case from `/home/dcreager/Documents/scratch/ty/top.py`: - - ```bash - cargo build --bin ty - target/debug/ty check /home/dcreager/Documents/scratch/ty/top.py - ``` - -1. Verify that the `invalid-argument-type` error no longer occurs for `x.method()` - -1. Revert the merge to restore the `work` branch: - - ```bash - git merge --abort # or git reset --hard HEAD if merge was completed - ``` - -### Why This Approach? - -- The `main` branch doesn't have the inferable typevar changes yet, so the bug doesn't manifest -- The `work` branch has the inferable changes that trigger the bug -- By merging our fix into `work`, we can test that it resolves the issue -- We revert to keep the `work` and new feature branches independent - -## Migration Notes - -- This is a significant refactoring that touches core type system code -- All places that construct `TypeVarInstance` must be updated -- All places that use `BoundTypeVarInstance` for identity/lookup must use `BoundTypeVarIdentity` -- Salsa will need to recompute caches after these changes -- Performance should be similar or slightly better (smaller identity keys for lookups) - -## Rollout - -1. Implement changes incrementally, ensuring tests pass at each step -1. Start with creating new types (`TypeVarIdentity`, `BoundTypeVarIdentity`) -1. Update `TypeVarInstance` structure -1. Update all construction sites -1. Update data structures (`GenericContext`, `InferableTypeVars`) -1. Update comparison logic -1. Run full test suite -1. Test with real-world cases including the motivating example From 625cee6fbb456f83aa363316a031b08c980fbb94 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Sat, 11 Oct 2025 15:24:42 -0400 Subject: [PATCH 28/38] pre-commit --- crates/ty_python_semantic/src/types.rs | 4 ++-- .../ty_python_semantic/src/types/generics.rs | 22 ++++++++++++++----- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 27c8762a9d6cbb..2435be16baf4b2 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -1625,7 +1625,8 @@ impl<'db> Type<'db> { // `T` will always be a subtype of any union containing `T`. // A similar rule applies in reverse to intersection types. (Type::TypeVar(bound_typevar), Type::Union(union)) - if !inferable.is_inferable(db, bound_typevar) && union.elements(db).contains(&self) => + if !inferable.is_inferable(db, bound_typevar) + && union.elements(db).contains(&self) => { ConstraintSet::from(true) } @@ -8121,7 +8122,6 @@ impl<'db> TypeVarInstance<'db> { ) } - fn to_instance(self, db: &'db dyn Db) -> Option { let bound_or_constraints = match self.bound_or_constraints(db)? { TypeVarBoundOrConstraints::UpperBound(upper_bound) => { diff --git a/crates/ty_python_semantic/src/types/generics.rs b/crates/ty_python_semantic/src/types/generics.rs index b54591b609d30e..fcb1ad34631080 100644 --- a/crates/ty_python_semantic/src/types/generics.rs +++ b/crates/ty_python_semantic/src/types/generics.rs @@ -154,7 +154,11 @@ pub(crate) enum InferableTypeVars<'a, 'db> { } impl<'a, 'db> InferableTypeVars<'a, 'db> { - pub(crate) fn is_inferable(&self, db: &'db dyn Db, bound_typevar: BoundTypeVarInstance<'db>) -> bool { + pub(crate) fn is_inferable( + &self, + db: &'db dyn Db, + bound_typevar: BoundTypeVarInstance<'db>, + ) -> bool { match self { InferableTypeVars::None => false, InferableTypeVars::One(typevars) => typevars.contains(&bound_typevar.identity(db)), @@ -192,9 +196,12 @@ impl<'a, 'db> InferableTypeVars<'a, 'db> { find_typevars(&mut typevars, self); format!( "[{}]", - typevars.into_iter().map(|btv_id| { - format!("{}@{:?}", btv_id.identity.name(db), btv_id.binding_context) - }).format(", ") + typevars + .into_iter() + .map(|btv_id| { + format!("{}@{:?}", btv_id.identity.name(db), btv_id.binding_context) + }) + .format(", ") ) } } @@ -339,7 +346,9 @@ impl<'db> GenericContext<'db> { db: &'db dyn Db, bound_typevar: BoundTypeVarInstance<'db>, ) { - self.typevars.borrow_mut().insert(bound_typevar.identity(db)); + self.typevars + .borrow_mut() + .insert(bound_typevar.identity(db)); walk_bound_type_var_type(db, bound_typevar, self); } @@ -451,7 +460,8 @@ impl<'db> GenericContext<'db> { db: &'db dyn Db, bound_typevar: BoundTypeVarInstance<'db>, ) -> bool { - self.variables_inner(db).contains_key(&bound_typevar.identity(db)) + self.variables_inner(db) + .contains_key(&bound_typevar.identity(db)) } pub(crate) fn signature(self, db: &'db dyn Db) -> Signature<'db> { From bbacf203c7b360bbc6828edcf2ceef78d55710c4 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Sat, 11 Oct 2025 15:33:54 -0400 Subject: [PATCH 29/38] Move display method to BoundTypeVarIdentity Update DisplayBoundTypeVarInstance to DisplayBoundTypeVarIdentity and move the display() method from BoundTypeVarInstance to BoundTypeVarIdentity. Since the display implementation only uses the identity fields (name and binding context), it makes more sense for it to operate on the identity type directly. This also reduces coupling to the full BoundTypeVarInstance. Updated all callers to use .identity(db).display(db) when displaying a BoundTypeVarInstance. --- .../src/types/constraints.rs | 4 ++-- .../ty_python_semantic/src/types/display.rs | 24 +++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/crates/ty_python_semantic/src/types/constraints.rs b/crates/ty_python_semantic/src/types/constraints.rs index 1db37c3e56341e..a8cc0c5e36e2d2 100644 --- a/crates/ty_python_semantic/src/types/constraints.rs +++ b/crates/ty_python_semantic/src/types/constraints.rs @@ -348,7 +348,7 @@ impl<'db> ConstrainedTypeVar<'db> { return write!( f, "({} {} {})", - self.constraint.typevar(self.db).display(self.db), + self.constraint.typevar(self.db).identity(self.db).display(self.db), if self.negated { "≠" } else { "=" }, lower.display(self.db) ); @@ -361,7 +361,7 @@ impl<'db> ConstrainedTypeVar<'db> { if !lower.is_never() { write!(f, "{} ≤ ", lower.display(self.db))?; } - self.constraint.typevar(self.db).display(self.db).fmt(f)?; + self.constraint.typevar(self.db).identity(self.db).display(self.db).fmt(f)?; if !upper.is_object() { write!(f, " ≤ {}", upper.display(self.db))?; } diff --git a/crates/ty_python_semantic/src/types/display.rs b/crates/ty_python_semantic/src/types/display.rs index 1bee509e260407..191a71460b66e0 100644 --- a/crates/ty_python_semantic/src/types/display.rs +++ b/crates/ty_python_semantic/src/types/display.rs @@ -24,9 +24,9 @@ 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, - MaterializationKind, Protocol, ProtocolInstanceType, StringLiteralType, SubclassOfInner, Type, - UnionType, WrapperDescriptorKind, visitor, + BoundTypeVarIdentity, BoundTypeVarInstance, CallableType, IntersectionType, + KnownBoundMethodType, KnownClass, MaterializationKind, Protocol, ProtocolInstanceType, + StringLiteralType, SubclassOfInner, Type, UnionType, WrapperDescriptorKind, visitor, }; use ruff_db::parsed::parsed_module; @@ -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"), @@ -600,24 +600,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(()) From 119bcb40e3945761d6af721b1da7be12bbf39baf Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Sat, 11 Oct 2025 15:35:07 -0400 Subject: [PATCH 30/38] clippy --- crates/ty_python_semantic/src/types/display.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/ty_python_semantic/src/types/display.rs b/crates/ty_python_semantic/src/types/display.rs index 191a71460b66e0..5812592a97ad38 100644 --- a/crates/ty_python_semantic/src/types/display.rs +++ b/crates/ty_python_semantic/src/types/display.rs @@ -24,9 +24,9 @@ use crate::types::signatures::{CallableSignature, Parameter, Parameters, Signatu use crate::types::tuple::TupleSpec; use crate::types::visitor::TypeVisitor; use crate::types::{ - BoundTypeVarIdentity, BoundTypeVarInstance, CallableType, IntersectionType, - KnownBoundMethodType, KnownClass, MaterializationKind, Protocol, ProtocolInstanceType, - StringLiteralType, SubclassOfInner, Type, UnionType, WrapperDescriptorKind, visitor, + BoundTypeVarIdentity, CallableType, IntersectionType, KnownBoundMethodType, KnownClass, + MaterializationKind, Protocol, ProtocolInstanceType, StringLiteralType, SubclassOfInner, Type, + UnionType, WrapperDescriptorKind, visitor, }; use ruff_db::parsed::parsed_module; From 9adc068fdbc43344c9c27fdba5608bf67d8f70e2 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Sat, 11 Oct 2025 15:41:13 -0400 Subject: [PATCH 31/38] Update ConstraintSet to use BoundTypeVarIdentity Change ConstrainedTypeVar to store BoundTypeVarIdentity instead of BoundTypeVarInstance. This makes constraints independent of typevar materialization, which is appropriate since constraints should only care about the logical identity of a typevar, not its specific bounds. Updated: - ConstrainedTypeVar::typevar field to BoundTypeVarIdentity - ConstraintSet::range() and negated_range() to accept BoundTypeVarIdentity - ConstrainedTypeVar::new_node() signature - All callers in function.rs to pass .identity(db) - Display code which now receives BoundTypeVarIdentity directly --- crates/ty_python_semantic/src/types/constraints.rs | 14 +++++++------- crates/ty_python_semantic/src/types/function.rs | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/ty_python_semantic/src/types/constraints.rs b/crates/ty_python_semantic/src/types/constraints.rs index a8cc0c5e36e2d2..7c2696cdf13dc9 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, BoundTypeVarInstance, 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)); @@ -348,7 +348,7 @@ impl<'db> ConstrainedTypeVar<'db> { return write!( f, "({} {} {})", - self.constraint.typevar(self.db).identity(self.db).display(self.db), + self.constraint.typevar(self.db).display(self.db), if self.negated { "≠" } else { "=" }, lower.display(self.db) ); @@ -361,7 +361,7 @@ impl<'db> ConstrainedTypeVar<'db> { if !lower.is_never() { write!(f, "{} ≤ ", lower.display(self.db))?; } - self.constraint.typevar(self.db).identity(self.db).display(self.db).fmt(f)?; + self.constraint.typevar(self.db).display(self.db).fmt(f)?; if !upper.is_object() { write!(f, " ≤ {}", upper.display(self.db))?; } diff --git a/crates/ty_python_semantic/src/types/function.rs b/crates/ty_python_semantic/src/types/function.rs index 2f6b5858d75963..b1d42b9154b029 100644 --- a/crates/ty_python_semantic/src/types/function.rs +++ b/crates/ty_python_semantic/src/types/function.rs @@ -1666,7 +1666,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, @@ -1683,7 +1683,7 @@ 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, From 7dc5144e568a4544bf7a6f589ae4e5fa0f4452a9 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Sat, 11 Oct 2025 15:43:36 -0400 Subject: [PATCH 32/38] clippy --- crates/ty_python_semantic/src/types/constraints.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ty_python_semantic/src/types/constraints.rs b/crates/ty_python_semantic/src/types/constraints.rs index 7c2696cdf13dc9..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::{BoundTypeVarIdentity, 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 { From f589ad04e3027aed48b07987afbea161f773f480 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Sat, 11 Oct 2025 15:44:04 -0400 Subject: [PATCH 33/38] pre-commit --- crates/ty_python_semantic/src/types/function.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/ty_python_semantic/src/types/function.rs b/crates/ty_python_semantic/src/types/function.rs index b1d42b9154b029..bcced84655ddc7 100644 --- a/crates/ty_python_semantic/src/types/function.rs +++ b/crates/ty_python_semantic/src/types/function.rs @@ -1683,7 +1683,8 @@ impl KnownFunction { return; }; - let constraints = ConstraintSet::negated_range(db, *lower, typevar.identity(db), *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, From 88f3a218a7ce9f4cbcb256b7befdfec32070b200 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Sat, 11 Oct 2025 16:04:40 -0400 Subject: [PATCH 34/38] merge conflicts --- .../ty_python_semantic/src/types/function.rs | 18 +---------------- .../src/types/signatures.rs | 20 +++---------------- 2 files changed, 4 insertions(+), 34 deletions(-) diff --git a/crates/ty_python_semantic/src/types/function.rs b/crates/ty_python_semantic/src/types/function.rs index 623a8f6389cf88..9e2dedb8068e01 100644 --- a/crates/ty_python_semantic/src/types/function.rs +++ b/crates/ty_python_semantic/src/types/function.rs @@ -353,28 +353,12 @@ impl<'db> OverloadLiteral<'db> { if function_node.is_async && !is_generator { signature = signature.wrap_coroutine_return_type(db); } - signature = signature.mark_typevars_inferable(db); - - let pep695_ctx = function_node.type_params.as_ref().map(|type_params| { - GenericContext::from_type_params(db, index, self.definition(db), type_params) - }); - let legacy_ctx = GenericContext::from_function_params( - db, - self.definition(db), - signature.parameters(), - signature.return_ty, - ); - // We need to update `signature.generic_context` here, - // because type variables in `GenericContext::variables` are still non-inferable. - signature.generic_context = - GenericContext::merge_pep695_and_legacy(db, pep695_ctx, legacy_ctx); signature } /// Typed internally-visible "raw" signature for this function. - /// That is, type variables in parameter types and the return type remain non-inferable, - /// and the return types of async functions are not wrapped in `CoroutineType[...]`. + /// That is, the return types of async functions are not wrapped in `CoroutineType[...]`. /// /// ## Warning /// diff --git a/crates/ty_python_semantic/src/types/signatures.rs b/crates/ty_python_semantic/src/types/signatures.rs index 8b9cba149075f0..7c3851b276dbff 100644 --- a/crates/ty_python_semantic/src/types/signatures.rs +++ b/crates/ty_python_semantic/src/types/signatures.rs @@ -28,10 +28,9 @@ use crate::types::generics::{ }; use crate::types::infer::nearest_enclosing_class; use crate::types::{ - ApplyTypeMappingVisitor, BindingContext, BoundTypeVarInstance, ClassLiteral, - FindLegacyTypeVarsVisitor, HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, - KnownClass, MaterializationKind, NormalizedVisitor, TypeContext, TypeMapping, TypeRelation, - VarianceInferable, todo_type, + ApplyTypeMappingVisitor, BoundTypeVarInstance, ClassLiteral, FindLegacyTypeVarsVisitor, + HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, KnownClass, MaterializationKind, + NormalizedVisitor, TypeContext, TypeMapping, TypeRelation, VarianceInferable, todo_type, }; use crate::{Db, FxOrderSet}; use ruff_python_ast::{self as ast, name::Name}; @@ -446,19 +445,6 @@ impl<'db> Signature<'db> { } } - pub(super) fn mark_typevars_inferable(self, db: &'db dyn Db) -> Self { - if let Some(definition) = self.definition { - self.apply_type_mapping_impl( - db, - &TypeMapping::MarkTypeVarsInferable(Some(BindingContext::Definition(definition))), - TypeContext::default(), - &ApplyTypeMappingVisitor::default(), - ) - } else { - self - } - } - pub(super) fn wrap_coroutine_return_type(self, db: &'db dyn Db) -> Self { let return_ty = self.return_ty.map(|return_ty| { KnownClass::CoroutineType From dd9abdff5debf410ab3abdcd2570dce83e52edfa Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Sat, 11 Oct 2025 16:58:20 -0400 Subject: [PATCH 35/38] missed one --- crates/ty_python_semantic/src/types.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index e96835ee15f3fc..ce1bd3a640fbf1 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -2417,7 +2417,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(_)) From 8caf20840f9469d41145005dec2a0e55729394f4 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Sat, 11 Oct 2025 16:46:15 -0400 Subject: [PATCH 36/38] fix display --- crates/ty_python_semantic/src/types/generics.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/ty_python_semantic/src/types/generics.rs b/crates/ty_python_semantic/src/types/generics.rs index 3f4af68ffdf249..35d3714f9f76a3 100644 --- a/crates/ty_python_semantic/src/types/generics.rs +++ b/crates/ty_python_semantic/src/types/generics.rs @@ -198,9 +198,7 @@ impl<'a, 'db> InferableTypeVars<'a, 'db> { "[{}]", typevars .into_iter() - .map(|btv_id| { - format!("{}@{:?}", btv_id.identity.name(db), btv_id.binding_context) - }) + .map(|identity| identity.display(db)) .format(", ") ) } From 69a2b332aec26248f3ae8f90db8bb813f0ad6386 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Sat, 11 Oct 2025 16:56:08 -0400 Subject: [PATCH 37/38] inferable in filter_disjoint --- crates/ty_python_semantic/src/types.rs | 13 +++++++++++-- crates/ty_python_semantic/src/types/generics.rs | 2 +- .../ty_python_semantic/src/types/infer/builder.rs | 6 +++--- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index ff39aac94bf388..6a46e1023247c0 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -1172,9 +1172,18 @@ impl<'db> Type<'db> { } /// Remove the union elements that are not related to `target`. - pub(crate) fn filter_disjoint_elements(self, db: &'db dyn Db, target: Type<'db>) -> Type<'db> { + pub(crate) fn filter_disjoint_elements( + self, + db: &'db dyn Db, + target: Type<'db>, + inferable: InferableTypeVars<'_, 'db>, + ) -> Type<'db> { if let Type::Union(union) = self { - union.filter(db, |elem| !elem.is_disjoint_from(db, target)) + union.filter(db, |elem| { + !elem + .when_disjoint_from(db, target, inferable) + .is_always_satisfied() + }) } else { self } diff --git a/crates/ty_python_semantic/src/types/generics.rs b/crates/ty_python_semantic/src/types/generics.rs index 35d3714f9f76a3..0efe931b917303 100644 --- a/crates/ty_python_semantic/src/types/generics.rs +++ b/crates/ty_python_semantic/src/types/generics.rs @@ -1412,7 +1412,7 @@ impl<'db> SpecializationBuilder<'db> { // For example, if `formal` is `list[T]` and `actual` is `list[int] | None`, we want to specialize `T` to `int`. // So, here we remove the union elements that are not related to `formal`. - actual = actual.filter_disjoint_elements(self.db, formal); + actual = actual.filter_disjoint_elements(self.db, formal, self.inferable); match (formal, actual) { // TODO: We haven't implemented a full unification solver yet. If typevars appear in diff --git a/crates/ty_python_semantic/src/types/infer/builder.rs b/crates/ty_python_semantic/src/types/infer/builder.rs index 31a683fcaf1b26..6d58ed0981bbcf 100644 --- a/crates/ty_python_semantic/src/types/infer/builder.rs +++ b/crates/ty_python_semantic/src/types/infer/builder.rs @@ -5955,11 +5955,12 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { return None; }; + let inferable = generic_context.inferable_typevars(self.db()); let tcx = tcx.map_annotation(|annotation| { // Remove any union elements of `annotation` that are not related to `collection_ty`. // e.g. `annotation: list[int] | None => list[int]` if `collection_ty: list` let collection_ty = collection_class.to_instance(self.db()); - annotation.filter_disjoint_elements(self.db(), collection_ty) + annotation.filter_disjoint_elements(self.db(), collection_ty, inferable) }); // Extract the annotated type of `T`, if provided. @@ -5968,8 +5969,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { .map(|specialization| specialization.types(self.db())); // Create a set of constraints to infer a precise type for `T`. - let mut builder = - SpecializationBuilder::new(self.db(), generic_context.inferable_typevars(self.db())); + let mut builder = SpecializationBuilder::new(self.db(), inferable); match annotated_elt_tys { // The annotated type acts as a constraint for `T`. From b2825f0132399fa68dbd7bb73146f62ffc6450d0 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Sat, 11 Oct 2025 17:00:03 -0400 Subject: [PATCH 38/38] fix tests --- crates/ty_python_semantic/src/types.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 6a46e1023247c0..b5e01cbaf353e6 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -2568,7 +2568,8 @@ impl<'db> Type<'db> { // and `Any`!) Different typevars might be disjoint, depending on their bounds and // constraints, which are handled below. (Type::TypeVar(self_bound_typevar), Type::TypeVar(other_bound_typevar)) - if self_bound_typevar == other_bound_typevar => + if !inferable.is_inferable(db, self_bound_typevar) + && self_bound_typevar.identity(db) == other_bound_typevar.identity(db) => { ConstraintSet::from(false) }