[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:
@@ -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
|
||||
|
||||
@@ -21,14 +21,14 @@ class TypeVarAndParamSpec(Generic[P, T]): ...
|
||||
class SingleTypeVarTuple(Generic[Unpack[Ts]]): ...
|
||||
class TypeVarAndTypeVarTuple(Generic[T, Unpack[Ts]]): ...
|
||||
|
||||
# revealed: tuple[T@SingleTypevar]
|
||||
# revealed: ty_extensions.GenericContext[T@SingleTypevar]
|
||||
reveal_type(generic_context(SingleTypevar))
|
||||
# revealed: tuple[T@MultipleTypevars, S@MultipleTypevars]
|
||||
# revealed: ty_extensions.GenericContext[T@MultipleTypevars, S@MultipleTypevars]
|
||||
reveal_type(generic_context(MultipleTypevars))
|
||||
|
||||
# revealed: tuple[P@SingleParamSpec]
|
||||
# revealed: ty_extensions.GenericContext[P@SingleParamSpec]
|
||||
reveal_type(generic_context(SingleParamSpec))
|
||||
# revealed: tuple[P@TypeVarAndParamSpec, T@TypeVarAndParamSpec]
|
||||
# revealed: ty_extensions.GenericContext[P@TypeVarAndParamSpec, T@TypeVarAndParamSpec]
|
||||
reveal_type(generic_context(TypeVarAndParamSpec))
|
||||
|
||||
# TODO: support `TypeVarTuple` properly (these should not reveal `None`)
|
||||
@@ -66,9 +66,9 @@ class InheritedGeneric(MultipleTypevars[T, S]): ...
|
||||
class InheritedGenericPartiallySpecialized(MultipleTypevars[T, int]): ...
|
||||
class InheritedGenericFullySpecialized(MultipleTypevars[str, int]): ...
|
||||
|
||||
# revealed: tuple[T@InheritedGeneric, S@InheritedGeneric]
|
||||
# revealed: ty_extensions.GenericContext[T@InheritedGeneric, S@InheritedGeneric]
|
||||
reveal_type(generic_context(InheritedGeneric))
|
||||
# revealed: tuple[T@InheritedGenericPartiallySpecialized]
|
||||
# revealed: ty_extensions.GenericContext[T@InheritedGenericPartiallySpecialized]
|
||||
reveal_type(generic_context(InheritedGenericPartiallySpecialized))
|
||||
# revealed: None
|
||||
reveal_type(generic_context(InheritedGenericFullySpecialized))
|
||||
@@ -90,7 +90,7 @@ class OuterClass(Generic[T]):
|
||||
# revealed: None
|
||||
reveal_type(generic_context(InnerClassInMethod))
|
||||
|
||||
# revealed: tuple[T@OuterClass]
|
||||
# revealed: ty_extensions.GenericContext[T@OuterClass]
|
||||
reveal_type(generic_context(OuterClass))
|
||||
```
|
||||
|
||||
@@ -118,11 +118,11 @@ class ExplicitInheritedGenericPartiallySpecializedExtraTypevar(MultipleTypevars[
|
||||
# error: [invalid-generic-class] "`Generic` base class must include all type variables used in other base classes"
|
||||
class ExplicitInheritedGenericPartiallySpecializedMissingTypevar(MultipleTypevars[T, int], Generic[S]): ...
|
||||
|
||||
# revealed: tuple[T@ExplicitInheritedGeneric, S@ExplicitInheritedGeneric]
|
||||
# revealed: ty_extensions.GenericContext[T@ExplicitInheritedGeneric, S@ExplicitInheritedGeneric]
|
||||
reveal_type(generic_context(ExplicitInheritedGeneric))
|
||||
# revealed: tuple[T@ExplicitInheritedGenericPartiallySpecialized]
|
||||
# revealed: ty_extensions.GenericContext[T@ExplicitInheritedGenericPartiallySpecialized]
|
||||
reveal_type(generic_context(ExplicitInheritedGenericPartiallySpecialized))
|
||||
# revealed: tuple[T@ExplicitInheritedGenericPartiallySpecializedExtraTypevar, S@ExplicitInheritedGenericPartiallySpecializedExtraTypevar]
|
||||
# revealed: ty_extensions.GenericContext[T@ExplicitInheritedGenericPartiallySpecializedExtraTypevar, S@ExplicitInheritedGenericPartiallySpecializedExtraTypevar]
|
||||
reveal_type(generic_context(ExplicitInheritedGenericPartiallySpecializedExtraTypevar))
|
||||
```
|
||||
|
||||
@@ -594,18 +594,27 @@ class C(Generic[T]):
|
||||
def generic_method(self, t: T, u: U) -> U:
|
||||
return u
|
||||
|
||||
reveal_type(generic_context(C)) # revealed: tuple[T@C]
|
||||
reveal_type(generic_context(C.method)) # revealed: tuple[Self@method]
|
||||
reveal_type(generic_context(C.generic_method)) # revealed: tuple[Self@generic_method, U@generic_method]
|
||||
reveal_type(generic_context(C[int])) # revealed: None
|
||||
reveal_type(generic_context(C[int].method)) # revealed: tuple[Self@method]
|
||||
reveal_type(generic_context(C[int].generic_method)) # revealed: tuple[Self@generic_method, U@generic_method]
|
||||
# revealed: ty_extensions.GenericContext[T@C]
|
||||
reveal_type(generic_context(C))
|
||||
# revealed: ty_extensions.GenericContext[Self@method]
|
||||
reveal_type(generic_context(C.method))
|
||||
# revealed: ty_extensions.GenericContext[Self@generic_method, U@generic_method]
|
||||
reveal_type(generic_context(C.generic_method))
|
||||
# revealed: None
|
||||
reveal_type(generic_context(C[int]))
|
||||
# revealed: ty_extensions.GenericContext[Self@method]
|
||||
reveal_type(generic_context(C[int].method))
|
||||
# revealed: ty_extensions.GenericContext[Self@generic_method, U@generic_method]
|
||||
reveal_type(generic_context(C[int].generic_method))
|
||||
|
||||
c: C[int] = C[int]()
|
||||
reveal_type(c.generic_method(1, "string")) # revealed: Literal["string"]
|
||||
reveal_type(generic_context(c)) # revealed: None
|
||||
reveal_type(generic_context(c.method)) # revealed: tuple[Self@method]
|
||||
reveal_type(generic_context(c.generic_method)) # revealed: tuple[Self@generic_method, U@generic_method]
|
||||
# revealed: None
|
||||
reveal_type(generic_context(c))
|
||||
# revealed: ty_extensions.GenericContext[Self@method]
|
||||
reveal_type(generic_context(c.method))
|
||||
# revealed: ty_extensions.GenericContext[Self@generic_method, U@generic_method]
|
||||
reveal_type(generic_context(c.generic_method))
|
||||
```
|
||||
|
||||
## Specializations propagate
|
||||
|
||||
@@ -20,17 +20,21 @@ type TypeVarAndParamSpec[T, **P] = ...
|
||||
type SingleTypeVarTuple[*Ts] = ...
|
||||
type TypeVarAndTypeVarTuple[T, *Ts] = ...
|
||||
|
||||
# revealed: tuple[T@SingleTypevar]
|
||||
# revealed: ty_extensions.GenericContext[T@SingleTypevar]
|
||||
reveal_type(generic_context(SingleTypevar))
|
||||
# revealed: tuple[T@MultipleTypevars, S@MultipleTypevars]
|
||||
# revealed: ty_extensions.GenericContext[T@MultipleTypevars, S@MultipleTypevars]
|
||||
reveal_type(generic_context(MultipleTypevars))
|
||||
|
||||
# TODO: support `ParamSpec`/`TypeVarTuple` properly
|
||||
# (these should include the `ParamSpec`s and `TypeVarTuple`s in their generic contexts)
|
||||
reveal_type(generic_context(SingleParamSpec)) # revealed: tuple[()]
|
||||
reveal_type(generic_context(TypeVarAndParamSpec)) # revealed: tuple[T@TypeVarAndParamSpec]
|
||||
reveal_type(generic_context(SingleTypeVarTuple)) # revealed: tuple[()]
|
||||
reveal_type(generic_context(TypeVarAndTypeVarTuple)) # revealed: tuple[T@TypeVarAndTypeVarTuple]
|
||||
# revealed: ty_extensions.GenericContext[]
|
||||
reveal_type(generic_context(SingleParamSpec))
|
||||
# revealed: ty_extensions.GenericContext[T@TypeVarAndParamSpec]
|
||||
reveal_type(generic_context(TypeVarAndParamSpec))
|
||||
# revealed: ty_extensions.GenericContext[]
|
||||
reveal_type(generic_context(SingleTypeVarTuple))
|
||||
# revealed: ty_extensions.GenericContext[T@TypeVarAndTypeVarTuple]
|
||||
reveal_type(generic_context(TypeVarAndTypeVarTuple))
|
||||
```
|
||||
|
||||
You cannot use the same typevar more than once.
|
||||
|
||||
@@ -20,17 +20,21 @@ class TypeVarAndParamSpec[T, **P]: ...
|
||||
class SingleTypeVarTuple[*Ts]: ...
|
||||
class TypeVarAndTypeVarTuple[T, *Ts]: ...
|
||||
|
||||
# revealed: tuple[T@SingleTypevar]
|
||||
# revealed: ty_extensions.GenericContext[T@SingleTypevar]
|
||||
reveal_type(generic_context(SingleTypevar))
|
||||
# revealed: tuple[T@MultipleTypevars, S@MultipleTypevars]
|
||||
# revealed: ty_extensions.GenericContext[T@MultipleTypevars, S@MultipleTypevars]
|
||||
reveal_type(generic_context(MultipleTypevars))
|
||||
|
||||
# TODO: support `ParamSpec`/`TypeVarTuple` properly
|
||||
# (these should include the `ParamSpec`s and `TypeVarTuple`s in their generic contexts)
|
||||
reveal_type(generic_context(SingleParamSpec)) # revealed: tuple[()]
|
||||
reveal_type(generic_context(TypeVarAndParamSpec)) # revealed: tuple[T@TypeVarAndParamSpec]
|
||||
reveal_type(generic_context(SingleTypeVarTuple)) # revealed: tuple[()]
|
||||
reveal_type(generic_context(TypeVarAndTypeVarTuple)) # revealed: tuple[T@TypeVarAndTypeVarTuple]
|
||||
# revealed: ty_extensions.GenericContext[]
|
||||
reveal_type(generic_context(SingleParamSpec))
|
||||
# revealed: ty_extensions.GenericContext[T@TypeVarAndParamSpec]
|
||||
reveal_type(generic_context(TypeVarAndParamSpec))
|
||||
# revealed: ty_extensions.GenericContext[]
|
||||
reveal_type(generic_context(SingleTypeVarTuple))
|
||||
# revealed: ty_extensions.GenericContext[T@TypeVarAndTypeVarTuple]
|
||||
reveal_type(generic_context(TypeVarAndTypeVarTuple))
|
||||
```
|
||||
|
||||
You cannot use the same typevar more than once.
|
||||
@@ -49,9 +53,9 @@ class InheritedGeneric[U, V](MultipleTypevars[U, V]): ...
|
||||
class InheritedGenericPartiallySpecialized[U](MultipleTypevars[U, int]): ...
|
||||
class InheritedGenericFullySpecialized(MultipleTypevars[str, int]): ...
|
||||
|
||||
# revealed: tuple[U@InheritedGeneric, V@InheritedGeneric]
|
||||
# revealed: ty_extensions.GenericContext[U@InheritedGeneric, V@InheritedGeneric]
|
||||
reveal_type(generic_context(InheritedGeneric))
|
||||
# revealed: tuple[U@InheritedGenericPartiallySpecialized]
|
||||
# revealed: ty_extensions.GenericContext[U@InheritedGenericPartiallySpecialized]
|
||||
reveal_type(generic_context(InheritedGenericPartiallySpecialized))
|
||||
# revealed: None
|
||||
reveal_type(generic_context(InheritedGenericFullySpecialized))
|
||||
@@ -64,7 +68,8 @@ the inheriting class generic.
|
||||
```py
|
||||
class InheritedGenericDefaultSpecialization(MultipleTypevars): ...
|
||||
|
||||
reveal_type(generic_context(InheritedGenericDefaultSpecialization)) # revealed: None
|
||||
# revealed: None
|
||||
reveal_type(generic_context(InheritedGenericDefaultSpecialization))
|
||||
```
|
||||
|
||||
You cannot use PEP-695 syntax and the legacy syntax in the same class definition.
|
||||
@@ -512,18 +517,27 @@ class C[T]:
|
||||
# TODO: error
|
||||
def cannot_shadow_class_typevar[T](self, t: T): ...
|
||||
|
||||
reveal_type(generic_context(C)) # revealed: tuple[T@C]
|
||||
reveal_type(generic_context(C.method)) # revealed: tuple[Self@method]
|
||||
reveal_type(generic_context(C.generic_method)) # revealed: tuple[Self@generic_method, U@generic_method]
|
||||
reveal_type(generic_context(C[int])) # revealed: None
|
||||
reveal_type(generic_context(C[int].method)) # revealed: tuple[Self@method]
|
||||
reveal_type(generic_context(C[int].generic_method)) # revealed: tuple[Self@generic_method, U@generic_method]
|
||||
# revealed: ty_extensions.GenericContext[T@C]
|
||||
reveal_type(generic_context(C))
|
||||
# revealed: ty_extensions.GenericContext[Self@method]
|
||||
reveal_type(generic_context(C.method))
|
||||
# revealed: ty_extensions.GenericContext[Self@generic_method, U@generic_method]
|
||||
reveal_type(generic_context(C.generic_method))
|
||||
# revealed: None
|
||||
reveal_type(generic_context(C[int]))
|
||||
# revealed: ty_extensions.GenericContext[Self@method]
|
||||
reveal_type(generic_context(C[int].method))
|
||||
# revealed: ty_extensions.GenericContext[Self@generic_method, U@generic_method]
|
||||
reveal_type(generic_context(C[int].generic_method))
|
||||
|
||||
c: C[int] = C[int]()
|
||||
reveal_type(c.generic_method(1, "string")) # revealed: Literal["string"]
|
||||
reveal_type(generic_context(c)) # revealed: None
|
||||
reveal_type(generic_context(c.method)) # revealed: tuple[Self@method]
|
||||
reveal_type(generic_context(c.generic_method)) # revealed: tuple[Self@generic_method, U@generic_method]
|
||||
# revealed: None
|
||||
reveal_type(generic_context(c))
|
||||
# revealed: ty_extensions.GenericContext[Self@method]
|
||||
reveal_type(generic_context(c.method))
|
||||
# revealed: ty_extensions.GenericContext[Self@generic_method, U@generic_method]
|
||||
reveal_type(generic_context(c.generic_method))
|
||||
```
|
||||
|
||||
## Specializations propagate
|
||||
|
||||
@@ -154,8 +154,10 @@ from ty_extensions import generic_context
|
||||
|
||||
legacy.m("string", None) # error: [invalid-argument-type]
|
||||
reveal_type(legacy.m) # revealed: bound method Legacy[int].m[S](x: int, y: S@m) -> S@m
|
||||
reveal_type(generic_context(Legacy)) # revealed: tuple[T@Legacy]
|
||||
reveal_type(generic_context(legacy.m)) # revealed: tuple[Self@m, S@m]
|
||||
# revealed: ty_extensions.GenericContext[T@Legacy]
|
||||
reveal_type(generic_context(Legacy))
|
||||
# revealed: ty_extensions.GenericContext[Self@m, S@m]
|
||||
reveal_type(generic_context(legacy.m))
|
||||
```
|
||||
|
||||
With PEP 695 syntax, it is clearer that the method uses a separate typevar:
|
||||
|
||||
@@ -0,0 +1,305 @@
|
||||
# Creating a specialization from a constraint set
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
We create constraint sets to describe which types a set of typevars can specialize to. We have a
|
||||
`specialize_constrained` method that creates a "best" specialization for a constraint set, which
|
||||
lets us test this logic in isolation, without having to bring in the rest of the specialization
|
||||
inference logic.
|
||||
|
||||
## Unbounded typevars
|
||||
|
||||
An unbounded typevar can specialize to any type. We will specialize the typevar to the least upper
|
||||
bound of all of the types that satisfy the constraint set.
|
||||
|
||||
```py
|
||||
from typing import Never
|
||||
from ty_extensions import ConstraintSet, generic_context
|
||||
|
||||
# fmt: off
|
||||
|
||||
def unbounded[T]():
|
||||
# revealed: ty_extensions.Specialization[T@unbounded = object]
|
||||
reveal_type(generic_context(unbounded).specialize_constrained(ConstraintSet.always()))
|
||||
# revealed: None
|
||||
reveal_type(generic_context(unbounded).specialize_constrained(ConstraintSet.never()))
|
||||
|
||||
# revealed: ty_extensions.Specialization[T@unbounded = int]
|
||||
reveal_type(generic_context(unbounded).specialize_constrained(ConstraintSet.range(Never, T, int)))
|
||||
# revealed: ty_extensions.Specialization[T@unbounded = int]
|
||||
reveal_type(generic_context(unbounded).specialize_constrained(ConstraintSet.range(bool, T, int)))
|
||||
|
||||
# revealed: ty_extensions.Specialization[T@unbounded = bool]
|
||||
reveal_type(generic_context(unbounded).specialize_constrained(ConstraintSet.range(Never, T, int) & ConstraintSet.range(Never, T, bool)))
|
||||
# revealed: ty_extensions.Specialization[T@unbounded = Never]
|
||||
reveal_type(generic_context(unbounded).specialize_constrained(ConstraintSet.range(Never, T, int) & ConstraintSet.range(Never, T, str)))
|
||||
# revealed: None
|
||||
reveal_type(generic_context(unbounded).specialize_constrained(ConstraintSet.range(bool, T, bool) & ConstraintSet.range(Never, T, str)))
|
||||
|
||||
# revealed: ty_extensions.Specialization[T@unbounded = int]
|
||||
reveal_type(generic_context(unbounded).specialize_constrained(ConstraintSet.range(Never, T, int) | ConstraintSet.range(Never, T, bool)))
|
||||
# revealed: ty_extensions.Specialization[T@unbounded = Never]
|
||||
reveal_type(generic_context(unbounded).specialize_constrained(ConstraintSet.range(Never, T, int) | ConstraintSet.range(Never, T, str)))
|
||||
# revealed: None
|
||||
reveal_type(generic_context(unbounded).specialize_constrained(ConstraintSet.range(bool, T, bool) | ConstraintSet.range(Never, T, str)))
|
||||
```
|
||||
|
||||
## Typevar with an upper bound
|
||||
|
||||
If a typevar has an upper bound, then it must specialize to a type that is a subtype of that bound.
|
||||
|
||||
```py
|
||||
from typing import final, Never
|
||||
from ty_extensions import ConstraintSet, generic_context
|
||||
|
||||
class Super: ...
|
||||
class Base(Super): ...
|
||||
class Sub(Base): ...
|
||||
|
||||
@final
|
||||
class Unrelated: ...
|
||||
|
||||
def bounded[T: Base]():
|
||||
# revealed: ty_extensions.Specialization[T@bounded = Base]
|
||||
reveal_type(generic_context(bounded).specialize_constrained(ConstraintSet.always()))
|
||||
# revealed: None
|
||||
reveal_type(generic_context(bounded).specialize_constrained(ConstraintSet.never()))
|
||||
|
||||
# revealed: ty_extensions.Specialization[T@bounded = Base]
|
||||
reveal_type(generic_context(bounded).specialize_constrained(ConstraintSet.range(Never, T, Super)))
|
||||
# revealed: ty_extensions.Specialization[T@bounded = Base]
|
||||
reveal_type(generic_context(bounded).specialize_constrained(ConstraintSet.range(Never, T, Base)))
|
||||
# revealed: ty_extensions.Specialization[T@bounded = Sub]
|
||||
reveal_type(generic_context(bounded).specialize_constrained(ConstraintSet.range(Never, T, Sub)))
|
||||
|
||||
# revealed: ty_extensions.Specialization[T@bounded = Never]
|
||||
reveal_type(generic_context(bounded).specialize_constrained(ConstraintSet.range(Never, T, Unrelated)))
|
||||
# revealed: None
|
||||
reveal_type(generic_context(bounded).specialize_constrained(ConstraintSet.range(Unrelated, T, Unrelated)))
|
||||
```
|
||||
|
||||
If the upper bound is a gradual type, we are free to choose any materialization of the upper bound
|
||||
that makes the test succeed.
|
||||
|
||||
```py
|
||||
from typing import Any
|
||||
|
||||
def bounded_by_gradual[T: Any]():
|
||||
# revealed: ty_extensions.Specialization[T@bounded_by_gradual = object]
|
||||
reveal_type(generic_context(bounded_by_gradual).specialize_constrained(ConstraintSet.always()))
|
||||
# revealed: None
|
||||
reveal_type(generic_context(bounded_by_gradual).specialize_constrained(ConstraintSet.never()))
|
||||
|
||||
# revealed: ty_extensions.Specialization[T@bounded_by_gradual = Base]
|
||||
reveal_type(generic_context(bounded_by_gradual).specialize_constrained(ConstraintSet.range(Never, T, Base)))
|
||||
|
||||
# revealed: ty_extensions.Specialization[T@bounded_by_gradual = Unrelated]
|
||||
reveal_type(generic_context(bounded_by_gradual).specialize_constrained(ConstraintSet.range(Never, T, Unrelated)))
|
||||
|
||||
def bounded_by_gradual_list[T: list[Any]]():
|
||||
# revealed: ty_extensions.Specialization[T@bounded_by_gradual_list = Top[list[Any]]]
|
||||
reveal_type(generic_context(bounded_by_gradual_list).specialize_constrained(ConstraintSet.always()))
|
||||
# revealed: None
|
||||
reveal_type(generic_context(bounded_by_gradual_list).specialize_constrained(ConstraintSet.never()))
|
||||
|
||||
# revealed: ty_extensions.Specialization[T@bounded_by_gradual_list = list[Base]]
|
||||
reveal_type(generic_context(bounded_by_gradual_list).specialize_constrained(ConstraintSet.range(Never, T, list[Base])))
|
||||
|
||||
# revealed: ty_extensions.Specialization[T@bounded_by_gradual_list = list[Unrelated]]
|
||||
reveal_type(generic_context(bounded_by_gradual_list).specialize_constrained(ConstraintSet.range(Never, T, list[Unrelated])))
|
||||
```
|
||||
|
||||
## Constrained typevar
|
||||
|
||||
If a typevar has constraints, then it must specialize to one of those specific types. (Not to a
|
||||
subtype of one of those types!)
|
||||
|
||||
In particular, note that if a constraint set is satisfied by more than one of the typevar's
|
||||
constraints (i.e., we have no reason to prefer one over the others), then we return `None` to
|
||||
indicate an ambiguous result. We could, in theory, return _more than one_ specialization, since we
|
||||
have all of the information necessary to produce this. But it's not clear what we would do with that
|
||||
information at the moment.
|
||||
|
||||
```py
|
||||
from typing import final, Never
|
||||
from ty_extensions import ConstraintSet, generic_context
|
||||
|
||||
class Super: ...
|
||||
class Base(Super): ...
|
||||
class Sub(Base): ...
|
||||
|
||||
@final
|
||||
class Unrelated: ...
|
||||
|
||||
def constrained[T: (Base, Unrelated)]():
|
||||
# revealed: None
|
||||
reveal_type(generic_context(constrained).specialize_constrained(ConstraintSet.always()))
|
||||
# revealed: None
|
||||
reveal_type(generic_context(constrained).specialize_constrained(ConstraintSet.never()))
|
||||
|
||||
# revealed: ty_extensions.Specialization[T@constrained = Base]
|
||||
reveal_type(generic_context(constrained).specialize_constrained(ConstraintSet.range(Never, T, Base)))
|
||||
# revealed: ty_extensions.Specialization[T@constrained = Unrelated]
|
||||
reveal_type(generic_context(constrained).specialize_constrained(ConstraintSet.range(Never, T, Unrelated)))
|
||||
|
||||
# revealed: ty_extensions.Specialization[T@constrained = Base]
|
||||
reveal_type(generic_context(constrained).specialize_constrained(ConstraintSet.range(Never, T, Super)))
|
||||
# revealed: None
|
||||
reveal_type(generic_context(constrained).specialize_constrained(ConstraintSet.range(Super, T, Super)))
|
||||
|
||||
# revealed: ty_extensions.Specialization[T@constrained = Base]
|
||||
reveal_type(generic_context(constrained).specialize_constrained(ConstraintSet.range(Sub, T, object)))
|
||||
# revealed: None
|
||||
reveal_type(generic_context(constrained).specialize_constrained(ConstraintSet.range(Sub, T, Sub)))
|
||||
```
|
||||
|
||||
If any of the constraints is a gradual type, we are free to choose any materialization of that
|
||||
constraint that makes the test succeed.
|
||||
|
||||
TODO: At the moment, we are producing a specialization that shows which particular materialization
|
||||
that we chose, but really, we should be returning the gradual constraint as the specialization.
|
||||
|
||||
```py
|
||||
from typing import Any
|
||||
|
||||
# fmt: off
|
||||
|
||||
def constrained_by_gradual[T: (Base, Any)]():
|
||||
# TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = Any]
|
||||
# revealed: ty_extensions.Specialization[T@constrained_by_gradual = object]
|
||||
reveal_type(generic_context(constrained_by_gradual).specialize_constrained(ConstraintSet.always()))
|
||||
# revealed: None
|
||||
reveal_type(generic_context(constrained_by_gradual).specialize_constrained(ConstraintSet.never()))
|
||||
|
||||
# revealed: ty_extensions.Specialization[T@constrained_by_gradual = Base]
|
||||
reveal_type(generic_context(constrained_by_gradual).specialize_constrained(ConstraintSet.range(Never, T, Base)))
|
||||
# TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = Any]
|
||||
# revealed: ty_extensions.Specialization[T@constrained_by_gradual = Unrelated]
|
||||
reveal_type(generic_context(constrained_by_gradual).specialize_constrained(ConstraintSet.range(Never, T, Unrelated)))
|
||||
|
||||
# TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = Any]
|
||||
# revealed: ty_extensions.Specialization[T@constrained_by_gradual = Super]
|
||||
reveal_type(generic_context(constrained_by_gradual).specialize_constrained(ConstraintSet.range(Never, T, Super)))
|
||||
# TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = Any]
|
||||
# revealed: ty_extensions.Specialization[T@constrained_by_gradual = Super]
|
||||
reveal_type(generic_context(constrained_by_gradual).specialize_constrained(ConstraintSet.range(Super, T, Super)))
|
||||
|
||||
# TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = Any]
|
||||
# revealed: ty_extensions.Specialization[T@constrained_by_gradual = object]
|
||||
reveal_type(generic_context(constrained_by_gradual).specialize_constrained(ConstraintSet.range(Sub, T, object)))
|
||||
# TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = Any]
|
||||
# revealed: ty_extensions.Specialization[T@constrained_by_gradual = Sub]
|
||||
reveal_type(generic_context(constrained_by_gradual).specialize_constrained(ConstraintSet.range(Sub, T, Sub)))
|
||||
|
||||
def constrained_by_two_gradual[T: (Any, Any)]():
|
||||
# TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = Any]
|
||||
# revealed: ty_extensions.Specialization[T@constrained_by_two_gradual = object]
|
||||
reveal_type(generic_context(constrained_by_two_gradual).specialize_constrained(ConstraintSet.always()))
|
||||
# revealed: None
|
||||
reveal_type(generic_context(constrained_by_two_gradual).specialize_constrained(ConstraintSet.never()))
|
||||
|
||||
# TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = Any]
|
||||
# revealed: ty_extensions.Specialization[T@constrained_by_two_gradual = Base]
|
||||
reveal_type(generic_context(constrained_by_two_gradual).specialize_constrained(ConstraintSet.range(Never, T, Base)))
|
||||
# TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = Any]
|
||||
# revealed: ty_extensions.Specialization[T@constrained_by_two_gradual = Unrelated]
|
||||
reveal_type(generic_context(constrained_by_two_gradual).specialize_constrained(ConstraintSet.range(Never, T, Unrelated)))
|
||||
|
||||
# TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = Any]
|
||||
# revealed: ty_extensions.Specialization[T@constrained_by_two_gradual = Super]
|
||||
reveal_type(generic_context(constrained_by_two_gradual).specialize_constrained(ConstraintSet.range(Never, T, Super)))
|
||||
# TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = Any]
|
||||
# revealed: ty_extensions.Specialization[T@constrained_by_two_gradual = Super]
|
||||
reveal_type(generic_context(constrained_by_two_gradual).specialize_constrained(ConstraintSet.range(Super, T, Super)))
|
||||
|
||||
# TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = Any]
|
||||
# revealed: ty_extensions.Specialization[T@constrained_by_two_gradual = object]
|
||||
reveal_type(generic_context(constrained_by_two_gradual).specialize_constrained(ConstraintSet.range(Sub, T, object)))
|
||||
# TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = Any]
|
||||
# revealed: ty_extensions.Specialization[T@constrained_by_two_gradual = Sub]
|
||||
reveal_type(generic_context(constrained_by_two_gradual).specialize_constrained(ConstraintSet.range(Sub, T, Sub)))
|
||||
|
||||
def constrained_by_gradual_list[T: (list[Base], list[Any])]():
|
||||
# revealed: ty_extensions.Specialization[T@constrained_by_gradual_list = list[Base]]
|
||||
reveal_type(generic_context(constrained_by_gradual_list).specialize_constrained(ConstraintSet.always()))
|
||||
# revealed: None
|
||||
reveal_type(generic_context(constrained_by_gradual_list).specialize_constrained(ConstraintSet.never()))
|
||||
|
||||
# revealed: ty_extensions.Specialization[T@constrained_by_gradual_list = list[Base]]
|
||||
reveal_type(generic_context(constrained_by_gradual_list).specialize_constrained(ConstraintSet.range(Never, T, list[Base])))
|
||||
# TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = list[Any]]
|
||||
# revealed: ty_extensions.Specialization[T@constrained_by_gradual_list = list[Unrelated]]
|
||||
reveal_type(generic_context(constrained_by_gradual_list).specialize_constrained(ConstraintSet.range(Never, T, list[Unrelated])))
|
||||
|
||||
# TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = list[Any]]
|
||||
# revealed: ty_extensions.Specialization[T@constrained_by_gradual_list = list[Super]]
|
||||
reveal_type(generic_context(constrained_by_gradual_list).specialize_constrained(ConstraintSet.range(Never, T, list[Super])))
|
||||
# TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = list[Any]]
|
||||
# revealed: ty_extensions.Specialization[T@constrained_by_gradual_list = list[Super]]
|
||||
reveal_type(generic_context(constrained_by_gradual_list).specialize_constrained(ConstraintSet.range(list[Super], T, list[Super])))
|
||||
# TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = list[Any]]
|
||||
# revealed: ty_extensions.Specialization[T@constrained_by_gradual_list = list[Sub]]
|
||||
reveal_type(generic_context(constrained_by_gradual_list).specialize_constrained(ConstraintSet.range(list[Sub], T, list[Sub])))
|
||||
|
||||
def constrained_by_two_gradual_lists[T: (list[Any], list[Any])]():
|
||||
# TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = list[Any]]
|
||||
# revealed: ty_extensions.Specialization[T@constrained_by_two_gradual_lists = Top[list[Any]]]
|
||||
reveal_type(generic_context(constrained_by_two_gradual_lists).specialize_constrained(ConstraintSet.always()))
|
||||
# revealed: None
|
||||
reveal_type(generic_context(constrained_by_two_gradual_lists).specialize_constrained(ConstraintSet.never()))
|
||||
|
||||
# TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = list[Any]]
|
||||
# revealed: ty_extensions.Specialization[T@constrained_by_two_gradual_lists = list[Base]]
|
||||
reveal_type(generic_context(constrained_by_two_gradual_lists).specialize_constrained(ConstraintSet.range(Never, T, list[Base])))
|
||||
# TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = list[Any]]
|
||||
# revealed: ty_extensions.Specialization[T@constrained_by_two_gradual_lists = list[Unrelated]]
|
||||
reveal_type(generic_context(constrained_by_two_gradual_lists).specialize_constrained(ConstraintSet.range(Never, T, list[Unrelated])))
|
||||
|
||||
# TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = list[Any]]
|
||||
# revealed: ty_extensions.Specialization[T@constrained_by_two_gradual_lists = list[Super]]
|
||||
reveal_type(generic_context(constrained_by_two_gradual_lists).specialize_constrained(ConstraintSet.range(Never, T, list[Super])))
|
||||
# TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = list[Any]]
|
||||
# revealed: ty_extensions.Specialization[T@constrained_by_two_gradual_lists = list[Super]]
|
||||
reveal_type(generic_context(constrained_by_two_gradual_lists).specialize_constrained(ConstraintSet.range(list[Super], T, list[Super])))
|
||||
# TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = list[Any]]
|
||||
# revealed: ty_extensions.Specialization[T@constrained_by_two_gradual_lists = list[Sub]]
|
||||
reveal_type(generic_context(constrained_by_two_gradual_lists).specialize_constrained(ConstraintSet.range(list[Sub], T, list[Sub])))
|
||||
```
|
||||
|
||||
## Mutually constrained typevars
|
||||
|
||||
If one typevar is constrained by another, the specialization of one can affect the specialization of
|
||||
the other.
|
||||
|
||||
```py
|
||||
from typing import final, Never
|
||||
from ty_extensions import ConstraintSet, generic_context
|
||||
|
||||
class Super: ...
|
||||
class Base(Super): ...
|
||||
class Sub(Base): ...
|
||||
|
||||
@final
|
||||
class Unrelated: ...
|
||||
|
||||
# fmt: off
|
||||
|
||||
def mutually_bound[T: Base, U]():
|
||||
# revealed: ty_extensions.Specialization[T@mutually_bound = Base, U@mutually_bound = object]
|
||||
reveal_type(generic_context(mutually_bound).specialize_constrained(ConstraintSet.always()))
|
||||
# revealed: None
|
||||
reveal_type(generic_context(mutually_bound).specialize_constrained(ConstraintSet.never()))
|
||||
|
||||
# revealed: ty_extensions.Specialization[T@mutually_bound = Base, U@mutually_bound = Base]
|
||||
reveal_type(generic_context(mutually_bound).specialize_constrained(ConstraintSet.range(Never, U, T)))
|
||||
|
||||
# revealed: ty_extensions.Specialization[T@mutually_bound = Sub, U@mutually_bound = object]
|
||||
reveal_type(generic_context(mutually_bound).specialize_constrained(ConstraintSet.range(Never, T, Sub)))
|
||||
# revealed: ty_extensions.Specialization[T@mutually_bound = Sub, U@mutually_bound = Sub]
|
||||
reveal_type(generic_context(mutually_bound).specialize_constrained(ConstraintSet.range(Never, T, Sub) & ConstraintSet.range(Never, U, T)))
|
||||
# revealed: ty_extensions.Specialization[T@mutually_bound = Base, U@mutually_bound = Sub]
|
||||
reveal_type(generic_context(mutually_bound).specialize_constrained(ConstraintSet.range(Never, U, Sub) & ConstraintSet.range(Never, U, T)))
|
||||
```
|
||||
Reference in New Issue
Block a user