Compare commits

...

11 Commits

Author SHA1 Message Date
Douglas Creager
6a448e1623 debug 2025-11-19 17:37:53 -05:00
Douglas Creager
336d01957d start using constraint set specs 2025-11-19 17:37:53 -05:00
Douglas Creager
1edae38adf maybe? 2025-11-19 17:37:53 -05:00
Douglas Creager
ba7f4c4364 fix more test reveals 2025-11-19 17:37:53 -05:00
Douglas Creager
10eeb9dfda fix gradual bounds/constraints tests 2025-11-19 17:37:53 -05:00
Douglas Creager
5fd909b61e limit to valid specs in display 2025-11-19 17:37:53 -05:00
Douglas Creager
7ae7d5db34 fix some generics test reveal_types 2025-11-19 17:37:53 -05:00
Douglas Creager
443586e9cd remove a lot of TODOs 2025-11-19 17:37:53 -05:00
Douglas Creager
31c9f7059a tautology 2025-11-19 17:37:53 -05:00
Douglas Creager
935abab07e into 2025-11-19 17:37:53 -05:00
Douglas Creager
b2d1475e65 _use_ satisfies_all_typevars 2025-11-19 17:37:53 -05:00
11 changed files with 364 additions and 429 deletions

View File

@@ -145,7 +145,7 @@ def unbounded_unconstrained[T, U](t: T, u: U) -> None:
reveal_type(is_assignable_to(T, object))
static_assert(is_assignable_to(T, object))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(T@unbounded_unconstrained ≤ Super)]
reveal_type(is_assignable_to(T, Super))
static_assert(not is_assignable_to(T, Super))
@@ -165,15 +165,15 @@ def unbounded_unconstrained[T, U](t: T, u: U) -> None:
reveal_type(is_assignable_to(U, object))
static_assert(is_assignable_to(U, object))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(U@unbounded_unconstrained ≤ Super)]
reveal_type(is_assignable_to(U, Super))
static_assert(not is_assignable_to(U, Super))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(T@unbounded_unconstrained ≤ U@unbounded_unconstrained)]
reveal_type(is_assignable_to(T, U))
static_assert(not is_assignable_to(T, U))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(U@unbounded_unconstrained ≤ T@unbounded_unconstrained)]
reveal_type(is_assignable_to(U, T))
static_assert(not is_assignable_to(U, T))
@@ -185,15 +185,15 @@ def unbounded_unconstrained[T, U](t: T, u: U) -> None:
reveal_type(is_subtype_of(T, object))
static_assert(is_subtype_of(T, object))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(T@unbounded_unconstrained ≤ Super)]
reveal_type(is_subtype_of(T, Super))
static_assert(not is_subtype_of(T, Super))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(T@unbounded_unconstrained = Never)]
reveal_type(is_subtype_of(T, Any))
static_assert(not is_subtype_of(T, Any))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(T@unbounded_unconstrained = object)]
reveal_type(is_subtype_of(Any, T))
static_assert(not is_subtype_of(Any, T))
@@ -205,15 +205,15 @@ def unbounded_unconstrained[T, U](t: T, u: U) -> None:
reveal_type(is_subtype_of(U, object))
static_assert(is_subtype_of(U, object))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(U@unbounded_unconstrained ≤ Super)]
reveal_type(is_subtype_of(U, Super))
static_assert(not is_subtype_of(U, Super))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(T@unbounded_unconstrained ≤ U@unbounded_unconstrained)]
reveal_type(is_subtype_of(T, U))
static_assert(not is_subtype_of(T, U))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(U@unbounded_unconstrained ≤ T@unbounded_unconstrained)]
reveal_type(is_subtype_of(U, T))
static_assert(not is_subtype_of(U, T))
```
@@ -229,31 +229,31 @@ from typing import Any
from typing_extensions import final
def bounded[T: Super](t: T) -> None:
# revealed: ty_extensions.ConstraintSet[always]
# revealed: ty_extensions.ConstraintSet[(T@bounded ≤ Super)]
reveal_type(is_assignable_to(T, Any))
static_assert(is_assignable_to(T, Any))
# revealed: ty_extensions.ConstraintSet[always]
# revealed: ty_extensions.ConstraintSet[(T@bounded ≤ Super)]
reveal_type(is_assignable_to(Any, T))
static_assert(is_assignable_to(Any, T))
# revealed: ty_extensions.ConstraintSet[always]
# revealed: ty_extensions.ConstraintSet[(T@bounded ≤ Super)]
reveal_type(is_assignable_to(T, Super))
static_assert(is_assignable_to(T, Super))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(T@bounded ≤ Sub)]
reveal_type(is_assignable_to(T, Sub))
static_assert(not is_assignable_to(T, Sub))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(T@bounded = Super)]
reveal_type(is_assignable_to(Super, T))
static_assert(not is_assignable_to(Super, T))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(Sub ≤ T@bounded ≤ Super)]
reveal_type(is_assignable_to(Sub, T))
static_assert(not is_assignable_to(Sub, T))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(T@bounded = Never)]
reveal_type(is_subtype_of(T, Any))
static_assert(not is_subtype_of(T, Any))
@@ -261,19 +261,19 @@ def bounded[T: Super](t: T) -> None:
reveal_type(is_subtype_of(Any, T))
static_assert(not is_subtype_of(Any, T))
# revealed: ty_extensions.ConstraintSet[always]
# revealed: ty_extensions.ConstraintSet[(T@bounded ≤ Super)]
reveal_type(is_subtype_of(T, Super))
static_assert(is_subtype_of(T, Super))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(T@bounded ≤ Sub)]
reveal_type(is_subtype_of(T, Sub))
static_assert(not is_subtype_of(T, Sub))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(T@bounded = Super)]
reveal_type(is_subtype_of(Super, T))
static_assert(not is_subtype_of(Super, T))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(Sub ≤ T@bounded ≤ Super)]
reveal_type(is_subtype_of(Sub, T))
static_assert(not is_subtype_of(Sub, T))
@@ -286,43 +286,43 @@ def bounded_by_gradual[T: Any](t: T) -> None:
reveal_type(is_assignable_to(Any, T))
static_assert(is_assignable_to(Any, T))
# revealed: ty_extensions.ConstraintSet[always]
# revealed: ty_extensions.ConstraintSet[(T@bounded_by_gradual ≤ Super)]
reveal_type(is_assignable_to(T, Super))
static_assert(is_assignable_to(T, Super))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(Super ≤ T@bounded_by_gradual)]
reveal_type(is_assignable_to(Super, T))
static_assert(not is_assignable_to(Super, T))
# revealed: ty_extensions.ConstraintSet[always]
# revealed: ty_extensions.ConstraintSet[(T@bounded_by_gradual ≤ Sub)]
reveal_type(is_assignable_to(T, Sub))
static_assert(is_assignable_to(T, Sub))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(Sub ≤ T@bounded_by_gradual)]
reveal_type(is_assignable_to(Sub, T))
static_assert(not is_assignable_to(Sub, T))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(T@bounded_by_gradual = Never)]
reveal_type(is_subtype_of(T, Any))
static_assert(not is_subtype_of(T, Any))
static_assert(is_subtype_of(T, Any))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(T@bounded_by_gradual = object)]
reveal_type(is_subtype_of(Any, T))
static_assert(not is_subtype_of(Any, T))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(T@bounded_by_gradual ≤ Super)]
reveal_type(is_subtype_of(T, Super))
static_assert(not is_subtype_of(T, Super))
static_assert(is_subtype_of(T, Super))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(Super ≤ T@bounded_by_gradual)]
reveal_type(is_subtype_of(Super, T))
static_assert(not is_subtype_of(Super, T))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(T@bounded_by_gradual ≤ Sub)]
reveal_type(is_subtype_of(T, Sub))
static_assert(not is_subtype_of(T, Sub))
static_assert(is_subtype_of(T, Sub))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(Sub ≤ T@bounded_by_gradual)]
reveal_type(is_subtype_of(Sub, T))
static_assert(not is_subtype_of(Sub, T))
@@ -330,23 +330,23 @@ def bounded_by_gradual[T: Any](t: T) -> None:
class FinalClass: ...
def bounded_final[T: FinalClass](t: T) -> None:
# revealed: ty_extensions.ConstraintSet[always]
# revealed: ty_extensions.ConstraintSet[(T@bounded_final ≤ FinalClass)]
reveal_type(is_assignable_to(T, Any))
static_assert(is_assignable_to(T, Any))
# revealed: ty_extensions.ConstraintSet[always]
# revealed: ty_extensions.ConstraintSet[(T@bounded_final ≤ FinalClass)]
reveal_type(is_assignable_to(Any, T))
static_assert(is_assignable_to(Any, T))
# revealed: ty_extensions.ConstraintSet[always]
# revealed: ty_extensions.ConstraintSet[(T@bounded_final ≤ FinalClass)]
reveal_type(is_assignable_to(T, FinalClass))
static_assert(is_assignable_to(T, FinalClass))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(T@bounded_final = FinalClass)]
reveal_type(is_assignable_to(FinalClass, T))
static_assert(not is_assignable_to(FinalClass, T))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(T@bounded_final = Never)]
reveal_type(is_subtype_of(T, Any))
static_assert(not is_subtype_of(T, Any))
@@ -354,11 +354,11 @@ def bounded_final[T: FinalClass](t: T) -> None:
reveal_type(is_subtype_of(Any, T))
static_assert(not is_subtype_of(Any, T))
# revealed: ty_extensions.ConstraintSet[always]
# revealed: ty_extensions.ConstraintSet[(T@bounded_final ≤ FinalClass)]
reveal_type(is_subtype_of(T, FinalClass))
static_assert(is_subtype_of(T, FinalClass))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(T@bounded_final = FinalClass)]
reveal_type(is_subtype_of(FinalClass, T))
static_assert(not is_subtype_of(FinalClass, T))
```
@@ -370,36 +370,36 @@ typevars to `Never` in addition to that final class.
```py
def two_bounded[T: Super, U: Super](t: T, u: U) -> None:
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[((T@two_bounded ≤ U@two_bounded) ∧ (U@two_bounded ≤ Super))]
reveal_type(is_assignable_to(T, U))
static_assert(not is_assignable_to(T, U))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[((U@two_bounded ≤ Super) ∧ (U@two_bounded ≤ T@two_bounded ≤ Super))]
reveal_type(is_assignable_to(U, T))
static_assert(not is_assignable_to(U, T))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[((T@two_bounded ≤ U@two_bounded) ∧ (U@two_bounded ≤ Super))]
reveal_type(is_subtype_of(T, U))
static_assert(not is_subtype_of(T, U))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[((U@two_bounded ≤ Super) ∧ (U@two_bounded ≤ T@two_bounded ≤ Super))]
reveal_type(is_subtype_of(U, T))
static_assert(not is_subtype_of(U, T))
def two_final_bounded[T: FinalClass, U: FinalClass](t: T, u: U) -> None:
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[((T@two_final_bounded ≤ U@two_final_bounded) ∧ (U@two_final_bounded ≤ FinalClass))]
reveal_type(is_assignable_to(T, U))
static_assert(not is_assignable_to(T, U))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[((U@two_final_bounded ≤ FinalClass) ∧ (U@two_final_bounded ≤ T@two_final_bounded ≤ FinalClass))]
reveal_type(is_assignable_to(U, T))
static_assert(not is_assignable_to(U, T))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[((T@two_final_bounded ≤ U@two_final_bounded) ∧ (U@two_final_bounded ≤ FinalClass))]
reveal_type(is_subtype_of(T, U))
static_assert(not is_subtype_of(T, U))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[((U@two_final_bounded ≤ FinalClass) ∧ (U@two_final_bounded ≤ T@two_final_bounded ≤ FinalClass))]
reveal_type(is_subtype_of(U, T))
static_assert(not is_subtype_of(U, T))
```
@@ -412,11 +412,11 @@ intersection of all of its constraints is a subtype of the typevar.
from ty_extensions import Intersection
def constrained[T: (Base, Unrelated)](t: T) -> None:
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(T@constrained = Base)]
reveal_type(is_assignable_to(T, Super))
static_assert(not is_assignable_to(T, Super))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(T@constrained = Base)]
reveal_type(is_assignable_to(T, Base))
static_assert(not is_assignable_to(T, Base))
@@ -424,27 +424,27 @@ def constrained[T: (Base, Unrelated)](t: T) -> None:
reveal_type(is_assignable_to(T, Sub))
static_assert(not is_assignable_to(T, Sub))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(T@constrained = Unrelated)]
reveal_type(is_assignable_to(T, Unrelated))
static_assert(not is_assignable_to(T, Unrelated))
# revealed: ty_extensions.ConstraintSet[always]
# revealed: ty_extensions.ConstraintSet[(T@constrained = Base) (T@constrained = Unrelated)]
reveal_type(is_assignable_to(T, Any))
static_assert(is_assignable_to(T, Any))
# revealed: ty_extensions.ConstraintSet[always]
# revealed: ty_extensions.ConstraintSet[(T@constrained = Base) (T@constrained = Unrelated)]
reveal_type(is_assignable_to(T, Super | Unrelated))
static_assert(is_assignable_to(T, Super | Unrelated))
# revealed: ty_extensions.ConstraintSet[always]
# revealed: ty_extensions.ConstraintSet[(T@constrained = Base) (T@constrained = Unrelated)]
reveal_type(is_assignable_to(T, Base | Unrelated))
static_assert(is_assignable_to(T, Base | Unrelated))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(T@constrained = Unrelated)]
reveal_type(is_assignable_to(T, Sub | Unrelated))
static_assert(not is_assignable_to(T, Sub | Unrelated))
# revealed: ty_extensions.ConstraintSet[always]
# revealed: ty_extensions.ConstraintSet[(T@constrained = Base) (T@constrained = Unrelated)]
reveal_type(is_assignable_to(Any, T))
static_assert(is_assignable_to(Any, T))
@@ -452,7 +452,7 @@ def constrained[T: (Base, Unrelated)](t: T) -> None:
reveal_type(is_assignable_to(Super, T))
static_assert(not is_assignable_to(Super, T))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(T@constrained = Unrelated)]
reveal_type(is_assignable_to(Unrelated, T))
static_assert(not is_assignable_to(Unrelated, T))
@@ -460,15 +460,15 @@ def constrained[T: (Base, Unrelated)](t: T) -> None:
reveal_type(is_assignable_to(Super | Unrelated, T))
static_assert(not is_assignable_to(Super | Unrelated, T))
# revealed: ty_extensions.ConstraintSet[always]
# revealed: ty_extensions.ConstraintSet[(T@constrained = Base) (T@constrained = Unrelated)]
reveal_type(is_assignable_to(Intersection[Base, Unrelated], T))
static_assert(is_assignable_to(Intersection[Base, Unrelated], T))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(T@constrained = Base)]
reveal_type(is_subtype_of(T, Super))
static_assert(not is_subtype_of(T, Super))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(T@constrained = Base)]
reveal_type(is_subtype_of(T, Base))
static_assert(not is_subtype_of(T, Base))
@@ -476,7 +476,7 @@ def constrained[T: (Base, Unrelated)](t: T) -> None:
reveal_type(is_subtype_of(T, Sub))
static_assert(not is_subtype_of(T, Sub))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(T@constrained = Unrelated)]
reveal_type(is_subtype_of(T, Unrelated))
static_assert(not is_subtype_of(T, Unrelated))
@@ -484,15 +484,15 @@ def constrained[T: (Base, Unrelated)](t: T) -> None:
reveal_type(is_subtype_of(T, Any))
static_assert(not is_subtype_of(T, Any))
# revealed: ty_extensions.ConstraintSet[always]
# revealed: ty_extensions.ConstraintSet[(T@constrained = Base) (T@constrained = Unrelated)]
reveal_type(is_subtype_of(T, Super | Unrelated))
static_assert(is_subtype_of(T, Super | Unrelated))
# revealed: ty_extensions.ConstraintSet[always]
# revealed: ty_extensions.ConstraintSet[(T@constrained = Base) (T@constrained = Unrelated)]
reveal_type(is_subtype_of(T, Base | Unrelated))
static_assert(is_subtype_of(T, Base | Unrelated))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(T@constrained = Unrelated)]
reveal_type(is_subtype_of(T, Sub | Unrelated))
static_assert(not is_subtype_of(T, Sub | Unrelated))
@@ -504,7 +504,7 @@ def constrained[T: (Base, Unrelated)](t: T) -> None:
reveal_type(is_subtype_of(Super, T))
static_assert(not is_subtype_of(Super, T))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(T@constrained = Unrelated)]
reveal_type(is_subtype_of(Unrelated, T))
static_assert(not is_subtype_of(Unrelated, T))
@@ -512,24 +512,24 @@ def constrained[T: (Base, Unrelated)](t: T) -> None:
reveal_type(is_subtype_of(Super | Unrelated, T))
static_assert(not is_subtype_of(Super | Unrelated, T))
# revealed: ty_extensions.ConstraintSet[always]
# revealed: ty_extensions.ConstraintSet[(T@constrained = Base) (T@constrained = Unrelated)]
reveal_type(is_subtype_of(Intersection[Base, Unrelated], T))
static_assert(is_subtype_of(Intersection[Base, Unrelated], T))
def constrained_by_gradual[T: (Base, Any)](t: T) -> None:
# revealed: ty_extensions.ConstraintSet[always]
# revealed: ty_extensions.ConstraintSet[(T@constrained_by_gradual ≤ Super)]
reveal_type(is_assignable_to(T, Super))
static_assert(is_assignable_to(T, Super))
# revealed: ty_extensions.ConstraintSet[always]
# revealed: ty_extensions.ConstraintSet[(T@constrained_by_gradual ≤ Base)]
reveal_type(is_assignable_to(T, Base))
static_assert(is_assignable_to(T, Base))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(T@constrained_by_gradual ≤ Sub)]
reveal_type(is_assignable_to(T, Sub))
static_assert(not is_assignable_to(T, Sub))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(T@constrained_by_gradual ≤ Unrelated)]
reveal_type(is_assignable_to(T, Unrelated))
static_assert(not is_assignable_to(T, Unrelated))
@@ -541,19 +541,19 @@ def constrained_by_gradual[T: (Base, Any)](t: T) -> None:
reveal_type(is_assignable_to(T, Super | Any))
static_assert(is_assignable_to(T, Super | Any))
# revealed: ty_extensions.ConstraintSet[always]
# revealed: ty_extensions.ConstraintSet[(T@constrained_by_gradual ≤ Super | Unrelated)]
reveal_type(is_assignable_to(T, Super | Unrelated))
static_assert(is_assignable_to(T, Super | Unrelated))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(Super ≤ T@constrained_by_gradual)]
reveal_type(is_assignable_to(Super, T))
static_assert(not is_assignable_to(Super, T))
# revealed: ty_extensions.ConstraintSet[always]
# revealed: ty_extensions.ConstraintSet[(Base ≤ T@constrained_by_gradual)]
reveal_type(is_assignable_to(Base, T))
static_assert(is_assignable_to(Base, T))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(Unrelated ≤ T@constrained_by_gradual)]
reveal_type(is_assignable_to(Unrelated, T))
static_assert(not is_assignable_to(Unrelated, T))
@@ -561,19 +561,19 @@ def constrained_by_gradual[T: (Base, Any)](t: T) -> None:
reveal_type(is_assignable_to(Any, T))
static_assert(is_assignable_to(Any, T))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(Super ≤ T@constrained_by_gradual)]
reveal_type(is_assignable_to(Super | Any, T))
static_assert(not is_assignable_to(Super | Any, T))
# revealed: ty_extensions.ConstraintSet[always]
# revealed: ty_extensions.ConstraintSet[(Base ≤ T@constrained_by_gradual)]
reveal_type(is_assignable_to(Base | Any, T))
static_assert(is_assignable_to(Base | Any, T))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(Super | Unrelated ≤ T@constrained_by_gradual)]
reveal_type(is_assignable_to(Super | Unrelated, T))
static_assert(not is_assignable_to(Super | Unrelated, T))
# revealed: ty_extensions.ConstraintSet[always]
# revealed: ty_extensions.ConstraintSet[(Base & Unrelated ≤ T@constrained_by_gradual)]
reveal_type(is_assignable_to(Intersection[Base, Unrelated], T))
static_assert(is_assignable_to(Intersection[Base, Unrelated], T))
@@ -581,69 +581,69 @@ def constrained_by_gradual[T: (Base, Any)](t: T) -> None:
reveal_type(is_assignable_to(Intersection[Base, Any], T))
static_assert(is_assignable_to(Intersection[Base, Any], T))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(T@constrained_by_gradual ≤ Super)]
reveal_type(is_subtype_of(T, Super))
static_assert(not is_subtype_of(T, Super))
static_assert(is_subtype_of(T, Super))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(T@constrained_by_gradual ≤ Base)]
reveal_type(is_subtype_of(T, Base))
static_assert(not is_subtype_of(T, Base))
static_assert(is_subtype_of(T, Base))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(T@constrained_by_gradual ≤ Sub)]
reveal_type(is_subtype_of(T, Sub))
static_assert(not is_subtype_of(T, Sub))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(T@constrained_by_gradual ≤ Unrelated)]
reveal_type(is_subtype_of(T, Unrelated))
static_assert(not is_subtype_of(T, Unrelated))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(T@constrained_by_gradual = Never)]
reveal_type(is_subtype_of(T, Any))
static_assert(not is_subtype_of(T, Any))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(T@constrained_by_gradual ≤ Super)]
reveal_type(is_subtype_of(T, Super | Any))
static_assert(not is_subtype_of(T, Super | Any))
static_assert(is_subtype_of(T, Super | Any))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(T@constrained_by_gradual ≤ Super | Unrelated)]
reveal_type(is_subtype_of(T, Super | Unrelated))
static_assert(not is_subtype_of(T, Super | Unrelated))
static_assert(is_subtype_of(T, Super | Unrelated))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(Super ≤ T@constrained_by_gradual)]
reveal_type(is_subtype_of(Super, T))
static_assert(not is_subtype_of(Super, T))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(Base ≤ T@constrained_by_gradual)]
reveal_type(is_subtype_of(Base, T))
static_assert(not is_subtype_of(Base, T))
static_assert(is_subtype_of(Base, T))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(Unrelated ≤ T@constrained_by_gradual)]
reveal_type(is_subtype_of(Unrelated, T))
static_assert(not is_subtype_of(Unrelated, T))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(T@constrained_by_gradual = object)]
reveal_type(is_subtype_of(Any, T))
static_assert(not is_subtype_of(Any, T))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(T@constrained_by_gradual = object)]
reveal_type(is_subtype_of(Super | Any, T))
static_assert(not is_subtype_of(Super | Any, T))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(T@constrained_by_gradual = object)]
reveal_type(is_subtype_of(Base | Any, T))
static_assert(not is_subtype_of(Base | Any, T))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(Super | Unrelated ≤ T@constrained_by_gradual)]
reveal_type(is_subtype_of(Super | Unrelated, T))
static_assert(not is_subtype_of(Super | Unrelated, T))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(Base & Unrelated ≤ T@constrained_by_gradual)]
reveal_type(is_subtype_of(Intersection[Base, Unrelated], T))
static_assert(not is_subtype_of(Intersection[Base, Unrelated], T))
static_assert(is_subtype_of(Intersection[Base, Unrelated], T))
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(Base ≤ T@constrained_by_gradual)]
reveal_type(is_subtype_of(Intersection[Base, Any], T))
static_assert(not is_subtype_of(Intersection[Base, Any], T))
static_assert(is_subtype_of(Intersection[Base, Any], T))
```
Two distinct fully static typevars are not subtypes of each other, even if they have the same
@@ -694,19 +694,19 @@ A bound or constrained typevar is a subtype of itself in a union:
```py
def union[T: Base, U: (Base, Unrelated)](t: T, u: U) -> None:
# revealed: ty_extensions.ConstraintSet[always]
# revealed: ty_extensions.ConstraintSet[(T@union ≤ Base)]
reveal_type(is_assignable_to(T, T | None))
static_assert(is_assignable_to(T, T | None))
# revealed: ty_extensions.ConstraintSet[always]
# revealed: ty_extensions.ConstraintSet[(U@union = Base) (U@union = Unrelated)]
reveal_type(is_assignable_to(U, U | None))
static_assert(is_assignable_to(U, U | None))
# revealed: ty_extensions.ConstraintSet[always]
# revealed: ty_extensions.ConstraintSet[(T@union ≤ Base)]
reveal_type(is_subtype_of(T, T | None))
static_assert(is_subtype_of(T, T | None))
# revealed: ty_extensions.ConstraintSet[always]
# revealed: ty_extensions.ConstraintSet[(U@union = Base) (U@union = Unrelated)]
reveal_type(is_subtype_of(U, U | None))
static_assert(is_subtype_of(U, U | None))
```
@@ -715,11 +715,11 @@ A bound or constrained typevar in a union with a dynamic type is assignable to t
```py
def union_with_dynamic[T: Base, U: (Base, Unrelated)](t: T, u: U) -> None:
# revealed: ty_extensions.ConstraintSet[always]
# revealed: ty_extensions.ConstraintSet[(T@union_with_dynamic ≤ Base)]
reveal_type(is_assignable_to(T | Any, T))
static_assert(is_assignable_to(T | Any, T))
# revealed: ty_extensions.ConstraintSet[always]
# revealed: ty_extensions.ConstraintSet[(U@union_with_dynamic = Base) (U@union_with_dynamic = Unrelated)]
reveal_type(is_assignable_to(U | Any, U))
static_assert(is_assignable_to(U | Any, U))
@@ -740,19 +740,19 @@ from ty_extensions import Intersection, Not, is_disjoint_from
class A: ...
def inter[T: Base, U: (Base, Unrelated)](t: T, u: U) -> None:
# revealed: ty_extensions.ConstraintSet[always]
# revealed: ty_extensions.ConstraintSet[(T@inter ≤ Base)]
reveal_type(is_assignable_to(Intersection[T, Unrelated], T))
static_assert(is_assignable_to(Intersection[T, Unrelated], T))
# revealed: ty_extensions.ConstraintSet[always]
# revealed: ty_extensions.ConstraintSet[(T@inter ≤ Base)]
reveal_type(is_subtype_of(Intersection[T, Unrelated], T))
static_assert(is_subtype_of(Intersection[T, Unrelated], T))
# revealed: ty_extensions.ConstraintSet[always]
# revealed: ty_extensions.ConstraintSet[(U@inter = Base) (U@inter = Unrelated)]
reveal_type(is_assignable_to(Intersection[U, A], U))
static_assert(is_assignable_to(Intersection[U, A], U))
# revealed: ty_extensions.ConstraintSet[always]
# revealed: ty_extensions.ConstraintSet[(U@inter = Base) (U@inter = Unrelated)]
reveal_type(is_subtype_of(Intersection[U, A], U))
static_assert(is_subtype_of(Intersection[U, A], U))

