mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 05:14:52 +00:00
[ty] More constraint set simplifications via simpler constraint representation (#20423)
Previously, we used a very fine-grained representation for individual constraints: each constraint was _either_ a range constraint, a not-equivalent constraint, or an incomparable constraint. These three pieces are enough to represent all of the "real" constraints we need to create — range constraints and their negation. However, it meant that we weren't picking up as many chances to simplify constraint sets as we could. Our simplification logic depends on being able to look at _pairs_ of constraints or clauses to see if they simplify relative to each other. With our fine-grained representation, we could easily encounter situations that we should have been able to simplify, but that would require looking at three or more individual constraints. For instance, negating a range constraint would produce: ``` ¬(Base ≤ T ≤ Super) = ((T ≤ Base) ∧ (T ≠ Base)) ∨ (T ≁ Base) ∨ ((Super ≤ T) ∧ (T ≠ Super)) ∨ (T ≁ Super) ``` That is, `T` must be (strictly) less than `Base`, (strictly) greater than `Super`, or incomparable to either. If we tried to union those back together, we should get `always`, since `x ∨ ¬x` should always be true, no matter what `x` is. But instead we would get: ``` (Base ≤ T ≤ Super) ∨ ((T ≤ Base) ∧ (T ≠ Base)) ∨ (T ≁ Base) ∨ ((Super ≤ T) ∧ (T ≠ Super)) ∨ (T ≁ Super) ``` Nothing would simplify relative to each other, because we'd have to look at all five union elements to see that together they do in fact combine to `always`. The fine-grained representation was nice, because it made it easier to [work out the math](https://dcreager.net/theory/constraints/) for intersections and unions of each kind of constraint. But being able to simplify is more important, since the example above comes up immediately in #20093 when trying to handle constrained typevars. The fix in this PR is to go back to a more coarse-grained representation, where each individual constraint consists of a positive range (which might be `always` / `Never ≤ T ≤ object`), and zero or more negative ranges. The intuition is to think of a constraint as a region of the type space (representable as a range) with zero or more "holes" removed from it. With this representation, negating a range constraint produces: ``` ¬(Base ≤ T ≤ Super) = (always ∧ ¬(Base ≤ T ≤ Super)) ``` (That looks trivial, because it is! We just move the positive range to the negative side.) The math is not that much harder than before, because there are only three combinations to consider (each for intersection and union) — though the fact that there can be multiple holes in a constraint does require some nested loops. But the mdtest suite gives me confidence that this is not introducing any new issues, and it definitely removes a troublesome TODO. (As an aside, this change also means that we are back to having each clause contain no more than one individual constraint for any typevar. This turned out to be important, because part of our simplification logic was also depending on that!) --------- Co-authored-by: Carl Meyer <carl@astral.sh>
This commit is contained in:
parent
0d424d8e78
commit
1f46c18921
5 changed files with 752 additions and 999 deletions
|
@ -91,7 +91,7 @@ that specific type.
|
|||
|
||||
```py
|
||||
def _[T]() -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[(Base ≤ T@_ ≤ Base)]
|
||||
# revealed: ty_extensions.ConstraintSet[(T@_ = Base)]
|
||||
reveal_type(range_constraint(Base, T, Base))
|
||||
```
|
||||
|
||||
|
@ -111,88 +111,89 @@ def _[T]() -> None:
|
|||
reveal_type(range_constraint(Sequence[Any], T, Sequence[Base]))
|
||||
```
|
||||
|
||||
### Not equivalent
|
||||
### Negated range
|
||||
|
||||
A _not-equivalent_ constraint requires the typevar to specialize to anything _other_ than a
|
||||
particular type (the "hole").
|
||||
A _negated range_ constraint is the opposite of a range constraint: it requires the typevar to _not_
|
||||
be within a particular lower and upper bound. The typevar can only specialize to a type that is a
|
||||
strict subtype of the lower bound, a strict supertype of the upper bound, or incomparable to either.
|
||||
|
||||
```py
|
||||
from typing import Any, Never, Sequence
|
||||
from ty_extensions import not_equivalent_constraint
|
||||
from typing import Any, final, Never, Sequence
|
||||
from ty_extensions import negated_range_constraint
|
||||
|
||||
class Base: ...
|
||||
class Super: ...
|
||||
class Base(Super): ...
|
||||
class Sub(Base): ...
|
||||
|
||||
@final
|
||||
class Unrelated: ...
|
||||
|
||||
def _[T]() -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[¬(Sub ≤ T@_ ≤ Super)]
|
||||
reveal_type(negated_range_constraint(Sub, T, Super))
|
||||
```
|
||||
|
||||
Every type is a supertype of `Never`, so a lower bound of `Never` is the same as having no lower
|
||||
bound.
|
||||
|
||||
```py
|
||||
def _[T]() -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[¬(T@_ ≤ Base)]
|
||||
reveal_type(negated_range_constraint(Never, T, Base))
|
||||
```
|
||||
|
||||
Similarly, every type is a subtype of `object`, so an upper bound of `object` is the same as having
|
||||
no upper bound.
|
||||
|
||||
```py
|
||||
def _[T]() -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[¬(Base ≤ T@_)]
|
||||
reveal_type(negated_range_constraint(Base, T, object))
|
||||
```
|
||||
|
||||
And a negated range constraint with _both_ a lower bound of `Never` and an upper bound of `object`
|
||||
cannot be satisfied at all.
|
||||
|
||||
```py
|
||||
def _[T]() -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(negated_range_constraint(Never, T, object))
|
||||
```
|
||||
|
||||
If the lower bound and upper bounds are "inverted" (the upper bound is a subtype of the lower bound)
|
||||
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))
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(negated_range_constraint(Base, T, Unrelated))
|
||||
```
|
||||
|
||||
The lower and upper bound can be the same type, in which case the typevar can be specialized to any
|
||||
type other than that specific type.
|
||||
|
||||
```py
|
||||
def _[T]() -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[(T@_ ≠ Base)]
|
||||
reveal_type(not_equivalent_constraint(T, Base))
|
||||
reveal_type(negated_range_constraint(Base, T, Base))
|
||||
```
|
||||
|
||||
Unlike range constraints, `Never` and `object` are not special when used as the hole of a
|
||||
not-equivalent constraint — there are many types that are not equivalent to `Never` or `object`.
|
||||
Constraints can only refer to fully static types, so the lower and upper bounds are transformed into
|
||||
their bottom and top materializations, respectively.
|
||||
|
||||
```py
|
||||
def _[T]() -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[(T@_ ≠ Never)]
|
||||
reveal_type(not_equivalent_constraint(T, Never))
|
||||
# revealed: ty_extensions.ConstraintSet[¬(Base ≤ T@_)]
|
||||
reveal_type(negated_range_constraint(Base, T, Any))
|
||||
# revealed: ty_extensions.ConstraintSet[¬(Sequence[Base] ≤ T@_ ≤ Sequence[object])]
|
||||
reveal_type(negated_range_constraint(Sequence[Base], T, Sequence[Any]))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[(T@_ ≠ object)]
|
||||
reveal_type(not_equivalent_constraint(T, object))
|
||||
```
|
||||
|
||||
Constraints can only refer to fully static types. However, not-equivalent constraints are not
|
||||
created directly; they are only created when negating a range constraint. Since that range
|
||||
constraint will have fully static lower and upper bounds, the not-equivalent constraints that we
|
||||
create will already have a fully static hole. Therefore, we raise a diagnostic when calling the
|
||||
internal `not_equivalent_constraint` constructor with a non-fully-static type.
|
||||
|
||||
```py
|
||||
def _[T]() -> None:
|
||||
# error: [invalid-argument-type] "Not-equivalent constraint must have a fully static type"
|
||||
reveal_type(not_equivalent_constraint(T, Any)) # revealed: ConstraintSet
|
||||
# error: [invalid-argument-type] "Not-equivalent constraint must have a fully static type"
|
||||
reveal_type(not_equivalent_constraint(T, Sequence[Any])) # revealed: ConstraintSet
|
||||
```
|
||||
|
||||
### Incomparable
|
||||
|
||||
An _incomparable_ constraint requires the typevar to specialize to any type that is neither a
|
||||
subtype nor a supertype of a particular type (the "pivot").
|
||||
|
||||
```py
|
||||
from typing import Any, Never, Sequence
|
||||
from ty_extensions import incomparable_constraint
|
||||
|
||||
class Base: ...
|
||||
|
||||
def _[T]() -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[(T@_ ≁ Base)]
|
||||
reveal_type(incomparable_constraint(T, Base))
|
||||
```
|
||||
|
||||
Every type is comparable with `Never` and with `object`, so an incomparable constraint with either
|
||||
as a pivot cannot ever be satisfied.
|
||||
|
||||
```py
|
||||
def _[T]() -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(incomparable_constraint(T, Never))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(incomparable_constraint(T, object))
|
||||
```
|
||||
|
||||
Constraints can only refer to fully static types. However, incomparable constraints are not created
|
||||
directly; they are only created when negating a range constraint. Since that range constraint will
|
||||
have fully static lower and upper bounds, the incomparable constraints that we create will already
|
||||
have a fully static hole. Therefore, we raise a diagnostic when calling the internal
|
||||
`incomparable_constraint` constructor with a non-fully-static type.
|
||||
|
||||
```py
|
||||
def _[T]() -> None:
|
||||
# error: [invalid-argument-type] "Incomparable constraint must have a fully static type"
|
||||
reveal_type(incomparable_constraint(T, Any)) # revealed: ConstraintSet
|
||||
# error: [invalid-argument-type] "Incomparable constraint must have a fully static type"
|
||||
reveal_type(incomparable_constraint(T, Sequence[Any])) # revealed: ConstraintSet
|
||||
# revealed: ty_extensions.ConstraintSet[¬(T@_ ≤ Base)]
|
||||
reveal_type(negated_range_constraint(Any, T, Base))
|
||||
# revealed: ty_extensions.ConstraintSet[¬(Sequence[Never] ≤ T@_ ≤ Sequence[Base])]
|
||||
reveal_type(negated_range_constraint(Sequence[Any], T, Sequence[Base]))
|
||||
```
|
||||
|
||||
## Intersection
|
||||
|
@ -203,7 +204,7 @@ cases, we can simplify the result of an intersection.
|
|||
### Different typevars
|
||||
|
||||
```py
|
||||
from ty_extensions import incomparable_constraint, not_equivalent_constraint, range_constraint
|
||||
from ty_extensions import range_constraint, negated_range_constraint
|
||||
|
||||
class Super: ...
|
||||
class Base(Super): ...
|
||||
|
@ -214,20 +215,10 @@ We cannot simplify the intersection of constraints that refer to different typev
|
|||
|
||||
```py
|
||||
def _[T, U]() -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[((T@_ ≁ Base) ∧ (U@_ ≁ Base))]
|
||||
reveal_type(incomparable_constraint(T, Base) & incomparable_constraint(U, Base))
|
||||
# revealed: ty_extensions.ConstraintSet[((T@_ ≠ Base) ∧ (U@_ ≠ Base))]
|
||||
reveal_type(not_equivalent_constraint(T, Base) & not_equivalent_constraint(U, Base))
|
||||
# revealed: ty_extensions.ConstraintSet[((Sub ≤ T@_ ≤ Base) ∧ (Sub ≤ U@_ ≤ Base))]
|
||||
reveal_type(range_constraint(Sub, T, Base) & range_constraint(Sub, U, Base))
|
||||
```
|
||||
|
||||
Intersection is reflexive.
|
||||
|
||||
```py
|
||||
def _[T]() -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[(T@_ ≁ Base)]
|
||||
reveal_type(incomparable_constraint(T, Base) & incomparable_constraint(T, 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))
|
||||
```
|
||||
|
||||
### Intersection of two ranges
|
||||
|
@ -251,7 +242,7 @@ def _[T]() -> None:
|
|||
reveal_type(range_constraint(SubSub, T, Base) & range_constraint(Sub, T, Super))
|
||||
# revealed: ty_extensions.ConstraintSet[(Sub ≤ T@_ ≤ Base)]
|
||||
reveal_type(range_constraint(SubSub, T, Super) & range_constraint(Sub, T, Base))
|
||||
# revealed: ty_extensions.ConstraintSet[(Base ≤ T@_ ≤ Base)]
|
||||
# revealed: ty_extensions.ConstraintSet[(T@_ = Base)]
|
||||
reveal_type(range_constraint(Sub, T, Base) & range_constraint(Base, T, Super))
|
||||
# revealed: ty_extensions.ConstraintSet[(Sub ≤ T@_ ≤ Super)]
|
||||
reveal_type(range_constraint(Sub, T, Super) & range_constraint(Sub, T, Super))
|
||||
|
@ -267,180 +258,110 @@ def _[T]() -> None:
|
|||
reveal_type(range_constraint(SubSub, T, Sub) & range_constraint(Unrelated, T, object))
|
||||
```
|
||||
|
||||
### Intersection of range and not-equivalent
|
||||
### Intersection of a range and a negated range
|
||||
|
||||
If the hole of a not-equivalent constraint is within the lower and upper bounds of a range
|
||||
constraint, the intersection "removes" the hole from the range. The intersection cannot be
|
||||
simplified.
|
||||
The bounds of the range constraint provide a range of types that should be included; the bounds of
|
||||
the negated range constraint provide a "hole" of types that should not be included. We can think of
|
||||
the intersection as removing the hole from the range constraint.
|
||||
|
||||
```py
|
||||
from typing import final
|
||||
from ty_extensions import not_equivalent_constraint, range_constraint
|
||||
from typing import final, Never
|
||||
from ty_extensions import range_constraint, negated_range_constraint
|
||||
|
||||
class Super: ...
|
||||
class Base(Super): ...
|
||||
class Sub(Base): ...
|
||||
class SubSub(Sub): ...
|
||||
|
||||
@final
|
||||
class Unrelated: ...
|
||||
|
||||
def _[T]() -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[((Sub ≤ T@_ ≤ Super) ∧ (T@_ ≠ Base))]
|
||||
reveal_type(range_constraint(Sub, T, Super) & not_equivalent_constraint(T, Base))
|
||||
```
|
||||
|
||||
If the hole is not within the lower and upper bounds (because it's a subtype of the lower bound, a
|
||||
supertype of the upper bound, or not comparable with either), then removing the hole doesn't do
|
||||
anything.
|
||||
If the negative range completely contains the positive range, then the intersection is empty.
|
||||
|
||||
```py
|
||||
def _[T]() -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(range_constraint(Sub, T, Base) & negated_range_constraint(SubSub, T, Super))
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(range_constraint(Sub, T, Base) & negated_range_constraint(Sub, T, Base))
|
||||
```
|
||||
|
||||
If the negative range is disjoint from the positive range, the negative range doesn't remove
|
||||
anything; the intersection is the positive range.
|
||||
|
||||
```py
|
||||
def _[T]() -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[(Base ≤ T@_ ≤ Super)]
|
||||
reveal_type(range_constraint(Base, T, Super) & not_equivalent_constraint(T, Sub))
|
||||
# revealed: ty_extensions.ConstraintSet[(Base ≤ T@_ ≤ Super)]
|
||||
reveal_type(range_constraint(Base, T, Super) & not_equivalent_constraint(T, Unrelated))
|
||||
# revealed: ty_extensions.ConstraintSet[(Sub ≤ T@_ ≤ Base)]
|
||||
reveal_type(range_constraint(Sub, T, Base) & not_equivalent_constraint(T, Super))
|
||||
# revealed: ty_extensions.ConstraintSet[(Sub ≤ T@_ ≤ Base)]
|
||||
reveal_type(range_constraint(Sub, T, Base) & not_equivalent_constraint(T, Unrelated))
|
||||
reveal_type(range_constraint(Sub, T, Base) & negated_range_constraint(Never, T, Unrelated))
|
||||
# revealed: ty_extensions.ConstraintSet[(SubSub ≤ T@_ ≤ Sub)]
|
||||
reveal_type(range_constraint(SubSub, T, Sub) & negated_range_constraint(Base, T, Super))
|
||||
# revealed: ty_extensions.ConstraintSet[(Base ≤ T@_ ≤ Super)]
|
||||
reveal_type(range_constraint(Base, T, Super) & negated_range_constraint(SubSub, T, Sub))
|
||||
```
|
||||
|
||||
If the lower and upper bounds are the same, it's actually an "equivalent" constraint. If the hole is
|
||||
also that same type, then the intersection is empty — the not-equivalent constraint removes the only
|
||||
type that satisfies the range constraint.
|
||||
Otherwise we clip the negative constraint to the mininum range that overlaps with the positive
|
||||
range.
|
||||
|
||||
```py
|
||||
def _[T]() -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(range_constraint(Base, T, Base) & not_equivalent_constraint(T, Base))
|
||||
# revealed: ty_extensions.ConstraintSet[((SubSub ≤ T@_ ≤ Base) ∧ ¬(Sub ≤ T@_ ≤ Base))]
|
||||
reveal_type(range_constraint(SubSub, T, Base) & negated_range_constraint(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))
|
||||
```
|
||||
|
||||
### Intersection of range and incomparable
|
||||
### Intersection of two negated ranges
|
||||
|
||||
The intersection of a range constraint and an incomparable constraint cannot be satisfied if the
|
||||
pivot is a subtype of the lower bound, or a supertype of the upper bound. (If the pivot is a subtype
|
||||
of the lower bound, then by transitivity, the pivot is also a subtype of everything in the range.)
|
||||
When one of the bounds is entirely contained within the other, the intersection simplifies to the
|
||||
smaller constraint. For negated ranges, the smaller constraint is the one with the larger "hole".
|
||||
|
||||
```py
|
||||
from typing import final
|
||||
from ty_extensions import incomparable_constraint, range_constraint
|
||||
from ty_extensions import negated_range_constraint
|
||||
|
||||
class Super: ...
|
||||
class Base(Super): ...
|
||||
class Sub(Base): ...
|
||||
class SubSub(Sub): ...
|
||||
|
||||
@final
|
||||
class Unrelated: ...
|
||||
|
||||
def _[T]() -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(range_constraint(Base, T, Super) & incomparable_constraint(T, Sub))
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(range_constraint(Base, T, Super) & incomparable_constraint(T, Base))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(range_constraint(Sub, T, Base) & incomparable_constraint(T, Super))
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(range_constraint(Sub, T, Base) & incomparable_constraint(T, Base))
|
||||
# revealed: ty_extensions.ConstraintSet[¬(SubSub ≤ T@_ ≤ Super)]
|
||||
reveal_type(negated_range_constraint(SubSub, T, Super) & negated_range_constraint(Sub, T, Base))
|
||||
# revealed: ty_extensions.ConstraintSet[¬(Sub ≤ T@_ ≤ Super)]
|
||||
reveal_type(negated_range_constraint(Sub, T, Super) & negated_range_constraint(Sub, T, Super))
|
||||
```
|
||||
|
||||
Otherwise, the intersection cannot be simplified.
|
||||
Otherwise, the union cannot be simplified.
|
||||
|
||||
```py
|
||||
from ty_extensions import is_subtype_of
|
||||
|
||||
def _[T]() -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[((Base ≤ T@_ ≤ Super) ∧ (T@_ ≁ Unrelated))]
|
||||
reveal_type(range_constraint(Base, T, Super) & incomparable_constraint(T, Unrelated))
|
||||
# revealed: ty_extensions.ConstraintSet[(¬(Sub ≤ T@_ ≤ Base) ∧ ¬(Base ≤ T@_ ≤ Super))]
|
||||
reveal_type(negated_range_constraint(Sub, T, Base) & negated_range_constraint(Base, T, Super))
|
||||
# revealed: ty_extensions.ConstraintSet[(¬(SubSub ≤ T@_ ≤ Sub) ∧ ¬(Base ≤ T@_ ≤ Super))]
|
||||
reveal_type(negated_range_constraint(SubSub, T, Sub) & negated_range_constraint(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))
|
||||
```
|
||||
|
||||
### Intersection of two not-equivalents
|
||||
|
||||
Intersection is reflexive, so the intersection of a not-equivalent constraint with itself is itself.
|
||||
In particular, the following does not simplify, even though it seems like it could simplify to
|
||||
`¬(SubSub ≤ T@_ ≤ Super)`. The issue is that there are types that are within the bounds of
|
||||
`SubSub ≤ T@_ ≤ Super`, but which are not comparable to `Base` or `Sub`, and which therefore should
|
||||
be included in the union. An example would be the type that contains all instances of `Super`,
|
||||
`Base`, and `SubSub` (but _not_ including instances of `Sub`). (We don't have a way to spell that
|
||||
type at the moment, but it is a valid type.) That type is not in `SubSub ≤ T ≤ Base`, since it
|
||||
includes `Super`, which is outside the range. It's also not in `Sub ≤ T ≤ Super`, because it does
|
||||
not include `Sub`. That means it should be in the union. (Remember that for negated range
|
||||
constraints, the lower and upper bounds define the "hole" of types that are _not_ allowed.) Since
|
||||
that type _is_ in `SubSub ≤ T ≤ Super`, it is not correct to simplify the union in this way.
|
||||
|
||||
```py
|
||||
from typing import final
|
||||
from ty_extensions import not_equivalent_constraint
|
||||
|
||||
class Super: ...
|
||||
class Base(Super): ...
|
||||
class Sub(Base): ...
|
||||
|
||||
@final
|
||||
class Unrelated: ...
|
||||
|
||||
def _[T]() -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[(T@_ ≠ Base)]
|
||||
reveal_type(not_equivalent_constraint(T, Base) & not_equivalent_constraint(T, Base))
|
||||
# revealed: ty_extensions.ConstraintSet[((T@_ ≠ Base) ∧ (T@_ ≠ Super))]
|
||||
reveal_type(not_equivalent_constraint(T, Base) & not_equivalent_constraint(T, Super))
|
||||
# revealed: ty_extensions.ConstraintSet[((T@_ ≠ Base) ∧ (T@_ ≠ Sub))]
|
||||
reveal_type(not_equivalent_constraint(T, Base) & not_equivalent_constraint(T, Sub))
|
||||
# revealed: ty_extensions.ConstraintSet[((T@_ ≠ Base) ∧ (T@_ ≠ Unrelated))]
|
||||
reveal_type(not_equivalent_constraint(T, Base) & not_equivalent_constraint(T, Unrelated))
|
||||
```
|
||||
|
||||
### Intersection of not-equivalent and incomparable
|
||||
|
||||
When intersecting a not-equivalent constraint and an incomparable constraint, if the hole and pivot
|
||||
are comparable, then the incomparable constraint already excludes the hole, so removing the hole
|
||||
doesn't do anything.
|
||||
|
||||
```py
|
||||
from typing import final
|
||||
from ty_extensions import incomparable_constraint, not_equivalent_constraint
|
||||
|
||||
class Super: ...
|
||||
class Base(Super): ...
|
||||
class Sub(Base): ...
|
||||
|
||||
@final
|
||||
class Unrelated: ...
|
||||
|
||||
def _[T]() -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[(T@_ ≁ Base)]
|
||||
reveal_type(not_equivalent_constraint(T, Super) & incomparable_constraint(T, Base))
|
||||
# revealed: ty_extensions.ConstraintSet[(T@_ ≁ Base)]
|
||||
reveal_type(not_equivalent_constraint(T, Base) & incomparable_constraint(T, Base))
|
||||
# revealed: ty_extensions.ConstraintSet[(T@_ ≁ Base)]
|
||||
reveal_type(not_equivalent_constraint(T, Sub) & incomparable_constraint(T, Base))
|
||||
# revealed: ty_extensions.ConstraintSet[((T@_ ≠ Unrelated) ∧ (T@_ ≁ Base))]
|
||||
reveal_type(not_equivalent_constraint(T, Unrelated) & incomparable_constraint(T, Base))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[(T@_ ≁ Super)]
|
||||
reveal_type(not_equivalent_constraint(T, Base) & incomparable_constraint(T, Super))
|
||||
# revealed: ty_extensions.ConstraintSet[(T@_ ≁ Base)]
|
||||
reveal_type(not_equivalent_constraint(T, Base) & incomparable_constraint(T, Base))
|
||||
# revealed: ty_extensions.ConstraintSet[(T@_ ≁ Sub)]
|
||||
reveal_type(not_equivalent_constraint(T, Base) & incomparable_constraint(T, Sub))
|
||||
# revealed: ty_extensions.ConstraintSet[((T@_ ≠ Base) ∧ (T@_ ≁ Unrelated))]
|
||||
reveal_type(not_equivalent_constraint(T, Base) & incomparable_constraint(T, Unrelated))
|
||||
```
|
||||
|
||||
### Intersection of two incomparables
|
||||
|
||||
We can only simplify the intersection of two incomparable constraints if they have the same pivot.
|
||||
|
||||
```py
|
||||
from typing import final
|
||||
from ty_extensions import incomparable_constraint
|
||||
|
||||
class Super: ...
|
||||
class Base(Super): ...
|
||||
class Sub(Base): ...
|
||||
|
||||
@final
|
||||
class Unrelated: ...
|
||||
|
||||
def _[T]() -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[(T@_ ≁ Base)]
|
||||
reveal_type(incomparable_constraint(T, Base) & incomparable_constraint(T, Base))
|
||||
# revealed: ty_extensions.ConstraintSet[((T@_ ≁ Base) ∧ (T@_ ≁ Super))]
|
||||
reveal_type(incomparable_constraint(T, Base) & incomparable_constraint(T, Super))
|
||||
# revealed: ty_extensions.ConstraintSet[((T@_ ≁ Base) ∧ (T@_ ≁ Sub))]
|
||||
reveal_type(incomparable_constraint(T, Base) & incomparable_constraint(T, Sub))
|
||||
# revealed: ty_extensions.ConstraintSet[((T@_ ≁ Base) ∧ (T@_ ≁ Unrelated))]
|
||||
reveal_type(incomparable_constraint(T, Base) & incomparable_constraint(T, Unrelated))
|
||||
# revealed: ty_extensions.ConstraintSet[(¬(SubSub ≤ T@_ ≤ Base) ∧ ¬(Sub ≤ T@_ ≤ Super))]
|
||||
reveal_type(negated_range_constraint(SubSub, T, Base) & negated_range_constraint(Sub, T, Super))
|
||||
```
|
||||
|
||||
## Union
|
||||
|
@ -451,7 +372,7 @@ can simplify the result of an union.
|
|||
### Different typevars
|
||||
|
||||
```py
|
||||
from ty_extensions import incomparable_constraint, not_equivalent_constraint, range_constraint
|
||||
from ty_extensions import range_constraint, negated_range_constraint
|
||||
|
||||
class Super: ...
|
||||
class Base(Super): ...
|
||||
|
@ -462,20 +383,10 @@ We cannot simplify the union of constraints that refer to different typevars.
|
|||
|
||||
```py
|
||||
def _[T, U]() -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[(T@_ ≁ Base) ∨ (U@_ ≁ Base)]
|
||||
reveal_type(incomparable_constraint(T, Base) | incomparable_constraint(U, Base))
|
||||
# revealed: ty_extensions.ConstraintSet[(T@_ ≠ Base) ∨ (U@_ ≠ Base)]
|
||||
reveal_type(not_equivalent_constraint(T, Base) | not_equivalent_constraint(U, Base))
|
||||
# revealed: ty_extensions.ConstraintSet[(Sub ≤ T@_ ≤ Base) ∨ (Sub ≤ U@_ ≤ Base)]
|
||||
reveal_type(range_constraint(Sub, T, Base) | range_constraint(Sub, U, Base))
|
||||
```
|
||||
|
||||
Union is reflexive.
|
||||
|
||||
```py
|
||||
def _[T]() -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[(T@_ ≁ Base)]
|
||||
reveal_type(incomparable_constraint(T, Base) | incomparable_constraint(T, 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))
|
||||
```
|
||||
|
||||
### Union of two ranges
|
||||
|
@ -530,175 +441,100 @@ def _[T]() -> None:
|
|||
reveal_type(range_constraint(SubSub, T, Base) | range_constraint(Sub, T, Super))
|
||||
```
|
||||
|
||||
### Union of range and not-equivalent
|
||||
### Union of a range and a negated range
|
||||
|
||||
If the hole of a not-equivalent constraint is within the lower and upper bounds of a range
|
||||
constraint, the range "fills in" the hole. The resulting union can always be satisfied.
|
||||
The bounds of the range constraint provide a range of types that should be included; the bounds of
|
||||
the negated range constraint provide a "hole" of types that should not be included. We can think of
|
||||
the union as filling part of the hole with the types from the range constraint.
|
||||
|
||||
```py
|
||||
from typing import final
|
||||
from ty_extensions import not_equivalent_constraint, range_constraint
|
||||
|
||||
class Super: ...
|
||||
class Base(Super): ...
|
||||
class Sub(Base): ...
|
||||
|
||||
@final
|
||||
class Unrelated: ...
|
||||
|
||||
def _[T]() -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(range_constraint(Sub, T, Super) | not_equivalent_constraint(T, Base))
|
||||
```
|
||||
|
||||
Otherwise the range constraint is subsumed by the not-equivalent constraint.
|
||||
|
||||
```py
|
||||
def _[T]() -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[(T@_ ≠ Super)]
|
||||
reveal_type(range_constraint(Sub, T, Base) | not_equivalent_constraint(T, Super))
|
||||
```
|
||||
|
||||
### Union of range and incomparable
|
||||
|
||||
When the "pivot" of the incomparable constraint is not comparable with either bound of the range
|
||||
constraint, the incomparable constraint subsumes the range constraint.
|
||||
|
||||
```py
|
||||
from typing import final
|
||||
from ty_extensions import incomparable_constraint, range_constraint
|
||||
|
||||
class Super: ...
|
||||
class Base(Super): ...
|
||||
class Sub(Base): ...
|
||||
|
||||
@final
|
||||
class Unrelated: ...
|
||||
|
||||
def _[T]() -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[(T@_ ≁ Unrelated)]
|
||||
reveal_type(range_constraint(Base, T, Super) | incomparable_constraint(T, Unrelated))
|
||||
```
|
||||
|
||||
Otherwise, the union cannot be simplified.
|
||||
|
||||
```py
|
||||
from ty_extensions import is_subtype_of
|
||||
|
||||
def _[T]() -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[(Base ≤ T@_ ≤ Super) ∨ (T@_ ≁ Base)]
|
||||
reveal_type(range_constraint(Base, T, Super) | incomparable_constraint(T, Base))
|
||||
# revealed: ty_extensions.ConstraintSet[(Base ≤ T@_ ≤ Super) ∨ (T@_ ≁ Sub)]
|
||||
reveal_type(range_constraint(Base, T, Super) | incomparable_constraint(T, Sub))
|
||||
# revealed: ty_extensions.ConstraintSet[(Base ≤ T@_ ≤ Super) ∨ (T@_ ≁ Super)]
|
||||
reveal_type(range_constraint(Base, T, Super) | incomparable_constraint(T, Super))
|
||||
```
|
||||
|
||||
### Union of two not-equivalents
|
||||
|
||||
Union is reflexive, so the union of a not-equivalent constraint with itself is itself. The union of
|
||||
two different not-equivalent constraints is always satisfied.
|
||||
|
||||
```py
|
||||
from typing import final
|
||||
from ty_extensions import not_equivalent_constraint
|
||||
from typing import final, Never
|
||||
from ty_extensions import range_constraint, negated_range_constraint
|
||||
|
||||
class Super: ...
|
||||
class Base(Super): ...
|
||||
class Sub(Base): ...
|
||||
class SubSub(Sub): ...
|
||||
|
||||
@final
|
||||
class Unrelated: ...
|
||||
```
|
||||
|
||||
If the positive range completely contains the negative range, then the union is always satisfied.
|
||||
|
||||
```py
|
||||
def _[T]() -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(negated_range_constraint(Sub, T, Base) | range_constraint(SubSub, T, Super))
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(negated_range_constraint(Sub, T, Base) | range_constraint(Sub, T, Base))
|
||||
```
|
||||
|
||||
If the negative range is disjoint from the positive range, the positive range doesn't add anything;
|
||||
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))
|
||||
# revealed: ty_extensions.ConstraintSet[¬(SubSub ≤ T@_ ≤ Sub)]
|
||||
reveal_type(negated_range_constraint(SubSub, T, Sub) | range_constraint(Base, T, Super))
|
||||
# revealed: ty_extensions.ConstraintSet[¬(Base ≤ T@_ ≤ Super)]
|
||||
reveal_type(negated_range_constraint(Base, T, Super) | range_constraint(SubSub, T, Sub))
|
||||
```
|
||||
|
||||
Otherwise we clip the positive constraint to the mininum range that overlaps with the negative
|
||||
range.
|
||||
|
||||
```py
|
||||
def _[T]() -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[¬(SubSub ≤ T@_ ≤ Base) ∨ (Sub ≤ T@_ ≤ Base)]
|
||||
reveal_type(negated_range_constraint(SubSub, T, Base) | range_constraint(Sub, T, Super))
|
||||
# revealed: ty_extensions.ConstraintSet[¬(SubSub ≤ T@_ ≤ Super) ∨ (Sub ≤ T@_ ≤ Base)]
|
||||
reveal_type(negated_range_constraint(SubSub, T, Super) | range_constraint(Sub, T, Base))
|
||||
```
|
||||
|
||||
### Union of two negated ranges
|
||||
|
||||
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
|
||||
|
||||
class Super: ...
|
||||
class Base(Super): ...
|
||||
class Sub(Base): ...
|
||||
class SubSub(Sub): ...
|
||||
|
||||
@final
|
||||
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))
|
||||
# revealed: ty_extensions.ConstraintSet[¬(Sub ≤ T@_ ≤ Base)]
|
||||
reveal_type(negated_range_constraint(SubSub, T, Super) | negated_range_constraint(Sub, T, Base))
|
||||
# revealed: ty_extensions.ConstraintSet[(T@_ ≠ Base)]
|
||||
reveal_type(not_equivalent_constraint(T, Base) | not_equivalent_constraint(T, Base))
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(not_equivalent_constraint(T, Base) | not_equivalent_constraint(T, Super))
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(not_equivalent_constraint(T, Base) | not_equivalent_constraint(T, Sub))
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(not_equivalent_constraint(T, Base) | not_equivalent_constraint(T, Unrelated))
|
||||
reveal_type(negated_range_constraint(Sub, T, Base) | negated_range_constraint(Base, T, Super))
|
||||
# revealed: ty_extensions.ConstraintSet[¬(Sub ≤ T@_ ≤ Super)]
|
||||
reveal_type(negated_range_constraint(Sub, T, Super) | negated_range_constraint(Sub, T, Super))
|
||||
```
|
||||
|
||||
### Union of not-equivalent and incomparable
|
||||
|
||||
When the hole of the non-equivalent constraint and the pivot of the incomparable constraint are not
|
||||
comparable, then the hole is covered by the incomparable constraint, and the union is therefore
|
||||
always satisfied.
|
||||
|
||||
```py
|
||||
from typing import final
|
||||
from ty_extensions import incomparable_constraint, not_equivalent_constraint
|
||||
|
||||
class Super: ...
|
||||
class Base(Super): ...
|
||||
class Sub(Base): ...
|
||||
|
||||
@final
|
||||
class Unrelated: ...
|
||||
|
||||
def _[T]() -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(not_equivalent_constraint(T, Unrelated) | incomparable_constraint(T, Base))
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(not_equivalent_constraint(T, Base) | incomparable_constraint(T, Unrelated))
|
||||
```
|
||||
|
||||
Otherwise, the hole and pivot are comparable, and the non-equivalent constraint subsumes the
|
||||
incomparable constraint.
|
||||
If the holes don't overlap, the union is always satisfied.
|
||||
|
||||
```py
|
||||
def _[T]() -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[(T@_ ≠ Base)]
|
||||
reveal_type(not_equivalent_constraint(T, Base) | incomparable_constraint(T, Super))
|
||||
# revealed: ty_extensions.ConstraintSet[(T@_ ≠ Base)]
|
||||
reveal_type(not_equivalent_constraint(T, Base) | incomparable_constraint(T, Base))
|
||||
# revealed: ty_extensions.ConstraintSet[(T@_ ≠ Base)]
|
||||
reveal_type(not_equivalent_constraint(T, Base) | incomparable_constraint(T, Sub))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[(T@_ ≠ Super)]
|
||||
reveal_type(not_equivalent_constraint(T, Super) | incomparable_constraint(T, Base))
|
||||
# revealed: ty_extensions.ConstraintSet[(T@_ ≠ Base)]
|
||||
reveal_type(not_equivalent_constraint(T, Base) | incomparable_constraint(T, Base))
|
||||
# revealed: ty_extensions.ConstraintSet[(T@_ ≠ Sub)]
|
||||
reveal_type(not_equivalent_constraint(T, Sub) | incomparable_constraint(T, Base))
|
||||
```
|
||||
|
||||
### Union of two incomparables
|
||||
|
||||
We can only simplify the union of two incomparable constraints if they have the same pivot.
|
||||
|
||||
```py
|
||||
from typing import final
|
||||
from ty_extensions import incomparable_constraint
|
||||
|
||||
class Super: ...
|
||||
class Base(Super): ...
|
||||
class Sub(Base): ...
|
||||
|
||||
@final
|
||||
class Unrelated: ...
|
||||
|
||||
def _[T]() -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[(T@_ ≁ Base)]
|
||||
reveal_type(incomparable_constraint(T, Base) | incomparable_constraint(T, Base))
|
||||
# revealed: ty_extensions.ConstraintSet[(T@_ ≁ Base) ∨ (T@_ ≁ Super)]
|
||||
reveal_type(incomparable_constraint(T, Base) | incomparable_constraint(T, Super))
|
||||
# revealed: ty_extensions.ConstraintSet[(T@_ ≁ Base) ∨ (T@_ ≁ Sub)]
|
||||
reveal_type(incomparable_constraint(T, Base) | incomparable_constraint(T, Sub))
|
||||
# revealed: ty_extensions.ConstraintSet[(T@_ ≁ Base) ∨ (T@_ ≁ Unrelated)]
|
||||
reveal_type(incomparable_constraint(T, Base) | incomparable_constraint(T, Unrelated))
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(negated_range_constraint(SubSub, T, Sub) | negated_range_constraint(Base, T, Super))
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(negated_range_constraint(SubSub, T, Sub) | negated_range_constraint(Unrelated, T, object))
|
||||
```
|
||||
|
||||
## Negation
|
||||
|
||||
### Negation of a range constraint
|
||||
|
||||
In the negation of a range constraint, the typevar must specialize to a type that is not a subtype
|
||||
of the lower bound, or is not a supertype of the upper bound. Subtyping is a partial order, so one
|
||||
type is not a subtype of another if it is a _proper_ supertype, or if they are incomparable.
|
||||
|
||||
```py
|
||||
from typing import Never
|
||||
from ty_extensions import range_constraint
|
||||
|
@ -708,62 +544,21 @@ class Base(Super): ...
|
|||
class Sub(Base): ...
|
||||
|
||||
def _[T]() -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[((T@_ ≤ Sub) ∧ (T@_ ≠ Sub)) ∨ (T@_ ≁ Sub) ∨ ((Base ≤ T@_) ∧ (T@_ ≠ Base)) ∨ (T@_ ≁ Base)]
|
||||
# revealed: ty_extensions.ConstraintSet[¬(Sub ≤ T@_ ≤ Base)]
|
||||
reveal_type(~range_constraint(Sub, T, Base))
|
||||
# revealed: ty_extensions.ConstraintSet[((Base ≤ T@_) ∧ (T@_ ≠ Base)) ∨ (T@_ ≁ Base)]
|
||||
# revealed: ty_extensions.ConstraintSet[¬(T@_ ≤ Base)]
|
||||
reveal_type(~range_constraint(Never, T, Base))
|
||||
# revealed: ty_extensions.ConstraintSet[((T@_ ≤ Sub) ∧ (T@_ ≠ Sub)) ∨ (T@_ ≁ Sub)]
|
||||
# revealed: ty_extensions.ConstraintSet[¬(Sub ≤ T@_)]
|
||||
reveal_type(~range_constraint(Sub, T, object))
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(~range_constraint(Never, T, object))
|
||||
```
|
||||
|
||||
### Negation of a not-equivalent constraint
|
||||
|
||||
In the negation of a not-equivalent constrant, the typevar must specialize to a type that _is_
|
||||
equivalent to the hole. The negation does not include types that are incomparable with the hole —
|
||||
those types are not equivalent to the hole, and are therefore in the original not-equivalent
|
||||
constraint, not its negation.
|
||||
The union of a range constraint and its negation should always be satisfiable.
|
||||
|
||||
```py
|
||||
from typing import Never
|
||||
from ty_extensions import not_equivalent_constraint
|
||||
|
||||
class Super: ...
|
||||
class Base(Super): ...
|
||||
class Sub(Base): ...
|
||||
|
||||
def _[T]() -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[(Base ≤ T@_ ≤ Base)]
|
||||
reveal_type(~not_equivalent_constraint(T, Base))
|
||||
# revealed: ty_extensions.ConstraintSet[(Sub ≤ T@_ ≤ Sub)]
|
||||
reveal_type(~not_equivalent_constraint(T, Sub))
|
||||
# revealed: ty_extensions.ConstraintSet[(T@_ ≤ Never)]
|
||||
reveal_type(~not_equivalent_constraint(T, Never))
|
||||
# revealed: ty_extensions.ConstraintSet[(object ≤ T@_)]
|
||||
reveal_type(~not_equivalent_constraint(T, object))
|
||||
```
|
||||
|
||||
### Negation of an incomparable constraint
|
||||
|
||||
In the negation of an incomparable constraint, the typevar must specialize to a type that _is_
|
||||
comparable with (either a subtype _or_ supertype of) the pivot.
|
||||
|
||||
```py
|
||||
from typing import Never
|
||||
from ty_extensions import incomparable_constraint
|
||||
|
||||
class Super: ...
|
||||
class Base(Super): ...
|
||||
class Sub(Base): ...
|
||||
|
||||
def _[T]() -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[(T@_ ≤ Base) ∨ (Base ≤ T@_)]
|
||||
reveal_type(~incomparable_constraint(T, Base))
|
||||
# revealed: ty_extensions.ConstraintSet[(T@_ ≤ Sub) ∨ (Sub ≤ T@_)]
|
||||
reveal_type(~incomparable_constraint(T, Sub))
|
||||
constraint = range_constraint(Sub, T, Base)
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(~incomparable_constraint(T, Never))
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(~incomparable_constraint(T, object))
|
||||
reveal_type(constraint | ~constraint)
|
||||
```
|
||||
|
|
|
@ -4058,56 +4058,26 @@ impl<'db> Type<'db> {
|
|||
)
|
||||
.into(),
|
||||
|
||||
Some(KnownFunction::RangeConstraint) => 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::NotEquivalentConstraint) => Binding::single(
|
||||
self,
|
||||
Signature::new(
|
||||
Parameters::new([
|
||||
Parameter::positional_only(Some(Name::new_static("typevar")))
|
||||
.type_form()
|
||||
.with_annotated_type(Type::any()),
|
||||
Parameter::positional_only(Some(Name::new_static("hole")))
|
||||
.type_form()
|
||||
.with_annotated_type(Type::any()),
|
||||
]),
|
||||
Some(KnownClass::ConstraintSet.to_instance(db)),
|
||||
),
|
||||
)
|
||||
.into(),
|
||||
|
||||
Some(KnownFunction::IncomparableConstraint) => Binding::single(
|
||||
self,
|
||||
Signature::new(
|
||||
Parameters::new([
|
||||
Parameter::positional_only(Some(Name::new_static("typevar")))
|
||||
.type_form()
|
||||
.with_annotated_type(Type::any()),
|
||||
Parameter::positional_only(Some(Name::new_static("pivot")))
|
||||
.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(
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1259,10 +1259,8 @@ pub enum KnownFunction {
|
|||
RevealProtocolInterface,
|
||||
/// `ty_extensions.range_constraint`
|
||||
RangeConstraint,
|
||||
/// `ty_extensions.not_equivalent_constraint`
|
||||
NotEquivalentConstraint,
|
||||
/// `ty_extensions.incomparable_constraint`
|
||||
IncomparableConstraint,
|
||||
/// `ty_extensions.negated_range_constraint`
|
||||
NegatedRangeConstraint,
|
||||
}
|
||||
|
||||
impl KnownFunction {
|
||||
|
@ -1330,8 +1328,7 @@ impl KnownFunction {
|
|||
| Self::HasMember
|
||||
| Self::RevealProtocolInterface
|
||||
| Self::RangeConstraint
|
||||
| Self::NotEquivalentConstraint
|
||||
| Self::IncomparableConstraint
|
||||
| Self::NegatedRangeConstraint
|
||||
| Self::AllMembers => module.is_ty_extensions(),
|
||||
Self::ImportModule => module.is_importlib(),
|
||||
}
|
||||
|
@ -1644,61 +1641,17 @@ impl KnownFunction {
|
|||
)));
|
||||
}
|
||||
|
||||
KnownFunction::NotEquivalentConstraint => {
|
||||
let [Some(Type::NonInferableTypeVar(typevar)), Some(hole)] = parameter_types else {
|
||||
return;
|
||||
};
|
||||
|
||||
if !hole.is_equivalent_to(db, hole.top_materialization(db)) {
|
||||
if let Some(builder) =
|
||||
context.report_lint(&INVALID_ARGUMENT_TYPE, call_expression)
|
||||
{
|
||||
let mut diagnostic = builder.into_diagnostic(format_args!(
|
||||
"Not-equivalent constraint must have a fully static type"
|
||||
));
|
||||
diagnostic.annotate(
|
||||
Annotation::secondary(context.span(&call_expression.arguments.args[1]))
|
||||
.message(format_args!(
|
||||
"Type `{}` is not fully static",
|
||||
hole.display(db)
|
||||
)),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let constraints = ConstraintSet::not_equivalent(db, *typevar, *hole);
|
||||
let tracked = TrackedConstraintSet::new(db, constraints);
|
||||
overload.set_return_type(Type::KnownInstance(KnownInstanceType::ConstraintSet(
|
||||
tracked,
|
||||
)));
|
||||
}
|
||||
|
||||
KnownFunction::IncomparableConstraint => {
|
||||
let [Some(Type::NonInferableTypeVar(typevar)), Some(pivot)] = parameter_types
|
||||
KnownFunction::NegatedRangeConstraint => {
|
||||
let [
|
||||
Some(lower),
|
||||
Some(Type::NonInferableTypeVar(typevar)),
|
||||
Some(upper),
|
||||
] = parameter_types
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
if !pivot.is_equivalent_to(db, pivot.top_materialization(db)) {
|
||||
if let Some(builder) =
|
||||
context.report_lint(&INVALID_ARGUMENT_TYPE, call_expression)
|
||||
{
|
||||
let mut diagnostic = builder.into_diagnostic(format_args!(
|
||||
"Incomparable constraint must have a fully static type"
|
||||
));
|
||||
diagnostic.annotate(
|
||||
Annotation::secondary(context.span(&call_expression.arguments.args[1]))
|
||||
.message(format_args!(
|
||||
"Type `{}` is not fully static",
|
||||
pivot.display(db)
|
||||
)),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let constraints = ConstraintSet::incomparable(db, *typevar, *pivot);
|
||||
let constraints = ConstraintSet::negated_range(db, *lower, *typevar, *upper);
|
||||
let tracked = TrackedConstraintSet::new(db, constraints);
|
||||
overload.set_return_type(Type::KnownInstance(KnownInstanceType::ConstraintSet(
|
||||
tracked,
|
||||
|
@ -1837,8 +1790,7 @@ pub(crate) mod tests {
|
|||
| KnownFunction::HasMember
|
||||
| KnownFunction::RevealProtocolInterface
|
||||
| KnownFunction::RangeConstraint
|
||||
| KnownFunction::NotEquivalentConstraint
|
||||
| KnownFunction::IncomparableConstraint
|
||||
| KnownFunction::NegatedRangeConstraint
|
||||
| KnownFunction::AllMembers => KnownModule::TyExtensions,
|
||||
|
||||
KnownFunction::ImportModule => KnownModule::ImportLib,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue