diff --git a/crates/ty_python_semantic/resources/mdtest/type_properties/constraints.md b/crates/ty_python_semantic/resources/mdtest/type_properties/constraints.md index 5d1f4e7142..00a3e2837f 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_properties/constraints.md +++ b/crates/ty_python_semantic/resources/mdtest/type_properties/constraints.md @@ -34,7 +34,7 @@ upper bound. ```py from typing import Any, final, Never, Sequence -from ty_extensions import range_constraint +from ty_extensions import ConstraintSet class Super: ... class Base(Super): ... @@ -45,7 +45,7 @@ class Unrelated: ... def _[T]() -> None: # revealed: ty_extensions.ConstraintSet[(Sub ≤ T@_ ≤ Super)] - reveal_type(range_constraint(Sub, T, Super)) + reveal_type(ConstraintSet.range(Sub, T, Super)) ``` Every type is a supertype of `Never`, so a lower bound of `Never` is the same as having no lower @@ -54,7 +54,7 @@ bound. ```py def _[T]() -> None: # revealed: ty_extensions.ConstraintSet[(T@_ ≤ Base)] - reveal_type(range_constraint(Never, T, Base)) + reveal_type(ConstraintSet.range(Never, T, Base)) ``` Similarly, every type is a subtype of `object`, so an upper bound of `object` is the same as having @@ -63,7 +63,7 @@ no upper bound. ```py def _[T]() -> None: # revealed: ty_extensions.ConstraintSet[(Base ≤ T@_)] - reveal_type(range_constraint(Base, T, object)) + reveal_type(ConstraintSet.range(Base, T, object)) ``` And a range constraint with _both_ a lower bound of `Never` and an upper bound of `object` does not @@ -72,7 +72,7 @@ constrain the typevar at all. ```py def _[T]() -> None: # revealed: ty_extensions.ConstraintSet[always] - reveal_type(range_constraint(Never, T, object)) + reveal_type(ConstraintSet.range(Never, T, object)) ``` If the lower bound and upper bounds are "inverted" (the upper bound is a subtype of the lower bound) @@ -81,9 +81,9 @@ or incomparable, then there is no type that can satisfy the constraint. ```py def _[T]() -> None: # revealed: ty_extensions.ConstraintSet[never] - reveal_type(range_constraint(Super, T, Sub)) + reveal_type(ConstraintSet.range(Super, T, Sub)) # revealed: ty_extensions.ConstraintSet[never] - reveal_type(range_constraint(Base, T, Unrelated)) + reveal_type(ConstraintSet.range(Base, T, Unrelated)) ``` The lower and upper bound can be the same type, in which case the typevar can only be specialized to @@ -92,7 +92,7 @@ that specific type. ```py def _[T]() -> None: # revealed: ty_extensions.ConstraintSet[(T@_ = Base)] - reveal_type(range_constraint(Base, T, Base)) + reveal_type(ConstraintSet.range(Base, T, Base)) ``` Constraints can only refer to fully static types, so the lower and upper bounds are transformed into @@ -101,14 +101,14 @@ their bottom and top materializations, respectively. ```py def _[T]() -> None: # revealed: ty_extensions.ConstraintSet[(Base ≤ T@_)] - reveal_type(range_constraint(Base, T, Any)) + reveal_type(ConstraintSet.range(Base, T, Any)) # revealed: ty_extensions.ConstraintSet[(Sequence[Base] ≤ T@_ ≤ Sequence[object])] - reveal_type(range_constraint(Sequence[Base], T, Sequence[Any])) + reveal_type(ConstraintSet.range(Sequence[Base], T, Sequence[Any])) # revealed: ty_extensions.ConstraintSet[(T@_ ≤ Base)] - reveal_type(range_constraint(Any, T, Base)) + reveal_type(ConstraintSet.range(Any, T, Base)) # revealed: ty_extensions.ConstraintSet[(Sequence[Never] ≤ T@_ ≤ Sequence[Base])] - reveal_type(range_constraint(Sequence[Any], T, Sequence[Base])) + reveal_type(ConstraintSet.range(Sequence[Any], T, Sequence[Base])) ``` ### Negated range @@ -119,7 +119,7 @@ strict subtype of the lower bound, a strict supertype of the upper bound, or inc ```py from typing import Any, final, Never, Sequence -from ty_extensions import negated_range_constraint +from ty_extensions import ConstraintSet class Super: ... class Base(Super): ... @@ -130,7 +130,7 @@ class Unrelated: ... def _[T]() -> None: # revealed: ty_extensions.ConstraintSet[¬(Sub ≤ T@_ ≤ Super)] - reveal_type(negated_range_constraint(Sub, T, Super)) + reveal_type(~ConstraintSet.range(Sub, T, Super)) ``` Every type is a supertype of `Never`, so a lower bound of `Never` is the same as having no lower @@ -139,7 +139,7 @@ bound. ```py def _[T]() -> None: # revealed: ty_extensions.ConstraintSet[¬(T@_ ≤ Base)] - reveal_type(negated_range_constraint(Never, T, Base)) + reveal_type(~ConstraintSet.range(Never, T, Base)) ``` Similarly, every type is a subtype of `object`, so an upper bound of `object` is the same as having @@ -148,7 +148,7 @@ no upper bound. ```py def _[T]() -> None: # revealed: ty_extensions.ConstraintSet[¬(Base ≤ T@_)] - reveal_type(negated_range_constraint(Base, T, object)) + reveal_type(~ConstraintSet.range(Base, T, object)) ``` And a negated range constraint with _both_ a lower bound of `Never` and an upper bound of `object` @@ -157,7 +157,7 @@ cannot be satisfied at all. ```py def _[T]() -> None: # revealed: ty_extensions.ConstraintSet[never] - reveal_type(negated_range_constraint(Never, T, object)) + reveal_type(~ConstraintSet.range(Never, T, object)) ``` If the lower bound and upper bounds are "inverted" (the upper bound is a subtype of the lower bound) @@ -166,9 +166,9 @@ or incomparable, then the negated range constraint can always be satisfied. ```py def _[T]() -> None: # revealed: ty_extensions.ConstraintSet[always] - reveal_type(negated_range_constraint(Super, T, Sub)) + reveal_type(~ConstraintSet.range(Super, T, Sub)) # revealed: ty_extensions.ConstraintSet[always] - reveal_type(negated_range_constraint(Base, T, Unrelated)) + reveal_type(~ConstraintSet.range(Base, T, Unrelated)) ``` The lower and upper bound can be the same type, in which case the typevar can be specialized to any @@ -177,7 +177,7 @@ type other than that specific type. ```py def _[T]() -> None: # revealed: ty_extensions.ConstraintSet[(T@_ ≠ Base)] - reveal_type(negated_range_constraint(Base, T, Base)) + reveal_type(~ConstraintSet.range(Base, T, Base)) ``` Constraints can only refer to fully static types, so the lower and upper bounds are transformed into @@ -186,14 +186,14 @@ their bottom and top materializations, respectively. ```py def _[T]() -> None: # revealed: ty_extensions.ConstraintSet[¬(Base ≤ T@_)] - reveal_type(negated_range_constraint(Base, T, Any)) + reveal_type(~ConstraintSet.range(Base, T, Any)) # revealed: ty_extensions.ConstraintSet[¬(Sequence[Base] ≤ T@_ ≤ Sequence[object])] - reveal_type(negated_range_constraint(Sequence[Base], T, Sequence[Any])) + reveal_type(~ConstraintSet.range(Sequence[Base], T, Sequence[Any])) # revealed: ty_extensions.ConstraintSet[¬(T@_ ≤ Base)] - reveal_type(negated_range_constraint(Any, T, Base)) + reveal_type(~ConstraintSet.range(Any, T, Base)) # revealed: ty_extensions.ConstraintSet[¬(Sequence[Never] ≤ T@_ ≤ Sequence[Base])] - reveal_type(negated_range_constraint(Sequence[Any], T, Sequence[Base])) + reveal_type(~ConstraintSet.range(Sequence[Any], T, Sequence[Base])) ``` ## Intersection @@ -204,7 +204,7 @@ cases, we can simplify the result of an intersection. ### Different typevars ```py -from ty_extensions import range_constraint, negated_range_constraint +from ty_extensions import ConstraintSet class Super: ... class Base(Super): ... @@ -216,9 +216,9 @@ We cannot simplify the intersection of constraints that refer to different typev ```py def _[T, U]() -> None: # revealed: ty_extensions.ConstraintSet[((Sub ≤ T@_ ≤ Base) ∧ (Sub ≤ U@_ ≤ Base))] - reveal_type(range_constraint(Sub, T, Base) & range_constraint(Sub, U, Base)) + reveal_type(ConstraintSet.range(Sub, T, Base) & ConstraintSet.range(Sub, U, Base)) # revealed: ty_extensions.ConstraintSet[(¬(Sub ≤ T@_ ≤ Base) ∧ ¬(Sub ≤ U@_ ≤ Base))] - reveal_type(negated_range_constraint(Sub, T, Base) & negated_range_constraint(Sub, U, Base)) + reveal_type(~ConstraintSet.range(Sub, T, Base) & ~ConstraintSet.range(Sub, U, Base)) ``` ### Intersection of two ranges @@ -227,7 +227,7 @@ The intersection of two ranges is where the ranges "overlap". ```py from typing import final -from ty_extensions import range_constraint +from ty_extensions import ConstraintSet class Super: ... class Base(Super): ... @@ -239,13 +239,13 @@ class Unrelated: ... def _[T]() -> None: # revealed: ty_extensions.ConstraintSet[(Sub ≤ T@_ ≤ Base)] - reveal_type(range_constraint(SubSub, T, Base) & range_constraint(Sub, T, Super)) + reveal_type(ConstraintSet.range(SubSub, T, Base) & ConstraintSet.range(Sub, T, Super)) # revealed: ty_extensions.ConstraintSet[(Sub ≤ T@_ ≤ Base)] - reveal_type(range_constraint(SubSub, T, Super) & range_constraint(Sub, T, Base)) + reveal_type(ConstraintSet.range(SubSub, T, Super) & ConstraintSet.range(Sub, T, Base)) # revealed: ty_extensions.ConstraintSet[(T@_ = Base)] - reveal_type(range_constraint(Sub, T, Base) & range_constraint(Base, T, Super)) + reveal_type(ConstraintSet.range(Sub, T, Base) & ConstraintSet.range(Base, T, Super)) # revealed: ty_extensions.ConstraintSet[(Sub ≤ T@_ ≤ Super)] - reveal_type(range_constraint(Sub, T, Super) & range_constraint(Sub, T, Super)) + reveal_type(ConstraintSet.range(Sub, T, Super) & ConstraintSet.range(Sub, T, Super)) ``` If they don't overlap, the intersection is empty. @@ -253,9 +253,9 @@ If they don't overlap, the intersection is empty. ```py def _[T]() -> None: # revealed: ty_extensions.ConstraintSet[never] - reveal_type(range_constraint(SubSub, T, Sub) & range_constraint(Base, T, Super)) + reveal_type(ConstraintSet.range(SubSub, T, Sub) & ConstraintSet.range(Base, T, Super)) # revealed: ty_extensions.ConstraintSet[never] - reveal_type(range_constraint(SubSub, T, Sub) & range_constraint(Unrelated, T, object)) + reveal_type(ConstraintSet.range(SubSub, T, Sub) & ConstraintSet.range(Unrelated, T, object)) ``` ### Intersection of a range and a negated range @@ -266,7 +266,7 @@ the intersection as removing the hole from the range constraint. ```py from typing import final, Never -from ty_extensions import range_constraint, negated_range_constraint +from ty_extensions import ConstraintSet class Super: ... class Base(Super): ... @@ -282,9 +282,9 @@ If the negative range completely contains the positive range, then the intersect ```py def _[T]() -> None: # revealed: ty_extensions.ConstraintSet[never] - reveal_type(range_constraint(Sub, T, Base) & negated_range_constraint(SubSub, T, Super)) + reveal_type(ConstraintSet.range(Sub, T, Base) & ~ConstraintSet.range(SubSub, T, Super)) # revealed: ty_extensions.ConstraintSet[never] - reveal_type(range_constraint(Sub, T, Base) & negated_range_constraint(Sub, T, Base)) + reveal_type(ConstraintSet.range(Sub, T, Base) & ~ConstraintSet.range(Sub, T, Base)) ``` If the negative range is disjoint from the positive range, the negative range doesn't remove @@ -293,11 +293,11 @@ anything; the intersection is the positive range. ```py def _[T]() -> None: # revealed: ty_extensions.ConstraintSet[(Sub ≤ T@_ ≤ Base)] - reveal_type(range_constraint(Sub, T, Base) & negated_range_constraint(Never, T, Unrelated)) + reveal_type(ConstraintSet.range(Sub, T, Base) & ~ConstraintSet.range(Never, T, Unrelated)) # revealed: ty_extensions.ConstraintSet[(SubSub ≤ T@_ ≤ Sub)] - reveal_type(range_constraint(SubSub, T, Sub) & negated_range_constraint(Base, T, Super)) + reveal_type(ConstraintSet.range(SubSub, T, Sub) & ~ConstraintSet.range(Base, T, Super)) # revealed: ty_extensions.ConstraintSet[(Base ≤ T@_ ≤ Super)] - reveal_type(range_constraint(Base, T, Super) & negated_range_constraint(SubSub, T, Sub)) + reveal_type(ConstraintSet.range(Base, T, Super) & ~ConstraintSet.range(SubSub, T, Sub)) ``` Otherwise we clip the negative constraint to the mininum range that overlaps with the positive @@ -306,9 +306,9 @@ range. ```py def _[T]() -> None: # revealed: ty_extensions.ConstraintSet[((SubSub ≤ T@_ ≤ Base) ∧ ¬(Sub ≤ T@_ ≤ Base))] - reveal_type(range_constraint(SubSub, T, Base) & negated_range_constraint(Sub, T, Super)) + reveal_type(ConstraintSet.range(SubSub, T, Base) & ~ConstraintSet.range(Sub, T, Super)) # revealed: ty_extensions.ConstraintSet[((SubSub ≤ T@_ ≤ Super) ∧ ¬(Sub ≤ T@_ ≤ Base))] - reveal_type(range_constraint(SubSub, T, Super) & negated_range_constraint(Sub, T, Base)) + reveal_type(ConstraintSet.range(SubSub, T, Super) & ~ConstraintSet.range(Sub, T, Base)) ``` ### Intersection of two negated ranges @@ -318,7 +318,7 @@ smaller constraint. For negated ranges, the smaller constraint is the one with t ```py from typing import final -from ty_extensions import negated_range_constraint +from ty_extensions import ConstraintSet class Super: ... class Base(Super): ... @@ -330,9 +330,9 @@ class Unrelated: ... def _[T]() -> None: # revealed: ty_extensions.ConstraintSet[¬(SubSub ≤ T@_ ≤ Super)] - reveal_type(negated_range_constraint(SubSub, T, Super) & negated_range_constraint(Sub, T, Base)) + reveal_type(~ConstraintSet.range(SubSub, T, Super) & ~ConstraintSet.range(Sub, T, Base)) # revealed: ty_extensions.ConstraintSet[¬(Sub ≤ T@_ ≤ Super)] - reveal_type(negated_range_constraint(Sub, T, Super) & negated_range_constraint(Sub, T, Super)) + reveal_type(~ConstraintSet.range(Sub, T, Super) & ~ConstraintSet.range(Sub, T, Super)) ``` Otherwise, the union cannot be simplified. @@ -340,11 +340,11 @@ Otherwise, the union cannot be simplified. ```py def _[T]() -> None: # revealed: ty_extensions.ConstraintSet[(¬(Base ≤ T@_ ≤ Super) ∧ ¬(Sub ≤ T@_ ≤ Base))] - reveal_type(negated_range_constraint(Sub, T, Base) & negated_range_constraint(Base, T, Super)) + reveal_type(~ConstraintSet.range(Sub, T, Base) & ~ConstraintSet.range(Base, T, Super)) # revealed: ty_extensions.ConstraintSet[(¬(Base ≤ T@_ ≤ Super) ∧ ¬(SubSub ≤ T@_ ≤ Sub))] - reveal_type(negated_range_constraint(SubSub, T, Sub) & negated_range_constraint(Base, T, Super)) + reveal_type(~ConstraintSet.range(SubSub, T, Sub) & ~ConstraintSet.range(Base, T, Super)) # revealed: ty_extensions.ConstraintSet[(¬(SubSub ≤ T@_ ≤ Sub) ∧ ¬(Unrelated ≤ T@_))] - reveal_type(negated_range_constraint(SubSub, T, Sub) & negated_range_constraint(Unrelated, T, object)) + reveal_type(~ConstraintSet.range(SubSub, T, Sub) & ~ConstraintSet.range(Unrelated, T, object)) ``` In particular, the following does not simplify, even though it seems like it could simplify to @@ -361,7 +361,7 @@ that type _is_ in `SubSub ≤ T ≤ Super`, it is not correct to simplify the un ```py def _[T]() -> None: # revealed: ty_extensions.ConstraintSet[(¬(Sub ≤ T@_ ≤ Super) ∧ ¬(SubSub ≤ T@_ ≤ Base))] - reveal_type(negated_range_constraint(SubSub, T, Base) & negated_range_constraint(Sub, T, Super)) + reveal_type(~ConstraintSet.range(SubSub, T, Base) & ~ConstraintSet.range(Sub, T, Super)) ``` ## Union @@ -372,7 +372,7 @@ can simplify the result of an union. ### Different typevars ```py -from ty_extensions import range_constraint, negated_range_constraint +from ty_extensions import ConstraintSet class Super: ... class Base(Super): ... @@ -384,9 +384,9 @@ We cannot simplify the union of constraints that refer to different typevars. ```py def _[T, U]() -> None: # revealed: ty_extensions.ConstraintSet[(Sub ≤ T@_ ≤ Base) ∨ (Sub ≤ U@_ ≤ Base)] - reveal_type(range_constraint(Sub, T, Base) | range_constraint(Sub, U, Base)) + reveal_type(ConstraintSet.range(Sub, T, Base) | ConstraintSet.range(Sub, U, Base)) # revealed: ty_extensions.ConstraintSet[¬(Sub ≤ T@_ ≤ Base) ∨ ¬(Sub ≤ U@_ ≤ Base)] - reveal_type(negated_range_constraint(Sub, T, Base) | negated_range_constraint(Sub, U, Base)) + reveal_type(~ConstraintSet.range(Sub, T, Base) | ~ConstraintSet.range(Sub, U, Base)) ``` ### Union of two ranges @@ -396,7 +396,7 @@ bounds. ```py from typing import final -from ty_extensions import range_constraint +from ty_extensions import ConstraintSet class Super: ... class Base(Super): ... @@ -408,9 +408,9 @@ class Unrelated: ... def _[T]() -> None: # revealed: ty_extensions.ConstraintSet[(SubSub ≤ T@_ ≤ Super)] - reveal_type(range_constraint(SubSub, T, Super) | range_constraint(Sub, T, Base)) + reveal_type(ConstraintSet.range(SubSub, T, Super) | ConstraintSet.range(Sub, T, Base)) # revealed: ty_extensions.ConstraintSet[(Sub ≤ T@_ ≤ Super)] - reveal_type(range_constraint(Sub, T, Super) | range_constraint(Sub, T, Super)) + reveal_type(ConstraintSet.range(Sub, T, Super) | ConstraintSet.range(Sub, T, Super)) ``` Otherwise, the union cannot be simplified. @@ -418,11 +418,11 @@ Otherwise, the union cannot be simplified. ```py def _[T]() -> None: # revealed: ty_extensions.ConstraintSet[(Base ≤ T@_ ≤ Super) ∨ (Sub ≤ T@_ ≤ Base)] - reveal_type(range_constraint(Sub, T, Base) | range_constraint(Base, T, Super)) + reveal_type(ConstraintSet.range(Sub, T, Base) | ConstraintSet.range(Base, T, Super)) # revealed: ty_extensions.ConstraintSet[(Base ≤ T@_ ≤ Super) ∨ (SubSub ≤ T@_ ≤ Sub)] - reveal_type(range_constraint(SubSub, T, Sub) | range_constraint(Base, T, Super)) + reveal_type(ConstraintSet.range(SubSub, T, Sub) | ConstraintSet.range(Base, T, Super)) # revealed: ty_extensions.ConstraintSet[(SubSub ≤ T@_ ≤ Sub) ∨ (Unrelated ≤ T@_)] - reveal_type(range_constraint(SubSub, T, Sub) | range_constraint(Unrelated, T, object)) + reveal_type(ConstraintSet.range(SubSub, T, Sub) | ConstraintSet.range(Unrelated, T, object)) ``` In particular, the following does not simplify, even though it seems like it could simplify to @@ -438,7 +438,7 @@ not include `Sub`. That means it should not be in the union. Since that type _is ```py def _[T]() -> None: # revealed: ty_extensions.ConstraintSet[(Sub ≤ T@_ ≤ Super) ∨ (SubSub ≤ T@_ ≤ Base)] - reveal_type(range_constraint(SubSub, T, Base) | range_constraint(Sub, T, Super)) + reveal_type(ConstraintSet.range(SubSub, T, Base) | ConstraintSet.range(Sub, T, Super)) ``` ### Union of a range and a negated range @@ -449,7 +449,7 @@ the union as filling part of the hole with the types from the range constraint. ```py from typing import final, Never -from ty_extensions import range_constraint, negated_range_constraint +from ty_extensions import ConstraintSet class Super: ... class Base(Super): ... @@ -465,9 +465,9 @@ If the positive range completely contains the negative range, then the union is ```py def _[T]() -> None: # revealed: ty_extensions.ConstraintSet[always] - reveal_type(negated_range_constraint(Sub, T, Base) | range_constraint(SubSub, T, Super)) + reveal_type(~ConstraintSet.range(Sub, T, Base) | ConstraintSet.range(SubSub, T, Super)) # revealed: ty_extensions.ConstraintSet[always] - reveal_type(negated_range_constraint(Sub, T, Base) | range_constraint(Sub, T, Base)) + reveal_type(~ConstraintSet.range(Sub, T, Base) | ConstraintSet.range(Sub, T, Base)) ``` If the negative range is disjoint from the positive range, the positive range doesn't add anything; @@ -476,11 +476,11 @@ the union is the negative range. ```py def _[T]() -> None: # revealed: ty_extensions.ConstraintSet[¬(Sub ≤ T@_ ≤ Base)] - reveal_type(negated_range_constraint(Sub, T, Base) | range_constraint(Never, T, Unrelated)) + reveal_type(~ConstraintSet.range(Sub, T, Base) | ConstraintSet.range(Never, T, Unrelated)) # revealed: ty_extensions.ConstraintSet[¬(SubSub ≤ T@_ ≤ Sub)] - reveal_type(negated_range_constraint(SubSub, T, Sub) | range_constraint(Base, T, Super)) + reveal_type(~ConstraintSet.range(SubSub, T, Sub) | ConstraintSet.range(Base, T, Super)) # revealed: ty_extensions.ConstraintSet[¬(Base ≤ T@_ ≤ Super)] - reveal_type(negated_range_constraint(Base, T, Super) | range_constraint(SubSub, T, Sub)) + reveal_type(~ConstraintSet.range(Base, T, Super) | ConstraintSet.range(SubSub, T, Sub)) ``` Otherwise we clip the positive constraint to the mininum range that overlaps with the negative @@ -489,9 +489,9 @@ range. ```py def _[T]() -> None: # revealed: ty_extensions.ConstraintSet[(Sub ≤ T@_ ≤ Base) ∨ ¬(SubSub ≤ T@_ ≤ Base)] - reveal_type(negated_range_constraint(SubSub, T, Base) | range_constraint(Sub, T, Super)) + reveal_type(~ConstraintSet.range(SubSub, T, Base) | ConstraintSet.range(Sub, T, Super)) # revealed: ty_extensions.ConstraintSet[(Sub ≤ T@_ ≤ Base) ∨ ¬(SubSub ≤ T@_ ≤ Super)] - reveal_type(negated_range_constraint(SubSub, T, Super) | range_constraint(Sub, T, Base)) + reveal_type(~ConstraintSet.range(SubSub, T, Super) | ConstraintSet.range(Sub, T, Base)) ``` ### Union of two negated ranges @@ -500,7 +500,7 @@ The union of two negated ranges has a hole where the ranges "overlap". ```py from typing import final -from ty_extensions import negated_range_constraint +from ty_extensions import ConstraintSet class Super: ... class Base(Super): ... @@ -512,13 +512,13 @@ class Unrelated: ... def _[T]() -> None: # revealed: ty_extensions.ConstraintSet[¬(Sub ≤ T@_ ≤ Base)] - reveal_type(negated_range_constraint(SubSub, T, Base) | negated_range_constraint(Sub, T, Super)) + reveal_type(~ConstraintSet.range(SubSub, T, Base) | ~ConstraintSet.range(Sub, T, Super)) # revealed: ty_extensions.ConstraintSet[¬(Sub ≤ T@_ ≤ Base)] - reveal_type(negated_range_constraint(SubSub, T, Super) | negated_range_constraint(Sub, T, Base)) + reveal_type(~ConstraintSet.range(SubSub, T, Super) | ~ConstraintSet.range(Sub, T, Base)) # revealed: ty_extensions.ConstraintSet[(T@_ ≠ Base)] - reveal_type(negated_range_constraint(Sub, T, Base) | negated_range_constraint(Base, T, Super)) + reveal_type(~ConstraintSet.range(Sub, T, Base) | ~ConstraintSet.range(Base, T, Super)) # revealed: ty_extensions.ConstraintSet[¬(Sub ≤ T@_ ≤ Super)] - reveal_type(negated_range_constraint(Sub, T, Super) | negated_range_constraint(Sub, T, Super)) + reveal_type(~ConstraintSet.range(Sub, T, Super) | ~ConstraintSet.range(Sub, T, Super)) ``` If the holes don't overlap, the union is always satisfied. @@ -526,9 +526,9 @@ If the holes don't overlap, the union is always satisfied. ```py def _[T]() -> None: # revealed: ty_extensions.ConstraintSet[always] - reveal_type(negated_range_constraint(SubSub, T, Sub) | negated_range_constraint(Base, T, Super)) + reveal_type(~ConstraintSet.range(SubSub, T, Sub) | ~ConstraintSet.range(Base, T, Super)) # revealed: ty_extensions.ConstraintSet[always] - reveal_type(negated_range_constraint(SubSub, T, Sub) | negated_range_constraint(Unrelated, T, object)) + reveal_type(~ConstraintSet.range(SubSub, T, Sub) | ~ConstraintSet.range(Unrelated, T, object)) ``` ## Negation @@ -537,7 +537,7 @@ def _[T]() -> None: ```py from typing import Never -from ty_extensions import range_constraint +from ty_extensions import ConstraintSet class Super: ... class Base(Super): ... @@ -545,20 +545,20 @@ class Sub(Base): ... def _[T]() -> None: # revealed: ty_extensions.ConstraintSet[¬(Sub ≤ T@_ ≤ Base)] - reveal_type(~range_constraint(Sub, T, Base)) + reveal_type(~ConstraintSet.range(Sub, T, Base)) # revealed: ty_extensions.ConstraintSet[¬(T@_ ≤ Base)] - reveal_type(~range_constraint(Never, T, Base)) + reveal_type(~ConstraintSet.range(Never, T, Base)) # revealed: ty_extensions.ConstraintSet[¬(Sub ≤ T@_)] - reveal_type(~range_constraint(Sub, T, object)) + reveal_type(~ConstraintSet.range(Sub, T, object)) # revealed: ty_extensions.ConstraintSet[never] - reveal_type(~range_constraint(Never, T, object)) + reveal_type(~ConstraintSet.range(Never, T, object)) ``` The union of a range constraint and its negation should always be satisfiable. ```py def _[T]() -> None: - constraint = range_constraint(Sub, T, Base) + constraint = ConstraintSet.range(Sub, T, Base) # revealed: ty_extensions.ConstraintSet[always] reveal_type(constraint | ~constraint) ``` @@ -567,7 +567,7 @@ def _[T]() -> None: ```py from typing import final, Never -from ty_extensions import range_constraint +from ty_extensions import ConstraintSet class Base: ... @@ -576,20 +576,20 @@ class Unrelated: ... def _[T, U]() -> None: # revealed: ty_extensions.ConstraintSet[¬(T@_ ≤ Base) ∨ ¬(U@_ ≤ Base)] - reveal_type(~(range_constraint(Never, T, Base) & range_constraint(Never, U, Base))) + reveal_type(~(ConstraintSet.range(Never, T, Base) & ConstraintSet.range(Never, U, Base))) ``` The union of a constraint and its negation should always be satisfiable. ```py def _[T, U]() -> None: - c1 = range_constraint(Never, T, Base) & range_constraint(Never, U, Base) + c1 = ConstraintSet.range(Never, T, Base) & ConstraintSet.range(Never, U, Base) # revealed: ty_extensions.ConstraintSet[always] reveal_type(c1 | ~c1) # revealed: ty_extensions.ConstraintSet[always] reveal_type(~c1 | c1) - c2 = range_constraint(Unrelated, T, object) & range_constraint(Unrelated, U, object) + c2 = ConstraintSet.range(Unrelated, T, object) & ConstraintSet.range(Unrelated, U, object) # revealed: ty_extensions.ConstraintSet[always] reveal_type(c2 | ~c2) # revealed: ty_extensions.ConstraintSet[always] @@ -614,19 +614,19 @@ since we always hide `Never` lower bounds and `object` upper bounds. ```py from typing import Never -from ty_extensions import range_constraint +from ty_extensions import ConstraintSet def f[S, T](): # revealed: ty_extensions.ConstraintSet[(S@f ≤ T@f)] - reveal_type(range_constraint(Never, S, T)) + reveal_type(ConstraintSet.range(Never, S, T)) # revealed: ty_extensions.ConstraintSet[(S@f ≤ T@f)] - reveal_type(range_constraint(S, T, object)) + reveal_type(ConstraintSet.range(S, T, object)) def f[T, S](): # revealed: ty_extensions.ConstraintSet[(S@f ≤ T@f)] - reveal_type(range_constraint(Never, S, T)) + reveal_type(ConstraintSet.range(Never, S, T)) # revealed: ty_extensions.ConstraintSet[(S@f ≤ T@f)] - reveal_type(range_constraint(S, T, object)) + reveal_type(ConstraintSet.range(S, T, object)) ``` Equivalence constraints are similar; internally we arbitrarily choose the "earlier" typevar to be @@ -635,15 +635,15 @@ the constraint, and the other the bound. But we display the result the same way ```py def f[S, T](): # revealed: ty_extensions.ConstraintSet[(S@f = T@f)] - reveal_type(range_constraint(T, S, T)) + reveal_type(ConstraintSet.range(T, S, T)) # revealed: ty_extensions.ConstraintSet[(S@f = T@f)] - reveal_type(range_constraint(S, T, S)) + reveal_type(ConstraintSet.range(S, T, S)) def f[T, S](): # revealed: ty_extensions.ConstraintSet[(S@f = T@f)] - reveal_type(range_constraint(T, S, T)) + reveal_type(ConstraintSet.range(T, S, T)) # revealed: ty_extensions.ConstraintSet[(S@f = T@f)] - reveal_type(range_constraint(S, T, S)) + reveal_type(ConstraintSet.range(S, T, S)) ``` But in the case of `S ≤ T ≤ U`, we end up with an ambiguity. Depending on the typevar ordering, that @@ -654,7 +654,7 @@ def f[S, T, U](): # Could be either of: # ty_extensions.ConstraintSet[(S@f ≤ T@f ≤ U@f)] # ty_extensions.ConstraintSet[(S@f ≤ T@f) ∧ (T@f ≤ U@f)] - # reveal_type(range_constraint(S, T, U)) + # reveal_type(ConstraintSet.range(S, T, U)) ... ``` @@ -668,13 +668,13 @@ This section contains several examples that show that we simplify the DNF formul before displaying it. ```py -from ty_extensions import range_constraint +from ty_extensions import ConstraintSet def f[T, U](): - t1 = range_constraint(str, T, str) - t2 = range_constraint(bool, T, bool) - u1 = range_constraint(str, U, str) - u2 = range_constraint(bool, U, bool) + t1 = ConstraintSet.range(str, T, str) + t2 = ConstraintSet.range(bool, T, bool) + u1 = ConstraintSet.range(str, U, str) + u2 = ConstraintSet.range(bool, U, bool) # revealed: ty_extensions.ConstraintSet[(T@f = bool) ∨ (T@f = str)] reveal_type(t1 | t2) @@ -692,8 +692,8 @@ from typing import Never from ty_extensions import static_assert def f[T](): - t_int = range_constraint(Never, T, int) - t_bool = range_constraint(Never, T, bool) + t_int = ConstraintSet.range(Never, T, int) + t_bool = ConstraintSet.range(Never, T, bool) # `T ≤ bool` implies `T ≤ int`: if a type satisfies the former, it must always satisfy the # latter. We can turn that into a constraint set, using the equivalence `p → q == ¬p ∨ q`: @@ -707,7 +707,7 @@ def f[T](): # "domain", which maps valid inputs to `true` and invalid inputs to `false`. This means that two # constraint sets that are both always satisfied will not be identical if they have different # domains! - always = range_constraint(Never, T, object) + always = ConstraintSet.range(Never, T, object) # revealed: ty_extensions.ConstraintSet[always] reveal_type(always) static_assert(always) @@ -721,11 +721,11 @@ intersections whose elements appear in different orders. ```py from typing import Never -from ty_extensions import range_constraint +from ty_extensions import ConstraintSet def f[T](): # revealed: ty_extensions.ConstraintSet[(T@f ≤ int | str)] - reveal_type(range_constraint(Never, T, str | int)) + reveal_type(ConstraintSet.range(Never, T, str | int)) # revealed: ty_extensions.ConstraintSet[(T@f ≤ int | str)] - reveal_type(range_constraint(Never, T, int | str)) + reveal_type(ConstraintSet.range(Never, T, int | str)) ``` diff --git a/crates/ty_python_semantic/resources/mdtest/type_properties/is_subtype_of_given.md b/crates/ty_python_semantic/resources/mdtest/type_properties/implies_subtype_of.md similarity index 70% rename from crates/ty_python_semantic/resources/mdtest/type_properties/is_subtype_of_given.md rename to crates/ty_python_semantic/resources/mdtest/type_properties/implies_subtype_of.md index c1a0577fa3..7a276b6d2c 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_properties/is_subtype_of_given.md +++ b/crates/ty_python_semantic/resources/mdtest/type_properties/implies_subtype_of.md @@ -5,7 +5,7 @@ python-version = "3.12" ``` -This file tests the _constraint implication_ relationship between types, aka `is_subtype_of_given`, +This file tests the _constraint implication_ relationship between types, aka `implies_subtype_of`, which tests whether one type is a [subtype][subtyping] of another _assuming that the constraints in a particular constraint set hold_. @@ -16,14 +16,14 @@ fully static type that is not a typevar. It can _contain_ a typevar, though — considered concrete.) ```py -from ty_extensions import is_subtype_of, is_subtype_of_given, static_assert +from ty_extensions import ConstraintSet, is_subtype_of, static_assert def equivalent_to_other_relationships[T](): static_assert(is_subtype_of(bool, int)) - static_assert(is_subtype_of_given(True, bool, int)) + static_assert(ConstraintSet.always().implies_subtype_of(bool, int)) static_assert(not is_subtype_of(bool, str)) - static_assert(not is_subtype_of_given(True, bool, str)) + static_assert(not ConstraintSet.always().implies_subtype_of(bool, str)) ``` Moreover, for concrete types, the answer does not depend on which constraint set we are considering. @@ -32,16 +32,16 @@ there isn't a valid specialization for the typevars we are considering. ```py from typing import Never -from ty_extensions import range_constraint +from ty_extensions import ConstraintSet def even_given_constraints[T](): - constraints = range_constraint(Never, T, int) - static_assert(is_subtype_of_given(constraints, bool, int)) - static_assert(not is_subtype_of_given(constraints, bool, str)) + constraints = ConstraintSet.range(Never, T, int) + static_assert(constraints.implies_subtype_of(bool, int)) + static_assert(not constraints.implies_subtype_of(bool, str)) def even_given_unsatisfiable_constraints(): - static_assert(is_subtype_of_given(False, bool, int)) - static_assert(not is_subtype_of_given(False, bool, str)) + static_assert(ConstraintSet.never().implies_subtype_of(bool, int)) + static_assert(not ConstraintSet.never().implies_subtype_of(bool, str)) ``` ## Type variables @@ -141,37 +141,37 @@ considering. ```py from typing import Never -from ty_extensions import is_subtype_of_given, range_constraint, static_assert +from ty_extensions import ConstraintSet, static_assert def given_constraints[T](): - static_assert(not is_subtype_of_given(True, T, int)) - static_assert(not is_subtype_of_given(True, T, bool)) - static_assert(not is_subtype_of_given(True, T, str)) + static_assert(not ConstraintSet.always().implies_subtype_of(T, int)) + static_assert(not ConstraintSet.always().implies_subtype_of(T, bool)) + static_assert(not ConstraintSet.always().implies_subtype_of(T, str)) # These are vacuously true; false implies anything - static_assert(is_subtype_of_given(False, T, int)) - static_assert(is_subtype_of_given(False, T, bool)) - static_assert(is_subtype_of_given(False, T, str)) + static_assert(ConstraintSet.never().implies_subtype_of(T, int)) + static_assert(ConstraintSet.never().implies_subtype_of(T, bool)) + static_assert(ConstraintSet.never().implies_subtype_of(T, str)) - given_int = range_constraint(Never, T, int) - static_assert(is_subtype_of_given(given_int, T, int)) - static_assert(not is_subtype_of_given(given_int, T, bool)) - static_assert(not is_subtype_of_given(given_int, T, str)) + given_int = ConstraintSet.range(Never, T, int) + static_assert(given_int.implies_subtype_of(T, int)) + static_assert(not given_int.implies_subtype_of(T, bool)) + static_assert(not given_int.implies_subtype_of(T, str)) - given_bool = range_constraint(Never, T, bool) - static_assert(is_subtype_of_given(given_bool, T, int)) - static_assert(is_subtype_of_given(given_bool, T, bool)) - static_assert(not is_subtype_of_given(given_bool, T, str)) + given_bool = ConstraintSet.range(Never, T, bool) + static_assert(given_bool.implies_subtype_of(T, int)) + static_assert(given_bool.implies_subtype_of(T, bool)) + static_assert(not given_bool.implies_subtype_of(T, str)) given_both = given_bool & given_int - static_assert(is_subtype_of_given(given_both, T, int)) - static_assert(is_subtype_of_given(given_both, T, bool)) - static_assert(not is_subtype_of_given(given_both, T, str)) + static_assert(given_both.implies_subtype_of(T, int)) + static_assert(given_both.implies_subtype_of(T, bool)) + static_assert(not given_both.implies_subtype_of(T, str)) - given_str = range_constraint(Never, T, str) - static_assert(not is_subtype_of_given(given_str, T, int)) - static_assert(not is_subtype_of_given(given_str, T, bool)) - static_assert(is_subtype_of_given(given_str, T, str)) + given_str = ConstraintSet.range(Never, T, str) + static_assert(not given_str.implies_subtype_of(T, int)) + static_assert(not given_str.implies_subtype_of(T, bool)) + static_assert(given_str.implies_subtype_of(T, str)) ``` This might require propagating constraints from other typevars. @@ -179,20 +179,20 @@ This might require propagating constraints from other typevars. ```py def mutually_constrained[T, U](): # If [T = U ∧ U ≤ int], then [T ≤ int] must be true as well. - given_int = range_constraint(U, T, U) & range_constraint(Never, U, int) + given_int = ConstraintSet.range(U, T, U) & ConstraintSet.range(Never, U, int) # TODO: no static-assert-error # error: [static-assert-error] - static_assert(is_subtype_of_given(given_int, T, int)) - static_assert(not is_subtype_of_given(given_int, T, bool)) - static_assert(not is_subtype_of_given(given_int, T, str)) + static_assert(given_int.implies_subtype_of(T, int)) + static_assert(not given_int.implies_subtype_of(T, bool)) + static_assert(not given_int.implies_subtype_of(T, str)) # If [T ≤ U ∧ U ≤ int], then [T ≤ int] must be true as well. - given_int = range_constraint(Never, T, U) & range_constraint(Never, U, int) + given_int = ConstraintSet.range(Never, T, U) & ConstraintSet.range(Never, U, int) # TODO: no static-assert-error # error: [static-assert-error] - static_assert(is_subtype_of_given(given_int, T, int)) - static_assert(not is_subtype_of_given(given_int, T, bool)) - static_assert(not is_subtype_of_given(given_int, T, str)) + static_assert(given_int.implies_subtype_of(T, int)) + static_assert(not given_int.implies_subtype_of(T, bool)) + static_assert(not given_int.implies_subtype_of(T, str)) ``` [subtyping]: https://typing.python.org/en/latest/spec/concepts.html#subtype-supertype-and-type-equivalence diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 8f87593986..5efb65d289 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -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)), + ))) + } } } } diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index d1aa621b82..81ed3fed50 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -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)), diff --git a/crates/ty_python_semantic/src/types/constraints.rs b/crates/ty_python_semantic/src/types/constraints.rs index b5e3cc3e7d..d7aee0eb6f 100644 --- a/crates/ty_python_semantic/src/types/constraints.rs +++ b/crates/ty_python_semantic/src/types/constraints.rs @@ -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) } diff --git a/crates/ty_python_semantic/src/types/display.rs b/crates/ty_python_semantic/src/types/display.rs index 560943aba1..7748dd3ab5 100644 --- a/crates/ty_python_semantic/src/types/display.rs +++ b/crates/ty_python_semantic/src/types/display.rs @@ -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"), diff --git a/crates/ty_python_semantic/src/types/function.rs b/crates/ty_python_semantic/src/types/function.rs index 9aae4aef02..459edb6c25 100644 --- a/crates/ty_python_semantic/src/types/function.rs +++ b/crates/ty_python_semantic/src/types/function.rs @@ -81,9 +81,9 @@ use crate::types::visitor::any_over_type; use crate::types::{ ApplyTypeMappingVisitor, BoundMethodType, BoundTypeVarInstance, CallableType, ClassBase, ClassLiteral, ClassType, DeprecatedInstance, DynamicType, FindLegacyTypeVarsVisitor, - HasRelationToVisitor, 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, diff --git a/crates/ty_vendored/ty_extensions/ty_extensions.pyi b/crates/ty_vendored/ty_extensions/ty_extensions.pyi index be9b4ebff4..79cda64bef 100644 --- a/crates/ty_vendored/ty_extensions/ty_extensions.pyi +++ b/crates/ty_vendored/ty_extensions/ty_extensions.pyi @@ -44,6 +44,29 @@ type JustComplex = TypeOf[1.0j] # Constraints class ConstraintSet: + @staticmethod + def range(lower_bound: Any, typevar: Any, upper_bound: Any) -> Self: + """ + Returns a constraint set that requires `typevar` to specialize to a type + that is a supertype of `lower_bound` and a subtype of `upper_bound`. + """ + + @staticmethod + def always() -> Self: + """Returns a constraint set that is always satisfied""" + + @staticmethod + def never() -> Self: + """Returns a constraint set that is never satisfied""" + + def implies_subtype_of(self, ty: Any, of: Any) -> Self: + """ + Returns a constraint set that is satisfied when `ty` is a `subtype`_ of + `of`, assuming that all of the constraints in `self` hold. + + .. _subtype: https://typing.python.org/en/latest/spec/concepts.html#subtype-supertype-and-type-equivalence + """ + def __bool__(self) -> bool: ... def __eq__(self, other: ConstraintSet) -> bool: ... def __ne__(self, other: ConstraintSet) -> bool: ... @@ -51,13 +74,6 @@ class ConstraintSet: def __or__(self, other: ConstraintSet) -> ConstraintSet: ... def __invert__(self) -> ConstraintSet: ... -def range_constraint( - lower_bound: Any, typevar: Any, upper_bound: Any -) -> ConstraintSet: ... -def negated_range_constraint( - lower_bound: Any, typevar: Any, upper_bound: Any -) -> ConstraintSet: ... - # Predicates on types # # Ideally, these would be annotated using `TypeForm`, but that has not been @@ -75,15 +91,6 @@ def is_subtype_of(ty: Any, of: Any) -> ConstraintSet: .. _subtype: https://typing.python.org/en/latest/spec/concepts.html#subtype-supertype-and-type-equivalence """ -def is_subtype_of_given( - constraints: bool | ConstraintSet, ty: Any, of: Any -) -> ConstraintSet: - """Returns a constraint set that is satisfied when `ty` is a `subtype`_ of `of`, - assuming that all of the constraints in `constraints` hold. - - .. _subtype: https://typing.python.org/en/latest/spec/concepts.html#subtype-supertype-and-type-equivalence - """ - def is_assignable_to(ty: Any, to: Any) -> ConstraintSet: """Returns a constraint set that is satisfied when `ty` is `assignable`_ to `to`.