View File

@@ -45,29 +45,25 @@ def even_given_unsatisfiable_constraints():
## Type variables
The interesting case is typevars. The other typing relationships (TODO: will) all "punt" on the
question when considering a typevar, by translating the desired relationship into a constraint set.
The interesting case is typevars. The other typing relationships all "punt" on the question when
considering a typevar, by translating the desired relationship into a constraint set.
```py
from typing import Any
from ty_extensions import is_assignable_to, is_subtype_of
def assignability[T]():
# TODO: revealed: ty_extensions.ConstraintSet[T@assignability ≤ bool]
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(T@assignability ≤ bool)]
reveal_type(is_assignable_to(T, bool))
# TODO: revealed: ty_extensions.ConstraintSet[T@assignability ≤ int]
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(T@assignability ≤ int)]
reveal_type(is_assignable_to(T, int))
# revealed: ty_extensions.ConstraintSet[always]
reveal_type(is_assignable_to(T, object))
def subtyping[T]():
# TODO: revealed: ty_extensions.ConstraintSet[T@subtyping ≤ bool]
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(T@subtyping ≤ bool)]
reveal_type(is_subtype_of(T, bool))
# TODO: revealed: ty_extensions.ConstraintSet[T@subtyping ≤ int]
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(T@subtyping ≤ int)]
reveal_type(is_subtype_of(T, int))
# revealed: ty_extensions.ConstraintSet[always]
reveal_type(is_subtype_of(T, object))
@@ -88,49 +84,37 @@ class Contravariant[T]:
pass
def assignability[T]():
# aka [T@assignability ≤ object], which is always satisfiable
# revealed: ty_extensions.ConstraintSet[always]
reveal_type(is_assignable_to(T, Any))
# aka [Never ≤ T@assignability], which is always satisfiable
# revealed: ty_extensions.ConstraintSet[always]
reveal_type(is_assignable_to(Any, T))
# TODO: revealed: ty_extensions.ConstraintSet[T@assignability ≤ Covariant[object]]
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(T@assignability ≤ Covariant[object])]
reveal_type(is_assignable_to(T, Covariant[Any]))
# TODO: revealed: ty_extensions.ConstraintSet[Covariant[Never] ≤ T@assignability]
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(Covariant[Never] ≤ T@assignability)]
reveal_type(is_assignable_to(Covariant[Any], T))
# TODO: revealed: ty_extensions.ConstraintSet[T@assignability ≤ Contravariant[Never]]
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(T@assignability ≤ Contravariant[Never])]
reveal_type(is_assignable_to(T, Contravariant[Any]))
# TODO: revealed: ty_extensions.ConstraintSet[Contravariant[object] ≤ T@assignability]
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(Contravariant[object] ≤ T@assignability)]
reveal_type(is_assignable_to(Contravariant[Any], T))
def subtyping[T]():
# aka [T@assignability ≤ object], which is always satisfiable
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(T@subtyping = Never)]
reveal_type(is_subtype_of(T, Any))
# aka [Never ≤ T@assignability], which is always satisfiable
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(T@subtyping = object)]
reveal_type(is_subtype_of(Any, T))
# TODO: revealed: ty_extensions.ConstraintSet[T@subtyping ≤ Covariant[Never]]
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(T@subtyping ≤ Covariant[Never])]
reveal_type(is_subtype_of(T, Covariant[Any]))
# TODO: revealed: ty_extensions.ConstraintSet[Covariant[object] ≤ T@subtyping]
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(Covariant[object] ≤ T@subtyping)]
reveal_type(is_subtype_of(Covariant[Any], T))
# TODO: revealed: ty_extensions.ConstraintSet[T@subtyping ≤ Contravariant[object]]
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(T@subtyping ≤ Contravariant[object])]
reveal_type(is_subtype_of(T, Contravariant[Any]))
# TODO: revealed: ty_extensions.ConstraintSet[Contravariant[Never] ≤ T@subtyping]
# revealed: ty_extensions.ConstraintSet[never]
# revealed: ty_extensions.ConstraintSet[(Contravariant[Never] ≤ T@subtyping)]
reveal_type(is_subtype_of(Contravariant[Any], T))
```

