add with_inferable mdtest function

This commit is contained in:
Douglas Creager 2025-11-08 10:04:01 -05:00
parent b033a42ced
commit e6ec4062d5
5 changed files with 143 additions and 102 deletions

View File

@ -60,29 +60,29 @@ class Sub(Base): ...
class Unrelated: ...
def unbounded[T]():
static_assert(ConstraintSet.always().satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(ConstraintSet.always().with_inferable(T).satisfied_by_all_typevars())
static_assert(ConstraintSet.always().satisfied_by_all_typevars())
static_assert(not ConstraintSet.never().satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(not ConstraintSet.never().with_inferable(T).satisfied_by_all_typevars())
static_assert(not ConstraintSet.never().satisfied_by_all_typevars())
# (T = Never) is a valid specialization, which satisfies (T ≤ Unrelated).
static_assert(ConstraintSet.range(Never, T, Unrelated).satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(ConstraintSet.range(Never, T, Unrelated).with_inferable(T).satisfied_by_all_typevars())
# (T = Base) is a valid specialization, which does not satisfy (T ≤ Unrelated).
static_assert(not ConstraintSet.range(Never, T, Unrelated).satisfied_by_all_typevars())
# (T = Base) is a valid specialization, which satisfies (T ≤ Super).
static_assert(ConstraintSet.range(Never, T, Super).satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(ConstraintSet.range(Never, T, Super).with_inferable(T).satisfied_by_all_typevars())
# (T = Unrelated) is a valid specialization, which does not satisfy (T ≤ Super).
static_assert(not ConstraintSet.range(Never, T, Super).satisfied_by_all_typevars())
# (T = Base) is a valid specialization, which satisfies (T ≤ Base).
static_assert(ConstraintSet.range(Never, T, Base).satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(ConstraintSet.range(Never, T, Base).with_inferable(T).satisfied_by_all_typevars())
# (T = Unrelated) is a valid specialization, which does not satisfy (T ≤ Base).
static_assert(not ConstraintSet.range(Never, T, Base).satisfied_by_all_typevars())
# (T = Sub) is a valid specialization, which satisfies (T ≤ Sub).
static_assert(ConstraintSet.range(Never, T, Sub).satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(ConstraintSet.range(Never, T, Sub).with_inferable(T).satisfied_by_all_typevars())
# (T = Unrelated) is a valid specialization, which does not satisfy (T ≤ Sub).
static_assert(not ConstraintSet.range(Never, T, Sub).satisfied_by_all_typevars())
```
@ -106,38 +106,38 @@ class Sub(Base): ...
class Unrelated: ...
def bounded[T: Base]():
static_assert(ConstraintSet.always().satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(ConstraintSet.always().with_inferable(T).satisfied_by_all_typevars())
static_assert(ConstraintSet.always().satisfied_by_all_typevars())
static_assert(not ConstraintSet.never().satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(not ConstraintSet.never().with_inferable(T).satisfied_by_all_typevars())
static_assert(not ConstraintSet.never().satisfied_by_all_typevars())
# (T = Base) is a valid specialization, which satisfies (T ≤ Super).
static_assert(ConstraintSet.range(Never, T, Super).satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(ConstraintSet.range(Never, T, Super).with_inferable(T).satisfied_by_all_typevars())
# Every valid specialization satisfies (T ≤ Base). Since (Base ≤ Super), every valid
# specialization also satisfies (T ≤ Super).
static_assert(ConstraintSet.range(Never, T, Super).satisfied_by_all_typevars())
# (T = Base) is a valid specialization, which satisfies (T ≤ Base).
static_assert(ConstraintSet.range(Never, T, Base).satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(ConstraintSet.range(Never, T, Base).with_inferable(T).satisfied_by_all_typevars())
# Every valid specialization satisfies (T ≤ Base).
static_assert(ConstraintSet.range(Never, T, Base).satisfied_by_all_typevars())
# (T = Sub) is a valid specialization, which satisfies (T ≤ Sub).
static_assert(ConstraintSet.range(Never, T, Sub).satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(ConstraintSet.range(Never, T, Sub).with_inferable(T).satisfied_by_all_typevars())
# (T = Base) is a valid specialization, which does not satisfy (T ≤ Sub).
static_assert(not ConstraintSet.range(Never, T, Sub).satisfied_by_all_typevars())
# (T = Never) is a valid specialization, which satisfies (T ≤ Unrelated).
constraints = ConstraintSet.range(Never, T, Unrelated)
static_assert(constraints.satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(constraints.with_inferable(T).satisfied_by_all_typevars())
# (T = Base) is a valid specialization, which does not satisfy (T ≤ Unrelated).
static_assert(not constraints.satisfied_by_all_typevars())
# Never is the only type that satisfies both (T ≤ Base) and (T ≤ Unrelated). So there is no
# valid specialization that satisfies (T ≤ Unrelated ∧ T ≠ Never).
constraints = constraints & ~ConstraintSet.range(Never, T, Never)
static_assert(not constraints.satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(not constraints.with_inferable(T).satisfied_by_all_typevars())
static_assert(not constraints.satisfied_by_all_typevars())
```
@ -153,15 +153,15 @@ the constraint set.
from typing import Any
def bounded_by_gradual[T: Any]():
static_assert(ConstraintSet.always().satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(ConstraintSet.always().with_inferable(T).satisfied_by_all_typevars())
static_assert(ConstraintSet.always().satisfied_by_all_typevars())
static_assert(not ConstraintSet.never().satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(not ConstraintSet.never().with_inferable(T).satisfied_by_all_typevars())
static_assert(not ConstraintSet.never().satisfied_by_all_typevars())
# If we choose Base as the materialization for the upper bound, then (T = Base) is a valid
# specialization, which satisfies (T ≤ Base).
static_assert(ConstraintSet.range(Never, T, Base).satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(ConstraintSet.range(Never, T, Base).with_inferable(T).satisfied_by_all_typevars())
# We are free to choose any materialization of the upper bound, and only have to show that the
# constraint set holds for that one materialization. Having chosen one materialization, we then
# have to show that the constraint set holds for all valid specializations of that
@ -173,7 +173,7 @@ def bounded_by_gradual[T: Any]():
# If we choose Unrelated as the materialization, then (T = Unrelated) is a valid specialization,
# which satisfies (T ≤ Unrelated).
constraints = ConstraintSet.range(Never, T, Unrelated)
static_assert(constraints.satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(constraints.with_inferable(T).satisfied_by_all_typevars())
# If we choose Never as the materialization, then (T = Never) is the only valid specialization,
# which satisfies (T ≤ Unrelated).
static_assert(constraints.satisfied_by_all_typevars())
@ -181,7 +181,7 @@ def bounded_by_gradual[T: Any]():
# If we choose Unrelated as the materialization, then (T = Unrelated) is a valid specialization,
# which satisfies (T ≤ Unrelated ∧ T ≠ Never).
constraints = constraints & ~ConstraintSet.range(Never, T, Never)
static_assert(constraints.satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(constraints.with_inferable(T).satisfied_by_all_typevars())
# There is no upper bound that we can choose to satisfy this constraint set in non-inferable
# position. (T = Never) will be a valid assignment no matter what, and that does not satisfy
# (T ≤ Unrelated ∧ T ≠ Never).
@ -196,15 +196,15 @@ restrictive variance (i.e., invariance), but we get the same results for other v
```py
def bounded_by_gradual[T: list[Any]]():
static_assert(ConstraintSet.always().satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(ConstraintSet.always().with_inferable(T).satisfied_by_all_typevars())
static_assert(ConstraintSet.always().satisfied_by_all_typevars())
static_assert(not ConstraintSet.never().satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(not ConstraintSet.never().with_inferable(T).satisfied_by_all_typevars())
static_assert(not ConstraintSet.never().satisfied_by_all_typevars())
# If we choose list[Base] as the materialization of the upper bound, then (T = list[Base]) is a
# valid specialization, which satisfies (T ≤ list[Base]).
static_assert(ConstraintSet.range(Never, T, list[Base]).satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(ConstraintSet.range(Never, T, list[Base]).with_inferable(T).satisfied_by_all_typevars())
# If we choose Base as the materialization, then all valid specializations must satisfy
# (T ≤ list[Base]).
# We are free to choose any materialization of the upper bound, and only have to show that the
@ -217,7 +217,7 @@ def bounded_by_gradual[T: list[Any]]():
# If we choose Unrelated as the materialization, then (T = list[Unrelated]) is a valid
# specialization, which satisfies (T ≤ list[Unrelated]).
constraints = ConstraintSet.range(Never, T, list[Unrelated])
static_assert(constraints.satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(constraints.with_inferable(T).satisfied_by_all_typevars())
# If we choose Unrelated as the materialization, then all valid specializations must satisfy
# (T ≤ list[Unrelated]).
static_assert(constraints.satisfied_by_all_typevars())
@ -225,7 +225,7 @@ def bounded_by_gradual[T: list[Any]]():
# If we choose Unrelated as the materialization, then (T = list[Unrelated]) is a valid
# specialization, which satisfies (T ≤ list[Unrelated] ∧ T ≠ Never).
constraints = constraints & ~ConstraintSet.range(Never, T, Never)
static_assert(constraints.satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(constraints.with_inferable(T).satisfied_by_all_typevars())
# There is no upper bound that we can choose to satisfy this constraint set in non-inferable
# position. (T = Never) will be a valid assignment no matter what, and that does not satisfy
# (T ≤ list[Unrelated] ∧ T ≠ Never).
@ -251,61 +251,61 @@ class Sub(Base): ...
class Unrelated: ...
def constrained[T: (Base, Unrelated)]():
static_assert(ConstraintSet.always().satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(ConstraintSet.always().with_inferable(T).satisfied_by_all_typevars())
static_assert(ConstraintSet.always().satisfied_by_all_typevars())
static_assert(not ConstraintSet.never().satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(not ConstraintSet.never().with_inferable(T).satisfied_by_all_typevars())
static_assert(not ConstraintSet.never().satisfied_by_all_typevars())
# (T = Unrelated) is a valid specialization, which satisfies (T ≤ Unrelated).
static_assert(ConstraintSet.range(Never, T, Unrelated).satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(ConstraintSet.range(Never, T, Unrelated).with_inferable(T).satisfied_by_all_typevars())
# (T = Base) is a valid specialization, which does not satisfy (T ≤ Unrelated).
static_assert(not ConstraintSet.range(Never, T, Unrelated).satisfied_by_all_typevars())
# (T = Base) is a valid specialization, which satisfies (T ≤ Super).
static_assert(ConstraintSet.range(Never, T, Super).satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(ConstraintSet.range(Never, T, Super).with_inferable(T).satisfied_by_all_typevars())
# (T = Unrelated) is a valid specialization, which does not satisfy (T ≤ Super).
static_assert(not ConstraintSet.range(Never, T, Super).satisfied_by_all_typevars())
# (T = Base) is a valid specialization, which satisfies (T ≤ Base).
static_assert(ConstraintSet.range(Never, T, Base).satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(ConstraintSet.range(Never, T, Base).with_inferable(T).satisfied_by_all_typevars())
# (T = Unrelated) is a valid specialization, which does not satisfy (T ≤ Base).
static_assert(not ConstraintSet.range(Never, T, Base).satisfied_by_all_typevars())
# Neither (T = Base) nor (T = Unrelated) satisfy (T ≤ Sub).
static_assert(not ConstraintSet.range(Never, T, Sub).satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(not ConstraintSet.range(Never, T, Sub).with_inferable(T).satisfied_by_all_typevars())
static_assert(not ConstraintSet.range(Never, T, Sub).satisfied_by_all_typevars())
# (T = Base) and (T = Unrelated) both satisfy (T ≤ Super T ≤ Unrelated).
constraints = ConstraintSet.range(Never, T, Super) | ConstraintSet.range(Never, T, Unrelated)
static_assert(constraints.satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(constraints.with_inferable(T).satisfied_by_all_typevars())
static_assert(constraints.satisfied_by_all_typevars())
# (T = Base) and (T = Unrelated) both satisfy (T ≤ Base T ≤ Unrelated).
constraints = ConstraintSet.range(Never, T, Base) | ConstraintSet.range(Never, T, Unrelated)
static_assert(constraints.satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(constraints.with_inferable(T).satisfied_by_all_typevars())
static_assert(constraints.satisfied_by_all_typevars())
# (T = Unrelated) is a valid specialization, which satisfies (T ≤ Sub T ≤ Unrelated).
constraints = ConstraintSet.range(Never, T, Sub) | ConstraintSet.range(Never, T, Unrelated)
static_assert(constraints.satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(constraints.with_inferable(T).satisfied_by_all_typevars())
# (T = Base) is a valid specialization, which does not satisfy (T ≤ Sub T ≤ Unrelated).
static_assert(not constraints.satisfied_by_all_typevars())
# (T = Unrelated) is a valid specialization, which satisfies (T = Super T = Unrelated).
constraints = ConstraintSet.range(Super, T, Super) | ConstraintSet.range(Unrelated, T, Unrelated)
static_assert(constraints.satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(constraints.with_inferable(T).satisfied_by_all_typevars())
# (T = Base) is a valid specialization, which does not satisfy (T = Super T = Unrelated).
static_assert(not constraints.satisfied_by_all_typevars())
# (T = Base) and (T = Unrelated) both satisfy (T = Base T = Unrelated).
constraints = ConstraintSet.range(Base, T, Base) | ConstraintSet.range(Unrelated, T, Unrelated)
static_assert(constraints.satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(constraints.with_inferable(T).satisfied_by_all_typevars())
static_assert(constraints.satisfied_by_all_typevars())
# (T = Unrelated) is a valid specialization, which satisfies (T = Sub T = Unrelated).
constraints = ConstraintSet.range(Sub, T, Sub) | ConstraintSet.range(Unrelated, T, Unrelated)
static_assert(constraints.satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(constraints.with_inferable(T).satisfied_by_all_typevars())
# (T = Base) is a valid specialization, which does not satisfy (T = Sub T = Unrelated).
static_assert(not constraints.satisfied_by_all_typevars())
```
@ -322,50 +322,50 @@ satisfy the constraint set.
from typing import Any
def constrained_by_gradual[T: (Base, Any)]():
static_assert(ConstraintSet.always().satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(ConstraintSet.always().with_inferable(T).satisfied_by_all_typevars())
static_assert(ConstraintSet.always().satisfied_by_all_typevars())
static_assert(not ConstraintSet.never().satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(not ConstraintSet.never().with_inferable(T).satisfied_by_all_typevars())
static_assert(not ConstraintSet.never().satisfied_by_all_typevars())
# If we choose Unrelated as the materialization of the gradual constraint, then (T = Unrelated)
# is a valid specialization, which satisfies (T ≤ Unrelated).
static_assert(ConstraintSet.range(Never, T, Unrelated).satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(ConstraintSet.range(Never, T, Unrelated).with_inferable(T).satisfied_by_all_typevars())
# No matter which materialization we choose, (T = Base) is a valid specialization, which does
# not satisfy (T ≤ Unrelated).
static_assert(not ConstraintSet.range(Never, T, Unrelated).satisfied_by_all_typevars())
# If we choose Super as the materialization, then (T = Super) is a valid specialization, which
# satisfies (T ≤ Super).
static_assert(ConstraintSet.range(Never, T, Super).satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(ConstraintSet.range(Never, T, Super).with_inferable(T).satisfied_by_all_typevars())
# If we choose Never as the materialization, then (T = Base) and (T = Never) are the only valid
# specializations, both of which satisfy (T ≤ Super).
static_assert(ConstraintSet.range(Never, T, Super).satisfied_by_all_typevars())
# If we choose Base as the materialization, then (T = Base) is a valid specialization, which
# satisfies (T ≤ Base).
static_assert(ConstraintSet.range(Never, T, Base).satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(ConstraintSet.range(Never, T, Base).with_inferable(T).satisfied_by_all_typevars())
# If we choose Never as the materialization, then (T = Base) and (T = Never) are the only valid
# specializations, both of which satisfy (T ≤ Base).
static_assert(ConstraintSet.range(Never, T, Base).satisfied_by_all_typevars())
def constrained_by_two_gradual[T: (Any, Any)]():
static_assert(ConstraintSet.always().satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(ConstraintSet.always().with_inferable(T).satisfied_by_all_typevars())
static_assert(ConstraintSet.always().satisfied_by_all_typevars())
static_assert(not ConstraintSet.never().satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(not ConstraintSet.never().with_inferable(T).satisfied_by_all_typevars())
static_assert(not ConstraintSet.never().satisfied_by_all_typevars())
# If we choose Unrelated as the materialization of either constraint, then (T = Unrelated) is a
# valid specialization, which satisfies (T ≤ Unrelated).
static_assert(ConstraintSet.range(Never, T, Unrelated).satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(ConstraintSet.range(Never, T, Unrelated).with_inferable(T).satisfied_by_all_typevars())
# If we choose Unrelated as the materialization of both constraints, then (T = Unrelated) is the
# only valid specialization, which satisfies (T ≤ Unrelated).
static_assert(ConstraintSet.range(Never, T, Unrelated).satisfied_by_all_typevars())
# If we choose Base as the materialization of either constraint, then (T = Base) is a valid
# specialization, which satisfies (T ≤ Base).
static_assert(ConstraintSet.range(Never, T, Base).satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(ConstraintSet.range(Never, T, Base).with_inferable(T).satisfied_by_all_typevars())
# If we choose Never as the materialization of both constraints, then (T = Never) is the only
# valid specialization, which satisfies (T ≤ Base).
static_assert(ConstraintSet.range(Never, T, Base).satisfied_by_all_typevars())
@ -379,35 +379,35 @@ restrictive variance (i.e., invariance), but we get the same results for other v
```py
def constrained_by_gradual[T: (list[Base], list[Any])]():
static_assert(ConstraintSet.always().satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(ConstraintSet.always().with_inferable(T).satisfied_by_all_typevars())
static_assert(ConstraintSet.always().satisfied_by_all_typevars())
static_assert(not ConstraintSet.never().satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(not ConstraintSet.never().with_inferable(T).satisfied_by_all_typevars())
static_assert(not ConstraintSet.never().satisfied_by_all_typevars())
# No matter which materialization we choose, every valid specialization will be of the form
# (T = list[X]). Because Unrelated is final, it is disjoint from all lists. There is therefore
# no materialization or specialization that satisfies (T ≤ Unrelated).
static_assert(not ConstraintSet.range(Never, T, Unrelated).satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(not ConstraintSet.range(Never, T, Unrelated).with_inferable(T).satisfied_by_all_typevars())
static_assert(not ConstraintSet.range(Never, T, Unrelated).satisfied_by_all_typevars())
# If we choose list[Super] as the materialization, then (T = list[Super]) is a valid
# specialization, which satisfies (T ≤ list[Super]).
static_assert(ConstraintSet.range(Never, T, list[Super]).satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(ConstraintSet.range(Never, T, list[Super]).with_inferable(T).satisfied_by_all_typevars())
# No matter which materialization we choose, (T = list[Base]) is a valid specialization, which
# does not satisfy (T ≤ list[Super]).
static_assert(not ConstraintSet.range(Never, T, list[Super]).satisfied_by_all_typevars())
# If we choose list[Base] as the materialization, then (T = list[Base]) is a valid
# specialization, which satisfies (T ≤ list[Base]).
static_assert(ConstraintSet.range(Never, T, list[Base]).satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(ConstraintSet.range(Never, T, list[Base]).with_inferable(T).satisfied_by_all_typevars())
# If we choose list[Base] as the materialization, then all valid specializations must satisfy
# (T ≤ list[Base]).
static_assert(ConstraintSet.range(Never, T, list[Base]).satisfied_by_all_typevars())
# If we choose list[Sub] as the materialization, then (T = list[Sub]) is a valid specialization,
# which # satisfies (T ≤ list[Sub]).
static_assert(ConstraintSet.range(Never, T, list[Sub]).satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(ConstraintSet.range(Never, T, list[Sub]).with_inferable(T).satisfied_by_all_typevars())
# No matter which materialization we choose, (T = list[Base]) is a valid specialization, which
# does not satisfy (T ≤ list[Sub]).
static_assert(not ConstraintSet.range(Never, T, list[Sub]).satisfied_by_all_typevars())
@ -415,7 +415,7 @@ def constrained_by_gradual[T: (list[Base], list[Any])]():
# If we choose list[Unrelated] as the materialization, then (T = list[Unrelated]) is a valid
# specialization, which satisfies (T ≤ list[Unrelated]).
constraints = ConstraintSet.range(Never, T, list[Unrelated])
static_assert(constraints.satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(constraints.with_inferable(T).satisfied_by_all_typevars())
# No matter which materialization we choose, (T = list[Base]) is a valid specialization, which
# does not satisfy (T ≤ list[Unrelated]).
static_assert(not constraints.satisfied_by_all_typevars())
@ -423,42 +423,42 @@ def constrained_by_gradual[T: (list[Base], list[Any])]():
# If we choose list[Unrelated] as the materialization, then (T = list[Unrelated]) is a valid
# specialization, which satisfies (T ≤ list[Unrelated] ∧ T ≠ Never).
constraints = constraints & ~ConstraintSet.range(Never, T, Never)
static_assert(constraints.satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(constraints.with_inferable(T).satisfied_by_all_typevars())
# There is no materialization that we can choose to satisfy this constraint set in non-inferable
# position. (T = Never) will be a valid assignment no matter what, and that does not satisfy
# (T ≤ list[Unrelated] ∧ T ≠ Never).
static_assert(not constraints.satisfied_by_all_typevars())
def constrained_by_two_gradual[T: (list[Any], list[Any])]():
static_assert(ConstraintSet.always().satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(ConstraintSet.always().with_inferable(T).satisfied_by_all_typevars())
static_assert(ConstraintSet.always().satisfied_by_all_typevars())
static_assert(not ConstraintSet.never().satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(not ConstraintSet.never().with_inferable(T).satisfied_by_all_typevars())
static_assert(not ConstraintSet.never().satisfied_by_all_typevars())
# No matter which materialization we choose, every valid specialization will be of the form
# (T = list[X]). Because Unrelated is final, it is disjoint from all lists. There is therefore
# no materialization or specialization that satisfies (T ≤ Unrelated).
static_assert(not ConstraintSet.range(Never, T, Unrelated).satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(not ConstraintSet.range(Never, T, Unrelated).with_inferable(T).satisfied_by_all_typevars())
static_assert(not ConstraintSet.range(Never, T, Unrelated).satisfied_by_all_typevars())
# If we choose list[Super] as the materialization, then (T = list[Super]) is a valid
# specialization, which satisfies (T ≤ list[Super]).
static_assert(ConstraintSet.range(Never, T, list[Super]).satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(ConstraintSet.range(Never, T, list[Super]).with_inferable(T).satisfied_by_all_typevars())
# No matter which materialization we choose, (T = list[Base]) is a valid specialization, which
# does not satisfy (T ≤ list[Super]).
static_assert(ConstraintSet.range(Never, T, list[Super]).satisfied_by_all_typevars())
# If we choose list[Base] as the materialization, then (T = list[Base]) is a valid
# specialization, which satisfies (T ≤ list[Base]).
static_assert(ConstraintSet.range(Never, T, list[Base]).satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(ConstraintSet.range(Never, T, list[Base]).with_inferable(T).satisfied_by_all_typevars())
# If we choose Base as the materialization, then all valid specializations must satisfy
# (T ≤ list[Base]).
static_assert(ConstraintSet.range(Never, T, list[Base]).satisfied_by_all_typevars())
# If we choose list[Sub] as the materialization, then (T = list[Sub]) is a valid specialization,
# which satisfies (T ≤ list[Sub]).
static_assert(ConstraintSet.range(Never, T, list[Sub]).satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(ConstraintSet.range(Never, T, list[Sub]).with_inferable(T).satisfied_by_all_typevars())
# No matter which materialization we choose, (T = list[Base]) is a valid specialization, which
# does not satisfy (T ≤ list[Sub]).
static_assert(ConstraintSet.range(Never, T, list[Sub]).satisfied_by_all_typevars())
@ -466,7 +466,7 @@ def constrained_by_two_gradual[T: (list[Any], list[Any])]():
# If we choose list[Unrelated] as the materialization, then (T = list[Unrelated]) is a valid
# specialization, which satisfies (T ≤ list[Unrelated]).
constraints = ConstraintSet.range(Never, T, list[Unrelated])
static_assert(constraints.satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(constraints.with_inferable(T).satisfied_by_all_typevars())
# No matter which materialization we choose, (T = list[Base]) is a valid specialization, which
# does not satisfy (T ≤ list[Unrelated]).
static_assert(constraints.satisfied_by_all_typevars())
@ -474,7 +474,7 @@ def constrained_by_two_gradual[T: (list[Any], list[Any])]():
# If we choose list[Unrelated] as the materialization, then (T = list[Unrelated]) is a valid
# specialization, which satisfies (T ≤ list[Unrelated] ∧ T ≠ Never).
constraints = constraints & ~ConstraintSet.range(Never, T, Never)
static_assert(constraints.satisfied_by_all_typevars(inferable=tuple[T]))
static_assert(constraints.with_inferable(T).satisfied_by_all_typevars())
# There is no constraint that we can choose to satisfy this constraint set in non-inferable
# position. (T = Never) will be a valid assignment no matter what, and that does not satisfy
# (T ≤ list[Unrelated] ∧ T ≠ Never).

View File

@ -4317,6 +4317,14 @@ impl<'db> Type<'db> {
))
.into()
}
Type::KnownInstance(KnownInstanceType::ConstraintSet(tracked))
if name == "with_inferable" =>
{
Place::bound(Type::KnownBoundMethod(
KnownBoundMethodType::ConstraintSetWithInferable(tracked),
))
.into()
}
Type::KnownInstance(KnownInstanceType::ConstraintSet(tracked))
if name == "implies_subtype_of" =>
{
@ -7162,6 +7170,7 @@ impl<'db> Type<'db> {
| KnownBoundMethodType::ConstraintSetRange
| KnownBoundMethodType::ConstraintSetAlways
| KnownBoundMethodType::ConstraintSetNever
| KnownBoundMethodType::ConstraintSetWithInferable(_)
| KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_)
| KnownBoundMethodType::ConstraintSetSatisfies(_)
| KnownBoundMethodType::ConstraintSetSatisfiedByAllTypeVars(_)
@ -7322,6 +7331,7 @@ impl<'db> Type<'db> {
| KnownBoundMethodType::ConstraintSetRange
| KnownBoundMethodType::ConstraintSetAlways
| KnownBoundMethodType::ConstraintSetNever
| KnownBoundMethodType::ConstraintSetWithInferable(_)
| KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_)
| KnownBoundMethodType::ConstraintSetSatisfies(_)
| KnownBoundMethodType::ConstraintSetSatisfiedByAllTypeVars(_),
@ -10780,6 +10790,7 @@ pub enum KnownBoundMethodType<'db> {
ConstraintSetRange,
ConstraintSetAlways,
ConstraintSetNever,
ConstraintSetWithInferable(TrackedConstraintSet<'db>),
ConstraintSetImpliesSubtypeOf(TrackedConstraintSet<'db>),
ConstraintSetSatisfies(TrackedConstraintSet<'db>),
ConstraintSetSatisfiedByAllTypeVars(TrackedConstraintSet<'db>),
@ -10810,6 +10821,7 @@ pub(super) fn walk_method_wrapper_type<'db, V: visitor::TypeVisitor<'db> + ?Size
| KnownBoundMethodType::ConstraintSetRange
| KnownBoundMethodType::ConstraintSetAlways
| KnownBoundMethodType::ConstraintSetNever
| KnownBoundMethodType::ConstraintSetWithInferable(_)
| KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_)
| KnownBoundMethodType::ConstraintSetSatisfies(_)
| KnownBoundMethodType::ConstraintSetSatisfiedByAllTypeVars(_) => {}
@ -10877,6 +10889,10 @@ impl<'db> KnownBoundMethodType<'db> {
KnownBoundMethodType::ConstraintSetNever,
KnownBoundMethodType::ConstraintSetNever,
)
| (
KnownBoundMethodType::ConstraintSetWithInferable(_),
KnownBoundMethodType::ConstraintSetWithInferable(_),
)
| (
KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_),
KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_),
@ -10900,6 +10916,7 @@ impl<'db> KnownBoundMethodType<'db> {
| KnownBoundMethodType::ConstraintSetRange
| KnownBoundMethodType::ConstraintSetAlways
| KnownBoundMethodType::ConstraintSetNever
| KnownBoundMethodType::ConstraintSetWithInferable(_)
| KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_)
| KnownBoundMethodType::ConstraintSetSatisfies(_)
| KnownBoundMethodType::ConstraintSetSatisfiedByAllTypeVars(_),
@ -10912,6 +10929,7 @@ impl<'db> KnownBoundMethodType<'db> {
| KnownBoundMethodType::ConstraintSetRange
| KnownBoundMethodType::ConstraintSetAlways
| KnownBoundMethodType::ConstraintSetNever
| KnownBoundMethodType::ConstraintSetWithInferable(_)
| KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_)
| KnownBoundMethodType::ConstraintSetSatisfies(_)
| KnownBoundMethodType::ConstraintSetSatisfiedByAllTypeVars(_),
@ -10965,6 +10983,10 @@ impl<'db> KnownBoundMethodType<'db> {
) => ConstraintSet::from(true),
(
KnownBoundMethodType::ConstraintSetWithInferable(left_constraints),
KnownBoundMethodType::ConstraintSetWithInferable(right_constraints),
)
| (
KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(left_constraints),
KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(right_constraints),
)
@ -10989,6 +11011,7 @@ impl<'db> KnownBoundMethodType<'db> {
| KnownBoundMethodType::ConstraintSetRange
| KnownBoundMethodType::ConstraintSetAlways
| KnownBoundMethodType::ConstraintSetNever
| KnownBoundMethodType::ConstraintSetWithInferable(_)
| KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_)
| KnownBoundMethodType::ConstraintSetSatisfies(_)
| KnownBoundMethodType::ConstraintSetSatisfiedByAllTypeVars(_),
@ -11001,6 +11024,7 @@ impl<'db> KnownBoundMethodType<'db> {
| KnownBoundMethodType::ConstraintSetRange
| KnownBoundMethodType::ConstraintSetAlways
| KnownBoundMethodType::ConstraintSetNever
| KnownBoundMethodType::ConstraintSetWithInferable(_)
| KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_)
| KnownBoundMethodType::ConstraintSetSatisfies(_)
| KnownBoundMethodType::ConstraintSetSatisfiedByAllTypeVars(_),
@ -11027,6 +11051,7 @@ impl<'db> KnownBoundMethodType<'db> {
| KnownBoundMethodType::ConstraintSetRange
| KnownBoundMethodType::ConstraintSetAlways
| KnownBoundMethodType::ConstraintSetNever
| KnownBoundMethodType::ConstraintSetWithInferable(_)
| KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_)
| KnownBoundMethodType::ConstraintSetSatisfies(_)
| KnownBoundMethodType::ConstraintSetSatisfiedByAllTypeVars(_) => self,
@ -11045,6 +11070,7 @@ impl<'db> KnownBoundMethodType<'db> {
KnownBoundMethodType::ConstraintSetRange
| KnownBoundMethodType::ConstraintSetAlways
| KnownBoundMethodType::ConstraintSetNever
| KnownBoundMethodType::ConstraintSetWithInferable(_)
| KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_)
| KnownBoundMethodType::ConstraintSetSatisfies(_)
| KnownBoundMethodType::ConstraintSetSatisfiedByAllTypeVars(_) => {
@ -11175,6 +11201,15 @@ impl<'db> KnownBoundMethodType<'db> {
)))
}
KnownBoundMethodType::ConstraintSetWithInferable(_) => {
Either::Right(std::iter::once(Signature::new(
Parameters::new([Parameter::variadic(Name::new_static("inferable"))
.type_form()
.with_annotated_type(Type::any())]),
Some(KnownClass::ConstraintSet.to_instance(db)),
)))
}
KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_) => {
Either::Right(std::iter::once(Signature::new(
Parameters::new([
@ -11199,13 +11234,7 @@ impl<'db> KnownBoundMethodType<'db> {
KnownBoundMethodType::ConstraintSetSatisfiedByAllTypeVars(_) => {
Either::Right(std::iter::once(Signature::new(
Parameters::new([Parameter::keyword_only(Name::new_static("inferable"))
.type_form()
.with_annotated_type(UnionType::from_elements(
db,
[Type::homogeneous_tuple(db, Type::any()), Type::none(db)],
))
.with_default_type(Type::none(db))]),
Parameters::empty(),
Some(KnownClass::Bool.to_instance(db)),
)))
}

View File

@ -36,9 +36,9 @@ use crate::types::tuple::{TupleLength, TupleType};
use crate::types::{
BoundMethodType, BoundTypeVarIdentity, ClassLiteral, DataclassFlags, DataclassParams,
FieldInstance, KnownBoundMethodType, KnownClass, KnownInstanceType, MemberLookupPolicy,
NominalInstanceType, PropertyInstanceType, SpecialFormType, TrackedConstraintSet,
TypeAliasType, TypeContext, UnionBuilder, UnionType, WrapperDescriptorKind, enums, ide_support,
infer_isolated_expression, todo_type,
PropertyInstanceType, SpecialFormType, TrackedConstraintSet, TypeAliasType, TypeContext,
UnionBuilder, UnionType, WrapperDescriptorKind, enums, ide_support, infer_isolated_expression,
todo_type,
};
use ruff_db::diagnostic::{Annotation, Diagnostic, SubDiagnostic, SubDiagnosticSeverity};
use ruff_python_ast::{self as ast, ArgOrKeyword, PythonVersion};
@ -181,7 +181,7 @@ impl<'db> Bindings<'db> {
}
}
self.evaluate_known_cases(db, dataclass_field_specifiers);
self.evaluate_known_cases(db, argument_types, dataclass_field_specifiers);
// In order of precedence:
//
@ -300,7 +300,12 @@ impl<'db> Bindings<'db> {
/// Evaluates the return type of certain known callables, where we have special-case logic to
/// determine the return type in a way that isn't directly expressible in the type system.
fn evaluate_known_cases(&mut self, db: &'db dyn Db, dataclass_field_specifiers: &[Type<'db>]) {
fn evaluate_known_cases(
&mut self,
db: &'db dyn Db,
argument_types: &CallArguments<'_, 'db>,
dataclass_field_specifiers: &[Type<'db>],
) {
let to_bool = |ty: &Option<Type<'_>>, default: bool| -> bool {
if let Some(Type::BooleanLiteral(value)) = ty {
*value
@ -1177,6 +1182,33 @@ impl<'db> Bindings<'db> {
));
}
Type::KnownBoundMethod(KnownBoundMethodType::ConstraintSetWithInferable(
tracked,
)) => {
let mut any_invalid = false;
let inferable = InferableTypeVars::from_bound_typevars(
db,
overload
.arguments_for_parameter(argument_types, 0)
.filter_map(|(_, ty)| {
let identity = ty
.as_typevar()
.map(|bound_typevar| bound_typevar.identity(db));
any_invalid |= identity.is_none();
identity
}),
);
if any_invalid {
continue;
}
let result = tracked.constraints(db).with_inferable(inferable);
let tracked = TrackedConstraintSet::new(db, result);
overload.set_return_type(Type::KnownInstance(
KnownInstanceType::ConstraintSet(tracked),
));
}
Type::KnownBoundMethod(
KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(tracked),
) => {
@ -1214,36 +1246,7 @@ impl<'db> Bindings<'db> {
Type::KnownBoundMethod(
KnownBoundMethodType::ConstraintSetSatisfiedByAllTypeVars(tracked),
) => {
let extract_inferable = |instance: &NominalInstanceType<'db>| {
if instance.has_known_class(db, KnownClass::NoneType) {
// Caller explicitly passed None, so no typevars are inferable.
return Some(InferableTypeVars::none());
}
Some(InferableTypeVars::from_bound_typevars(
db,
instance.tuple_spec(db)?.fixed_elements().filter_map(|ty| {
ty.as_typevar()
.map(|bound_typevar| bound_typevar.identity(db))
}),
))
};
let inferable = match overload.parameter_types() {
// Caller did not provide argument, so no typevars are inferable.
[None] => InferableTypeVars::none(),
[Some(Type::NominalInstance(instance))] => {
match extract_inferable(instance) {
Some(inferable) => inferable,
None => continue,
}
}
_ => continue,
};
let result = tracked
.constraints(db)
.with_inferable(inferable)
.satisfied_by_all_typevars(db);
let result = tracked.constraints(db).satisfied_by_all_typevars(db);
overload.set_return_type(Type::BooleanLiteral(result));
}

View File

@ -532,6 +532,9 @@ impl Display for DisplayRepresentation<'_> {
Type::KnownBoundMethod(KnownBoundMethodType::ConstraintSetNever) => {
f.write_str("bound method `ConstraintSet.never`")
}
Type::KnownBoundMethod(KnownBoundMethodType::ConstraintSetWithInferable(_)) => {
f.write_str("bound method `ConstraintSet.with_inferable`")
}
Type::KnownBoundMethod(KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_)) => {
f.write_str("bound method `ConstraintSet.implies_subtype_of`")
}

View File

@ -59,6 +59,12 @@ class ConstraintSet:
def never() -> Self:
"""Returns a constraint set that is never satisfied"""
def with_inferable(self, *inferable: Any) -> Self:
"""
Returns a copy of this constraint set with some typevars marked as being
in inferable position.
"""
def implies_subtype_of(self, ty: Any, of: Any) -> Self:
"""
Returns a constraint set that is satisfied when `ty` is a `subtype`_ of