[ty] Perform assignability etc checks using new Constraints trait (#19838)

"Why would you do this? This looks like you just replaced `bool` with an
overly complex trait"

Yes that's correct!

This should be a no-op refactoring. It replaces all of the logic in our
assignability, subtyping, equivalence, and disjointness methods to work
over an arbitrary `Constraints` trait instead of only working on `bool`.

The methods that `Constraints` provides looks very much like what we get
from `bool`. But soon we will add a new impl of this trait, and some new
methods, that let us express "fuzzy" constraints that aren't always true
or false. (In particular, a constraint will express the upper and lower
bounds of the allowed specializations of a typevar.)

Even once we have that, most of the operations that we perform on
constraint sets will be the usual boolean operations, just on sets.
(`false` becomes empty/never; `true` becomes universe/always; `or`
becomes union; `and` becomes intersection; `not` becomes negation.) So
it's helpful to have this separate PR to refactor how we invoke those
operations without introducing the new functionality yet.

Note that we also have translations of `Option::is_some_and` and
`is_none_or`, and of `Iterator::any` and `all`, and that the `and`,
`or`, `when_any`, and `when_all` methods are meant to short-circuit,
just like the corresponding boolean operations. For constraint sets,
that depends on being able to implement the `is_always` and `is_never`
trait methods.

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
This commit is contained in:
Douglas Creager 2025-08-21 09:30:09 -04:00 committed by GitHub
parent 045cba382a
commit 14fe1228e7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 1148 additions and 602 deletions

View file

@ -327,6 +327,17 @@ def union[T: Base, U: (Base, Unrelated)](t: T, u: U) -> None:
static_assert(is_subtype_of(U, U | None))
```
A bound or constrained typevar in a union with a dynamic type is assignable to the typevar:
```py
def union_with_dynamic[T: Base, U: (Base, Unrelated)](t: T, u: U) -> None:
static_assert(is_assignable_to(T | Any, T))
static_assert(is_assignable_to(U | Any, U))
static_assert(not is_subtype_of(T | Any, T))
static_assert(not is_subtype_of(U | Any, U))
```
And an intersection of a typevar with another type is always a subtype of the TypeVar:
```py