View File

@@ -1248,14 +1248,10 @@ def identity[T](t: T) -> T:
static_assert(is_assignable_to(TypeOf[identity], Callable[[int], int]))
static_assert(is_assignable_to(TypeOf[identity], Callable[[str], str]))
# TODO: no error
# error: [static-assert-error]
static_assert(not is_assignable_to(TypeOf[identity], Callable[[str], int]))
static_assert(is_assignable_to(CallableTypeOf[identity], Callable[[int], int]))
static_assert(is_assignable_to(CallableTypeOf[identity], Callable[[str], str]))
# TODO: no error
# error: [static-assert-error]
static_assert(not is_assignable_to(CallableTypeOf[identity], Callable[[str], int]))
```

View File

@@ -2221,23 +2221,11 @@ from ty_extensions import CallableTypeOf, TypeOf, is_subtype_of, static_assert
def identity[T](t: T) -> T:
return t
# TODO: Confusingly, these are not the same results as the corresponding checks in
# is_assignable_to.md, even though all of these types are fully static. We have some heuristics that
# currently conflict with each other, that we are in the process of removing with the constraint set
# work.
# TODO: no error
# error: [static-assert-error]
static_assert(is_subtype_of(TypeOf[identity], Callable[[int], int]))
# TODO: no error
# error: [static-assert-error]
static_assert(is_subtype_of(TypeOf[identity], Callable[[str], str]))
static_assert(not is_subtype_of(TypeOf[identity], Callable[[str], int]))
# TODO: no error
# error: [static-assert-error]
static_assert(is_subtype_of(CallableTypeOf[identity], Callable[[int], int]))
# TODO: no error
# error: [static-assert-error]
static_assert(is_subtype_of(CallableTypeOf[identity], Callable[[str], str]))
static_assert(not is_subtype_of(CallableTypeOf[identity], Callable[[str], int]))
```

