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 7ca7c2a44a2759..45eaba38cc8dd4 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -53,8 +53,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}; @@ -571,21 +571,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: InferableTypeVars<'_, 'db>, + ) -> 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: InferableTypeVars<'_, 'db>, 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); @@ -598,7 +604,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); @@ -763,15 +769,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`. @@ -1198,9 +1198,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 } @@ -1316,9 +1325,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)) }), @@ -1395,7 +1401,6 @@ impl<'db> Type<'db> { | Type::Union(_) | Type::Intersection(_) | Type::Callable(_) - | Type::NonInferableTypeVar(_) | Type::TypeVar(_) | Type::BoundSuper(_) | Type::TypeIs(_) @@ -1486,7 +1491,6 @@ impl<'db> Type<'db> { | Type::KnownInstance(_) | Type::PropertyInstance(_) | Type::Intersection(_) - | Type::NonInferableTypeVar(_) | Type::TypeVar(_) | Type::BoundSuper(_) => None, } @@ -1496,29 +1500,41 @@ 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, InferableTypeVars::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: InferableTypeVars<'_, 'db>, + ) -> 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, InferableTypeVars::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: InferableTypeVars<'_, 'db>, + ) -> 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, InferableTypeVars::None, TypeRelation::Redundancy) .is_always_satisfied() } @@ -1526,11 +1542,13 @@ impl<'db> Type<'db> { self, db: &'db dyn Db, target: Type<'db>, + inferable: InferableTypeVars<'_, 'db>, relation: TypeRelation, ) -> ConstraintSet<'db> { self.has_relation_to_impl( db, target, + inferable, relation, &HasRelationToVisitor::default(), &IsDisjointVisitor::default(), @@ -1541,6 +1559,7 @@ impl<'db> Type<'db> { self, db: &'db dyn Db, target: Type<'db>, + inferable: InferableTypeVars<'_, 'db>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -1580,6 +1599,7 @@ impl<'db> Type<'db> { self_alias.value_type(db).has_relation_to_impl( db, target, + inferable, relation, relation_visitor, disjointness_visitor, @@ -1592,6 +1612,7 @@ impl<'db> Type<'db> { self.has_relation_to_impl( db, target_alias.value_type(db), + inferable, relation, relation_visitor, disjointness_visitor, @@ -1608,6 +1629,7 @@ impl<'db> Type<'db> { field.default_type(db).has_relation_to_impl( db, right, + inferable, relation, relation_visitor, disjointness_visitor, @@ -1650,18 +1672,21 @@ 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(bound_typevar), Type::Union(union)) + if !inferable.is_inferable(db, bound_typevar) + && union.elements(db).contains(&self) => { ConstraintSet::from(true) } - (Type::Intersection(intersection), Type::NonInferableTypeVar(_)) - if intersection.positive(db).contains(&target) => + (Type::Intersection(intersection), Type::TypeVar(bound_typevar)) + if !inferable.is_inferable(db, bound_typevar) + && intersection.positive(db).contains(&target) => { ConstraintSet::from(true) } - (Type::Intersection(intersection), Type::NonInferableTypeVar(_)) - if intersection.negative(db).contains(&target) => + (Type::Intersection(intersection), Type::TypeVar(bound_typevar)) + if !inferable.is_inferable(db, bound_typevar) + && intersection.negative(db).contains(&target) => { ConstraintSet::from(false) } @@ -1671,18 +1696,19 @@ 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.identity(db) == rhs_bound_typevar.identity(db) => { + (Type::TypeVar(lhs_bound_typevar), Type::TypeVar(rhs_bound_typevar)) + if !inferable.is_inferable(db, lhs_bound_typevar) + && lhs_bound_typevar.identity(db) == rhs_bound_typevar.identity(db) => + { 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_inferable(db, bound_typevar) + && bound_typevar.typevar(db).bound_or_constraints(db).is_some() => { match bound_typevar.typevar(db).bound_or_constraints(db) { None => unreachable!(), @@ -1690,6 +1716,7 @@ impl<'db> Type<'db> { .has_relation_to_impl( db, target, + inferable, relation, relation_visitor, disjointness_visitor, @@ -1699,6 +1726,7 @@ impl<'db> Type<'db> { constraint.has_relation_to_impl( db, target, + inferable, relation, relation_visitor, disjointness_visitor, @@ -1711,22 +1739,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, - relation, - relation_visitor, - disjointness_visitor, - ) + (_, Type::TypeVar(bound_typevar)) + if !inferable.is_inferable(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, @@ -1740,6 +1770,7 @@ impl<'db> Type<'db> { self.has_relation_to_impl( db, *constraint, + inferable, relation, relation_visitor, disjointness_visitor, @@ -1748,7 +1779,9 @@ impl<'db> Type<'db> { }) } - (Type::TypeVar(_), _) if relation.is_assignability() => { + (Type::TypeVar(bound_typevar), _) + if inferable.is_inferable(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. @@ -1764,6 +1797,7 @@ impl<'db> Type<'db> { elem_ty.has_relation_to_impl( db, target, + inferable, relation, relation_visitor, disjointness_visitor, @@ -1774,6 +1808,7 @@ impl<'db> Type<'db> { self.has_relation_to_impl( db, elem_ty, + inferable, relation, relation_visitor, disjointness_visitor, @@ -1790,6 +1825,7 @@ impl<'db> Type<'db> { self.has_relation_to_impl( db, pos_ty, + inferable, relation, relation_visitor, disjointness_visitor, @@ -1821,6 +1857,7 @@ impl<'db> Type<'db> { self_ty.is_disjoint_from_impl( db, neg_ty, + inferable, disjointness_visitor, relation_visitor, ) @@ -1832,6 +1869,7 @@ impl<'db> Type<'db> { elem_ty.has_relation_to_impl( db, target, + inferable, relation, relation_visitor, disjointness_visitor, @@ -1844,15 +1882,19 @@ 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_inferable(db, bound_typevar) => { + ConstraintSet::from(false) + } (_, Type::TypeVar(typevar)) - if relation.is_assignability() + if inferable.is_inferable(db, typevar) + && relation.is_assignability() && typevar.typevar(db).upper_bound(db).is_none_or(|bound| { !self .has_relation_to_impl( db, bound, + inferable, relation, relation_visitor, disjointness_visitor, @@ -1866,6 +1908,7 @@ impl<'db> Type<'db> { self.has_relation_to_impl( db, bound, + inferable, relation, relation_visitor, disjointness_visitor, @@ -1874,7 +1917,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(db, bound_typevar) => + { + ConstraintSet::from(false) + } (Type::TypedDict(_), _) | (_, Type::TypedDict(_)) => { // TODO: Implement assignability and subtyping for TypedDict @@ -1887,7 +1934,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 @@ -1896,18 +1943,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 @@ -1936,6 +1998,7 @@ impl<'db> Type<'db> { self_callable.has_relation_to_impl( db, other_callable, + inferable, relation, relation_visitor, disjointness_visitor, @@ -1947,6 +2010,7 @@ impl<'db> Type<'db> { callable.has_relation_to_impl( db, target, + inferable, relation, relation_visitor, disjointness_visitor, @@ -1959,6 +2023,7 @@ impl<'db> Type<'db> { self.satisfies_protocol( db, protocol, + inferable, relation, relation_visitor, disjointness_visitor, @@ -2002,6 +2067,7 @@ impl<'db> Type<'db> { instance.has_relation_to_impl( db, target, + inferable, relation, relation_visitor, disjointness_visitor, @@ -2009,16 +2075,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. @@ -2031,6 +2117,7 @@ impl<'db> Type<'db> { .has_relation_to_impl( db, right.return_type(db), + inferable, relation, relation_visitor, disjointness_visitor, @@ -2039,6 +2126,7 @@ impl<'db> Type<'db> { right.return_type(db).has_relation_to_impl( db, left.return_type(db), + inferable, relation, relation_visitor, disjointness_visitor, @@ -2049,6 +2137,7 @@ impl<'db> Type<'db> { (Type::TypeIs(_), _) => KnownClass::Bool.to_instance(db).has_relation_to_impl( db, target, + inferable, relation, relation_visitor, disjointness_visitor, @@ -2061,6 +2150,7 @@ impl<'db> Type<'db> { .has_relation_to_impl( db, target, + inferable, relation, relation_visitor, disjointness_visitor, @@ -2069,10 +2159,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, @@ -2087,6 +2180,7 @@ impl<'db> Type<'db> { ClassType::NonGeneric(class).has_relation_to_impl( db, subclass_of_class, + inferable, relation, relation_visitor, disjointness_visitor, @@ -2100,6 +2194,7 @@ impl<'db> Type<'db> { ClassType::Generic(alias).has_relation_to_impl( db, subclass_of_class, + inferable, relation, relation_visitor, disjointness_visitor, @@ -2112,6 +2207,7 @@ impl<'db> Type<'db> { self_subclass_ty.has_relation_to_impl( db, target_subclass_ty, + inferable, relation, relation_visitor, disjointness_visitor, @@ -2121,12 +2217,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() => { @@ -2135,6 +2245,7 @@ impl<'db> Type<'db> { .has_relation_to_impl( db, other, + inferable, relation, relation_visitor, disjointness_visitor, @@ -2144,6 +2255,7 @@ impl<'db> Type<'db> { other.has_relation_to_impl( db, KnownClass::Type.to_instance(db), + inferable, relation, relation_visitor, disjointness_visitor, @@ -2159,6 +2271,7 @@ impl<'db> Type<'db> { other.has_relation_to_impl( db, KnownClass::Type.to_instance(db), + inferable, relation, relation_visitor, disjointness_visitor, @@ -2177,7 +2290,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 @@ -2185,6 +2305,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, @@ -2193,6 +2314,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, @@ -2205,6 +2327,7 @@ impl<'db> Type<'db> { self_instance.has_relation_to_impl( db, target_instance, + inferable, relation, relation_visitor, disjointness_visitor, @@ -2212,12 +2335,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, @@ -2225,9 +2356,12 @@ 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(_), _) => { + (Type::TypeVar(bound_typevar), _) => { + // All inferable cases should have been handled above + debug_assert!(!inferable.is_inferable(db, bound_typevar)); ConstraintSet::from(false) } + (Type::NominalInstance(_), _) => ConstraintSet::from(false), } } @@ -2244,17 +2378,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, InferableTypeVars::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: InferableTypeVars<'_, 'db>, + ) -> 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: InferableTypeVars<'_, 'db>, visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { if self == other { @@ -2283,44 +2424,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)) => { @@ -2337,7 +2478,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), @@ -2360,13 +2501,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, InferableTypeVars::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: InferableTypeVars<'_, 'db>, + ) -> ConstraintSet<'db> { self.is_disjoint_from_impl( db, other, + inferable, &IsDisjointVisitor::default(), &HasRelationToVisitor::default(), ) @@ -2376,6 +2524,7 @@ impl<'db> Type<'db> { self, db: &'db dyn Db, other: Type<'db>, + inferable: InferableTypeVars<'_, 'db>, disjointness_visitor: &IsDisjointVisitor<'db>, relation_visitor: &HasRelationToVisitor<'db>, ) -> ConstraintSet<'db> { @@ -2383,6 +2532,7 @@ impl<'db> Type<'db> { db: &'db dyn Db, protocol: ProtocolInstanceType<'db>, other: Type<'db>, + inferable: InferableTypeVars<'_, 'db>, disjointness_visitor: &IsDisjointVisitor<'db>, relation_visitor: &HasRelationToVisitor<'db>, ) -> ConstraintSet<'db> { @@ -2395,6 +2545,7 @@ impl<'db> Type<'db> { member.has_disjoint_type_from( db, attribute_type, + inferable, disjointness_visitor, relation_visitor, ) @@ -2413,6 +2564,7 @@ impl<'db> Type<'db> { self_alias_ty.is_disjoint_from_impl( db, other, + inferable, disjointness_visitor, relation_visitor, ) @@ -2425,6 +2577,7 @@ impl<'db> Type<'db> { self.is_disjoint_from_impl( db, other_alias_ty, + inferable, disjointness_visitor, relation_visitor, ) @@ -2440,16 +2593,17 @@ 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.identity(db) == other_bound_typevar.identity(db) => { + (Type::TypeVar(self_bound_typevar), Type::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) } - (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_inferable(db, bound_typevar) + && intersection.negative(db).contains(&tvar) => { ConstraintSet::from(true) } @@ -2458,17 +2612,25 @@ 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_inferable(db, bound_typevar) => + { 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, ) @@ -2482,7 +2644,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, + ) }) } @@ -2498,6 +2666,7 @@ impl<'db> Type<'db> { p.is_disjoint_from_impl( db, other, + inferable, disjointness_visitor, relation_visitor, ) @@ -2507,6 +2676,7 @@ impl<'db> Type<'db> { p.is_disjoint_from_impl( db, self, + inferable, disjointness_visitor, relation_visitor, ) @@ -2525,6 +2695,7 @@ impl<'db> Type<'db> { p.is_disjoint_from_impl( db, non_intersection, + inferable, disjointness_visitor, relation_visitor, ) @@ -2535,6 +2706,7 @@ impl<'db> Type<'db> { non_intersection.has_relation_to_impl( db, neg_ty, + inferable, TypeRelation::Subtyping, relation_visitor, disjointness_visitor, @@ -2618,7 +2790,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)) @@ -2628,6 +2800,7 @@ impl<'db> Type<'db> { db, protocol, special_form.instance_fallback(db), + inferable, disjointness_visitor, relation_visitor, ) @@ -2641,6 +2814,7 @@ impl<'db> Type<'db> { db, protocol, known_instance.instance_fallback(db), + inferable, disjointness_visitor, relation_visitor, ) @@ -2704,6 +2878,7 @@ impl<'db> Type<'db> { db, protocol, ty, + inferable, disjointness_visitor, relation_visitor, ) @@ -2721,6 +2896,7 @@ impl<'db> Type<'db> { db, protocol, nominal, + inferable, disjointness_visitor, relation_visitor, ) @@ -2735,6 +2911,7 @@ impl<'db> Type<'db> { Place::Type(attribute_type, _) => member.has_disjoint_type_from( db, attribute_type, + inferable, disjointness_visitor, relation_visitor, ), @@ -2759,25 +2936,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)) @@ -2844,6 +3033,7 @@ impl<'db> Type<'db> { .has_relation_to_impl( db, instance, + inferable, TypeRelation::Subtyping, relation_visitor, disjointness_visitor, @@ -2858,7 +3048,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)) => { @@ -2867,6 +3057,7 @@ impl<'db> Type<'db> { .has_relation_to_impl( db, instance, + inferable, TypeRelation::Subtyping, relation_visitor, disjointness_visitor, @@ -2885,12 +3076,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, ) @@ -2899,7 +3097,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(_)) @@ -2947,6 +3151,7 @@ impl<'db> Type<'db> { .has_relation_to_impl( db, CallableType::unknown(db), + inferable, TypeRelation::Assignability, relation_visitor, disjointness_visitor, @@ -2972,6 +3177,7 @@ impl<'db> Type<'db> { other.is_disjoint_from_impl( db, KnownClass::ModuleType.to_instance(db), + inferable, disjointness_visitor, relation_visitor, ) @@ -2979,24 +3185,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), } } @@ -3043,7 +3262,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, @@ -3054,8 +3273,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, @@ -3154,7 +3371,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, @@ -3165,8 +3382,6 @@ impl<'db> Type<'db> { } } - Type::TypeVar(_) => false, - Type::SubclassOf(..) => { // TODO: Same comment as above for `is_singleton` false @@ -3331,7 +3546,6 @@ impl<'db> Type<'db> { | Type::LiteralString | Type::BytesLiteral(_) | Type::EnumLiteral(_) - | Type::NonInferableTypeVar(_) | Type::TypeVar(_) | Type::NominalInstance(_) | Type::ProtocolInstance(_) @@ -3442,7 +3656,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)) => { @@ -3455,16 +3669,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) @@ -4032,7 +4236,6 @@ impl<'db> Type<'db> { | Type::BytesLiteral(..) | Type::EnumLiteral(..) | Type::LiteralString - | Type::NonInferableTypeVar(..) | Type::TypeVar(..) | Type::SpecialForm(..) | Type::KnownInstance(..) @@ -4384,7 +4587,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)) => { @@ -4395,7 +4598,6 @@ impl<'db> Type<'db> { } } } - Type::TypeVar(_) => Truthiness::Ambiguous, Type::NominalInstance(instance) => instance .known_class(db) @@ -4494,7 +4696,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), @@ -4507,15 +4709,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()) @@ -5433,18 +5626,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))), @@ -5761,7 +5952,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), }, @@ -5914,9 +6105,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")), @@ -6000,7 +6188,6 @@ impl<'db> Type<'db> { | Type::LiteralString | Type::ModuleLiteral(_) | Type::StringLiteral(_) - | Type::NonInferableTypeVar(_) | Type::TypeVar(_) | Type::Callable(_) | Type::BoundMethod(_) @@ -6032,7 +6219,7 @@ impl<'db> Type<'db> { typevar_binding_context, *typevar, ) - .map(Type::NonInferableTypeVar) + .map(Type::TypeVar) .unwrap_or(*self)) } KnownInstanceType::Deprecated(_) => Err(InvalidTypeExpressionError { @@ -6109,14 +6296,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 { @@ -6286,7 +6466,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), @@ -6297,7 +6477,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), @@ -6431,36 +6610,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)) @@ -6470,7 +6625,6 @@ impl<'db> Type<'db> { TypeMapping::PromoteLiterals | TypeMapping::BindSelf(_) | TypeMapping::ReplaceSelf { .. } | - TypeMapping::MarkTypeVarsInferable(_) | TypeMapping::Materialize(_) => self, } @@ -6585,7 +6739,6 @@ impl<'db> Type<'db> { TypeMapping::BindLegacyTypevars(_) | TypeMapping::BindSelf(_) | TypeMapping::ReplaceSelf { .. } | - TypeMapping::MarkTypeVarsInferable(_) | TypeMapping::Materialize(_) => self, TypeMapping::PromoteLiterals => self.promote_literals_impl(db, tcx) } @@ -6596,7 +6749,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(), @@ -6646,7 +6798,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 @@ -6878,7 +7030,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 { @@ -7020,9 +7171,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 } @@ -7078,7 +7227,6 @@ impl<'db> VarianceInferable<'db> for Type<'db> { | Type::AlwaysTruthy | Type::BoundSuper(_) | Type::TypeVar(_) - | Type::NonInferableTypeVar(_) | Type::TypedDict(_) | Type::TypeAlias(_) => TypeVarVariance::Bivariant, }; @@ -7132,17 +7280,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), } @@ -7159,7 +7296,6 @@ impl<'db> TypeMapping<'_, 'db> { | TypeMapping::PartialSpecialization(_) | TypeMapping::PromoteLiterals | TypeMapping::BindLegacyTypevars(_) - | TypeMapping::MarkTypeVarsInferable(_) | TypeMapping::Materialize(_) => context, TypeMapping::BindSelf(_) => GenericContext::from_typevar_instances( db, @@ -8035,48 +8171,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() - }), - }); - - 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 { let bound_or_constraints = match self.bound_or_constraints(db)? { TypeVarBoundOrConstraints::UpperBound(upper_bound) => { @@ -8234,7 +8328,7 @@ 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, get_size2::GetSize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, get_size2::GetSize, salsa::Update)] pub struct BoundTypeVarIdentity<'db> { pub(crate) identity: TypeVarIdentity<'db>, pub(crate) binding_context: BindingContext<'db>, @@ -8399,18 +8493,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, @@ -8516,38 +8598,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. @@ -9814,6 +9864,7 @@ impl<'db> BoundMethodType<'db> { self, db: &'db dyn Db, other: Self, + inferable: InferableTypeVars<'_, 'db>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -9823,11 +9874,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, @@ -9839,14 +9898,18 @@ impl<'db> BoundMethodType<'db> { self, db: &'db dyn Db, other: Self, + inferable: InferableTypeVars<'_, 'db>, 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, + ) }) } } @@ -9968,6 +10031,7 @@ impl<'db> CallableType<'db> { self, db: &'db dyn Db, other: Self, + inferable: InferableTypeVars<'_, 'db>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -9978,6 +10042,7 @@ impl<'db> CallableType<'db> { self.signatures(db).has_relation_to_impl( db, other.signatures(db), + inferable, relation, relation_visitor, disjointness_visitor, @@ -9991,6 +10056,7 @@ impl<'db> CallableType<'db> { self, db: &'db dyn Db, other: Self, + inferable: InferableTypeVars<'_, 'db>, visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { if self == other { @@ -9999,7 +10065,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) }) } } @@ -10060,19 +10126,35 @@ impl<'db> KnownBoundMethodType<'db> { self, db: &'db dyn Db, other: Self, + inferable: InferableTypeVars<'_, 'db>, 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), @@ -10081,7 +10163,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) @@ -10112,18 +10194,19 @@ impl<'db> KnownBoundMethodType<'db> { self, db: &'db dyn Db, other: Self, + inferable: InferableTypeVars<'_, 'db>, 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), @@ -10132,7 +10215,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) @@ -10977,6 +11060,7 @@ impl<'db> UnionType<'db> { self, db: &'db dyn Db, other: Self, + _inferable: InferableTypeVars<'_, 'db>, _visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { if self == other { @@ -11078,6 +11162,7 @@ impl<'db> IntersectionType<'db> { self, db: &'db dyn Db, other: Self, + _inferable: InferableTypeVars<'_, 'db>, _visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { if self == other { diff --git a/crates/ty_python_semantic/src/types/bound_super.rs b/crates/ty_python_semantic/src/types/bound_super.rs index 07ce3fbc46146d..03d463413ea2e7 100644 --- a/crates/ty_python_semantic/src/types/bound_super.rs +++ b/crates/ty_python_semantic/src/types/bound_super.rs @@ -329,7 +329,7 @@ impl<'db> BoundSuperType<'db> { Type::TypeAlias(alias) => { return delegate_with_error_mapped(alias.value_type(db), None); } - Type::TypeVar(type_var) | Type::NonInferableTypeVar(type_var) => { + Type::TypeVar(type_var) => { let type_var = type_var.typevar(db); return match type_var.bound_or_constraints(db) { Some(TypeVarBoundOrConstraints::UpperBound(bound)) => { 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/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index 7854982a7301d3..1d29ef48d5de62 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -26,7 +26,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 +599,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); + 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 +610,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); + 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 +621,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); + 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 +632,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); + 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), @@ -1407,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; } @@ -1633,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 { @@ -1750,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 @@ -2461,6 +2482,7 @@ struct ArgumentTypeChecker<'a, 'db> { call_expression_tcx: &'a TypeContext<'db>, errors: &'a mut Vec>, + inferable_typevars: InferableTypeVars<'db, 'db>, specialization: Option>, } @@ -2482,6 +2504,7 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> { parameter_tys, call_expression_tcx, errors, + inferable_typevars: InferableTypeVars::None, specialization: None, } } @@ -2514,11 +2537,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); + 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 @@ -2563,10 +2587,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( @@ -2590,7 +2611,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) + .when_assignable_to(self.db, expected_ty, self.inferable_typevars) .is_never_satisfied() { let positional = matches!(argument, Argument::Positional | Argument::Synthetic) @@ -2719,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, @@ -2754,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) } } @@ -2819,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>, @@ -2845,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, @@ -2916,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); } @@ -3010,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(), @@ -3020,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, @@ -3027,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; @@ -3046,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([]); @@ -3056,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>]>, @@ -3095,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 diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index 201e382fdd4a3b..a93315d76b65fa 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -22,7 +22,7 @@ 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_generic_context, walk_specialization, + GenericContext, InferableTypeVars, Specialization, walk_generic_context, walk_specialization, }; use crate::types::infer::nearest_enclosing_class; use crate::types::member::{Member, class_member}; @@ -540,17 +540,20 @@ 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, InferableTypeVars::None) + .is_always_satisfied() } pub(super) fn when_subclass_of( self, db: &'db dyn Db, other: ClassType<'db>, + inferable: InferableTypeVars<'_, 'db>, ) -> ConstraintSet<'db> { self.has_relation_to_impl( db, other, + inferable, TypeRelation::Subtyping, &HasRelationToVisitor::default(), &IsDisjointVisitor::default(), @@ -561,6 +564,7 @@ impl<'db> ClassType<'db> { self, db: &'db dyn Db, other: Self, + inferable: InferableTypeVars<'_, 'db>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -586,6 +590,7 @@ impl<'db> ClassType<'db> { base.specialization(db).has_relation_to_impl( db, other.specialization(db), + inferable, relation, relation_visitor, disjointness_visitor, @@ -610,6 +615,7 @@ impl<'db> ClassType<'db> { self, db: &'db dyn Db, other: ClassType<'db>, + inferable: InferableTypeVars<'_, 'db>, visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { if self == other { @@ -628,6 +634,7 @@ impl<'db> ClassType<'db> { this.specialization(db).is_equivalent_to_impl( db, other.specialization(db), + inferable, visitor, ) }) @@ -1617,15 +1624,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 debde1a54c5c8a..9415851f9a2c7a 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.identity(self.db).display(self.db).fmt(f) - } + Type::TypeVar(bound_typevar) => bound_typevar.identity(self.db).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 f7cce4e03a869b..dc62d700fd1537 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}; @@ -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}; @@ -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 /// @@ -959,46 +943,42 @@ impl<'db> FunctionType<'db> { self, db: &'db dyn Db, other: Self, + inferable: InferableTypeVars<'_, 'db>, relation: TypeRelation, - _visitor: &HasRelationToVisitor<'db>, + relation_visitor: &HasRelationToVisitor<'db>, + disjointness_visitor: &IsDisjointVisitor<'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, + inferable, + relation, + relation_visitor, + disjointness_visitor, + ) } pub(crate) fn is_equivalent_to_impl( self, db: &'db dyn Db, other: Self, + inferable: InferableTypeVars<'_, 'db>, visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { if self.normalized(db) == other.normalized(db) { @@ -1009,7 +989,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( @@ -1125,7 +1105,6 @@ fn is_instance_truthiness<'db>( | Type::PropertyInstance(..) | Type::AlwaysTruthy | Type::AlwaysFalsy - | Type::NonInferableTypeVar(..) | Type::TypeVar(..) | Type::BoundSuper(..) | Type::TypeIs(..) @@ -1721,11 +1700,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; }; @@ -1738,11 +1713,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 afb3cc45cedbad..a6896537bf7dd5 100644 --- a/crates/ty_python_semantic/src/types/generics.rs +++ b/crates/ty_python_semantic/src/types/generics.rs @@ -1,26 +1,30 @@ -use crate::types::constraints::ConstraintSet; +use std::cell::RefCell; +use std::fmt::Display; 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, 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, + 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. @@ -105,7 +109,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)); @@ -117,7 +120,7 @@ pub(crate) fn typing_self<'db>( ); let bounds = TypeVarBoundOrConstraints::UpperBound(Type::instance( db, - class.identity_specialization(db, typevar_to_type), + class.identity_specialization(db), )); let typevar = TypeVarInstance::new( db, @@ -137,7 +140,68 @@ pub(crate) fn typing_self<'db>( typevar_binding_context, typevar, ) - .map(typevar_to_type) + .map(Type::TypeVar) +} + +#[derive(Clone, Copy, Debug)] +pub(crate) enum InferableTypeVars<'a, 'db> { + None, + One(&'a FxHashSet>), + Two( + &'a InferableTypeVars<'a, 'db>, + &'a InferableTypeVars<'a, 'db>, + ), +} + +impl<'a, 'db> InferableTypeVars<'a, 'db> { + 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)), + InferableTypeVars::Two(left, right) => { + left.is_inferable(db, bound_typevar) || right.is_inferable(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, + } + } + + // 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(|identity| identity.display(db)) + .format(", ") + ) + } } #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, get_size2::GetSize)] @@ -185,6 +249,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, @@ -244,6 +309,71 @@ 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), + 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. + 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.identity(db)); + 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( self, db: &'db dyn Db, @@ -346,6 +476,15 @@ 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.identity(db)) + } + pub(crate) fn signature(self, db: &'db dyn Db) -> Signature<'db> { let parameters = Parameters::new( self.variables(db) @@ -399,14 +538,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) } @@ -532,6 +665,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, @@ -593,12 +742,14 @@ 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>, derived_materialization: MaterializationKind, base_type: &Type<'db>, base_materialization: MaterializationKind, + inferable: InferableTypeVars<'_, 'db>, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, ) -> ConstraintSet<'db> { @@ -623,6 +774,7 @@ fn is_subtype_in_invariant_position<'db>( derived.has_relation_to_impl( db, base, + inferable, TypeRelation::Subtyping, relation_visitor, disjointness_visitor, @@ -675,6 +827,7 @@ fn has_relation_in_invariant_position<'db>( derived_materialization: Option, base_type: &Type<'db>, base_materialization: Option, + inferable: InferableTypeVars<'_, 'db>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -688,6 +841,7 @@ fn has_relation_in_invariant_position<'db>( derived_mat, base_type, base_mat, + inferable, relation_visitor, disjointness_visitor, ), @@ -707,6 +861,7 @@ fn has_relation_in_invariant_position<'db>( .has_relation_to_impl( db, *base_type, + inferable, relation, relation_visitor, disjointness_visitor, @@ -715,6 +870,7 @@ fn has_relation_in_invariant_position<'db>( base_type.has_relation_to_impl( db, *derived_type, + inferable, relation, relation_visitor, disjointness_visitor, @@ -728,6 +884,7 @@ fn has_relation_in_invariant_position<'db>( MaterializationKind::Top, base_type, base_mat, + inferable, relation_visitor, disjointness_visitor, ) @@ -739,6 +896,7 @@ fn has_relation_in_invariant_position<'db>( derived_mat, base_type, MaterializationKind::Bottom, + inferable, relation_visitor, disjointness_visitor, ) @@ -750,6 +908,7 @@ fn has_relation_in_invariant_position<'db>( MaterializationKind::Bottom, base_type, base_mat, + inferable, relation_visitor, disjointness_visitor, ), @@ -759,6 +918,7 @@ fn has_relation_in_invariant_position<'db>( derived_mat, base_type, MaterializationKind::Top, + inferable, relation_visitor, disjointness_visitor, ), @@ -1002,6 +1162,7 @@ impl<'db> Specialization<'db> { self, db: &'db dyn Db, other: Self, + inferable: InferableTypeVars<'_, 'db>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -1016,6 +1177,7 @@ impl<'db> Specialization<'db> { return self_tuple.has_relation_to_impl( db, other_tuple, + inferable, relation, relation_visitor, disjointness_visitor, @@ -1043,6 +1205,7 @@ impl<'db> Specialization<'db> { self_materialization_kind, other_type, other_materialization_kind, + inferable, relation, relation_visitor, disjointness_visitor, @@ -1050,6 +1213,7 @@ impl<'db> Specialization<'db> { TypeVarVariance::Covariant => self_type.has_relation_to_impl( db, *other_type, + inferable, relation, relation_visitor, disjointness_visitor, @@ -1057,6 +1221,7 @@ impl<'db> Specialization<'db> { TypeVarVariance::Contravariant => other_type.has_relation_to_impl( db, *self_type, + inferable, relation, relation_visitor, disjointness_visitor, @@ -1075,6 +1240,7 @@ impl<'db> Specialization<'db> { self, db: &'db dyn Db, other: Specialization<'db>, + inferable: InferableTypeVars<'_, 'db>, visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { if self.materialization_kind(db) != other.materialization_kind(db) { @@ -1100,7 +1266,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), }; @@ -1113,7 +1279,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; } @@ -1168,13 +1335,15 @@ impl<'db> PartialSpecialization<'_, 'db> { /// specialization of a generic function. pub(crate) struct SpecializationBuilder<'db> { db: &'db dyn Db, + inferable: InferableTypeVars<'db, '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: InferableTypeVars<'db, 'db>) -> Self { Self { db, + inferable, types: FxHashMap::default(), } } @@ -1244,14 +1413,16 @@ 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(()); } // 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 @@ -1321,10 +1492,15 @@ 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(self.db, 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, @@ -1334,7 +1510,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/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 538d170ae60a38..b71cccfdb4fdde 100644 --- a/crates/ty_python_semantic/src/types/infer/builder.rs +++ b/crates/ty_python_semantic/src/types/infer/builder.rs @@ -3571,7 +3571,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { | Type::WrapperDescriptor(_) | Type::DataclassDecorator(_) | Type::DataclassTransformer(_) - | Type::NonInferableTypeVar(..) | Type::TypeVar(..) | Type::AlwaysTruthy | Type::AlwaysFalsy @@ -5409,6 +5408,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 @@ -5421,7 +5421,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 @@ -5938,10 +5979,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() { @@ -5951,11 +5996,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. @@ -5964,7 +6010,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()); + let mut builder = SpecializationBuilder::new(self.db(), inferable); match annotated_elt_tys { // The annotated type acts as a constraint for `T`. @@ -6029,7 +6075,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()) }); @@ -7601,7 +7647,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { | Type::BytesLiteral(_) | Type::EnumLiteral(_) | Type::BoundSuper(_) - | Type::NonInferableTypeVar(_) | Type::TypeVar(_) | Type::TypeIs(_) | Type::TypedDict(_), @@ -7973,7 +8018,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { | Type::BytesLiteral(_) | Type::EnumLiteral(_) | Type::BoundSuper(_) - | Type::NonInferableTypeVar(_) | Type::TypeVar(_) | Type::TypeIs(_) | Type::TypedDict(_), @@ -8003,7 +8047,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/instance.rs b/crates/ty_python_semantic/src/types/instance.rs index a7515898d1f238..b2a2b68cae98c6 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::{InferableTypeVars, 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: InferableTypeVars<'_, 'db>, 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, @@ -173,6 +176,7 @@ impl<'db> Type<'db> { type_to_test.has_relation_to_impl( db, Type::NominalInstance(nominal_instance), + inferable, relation, relation_visitor, disjointness_visitor, @@ -360,6 +364,7 @@ impl<'db> NominalInstanceType<'db> { self, db: &'db dyn Db, other: Self, + inferable: InferableTypeVars<'_, 'db>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -372,6 +377,7 @@ impl<'db> NominalInstanceType<'db> { ) => tuple1.has_relation_to_impl( db, tuple2, + inferable, relation, relation_visitor, disjointness_visitor, @@ -379,6 +385,7 @@ impl<'db> NominalInstanceType<'db> { _ => self.class(db).has_relation_to_impl( db, other.class(db), + inferable, relation, relation_visitor, disjointness_visitor, @@ -390,18 +397,19 @@ impl<'db> NominalInstanceType<'db> { self, db: &'db dyn Db, other: Self, + inferable: InferableTypeVars<'_, 'db>, 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), } @@ -411,6 +419,7 @@ impl<'db> NominalInstanceType<'db> { self, db: &'db dyn Db, other: Self, + inferable: InferableTypeVars<'_, 'db>, disjointness_visitor: &IsDisjointVisitor<'db>, relation_visitor: &HasRelationToVisitor<'db>, ) -> ConstraintSet<'db> { @@ -423,6 +432,7 @@ impl<'db> NominalInstanceType<'db> { let compatible = self_spec.is_disjoint_from_impl( db, &other_spec, + inferable, disjointness_visitor, relation_visitor, ); @@ -650,6 +660,7 @@ impl<'db> ProtocolInstanceType<'db> { .satisfies_protocol( db, protocol, + InferableTypeVars::None, TypeRelation::Subtyping, &HasRelationToVisitor::default(), &IsDisjointVisitor::default(), @@ -708,6 +719,7 @@ impl<'db> ProtocolInstanceType<'db> { self, db: &'db dyn Db, other: Self, + _inferable: InferableTypeVars<'_, 'db>, _visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { if self == other { @@ -729,6 +741,7 @@ impl<'db> ProtocolInstanceType<'db> { self, _db: &'db dyn Db, _other: Self, + _inferable: InferableTypeVars<'_, 'db>, _visitor: &IsDisjointVisitor<'db>, ) -> ConstraintSet<'db> { ConstraintSet::from(false) 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/protocol_class.rs b/crates/ty_python_semantic/src/types/protocol_class.rs index 529824b1eab29a..78bf8cc6924b2b 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::InferableTypeVars, signatures::{Parameter, Parameters}, todo_type, }, @@ -235,6 +236,7 @@ impl<'db> ProtocolInterface<'db> { self, db: &'db dyn Db, other: Self, + inferable: InferableTypeVars<'_, 'db>, 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: InferableTypeVars<'_, 'db>, 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: InferableTypeVars<'_, 'db>, 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 8547d41a8e28cd..efdec2696ed9fb 100644 --- a/crates/ty_python_semantic/src/types/signatures.rs +++ b/crates/ty_python_semantic/src/types/signatures.rs @@ -23,13 +23,14 @@ 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, 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}; @@ -174,41 +175,27 @@ 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> { + fn is_subtype_of_impl( + &self, + db: &'db dyn Db, + other: &Self, + inferable: InferableTypeVars<'_, 'db>, + ) -> ConstraintSet<'db> { self.has_relation_to_impl( db, other, + inferable, TypeRelation::Subtyping, &HasRelationToVisitor::default(), &IsDisjointVisitor::default(), ) } - /// 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, other: &Self, + inferable: InferableTypeVars<'_, 'db>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -217,6 +204,7 @@ impl<'db> CallableSignature<'db> { db, &self.overloads, &other.overloads, + inferable, relation, relation_visitor, disjointness_visitor, @@ -229,6 +217,7 @@ impl<'db> CallableSignature<'db> { db: &'db dyn Db, self_signatures: &[Signature<'db>], other_signatures: &[Signature<'db>], + inferable: InferableTypeVars<'_, 'db>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -239,6 +228,7 @@ impl<'db> CallableSignature<'db> { self_signature.has_relation_to_impl( db, other_signature, + inferable, relation, relation_visitor, disjointness_visitor, @@ -251,6 +241,7 @@ impl<'db> CallableSignature<'db> { db, std::slice::from_ref(self_signature), other_signatures, + inferable, relation, relation_visitor, disjointness_visitor, @@ -263,6 +254,7 @@ impl<'db> CallableSignature<'db> { db, self_signatures, std::slice::from_ref(other_signature), + inferable, relation, relation_visitor, disjointness_visitor, @@ -275,6 +267,7 @@ impl<'db> CallableSignature<'db> { db, self_signatures, std::slice::from_ref(other_signature), + inferable, relation, relation_visitor, disjointness_visitor, @@ -290,20 +283,21 @@ impl<'db> CallableSignature<'db> { &self, db: &'db dyn Db, other: &Self, + inferable: InferableTypeVars<'_, 'db>, 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)) } } } @@ -451,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 @@ -619,14 +600,27 @@ impl<'db> Signature<'db> { &self, db: &'db dyn Db, other: &Signature<'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()); 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() }; @@ -702,6 +696,7 @@ impl<'db> Signature<'db> { &self, db: &'db dyn Db, other: &Signature<'db>, + inferable: InferableTypeVars<'_, 'db>, relation: TypeRelation, relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, @@ -767,6 +762,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()); @@ -777,6 +781,7 @@ impl<'db> Signature<'db> { type1.has_relation_to_impl( db, type2, + inferable, relation, relation_visitor, disjointness_visitor, @@ -1300,19 +1305,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 diff --git a/crates/ty_python_semantic/src/types/subclass_of.rs b/crates/ty_python_semantic/src/types/subclass_of.rs index c6a16620a94c3d..db55132c1ea131 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::InferableTypeVars; 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: InferableTypeVars<'_, 'db>, 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: 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 a94a0610e72569..e70c7708a290e9 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::InferableTypeVars; 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: InferableTypeVars<'_, 'db>, 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: InferableTypeVars<'_, 'db>, 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: InferableTypeVars<'_, 'db>, 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: InferableTypeVars<'_, 'db>, 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: InferableTypeVars<'_, 'db>, 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: InferableTypeVars<'_, 'db>, 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: InferableTypeVars<'_, 'db>, 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: InferableTypeVars<'_, 'db>, 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: InferableTypeVars<'_, 'db>, 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: InferableTypeVars<'_, 'db>, 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, ) diff --git a/crates/ty_python_semantic/src/types/type_ordering.rs b/crates/ty_python_semantic/src/types/type_ordering.rs index f331aefa63219c..fe9631d405bef3 100644 --- a/crates/ty_python_semantic/src/types/type_ordering.rs +++ b/crates/ty_python_semantic/src/types/type_ordering.rs @@ -141,10 +141,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 69ce7a663d4fd1..51b77432a40818 100644 --- a/crates/ty_python_semantic/src/types/visitor.rs +++ b/crates/ty_python_semantic/src/types/visitor.rs @@ -122,7 +122,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>), @@ -186,9 +185,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)) } @@ -228,9 +224,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); }