[ty] Move constraint set mdtest functions into ConstraintSet class (#21108)
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (${{ github.repository == 'astral-sh/ruff' && 'depot-windows-2022-16' || 'windows-latest' }}) (push) Blocked by required conditions
CI / cargo test (macos-latest) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / ty completion evaluation (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks instrumented (ruff) (push) Blocked by required conditions
CI / benchmarks instrumented (ty) (push) Blocked by required conditions
CI / benchmarks walltime (medium|multithreaded) (push) Blocked by required conditions
CI / benchmarks walltime (small|large) (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run

We have several functions in `ty_extensions` for testing our constraint
set implementation. This PR refactors those functions so that they are
all methods of the `ConstraintSet` class, rather than being standalone
top-level functions. 🎩 to @sharkdp for pointing out that
`KnownBoundMethod` gives us what we need to implement that!
This commit is contained in:
Douglas Creager 2025-10-28 14:32:41 -04:00 committed by GitHub
parent 7b959ef44b
commit 4d2ee41e24
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 407 additions and 297 deletions

View file

@ -4119,6 +4119,39 @@ impl<'db> Type<'db> {
Place::bound(Type::KnownBoundMethod(KnownBoundMethodType::PathOpen)).into()
}
Type::ClassLiteral(class)
if name == "range" && class.is_known(db, KnownClass::ConstraintSet) =>
{
Place::bound(Type::KnownBoundMethod(
KnownBoundMethodType::ConstraintSetRange,
))
.into()
}
Type::ClassLiteral(class)
if name == "always" && class.is_known(db, KnownClass::ConstraintSet) =>
{
Place::bound(Type::KnownBoundMethod(
KnownBoundMethodType::ConstraintSetAlways,
))
.into()
}
Type::ClassLiteral(class)
if name == "never" && class.is_known(db, KnownClass::ConstraintSet) =>
{
Place::bound(Type::KnownBoundMethod(
KnownBoundMethodType::ConstraintSetNever,
))
.into()
}
Type::KnownInstance(KnownInstanceType::ConstraintSet(tracked))
if name == "implies_subtype_of" =>
{
Place::bound(Type::KnownBoundMethod(
KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(tracked),
))
.into()
}
Type::ClassLiteral(class)
if name == "__get__" && class.is_known(db, KnownClass::FunctionType) =>
{
@ -4833,51 +4866,6 @@ impl<'db> Type<'db> {
)
.into(),
Some(KnownFunction::IsSubtypeOfGiven) => Binding::single(
self,
Signature::new(
Parameters::new([
Parameter::positional_only(Some(Name::new_static("constraints")))
.with_annotated_type(UnionType::from_elements(
db,
[
KnownClass::Bool.to_instance(db),
KnownClass::ConstraintSet.to_instance(db),
],
)),
Parameter::positional_only(Some(Name::new_static("ty")))
.type_form()
.with_annotated_type(Type::any()),
Parameter::positional_only(Some(Name::new_static("of")))
.type_form()
.with_annotated_type(Type::any()),
]),
Some(KnownClass::ConstraintSet.to_instance(db)),
),
)
.into(),
Some(KnownFunction::RangeConstraint | KnownFunction::NegatedRangeConstraint) => {
Binding::single(
self,
Signature::new(
Parameters::new([
Parameter::positional_only(Some(Name::new_static("lower_bound")))
.type_form()
.with_annotated_type(Type::any()),
Parameter::positional_only(Some(Name::new_static("typevar")))
.type_form()
.with_annotated_type(Type::any()),
Parameter::positional_only(Some(Name::new_static("upper_bound")))
.type_form()
.with_annotated_type(Type::any()),
]),
Some(KnownClass::ConstraintSet.to_instance(db)),
),
)
.into()
}
Some(KnownFunction::IsSingleton | KnownFunction::IsSingleValued) => {
Binding::single(
self,
@ -6918,7 +6906,14 @@ impl<'db> Type<'db> {
| Type::AlwaysTruthy
| Type::AlwaysFalsy
| Type::WrapperDescriptor(_)
| Type::KnownBoundMethod(KnownBoundMethodType::StrStartswith(_) | KnownBoundMethodType::PathOpen)
| Type::KnownBoundMethod(
KnownBoundMethodType::StrStartswith(_)
| KnownBoundMethodType::PathOpen
| KnownBoundMethodType::ConstraintSetRange
| KnownBoundMethodType::ConstraintSetAlways
| KnownBoundMethodType::ConstraintSetNever
| KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_)
)
| Type::DataclassDecorator(_)
| Type::DataclassTransformer(_)
// A non-generic class never needs to be specialized. A generic class is specialized
@ -7064,7 +7059,12 @@ impl<'db> Type<'db> {
| Type::AlwaysFalsy
| Type::WrapperDescriptor(_)
| Type::KnownBoundMethod(
KnownBoundMethodType::StrStartswith(_) | KnownBoundMethodType::PathOpen,
KnownBoundMethodType::StrStartswith(_)
| KnownBoundMethodType::PathOpen
| KnownBoundMethodType::ConstraintSetRange
| KnownBoundMethodType::ConstraintSetAlways
| KnownBoundMethodType::ConstraintSetNever
| KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_),
)
| Type::DataclassDecorator(_)
| Type::DataclassTransformer(_)
@ -10318,6 +10318,12 @@ pub enum KnownBoundMethodType<'db> {
StrStartswith(StringLiteralType<'db>),
/// Method wrapper for `Path.open`,
PathOpen,
// ConstraintSet methods
ConstraintSetRange,
ConstraintSetAlways,
ConstraintSetNever,
ConstraintSetImpliesSubtypeOf(TrackedConstraintSet<'db>),
}
pub(super) fn walk_method_wrapper_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
@ -10341,7 +10347,11 @@ pub(super) fn walk_method_wrapper_type<'db, V: visitor::TypeVisitor<'db> + ?Size
KnownBoundMethodType::StrStartswith(string_literal) => {
visitor.visit_type(db, Type::StringLiteral(string_literal));
}
KnownBoundMethodType::PathOpen => {}
KnownBoundMethodType::PathOpen
| KnownBoundMethodType::ConstraintSetRange
| KnownBoundMethodType::ConstraintSetAlways
| KnownBoundMethodType::ConstraintSetNever
| KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_) => {}
}
}
@ -10393,9 +10403,23 @@ impl<'db> KnownBoundMethodType<'db> {
ConstraintSet::from(self == other)
}
(KnownBoundMethodType::PathOpen, KnownBoundMethodType::PathOpen) => {
ConstraintSet::from(true)
}
(KnownBoundMethodType::PathOpen, KnownBoundMethodType::PathOpen)
| (
KnownBoundMethodType::ConstraintSetRange,
KnownBoundMethodType::ConstraintSetRange,
)
| (
KnownBoundMethodType::ConstraintSetAlways,
KnownBoundMethodType::ConstraintSetAlways,
)
| (
KnownBoundMethodType::ConstraintSetNever,
KnownBoundMethodType::ConstraintSetNever,
)
| (
KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_),
KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_),
) => ConstraintSet::from(true),
(
KnownBoundMethodType::FunctionTypeDunderGet(_)
@ -10403,13 +10427,21 @@ impl<'db> KnownBoundMethodType<'db> {
| KnownBoundMethodType::PropertyDunderGet(_)
| KnownBoundMethodType::PropertyDunderSet(_)
| KnownBoundMethodType::StrStartswith(_)
| KnownBoundMethodType::PathOpen,
| KnownBoundMethodType::PathOpen
| KnownBoundMethodType::ConstraintSetRange
| KnownBoundMethodType::ConstraintSetAlways
| KnownBoundMethodType::ConstraintSetNever
| KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_),
KnownBoundMethodType::FunctionTypeDunderGet(_)
| KnownBoundMethodType::FunctionTypeDunderCall(_)
| KnownBoundMethodType::PropertyDunderGet(_)
| KnownBoundMethodType::PropertyDunderSet(_)
| KnownBoundMethodType::StrStartswith(_)
| KnownBoundMethodType::PathOpen,
| KnownBoundMethodType::PathOpen
| KnownBoundMethodType::ConstraintSetRange
| KnownBoundMethodType::ConstraintSetAlways
| KnownBoundMethodType::ConstraintSetNever
| KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_),
) => ConstraintSet::from(false),
}
}
@ -10445,9 +10477,26 @@ impl<'db> KnownBoundMethodType<'db> {
ConstraintSet::from(self == other)
}
(KnownBoundMethodType::PathOpen, KnownBoundMethodType::PathOpen) => {
ConstraintSet::from(true)
}
(KnownBoundMethodType::PathOpen, KnownBoundMethodType::PathOpen)
| (
KnownBoundMethodType::ConstraintSetRange,
KnownBoundMethodType::ConstraintSetRange,
)
| (
KnownBoundMethodType::ConstraintSetAlways,
KnownBoundMethodType::ConstraintSetAlways,
)
| (
KnownBoundMethodType::ConstraintSetNever,
KnownBoundMethodType::ConstraintSetNever,
) => ConstraintSet::from(true),
(
KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(left_constraints),
KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(right_constraints),
) => left_constraints
.constraints(db)
.iff(db, right_constraints.constraints(db)),
(
KnownBoundMethodType::FunctionTypeDunderGet(_)
@ -10455,13 +10504,21 @@ impl<'db> KnownBoundMethodType<'db> {
| KnownBoundMethodType::PropertyDunderGet(_)
| KnownBoundMethodType::PropertyDunderSet(_)
| KnownBoundMethodType::StrStartswith(_)
| KnownBoundMethodType::PathOpen,
| KnownBoundMethodType::PathOpen
| KnownBoundMethodType::ConstraintSetRange
| KnownBoundMethodType::ConstraintSetAlways
| KnownBoundMethodType::ConstraintSetNever
| KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_),
KnownBoundMethodType::FunctionTypeDunderGet(_)
| KnownBoundMethodType::FunctionTypeDunderCall(_)
| KnownBoundMethodType::PropertyDunderGet(_)
| KnownBoundMethodType::PropertyDunderSet(_)
| KnownBoundMethodType::StrStartswith(_)
| KnownBoundMethodType::PathOpen,
| KnownBoundMethodType::PathOpen
| KnownBoundMethodType::ConstraintSetRange
| KnownBoundMethodType::ConstraintSetAlways
| KnownBoundMethodType::ConstraintSetNever
| KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_),
) => ConstraintSet::from(false),
}
}
@ -10480,7 +10537,12 @@ impl<'db> KnownBoundMethodType<'db> {
KnownBoundMethodType::PropertyDunderSet(property) => {
KnownBoundMethodType::PropertyDunderSet(property.normalized_impl(db, visitor))
}
KnownBoundMethodType::StrStartswith(_) | KnownBoundMethodType::PathOpen => self,
KnownBoundMethodType::StrStartswith(_)
| KnownBoundMethodType::PathOpen
| KnownBoundMethodType::ConstraintSetRange
| KnownBoundMethodType::ConstraintSetAlways
| KnownBoundMethodType::ConstraintSetNever
| KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_) => self,
}
}
@ -10493,6 +10555,10 @@ impl<'db> KnownBoundMethodType<'db> {
| KnownBoundMethodType::PropertyDunderSet(_) => KnownClass::MethodWrapperType,
KnownBoundMethodType::StrStartswith(_) => KnownClass::BuiltinFunctionType,
KnownBoundMethodType::PathOpen => KnownClass::MethodType,
KnownBoundMethodType::ConstraintSetRange
| KnownBoundMethodType::ConstraintSetAlways
| KnownBoundMethodType::ConstraintSetNever
| KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_) => KnownClass::ConstraintSet,
}
}
@ -10592,6 +10658,45 @@ impl<'db> KnownBoundMethodType<'db> {
KnownBoundMethodType::PathOpen => {
Either::Right(std::iter::once(Signature::todo("`Path.open` return type")))
}
KnownBoundMethodType::ConstraintSetRange => {
Either::Right(std::iter::once(Signature::new(
Parameters::new([
Parameter::positional_only(Some(Name::new_static("lower_bound")))
.type_form()
.with_annotated_type(Type::any()),
Parameter::positional_only(Some(Name::new_static("typevar")))
.type_form()
.with_annotated_type(Type::any()),
Parameter::positional_only(Some(Name::new_static("upper_bound")))
.type_form()
.with_annotated_type(Type::any()),
]),
Some(KnownClass::ConstraintSet.to_instance(db)),
)))
}
KnownBoundMethodType::ConstraintSetAlways
| KnownBoundMethodType::ConstraintSetNever => {
Either::Right(std::iter::once(Signature::new(
Parameters::empty(),
Some(KnownClass::ConstraintSet.to_instance(db)),
)))
}
KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_) => {
Either::Right(std::iter::once(Signature::new(
Parameters::new([
Parameter::positional_only(Some(Name::new_static("ty")))
.type_form()
.with_annotated_type(Type::any()),
Parameter::positional_only(Some(Name::new_static("of")))
.type_form()
.with_annotated_type(Type::any()),
]),
Some(KnownClass::ConstraintSet.to_instance(db)),
)))
}
}
}
}

