[ty] Create a specialization from a constraint set (#21414)

This patch lets us create specializations from a constraint set. The
constraint encodes the restrictions on which types each typevar can
specialize to. Given a generic context and a constraint set, we iterate
through all of the generic context's typevars. For each typevar, we
abstract the constraint set so that it only mentions the typevar in
question (propagating derived facts if needed). We then find the "best
representative type" for the typevar given the abstracted constraint
set.

When considering the BDD structure of the abstracted constraint set,
each path from the BDD root to the `true` terminal represents one way
that the constraint set can be satisfied. (This is also one of the
clauses in the DNF representation of the constraint set's boolean
formula.) Each of those paths is the conjunction of the individual
constraints of each internal node that we traverse as we walk that path,
giving a single lower/upper bound for the path. We use the upper bound
as the "best" (i.e. "closest to `object`") type for that path.

If there are multiple paths in the BDD, they technically represent
independent possible specializations. If there's a single specialization
that satisfies all of them, we will return that as the specialization.
If not, then the constraint set is ambiguous. (This happens most often
with constrained typevars.) We could in the future turn _each_ of the
paths into separate specializations, but it's not clear what we would do
with that, so instead we just report the ambiguity as a specialization
failure.
This commit is contained in:
Douglas Creager
2025-11-19 14:20:33 -05:00
committed by GitHub
parent 68ebd5132c
commit 97935518e9
15 changed files with 964 additions and 170 deletions

View File

@@ -503,9 +503,11 @@ class C[T]():
def f(self: Self):
def b(x: Self):
reveal_type(x) # revealed: Self@f
reveal_type(generic_context(b)) # revealed: None
# revealed: None
reveal_type(generic_context(b))
reveal_type(generic_context(C.f)) # revealed: tuple[Self@f]
# revealed: ty_extensions.GenericContext[Self@f]
reveal_type(generic_context(C.f))
```
Even if the `Self` annotation appears first in the nested function, it is the method that binds
@@ -519,9 +521,11 @@ class C:
def f(self: "C"):
def b(x: Self):
reveal_type(x) # revealed: Self@f
reveal_type(generic_context(b)) # revealed: None
# revealed: None
reveal_type(generic_context(b))
reveal_type(generic_context(C.f)) # revealed: None
# revealed: None
reveal_type(generic_context(C.f))
```
## Non-positional first parameters