This commit is contained in:
Douglas Creager 2025-11-10 18:20:35 -05:00
parent e6ec4062d5
commit 24e60539f9
13 changed files with 629 additions and 486 deletions

View File

@ -357,19 +357,11 @@ def identity[T](t: T) -> T:
constraints = ConstraintSet.always() constraints = ConstraintSet.always()
# TODO: no error
# error: [static-assert-error]
static_assert(constraints.implies_subtype_of(TypeOf[identity], Callable[[int], int])) 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(constraints.implies_subtype_of(TypeOf[identity], Callable[[str], str]))
static_assert(not constraints.implies_subtype_of(TypeOf[identity], Callable[[str], int])) 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])) 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(constraints.implies_subtype_of(CallableTypeOf[identity], Callable[[str], str]))
static_assert(not constraints.implies_subtype_of(CallableTypeOf[identity], Callable[[str], int])) 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! # This constraint set refers to the same typevar as the generic function types below!
constraints = ConstraintSet.range(bool, T, int) 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[[int], int]))
static_assert(constraints.implies_subtype_of(TypeOf[identity2], Callable[[str], str]))
# TODO: no error # TODO: no error
# error: [static-assert-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(TypeOf[identity2], Callable[[str], int]))
static_assert(not constraints.implies_subtype_of(Callable[[int], int], TypeOf[identity2])) static_assert(not constraints.implies_subtype_of(Callable[[int], int], TypeOf[identity2]))

File diff suppressed because it is too large Load Diff

View File