View file

@ -705,33 +705,6 @@ impl<'db> Bindings<'db> {
}
}
Some(KnownFunction::IsSubtypeOfGiven) => {
let [Some(constraints), Some(ty_a), Some(ty_b)] =
overload.parameter_types()
else {
continue;
};
let constraints = match constraints {
Type::KnownInstance(KnownInstanceType::ConstraintSet(tracked)) => {
tracked.constraints(db)
}
Type::BooleanLiteral(b) => ConstraintSet::from(*b),
_ => continue,
};
let result = constraints.when_subtype_of_given(
db,
*ty_a,
*ty_b,
InferableTypeVars::None,
);
let tracked = TrackedConstraintSet::new(db, result);
overload.set_return_type(Type::KnownInstance(
KnownInstanceType::ConstraintSet(tracked),
));
}
Some(KnownFunction::IsAssignableTo) => {
if let [Some(ty_a), Some(ty_b)] = overload.parameter_types() {
let constraints =
@ -1149,6 +1122,60 @@ impl<'db> Bindings<'db> {
}
},
Type::KnownBoundMethod(KnownBoundMethodType::ConstraintSetRange) => {
let [Some(lower), Some(Type::TypeVar(typevar)), Some(upper)] =
overload.parameter_types()
else {
return;
};
let constraints = ConstraintSet::range(db, *lower, *typevar, *upper);
let tracked = TrackedConstraintSet::new(db, constraints);
overload.set_return_type(Type::KnownInstance(
KnownInstanceType::ConstraintSet(tracked),
));
}
Type::KnownBoundMethod(KnownBoundMethodType::ConstraintSetAlways) => {
if !overload.parameter_types().is_empty() {
return;
}
let constraints = ConstraintSet::from(true);
let tracked = TrackedConstraintSet::new(db, constraints);
overload.set_return_type(Type::KnownInstance(
KnownInstanceType::ConstraintSet(tracked),
));
}
Type::KnownBoundMethod(KnownBoundMethodType::ConstraintSetNever) => {
if !overload.parameter_types().is_empty() {
return;
}
let constraints = ConstraintSet::from(false);
let tracked = TrackedConstraintSet::new(db, constraints);
overload.set_return_type(Type::KnownInstance(
KnownInstanceType::ConstraintSet(tracked),
));
}
Type::KnownBoundMethod(
KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(tracked),
) => {
let [Some(ty_a), Some(ty_b)] = overload.parameter_types() else {
continue;
};
let result = tracked.constraints(db).when_subtype_of_given(
db,
*ty_a,
*ty_b,
InferableTypeVars::None,
);
let tracked = TrackedConstraintSet::new(db, result);
overload.set_return_type(Type::KnownInstance(
KnownInstanceType::ConstraintSet(tracked),
));
}
Type::ClassLiteral(class) => match class.known(db) {
Some(KnownClass::Bool) => match overload.parameter_types() {
[Some(arg)] => overload.set_return_type(arg.bool(db).into_type(db)),

View file

@ -295,6 +295,12 @@ impl<'db> ConstraintSet<'db> {
self
}
pub(crate) fn iff(self, db: &'db dyn Db, other: Self) -> Self {
ConstraintSet {
node: self.node.iff(db, other.node),
}
}
pub(crate) fn range(
db: &'db dyn Db,
lower: Type<'db>,
@ -304,15 +310,6 @@ impl<'db> ConstraintSet<'db> {
Self::constrain_typevar(db, typevar, lower, upper, TypeRelation::Assignability)
}
pub(crate) fn negated_range(
db: &'db dyn Db,
lower: Type<'db>,
typevar: BoundTypeVarInstance<'db>,
upper: Type<'db>,
) -> Self {
Self::range(db, lower, typevar, upper).negate(db)
}
pub(crate) fn display(self, db: &'db dyn Db) -> impl Display {
self.node.simplify(db).display(db)
}

View file

@ -523,6 +523,18 @@ impl Display for DisplayRepresentation<'_> {
Type::KnownBoundMethod(KnownBoundMethodType::PathOpen) => {
f.write_str("bound method `Path.open`")
}
Type::KnownBoundMethod(KnownBoundMethodType::ConstraintSetRange) => {
f.write_str("bound method `ConstraintSet.range`")
}
Type::KnownBoundMethod(KnownBoundMethodType::ConstraintSetAlways) => {
f.write_str("bound method `ConstraintSet.always`")
}
Type::KnownBoundMethod(KnownBoundMethodType::ConstraintSetNever) => {
f.write_str("bound method `ConstraintSet.never`")
}
Type::KnownBoundMethod(KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_)) => {
f.write_str("bound method `ConstraintSet.implies_subtype_of`")
}
Type::WrapperDescriptor(kind) => {
let (method, object) = match kind {
WrapperDescriptorKind::FunctionTypeDunderGet => ("__get__", "function"),

View file

@ -81,9 +81,9 @@ use crate::types::visitor::any_over_type;
use crate::types::{
ApplyTypeMappingVisitor, BoundMethodType, BoundTypeVarInstance, CallableType, ClassBase,
ClassLiteral, ClassType, DeprecatedInstance, DynamicType, FindLegacyTypeVarsVisitor,
HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, KnownClass, KnownInstanceType,
NormalizedVisitor, SpecialFormType, TrackedConstraintSet, Truthiness, Type, TypeContext,
TypeMapping, TypeRelation, UnionBuilder, binding_type, todo_type, walk_signature,
HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, KnownClass, NormalizedVisitor,
SpecialFormType, Truthiness, Type, TypeContext, TypeMapping, TypeRelation, UnionBuilder,
binding_type, todo_type, walk_signature,
};
use crate::{Db, FxOrderSet, ModuleName, resolve_module};
@ -1299,8 +1299,6 @@ pub enum KnownFunction {
IsEquivalentTo,
/// `ty_extensions.is_subtype_of`
IsSubtypeOf,
/// `ty_extensions.is_subtype_of_given`
IsSubtypeOfGiven,
/// `ty_extensions.is_assignable_to`
IsAssignableTo,
/// `ty_extensions.is_disjoint_from`
@ -1323,10 +1321,6 @@ pub enum KnownFunction {
RevealProtocolInterface,
/// `ty_extensions.reveal_mro`
RevealMro,
/// `ty_extensions.range_constraint`
RangeConstraint,
/// `ty_extensions.negated_range_constraint`
NegatedRangeConstraint,
}
impl KnownFunction {
@ -1393,15 +1387,12 @@ impl KnownFunction {
| Self::IsSingleValued
| Self::IsSingleton
| Self::IsSubtypeOf
| Self::IsSubtypeOfGiven
| Self::GenericContext
| Self::DunderAllNames
| Self::EnumMembers
| Self::StaticAssert
| Self::HasMember
| Self::RevealProtocolInterface
| Self::RangeConstraint
| Self::NegatedRangeConstraint
| Self::RevealMro
| Self::AllMembers => module.is_ty_extensions(),
Self::ImportModule => module.is_importlib(),
@ -1780,32 +1771,6 @@ impl KnownFunction {
overload.set_return_type(Type::module_literal(db, file, module));
}
KnownFunction::RangeConstraint => {
let [Some(lower), Some(Type::TypeVar(typevar)), Some(upper)] = parameter_types
else {
return;
};
let constraints = ConstraintSet::range(db, *lower, *typevar, *upper);
let tracked = TrackedConstraintSet::new(db, constraints);
overload.set_return_type(Type::KnownInstance(KnownInstanceType::ConstraintSet(
tracked,
)));
}
KnownFunction::NegatedRangeConstraint => {
let [Some(lower), Some(Type::TypeVar(typevar)), Some(upper)] = parameter_types
else {
return;
};
let constraints = ConstraintSet::negated_range(db, *lower, *typevar, *upper);
let tracked = TrackedConstraintSet::new(db, constraints);
overload.set_return_type(Type::KnownInstance(KnownInstanceType::ConstraintSet(
tracked,
)));
}
KnownFunction::Open => {
// TODO: Temporary special-casing for `builtins.open` to avoid an excessive number of
// false positives in lieu of proper support for PEP-613 type aliases.
@ -1894,7 +1859,6 @@ pub(crate) mod tests {
KnownFunction::IsSingleton
| KnownFunction::IsSubtypeOf
| KnownFunction::IsSubtypeOfGiven
| KnownFunction::GenericContext
| KnownFunction::DunderAllNames
| KnownFunction::EnumMembers
@ -1905,8 +1869,6 @@ pub(crate) mod tests {
| KnownFunction::IsEquivalentTo
| KnownFunction::HasMember
| KnownFunction::RevealProtocolInterface
| KnownFunction::RangeConstraint
| KnownFunction::NegatedRangeConstraint
| KnownFunction::RevealMro
| KnownFunction::AllMembers => KnownModule::TyExtensions,