View File

@@ -1310,7 +1310,7 @@ impl<'db> Type<'db> {
self.filter_union(db, |elem| {
!elem
.when_disjoint_from(db, target, inferable)
.is_always_satisfied(db)
.satisfied_by_all_typevars(db, inferable)
})
}
@@ -1625,7 +1625,7 @@ impl<'db> Type<'db> {
/// See [`TypeRelation::Subtyping`] for more details.
pub(crate) fn is_subtype_of(self, db: &'db dyn Db, target: Type<'db>) -> bool {
self.when_subtype_of(db, target, InferableTypeVars::None)
.is_always_satisfied(db)
.satisfied_by_all_typevars(db, InferableTypeVars::None)
}
fn when_subtype_of(
@@ -1661,7 +1661,7 @@ impl<'db> Type<'db> {
/// See [`TypeRelation::Assignability`] for more details.
pub(crate) fn is_assignable_to(self, db: &'db dyn Db, target: Type<'db>) -> bool {
self.when_assignable_to(db, target, InferableTypeVars::None)
.is_always_satisfied(db)
.satisfied_by_all_typevars(db, InferableTypeVars::None)
}
fn when_assignable_to(
@@ -1679,7 +1679,7 @@ impl<'db> Type<'db> {
#[salsa::tracked(cycle_initial=is_redundant_with_cycle_initial, heap_size=ruff_memory_usage::heap_size)]
pub(crate) fn is_redundant_with(self, db: &'db dyn Db, other: Type<'db>) -> bool {
self.has_relation_to(db, other, InferableTypeVars::None, TypeRelation::Redundancy)
.is_always_satisfied(db)
.satisfied_by_all_typevars(db, InferableTypeVars::None)
}
fn has_relation_to(
@@ -1726,6 +1726,29 @@ impl<'db> Type<'db> {
}
match (self, target) {
// Two identical typevars must always solve to the same type, so they are always
// subtypes of each other and assignable to each other.
//
// Note that this is not handled by the early return at the beginning of this method,
// since subtyping between a TypeVar and an arbitrary other type cannot be guaranteed to be reflexive.
(Type::TypeVar(lhs_bound_typevar), Type::TypeVar(rhs_bound_typevar))
if lhs_bound_typevar.is_same_typevar_as(db, rhs_bound_typevar) =>
{
ConstraintSet::from(true)
}
// A typevar satisfies a relation when...it satisfies the relation. Yes that's a
// tautology! We're moving the caller's subtyping/assignability requirement into a
// constraint set. If the typevar has an upper bound or constraints, then the relation
// only has to hold when the typevar has a valid specialization (i.e., one that
// satisfies the upper bound/constraints).
(Type::TypeVar(bound_typevar), _) => {
ConstraintSet::constrain_typevar(db, bound_typevar, Type::Never, target, relation)
}
(_, Type::TypeVar(bound_typevar)) => {
ConstraintSet::constrain_typevar(db, bound_typevar, self, Type::object(), relation)
}
// Everything is a subtype of `object`.
(_, Type::NominalInstance(instance)) if instance.is_object() => {
ConstraintSet::from(true)
@@ -1829,130 +1852,6 @@ impl<'db> Type<'db> {
},
}),
// In general, a TypeVar `T` is not a subtype of a type `S` unless one of the two conditions is satisfied:
// 1. `T` is a bound TypeVar and `T`'s upper bound is a subtype of `S`.
// TypeVars without an explicit upper bound are treated as having an implicit upper bound of `object`.
// 2. `T` is a constrained TypeVar and all of `T`'s constraints are subtypes of `S`.
//
// However, there is one exception to this general rule: for any given typevar `T`,
// `T` will always be a subtype of any union containing `T`.
// A similar rule applies in reverse to intersection types.
(Type::TypeVar(bound_typevar), Type::Union(union))
if !bound_typevar.is_inferable(db, inferable)
&& union.elements(db).contains(&self) =>
{
ConstraintSet::from(true)
}
(Type::Intersection(intersection), Type::TypeVar(bound_typevar))
if !bound_typevar.is_inferable(db, inferable)
&& intersection.positive(db).contains(&target) =>
{
ConstraintSet::from(true)
}
(Type::Intersection(intersection), Type::TypeVar(bound_typevar))
if !bound_typevar.is_inferable(db, inferable)
&& intersection.negative(db).contains(&target) =>
{
ConstraintSet::from(false)
}
// Two identical typevars must always solve to the same type, so they are always
// subtypes of each other and assignable to each other.
//
// Note that this is not handled by the early return at the beginning of this method,
// since subtyping between a TypeVar and an arbitrary other type cannot be guaranteed to be reflexive.
(Type::TypeVar(lhs_bound_typevar), Type::TypeVar(rhs_bound_typevar))
if !lhs_bound_typevar.is_inferable(db, inferable)
&& lhs_bound_typevar.is_same_typevar_as(db, rhs_bound_typevar) =>
{
ConstraintSet::from(true)
}
// A fully static typevar is a subtype of its upper bound, and to something similar to
// the union of its constraints. An unbound, unconstrained, fully static typevar has an
// implicit upper bound of `object` (which is handled above).
(Type::TypeVar(bound_typevar), _)
if !bound_typevar.is_inferable(db, inferable)
&& bound_typevar.typevar(db).bound_or_constraints(db).is_some() =>
{
match bound_typevar.typevar(db).bound_or_constraints(db) {
None => unreachable!(),
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => bound
.has_relation_to_impl(
db,
target,
inferable,
relation,
relation_visitor,
disjointness_visitor,
),
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => {
constraints.elements(db).iter().when_all(db, |constraint| {
constraint.has_relation_to_impl(
db,
target,
inferable,
relation,
relation_visitor,
disjointness_visitor,
)
})
}
}
}
// If the typevar is constrained, there must be multiple constraints, and the typevar
// might be specialized to any one of them. However, the constraints do not have to be
// disjoint, which means an lhs type might be a subtype of all of the constraints.
(_, Type::TypeVar(bound_typevar))
if !bound_typevar.is_inferable(db, inferable)
&& !bound_typevar
.typevar(db)
.constraints(db)
.when_some_and(|constraints| {
constraints.iter().when_all(db, |constraint| {
self.has_relation_to_impl(
db,
*constraint,
inferable,
relation,
relation_visitor,
disjointness_visitor,
)
})
})
.is_never_satisfied(db) =>
{
// TODO: The repetition here isn't great, but we really need the fallthrough logic,
// where this arm only engages if it returns true (or in the world of constraints,
// not false). Once we're using real constraint sets instead of bool, we should be
// able to simplify the typevar logic.
bound_typevar
.typevar(db)
.constraints(db)
.when_some_and(|constraints| {
constraints.iter().when_all(db, |constraint| {
self.has_relation_to_impl(
db,
*constraint,
inferable,
relation,
relation_visitor,
disjointness_visitor,
)
})
})
}
(Type::TypeVar(bound_typevar), _) if bound_typevar.is_inferable(db, inferable) => {
// The implicit lower bound of a typevar is `Never`, which means
// that it is always assignable to any other type.
// TODO: record the unification constraints
ConstraintSet::from(true)
}
// `Never` is the bottom type, the empty set.
(_, Type::Never) => ConstraintSet::from(false),
@@ -2044,55 +1943,6 @@ impl<'db> Type<'db> {
})
}
// Other than the special cases checked above, no other types are a subtype of a
// typevar, since there's no guarantee what type the typevar will be specialized to.
// (If the typevar is bounded, it might be specialized to a smaller type than the
// bound. This is true even if the bound is a final class, since the typevar can still
// be specialized to `Never`.)
(_, Type::TypeVar(bound_typevar)) if !bound_typevar.is_inferable(db, inferable) => {
ConstraintSet::from(false)
}
(_, Type::TypeVar(typevar))
if typevar.is_inferable(db, inferable)
&& relation.is_assignability()
&& typevar.typevar(db).upper_bound(db).is_none_or(|bound| {
!self
.has_relation_to_impl(
db,
bound,
inferable,
relation,
relation_visitor,
disjointness_visitor,
)
.is_never_satisfied(db)
}) =>
{
// TODO: record the unification constraints
typevar.typevar(db).upper_bound(db).when_none_or(|bound| {
self.has_relation_to_impl(
db,
bound,
inferable,
relation,
relation_visitor,
disjointness_visitor,
)
})
}
// TODO: Infer specializations here
(_, Type::TypeVar(bound_typevar)) if bound_typevar.is_inferable(db, inferable) => {
ConstraintSet::from(false)
}
(Type::TypeVar(bound_typevar), _) => {
// All inferable cases should have been handled above
assert!(!bound_typevar.is_inferable(db, inferable));
ConstraintSet::from(false)
}
(Type::TypedDict(_), _) => {
// TODO: Implement assignability and subtyping for TypedDict
ConstraintSet::from(relation.is_assignability())
@@ -2564,7 +2414,7 @@ impl<'db> Type<'db> {
/// [equivalent to]: https://typing.python.org/en/latest/spec/glossary.html#term-equivalent
pub(crate) fn is_equivalent_to(self, db: &'db dyn Db, other: Type<'db>) -> bool {
self.when_equivalent_to(db, other, InferableTypeVars::None)
.is_always_satisfied(db)
.satisfied_by_all_typevars(db, InferableTypeVars::None)
}
fn when_equivalent_to(
@@ -2691,7 +2541,7 @@ impl<'db> Type<'db> {
/// `false` answers in some cases.
pub(crate) fn is_disjoint_from(self, db: &'db dyn Db, other: Type<'db>) -> bool {
self.when_disjoint_from(db, other, InferableTypeVars::None)
.is_always_satisfied(db)
.satisfied_by_all_typevars(db, InferableTypeVars::None)
}
fn when_disjoint_from(
@@ -4957,7 +4807,7 @@ impl<'db> Type<'db> {
Type::KnownInstance(KnownInstanceType::ConstraintSet(tracked_set)) => {
let constraints = tracked_set.constraints(db);
Truthiness::from(constraints.is_always_satisfied(db))
Truthiness::from(constraints.satisfied_by_all_typevars(db, InferableTypeVars::None))
}
Type::FunctionLiteral(_)
@@ -8211,7 +8061,9 @@ impl<'db> KnownInstanceType<'db> {
Ok(())
}
KnownInstanceType::ConstraintSet(tracked_set) => {
let constraints = tracked_set.constraints(self.db);
let constraints = tracked_set
.constraints(self.db)
.limit_to_valid_specializations(self.db);
write!(
f,
"ty_extensions.ConstraintSet[{}]",

View File

@@ -1658,7 +1658,7 @@ impl<'db> CallableBinding<'db> {
.unwrap_or(Type::unknown());
if argument_type
.when_assignable_to(db, parameter_type, overload.inferable_typevars)
.is_always_satisfied(db)
.satisfied_by_all_typevars(db, overload.inferable_typevars)
{
is_argument_assignable_to_any_overload = true;
break 'overload;
@@ -1888,7 +1888,7 @@ impl<'db> CallableBinding<'db> {
current_parameter_type,
overload.inferable_typevars,
)
.is_always_satisfied(db)
.satisfied_by_all_typevars(db, overload.inferable_typevars)
{
participating_parameter_indexes.insert(parameter_index);
}
@@ -2011,7 +2011,7 @@ impl<'db> CallableBinding<'db> {
first_overload_return_type,
overload.inferable_typevars,
)
.is_always_satisfied(db)
.satisfied_by_all_typevars(db, overload.inferable_typevars)
})
} else {
// No matching overload
@@ -2809,15 +2809,21 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> {
.zip(self.call_expression_tcx.annotation);
self.inferable_typevars = generic_context.inferable_typevars(self.db);
let valid_specializations = generic_context.valid_specializations(self.db);
let mut constraints = ConstraintSet::from(true);
let mut builder = SpecializationBuilder::new(self.db, self.inferable_typevars);
// Prefer the declared type of generic classes.
let preferred_type_mappings = return_with_tcx.and_then(|(return_ty, tcx)| {
tcx.filter_union(self.db, |ty| ty.class_specialization(self.db).is_some())
.class_specialization(self.db)?;
builder.infer(return_ty, tcx).ok()?;
Some(builder.type_mappings().clone())
let return_type_mappings =
return_ty.when_assignable_to(self.db, tcx, self.inferable_typevars);
if return_type_mappings.is_never_satisfied(self.db) {
return None;
}
constraints.intersect(self.db, return_type_mappings);
Some(return_type_mappings)
});
// For a given type variable, we track the variance of any assignments to that type variable
@@ -2837,22 +2843,11 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> {
continue;
};
let argument_type = variadic_argument_type.unwrap_or(argument_type);
let specialization_result = builder.infer_map(
expected_type,
variadic_argument_type.unwrap_or(argument_type),
argument_type,
|(identity, variance, inferred_ty)| {
// Avoid widening the inferred type if it is already assignable to the
// preferred declared type.
if preferred_type_mappings
.as_ref()
.and_then(|types| types.get(&identity))
.is_some_and(|preferred_ty| {
inferred_ty.is_assignable_to(self.db, *preferred_ty)
})
{
return None;
}
variance_in_arguments
.entry(identity)
.and_modify(|current| *current = current.join(variance))
@@ -2868,8 +2863,44 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> {
argument_index: adjusted_argument_index,
});
}
let argument_constraints = expected_type.when_assignable_to(
self.db,
variadic_argument_type.unwrap_or(argument_type),
self.inferable_typevars,
);
if argument_constraints.is_never_satisfied(self.db) {
// This argument is never assignable to its parameter, without considering any
// typevars. This will be caught by `check_argument_types` later.
continue;
}
let valid_argument_constraints =
argument_constraints.and(self.db, || valid_specializations);
if valid_argument_constraints.is_never_satisfied(self.db) {
// There are specializations that make this argument assignable to its
// parameter, but none of them are _valid_ specializations.
// XXX: Figure out which typevars are violated and create a nice
// SpecializationError.
continue;
}
// Avoid widening the inferred type if it is already assignable to the
// preferred declared type.
// XXX: Because constraint sets are ANDed together this might not be needed? AND
// should prefer the tighter specialization.
// XXX: Determine typevar variance per argument
eprintln!(
"--> arg {} {} {}",
argument_index,
argument_constraints.display(self.db),
valid_argument_constraints.display(self.db),
);
constraints.intersect(self.db, valid_argument_constraints);
}
}
eprintln!("--> constraints {}", constraints.display(self.db));
// Attempt to promote any literal types assigned to the specialization.
let maybe_promote = |identity, typevar, ty: Type<'db>| {
@@ -2921,15 +2952,27 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> {
};
// Build the specialization first without inferring the complete type context.
let isolated_specialization = builder
.mapped(generic_context, maybe_promote)
.build(generic_context);
let Ok(isolated_specialization) =
generic_context.specialize_constrained_mapped(self.db, constraints, maybe_promote)
else {
// XXX: better error
eprintln!("--> X1");
return;
};
// XXX: maybe_promote
let isolated_return_ty = self
.return_ty
.apply_specialization(self.db, isolated_specialization);
eprintln!("--> spec {}", isolated_specialization.display_full(self.db));
eprintln!("--> rty {}", isolated_return_ty.display(self.db));
let mut try_infer_tcx = || {
let (return_ty, call_expression_tcx) = return_with_tcx?;
eprintln!(
"--> infer tcx {} {}",
return_ty.display(self.db),
call_expression_tcx.display(self.db)
);
// A type variable is not a useful type-context for expression inference, and applying it
// to the return type can lead to confusing unions in nested generic calls.
@@ -2940,17 +2983,31 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> {
// If the return type is already assignable to the annotated type, we ignore the rest of
// the type context and prefer the narrower inferred type.
if isolated_return_ty.is_assignable_to(self.db, call_expression_tcx) {
eprintln!("--> X2");
return None;
}
// TODO: Ideally we would infer the annotated type _before_ the arguments if this call is part of an
// annotated assignment, to closer match the order of any unions written in the type annotation.
builder.infer(return_ty, call_expression_tcx).ok()?;
let return_constraints = return_ty
.when_assignable_to(self.db, call_expression_tcx, self.inferable_typevars)
.and(self.db, || valid_specializations);
if return_constraints.is_never_satisfied(self.db) {
eprintln!("--> X3");
return None;
}
// Otherwise, build the specialization again after inferring the complete type context.
let specialization = builder
.mapped(generic_context, maybe_promote)
.build(generic_context);
let Ok(specialization) = generic_context.specialize_constrained_mapped(
self.db,
constraints.and(self.db, || return_constraints),
maybe_promote,
) else {
// XXX: better return
eprintln!("--> X4");
return None;
};
// XXX: maybe_promote
let return_ty = return_ty.apply_specialization(self.db, specialization);
Some((Some(specialization), return_ty))
@@ -2970,19 +3027,40 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> {
let parameters = self.signature.parameters();
let parameter = &parameters[parameter_index];
if let Some(mut expected_ty) = parameter.annotated_type() {
eprintln!(
"--> before {:?} {} {}",
adjusted_argument_index,
argument_type.display(self.db),
expected_ty.display(self.db),
);
if let Some(specialization) = self.specialization {
argument_type = argument_type.apply_specialization(self.db, specialization);
expected_ty = expected_ty.apply_specialization(self.db, specialization);
eprintln!(
"--> after {:?} {} {}",
adjusted_argument_index,
argument_type.display(self.db),
expected_ty.display(self.db),
);
eprintln!(" {:?}", argument_type);
eprintln!(" {:?}", expected_ty);
}
// This is one of the few places where we want to check if there's _any_ specialization
// where assignability holds; normally we want to check that assignability holds for
// _all_ specializations.
// TODO: Soon we will go further, and build the actual specializations from the
// constraint set that we get from this assignability check, instead of inferring and
// building them in an earlier separate step.
if argument_type
let when =
argument_type.when_assignable_to(self.db, expected_ty, self.inferable_typevars);
eprintln!("===> check argument");
eprintln!(" --> arg {}", argument_type.display(self.db));
eprintln!(" --> param {}", expected_ty.display(self.db));
eprintln!(" --> when {}", when.display(self.db));
eprintln!(
" --> sat {}",
when.satisfied_by_all_typevars(self.db, self.inferable_typevars)
);
if !argument_type
.when_assignable_to(self.db, expected_ty, self.inferable_typevars)
.is_never_satisfied(self.db)
.satisfied_by_all_typevars(self.db, self.inferable_typevars)
{
let positional = matches!(argument, Argument::Positional | Argument::Synthetic)
&& !parameter.is_variadic();
@@ -3116,7 +3194,7 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> {
KnownClass::Str.to_instance(self.db),
self.inferable_typevars,
)
.is_always_satisfied(self.db)
.satisfied_by_all_typevars(self.db, self.inferable_typevars)
{
self.errors.push(BindingError::InvalidKeyType {
argument_index: adjusted_argument_index,

View File

@@ -516,7 +516,7 @@ impl<'db> ClassType<'db> {
/// Return `true` if `other` is present in this class's MRO.
pub(super) fn is_subclass_of(self, db: &'db dyn Db, other: ClassType<'db>) -> bool {
self.when_subclass_of(db, other, InferableTypeVars::None)
.is_always_satisfied(db)
.satisfied_by_all_typevars(db, InferableTypeVars::None)
}
pub(super) fn when_subclass_of(

View File

@@ -168,15 +168,11 @@ pub struct ConstraintSet<'db> {
impl<'db> ConstraintSet<'db> {
fn never() -> Self {
Self {
node: Node::AlwaysFalse,
}
Node::AlwaysFalse.into()
}
fn always() -> Self {
Self {
node: Node::AlwaysTrue,
}
Node::AlwaysTrue.into()
}
/// Returns a constraint set that constraints a typevar to a particular range of types.
@@ -200,9 +196,7 @@ impl<'db> ConstraintSet<'db> {
),
};
Self {
node: ConstrainedTypeVar::new_node(db, typevar, lower, upper),
}
ConstrainedTypeVar::new_node(db, typevar, lower, upper).into()
}
/// Returns whether this constraint set never holds
@@ -318,9 +312,7 @@ impl<'db> ConstraintSet<'db> {
lhs: Type<'db>,
rhs: Type<'db>,
) -> Self {
Self {
node: self.node.implies_subtype_of(db, lhs, rhs),
}
self.node.implies_subtype_of(db, lhs, rhs).into()
}
/// Returns whether this constraint set is satisfied by all of the typevars that it mentions.
@@ -345,6 +337,17 @@ impl<'db> ConstraintSet<'db> {
self.node.satisfied_by_all_typevars(db, inferable)
}
/// Returns a new constraint set that ensures that all typevars mentioned in the current
/// constraint set have valid specializations, according to any upper bound or constraints they
/// might have.
pub(crate) fn limit_to_valid_specializations(self, db: &'db dyn Db) -> Self {
let mut result = self;
self.node.for_each_constraint(db, &mut |constraint| {
result.intersect(db, constraint.typevar(db).valid_specializations(db));
});
result.into()
}
/// Updates this constraint set to hold the union of itself and another constraint set.
pub(crate) fn union(&mut self, db: &'db dyn Db, other: Self) -> Self {
self.node = self.node.or(db, other.node);
@@ -359,9 +362,7 @@ impl<'db> ConstraintSet<'db> {
/// Returns the negation of this constraint set.
pub(crate) fn negate(self, db: &'db dyn Db) -> Self {
Self {
node: self.node.negate(db),
}
self.node.negate(db).into()
}
/// Returns the intersection of this constraint set and another. The other constraint set is
@@ -390,9 +391,7 @@ impl<'db> ConstraintSet<'db> {
}
pub(crate) fn iff(self, db: &'db dyn Db, other: Self) -> Self {
ConstraintSet {
node: self.node.iff(db, other.node),
}
self.node.iff(db, other.node).into()
}
/// Reduces the set of inferable typevars for this constraint set. You provide an iterator of
@@ -429,6 +428,12 @@ impl From<bool> for ConstraintSet<'_> {
}
}
impl<'db> From<Node<'db>> for ConstraintSet<'db> {
fn from(node: Node<'db>) -> Self {
Self { node }
}
}
impl<'db> BoundTypeVarInstance<'db> {
/// Returns whether this typevar can be the lower or upper bound of another typevar in a
/// constraint set.
@@ -1046,7 +1051,7 @@ impl<'db> Node<'db> {
// If the typevar is in inferable position, we need to verify that some valid
// specialization satisfies the constraint set.
let valid_specializations = typevar.valid_specializations(db);
if !some_specialization_satisfies(valid_specializations) {
if !some_specialization_satisfies(valid_specializations.node) {
return false;
}
} else {
@@ -2971,7 +2976,7 @@ impl<'db> BoundTypeVarInstance<'db> {
/// Returns the valid specializations of a typevar. This is used when checking a constraint set
/// when this typevar is in inferable position, where we only need _some_ specialization to
/// satisfy the constraint set.
fn valid_specializations(self, db: &'db dyn Db) -> Node<'db> {
pub(crate) fn valid_specializations(self, db: &'db dyn Db) -> ConstraintSet<'db> {
// For gradual upper bounds and constraints, we are free to choose any materialization that
// makes the check succeed. In inferable positions, it is most helpful to choose a
// materialization that is as permissive as possible, since that maximizes the number of
@@ -2983,10 +2988,10 @@ impl<'db> BoundTypeVarInstance<'db> {
// that _some_ valid specialization satisfies the constraint set, it's correct for us to
// return the range of valid materializations that we can choose from.
match self.typevar(db).bound_or_constraints(db) {
None => Node::AlwaysTrue,
None => ConstraintSet::from(true),
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => {
let bound = bound.top_materialization(db);
ConstrainedTypeVar::new_node(db, self, Type::Never, bound)
ConstrainedTypeVar::new_node(db, self, Type::Never, bound).into()
}
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => {
let mut specializations = Node::AlwaysFalse;
@@ -2998,7 +3003,7 @@ impl<'db> BoundTypeVarInstance<'db> {
ConstrainedTypeVar::new_node(db, self, constraint_lower, constraint_upper),
);
}
specializations
specializations.into()
}
}
}
@@ -3055,10 +3060,25 @@ impl<'db> BoundTypeVarInstance<'db> {
}
impl<'db> GenericContext<'db> {
/// Returns the valid specializations of all of the typevars in this generic context.
pub(crate) fn valid_specializations(self, db: &'db dyn Db) -> ConstraintSet<'db> {
self.variables(db)
.when_all(db, |bound_typevar| bound_typevar.valid_specializations(db))
}
pub(crate) fn specialize_constrained(
self,
db: &'db dyn Db,
constraints: ConstraintSet<'db>,
) -> Result<Specialization<'db>, ()> {
self.specialize_constrained_mapped(db, constraints, |_, _, ty| ty)
}
pub(crate) fn specialize_constrained_mapped(
self,
db: &'db dyn Db,
constraints: ConstraintSet<'db>,
f: impl Fn(BoundTypeVarIdentity<'db>, BoundTypeVarInstance<'db>, Type<'db>) -> Type<'db>,
) -> Result<Specialization<'db>, ()> {
// If the constraint set is cyclic, don't even try to construct a specialization.
if constraints.is_cyclic(db) {
@@ -3072,7 +3092,7 @@ impl<'db> GenericContext<'db> {
let abstracted = self
.variables(db)
.fold(constraints.node, |constraints, bound_typevar| {
constraints.and(db, bound_typevar.valid_specializations(db))
constraints.and(db, bound_typevar.valid_specializations(db).node)
});
// Then we find all of the "representative types" for each typevar in the constraint set.
@@ -3091,17 +3111,14 @@ impl<'db> GenericContext<'db> {
let mut satisfied = false;
let mut greatest_lower_bound = Type::Never;
let mut least_upper_bound = Type::object();
abstracted.find_representative_types(
db,
bound_typevar.identity(db),
|lower_bound, upper_bound| {
satisfied = true;
greatest_lower_bound =
UnionType::from_elements(db, [greatest_lower_bound, lower_bound]);
least_upper_bound =
IntersectionType::from_elements(db, [least_upper_bound, upper_bound]);
},
);
let identity = bound_typevar.identity(db);
abstracted.find_representative_types(db, identity, |lower_bound, upper_bound| {
satisfied = true;
greatest_lower_bound =
UnionType::from_elements(db, [greatest_lower_bound, lower_bound]);
least_upper_bound =
IntersectionType::from_elements(db, [least_upper_bound, upper_bound]);
});
// If there are no satisfiable paths in the BDD, then there is no valid specialization
// for this constraint set.
@@ -3119,7 +3136,7 @@ impl<'db> GenericContext<'db> {
// Of all of the types that satisfy all of the paths in the BDD, we choose the
// "largest" one (i.e., "closest to `object`") as the specialization.
types[i] = least_upper_bound;
types[i] = f(identity, bound_typevar, least_upper_bound);
}
Ok(self.specialize_recursive(db, types.into_boxed_slice()))

View File

@@ -1432,6 +1432,7 @@ impl<'db> SpecializationBuilder<'db> {
mut f: &mut dyn FnMut(TypeVarAssignment<'db>) -> Option<Type<'db>>,
) -> Result<(), SpecializationError<'db>> {
if formal == actual {
eprintln!(" --> AAA");
return Ok(());
}
@@ -1506,10 +1507,11 @@ impl<'db> SpecializationBuilder<'db> {
if !actual.is_never() {
let assignable_elements = (formal.elements(self.db).iter()).filter(|ty| {
actual
.when_subtype_of(self.db, **ty, self.inferable)
.is_always_satisfied(self.db)
.when_subtype_of(self.db, **ty, InferableTypeVars::None)
.satisfied_by_all_typevars(self.db, InferableTypeVars::None)
});
if assignable_elements.exactly_one().is_ok() {
eprintln!(" --> BBB");
return Ok(());
}
}
@@ -1538,7 +1540,7 @@ impl<'db> SpecializationBuilder<'db> {
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => {
if !ty
.when_assignable_to(self.db, bound, self.inferable)
.is_always_satisfied(self.db)
.satisfied_by_all_typevars(self.db, self.inferable)
{
return Err(SpecializationError::MismatchedBound {
bound_typevar,
@@ -1559,7 +1561,7 @@ impl<'db> SpecializationBuilder<'db> {
for constraint in constraints.elements(self.db) {
if ty
.when_assignable_to(self.db, *constraint, self.inferable)
.is_always_satisfied(self.db)
.satisfied_by_all_typevars(self.db, self.inferable)
{
self.add_type_mapping(bound_typevar, *constraint, polarity, f);
return Ok(());

View File

@@ -679,7 +679,7 @@ impl<'db> ProtocolInstanceType<'db> {
&HasRelationToVisitor::default(),
&IsDisjointVisitor::default(),
)
.is_always_satisfied(db)
.satisfied_by_all_typevars(db, InferableTypeVars::None)
}
fn initial<'db>(

View File

@@ -876,19 +876,22 @@ impl<'db> Signature<'db> {
let mut check_types = |type1: Option<Type<'db>>, type2: Option<Type<'db>>| {
let type1 = type1.unwrap_or(Type::unknown());
let type2 = type2.unwrap_or(Type::unknown());
!result
.intersect(
db,
type1.has_relation_to_impl(
db,
type2,
inferable,
relation,
relation_visitor,
disjointness_visitor,
),
)
.is_never_satisfied(db)
eprintln!(" --> check param");
eprintln!(" {}", type1.display(db));
eprintln!(" {}", type2.display(db));
let x = type1.has_relation_to_impl(
db,
type2,
inferable,
relation,
relation_visitor,
disjointness_visitor,
);
eprintln!(" x {}", x.display(db),);
let y = result.intersect(db, x);
eprintln!(" y {}", y.display(db),);
eprintln!(" ? {}", !y.is_never_satisfied(db));
!y.is_never_satisfied(db)
};
// Return types are covariant.
@@ -932,6 +935,7 @@ impl<'db> Signature<'db> {
let Some(next_parameter) = parameters.next() else {
// All parameters have been checked or both the parameter lists were empty. In
// either case, `self` is a subtype of `other`.
eprintln!(" --> X1 {}", result.display(db));
return result;
};
@@ -952,6 +956,7 @@ impl<'db> Signature<'db> {
// `other`, then the non-variadic parameters in `self` must have a default
// value.
if default_type.is_none() {
eprintln!(" --> X2");
return ConstraintSet::from(false);
}
}
@@ -964,6 +969,7 @@ impl<'db> Signature<'db> {
EitherOrBoth::Right(_) => {
// If there are more parameters in `other` than in `self`, then `self` is not a
// subtype of `other`.
eprintln!(" --> X3");
return ConstraintSet::from(false);
}
@@ -984,12 +990,14 @@ impl<'db> Signature<'db> {
},
) => {
if self_default.is_none() && other_default.is_some() {
eprintln!(" --> X4");
return ConstraintSet::from(false);
}
if !check_types(
other_parameter.annotated_type(),
self_parameter.annotated_type(),
) {
eprintln!(" --> X5");
return result;
}
}
@@ -1005,16 +1013,19 @@ impl<'db> Signature<'db> {
},
) => {
if self_name != other_name {
eprintln!(" --> X6");
return ConstraintSet::from(false);
}
// The following checks are the same as positional-only parameters.
if self_default.is_none() && other_default.is_some() {
eprintln!(" --> X7");
return ConstraintSet::from(false);
}
if !check_types(
other_parameter.annotated_type(),
self_parameter.annotated_type(),
) {
eprintln!(" --> X8");
return result;
}
}
@@ -1028,6 +1039,7 @@ impl<'db> Signature<'db> {
other_parameter.annotated_type(),
self_parameter.annotated_type(),
) {
eprintln!(" --> X9");
return result;
}
@@ -1068,6 +1080,7 @@ impl<'db> Signature<'db> {
other_parameter.annotated_type(),
self_parameter.annotated_type(),
) {
eprintln!(" --> X10");
return result;
}
parameters.next_other();
@@ -1079,6 +1092,7 @@ impl<'db> Signature<'db> {
other_parameter.annotated_type(),
self_parameter.annotated_type(),
) {
eprintln!(" --> X11");
return result;
}
}
@@ -1094,12 +1108,16 @@ impl<'db> Signature<'db> {
break;
}
_ => return ConstraintSet::from(false),
_ => {
eprintln!(" --> X12");
return ConstraintSet::from(false);
}
}
}
}
}
eprintln!(" --> YYY");
// At this point, the remaining parameters in `other` are keyword-only or keyword variadic.
// But, `self` could contain any unmatched positional parameters.
let (self_parameters, other_parameters) = parameters.into_remaining();