@ -1164,7 +1164,7 @@ impl<'db> Bindings<'db> {
if !overload.parameter_types().is_empty() { if !overload.parameter_types().is_empty() {
return; return;
} }
let constraints = ConstraintSet::from(true); let constraints = ConstraintSet::always(InferableTypeVars::none());
let tracked = TrackedConstraintSet::new(db, constraints); let tracked = TrackedConstraintSet::new(db, constraints);
overload.set_return_type(Type::KnownInstance( overload.set_return_type(Type::KnownInstance(
KnownInstanceType::ConstraintSet(tracked), KnownInstanceType::ConstraintSet(tracked),
@ -1175,7 +1175,7 @@ impl<'db> Bindings<'db> {
if !overload.parameter_types().is_empty() { if !overload.parameter_types().is_empty() {
return; return;
} }
let constraints = ConstraintSet::from(false); let constraints = ConstraintSet::never(InferableTypeVars::none());
let tracked = TrackedConstraintSet::new(db, constraints); let tracked = TrackedConstraintSet::new(db, constraints);
overload.set_return_type(Type::KnownInstance( overload.set_return_type(Type::KnownInstance(
KnownInstanceType::ConstraintSet(tracked), KnownInstanceType::ConstraintSet(tracked),

View File

@ -531,8 +531,8 @@ impl<'db> ClassType<'db> {
other, other,
inferable, inferable,
TypeRelation::Subtyping, TypeRelation::Subtyping,
&HasRelationToVisitor::default(), &HasRelationToVisitor::from_inferable(inferable),
&IsDisjointVisitor::default(), &IsDisjointVisitor::from_inferable(inferable),
) )
} }
@ -545,45 +545,48 @@ impl<'db> ClassType<'db> {
relation_visitor: &HasRelationToVisitor<'db>, relation_visitor: &HasRelationToVisitor<'db>,
disjointness_visitor: &IsDisjointVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>,
) -> ConstraintSet<'db> { ) -> ConstraintSet<'db> {
self.iter_mro(db).when_any(db, |base| { self.iter_mro(db).when_any(db, inferable, |base| {
match base { match base {
ClassBase::Dynamic(_) => match relation { ClassBase::Dynamic(_) => match relation {
TypeRelation::Subtyping TypeRelation::Subtyping
| TypeRelation::Redundancy | TypeRelation::Redundancy
| TypeRelation::ConstraintImplication(_) => { | 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. // 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) { ClassBase::Class(base) => match (base, other) {
(ClassType::NonGeneric(base), ClassType::NonGeneric(other)) => { (ClassType::NonGeneric(base), ClassType::NonGeneric(other)) => {
ConstraintSet::from(base == other) ConstraintSet::from_bool(base == other, inferable)
} }
(ClassType::Generic(base), ClassType::Generic(other)) => { (ClassType::Generic(base), ClassType::Generic(other)) => {
ConstraintSet::from(base.origin(db) == other.origin(db)).and(db, || { ConstraintSet::from_bool(base.origin(db) == other.origin(db), inferable)
base.specialization(db).has_relation_to_impl( .and(db, || {
db, base.specialization(db).has_relation_to_impl(
other.specialization(db), db,
inferable, other.specialization(db),
relation, inferable,
relation_visitor, relation,
disjointness_visitor, relation_visitor,
) disjointness_visitor,
}) )
})
} }
(ClassType::Generic(_), ClassType::NonGeneric(_)) (ClassType::Generic(_), ClassType::NonGeneric(_))
| (ClassType::NonGeneric(_), ClassType::Generic(_)) => { | (ClassType::NonGeneric(_), ClassType::Generic(_)) => {
ConstraintSet::from(false) ConstraintSet::never(inferable)
} }
}, },
ClassBase::TypedDict => { ClassBase::TypedDict => {
// TODO: Implement subclassing and assignability for TypedDicts. // TODO: Implement subclassing and assignability for TypedDicts.
ConstraintSet::from(true) ConstraintSet::always(inferable)
} }
} }
}) })
@ -597,26 +600,28 @@ impl<'db> ClassType<'db> {
visitor: &IsEquivalentVisitor<'db>, visitor: &IsEquivalentVisitor<'db>,
) -> ConstraintSet<'db> { ) -> ConstraintSet<'db> {
if self == other { if self == other {
return ConstraintSet::from(true); return ConstraintSet::always(inferable);
} }
match (self, other) { match (self, other) {
// A non-generic class is never equivalent to a generic class. // A non-generic class is never equivalent to a generic class.
// Two non-generic classes are only equivalent if they are equal (handled above). // Two non-generic classes are only equivalent if they are equal (handled above).
(ClassType::NonGeneric(_), _) | (_, ClassType::NonGeneric(_)) => { (ClassType::NonGeneric(_), _) | (_, ClassType::NonGeneric(_)) => {
ConstraintSet::from(false) ConstraintSet::never(inferable)
} }
(ClassType::Generic(this), ClassType::Generic(other)) => { (ClassType::Generic(this), ClassType::Generic(other)) => ConstraintSet::from_bool(
ConstraintSet::from(this.origin(db) == other.origin(db)).and(db, || { this.origin(db) == other.origin(db),
this.specialization(db).is_equivalent_to_impl( inferable,
db, )
other.specialization(db), .and(db, || {
inferable, this.specialization(db).is_equivalent_to_impl(
visitor, db,
) other.specialization(db),
}) inferable,
} visitor,
)
}),
} }
} }
@ -1775,7 +1780,10 @@ impl<'db> ClassLiteral<'db> {
specialization: Option<Specialization<'db>>, specialization: Option<Specialization<'db>>,
other: ClassType<'db>, other: ClassType<'db>,
) -> ConstraintSet<'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 /// Return `true` if this class constitutes a typed dict specification (inherits from
@ -4693,7 +4701,7 @@ impl KnownClass {
db: &'db dyn Db, db: &'db dyn Db,
other: ClassType<'db>, other: ClassType<'db>,
) -> ConstraintSet<'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 /// Return the module in which we should look up the definition for this class

View File

@ -74,25 +74,41 @@ use crate::types::{
pub(crate) trait OptionConstraintsExtension<T> { pub(crate) trait OptionConstraintsExtension<T> {
/// Returns a constraint set that is always satisfiable if the option is `None`; otherwise /// 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. /// 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 /// 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. /// 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<T> OptionConstraintsExtension<T> for Option<T> { impl<T> OptionConstraintsExtension<T> for Option<T> {
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 { match self {
Some(value) => f(value), 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 { match self {
Some(value) => f(value), Some(value) => f(value),
None => ConstraintSet::never(), None => ConstraintSet::never(inferable),
} }
} }
} }
@ -107,6 +123,7 @@ pub(crate) trait IteratorConstraintsExtension<T> {
fn when_any<'db>( fn when_any<'db>(
self, self,
db: &'db dyn Db, db: &'db dyn Db,
inferable: InferableTypeVars<'db>,
f: impl FnMut(T) -> ConstraintSet<'db>, f: impl FnMut(T) -> ConstraintSet<'db>,
) -> ConstraintSet<'db>; ) -> ConstraintSet<'db>;
@ -118,6 +135,7 @@ pub(crate) trait IteratorConstraintsExtension<T> {
fn when_all<'db>( fn when_all<'db>(
self, self,
db: &'db dyn Db, db: &'db dyn Db,
inferable: InferableTypeVars<'db>,
f: impl FnMut(T) -> ConstraintSet<'db>, f: impl FnMut(T) -> ConstraintSet<'db>,
) -> ConstraintSet<'db>; ) -> ConstraintSet<'db>;
} }
@ -129,9 +147,10 @@ where
fn when_any<'db>( fn when_any<'db>(
self, self,
db: &'db dyn Db, db: &'db dyn Db,
inferable: InferableTypeVars<'db>,
mut f: impl FnMut(T) -> ConstraintSet<'db>, mut f: impl FnMut(T) -> ConstraintSet<'db>,
) -> ConstraintSet<'db> { ) -> ConstraintSet<'db> {
let mut result = ConstraintSet::never(); let mut result = ConstraintSet::never(inferable);
for child in self { for child in self {
if result.union(db, f(child)).is_always_satisfied(db) { if result.union(db, f(child)).is_always_satisfied(db) {
return result; return result;
@ -143,9 +162,10 @@ where
fn when_all<'db>( fn when_all<'db>(
self, self,
db: &'db dyn Db, db: &'db dyn Db,
inferable: InferableTypeVars<'db>,
mut f: impl FnMut(T) -> ConstraintSet<'db>, mut f: impl FnMut(T) -> ConstraintSet<'db>,
) -> ConstraintSet<'db> { ) -> ConstraintSet<'db> {
let mut result = ConstraintSet::always(); let mut result = ConstraintSet::always(inferable);
for child in self { for child in self {
if result.intersect(db, f(child)).is_never_satisfied(db) { if result.intersect(db, f(child)).is_never_satisfied(db) {
return result; return result;
@ -170,17 +190,28 @@ pub struct ConstraintSet<'db> {
} }
impl<'db> ConstraintSet<'db> { impl<'db> ConstraintSet<'db> {
fn never() -> Self { pub(crate) fn never(inferable: InferableTypeVars<'db>) -> Self {
Self { Self {
node: Node::AlwaysFalse, node: Node::AlwaysFalse,
inferable: InferableTypeVars::none(), inferable,
} }
} }
fn always() -> Self { pub(crate) fn always(inferable: InferableTypeVars<'db>) -> Self {
Self { Self {
node: Node::AlwaysTrue, 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. /// 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 { 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.node = self.node.or(db, other.node);
self.inferable = self.inferable.merge(db, other.inferable);
*self *self
} }
/// Updates this constraint set to hold the intersection of itself and another constraint set. /// 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 { 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.node = self.node.and(db, other.node);
self.inferable = self.inferable.merge(db, other.inferable);
*self *self
} }
@ -310,10 +341,9 @@ impl<'db> ConstraintSet<'db> {
} }
pub(crate) fn iff(self, db: &'db dyn Db, other: Self) -> Self { pub(crate) fn iff(self, db: &'db dyn Db, other: Self) -> Self {
debug_assert!(self.inferable == other.inferable);
ConstraintSet { ConstraintSet {
node: self.node.iff(db, other.node), 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<bool> for ConstraintSet<'_> {
fn from(b: bool) -> Self {
if b { Self::always() } else { Self::never() }
}
}
impl<'db> BoundTypeVarInstance<'db> { impl<'db> BoundTypeVarInstance<'db> {
/// Returns whether this typevar can be the lower or upper bound of another typevar in a /// Returns whether this typevar can be the lower or upper bound of another typevar in a
/// constraint set. /// constraint set.

View File

@ -986,11 +986,11 @@ impl<'db> FunctionType<'db> {
| TypeRelation::ConstraintImplication(_) | TypeRelation::ConstraintImplication(_)
) && self.normalized(db) == other.normalized(db) ) && self.normalized(db) == other.normalized(db)
{ {
return ConstraintSet::from(true); return ConstraintSet::always(inferable);
} }
if self.literal(db) != other.literal(db) { if self.literal(db) != other.literal(db) {
return ConstraintSet::from(false); return ConstraintSet::never(inferable);
} }
let self_signature = self.signature(db); let self_signature = self.signature(db);
@ -1013,10 +1013,10 @@ impl<'db> FunctionType<'db> {
visitor: &IsEquivalentVisitor<'db>, visitor: &IsEquivalentVisitor<'db>,
) -> ConstraintSet<'db> { ) -> ConstraintSet<'db> {
if self.normalized(db) == other.normalized(db) { if self.normalized(db) == other.normalized(db) {
return ConstraintSet::from(true); return ConstraintSet::always(inferable);
} }
if self.literal(db) != other.literal(db) { if self.literal(db) != other.literal(db) {
return ConstraintSet::from(false); return ConstraintSet::never(inferable);
} }
let self_signature = self.signature(db); let self_signature = self.signature(db);
let other_signature = other.signature(db); let other_signature = other.signature(db);

View File

@ -744,12 +744,12 @@ fn is_subtype_in_invariant_position<'db>(
// This should be removed and properly handled in the respective // This should be removed and properly handled in the respective
// `(Type::TypeVar(_), _) | (_, Type::TypeVar(_))` branch of // `(Type::TypeVar(_), _) | (_, Type::TypeVar(_))` branch of
// `Type::has_relation_to_impl`. Right now, we cannot generally // `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 // leads to union simplification, which means that we lose track
// of type variables without recording the constraints under which // of type variables without recording the constraints under which
// the relation holds. // the relation holds.
if matches!(base, Type::TypeVar(_)) || matches!(derived, Type::TypeVar(_)) { if matches!(base, Type::TypeVar(_)) || matches!(derived, Type::TypeVar(_)) {
return ConstraintSet::from(true); return ConstraintSet::always(inferable);
} }
derived.has_relation_to_impl( derived.has_relation_to_impl(
@ -1163,7 +1163,7 @@ impl<'db> Specialization<'db> {
) -> ConstraintSet<'db> { ) -> ConstraintSet<'db> {
let generic_context = self.generic_context(db); let generic_context = self.generic_context(db);
if generic_context != other.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)) 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 self_materialization_kind = self.materialization_kind(db);
let other_materialization_kind = other.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)) for ((bound_typevar, self_type), other_type) in (generic_context.variables(db))
.zip(self.types(db)) .zip(self.types(db))
.zip(other.types(db)) .zip(other.types(db))
@ -1220,7 +1220,7 @@ impl<'db> Specialization<'db> {
relation_visitor, relation_visitor,
disjointness_visitor, disjointness_visitor,
), ),
TypeVarVariance::Bivariant => ConstraintSet::from(true), TypeVarVariance::Bivariant => ConstraintSet::always(inferable),
}; };
if result.intersect(db, compatible).is_never_satisfied(db) { if result.intersect(db, compatible).is_never_satisfied(db) {
return result; return result;
@ -1238,14 +1238,14 @@ impl<'db> Specialization<'db> {
visitor: &IsEquivalentVisitor<'db>, visitor: &IsEquivalentVisitor<'db>,
) -> ConstraintSet<'db> { ) -> ConstraintSet<'db> {
if self.materialization_kind(db) != other.materialization_kind(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); let generic_context = self.generic_context(db);
if generic_context != other.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)) for ((bound_typevar, self_type), other_type) in (generic_context.variables(db))
.zip(self.types(db)) .zip(self.types(db))
.zip(other.types(db)) .zip(other.types(db))
@ -1262,7 +1262,7 @@ impl<'db> Specialization<'db> {
| TypeVarVariance::Contravariant => { | TypeVarVariance::Contravariant => {
self_type.is_equivalent_to_impl(db, *other_type, inferable, visitor) 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) { if result.intersect(db, compatible).is_never_satisfied(db) {
return result; return result;
@ -1270,7 +1270,7 @@ impl<'db> Specialization<'db> {
} }
match (self.tuple_inner(db), other.tuple_inner(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) => {} (None, None) => {}
(Some(self_tuple), Some(other_tuple)) => { (Some(self_tuple), Some(other_tuple)) => {
let compatible = let compatible =

View File

@ -141,7 +141,7 @@ impl<'db> Type<'db> {
.inner .inner
.interface(db) .interface(db)
.members(db) .members(db)
.when_all(db, |member| { .when_all(db, inferable, |member| {
member.is_satisfied_by( member.is_satisfied_by(
db, db,
self, self,
@ -161,7 +161,7 @@ impl<'db> Type<'db> {
// recognise `str` as a subtype of `Container[str]`. // recognise `str` as a subtype of `Container[str]`.
structurally_satisfied.or(db, || { structurally_satisfied.or(db, || {
let Some(nominal_instance) = protocol.as_nominal_type() else { 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 // 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>, disjointness_visitor: &IsDisjointVisitor<'db>,
) -> ConstraintSet<'db> { ) -> ConstraintSet<'db> {
match (self.0, other.0) { match (self.0, other.0) {
(_, NominalInstanceInner::Object) => ConstraintSet::from(true), (_, NominalInstanceInner::Object) => ConstraintSet::always(inferable),
( (
NominalInstanceInner::ExactTuple(tuple1), NominalInstanceInner::ExactTuple(tuple1),
NominalInstanceInner::ExactTuple(tuple2), NominalInstanceInner::ExactTuple(tuple2),
@ -407,12 +407,12 @@ impl<'db> NominalInstanceType<'db> {
NominalInstanceInner::ExactTuple(tuple2), NominalInstanceInner::ExactTuple(tuple2),
) => tuple1.is_equivalent_to_impl(db, tuple2, inferable, visitor), ) => tuple1.is_equivalent_to_impl(db, tuple2, inferable, visitor),
(NominalInstanceInner::Object, NominalInstanceInner::Object) => { (NominalInstanceInner::Object, NominalInstanceInner::Object) => {
ConstraintSet::from(true) ConstraintSet::always(inferable)
} }
(NominalInstanceInner::NonTuple(class1), NominalInstanceInner::NonTuple(class2)) => { (NominalInstanceInner::NonTuple(class1), NominalInstanceInner::NonTuple(class2)) => {
class1.is_equivalent_to_impl(db, class2, inferable, visitor) 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>, relation_visitor: &HasRelationToVisitor<'db>,
) -> ConstraintSet<'db> { ) -> ConstraintSet<'db> {
if self.is_object() || other.is_object() { 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(self_spec) = self.tuple_spec(db) {
if let Some(other_spec) = other.tuple_spec(db) { if let Some(other_spec) = other.tuple_spec(db) {
let compatible = self_spec.is_disjoint_from_impl( let compatible = self_spec.is_disjoint_from_impl(
@ -443,7 +443,10 @@ impl<'db> NominalInstanceType<'db> {
} }
} }
result.or(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, protocol,
InferableTypeVars::none(), InferableTypeVars::none(),
TypeRelation::Subtyping, TypeRelation::Subtyping,
&HasRelationToVisitor::default(), &HasRelationToVisitor::from_inferable(InferableTypeVars::none()),
&IsDisjointVisitor::default(), &IsDisjointVisitor::from_inferable(InferableTypeVars::none()),
) )
.is_always_satisfied(db) .is_always_satisfied(db)
} }
@ -719,17 +722,17 @@ impl<'db> ProtocolInstanceType<'db> {
self, self,
db: &'db dyn Db, db: &'db dyn Db,
other: Self, other: Self,
_inferable: InferableTypeVars<'db>, inferable: InferableTypeVars<'db>,
_visitor: &IsEquivalentVisitor<'db>, _visitor: &IsEquivalentVisitor<'db>,
) -> ConstraintSet<'db> { ) -> ConstraintSet<'db> {
if self == other { if self == other {
return ConstraintSet::from(true); return ConstraintSet::always(inferable);
} }
let self_normalized = self.normalized(db); let self_normalized = self.normalized(db);
if self_normalized == Type::ProtocolInstance(other) { 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`. /// Return `true` if this protocol type is disjoint from the protocol `other`.
@ -741,10 +744,10 @@ impl<'db> ProtocolInstanceType<'db> {
self, self,
_db: &'db dyn Db, _db: &'db dyn Db,
_other: Self, _other: Self,
_inferable: InferableTypeVars<'db>, inferable: InferableTypeVars<'db>,
_visitor: &IsDisjointVisitor<'db>, _visitor: &IsDisjointVisitor<'db>,
) -> ConstraintSet<'db> { ) -> ConstraintSet<'db> {
ConstraintSet::from(false) ConstraintSet::never(inferable)
} }
pub(crate) fn instance_member(self, db: &'db dyn Db, name: &str) -> PlaceAndQualifiers<'db> { pub(crate) fn instance_member(self, db: &'db dyn Db, name: &str) -> PlaceAndQualifiers<'db> {

View File

@ -3,6 +3,7 @@ use std::collections::BTreeSet;
use crate::Db; use crate::Db;
use crate::semantic_index::definition::{Definition, DefinitionKind}; use crate::semantic_index::definition::{Definition, DefinitionKind};
use crate::types::constraints::ConstraintSet; use crate::types::constraints::ConstraintSet;
use crate::types::generics::InferableTypeVars;
use crate::types::{ClassType, Type, definition_expression_type, visitor}; use crate::types::{ClassType, Type, definition_expression_type, visitor};
use ruff_db::parsed::parsed_module; use ruff_db::parsed::parsed_module;
use ruff_python_ast as ast; 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 // 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 // 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. // 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) { if self.is_equivalent_to_impl(db, other) {
return ConstraintSet::from(true); return ConstraintSet::always(inferable);
} }
for base in self.iter_bases(db) { for base in self.iter_bases(db) {
if let NewTypeBase::NewType(base_newtype) = base { if let NewTypeBase::NewType(base_newtype) = base {
if base_newtype.is_equivalent_to_impl(db, other) { 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. // 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 // 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. // 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 mut self_not_subtype_of_other =
let other_not_subtype_of_self = other.has_relation_to_impl(db, self).negate(db); 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) self_not_subtype_of_other.intersect(db, other_not_subtype_of_self)
} }

View File

@ -238,13 +238,14 @@ impl<'db> ProtocolInterface<'db> {
relation_visitor: &HasRelationToVisitor<'db>, relation_visitor: &HasRelationToVisitor<'db>,
disjointness_visitor: &IsDisjointVisitor<'db>, disjointness_visitor: &IsDisjointVisitor<'db>,
) -> ConstraintSet<'db> { ) -> ConstraintSet<'db> {
other.members(db).when_all(db, |other_member| { other.members(db).when_all(db, inferable, |other_member| {
self.member_by_name(db, other_member.name) self.member_by_name(db, other_member.name).when_some_and(
.when_some_and(|our_member| match (our_member.kind, other_member.kind) { inferable,
|our_member| match (our_member.kind, other_member.kind) {
// Method members are always immutable; // Method members are always immutable;
// they can never be subtypes of/assignable to mutable attribute members. // they can never be subtypes of/assignable to mutable attribute members.
(ProtocolMemberKind::Method(_), ProtocolMemberKind::Other(_)) => { (ProtocolMemberKind::Method(_), ProtocolMemberKind::Other(_)) => {
ConstraintSet::from(false) ConstraintSet::never(inferable)
} }
// A property member can only be a subtype of an attribute member // 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. // TODO: this should also consider the types of the members on both sides.
(ProtocolMemberKind::Property(property), ProtocolMemberKind::Other(_)) => { (ProtocolMemberKind::Property(property), ProtocolMemberKind::Other(_)) => {
ConstraintSet::from( ConstraintSet::from_bool(
property.getter(db).is_some() && property.setter(db).is_some(), 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 // 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. // accessible on the meta-type, whereas a method member must be.
(ProtocolMemberKind::Property(_), ProtocolMemberKind::Method(_)) => { (ProtocolMemberKind::Property(_), ProtocolMemberKind::Method(_)) => {
ConstraintSet::from(false) ConstraintSet::never(inferable)
} }
// But an attribute member *can* be a subtype of a method member, // 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::Other(our_type),
ProtocolMemberKind::Method(other_type), ProtocolMemberKind::Method(other_type),
) => ConstraintSet::from( ) => ConstraintSet::from_bool(
our_member.qualifiers.contains(TypeQualifiers::CLASS_VAR), our_member.qualifiers.contains(TypeQualifiers::CLASS_VAR),
inferable,
) )
.and(db, || { .and(db, || {
our_type.has_relation_to_impl( our_type.has_relation_to_impl(
@ -324,8 +327,9 @@ impl<'db> ProtocolInterface<'db> {
| ProtocolMemberKind::Method(_) | ProtocolMemberKind::Method(_)
| ProtocolMemberKind::Other(_), | ProtocolMemberKind::Other(_),
ProtocolMemberKind::Property(_), ProtocolMemberKind::Property(_),
) => ConstraintSet::from(true), ) => ConstraintSet::always(inferable),
}) },
)
}) })
} }
@ -615,7 +619,7 @@ impl<'a, 'db> ProtocolMember<'a, 'db> {
match &self.kind { match &self.kind {
// TODO: implement disjointness for property/method members as well as attribute members // TODO: implement disjointness for property/method members as well as attribute members
ProtocolMemberKind::Property(_) | ProtocolMemberKind::Method(_) => { ProtocolMemberKind::Property(_) | ProtocolMemberKind::Method(_) => {
ConstraintSet::from(false) ConstraintSet::never(inferable)
} }
ProtocolMemberKind::Other(ty) => ty.is_disjoint_from_impl( ProtocolMemberKind::Other(ty) => ty.is_disjoint_from_impl(
db, db,
@ -651,7 +655,7 @@ impl<'a, 'db> ProtocolMember<'a, 'db> {
// complex interaction between `__new__`, `__init__` and metaclass `__call__`. // complex interaction between `__new__`, `__init__` and metaclass `__call__`.
let attribute_type = if self.name == "__call__" { let attribute_type = if self.name == "__call__" {
let Some(attribute_type) = other.try_upcast_to_callable(db) else { let Some(attribute_type) = other.try_upcast_to_callable(db) else {
return ConstraintSet::from(false); return ConstraintSet::never(inferable);
}; };
attribute_type attribute_type
} else { } else {
@ -665,7 +669,7 @@ impl<'a, 'db> ProtocolMember<'a, 'db> {
) )
.place .place
else { else {
return ConstraintSet::from(false); return ConstraintSet::never(inferable);
}; };
attribute_type attribute_type
}; };
@ -680,15 +684,18 @@ impl<'a, 'db> ProtocolMember<'a, 'db> {
) )
} }
// TODO: consider the types of the attribute on `other` for property members // TODO: consider the types of the attribute on `other` for property members
ProtocolMemberKind::Property(_) => ConstraintSet::from(matches!( ProtocolMemberKind::Property(_) => ConstraintSet::from_bool(
other.member(db, self.name).place, matches!(
Place::Defined(_, _, Definedness::AlwaysDefined) other.member(db, self.name).place,
)), Place::Defined(_, _, Definedness::AlwaysDefined)
),
inferable,
),
ProtocolMemberKind::Other(member_type) => { ProtocolMemberKind::Other(member_type) => {
let Place::Defined(attribute_type, _, Definedness::AlwaysDefined) = let Place::Defined(attribute_type, _, Definedness::AlwaysDefined) =
other.member(db, self.name).place other.member(db, self.name).place
else { else {
return ConstraintSet::from(false); return ConstraintSet::never(inferable);
}; };
member_type member_type
.has_relation_to_impl( .has_relation_to_impl(

View File

@ -224,8 +224,8 @@ impl<'db> CallableSignature<'db> {
other, other,
inferable, inferable,
TypeRelation::Subtyping, TypeRelation::Subtyping,
&HasRelationToVisitor::default(), &HasRelationToVisitor::from_inferable(inferable),
&IsDisjointVisitor::default(), &IsDisjointVisitor::from_inferable(inferable),
) )
} }
@ -274,43 +274,49 @@ impl<'db> CallableSignature<'db> {
} }
// `self` is possibly overloaded while `other` is definitely not overloaded. // `self` is possibly overloaded while `other` is definitely not overloaded.
(_, [_]) => self_signatures.iter().when_any(db, |self_signature| { (_, [_]) => self_signatures
Self::has_relation_to_inner( .iter()
db, .when_any(db, inferable, |self_signature| {
std::slice::from_ref(self_signature), Self::has_relation_to_inner(
other_signatures, db,
inferable, std::slice::from_ref(self_signature),
relation, other_signatures,
relation_visitor, inferable,
disjointness_visitor, relation,
) relation_visitor,
}), disjointness_visitor,
)
}),
// `self` is definitely not overloaded while `other` is possibly overloaded. // `self` is definitely not overloaded while `other` is possibly overloaded.
([_], _) => other_signatures.iter().when_all(db, |other_signature| { ([_], _) => other_signatures
Self::has_relation_to_inner( .iter()
db, .when_all(db, inferable, |other_signature| {
self_signatures, Self::has_relation_to_inner(
std::slice::from_ref(other_signature), db,
inferable, self_signatures,
relation, std::slice::from_ref(other_signature),
relation_visitor, inferable,
disjointness_visitor, relation,
) relation_visitor,
}), disjointness_visitor,
)
}),
// `self` is definitely overloaded while `other` is possibly overloaded. // `self` is definitely overloaded while `other` is possibly overloaded.
(_, _) => other_signatures.iter().when_all(db, |other_signature| { (_, _) => other_signatures
Self::has_relation_to_inner( .iter()
db, .when_all(db, inferable, |other_signature| {
self_signatures, Self::has_relation_to_inner(
std::slice::from_ref(other_signature), db,
inferable, self_signatures,
relation, std::slice::from_ref(other_signature),
relation_visitor, inferable,
disjointness_visitor, relation,
) relation_visitor,
}), disjointness_visitor,
)
}),
} }
} }
@ -332,7 +338,7 @@ impl<'db> CallableSignature<'db> {
} }
(_, _) => { (_, _) => {
if self == other { if self == other {
return ConstraintSet::from(true); return ConstraintSet::always(inferable);
} }
self.is_subtype_of_impl(db, other, inferable) self.is_subtype_of_impl(db, other, inferable)
.and(db, || other.is_subtype_of_impl(db, self, 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, self.inferable_typevars(db));
let inferable = inferable.merge(db, other.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<Type<'db>>, other_type: Option<Type<'db>>| { let mut check_types = |self_type: Option<Type<'db>>, other_type: Option<Type<'db>>| {
let self_type = self_type.unwrap_or(Type::unknown()); let self_type = self_type.unwrap_or(Type::unknown());
let other_type = other_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() { if self.parameters.is_gradual() != other.parameters.is_gradual() {
return ConstraintSet::from(false); return ConstraintSet::never(inferable);
} }
if self.parameters.len() != other.parameters.len() { if self.parameters.len() != other.parameters.len() {
return ConstraintSet::from(false); return ConstraintSet::never(inferable);
} }
if !check_types(self.return_ty, other.return_ty) { if !check_types(self.return_ty, other.return_ty) {
@ -715,7 +721,7 @@ impl<'db> Signature<'db> {
(ParameterKind::KeywordVariadic { .. }, ParameterKind::KeywordVariadic { .. }) => {} (ParameterKind::KeywordVariadic { .. }, ParameterKind::KeywordVariadic { .. }) => {}
_ => return ConstraintSet::from(false), _ => return ConstraintSet::never(inferable),
} }
if !check_types( 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, self.inferable_typevars(db));
let inferable = inferable.merge(db, other.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<Type<'db>>, type2: Option<Type<'db>>| { let mut check_types = |type1: Option<Type<'db>>, type2: Option<Type<'db>>| {
let type1 = type1.unwrap_or(Type::unknown()); let type1 = type1.unwrap_or(Type::unknown());
let type2 = type2.unwrap_or(Type::unknown()); let type2 = type2.unwrap_or(Type::unknown());
@ -841,13 +847,13 @@ impl<'db> Signature<'db> {
.keyword_variadic() .keyword_variadic()
.is_some_and(|(_, param)| param.annotated_type().is_some_and(|ty| ty.is_object())) .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 // 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. // any other parameter list, but not a subtype or supertype of any other parameter list.
if self.parameters.is_gradual() || other.parameters.is_gradual() { 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 { let mut parameters = ParametersZip {
@ -885,7 +891,7 @@ impl<'db> Signature<'db> {
// `other`, then the non-variadic parameters in `self` must have a default // `other`, then the non-variadic parameters in `self` must have a default
// value. // value.
if default_type.is_none() { if default_type.is_none() {
return ConstraintSet::from(false); return ConstraintSet::never(inferable);
} }
} }
ParameterKind::Variadic { .. } | ParameterKind::KeywordVariadic { .. } => { ParameterKind::Variadic { .. } | ParameterKind::KeywordVariadic { .. } => {
@ -897,7 +903,7 @@ impl<'db> Signature<'db> {
EitherOrBoth::Right(_) => { EitherOrBoth::Right(_) => {
// If there are more parameters in `other` than in `self`, then `self` is not a // If there are more parameters in `other` than in `self`, then `self` is not a
// subtype of `other`. // subtype of `other`.
return ConstraintSet::from(false); return ConstraintSet::never(inferable);
} }
EitherOrBoth::Both(self_parameter, other_parameter) => { EitherOrBoth::Both(self_parameter, other_parameter) => {
@ -917,7 +923,7 @@ impl<'db> Signature<'db> {
}, },
) => { ) => {
if self_default.is_none() && other_default.is_some() { if self_default.is_none() && other_default.is_some() {
return ConstraintSet::from(false); return ConstraintSet::never(inferable);
} }
if !check_types( if !check_types(
other_parameter.annotated_type(), other_parameter.annotated_type(),
@ -938,11 +944,11 @@ impl<'db> Signature<'db> {
}, },
) => { ) => {
if self_name != other_name { if self_name != other_name {
return ConstraintSet::from(false); return ConstraintSet::never(inferable);
} }
// The following checks are the same as positional-only parameters. // The following checks are the same as positional-only parameters.
if self_default.is_none() && other_default.is_some() { if self_default.is_none() && other_default.is_some() {
return ConstraintSet::from(false); return ConstraintSet::never(inferable);
} }
if !check_types( if !check_types(
other_parameter.annotated_type(), other_parameter.annotated_type(),
@ -1027,7 +1033,7 @@ impl<'db> Signature<'db> {
break; 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 // previous loop. They cannot be matched against any parameter in `other` which
// only contains keyword-only and keyword-variadic parameters so the subtype // only contains keyword-only and keyword-variadic parameters so the subtype
// relation is invalid. // relation is invalid.
return ConstraintSet::from(false); return ConstraintSet::never(inferable);
} }
ParameterKind::Variadic { .. } => {} ParameterKind::Variadic { .. } => {}
} }
@ -1088,7 +1094,7 @@ impl<'db> Signature<'db> {
.. ..
} => { } => {
if self_default.is_none() && other_default.is_some() { if self_default.is_none() && other_default.is_some() {
return ConstraintSet::from(false); return ConstraintSet::never(inferable);
} }
if !check_types( if !check_types(
other_parameter.annotated_type(), other_parameter.annotated_type(),
@ -1109,14 +1115,14 @@ impl<'db> Signature<'db> {
return result; return result;
} }
} else { } else {
return ConstraintSet::from(false); return ConstraintSet::never(inferable);
} }
} }
ParameterKind::KeywordVariadic { .. } => { ParameterKind::KeywordVariadic { .. } => {
let Some(self_keyword_variadic_type) = self_keyword_variadic else { let Some(self_keyword_variadic_type) = self_keyword_variadic else {
// For a `self <: other` relationship, if `other` has a keyword variadic // For a `self <: other` relationship, if `other` has a keyword variadic
// parameter, `self` must also have a keyword variadic parameter. // 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) { if !check_types(other_parameter.annotated_type(), self_keyword_variadic_type) {
return result; return result;
@ -1124,7 +1130,7 @@ impl<'db> Signature<'db> {
} }
_ => { _ => {
// This can only occur in case of a syntax error. // 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. // optional otherwise the subtype relation is invalid.
for (_, self_parameter) in self_keywords { for (_, self_parameter) in self_keywords {
if self_parameter.default_type().is_none() { if self_parameter.default_type().is_none() {
return ConstraintSet::from(false); return ConstraintSet::never(inferable);
} }
} }

View File

@ -143,13 +143,16 @@ impl<'db> SubclassOfType<'db> {
) -> ConstraintSet<'db> { ) -> ConstraintSet<'db> {
match (self.subclass_of, other.subclass_of) { match (self.subclass_of, other.subclass_of) {
(SubclassOfInner::Dynamic(_), SubclassOfInner::Dynamic(_)) => { (SubclassOfInner::Dynamic(_), SubclassOfInner::Dynamic(_)) => {
ConstraintSet::from(!relation.is_subtyping()) ConstraintSet::from_bool(!relation.is_subtyping(), inferable)
} }
(SubclassOfInner::Dynamic(_), SubclassOfInner::Class(other_class)) => { (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(_)) => { (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`, // For example, `type[bool]` describes all possible runtime subclasses of the class `bool`,
@ -174,15 +177,18 @@ impl<'db> SubclassOfType<'db> {
self, self,
db: &'db dyn Db, db: &'db dyn Db,
other: Self, other: Self,
_inferable: InferableTypeVars<'db>, inferable: InferableTypeVars<'db>,
_visitor: &IsDisjointVisitor<'db>, _visitor: &IsDisjointVisitor<'db>,
) -> ConstraintSet<'db> { ) -> ConstraintSet<'db> {
match (self.subclass_of, other.subclass_of) { match (self.subclass_of, other.subclass_of) {
(SubclassOfInner::Dynamic(_), _) | (_, SubclassOfInner::Dynamic(_)) => { (SubclassOfInner::Dynamic(_), _) | (_, SubclassOfInner::Dynamic(_)) => {
ConstraintSet::from(false) ConstraintSet::never(inferable)
} }
(SubclassOfInner::Class(self_class), SubclassOfInner::Class(other_class)) => { (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,
)
} }
} }
} }

View File

@ -448,8 +448,8 @@ impl<'db> FixedLengthTuple<Type<'db>> {
) -> ConstraintSet<'db> { ) -> ConstraintSet<'db> {
match other { match other {
Tuple::Fixed(other) => { Tuple::Fixed(other) => {
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)| { (self.0.iter().zip(&other.0)).when_all(db, inferable, |(self_ty, other_ty)| {
self_ty.has_relation_to_impl( self_ty.has_relation_to_impl(
db, db,
*other_ty, *other_ty,
@ -465,11 +465,11 @@ impl<'db> FixedLengthTuple<Type<'db>> {
Tuple::Variable(other) => { Tuple::Variable(other) => {
// This tuple must have enough elements to match up with the other tuple's prefix // 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. // 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(); let mut self_iter = self.0.iter();
for other_ty in &other.prefix { for other_ty in &other.prefix {
let Some(self_ty) = self_iter.next() else { 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( let element_constraints = self_ty.has_relation_to_impl(
db, db,
@ -488,7 +488,7 @@ impl<'db> FixedLengthTuple<Type<'db>> {
} }
for other_ty in other.suffix.iter().rev() { for other_ty in other.suffix.iter().rev() {
let Some(self_ty) = self_iter.next_back() else { 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( let element_constraints = self_ty.has_relation_to_impl(
db, db,
@ -509,7 +509,7 @@ impl<'db> FixedLengthTuple<Type<'db>> {
// In addition, any remaining elements in this tuple must satisfy the // In addition, any remaining elements in this tuple must satisfy the
// variable-length portion of the other tuple. // variable-length portion of the other tuple.
result.and(db, || { result.and(db, || {
self_iter.when_all(db, |self_ty| { self_iter.when_all(db, inferable, |self_ty| {
self_ty.has_relation_to_impl( self_ty.has_relation_to_impl(
db, db,
other.variable, other.variable,
@ -531,10 +531,10 @@ impl<'db> FixedLengthTuple<Type<'db>> {
inferable: InferableTypeVars<'db>, inferable: InferableTypeVars<'db>,
visitor: &IsEquivalentVisitor<'db>, visitor: &IsEquivalentVisitor<'db>,
) -> ConstraintSet<'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()) (self.0.iter())
.zip(&other.0) .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) self_ty.is_equivalent_to_impl(db, *other_ty, inferable, visitor)
}) })
}) })
@ -816,17 +816,17 @@ impl<'db> VariableLengthTuple<Type<'db>> {
// possible lengths. This means that `tuple[Any, ...]` can match any tuple of any // possible lengths. This means that `tuple[Any, ...]` can match any tuple of any
// length. // length.
if !relation.is_assignability() || !self.variable.is_dynamic() { 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 // 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 // tuple's prefix and suffix, and each of those elements must pairwise satisfy the
// relation. // relation.
let mut result = ConstraintSet::from(true); let mut result = ConstraintSet::always(inferable);
let mut other_iter = other.elements().copied(); let mut other_iter = other.elements().copied();
for self_ty in self.prenormalized_prefix_elements(db, None) { for self_ty in self.prenormalized_prefix_elements(db, None) {
let Some(other_ty) = other_iter.next() else { 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( let element_constraints = self_ty.has_relation_to_impl(
db, db,
@ -846,7 +846,7 @@ impl<'db> VariableLengthTuple<Type<'db>> {
let suffix: Vec<_> = self.prenormalized_suffix_elements(db, None).collect(); let suffix: Vec<_> = self.prenormalized_suffix_elements(db, None).collect();
for self_ty in suffix.iter().rev() { for self_ty in suffix.iter().rev() {
let Some(other_ty) = other_iter.next_back() else { 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( let element_constraints = self_ty.has_relation_to_impl(
db, db,
@ -882,7 +882,7 @@ impl<'db> VariableLengthTuple<Type<'db>> {
// The overlapping parts of the prefixes and suffixes must satisfy the relation. // The overlapping parts of the prefixes and suffixes must satisfy the relation.
// Any remaining parts must satisfy the relation with the other tuple's // Any remaining parts must satisfy the relation with the other tuple's
// variable-length part. // 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)) let pairwise = (self.prenormalized_prefix_elements(db, self_prenormalize_variable))
.zip_longest( .zip_longest(
other.prenormalized_prefix_elements(db, other_prenormalize_variable), other.prenormalized_prefix_elements(db, other_prenormalize_variable),
@ -908,7 +908,7 @@ impl<'db> VariableLengthTuple<Type<'db>> {
EitherOrBoth::Right(_) => { EitherOrBoth::Right(_) => {
// The rhs has a required element that the lhs is not guaranteed to // The rhs has a required element that the lhs is not guaranteed to
// provide. // provide.
return ConstraintSet::from(false); return ConstraintSet::never(inferable);
} }
}; };
if result if result
@ -947,7 +947,7 @@ impl<'db> VariableLengthTuple<Type<'db>> {
EitherOrBoth::Right(_) => { EitherOrBoth::Right(_) => {
// The rhs has a required element that the lhs is not guaranteed to // The rhs has a required element that the lhs is not guaranteed to
// provide. // provide.
return ConstraintSet::from(false); return ConstraintSet::never(inferable);
} }
}; };
if result if result
@ -985,24 +985,24 @@ impl<'db> VariableLengthTuple<Type<'db>> {
.and(db, || { .and(db, || {
(self.prenormalized_prefix_elements(db, None)) (self.prenormalized_prefix_elements(db, None))
.zip_longest(other.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) => { EitherOrBoth::Both(self_ty, other_ty) => {
self_ty.is_equivalent_to_impl(db, other_ty, inferable, visitor) self_ty.is_equivalent_to_impl(db, other_ty, inferable, visitor)
} }
EitherOrBoth::Left(_) | EitherOrBoth::Right(_) => { EitherOrBoth::Left(_) | EitherOrBoth::Right(_) => {
ConstraintSet::from(false) ConstraintSet::never(inferable)
} }
}) })
}) })
.and(db, || { .and(db, || {
(self.prenormalized_suffix_elements(db, None)) (self.prenormalized_suffix_elements(db, None))
.zip_longest(other.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) => { EitherOrBoth::Both(self_ty, other_ty) => {
self_ty.is_equivalent_to_impl(db, other_ty, inferable, visitor) self_ty.is_equivalent_to_impl(db, other_ty, inferable, visitor)
} }
EitherOrBoth::Left(_) | EitherOrBoth::Right(_) => { EitherOrBoth::Left(_) | EitherOrBoth::Right(_) => {
ConstraintSet::from(false) ConstraintSet::never(inferable)
} }
}) })
}) })
@ -1230,7 +1230,7 @@ impl<'db> Tuple<Type<'db>> {
self_tuple.is_equivalent_to_impl(db, other_tuple, inferable, visitor) self_tuple.is_equivalent_to_impl(db, other_tuple, inferable, visitor)
} }
(Tuple::Fixed(_), Tuple::Variable(_)) | (Tuple::Variable(_), Tuple::Fixed(_)) => { (Tuple::Fixed(_), Tuple::Variable(_)) | (Tuple::Variable(_), Tuple::Fixed(_)) => {
ConstraintSet::from(false) ConstraintSet::never(inferable)
} }
} }
} }
@ -1247,10 +1247,10 @@ impl<'db> Tuple<Type<'db>> {
let (self_min, self_max) = self.len().size_hint(); let (self_min, self_max) = self.len().size_hint();
let (other_min, other_max) = other.len().size_hint(); let (other_min, other_max) = other.len().size_hint();
if self_max.is_some_and(|max| max < other_min) { 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) { 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. // If any of the required elements are pairwise disjoint, the tuples are disjoint as well.
@ -1266,7 +1266,7 @@ impl<'db> Tuple<Type<'db>> {
where where
'db: 's, '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( self_element.is_disjoint_from_impl(
db, db,
*other_element, *other_element,