diff --git a/crates/ty_python_semantic/resources/mdtest/type_properties/implies_subtype_of.md b/crates/ty_python_semantic/resources/mdtest/type_properties/implies_subtype_of.md index 7de6911015..a3c682917d 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_properties/implies_subtype_of.md +++ b/crates/ty_python_semantic/resources/mdtest/type_properties/implies_subtype_of.md @@ -357,19 +357,11 @@ def identity[T](t: T) -> T: constraints = ConstraintSet.always() -# TODO: no error -# error: [static-assert-error] static_assert(constraints.implies_subtype_of(TypeOf[identity], Callable[[int], int])) -# TODO: no error -# error: [static-assert-error] static_assert(constraints.implies_subtype_of(TypeOf[identity], Callable[[str], str])) static_assert(not constraints.implies_subtype_of(TypeOf[identity], Callable[[str], int])) -# TODO: no error -# error: [static-assert-error] static_assert(constraints.implies_subtype_of(CallableTypeOf[identity], Callable[[int], int])) -# TODO: no error -# error: [static-assert-error] static_assert(constraints.implies_subtype_of(CallableTypeOf[identity], Callable[[str], str])) static_assert(not constraints.implies_subtype_of(CallableTypeOf[identity], Callable[[str], int])) ``` @@ -415,12 +407,10 @@ def identity2[T](t: T) -> T: # This constraint set refers to the same typevar as the generic function types below! constraints = ConstraintSet.range(bool, T, int) - # TODO: no error - # error: [static-assert-error] static_assert(constraints.implies_subtype_of(TypeOf[identity2], Callable[[int], int])) + static_assert(constraints.implies_subtype_of(TypeOf[identity2], Callable[[str], str])) # TODO: no error # error: [static-assert-error] - static_assert(constraints.implies_subtype_of(TypeOf[identity2], Callable[[str], str])) static_assert(not constraints.implies_subtype_of(TypeOf[identity2], Callable[[str], int])) static_assert(not constraints.implies_subtype_of(Callable[[int], int], TypeOf[identity2])) diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 2bbb1a1eb5..d6930d28f1 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -202,9 +202,9 @@ pub(crate) type ApplyTypeMappingVisitor<'db> = TypeTransformer<'db, TypeMapping< pub(crate) type HasRelationToVisitor<'db> = CycleDetector, (Type<'db>, Type<'db>, TypeRelation<'db>), ConstraintSet<'db>>; -impl Default for HasRelationToVisitor<'_> { - fn default() -> Self { - HasRelationToVisitor::new(ConstraintSet::from(true)) +impl<'db> HasRelationToVisitor<'db> { + fn from_inferable(inferable: InferableTypeVars<'db>) -> Self { + HasRelationToVisitor::new(ConstraintSet::always(inferable)) } } @@ -214,9 +214,9 @@ pub(crate) type IsDisjointVisitor<'db> = PairVisitor<'db, IsDisjoint, Constraint #[derive(Debug)] pub(crate) struct IsDisjoint; -impl Default for IsDisjointVisitor<'_> { - fn default() -> Self { - IsDisjointVisitor::new(ConstraintSet::from(false)) +impl<'db> IsDisjointVisitor<'db> { + fn from_inferable(inferable: InferableTypeVars<'db>) -> Self { + IsDisjointVisitor::new(ConstraintSet::never(inferable)) } } @@ -226,9 +226,9 @@ pub(crate) type IsEquivalentVisitor<'db> = PairVisitor<'db, IsEquivalent, Constr #[derive(Debug)] pub(crate) struct IsEquivalent; -impl Default for IsEquivalentVisitor<'_> { - fn default() -> Self { - IsEquivalentVisitor::new(ConstraintSet::from(true)) +impl<'db> IsEquivalentVisitor<'db> { + fn from_inferable(inferable: InferableTypeVars<'db>) -> Self { + IsEquivalentVisitor::new(ConstraintSet::always(inferable)) } } @@ -550,7 +550,12 @@ impl<'db> PropertyInstanceType<'db> { other: Self, inferable: InferableTypeVars<'db>, ) -> ConstraintSet<'db> { - self.is_equivalent_to_impl(db, other, inferable, &IsEquivalentVisitor::default()) + self.is_equivalent_to_impl( + db, + other, + inferable, + &IsEquivalentVisitor::from_inferable(inferable), + ) } fn is_equivalent_to_impl( @@ -562,27 +567,27 @@ impl<'db> PropertyInstanceType<'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); + return ConstraintSet::never(inferable); }; getter.is_equivalent_to_impl(db, other_getter, inferable, visitor) } else { if other.getter(db).is_some() { - return ConstraintSet::from(false); + return ConstraintSet::never(inferable); } - ConstraintSet::from(true) + ConstraintSet::always(inferable) }; let setter_equivalence = || { if let Some(setter) = self.setter(db) { let Some(other_setter) = other.setter(db) else { - return ConstraintSet::from(false); + return ConstraintSet::never(inferable); }; setter.is_equivalent_to_impl(db, other_setter, inferable, visitor) } else { if other.setter(db).is_some() { - return ConstraintSet::from(false); + return ConstraintSet::never(inferable); } - ConstraintSet::from(true) + ConstraintSet::always(inferable) } }; @@ -1679,8 +1684,8 @@ impl<'db> Type<'db> { target, inferable, relation, - &HasRelationToVisitor::default(), - &IsDisjointVisitor::default(), + &HasRelationToVisitor::from_inferable(inferable), + &IsDisjointVisitor::from_inferable(inferable), ) } @@ -1699,7 +1704,7 @@ impl<'db> Type<'db> { // Note that we could do a full equivalence check here, but that would be both expensive // and unnecessary. This early return is only an optimisation. if (!relation.is_subtyping() || self.subtyping_is_always_reflexive()) && self == target { - return ConstraintSet::from(true); + return ConstraintSet::always(inferable); } // Handle constraint implication first. If either `self` or `target` is a typevar, check @@ -1713,22 +1718,22 @@ impl<'db> Type<'db> { match (self, target) { // Everything is a subtype of `object`. (_, Type::NominalInstance(instance)) if instance.is_object() => { - ConstraintSet::from(true) + ConstraintSet::always(inferable) } (_, Type::ProtocolInstance(target)) if target.is_equivalent_to_object(db) => { - ConstraintSet::from(true) + ConstraintSet::always(inferable) } // `Never` is the bottom type, the empty set. // It is a subtype of all other types. - (Type::Never, _) => ConstraintSet::from(true), + (Type::Never, _) => ConstraintSet::always(inferable), // In some specific situations, `Any`/`Unknown`/`@Todo` can be simplified out of unions and intersections, // but this is not true for divergent types (and moving this case any lower down appears to cause // "too many cycle iterations" panics). (Type::Dynamic(DynamicType::Divergent(_)), _) | (_, Type::Dynamic(DynamicType::Divergent(_))) => { - ConstraintSet::from(relation.is_assignability()) + ConstraintSet::from_bool(relation.is_assignability(), inferable) } (Type::TypeAlias(self_alias), _) => { @@ -1763,16 +1768,18 @@ impl<'db> Type<'db> { (Type::KnownInstance(KnownInstanceType::Field(field)), right) if relation.is_assignability() => { - field.default_type(db).when_none_or(|default_type| { - default_type.has_relation_to_impl( - db, - right, - inferable, - relation, - relation_visitor, - disjointness_visitor, - ) - }) + field + .default_type(db) + .when_none_or(inferable, |default_type| { + default_type.has_relation_to_impl( + db, + right, + inferable, + relation, + relation_visitor, + disjointness_visitor, + ) + }) } // Dynamic is only a subtype of `object` and only a supertype of `Never`; both were @@ -1788,31 +1795,37 @@ impl<'db> Type<'db> { !matches!(dynamic, DynamicType::Divergent(_)), "DynamicType::Divergent should have been handled in an earlier branch" ); - ConstraintSet::from(match relation { + ConstraintSet::from_bool( + match relation { + TypeRelation::Subtyping | TypeRelation::ConstraintImplication(_) => false, + TypeRelation::Assignability => true, + TypeRelation::Redundancy => match target { + Type::Dynamic(_) => true, + Type::Union(union) => union.elements(db).iter().any(Type::is_dynamic), + _ => false, + }, + }, + inferable, + ) + } + (_, Type::Dynamic(_)) => ConstraintSet::from_bool( + match relation { TypeRelation::Subtyping | TypeRelation::ConstraintImplication(_) => false, TypeRelation::Assignability => true, - TypeRelation::Redundancy => match target { + TypeRelation::Redundancy => match self { Type::Dynamic(_) => true, - Type::Union(union) => union.elements(db).iter().any(Type::is_dynamic), + Type::Intersection(intersection) => { + // If a `Divergent` type is involved, it must not be eliminated. + intersection + .positive(db) + .iter() + .any(Type::is_non_divergent_dynamic) + } _ => false, }, - }) - } - (_, Type::Dynamic(_)) => ConstraintSet::from(match relation { - TypeRelation::Subtyping | TypeRelation::ConstraintImplication(_) => false, - TypeRelation::Assignability => true, - TypeRelation::Redundancy => match self { - Type::Dynamic(_) => true, - Type::Intersection(intersection) => { - // If a `Divergent` type is involved, it must not be eliminated. - intersection - .positive(db) - .iter() - .any(Type::is_non_divergent_dynamic) - } - _ => false, }, - }), + inferable, + ), // In general, a TypeVar `T` is not a subtype of a type `S` unless one of the two conditions is satisfied: // 1. `T` is a bound TypeVar and `T`'s upper bound is a subtype of `S`. @@ -1826,19 +1839,19 @@ impl<'db> Type<'db> { if !bound_typevar.is_inferable(db, inferable) && union.elements(db).contains(&self) => { - ConstraintSet::from(true) + ConstraintSet::always(inferable) } (Type::Intersection(intersection), Type::TypeVar(bound_typevar)) if !bound_typevar.is_inferable(db, inferable) && intersection.positive(db).contains(&target) => { - ConstraintSet::from(true) + ConstraintSet::always(inferable) } (Type::Intersection(intersection), Type::TypeVar(bound_typevar)) if !bound_typevar.is_inferable(db, inferable) && intersection.negative(db).contains(&target) => { - ConstraintSet::from(false) + ConstraintSet::never(inferable) } // Two identical typevars must always solve to the same type, so they are always @@ -1850,7 +1863,7 @@ impl<'db> Type<'db> { if !lhs_bound_typevar.is_inferable(db, inferable) && lhs_bound_typevar.is_same_typevar_as(db, rhs_bound_typevar) => { - ConstraintSet::from(true) + ConstraintSet::always(inferable) } // A fully static typevar is a subtype of its upper bound, and to something similar to @@ -1871,8 +1884,10 @@ impl<'db> Type<'db> { relation_visitor, disjointness_visitor, ), - Some(TypeVarBoundOrConstraints::Constraints(constraints)) => { - constraints.elements(db).iter().when_all(db, |constraint| { + Some(TypeVarBoundOrConstraints::Constraints(constraints)) => constraints + .elements(db) + .iter() + .when_all(db, inferable, |constraint| { constraint.has_relation_to_impl( db, target, @@ -1881,8 +1896,7 @@ impl<'db> Type<'db> { relation_visitor, disjointness_visitor, ) - }) - } + }), } } @@ -1894,8 +1908,8 @@ impl<'db> Type<'db> { && !bound_typevar .typevar(db) .constraints(db) - .when_some_and(|constraints| { - constraints.iter().when_all(db, |constraint| { + .when_some_and(inferable, |constraints| { + constraints.iter().when_all(db, inferable, |constraint| { self.has_relation_to_impl( db, *constraint, @@ -1915,8 +1929,8 @@ impl<'db> Type<'db> { bound_typevar .typevar(db) .constraints(db) - .when_some_and(|constraints| { - constraints.iter().when_all(db, |constraint| { + .when_some_and(inferable, |constraints| { + constraints.iter().when_all(db, inferable, |constraint| { self.has_relation_to_impl( db, *constraint, @@ -1935,33 +1949,43 @@ impl<'db> Type<'db> { // TODO: record the unification constraints - ConstraintSet::from(true) + ConstraintSet::always(inferable) } // `Never` is the bottom type, the empty set. - (_, Type::Never) => ConstraintSet::from(false), + (_, Type::Never) => ConstraintSet::never(inferable), - (Type::Union(union), _) => union.elements(db).iter().when_all(db, |&elem_ty| { - elem_ty.has_relation_to_impl( - db, - target, - inferable, - relation, - relation_visitor, - disjointness_visitor, - ) - }), + (Type::Union(union), _) => { + union + .elements(db) + .iter() + .when_all(db, inferable, |&elem_ty| { + elem_ty.has_relation_to_impl( + db, + target, + inferable, + relation, + relation_visitor, + disjointness_visitor, + ) + }) + } - (_, Type::Union(union)) => union.elements(db).iter().when_any(db, |&elem_ty| { - self.has_relation_to_impl( - db, - elem_ty, - inferable, - relation, - relation_visitor, - disjointness_visitor, - ) - }), + (_, Type::Union(union)) => { + union + .elements(db) + .iter() + .when_any(db, inferable, |&elem_ty| { + self.has_relation_to_impl( + db, + elem_ty, + inferable, + relation, + relation_visitor, + disjointness_visitor, + ) + }) + } // If both sides are intersections we need to handle the right side first // (A & B & C) is a subtype of (A & B) because the left is a subtype of both A and B, @@ -1969,7 +1993,7 @@ impl<'db> Type<'db> { (_, Type::Intersection(intersection)) => intersection .positive(db) .iter() - .when_all(db, |&pos_ty| { + .when_all(db, inferable, |&pos_ty| { self.has_relation_to_impl( db, pos_ty, @@ -1999,34 +2023,40 @@ impl<'db> Type<'db> { | TypeRelation::ConstraintImplication(_) => self, TypeRelation::Assignability => self.bottom_materialization(db), }; - intersection.negative(db).iter().when_all(db, |&neg_ty| { - let neg_ty = match relation { - TypeRelation::Subtyping - | TypeRelation::Redundancy - | TypeRelation::ConstraintImplication(_) => neg_ty, - TypeRelation::Assignability => neg_ty.bottom_materialization(db), - }; - self_ty.is_disjoint_from_impl( - db, - neg_ty, - inferable, - disjointness_visitor, - relation_visitor, - ) - }) + intersection + .negative(db) + .iter() + .when_all(db, inferable, |&neg_ty| { + let neg_ty = match relation { + TypeRelation::Subtyping + | TypeRelation::Redundancy + | TypeRelation::ConstraintImplication(_) => neg_ty, + TypeRelation::Assignability => neg_ty.bottom_materialization(db), + }; + self_ty.is_disjoint_from_impl( + db, + neg_ty, + inferable, + disjointness_visitor, + relation_visitor, + ) + }) }), (Type::Intersection(intersection), _) => { - intersection.positive(db).iter().when_any(db, |&elem_ty| { - elem_ty.has_relation_to_impl( - db, - target, - inferable, - relation, - relation_visitor, - disjointness_visitor, - ) - }) + intersection + .positive(db) + .iter() + .when_any(db, inferable, |&elem_ty| { + elem_ty.has_relation_to_impl( + db, + target, + inferable, + relation, + relation_visitor, + disjointness_visitor, + ) + }) } // Other than the special cases checked above, no other types are a subtype of a @@ -2035,7 +2065,7 @@ impl<'db> Type<'db> { // bound. This is true even if the bound is a final class, since the typevar can still // be specialized to `Never`.) (_, Type::TypeVar(bound_typevar)) if !bound_typevar.is_inferable(db, inferable) => { - ConstraintSet::from(false) + ConstraintSet::never(inferable) } (_, Type::TypeVar(typevar)) @@ -2056,40 +2086,47 @@ impl<'db> Type<'db> { { // TODO: record the unification constraints - typevar.typevar(db).upper_bound(db).when_none_or(|bound| { - self.has_relation_to_impl( - db, - bound, - inferable, - relation, - relation_visitor, - disjointness_visitor, - ) - }) + typevar + .typevar(db) + .upper_bound(db) + .when_none_or(inferable, |bound| { + self.has_relation_to_impl( + db, + bound, + inferable, + relation, + relation_visitor, + disjointness_visitor, + ) + }) } // TODO: Infer specializations here (_, Type::TypeVar(bound_typevar)) if bound_typevar.is_inferable(db, inferable) => { - ConstraintSet::from(false) + ConstraintSet::never(inferable) } (Type::TypeVar(bound_typevar), _) => { // All inferable cases should have been handled above assert!(!bound_typevar.is_inferable(db, inferable)); - ConstraintSet::from(false) + ConstraintSet::never(inferable) } (Type::TypedDict(_), _) => { // TODO: Implement assignability and subtyping for TypedDict - ConstraintSet::from(relation.is_assignability()) + ConstraintSet::from_bool(relation.is_assignability(), inferable) } // A non-`TypedDict` cannot subtype a `TypedDict` - (_, Type::TypedDict(_)) => ConstraintSet::from(false), + (_, Type::TypedDict(_)) => ConstraintSet::never(inferable), // Note that the definition of `Type::AlwaysFalsy` depends on the return value of `__bool__`. // If `__bool__` always returns True or False, it can be treated as a subtype of `AlwaysTruthy` or `AlwaysFalsy`, respectively. - (left, Type::AlwaysFalsy) => ConstraintSet::from(left.bool(db).is_always_false()), - (left, Type::AlwaysTruthy) => ConstraintSet::from(left.bool(db).is_always_true()), + (left, Type::AlwaysFalsy) => { + ConstraintSet::from_bool(left.bool(db).is_always_false(), inferable) + } + (left, Type::AlwaysTruthy) => { + ConstraintSet::from_bool(left.bool(db).is_always_true(), inferable) + } // 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(), inferable) @@ -2149,7 +2186,7 @@ impl<'db> Type<'db> { | Type::FunctionLiteral(_) | Type::ModuleLiteral(_) | Type::EnumLiteral(_), - ) => ConstraintSet::from(false), + ) => ConstraintSet::never(inferable), (Type::Callable(self_callable), Type::Callable(other_callable)) => relation_visitor .visit((self, target, relation), || { @@ -2164,16 +2201,17 @@ impl<'db> Type<'db> { }), (_, Type::Callable(_)) => relation_visitor.visit((self, target, relation), || { - self.try_upcast_to_callable(db).when_some_and(|callable| { - callable.has_relation_to_impl( - db, - target, - inferable, - relation, - relation_visitor, - disjointness_visitor, - ) - }) + self.try_upcast_to_callable(db) + .when_some_and(inferable, |callable| { + callable.has_relation_to_impl( + db, + target, + inferable, + relation, + relation_visitor, + disjointness_visitor, + ) + }) }), (_, Type::ProtocolInstance(protocol)) => { @@ -2190,22 +2228,22 @@ impl<'db> Type<'db> { } // A protocol instance can never be a subtype of a nominal type, with the *sole* exception of `object`. - (Type::ProtocolInstance(_), _) => ConstraintSet::from(false), + (Type::ProtocolInstance(_), _) => ConstraintSet::never(inferable), // All `StringLiteral` types are a subtype of `LiteralString`. - (Type::StringLiteral(_), Type::LiteralString) => ConstraintSet::from(true), + (Type::StringLiteral(_), Type::LiteralString) => ConstraintSet::always(inferable), // An instance is a subtype of an enum literal, if it is an instance of the enum class // and the enum has only one member. (Type::NominalInstance(_), Type::EnumLiteral(target_enum_literal)) => { if target_enum_literal.enum_class_instance(db) != self { - return ConstraintSet::from(false); + return ConstraintSet::never(inferable); } - ConstraintSet::from(is_single_member_enum( - db, - target_enum_literal.enum_class(db), - )) + ConstraintSet::from_bool( + is_single_member_enum(db, target_enum_literal.enum_class(db)), + inferable, + ) } // Except for the special `LiteralString` case above, @@ -2221,7 +2259,7 @@ impl<'db> Type<'db> { | Type::EnumLiteral(_) | Type::FunctionLiteral(_), _, - ) => (self.literal_fallback_instance(db)).when_some_and(|instance| { + ) => (self.literal_fallback_instance(db)).when_some_and(inferable, |instance| { instance.has_relation_to_impl( db, target, @@ -2266,7 +2304,7 @@ impl<'db> Type<'db> { (Type::DataclassDecorator(_) | Type::DataclassTransformer(_), _) => { // TODO: Implement subtyping using an equivalent `Callable` type. - ConstraintSet::from(false) + ConstraintSet::never(inferable) } // `TypeIs` is invariant. @@ -2315,7 +2353,7 @@ impl<'db> Type<'db> { ) } - (Type::Callable(_), _) => ConstraintSet::from(false), + (Type::Callable(_), _) => ConstraintSet::never(inferable), (Type::BoundSuper(_), Type::BoundSuper(_)) => { self.when_equivalent_to(db, target, inferable) @@ -2344,7 +2382,9 @@ impl<'db> Type<'db> { disjointness_visitor, ) }) - .unwrap_or_else(|| ConstraintSet::from(relation.is_assignability())), + .unwrap_or_else(|| { + ConstraintSet::from_bool(relation.is_assignability(), inferable) + }), (Type::GenericAlias(alias), Type::SubclassOf(target_subclass_ty)) => target_subclass_ty .subclass_of() .into_class() @@ -2358,7 +2398,9 @@ impl<'db> Type<'db> { disjointness_visitor, ) }) - .unwrap_or_else(|| ConstraintSet::from(relation.is_assignability())), + .unwrap_or_else(|| { + ConstraintSet::from_bool(relation.is_assignability(), inferable) + }), // This branch asks: given two types `type[T]` and `type[S]`, is `type[T]` a subtype of `type[S]`? (Type::SubclassOf(self_subclass_ty), Type::SubclassOf(target_subclass_ty)) => { @@ -2409,16 +2451,19 @@ impl<'db> Type<'db> { disjointness_visitor, ) .or(db, || { - ConstraintSet::from(relation.is_assignability()).and(db, || { - other.has_relation_to_impl( - db, - KnownClass::Type.to_instance(db), - inferable, - relation, - relation_visitor, - disjointness_visitor, - ) - }) + ConstraintSet::from_bool(relation.is_assignability(), inferable).and( + db, + || { + other.has_relation_to_impl( + db, + KnownClass::Type.to_instance(db), + inferable, + relation, + relation_visitor, + disjointness_visitor, + ) + }, + ) }) } @@ -2494,7 +2539,7 @@ impl<'db> Type<'db> { } (Type::NewTypeInstance(self_newtype), Type::NewTypeInstance(target_newtype)) => { - self_newtype.has_relation_to_impl(db, target_newtype) + self_newtype.has_relation_to_impl(db, target_newtype, inferable) } ( @@ -2530,8 +2575,8 @@ impl<'db> Type<'db> { // Other than the special cases enumerated above, nominal-instance types, and // newtype-instance types are never subtypes of any other variants - (Type::NominalInstance(_), _) => ConstraintSet::from(false), - (Type::NewTypeInstance(_), _) => ConstraintSet::from(false), + (Type::NominalInstance(_), _) => ConstraintSet::never(inferable), + (Type::NewTypeInstance(_), _) => ConstraintSet::never(inferable), } } @@ -2558,7 +2603,12 @@ impl<'db> Type<'db> { other: Type<'db>, inferable: InferableTypeVars<'db>, ) -> ConstraintSet<'db> { - self.is_equivalent_to_impl(db, other, inferable, &IsEquivalentVisitor::default()) + self.is_equivalent_to_impl( + db, + other, + inferable, + &IsEquivalentVisitor::from_inferable(inferable), + ) } pub(crate) fn is_equivalent_to_impl( @@ -2569,7 +2619,7 @@ impl<'db> Type<'db> { visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { if self == other { - return ConstraintSet::from(true); + return ConstraintSet::always(inferable); } match (self, other) { @@ -2577,17 +2627,17 @@ impl<'db> Type<'db> { // which prevents `Divergent` from being eliminated during union reduction. (Type::Dynamic(_), Type::Dynamic(DynamicType::Divergent(_))) | (Type::Dynamic(DynamicType::Divergent(_)), Type::Dynamic(_)) => { - ConstraintSet::from(false) + ConstraintSet::never(inferable) } - (Type::Dynamic(_), Type::Dynamic(_)) => ConstraintSet::from(true), + (Type::Dynamic(_), Type::Dynamic(_)) => ConstraintSet::always(inferable), (Type::SubclassOf(first), Type::SubclassOf(second)) => { match (first.subclass_of(), second.subclass_of()) { - (first, second) if first == second => ConstraintSet::from(true), + (first, second) if first == second => ConstraintSet::always(inferable), (SubclassOfInner::Dynamic(_), SubclassOfInner::Dynamic(_)) => { - ConstraintSet::from(true) + ConstraintSet::always(inferable) } - _ => ConstraintSet::from(false), + _ => ConstraintSet::never(inferable), } } @@ -2606,7 +2656,10 @@ impl<'db> Type<'db> { } (Type::NewTypeInstance(self_newtype), Type::NewTypeInstance(other_newtype)) => { - ConstraintSet::from(self_newtype.is_equivalent_to_impl(db, other_newtype)) + ConstraintSet::from_bool( + self_newtype.is_equivalent_to_impl(db, other_newtype), + inferable, + ) } (Type::NominalInstance(first), Type::NominalInstance(second)) => { @@ -2639,23 +2692,29 @@ impl<'db> Type<'db> { } (Type::ProtocolInstance(protocol), nominal @ Type::NominalInstance(n)) | (nominal @ Type::NominalInstance(n), Type::ProtocolInstance(protocol)) => { - ConstraintSet::from(n.is_object() && protocol.normalized(db) == nominal) + ConstraintSet::from_bool( + n.is_object() && protocol.normalized(db) == nominal, + inferable, + ) } // An instance of an enum class is equivalent to an enum literal of that class, // if that enum has only has one member. (Type::NominalInstance(instance), Type::EnumLiteral(literal)) | (Type::EnumLiteral(literal), Type::NominalInstance(instance)) => { if literal.enum_class_instance(db) != Type::NominalInstance(instance) { - return ConstraintSet::from(false); + return ConstraintSet::never(inferable); } - ConstraintSet::from(is_single_member_enum(db, instance.class_literal(db))) + ConstraintSet::from_bool( + is_single_member_enum(db, instance.class_literal(db)), + inferable, + ) } (Type::PropertyInstance(left), Type::PropertyInstance(right)) => { left.is_equivalent_to_impl(db, right, inferable, visitor) } - _ => ConstraintSet::from(false), + _ => ConstraintSet::never(inferable), } } @@ -2689,8 +2748,8 @@ impl<'db> Type<'db> { db, other, inferable, - &IsDisjointVisitor::default(), - &HasRelationToVisitor::default(), + &IsDisjointVisitor::from_inferable(inferable), + &HasRelationToVisitor::from_inferable(inferable), ) } @@ -2710,27 +2769,30 @@ impl<'db> Type<'db> { disjointness_visitor: &IsDisjointVisitor<'db>, relation_visitor: &HasRelationToVisitor<'db>, ) -> ConstraintSet<'db> { - protocol.interface(db).members(db).when_any(db, |member| { - other - .member(db, member.name()) - .place - .ignore_possibly_undefined() - .when_none_or(|attribute_type| { - member.has_disjoint_type_from( - db, - attribute_type, - inferable, - disjointness_visitor, - relation_visitor, - ) - }) - }) + protocol + .interface(db) + .members(db) + .when_any(db, inferable, |member| { + other + .member(db, member.name()) + .place + .ignore_possibly_undefined() + .when_none_or(inferable, |attribute_type| { + member.has_disjoint_type_from( + db, + attribute_type, + inferable, + disjointness_visitor, + relation_visitor, + ) + }) + }) } match (self, other) { - (Type::Never, _) | (_, Type::Never) => ConstraintSet::from(true), + (Type::Never, _) | (_, Type::Never) => ConstraintSet::always(inferable), - (Type::Dynamic(_), _) | (_, Type::Dynamic(_)) => ConstraintSet::from(false), + (Type::Dynamic(_), _) | (_, Type::Dynamic(_)) => ConstraintSet::never(inferable), (Type::TypeAlias(alias), _) => { let self_alias_ty = alias.value_type(db); @@ -2760,7 +2822,7 @@ impl<'db> Type<'db> { (Type::TypedDict(_), _) | (_, Type::TypedDict(_)) => { // TODO: Implement disjointness for TypedDict - ConstraintSet::from(false) + ConstraintSet::never(inferable) } // A typevar is never disjoint from itself, since all occurrences of the typevar must @@ -2771,7 +2833,7 @@ impl<'db> Type<'db> { if !self_bound_typevar.is_inferable(db, inferable) && self_bound_typevar.is_same_typevar_as(db, other_bound_typevar) => { - ConstraintSet::from(false) + ConstraintSet::never(inferable) } (tvar @ Type::TypeVar(bound_typevar), Type::Intersection(intersection)) @@ -2779,7 +2841,7 @@ impl<'db> Type<'db> { if !bound_typevar.is_inferable(db, inferable) && intersection.negative(db).contains(&tvar) => { - ConstraintSet::from(true) + ConstraintSet::always(inferable) } // An unbounded typevar is never disjoint from any other type, since it might be @@ -2790,7 +2852,7 @@ impl<'db> Type<'db> { if !bound_typevar.is_inferable(db, inferable) => { match bound_typevar.typevar(db).bound_or_constraints(db) { - None => ConstraintSet::from(false), + None => ConstraintSet::never(inferable), Some(TypeVarBoundOrConstraints::UpperBound(bound)) => bound .is_disjoint_from_impl( db, @@ -2799,8 +2861,10 @@ impl<'db> Type<'db> { disjointness_visitor, relation_visitor, ), - Some(TypeVarBoundOrConstraints::Constraints(constraints)) => { - constraints.elements(db).iter().when_all(db, |constraint| { + Some(TypeVarBoundOrConstraints::Constraints(constraints)) => constraints + .elements(db) + .iter() + .when_all(db, inferable, |constraint| { constraint.is_disjoint_from_impl( db, other, @@ -2808,16 +2872,15 @@ impl<'db> Type<'db> { disjointness_visitor, relation_visitor, ) - }) - } + }), } } // TODO: Infer specializations here - (Type::TypeVar(_), _) | (_, Type::TypeVar(_)) => ConstraintSet::from(false), + (Type::TypeVar(_), _) | (_, Type::TypeVar(_)) => ConstraintSet::never(inferable), (Type::Union(union), other) | (other, Type::Union(union)) => { - union.elements(db).iter().when_all(db, |e| { + union.elements(db).iter().when_all(db, inferable, |e| { e.is_disjoint_from_impl( db, other, @@ -2836,7 +2899,7 @@ impl<'db> Type<'db> { self_intersection .positive(db) .iter() - .when_any(db, |p| { + .when_any(db, inferable, |p| { p.is_disjoint_from_impl( db, other, @@ -2846,15 +2909,18 @@ impl<'db> Type<'db> { ) }) .or(db, || { - other_intersection.positive(db).iter().when_any(db, |p| { - p.is_disjoint_from_impl( - db, - self, - inferable, - disjointness_visitor, - relation_visitor, - ) - }) + other_intersection + .positive(db) + .iter() + .when_any(db, inferable, |p| { + p.is_disjoint_from_impl( + db, + self, + inferable, + disjointness_visitor, + relation_visitor, + ) + }) }) }) } @@ -2865,7 +2931,7 @@ impl<'db> Type<'db> { intersection .positive(db) .iter() - .when_any(db, |p| { + .when_any(db, inferable, |p| { p.is_disjoint_from_impl( db, non_intersection, @@ -2876,16 +2942,19 @@ impl<'db> Type<'db> { }) // A & B & Not[C] is disjoint from C .or(db, || { - intersection.negative(db).iter().when_any(db, |&neg_ty| { - non_intersection.has_relation_to_impl( - db, - neg_ty, - inferable, - TypeRelation::Subtyping, - relation_visitor, - disjointness_visitor, - ) - }) + intersection + .negative(db) + .iter() + .when_any(db, inferable, |&neg_ty| { + non_intersection.has_relation_to_impl( + db, + neg_ty, + inferable, + TypeRelation::Subtyping, + relation_visitor, + disjointness_visitor, + ) + }) }) }) } @@ -2921,7 +2990,7 @@ impl<'db> Type<'db> { | Type::GenericAlias(..) | Type::SpecialForm(..) | Type::KnownInstance(..)), - ) => ConstraintSet::from(left != right), + ) => ConstraintSet::from_bool(left != right, inferable), ( Type::SubclassOf(_), @@ -2950,16 +3019,16 @@ impl<'db> Type<'db> { | Type::WrapperDescriptor(..) | Type::ModuleLiteral(..), Type::SubclassOf(_), - ) => ConstraintSet::from(true), + ) => ConstraintSet::always(inferable), (Type::AlwaysTruthy, ty) | (ty, Type::AlwaysTruthy) => { // `Truthiness::Ambiguous` may include `AlwaysTrue` as a subset, so it's not guaranteed to be disjoint. // Thus, they are only disjoint if `ty.bool() == AlwaysFalse`. - ConstraintSet::from(ty.bool(db).is_always_false()) + ConstraintSet::from_bool(ty.bool(db).is_always_false(), inferable) } (Type::AlwaysFalsy, ty) | (ty, Type::AlwaysFalsy) => { // Similarly, they are only disjoint if `ty.bool() == AlwaysTrue`. - ConstraintSet::from(ty.bool(db).is_always_true()) + ConstraintSet::from_bool(ty.bool(db).is_always_true(), inferable) } (Type::ProtocolInstance(left), Type::ProtocolInstance(right)) => disjointness_visitor @@ -3080,25 +3149,29 @@ impl<'db> Type<'db> { (Type::ProtocolInstance(protocol), other) | (other, Type::ProtocolInstance(protocol)) => { disjointness_visitor.visit((self, other), || { - protocol.interface(db).members(db).when_any(db, |member| { - match other.member(db, member.name()).place { - Place::Defined(attribute_type, _, _) => member.has_disjoint_type_from( - db, - attribute_type, - inferable, - disjointness_visitor, - relation_visitor, - ), - Place::Undefined => ConstraintSet::from(false), - } - }) + protocol + .interface(db) + .members(db) + .when_any(db, inferable, |member| { + match other.member(db, member.name()).place { + Place::Defined(attribute_type, _, _) => member + .has_disjoint_type_from( + db, + attribute_type, + inferable, + disjointness_visitor, + relation_visitor, + ), + Place::Undefined => ConstraintSet::never(inferable), + } + }) }) } (Type::SubclassOf(subclass_of_ty), Type::ClassLiteral(class_b)) | (Type::ClassLiteral(class_b), Type::SubclassOf(subclass_of_ty)) => { match subclass_of_ty.subclass_of() { - SubclassOfInner::Dynamic(_) => ConstraintSet::from(false), + SubclassOfInner::Dynamic(_) => ConstraintSet::never(inferable), SubclassOfInner::Class(class_a) => { class_b.when_subclass_of(db, None, class_a).negate(db) } @@ -3108,7 +3181,7 @@ impl<'db> Type<'db> { (Type::SubclassOf(subclass_of_ty), Type::GenericAlias(alias_b)) | (Type::GenericAlias(alias_b), Type::SubclassOf(subclass_of_ty)) => { match subclass_of_ty.subclass_of() { - SubclassOfInner::Dynamic(_) => ConstraintSet::from(false), + SubclassOfInner::Dynamic(_) => ConstraintSet::never(inferable), SubclassOfInner::Class(class_a) => ClassType::from(alias_b) .when_subclass_of(db, class_a, inferable) .negate(db), @@ -3145,12 +3218,18 @@ impl<'db> Type<'db> { (Type::SpecialForm(special_form), Type::NominalInstance(instance)) | (Type::NominalInstance(instance), Type::SpecialForm(special_form)) => { - ConstraintSet::from(!special_form.is_instance_of(db, instance.class(db))) + ConstraintSet::from_bool( + !special_form.is_instance_of(db, instance.class(db)), + inferable, + ) } (Type::KnownInstance(known_instance), Type::NominalInstance(instance)) | (Type::NominalInstance(instance), Type::KnownInstance(known_instance)) => { - ConstraintSet::from(!known_instance.is_instance_of(db, instance.class(db))) + ConstraintSet::from_bool( + !known_instance.is_instance_of(db, instance.class(db)), + inferable, + ) } (Type::BooleanLiteral(..) | Type::TypeIs(_), Type::NominalInstance(instance)) @@ -3163,7 +3242,7 @@ impl<'db> Type<'db> { } (Type::BooleanLiteral(..) | Type::TypeIs(_), _) - | (_, Type::BooleanLiteral(..) | Type::TypeIs(_)) => ConstraintSet::from(true), + | (_, Type::BooleanLiteral(..) | Type::TypeIs(_)) => ConstraintSet::always(inferable), (Type::IntLiteral(..), Type::NominalInstance(instance)) | (Type::NominalInstance(instance), Type::IntLiteral(..)) => { @@ -3174,10 +3253,12 @@ impl<'db> Type<'db> { .negate(db) } - (Type::IntLiteral(..), _) | (_, Type::IntLiteral(..)) => ConstraintSet::from(true), + (Type::IntLiteral(..), _) | (_, Type::IntLiteral(..)) => { + ConstraintSet::always(inferable) + } (Type::StringLiteral(..), Type::LiteralString) - | (Type::LiteralString, Type::StringLiteral(..)) => ConstraintSet::from(false), + | (Type::LiteralString, Type::StringLiteral(..)) => ConstraintSet::never(inferable), (Type::StringLiteral(..) | Type::LiteralString, Type::NominalInstance(instance)) | (Type::NominalInstance(instance), Type::StringLiteral(..) | Type::LiteralString) => { @@ -3188,8 +3269,8 @@ impl<'db> Type<'db> { .negate(db) } - (Type::LiteralString, Type::LiteralString) => ConstraintSet::from(false), - (Type::LiteralString, _) | (_, Type::LiteralString) => ConstraintSet::from(true), + (Type::LiteralString, Type::LiteralString) => ConstraintSet::never(inferable), + (Type::LiteralString, _) | (_, Type::LiteralString) => ConstraintSet::always(inferable), (Type::BytesLiteral(..), Type::NominalInstance(instance)) | (Type::NominalInstance(instance), Type::BytesLiteral(..)) => { @@ -3214,7 +3295,9 @@ impl<'db> Type<'db> { ) .negate(db) } - (Type::EnumLiteral(..), _) | (_, Type::EnumLiteral(..)) => ConstraintSet::from(true), + (Type::EnumLiteral(..), _) | (_, Type::EnumLiteral(..)) => { + ConstraintSet::always(inferable) + } // A class-literal type `X` is always disjoint from an instance type `Y`, // unless the type expressing "all instances of `Z`" is a subtype of of `Y`, @@ -3285,7 +3368,7 @@ impl<'db> Type<'db> { // No two callable types are ever disjoint because // `(*args: object, **kwargs: object) -> Never` is a subtype of all fully static // callable types. - ConstraintSet::from(false) + ConstraintSet::never(inferable) } (Type::Callable(_), Type::StringLiteral(_) | Type::BytesLiteral(_)) @@ -3293,7 +3376,7 @@ impl<'db> Type<'db> { // A callable type is disjoint from other literal types. For example, // `Type::StringLiteral` must be an instance of exactly `str`, not a subclass // of `str`, and `str` is not callable. The same applies to other literal types. - ConstraintSet::from(true) + ConstraintSet::always(inferable) } (Type::Callable(_), Type::SpecialForm(special_form)) @@ -3302,7 +3385,7 @@ impl<'db> Type<'db> { // that are callable (like TypedDict and collection constructors). // Most special forms are type constructors/annotations (like `typing.Literal`, // `typing.Union`, etc.) that are subscripted, not called. - ConstraintSet::from(!special_form.is_callable()) + ConstraintSet::from_bool(!special_form.is_callable(), inferable) } ( @@ -3320,7 +3403,7 @@ impl<'db> Type<'db> { ) .place .ignore_possibly_undefined() - .when_none_or(|dunder_call| { + .when_none_or(inferable, |dunder_call| { dunder_call .has_relation_to_impl( db, @@ -3342,7 +3425,7 @@ impl<'db> Type<'db> { Type::Callable(_) | Type::DataclassDecorator(_) | Type::DataclassTransformer(_), ) => { // TODO: Implement disjointness for general callable type with other types - ConstraintSet::from(false) + ConstraintSet::never(inferable) } (Type::ModuleLiteral(..), other @ Type::NominalInstance(..)) @@ -3369,7 +3452,7 @@ impl<'db> Type<'db> { }), (Type::NewTypeInstance(left), Type::NewTypeInstance(right)) => { - left.is_disjoint_from_impl(db, right) + left.is_disjoint_from_impl(db, right, inferable) } (Type::NewTypeInstance(newtype), other) | (other, Type::NewTypeInstance(newtype)) => { Type::instance(db, newtype.base_class_type(db)).is_disjoint_from_impl( @@ -4876,7 +4959,7 @@ impl<'db> Type<'db> { Type::KnownInstance(KnownInstanceType::ConstraintSet(tracked_set)) => { let constraints = tracked_set.constraints(db); - Truthiness::from(constraints.is_always_satisfied(db)) + Truthiness::from(constraints.satisfied_by_all_typevars(db)) } Type::FunctionLiteral(_) @@ -10728,7 +10811,7 @@ impl<'db> CallableType<'db> { disjointness_visitor: &IsDisjointVisitor<'db>, ) -> ConstraintSet<'db> { if other.is_function_like(db) && !self.is_function_like(db) { - return ConstraintSet::from(false); + return ConstraintSet::never(inferable); } self.signatures(db).has_relation_to_impl( db, @@ -10751,10 +10834,14 @@ impl<'db> CallableType<'db> { visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { if self == other { - return ConstraintSet::from(true); + return ConstraintSet::always(inferable); } - ConstraintSet::from(self.is_function_like(db) == other.is_function_like(db)).and(db, || { + ConstraintSet::from_bool( + self.is_function_like(db) == other.is_function_like(db), + inferable, + ) + .and(db, || { self.signatures(db) .is_equivalent_to_impl(db, other.signatures(db), inferable, visitor) }) @@ -10873,7 +10960,7 @@ impl<'db> KnownBoundMethodType<'db> { ) => self_property.when_equivalent_to(db, other_property, inferable), (KnownBoundMethodType::StrStartswith(_), KnownBoundMethodType::StrStartswith(_)) => { - ConstraintSet::from(self == other) + ConstraintSet::from_bool(self == other, inferable) } (KnownBoundMethodType::PathOpen, KnownBoundMethodType::PathOpen) @@ -10904,7 +10991,7 @@ impl<'db> KnownBoundMethodType<'db> { | ( KnownBoundMethodType::ConstraintSetSatisfiedByAllTypeVars(_), KnownBoundMethodType::ConstraintSetSatisfiedByAllTypeVars(_), - ) => ConstraintSet::from(true), + ) => ConstraintSet::always(inferable), ( KnownBoundMethodType::FunctionTypeDunderGet(_) @@ -10933,7 +11020,7 @@ impl<'db> KnownBoundMethodType<'db> { | KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_) | KnownBoundMethodType::ConstraintSetSatisfies(_) | KnownBoundMethodType::ConstraintSetSatisfiedByAllTypeVars(_), - ) => ConstraintSet::from(false), + ) => ConstraintSet::never(inferable), } } @@ -10965,7 +11052,7 @@ impl<'db> KnownBoundMethodType<'db> { ) => self_property.is_equivalent_to_impl(db, other_property, inferable, visitor), (KnownBoundMethodType::StrStartswith(_), KnownBoundMethodType::StrStartswith(_)) => { - ConstraintSet::from(self == other) + ConstraintSet::from_bool(self == other, inferable) } (KnownBoundMethodType::PathOpen, KnownBoundMethodType::PathOpen) @@ -10980,7 +11067,7 @@ impl<'db> KnownBoundMethodType<'db> { | ( KnownBoundMethodType::ConstraintSetNever, KnownBoundMethodType::ConstraintSetNever, - ) => ConstraintSet::from(true), + ) => ConstraintSet::always(inferable), ( KnownBoundMethodType::ConstraintSetWithInferable(left_constraints), @@ -11028,7 +11115,7 @@ impl<'db> KnownBoundMethodType<'db> { | KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_) | KnownBoundMethodType::ConstraintSetSatisfies(_) | KnownBoundMethodType::ConstraintSetSatisfiedByAllTypeVars(_), - ) => ConstraintSet::from(false), + ) => ConstraintSet::never(inferable), } } @@ -11997,27 +12084,27 @@ impl<'db> UnionType<'db> { self, db: &'db dyn Db, other: Self, - _inferable: InferableTypeVars<'db>, + inferable: InferableTypeVars<'db>, _visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { if self == other { - return ConstraintSet::from(true); + return ConstraintSet::always(inferable); } let self_elements = self.elements(db); let other_elements = other.elements(db); if self_elements.len() != other_elements.len() { - return ConstraintSet::from(false); + return ConstraintSet::never(inferable); } let sorted_self = self.normalized(db); if sorted_self == Type::Union(other) { - return ConstraintSet::from(true); + return ConstraintSet::always(inferable); } - ConstraintSet::from(sorted_self == other.normalized(db)) + ConstraintSet::from_bool(sorted_self == other.normalized(db), inferable) } } @@ -12099,34 +12186,34 @@ impl<'db> IntersectionType<'db> { self, db: &'db dyn Db, other: Self, - _inferable: InferableTypeVars<'db>, + inferable: InferableTypeVars<'db>, _visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { if self == other { - return ConstraintSet::from(true); + return ConstraintSet::always(inferable); } let self_positive = self.positive(db); let other_positive = other.positive(db); if self_positive.len() != other_positive.len() { - return ConstraintSet::from(false); + return ConstraintSet::never(inferable); } let self_negative = self.negative(db); let other_negative = other.negative(db); if self_negative.len() != other_negative.len() { - return ConstraintSet::from(false); + return ConstraintSet::never(inferable); } let sorted_self = self.normalized(db); if sorted_self == other { - return ConstraintSet::from(true); + return ConstraintSet::always(inferable); } - ConstraintSet::from(sorted_self == other.normalized(db)) + ConstraintSet::from_bool(sorted_self == other.normalized(db), inferable) } /// Returns an iterator over the positive elements of the intersection. If diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index 0e2b6db22a..1f62b0a8f2 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -1164,7 +1164,7 @@ impl<'db> Bindings<'db> { if !overload.parameter_types().is_empty() { return; } - let constraints = ConstraintSet::from(true); + let constraints = ConstraintSet::always(InferableTypeVars::none()); let tracked = TrackedConstraintSet::new(db, constraints); overload.set_return_type(Type::KnownInstance( KnownInstanceType::ConstraintSet(tracked), @@ -1175,7 +1175,7 @@ impl<'db> Bindings<'db> { if !overload.parameter_types().is_empty() { return; } - let constraints = ConstraintSet::from(false); + let constraints = ConstraintSet::never(InferableTypeVars::none()); let tracked = TrackedConstraintSet::new(db, constraints); overload.set_return_type(Type::KnownInstance( KnownInstanceType::ConstraintSet(tracked), diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index 7dace10d6b..eee8788b83 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -531,8 +531,8 @@ impl<'db> ClassType<'db> { other, inferable, TypeRelation::Subtyping, - &HasRelationToVisitor::default(), - &IsDisjointVisitor::default(), + &HasRelationToVisitor::from_inferable(inferable), + &IsDisjointVisitor::from_inferable(inferable), ) } @@ -545,45 +545,48 @@ impl<'db> ClassType<'db> { relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, ) -> ConstraintSet<'db> { - self.iter_mro(db).when_any(db, |base| { + self.iter_mro(db).when_any(db, inferable, |base| { match base { ClassBase::Dynamic(_) => match relation { TypeRelation::Subtyping | TypeRelation::Redundancy | TypeRelation::ConstraintImplication(_) => { - ConstraintSet::from(other.is_object(db)) + ConstraintSet::from_bool(other.is_object(db), inferable) + } + TypeRelation::Assignability => { + ConstraintSet::from_bool(!other.is_final(db), inferable) } - TypeRelation::Assignability => ConstraintSet::from(!other.is_final(db)), }, // Protocol and Generic are not represented by a ClassType. - ClassBase::Protocol | ClassBase::Generic => ConstraintSet::from(false), + ClassBase::Protocol | ClassBase::Generic => ConstraintSet::never(inferable), ClassBase::Class(base) => match (base, other) { (ClassType::NonGeneric(base), ClassType::NonGeneric(other)) => { - ConstraintSet::from(base == other) + ConstraintSet::from_bool(base == other, inferable) } (ClassType::Generic(base), ClassType::Generic(other)) => { - ConstraintSet::from(base.origin(db) == other.origin(db)).and(db, || { - base.specialization(db).has_relation_to_impl( - db, - other.specialization(db), - inferable, - relation, - relation_visitor, - disjointness_visitor, - ) - }) + ConstraintSet::from_bool(base.origin(db) == other.origin(db), inferable) + .and(db, || { + base.specialization(db).has_relation_to_impl( + db, + other.specialization(db), + inferable, + relation, + relation_visitor, + disjointness_visitor, + ) + }) } (ClassType::Generic(_), ClassType::NonGeneric(_)) | (ClassType::NonGeneric(_), ClassType::Generic(_)) => { - ConstraintSet::from(false) + ConstraintSet::never(inferable) } }, ClassBase::TypedDict => { // TODO: Implement subclassing and assignability for TypedDicts. - ConstraintSet::from(true) + ConstraintSet::always(inferable) } } }) @@ -597,26 +600,28 @@ impl<'db> ClassType<'db> { visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { if self == other { - return ConstraintSet::from(true); + return ConstraintSet::always(inferable); } match (self, other) { // A non-generic class is never equivalent to a generic class. // Two non-generic classes are only equivalent if they are equal (handled above). (ClassType::NonGeneric(_), _) | (_, ClassType::NonGeneric(_)) => { - ConstraintSet::from(false) + ConstraintSet::never(inferable) } - (ClassType::Generic(this), ClassType::Generic(other)) => { - ConstraintSet::from(this.origin(db) == other.origin(db)).and(db, || { - this.specialization(db).is_equivalent_to_impl( - db, - other.specialization(db), - inferable, - visitor, - ) - }) - } + (ClassType::Generic(this), ClassType::Generic(other)) => ConstraintSet::from_bool( + this.origin(db) == other.origin(db), + inferable, + ) + .and(db, || { + this.specialization(db).is_equivalent_to_impl( + db, + other.specialization(db), + inferable, + visitor, + ) + }), } } @@ -1775,7 +1780,10 @@ impl<'db> ClassLiteral<'db> { specialization: Option>, other: ClassType<'db>, ) -> ConstraintSet<'db> { - ConstraintSet::from(self.is_subclass_of(db, specialization, other)) + ConstraintSet::from_bool( + self.is_subclass_of(db, specialization, other), + InferableTypeVars::none(), + ) } /// Return `true` if this class constitutes a typed dict specification (inherits from @@ -4693,7 +4701,7 @@ impl KnownClass { db: &'db dyn Db, other: ClassType<'db>, ) -> ConstraintSet<'db> { - ConstraintSet::from(self.is_subclass_of(db, other)) + ConstraintSet::from_bool(self.is_subclass_of(db, other), InferableTypeVars::none()) } /// Return the module in which we should look up the definition for this class diff --git a/crates/ty_python_semantic/src/types/constraints.rs b/crates/ty_python_semantic/src/types/constraints.rs index a163edb9dc..1b8a29fc2f 100644 --- a/crates/ty_python_semantic/src/types/constraints.rs +++ b/crates/ty_python_semantic/src/types/constraints.rs @@ -74,25 +74,41 @@ use crate::types::{ pub(crate) trait OptionConstraintsExtension { /// Returns a constraint set that is always satisfiable if the option is `None`; otherwise /// applies a function to determine under what constraints the value inside of it holds. - fn when_none_or<'db>(self, f: impl FnOnce(T) -> ConstraintSet<'db>) -> ConstraintSet<'db>; + fn when_none_or<'db>( + self, + inferable: InferableTypeVars<'db>, + f: impl FnOnce(T) -> ConstraintSet<'db>, + ) -> ConstraintSet<'db>; /// Returns a constraint set that is never satisfiable if the option is `None`; otherwise /// applies a function to determine under what constraints the value inside of it holds. - fn when_some_and<'db>(self, f: impl FnOnce(T) -> ConstraintSet<'db>) -> ConstraintSet<'db>; + fn when_some_and<'db>( + self, + inferable: InferableTypeVars<'db>, + f: impl FnOnce(T) -> ConstraintSet<'db>, + ) -> ConstraintSet<'db>; } impl OptionConstraintsExtension for Option { - fn when_none_or<'db>(self, f: impl FnOnce(T) -> ConstraintSet<'db>) -> ConstraintSet<'db> { + fn when_none_or<'db>( + self, + inferable: InferableTypeVars<'db>, + f: impl FnOnce(T) -> ConstraintSet<'db>, + ) -> ConstraintSet<'db> { match self { Some(value) => f(value), - None => ConstraintSet::always(), + None => ConstraintSet::always(inferable), } } - fn when_some_and<'db>(self, f: impl FnOnce(T) -> ConstraintSet<'db>) -> ConstraintSet<'db> { + fn when_some_and<'db>( + self, + inferable: InferableTypeVars<'db>, + f: impl FnOnce(T) -> ConstraintSet<'db>, + ) -> ConstraintSet<'db> { match self { Some(value) => f(value), - None => ConstraintSet::never(), + None => ConstraintSet::never(inferable), } } } @@ -107,6 +123,7 @@ pub(crate) trait IteratorConstraintsExtension { fn when_any<'db>( self, db: &'db dyn Db, + inferable: InferableTypeVars<'db>, f: impl FnMut(T) -> ConstraintSet<'db>, ) -> ConstraintSet<'db>; @@ -118,6 +135,7 @@ pub(crate) trait IteratorConstraintsExtension { fn when_all<'db>( self, db: &'db dyn Db, + inferable: InferableTypeVars<'db>, f: impl FnMut(T) -> ConstraintSet<'db>, ) -> ConstraintSet<'db>; } @@ -129,9 +147,10 @@ where fn when_any<'db>( self, db: &'db dyn Db, + inferable: InferableTypeVars<'db>, mut f: impl FnMut(T) -> ConstraintSet<'db>, ) -> ConstraintSet<'db> { - let mut result = ConstraintSet::never(); + let mut result = ConstraintSet::never(inferable); for child in self { if result.union(db, f(child)).is_always_satisfied(db) { return result; @@ -143,9 +162,10 @@ where fn when_all<'db>( self, db: &'db dyn Db, + inferable: InferableTypeVars<'db>, mut f: impl FnMut(T) -> ConstraintSet<'db>, ) -> ConstraintSet<'db> { - let mut result = ConstraintSet::always(); + let mut result = ConstraintSet::always(inferable); for child in self { if result.intersect(db, f(child)).is_never_satisfied(db) { return result; @@ -170,17 +190,28 @@ pub struct ConstraintSet<'db> { } impl<'db> ConstraintSet<'db> { - fn never() -> Self { + pub(crate) fn never(inferable: InferableTypeVars<'db>) -> Self { Self { node: Node::AlwaysFalse, - inferable: InferableTypeVars::none(), + inferable, } } - fn always() -> Self { + pub(crate) fn always(inferable: InferableTypeVars<'db>) -> Self { Self { node: Node::AlwaysTrue, - inferable: InferableTypeVars::none(), + inferable, + } + } + + pub(crate) fn from_bool(b: bool, inferable: InferableTypeVars<'db>) -> Self { + Self { + node: if b { + Node::AlwaysTrue + } else { + Node::AlwaysFalse + }, + inferable, } } @@ -264,15 +295,15 @@ impl<'db> ConstraintSet<'db> { /// Updates this constraint set to hold the union of itself and another constraint set. pub(crate) fn union(&mut self, db: &'db dyn Db, other: Self) -> Self { - debug_assert!(self.inferable == other.inferable); self.node = self.node.or(db, other.node); + self.inferable = self.inferable.merge(db, other.inferable); *self } /// Updates this constraint set to hold the intersection of itself and another constraint set. pub(crate) fn intersect(&mut self, db: &'db dyn Db, other: Self) -> Self { - debug_assert!(self.inferable == other.inferable); self.node = self.node.and(db, other.node); + self.inferable = self.inferable.merge(db, other.inferable); *self } @@ -310,10 +341,9 @@ impl<'db> ConstraintSet<'db> { } pub(crate) fn iff(self, db: &'db dyn Db, other: Self) -> Self { - debug_assert!(self.inferable == other.inferable); ConstraintSet { node: self.node.iff(db, other.node), - inferable: self.inferable, + inferable: self.inferable.merge(db, other.inferable), } } @@ -339,12 +369,6 @@ impl<'db> ConstraintSet<'db> { } } -impl From for ConstraintSet<'_> { - fn from(b: bool) -> Self { - if b { Self::always() } else { Self::never() } - } -} - impl<'db> BoundTypeVarInstance<'db> { /// Returns whether this typevar can be the lower or upper bound of another typevar in a /// constraint set. diff --git a/crates/ty_python_semantic/src/types/function.rs b/crates/ty_python_semantic/src/types/function.rs index 8903e24698..b6b76fbd9a 100644 --- a/crates/ty_python_semantic/src/types/function.rs +++ b/crates/ty_python_semantic/src/types/function.rs @@ -986,11 +986,11 @@ impl<'db> FunctionType<'db> { | TypeRelation::ConstraintImplication(_) ) && self.normalized(db) == other.normalized(db) { - return ConstraintSet::from(true); + return ConstraintSet::always(inferable); } if self.literal(db) != other.literal(db) { - return ConstraintSet::from(false); + return ConstraintSet::never(inferable); } let self_signature = self.signature(db); @@ -1013,10 +1013,10 @@ impl<'db> FunctionType<'db> { visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { if self.normalized(db) == other.normalized(db) { - return ConstraintSet::from(true); + return ConstraintSet::always(inferable); } if self.literal(db) != other.literal(db) { - return ConstraintSet::from(false); + return ConstraintSet::never(inferable); } let self_signature = self.signature(db); let other_signature = other.signature(db); diff --git a/crates/ty_python_semantic/src/types/generics.rs b/crates/ty_python_semantic/src/types/generics.rs index 7d56c1ac3f..0fd738a9c4 100644 --- a/crates/ty_python_semantic/src/types/generics.rs +++ b/crates/ty_python_semantic/src/types/generics.rs @@ -744,12 +744,12 @@ fn is_subtype_in_invariant_position<'db>( // This should be removed and properly handled in the respective // `(Type::TypeVar(_), _) | (_, Type::TypeVar(_))` branch of // `Type::has_relation_to_impl`. Right now, we cannot generally - // return `ConstraintSet::from(true)` from that branch, as that + // return `ConstraintSet::always(inferable)` from that branch, as that // leads to union simplification, which means that we lose track // of type variables without recording the constraints under which // the relation holds. if matches!(base, Type::TypeVar(_)) || matches!(derived, Type::TypeVar(_)) { - return ConstraintSet::from(true); + return ConstraintSet::always(inferable); } derived.has_relation_to_impl( @@ -1163,7 +1163,7 @@ impl<'db> Specialization<'db> { ) -> ConstraintSet<'db> { let generic_context = self.generic_context(db); if generic_context != other.generic_context(db) { - return ConstraintSet::from(false); + return ConstraintSet::never(inferable); } if let (Some(self_tuple), Some(other_tuple)) = (self.tuple_inner(db), other.tuple_inner(db)) @@ -1181,7 +1181,7 @@ impl<'db> Specialization<'db> { let self_materialization_kind = self.materialization_kind(db); let other_materialization_kind = other.materialization_kind(db); - let mut result = ConstraintSet::from(true); + let mut result = ConstraintSet::always(inferable); for ((bound_typevar, self_type), other_type) in (generic_context.variables(db)) .zip(self.types(db)) .zip(other.types(db)) @@ -1220,7 +1220,7 @@ impl<'db> Specialization<'db> { relation_visitor, disjointness_visitor, ), - TypeVarVariance::Bivariant => ConstraintSet::from(true), + TypeVarVariance::Bivariant => ConstraintSet::always(inferable), }; if result.intersect(db, compatible).is_never_satisfied(db) { return result; @@ -1238,14 +1238,14 @@ impl<'db> Specialization<'db> { visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { if self.materialization_kind(db) != other.materialization_kind(db) { - return ConstraintSet::from(false); + return ConstraintSet::never(inferable); } let generic_context = self.generic_context(db); if generic_context != other.generic_context(db) { - return ConstraintSet::from(false); + return ConstraintSet::never(inferable); } - let mut result = ConstraintSet::from(true); + let mut result = ConstraintSet::always(inferable); for ((bound_typevar, self_type), other_type) in (generic_context.variables(db)) .zip(self.types(db)) .zip(other.types(db)) @@ -1262,7 +1262,7 @@ impl<'db> Specialization<'db> { | TypeVarVariance::Contravariant => { self_type.is_equivalent_to_impl(db, *other_type, inferable, visitor) } - TypeVarVariance::Bivariant => ConstraintSet::from(true), + TypeVarVariance::Bivariant => ConstraintSet::always(inferable), }; if result.intersect(db, compatible).is_never_satisfied(db) { return result; @@ -1270,7 +1270,7 @@ impl<'db> Specialization<'db> { } match (self.tuple_inner(db), other.tuple_inner(db)) { - (Some(_), None) | (None, Some(_)) => return ConstraintSet::from(false), + (Some(_), None) | (None, Some(_)) => return ConstraintSet::never(inferable), (None, None) => {} (Some(self_tuple), Some(other_tuple)) => { let compatible = diff --git a/crates/ty_python_semantic/src/types/instance.rs b/crates/ty_python_semantic/src/types/instance.rs index e1410ca070..54acaac9ae 100644 --- a/crates/ty_python_semantic/src/types/instance.rs +++ b/crates/ty_python_semantic/src/types/instance.rs @@ -141,7 +141,7 @@ impl<'db> Type<'db> { .inner .interface(db) .members(db) - .when_all(db, |member| { + .when_all(db, inferable, |member| { member.is_satisfied_by( db, self, @@ -161,7 +161,7 @@ impl<'db> Type<'db> { // recognise `str` as a subtype of `Container[str]`. structurally_satisfied.or(db, || { let Some(nominal_instance) = protocol.as_nominal_type() else { - return ConstraintSet::from(false); + return ConstraintSet::never(inferable); }; // if `self` and `other` are *both* protocols, we also need to treat `self` as if it @@ -371,7 +371,7 @@ impl<'db> NominalInstanceType<'db> { disjointness_visitor: &IsDisjointVisitor<'db>, ) -> ConstraintSet<'db> { match (self.0, other.0) { - (_, NominalInstanceInner::Object) => ConstraintSet::from(true), + (_, NominalInstanceInner::Object) => ConstraintSet::always(inferable), ( NominalInstanceInner::ExactTuple(tuple1), NominalInstanceInner::ExactTuple(tuple2), @@ -407,12 +407,12 @@ impl<'db> NominalInstanceType<'db> { NominalInstanceInner::ExactTuple(tuple2), ) => tuple1.is_equivalent_to_impl(db, tuple2, inferable, visitor), (NominalInstanceInner::Object, NominalInstanceInner::Object) => { - ConstraintSet::from(true) + ConstraintSet::always(inferable) } (NominalInstanceInner::NonTuple(class1), NominalInstanceInner::NonTuple(class2)) => { class1.is_equivalent_to_impl(db, class2, inferable, visitor) } - _ => ConstraintSet::from(false), + _ => ConstraintSet::never(inferable), } } @@ -425,9 +425,9 @@ impl<'db> NominalInstanceType<'db> { relation_visitor: &HasRelationToVisitor<'db>, ) -> ConstraintSet<'db> { if self.is_object() || other.is_object() { - return ConstraintSet::from(false); + return ConstraintSet::never(inferable); } - let mut result = ConstraintSet::from(false); + let mut result = ConstraintSet::never(inferable); if let Some(self_spec) = self.tuple_spec(db) { if let Some(other_spec) = other.tuple_spec(db) { let compatible = self_spec.is_disjoint_from_impl( @@ -443,7 +443,10 @@ impl<'db> NominalInstanceType<'db> { } } result.or(db, || { - ConstraintSet::from(!(self.class(db)).could_coexist_in_mro_with(db, other.class(db))) + ConstraintSet::from_bool( + !(self.class(db)).could_coexist_in_mro_with(db, other.class(db)), + inferable, + ) }) } @@ -668,8 +671,8 @@ impl<'db> ProtocolInstanceType<'db> { protocol, InferableTypeVars::none(), TypeRelation::Subtyping, - &HasRelationToVisitor::default(), - &IsDisjointVisitor::default(), + &HasRelationToVisitor::from_inferable(InferableTypeVars::none()), + &IsDisjointVisitor::from_inferable(InferableTypeVars::none()), ) .is_always_satisfied(db) } @@ -719,17 +722,17 @@ impl<'db> ProtocolInstanceType<'db> { self, db: &'db dyn Db, other: Self, - _inferable: InferableTypeVars<'db>, + inferable: InferableTypeVars<'db>, _visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { if self == other { - return ConstraintSet::from(true); + return ConstraintSet::always(inferable); } let self_normalized = self.normalized(db); if self_normalized == Type::ProtocolInstance(other) { - return ConstraintSet::from(true); + return ConstraintSet::always(inferable); } - ConstraintSet::from(self_normalized == other.normalized(db)) + ConstraintSet::from_bool(self_normalized == other.normalized(db), inferable) } /// Return `true` if this protocol type is disjoint from the protocol `other`. @@ -741,10 +744,10 @@ impl<'db> ProtocolInstanceType<'db> { self, _db: &'db dyn Db, _other: Self, - _inferable: InferableTypeVars<'db>, + inferable: InferableTypeVars<'db>, _visitor: &IsDisjointVisitor<'db>, ) -> ConstraintSet<'db> { - ConstraintSet::from(false) + ConstraintSet::never(inferable) } pub(crate) fn instance_member(self, db: &'db dyn Db, name: &str) -> PlaceAndQualifiers<'db> { diff --git a/crates/ty_python_semantic/src/types/newtype.rs b/crates/ty_python_semantic/src/types/newtype.rs index fe08fa7bee..8e51c50a53 100644 --- a/crates/ty_python_semantic/src/types/newtype.rs +++ b/crates/ty_python_semantic/src/types/newtype.rs @@ -3,6 +3,7 @@ use std::collections::BTreeSet; use crate::Db; use crate::semantic_index::definition::{Definition, DefinitionKind}; use crate::types::constraints::ConstraintSet; +use crate::types::generics::InferableTypeVars; use crate::types::{ClassType, Type, definition_expression_type, visitor}; use ruff_db::parsed::parsed_module; use ruff_python_ast as ast; @@ -115,26 +116,37 @@ impl<'db> NewType<'db> { // Since a regular class can't inherit from a newtype, the only way for one newtype to be a // subtype of another is to have the other in its chain of newtype bases. Once we reach the // base class, we don't have to keep looking. - pub(crate) fn has_relation_to_impl(self, db: &'db dyn Db, other: Self) -> ConstraintSet<'db> { + pub(crate) fn has_relation_to_impl( + self, + db: &'db dyn Db, + other: Self, + inferable: InferableTypeVars<'db>, + ) -> ConstraintSet<'db> { if self.is_equivalent_to_impl(db, other) { - return ConstraintSet::from(true); + return ConstraintSet::always(inferable); } for base in self.iter_bases(db) { if let NewTypeBase::NewType(base_newtype) = base { if base_newtype.is_equivalent_to_impl(db, other) { - return ConstraintSet::from(true); + return ConstraintSet::always(inferable); } } } - ConstraintSet::from(false) + ConstraintSet::never(inferable) } - pub(crate) fn is_disjoint_from_impl(self, db: &'db dyn Db, other: Self) -> ConstraintSet<'db> { + pub(crate) fn is_disjoint_from_impl( + self, + db: &'db dyn Db, + other: Self, + inferable: InferableTypeVars<'db>, + ) -> ConstraintSet<'db> { // Two NewTypes are disjoint if they're not equal and neither inherits from the other. // NewTypes have single inheritance, and a regular class can't inherit from a NewType, so // it's not possible for some third type to multiply-inherit from both. - let mut self_not_subtype_of_other = self.has_relation_to_impl(db, other).negate(db); - let other_not_subtype_of_self = other.has_relation_to_impl(db, self).negate(db); + let mut self_not_subtype_of_other = + self.has_relation_to_impl(db, other, inferable).negate(db); + let other_not_subtype_of_self = other.has_relation_to_impl(db, self, inferable).negate(db); self_not_subtype_of_other.intersect(db, other_not_subtype_of_self) } diff --git a/crates/ty_python_semantic/src/types/protocol_class.rs b/crates/ty_python_semantic/src/types/protocol_class.rs index 31acf47416..36af560b18 100644 --- a/crates/ty_python_semantic/src/types/protocol_class.rs +++ b/crates/ty_python_semantic/src/types/protocol_class.rs @@ -238,13 +238,14 @@ impl<'db> ProtocolInterface<'db> { relation_visitor: &HasRelationToVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>, ) -> ConstraintSet<'db> { - other.members(db).when_all(db, |other_member| { - self.member_by_name(db, other_member.name) - .when_some_and(|our_member| match (our_member.kind, other_member.kind) { + other.members(db).when_all(db, inferable, |other_member| { + self.member_by_name(db, other_member.name).when_some_and( + inferable, + |our_member| match (our_member.kind, other_member.kind) { // Method members are always immutable; // they can never be subtypes of/assignable to mutable attribute members. (ProtocolMemberKind::Method(_), ProtocolMemberKind::Other(_)) => { - ConstraintSet::from(false) + ConstraintSet::never(inferable) } // A property member can only be a subtype of an attribute member @@ -252,15 +253,16 @@ impl<'db> ProtocolInterface<'db> { // // TODO: this should also consider the types of the members on both sides. (ProtocolMemberKind::Property(property), ProtocolMemberKind::Other(_)) => { - ConstraintSet::from( + ConstraintSet::from_bool( property.getter(db).is_some() && property.setter(db).is_some(), + inferable, ) } // A `@property` member can never be a subtype of a method member, as it is not necessarily // accessible on the meta-type, whereas a method member must be. (ProtocolMemberKind::Property(_), ProtocolMemberKind::Method(_)) => { - ConstraintSet::from(false) + ConstraintSet::never(inferable) } // But an attribute member *can* be a subtype of a method member, @@ -268,8 +270,9 @@ impl<'db> ProtocolInterface<'db> { ( ProtocolMemberKind::Other(our_type), ProtocolMemberKind::Method(other_type), - ) => ConstraintSet::from( + ) => ConstraintSet::from_bool( our_member.qualifiers.contains(TypeQualifiers::CLASS_VAR), + inferable, ) .and(db, || { our_type.has_relation_to_impl( @@ -324,8 +327,9 @@ impl<'db> ProtocolInterface<'db> { | ProtocolMemberKind::Method(_) | ProtocolMemberKind::Other(_), ProtocolMemberKind::Property(_), - ) => ConstraintSet::from(true), - }) + ) => ConstraintSet::always(inferable), + }, + ) }) } @@ -615,7 +619,7 @@ impl<'a, 'db> ProtocolMember<'a, 'db> { match &self.kind { // TODO: implement disjointness for property/method members as well as attribute members ProtocolMemberKind::Property(_) | ProtocolMemberKind::Method(_) => { - ConstraintSet::from(false) + ConstraintSet::never(inferable) } ProtocolMemberKind::Other(ty) => ty.is_disjoint_from_impl( db, @@ -651,7 +655,7 @@ impl<'a, 'db> ProtocolMember<'a, 'db> { // complex interaction between `__new__`, `__init__` and metaclass `__call__`. let attribute_type = if self.name == "__call__" { let Some(attribute_type) = other.try_upcast_to_callable(db) else { - return ConstraintSet::from(false); + return ConstraintSet::never(inferable); }; attribute_type } else { @@ -665,7 +669,7 @@ impl<'a, 'db> ProtocolMember<'a, 'db> { ) .place else { - return ConstraintSet::from(false); + return ConstraintSet::never(inferable); }; attribute_type }; @@ -680,15 +684,18 @@ impl<'a, 'db> ProtocolMember<'a, 'db> { ) } // TODO: consider the types of the attribute on `other` for property members - ProtocolMemberKind::Property(_) => ConstraintSet::from(matches!( - other.member(db, self.name).place, - Place::Defined(_, _, Definedness::AlwaysDefined) - )), + ProtocolMemberKind::Property(_) => ConstraintSet::from_bool( + matches!( + other.member(db, self.name).place, + Place::Defined(_, _, Definedness::AlwaysDefined) + ), + inferable, + ), ProtocolMemberKind::Other(member_type) => { let Place::Defined(attribute_type, _, Definedness::AlwaysDefined) = other.member(db, self.name).place else { - return ConstraintSet::from(false); + return ConstraintSet::never(inferable); }; member_type .has_relation_to_impl( diff --git a/crates/ty_python_semantic/src/types/signatures.rs b/crates/ty_python_semantic/src/types/signatures.rs index 6f70d0c7d9..6fbb7055ce 100644 --- a/crates/ty_python_semantic/src/types/signatures.rs +++ b/crates/ty_python_semantic/src/types/signatures.rs @@ -224,8 +224,8 @@ impl<'db> CallableSignature<'db> { other, inferable, TypeRelation::Subtyping, - &HasRelationToVisitor::default(), - &IsDisjointVisitor::default(), + &HasRelationToVisitor::from_inferable(inferable), + &IsDisjointVisitor::from_inferable(inferable), ) } @@ -274,43 +274,49 @@ impl<'db> CallableSignature<'db> { } // `self` is possibly overloaded while `other` is definitely not overloaded. - (_, [_]) => self_signatures.iter().when_any(db, |self_signature| { - Self::has_relation_to_inner( - db, - std::slice::from_ref(self_signature), - other_signatures, - inferable, - relation, - relation_visitor, - disjointness_visitor, - ) - }), + (_, [_]) => self_signatures + .iter() + .when_any(db, inferable, |self_signature| { + Self::has_relation_to_inner( + db, + std::slice::from_ref(self_signature), + other_signatures, + inferable, + relation, + relation_visitor, + disjointness_visitor, + ) + }), // `self` is definitely not overloaded while `other` is possibly overloaded. - ([_], _) => other_signatures.iter().when_all(db, |other_signature| { - Self::has_relation_to_inner( - db, - self_signatures, - std::slice::from_ref(other_signature), - inferable, - relation, - relation_visitor, - disjointness_visitor, - ) - }), + ([_], _) => other_signatures + .iter() + .when_all(db, inferable, |other_signature| { + Self::has_relation_to_inner( + db, + self_signatures, + std::slice::from_ref(other_signature), + inferable, + relation, + relation_visitor, + disjointness_visitor, + ) + }), // `self` is definitely overloaded while `other` is possibly overloaded. - (_, _) => other_signatures.iter().when_all(db, |other_signature| { - Self::has_relation_to_inner( - db, - self_signatures, - std::slice::from_ref(other_signature), - inferable, - relation, - relation_visitor, - disjointness_visitor, - ) - }), + (_, _) => other_signatures + .iter() + .when_all(db, inferable, |other_signature| { + Self::has_relation_to_inner( + db, + self_signatures, + std::slice::from_ref(other_signature), + inferable, + relation, + relation_visitor, + disjointness_visitor, + ) + }), } } @@ -332,7 +338,7 @@ impl<'db> CallableSignature<'db> { } (_, _) => { if self == other { - return ConstraintSet::from(true); + return ConstraintSet::always(inferable); } self.is_subtype_of_impl(db, other, inferable) .and(db, || other.is_subtype_of_impl(db, self, inferable)) @@ -650,7 +656,7 @@ impl<'db> Signature<'db> { let inferable = inferable.merge(db, self.inferable_typevars(db)); let inferable = inferable.merge(db, other.inferable_typevars(db)); - let mut result = ConstraintSet::from(true); + let mut result = ConstraintSet::always(inferable); 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()); @@ -663,11 +669,11 @@ impl<'db> Signature<'db> { }; if self.parameters.is_gradual() != other.parameters.is_gradual() { - return ConstraintSet::from(false); + return ConstraintSet::never(inferable); } if self.parameters.len() != other.parameters.len() { - return ConstraintSet::from(false); + return ConstraintSet::never(inferable); } if !check_types(self.return_ty, other.return_ty) { @@ -715,7 +721,7 @@ impl<'db> Signature<'db> { (ParameterKind::KeywordVariadic { .. }, ParameterKind::KeywordVariadic { .. }) => {} - _ => return ConstraintSet::from(false), + _ => return ConstraintSet::never(inferable), } if !check_types( @@ -805,7 +811,7 @@ impl<'db> Signature<'db> { let inferable = inferable.merge(db, self.inferable_typevars(db)); let inferable = inferable.merge(db, other.inferable_typevars(db)); - let mut result = ConstraintSet::from(true); + let mut result = ConstraintSet::always(inferable); let mut check_types = |type1: Option>, type2: Option>| { let type1 = type1.unwrap_or(Type::unknown()); let type2 = type2.unwrap_or(Type::unknown()); @@ -841,13 +847,13 @@ impl<'db> Signature<'db> { .keyword_variadic() .is_some_and(|(_, param)| param.annotated_type().is_some_and(|ty| ty.is_object())) { - return ConstraintSet::from(true); + return ConstraintSet::always(inferable); } // If either of the parameter lists is gradual (`...`), then it is assignable to and from // any other parameter list, but not a subtype or supertype of any other parameter list. if self.parameters.is_gradual() || other.parameters.is_gradual() { - return ConstraintSet::from(relation.is_assignability()); + return ConstraintSet::from_bool(relation.is_assignability(), inferable); } let mut parameters = ParametersZip { @@ -885,7 +891,7 @@ impl<'db> Signature<'db> { // `other`, then the non-variadic parameters in `self` must have a default // value. if default_type.is_none() { - return ConstraintSet::from(false); + return ConstraintSet::never(inferable); } } ParameterKind::Variadic { .. } | ParameterKind::KeywordVariadic { .. } => { @@ -897,7 +903,7 @@ impl<'db> Signature<'db> { EitherOrBoth::Right(_) => { // If there are more parameters in `other` than in `self`, then `self` is not a // subtype of `other`. - return ConstraintSet::from(false); + return ConstraintSet::never(inferable); } EitherOrBoth::Both(self_parameter, other_parameter) => { @@ -917,7 +923,7 @@ impl<'db> Signature<'db> { }, ) => { if self_default.is_none() && other_default.is_some() { - return ConstraintSet::from(false); + return ConstraintSet::never(inferable); } if !check_types( other_parameter.annotated_type(), @@ -938,11 +944,11 @@ impl<'db> Signature<'db> { }, ) => { if self_name != other_name { - return ConstraintSet::from(false); + return ConstraintSet::never(inferable); } // The following checks are the same as positional-only parameters. if self_default.is_none() && other_default.is_some() { - return ConstraintSet::from(false); + return ConstraintSet::never(inferable); } if !check_types( other_parameter.annotated_type(), @@ -1027,7 +1033,7 @@ impl<'db> Signature<'db> { break; } - _ => return ConstraintSet::from(false), + _ => return ConstraintSet::never(inferable), } } } @@ -1061,7 +1067,7 @@ impl<'db> Signature<'db> { // previous loop. They cannot be matched against any parameter in `other` which // only contains keyword-only and keyword-variadic parameters so the subtype // relation is invalid. - return ConstraintSet::from(false); + return ConstraintSet::never(inferable); } ParameterKind::Variadic { .. } => {} } @@ -1088,7 +1094,7 @@ impl<'db> Signature<'db> { .. } => { if self_default.is_none() && other_default.is_some() { - return ConstraintSet::from(false); + return ConstraintSet::never(inferable); } if !check_types( other_parameter.annotated_type(), @@ -1109,14 +1115,14 @@ impl<'db> Signature<'db> { return result; } } else { - return ConstraintSet::from(false); + return ConstraintSet::never(inferable); } } ParameterKind::KeywordVariadic { .. } => { let Some(self_keyword_variadic_type) = self_keyword_variadic else { // For a `self <: other` relationship, if `other` has a keyword variadic // parameter, `self` must also have a keyword variadic parameter. - return ConstraintSet::from(false); + return ConstraintSet::never(inferable); }; if !check_types(other_parameter.annotated_type(), self_keyword_variadic_type) { return result; @@ -1124,7 +1130,7 @@ impl<'db> Signature<'db> { } _ => { // This can only occur in case of a syntax error. - return ConstraintSet::from(false); + return ConstraintSet::never(inferable); } } } @@ -1133,7 +1139,7 @@ impl<'db> Signature<'db> { // optional otherwise the subtype relation is invalid. for (_, self_parameter) in self_keywords { if self_parameter.default_type().is_none() { - return ConstraintSet::from(false); + return ConstraintSet::never(inferable); } } diff --git a/crates/ty_python_semantic/src/types/subclass_of.rs b/crates/ty_python_semantic/src/types/subclass_of.rs index 3ff8cadf5c..ce3977bf39 100644 --- a/crates/ty_python_semantic/src/types/subclass_of.rs +++ b/crates/ty_python_semantic/src/types/subclass_of.rs @@ -143,13 +143,16 @@ impl<'db> SubclassOfType<'db> { ) -> ConstraintSet<'db> { match (self.subclass_of, other.subclass_of) { (SubclassOfInner::Dynamic(_), SubclassOfInner::Dynamic(_)) => { - ConstraintSet::from(!relation.is_subtyping()) + ConstraintSet::from_bool(!relation.is_subtyping(), inferable) } (SubclassOfInner::Dynamic(_), SubclassOfInner::Class(other_class)) => { - ConstraintSet::from(other_class.is_object(db) || relation.is_assignability()) + ConstraintSet::from_bool( + other_class.is_object(db) || relation.is_assignability(), + inferable, + ) } (SubclassOfInner::Class(_), SubclassOfInner::Dynamic(_)) => { - ConstraintSet::from(relation.is_assignability()) + ConstraintSet::from_bool(relation.is_assignability(), inferable) } // For example, `type[bool]` describes all possible runtime subclasses of the class `bool`, @@ -174,15 +177,18 @@ impl<'db> SubclassOfType<'db> { self, db: &'db dyn Db, other: Self, - _inferable: InferableTypeVars<'db>, + inferable: InferableTypeVars<'db>, _visitor: &IsDisjointVisitor<'db>, ) -> ConstraintSet<'db> { match (self.subclass_of, other.subclass_of) { (SubclassOfInner::Dynamic(_), _) | (_, SubclassOfInner::Dynamic(_)) => { - ConstraintSet::from(false) + ConstraintSet::never(inferable) } (SubclassOfInner::Class(self_class), SubclassOfInner::Class(other_class)) => { - ConstraintSet::from(!self_class.could_coexist_in_mro_with(db, other_class)) + ConstraintSet::from_bool( + !self_class.could_coexist_in_mro_with(db, other_class), + inferable, + ) } } } diff --git a/crates/ty_python_semantic/src/types/tuple.rs b/crates/ty_python_semantic/src/types/tuple.rs index 43d0873546..6a439dfb80 100644 --- a/crates/ty_python_semantic/src/types/tuple.rs +++ b/crates/ty_python_semantic/src/types/tuple.rs @@ -448,8 +448,8 @@ impl<'db> FixedLengthTuple> { ) -> ConstraintSet<'db> { match other { Tuple::Fixed(other) => { - ConstraintSet::from(self.0.len() == other.0.len()).and(db, || { - (self.0.iter().zip(&other.0)).when_all(db, |(self_ty, other_ty)| { + ConstraintSet::from_bool(self.0.len() == other.0.len(), inferable).and(db, || { + (self.0.iter().zip(&other.0)).when_all(db, inferable, |(self_ty, other_ty)| { self_ty.has_relation_to_impl( db, *other_ty, @@ -465,11 +465,11 @@ impl<'db> FixedLengthTuple> { Tuple::Variable(other) => { // This tuple must have enough elements to match up with the other tuple's prefix // and suffix, and each of those elements must pairwise satisfy the relation. - let mut result = ConstraintSet::from(true); + let mut result = ConstraintSet::always(inferable); let mut self_iter = self.0.iter(); for other_ty in &other.prefix { let Some(self_ty) = self_iter.next() else { - return ConstraintSet::from(false); + return ConstraintSet::never(inferable); }; let element_constraints = self_ty.has_relation_to_impl( db, @@ -488,7 +488,7 @@ impl<'db> FixedLengthTuple> { } for other_ty in other.suffix.iter().rev() { let Some(self_ty) = self_iter.next_back() else { - return ConstraintSet::from(false); + return ConstraintSet::never(inferable); }; let element_constraints = self_ty.has_relation_to_impl( db, @@ -509,7 +509,7 @@ impl<'db> FixedLengthTuple> { // In addition, any remaining elements in this tuple must satisfy the // variable-length portion of the other tuple. result.and(db, || { - self_iter.when_all(db, |self_ty| { + self_iter.when_all(db, inferable, |self_ty| { self_ty.has_relation_to_impl( db, other.variable, @@ -531,10 +531,10 @@ impl<'db> FixedLengthTuple> { inferable: InferableTypeVars<'db>, visitor: &IsEquivalentVisitor<'db>, ) -> ConstraintSet<'db> { - ConstraintSet::from(self.0.len() == other.0.len()).and(db, || { + ConstraintSet::from_bool(self.0.len() == other.0.len(), inferable).and(db, || { (self.0.iter()) .zip(&other.0) - .when_all(db, |(self_ty, other_ty)| { + .when_all(db, inferable, |(self_ty, other_ty)| { self_ty.is_equivalent_to_impl(db, *other_ty, inferable, visitor) }) }) @@ -816,17 +816,17 @@ impl<'db> VariableLengthTuple> { // possible lengths. This means that `tuple[Any, ...]` can match any tuple of any // length. if !relation.is_assignability() || !self.variable.is_dynamic() { - return ConstraintSet::from(false); + return ConstraintSet::never(inferable); } // In addition, the other tuple must have enough elements to match up with this // tuple's prefix and suffix, and each of those elements must pairwise satisfy the // relation. - let mut result = ConstraintSet::from(true); + let mut result = ConstraintSet::always(inferable); let mut other_iter = other.elements().copied(); for self_ty in self.prenormalized_prefix_elements(db, None) { let Some(other_ty) = other_iter.next() else { - return ConstraintSet::from(false); + return ConstraintSet::never(inferable); }; let element_constraints = self_ty.has_relation_to_impl( db, @@ -846,7 +846,7 @@ impl<'db> VariableLengthTuple> { let suffix: Vec<_> = self.prenormalized_suffix_elements(db, None).collect(); for self_ty in suffix.iter().rev() { let Some(other_ty) = other_iter.next_back() else { - return ConstraintSet::from(false); + return ConstraintSet::never(inferable); }; let element_constraints = self_ty.has_relation_to_impl( db, @@ -882,7 +882,7 @@ impl<'db> VariableLengthTuple> { // The overlapping parts of the prefixes and suffixes must satisfy the relation. // Any remaining parts must satisfy the relation with the other tuple's // variable-length part. - let mut result = ConstraintSet::from(true); + let mut result = ConstraintSet::always(inferable); let pairwise = (self.prenormalized_prefix_elements(db, self_prenormalize_variable)) .zip_longest( other.prenormalized_prefix_elements(db, other_prenormalize_variable), @@ -908,7 +908,7 @@ impl<'db> VariableLengthTuple> { EitherOrBoth::Right(_) => { // The rhs has a required element that the lhs is not guaranteed to // provide. - return ConstraintSet::from(false); + return ConstraintSet::never(inferable); } }; if result @@ -947,7 +947,7 @@ impl<'db> VariableLengthTuple> { EitherOrBoth::Right(_) => { // The rhs has a required element that the lhs is not guaranteed to // provide. - return ConstraintSet::from(false); + return ConstraintSet::never(inferable); } }; if result @@ -985,24 +985,24 @@ impl<'db> VariableLengthTuple> { .and(db, || { (self.prenormalized_prefix_elements(db, None)) .zip_longest(other.prenormalized_prefix_elements(db, None)) - .when_all(db, |pair| match pair { + .when_all(db, inferable, |pair| match pair { EitherOrBoth::Both(self_ty, other_ty) => { self_ty.is_equivalent_to_impl(db, other_ty, inferable, visitor) } EitherOrBoth::Left(_) | EitherOrBoth::Right(_) => { - ConstraintSet::from(false) + ConstraintSet::never(inferable) } }) }) .and(db, || { (self.prenormalized_suffix_elements(db, None)) .zip_longest(other.prenormalized_suffix_elements(db, None)) - .when_all(db, |pair| match pair { + .when_all(db, inferable, |pair| match pair { EitherOrBoth::Both(self_ty, other_ty) => { self_ty.is_equivalent_to_impl(db, other_ty, inferable, visitor) } EitherOrBoth::Left(_) | EitherOrBoth::Right(_) => { - ConstraintSet::from(false) + ConstraintSet::never(inferable) } }) }) @@ -1230,7 +1230,7 @@ impl<'db> Tuple> { self_tuple.is_equivalent_to_impl(db, other_tuple, inferable, visitor) } (Tuple::Fixed(_), Tuple::Variable(_)) | (Tuple::Variable(_), Tuple::Fixed(_)) => { - ConstraintSet::from(false) + ConstraintSet::never(inferable) } } } @@ -1247,10 +1247,10 @@ impl<'db> Tuple> { let (self_min, self_max) = self.len().size_hint(); let (other_min, other_max) = other.len().size_hint(); if self_max.is_some_and(|max| max < other_min) { - return ConstraintSet::from(true); + return ConstraintSet::always(inferable); } if other_max.is_some_and(|max| max < self_min) { - return ConstraintSet::from(true); + return ConstraintSet::always(inferable); } // If any of the required elements are pairwise disjoint, the tuples are disjoint as well. @@ -1266,7 +1266,7 @@ impl<'db> Tuple> { where 'db: 's, { - (a.into_iter().zip(b)).when_any(db, |(self_element, other_element)| { + (a.into_iter().zip(b)).when_any(db, inferable, |(self_element, other_element)| { self_element.is_disjoint_from_impl( db, *other_element,