Compare commits
30 Commits
main
...
dcreager/u
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b294a94bf2 | ||
|
|
ad8509dc22 | ||
|
|
ef272e32e4 | ||
|
|
6c03643a0b | ||
|
|
f2ebfe2c28 | ||
|
|
60d16288c5 | ||
|
|
c5d66318a6 | ||
|
|
ea14f41c2d | ||
|
|
d904d55c4d | ||
|
|
de2b651136 | ||
|
|
acd08168c8 | ||
|
|
dedfa8a642 | ||
|
|
a012e28216 | ||
|
|
f20368fadf | ||
|
|
3140140763 | ||
|
|
24e60539f9 | ||
|
|
e6ec4062d5 | ||
|
|
b033a42ced | ||
|
|
a1a3953cac | ||
|
|
1bc64d6e22 | ||
|
|
8c2603f2d2 | ||
|
|
b1d1e491dd | ||
|
|
671923bb6b | ||
|
|
8623176dc5 | ||
|
|
4cea4de220 | ||
|
|
57514926dd | ||
|
|
4ea6e668d2 | ||
|
|
18319d33f9 | ||
|
|
27ab1e9fa4 | ||
|
|
ac16a9fc87 |
@@ -458,6 +458,7 @@ class Event(Generic[_DataT]):
|
||||
|
||||
def async_fire_internal(event_data: _DataT):
|
||||
event: Event[_DataT] | None = None
|
||||
reveal_type(Event(event_data))
|
||||
event = Event(event_data)
|
||||
```
|
||||
|
||||
|
||||
@@ -2099,18 +2099,14 @@ static_assert(is_equivalent_to(LegacyFunctionScoped, NewStyleFunctionScoped)) #
|
||||
|
||||
static_assert(is_assignable_to(NominalNewStyle, NewStyleFunctionScoped))
|
||||
static_assert(is_assignable_to(NominalNewStyle, LegacyFunctionScoped))
|
||||
# TODO: should pass
|
||||
static_assert(is_subtype_of(NominalNewStyle, NewStyleFunctionScoped)) # error: [static-assert-error]
|
||||
# TODO: should pass
|
||||
static_assert(is_subtype_of(NominalNewStyle, LegacyFunctionScoped)) # error: [static-assert-error]
|
||||
static_assert(is_subtype_of(NominalNewStyle, NewStyleFunctionScoped))
|
||||
static_assert(is_subtype_of(NominalNewStyle, LegacyFunctionScoped))
|
||||
static_assert(not is_assignable_to(NominalNewStyle, UsesSelf))
|
||||
|
||||
static_assert(is_assignable_to(NominalLegacy, NewStyleFunctionScoped))
|
||||
static_assert(is_assignable_to(NominalLegacy, LegacyFunctionScoped))
|
||||
# TODO: should pass
|
||||
static_assert(is_subtype_of(NominalLegacy, NewStyleFunctionScoped)) # error: [static-assert-error]
|
||||
# TODO: should pass
|
||||
static_assert(is_subtype_of(NominalLegacy, LegacyFunctionScoped)) # error: [static-assert-error]
|
||||
static_assert(is_subtype_of(NominalLegacy, NewStyleFunctionScoped))
|
||||
static_assert(is_subtype_of(NominalLegacy, LegacyFunctionScoped))
|
||||
static_assert(not is_assignable_to(NominalLegacy, UsesSelf))
|
||||
|
||||
static_assert(not is_assignable_to(NominalWithSelf, NewStyleFunctionScoped))
|
||||
|
||||
@@ -12,8 +12,7 @@ a particular constraint set hold_.
|
||||
## Concrete types
|
||||
|
||||
For concrete types, constraint implication is exactly the same as subtyping. (A concrete type is any
|
||||
fully static type that is not a typevar. It can _contain_ a typevar, though — `list[T]` is
|
||||
considered concrete.)
|
||||
fully static type that does not contain a typevar.)
|
||||
|
||||
```py
|
||||
from ty_extensions import ConstraintSet, is_subtype_of, static_assert
|
||||
@@ -191,4 +190,230 @@ def mutually_constrained[T, U]():
|
||||
static_assert(not given_int.implies_subtype_of(T, str))
|
||||
```
|
||||
|
||||
## Compound types
|
||||
|
||||
All of the relationships in the above section also apply when a typevar appears in a compound type.
|
||||
|
||||
```py
|
||||
from typing import Never
|
||||
from ty_extensions import ConstraintSet, static_assert
|
||||
|
||||
class Covariant[T]:
|
||||
def get(self) -> T:
|
||||
raise ValueError
|
||||
|
||||
def given_constraints[T]():
|
||||
static_assert(not ConstraintSet.always().implies_subtype_of(Covariant[T], Covariant[int]))
|
||||
static_assert(not ConstraintSet.always().implies_subtype_of(Covariant[T], Covariant[bool]))
|
||||
static_assert(not ConstraintSet.always().implies_subtype_of(Covariant[T], Covariant[str]))
|
||||
|
||||
# These are vacuously true; false implies anything
|
||||
static_assert(ConstraintSet.never().implies_subtype_of(Covariant[T], Covariant[int]))
|
||||
static_assert(ConstraintSet.never().implies_subtype_of(Covariant[T], Covariant[bool]))
|
||||
static_assert(ConstraintSet.never().implies_subtype_of(Covariant[T], Covariant[str]))
|
||||
|
||||
# For a covariant typevar, (T ≤ int) implies that (Covariant[T] ≤ Covariant[int]).
|
||||
given_int = ConstraintSet.range(Never, T, int)
|
||||
static_assert(given_int.implies_subtype_of(Covariant[T], Covariant[int]))
|
||||
static_assert(not given_int.implies_subtype_of(Covariant[T], Covariant[bool]))
|
||||
static_assert(not given_int.implies_subtype_of(Covariant[T], Covariant[str]))
|
||||
|
||||
given_bool = ConstraintSet.range(Never, T, bool)
|
||||
static_assert(given_bool.implies_subtype_of(Covariant[T], Covariant[int]))
|
||||
static_assert(given_bool.implies_subtype_of(Covariant[T], Covariant[bool]))
|
||||
static_assert(not given_bool.implies_subtype_of(Covariant[T], Covariant[str]))
|
||||
|
||||
def mutually_constrained[T, U]():
|
||||
# If (T = U ∧ U ≤ int), then (T ≤ int) must be true as well, and therefore
|
||||
# (Covariant[T] ≤ Covariant[int]).
|
||||
given_int = ConstraintSet.range(U, T, U) & ConstraintSet.range(Never, U, int)
|
||||
static_assert(given_int.implies_subtype_of(Covariant[T], Covariant[int]))
|
||||
static_assert(not given_int.implies_subtype_of(Covariant[T], Covariant[bool]))
|
||||
static_assert(not given_int.implies_subtype_of(Covariant[T], Covariant[str]))
|
||||
|
||||
# If (T ≤ U ∧ U ≤ int), then (T ≤ int) must be true as well, and therefore
|
||||
# (Covariant[T] ≤ Covariant[int]).
|
||||
given_int = ConstraintSet.range(Never, T, U) & ConstraintSet.range(Never, U, int)
|
||||
static_assert(given_int.implies_subtype_of(Covariant[T], Covariant[int]))
|
||||
static_assert(not given_int.implies_subtype_of(Covariant[T], Covariant[bool]))
|
||||
static_assert(not given_int.implies_subtype_of(Covariant[T], Covariant[str]))
|
||||
```
|
||||
|
||||
Many of the relationships are reversed for typevars that appear in contravariant types.
|
||||
|
||||
```py
|
||||
class Contravariant[T]:
|
||||
def set(self, value: T):
|
||||
pass
|
||||
|
||||
def given_constraints[T]():
|
||||
static_assert(not ConstraintSet.always().implies_subtype_of(Contravariant[int], Contravariant[T]))
|
||||
static_assert(not ConstraintSet.always().implies_subtype_of(Contravariant[bool], Contravariant[T]))
|
||||
static_assert(not ConstraintSet.always().implies_subtype_of(Contravariant[str], Contravariant[T]))
|
||||
|
||||
# These are vacuously true; false implies anything
|
||||
static_assert(ConstraintSet.never().implies_subtype_of(Contravariant[int], Contravariant[T]))
|
||||
static_assert(ConstraintSet.never().implies_subtype_of(Contravariant[bool], Contravariant[T]))
|
||||
static_assert(ConstraintSet.never().implies_subtype_of(Contravariant[str], Contravariant[T]))
|
||||
|
||||
# For a contravariant typevar, (T ≤ int) implies that (Contravariant[int] ≤ Contravariant[T]).
|
||||
# (The order of the comparison is reversed because of contravariance.)
|
||||
given_int = ConstraintSet.range(Never, T, int)
|
||||
static_assert(given_int.implies_subtype_of(Contravariant[int], Contravariant[T]))
|
||||
static_assert(not given_int.implies_subtype_of(Contravariant[bool], Contravariant[T]))
|
||||
static_assert(not given_int.implies_subtype_of(Contravariant[str], Contravariant[T]))
|
||||
|
||||
given_bool = ConstraintSet.range(Never, T, int)
|
||||
static_assert(given_bool.implies_subtype_of(Contravariant[int], Contravariant[T]))
|
||||
static_assert(not given_bool.implies_subtype_of(Contravariant[bool], Contravariant[T]))
|
||||
static_assert(not given_bool.implies_subtype_of(Contravariant[str], Contravariant[T]))
|
||||
|
||||
def mutually_constrained[T, U]():
|
||||
# If (T = U ∧ U ≤ int), then (T ≤ int) must be true as well, and therefore
|
||||
# (Contravariant[int] ≤ Contravariant[T]).
|
||||
given_int = ConstraintSet.range(U, T, U) & ConstraintSet.range(Never, U, int)
|
||||
static_assert(given_int.implies_subtype_of(Contravariant[int], Contravariant[T]))
|
||||
static_assert(not given_int.implies_subtype_of(Contravariant[bool], Contravariant[T]))
|
||||
static_assert(not given_int.implies_subtype_of(Contravariant[str], Contravariant[T]))
|
||||
|
||||
# If (T ≤ U ∧ U ≤ int), then (T ≤ int) must be true as well, and therefore
|
||||
# (Contravariant[int] ≤ Contravariant[T]).
|
||||
given_int = ConstraintSet.range(Never, T, U) & ConstraintSet.range(Never, U, int)
|
||||
static_assert(given_int.implies_subtype_of(Contravariant[int], Contravariant[T]))
|
||||
static_assert(not given_int.implies_subtype_of(Contravariant[bool], Contravariant[T]))
|
||||
static_assert(not given_int.implies_subtype_of(Contravariant[str], Contravariant[T]))
|
||||
```
|
||||
|
||||
For invariant typevars, subtyping of the typevar does not imply subtyping of the compound type in
|
||||
either direction. But an equality constraint on the typevar does.
|
||||
|
||||
```py
|
||||
class Invariant[T]:
|
||||
def get(self) -> T:
|
||||
raise ValueError
|
||||
|
||||
def set(self, value: T):
|
||||
pass
|
||||
|
||||
def given_constraints[T]():
|
||||
static_assert(not ConstraintSet.always().implies_subtype_of(Invariant[T], Invariant[int]))
|
||||
static_assert(not ConstraintSet.always().implies_subtype_of(Invariant[T], Invariant[bool]))
|
||||
static_assert(not ConstraintSet.always().implies_subtype_of(Invariant[T], Invariant[str]))
|
||||
|
||||
# These are vacuously true; false implies anything
|
||||
static_assert(ConstraintSet.never().implies_subtype_of(Invariant[T], Invariant[int]))
|
||||
static_assert(ConstraintSet.never().implies_subtype_of(Invariant[T], Invariant[bool]))
|
||||
static_assert(ConstraintSet.never().implies_subtype_of(Invariant[T], Invariant[str]))
|
||||
|
||||
# For an invariant typevar, (T ≤ int) does not imply that (Invariant[T] ≤ Invariant[int]).
|
||||
given_int = ConstraintSet.range(Never, T, int)
|
||||
static_assert(not given_int.implies_subtype_of(Invariant[T], Invariant[int]))
|
||||
static_assert(not given_int.implies_subtype_of(Invariant[T], Invariant[bool]))
|
||||
static_assert(not given_int.implies_subtype_of(Invariant[T], Invariant[str]))
|
||||
|
||||
# It also does not imply the contravariant ordering (Invariant[int] ≤ Invariant[T]).
|
||||
static_assert(not given_int.implies_subtype_of(Invariant[int], Invariant[T]))
|
||||
static_assert(not given_int.implies_subtype_of(Invariant[bool], Invariant[T]))
|
||||
static_assert(not given_int.implies_subtype_of(Invariant[str], Invariant[T]))
|
||||
|
||||
# But (T = int) does imply both.
|
||||
given_int = ConstraintSet.range(int, T, int)
|
||||
static_assert(given_int.implies_subtype_of(Invariant[T], Invariant[int]))
|
||||
static_assert(given_int.implies_subtype_of(Invariant[int], Invariant[T]))
|
||||
static_assert(not given_int.implies_subtype_of(Invariant[bool], Invariant[T]))
|
||||
static_assert(not given_int.implies_subtype_of(Invariant[T], Invariant[bool]))
|
||||
static_assert(not given_int.implies_subtype_of(Invariant[str], Invariant[T]))
|
||||
static_assert(not given_int.implies_subtype_of(Invariant[T], Invariant[str]))
|
||||
|
||||
def mutually_constrained[T, U]():
|
||||
# If (T = U ∧ U ≤ int), then (T ≤ int) must be true as well. But because T is invariant, that
|
||||
# does _not_ imply that (Invariant[T] ≤ Invariant[int]).
|
||||
given_int = ConstraintSet.range(U, T, U) & ConstraintSet.range(Never, U, int)
|
||||
static_assert(not given_int.implies_subtype_of(Invariant[T], Invariant[int]))
|
||||
static_assert(not given_int.implies_subtype_of(Invariant[T], Invariant[bool]))
|
||||
static_assert(not given_int.implies_subtype_of(Invariant[T], Invariant[str]))
|
||||
|
||||
# If (T = U ∧ U = int), then (T = int) must be true as well. That is an equality constraint, so
|
||||
# even though T is invariant, it does imply that (Invariant[T] ≤ Invariant[int]).
|
||||
given_int = ConstraintSet.range(U, T, U) & ConstraintSet.range(int, U, int)
|
||||
static_assert(given_int.implies_subtype_of(Invariant[T], Invariant[int]))
|
||||
static_assert(not given_int.implies_subtype_of(Invariant[T], Invariant[bool]))
|
||||
static_assert(not given_int.implies_subtype_of(Invariant[T], Invariant[str]))
|
||||
```
|
||||
|
||||
## Generic callables
|
||||
|
||||
A generic callable can be considered equivalent to an intersection of all of its possible
|
||||
specializations. That means that a generic callable is a subtype of any particular specialization.
|
||||
(If someone expects a function that works with a particular specialization, it's fine to hand them
|
||||
the generic callable.)
|
||||
|
||||
```py
|
||||
from typing import Callable
|
||||
from ty_extensions import CallableTypeOf, ConstraintSet, TypeOf, is_subtype_of, static_assert
|
||||
|
||||
def identity[T](t: T) -> T:
|
||||
return t
|
||||
|
||||
constraints = ConstraintSet.always()
|
||||
|
||||
static_assert(constraints.implies_subtype_of(TypeOf[identity], Callable[[int], int]))
|
||||
static_assert(constraints.implies_subtype_of(TypeOf[identity], Callable[[str], str]))
|
||||
static_assert(not constraints.implies_subtype_of(TypeOf[identity], Callable[[str], int]))
|
||||
|
||||
static_assert(constraints.implies_subtype_of(CallableTypeOf[identity], Callable[[int], int]))
|
||||
static_assert(constraints.implies_subtype_of(CallableTypeOf[identity], Callable[[str], str]))
|
||||
static_assert(not constraints.implies_subtype_of(CallableTypeOf[identity], Callable[[str], int]))
|
||||
```
|
||||
|
||||
The reverse is not true — if someone expects a generic function that can be called with any
|
||||
specialization, we cannot hand them a function that only works with one specialization.
|
||||
|
||||
```py
|
||||
static_assert(not constraints.implies_subtype_of(Callable[[int], int], TypeOf[identity]))
|
||||
static_assert(not constraints.implies_subtype_of(Callable[[str], str], TypeOf[identity]))
|
||||
static_assert(not constraints.implies_subtype_of(Callable[[str], int], TypeOf[identity]))
|
||||
|
||||
static_assert(not constraints.implies_subtype_of(Callable[[int], int], CallableTypeOf[identity]))
|
||||
static_assert(not constraints.implies_subtype_of(Callable[[str], str], CallableTypeOf[identity]))
|
||||
static_assert(not constraints.implies_subtype_of(Callable[[str], int], CallableTypeOf[identity]))
|
||||
```
|
||||
|
||||
Unrelated typevars in the constraint set do not affect whether the subtyping check succeeds or
|
||||
fails.
|
||||
|
||||
```py
|
||||
def unrelated[T]():
|
||||
# Note that even though this typevar is also named T, it is not the same typevar as T@identity!
|
||||
constraints = ConstraintSet.range(bool, T, int)
|
||||
|
||||
static_assert(constraints.implies_subtype_of(TypeOf[identity], Callable[[int], int]))
|
||||
static_assert(constraints.implies_subtype_of(TypeOf[identity], Callable[[str], str]))
|
||||
static_assert(not constraints.implies_subtype_of(TypeOf[identity], Callable[[str], int]))
|
||||
|
||||
static_assert(not constraints.implies_subtype_of(Callable[[int], int], TypeOf[identity]))
|
||||
static_assert(not constraints.implies_subtype_of(Callable[[str], str], TypeOf[identity]))
|
||||
static_assert(not constraints.implies_subtype_of(Callable[[str], int], TypeOf[identity]))
|
||||
```
|
||||
|
||||
The generic callable's typevar _also_ does not affect whether the subtyping check succeeds or fails!
|
||||
|
||||
```py
|
||||
def identity2[T](t: T) -> T:
|
||||
# This constraint set refers to the same typevar as the generic function types below!
|
||||
constraints = ConstraintSet.range(bool, T, int)
|
||||
|
||||
static_assert(constraints.implies_subtype_of(TypeOf[identity2], Callable[[int], int]))
|
||||
static_assert(constraints.implies_subtype_of(TypeOf[identity2], Callable[[str], str]))
|
||||
# TODO: no error
|
||||
# error: [static-assert-error]
|
||||
static_assert(not constraints.implies_subtype_of(TypeOf[identity2], Callable[[str], int]))
|
||||
|
||||
static_assert(not constraints.implies_subtype_of(Callable[[int], int], TypeOf[identity2]))
|
||||
static_assert(not constraints.implies_subtype_of(Callable[[str], str], TypeOf[identity2]))
|
||||
static_assert(not constraints.implies_subtype_of(Callable[[str], int], TypeOf[identity2]))
|
||||
|
||||
return t
|
||||
```
|
||||
|
||||
[subtyping]: https://typing.python.org/en/latest/spec/concepts.html#subtype-supertype-and-type-equivalence
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
# Assignable-to relation
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
The `is_assignable_to(S, T)` relation below checks if type `S` is assignable to type `T` (target).
|
||||
This allows us to check if a type `S` can be used in a context where a type `T` is expected
|
||||
(function arguments, variable assignments). See the [typing documentation] for a precise definition
|
||||
@@ -1227,6 +1232,46 @@ from ty_extensions import static_assert, is_assignable_to
|
||||
static_assert(is_assignable_to(type, Callable[..., Any]))
|
||||
```
|
||||
|
||||
### Generic callables
|
||||
|
||||
A generic callable can be considered equivalent to an intersection of all of its possible
|
||||
specializations. That means that a generic callable is assignable to any particular specialization.
|
||||
(If someone expects a function that works with a particular specialization, it's fine to hand them
|
||||
the generic callable.)
|
||||
|
||||
```py
|
||||
from typing import Callable
|
||||
from ty_extensions import CallableTypeOf, TypeOf, is_assignable_to, static_assert
|
||||
|
||||
def identity[T](t: T) -> T:
|
||||
return 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]))
|
||||
```
|
||||
|
||||
The reverse is not true — if someone expects a generic function that can be called with any
|
||||
specialization, we cannot hand them a function that only works with one specialization.
|
||||
|
||||
```py
|
||||
static_assert(not is_assignable_to(Callable[[int], int], TypeOf[identity]))
|
||||
static_assert(not is_assignable_to(Callable[[str], str], TypeOf[identity]))
|
||||
static_assert(not is_assignable_to(Callable[[str], int], TypeOf[identity]))
|
||||
|
||||
static_assert(not is_assignable_to(Callable[[int], int], CallableTypeOf[identity]))
|
||||
static_assert(not is_assignable_to(Callable[[str], str], CallableTypeOf[identity]))
|
||||
static_assert(not is_assignable_to(Callable[[str], int], CallableTypeOf[identity]))
|
||||
```
|
||||
|
||||
## Generics
|
||||
|
||||
### Assignability of generic types parameterized by gradual types
|
||||
|
||||
@@ -2207,6 +2207,50 @@ static_assert(is_subtype_of(CallableTypeOf[overload_ab], CallableTypeOf[overload
|
||||
static_assert(is_subtype_of(CallableTypeOf[overload_ba], CallableTypeOf[overload_ab]))
|
||||
```
|
||||
|
||||
### Generic callables
|
||||
|
||||
A generic callable can be considered equivalent to an intersection of all of its possible
|
||||
specializations. That means that a generic callable is a subtype of any particular specialization.
|
||||
(If someone expects a function that works with a particular specialization, it's fine to hand them
|
||||
the generic callable.)
|
||||
|
||||
```py
|
||||
from typing import Callable
|
||||
from ty_extensions import CallableTypeOf, TypeOf, is_subtype_of, static_assert
|
||||
|
||||
def identity[T](t: T) -> T:
|
||||
return t
|
||||
|
||||
# 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]))
|
||||
```
|
||||
|
||||
The reverse is not true — if someone expects a generic function that can be called with any
|
||||
specialization, we cannot hand them a function that only works with one specialization.
|
||||
|
||||
```py
|
||||
static_assert(not is_subtype_of(Callable[[int], int], TypeOf[identity]))
|
||||
static_assert(not is_subtype_of(Callable[[str], str], TypeOf[identity]))
|
||||
static_assert(not is_subtype_of(Callable[[str], int], TypeOf[identity]))
|
||||
|
||||
static_assert(not is_subtype_of(Callable[[int], int], CallableTypeOf[identity]))
|
||||
static_assert(not is_subtype_of(Callable[[str], str], CallableTypeOf[identity]))
|
||||
static_assert(not is_subtype_of(Callable[[str], int], CallableTypeOf[identity]))
|
||||
```
|
||||
|
||||
[gradual form]: https://typing.python.org/en/latest/spec/glossary.html#term-gradual-form
|
||||
[gradual tuple]: https://typing.python.org/en/latest/spec/tuples.html#tuple-type-form
|
||||
[special case for float and complex]: https://typing.python.org/en/latest/spec/special-types.html#special-cases-for-float-and-complex
|
||||
|
||||
@@ -200,7 +200,7 @@ pub(crate) type ApplyTypeMappingVisitor<'db> = TypeTransformer<'db, TypeMapping<
|
||||
|
||||
/// A [`PairVisitor`] that is used in `has_relation_to` methods.
|
||||
pub(crate) type HasRelationToVisitor<'db> =
|
||||
CycleDetector<TypeRelation, (Type<'db>, Type<'db>, TypeRelation), ConstraintSet<'db>>;
|
||||
CycleDetector<TypeRelation<'db>, (Type<'db>, Type<'db>, TypeRelation<'db>), ConstraintSet<'db>>;
|
||||
|
||||
impl Default for HasRelationToVisitor<'_> {
|
||||
fn default() -> Self {
|
||||
@@ -1291,7 +1291,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)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1606,7 +1606,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(
|
||||
@@ -1618,12 +1618,31 @@ impl<'db> Type<'db> {
|
||||
self.has_relation_to(db, target, inferable, TypeRelation::Subtyping)
|
||||
}
|
||||
|
||||
/// Return the constraints under which this type is a subtype of type `target`, assuming that
|
||||
/// all of the restrictions in `constraints` hold.
|
||||
///
|
||||
/// See [`TypeRelation::ConstraintImplication`] for more details.
|
||||
fn when_subtype_of_given(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
target: Type<'db>,
|
||||
constraints: ConstraintSet<'db>,
|
||||
inferable: InferableTypeVars<'_, 'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
self.has_relation_to(
|
||||
db,
|
||||
target,
|
||||
inferable,
|
||||
TypeRelation::ConstraintImplication(constraints),
|
||||
)
|
||||
}
|
||||
|
||||
/// Return true if this type is assignable to type `target`.
|
||||
///
|
||||
/// 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(
|
||||
@@ -1641,7 +1660,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(
|
||||
@@ -1649,7 +1668,7 @@ impl<'db> Type<'db> {
|
||||
db: &'db dyn Db,
|
||||
target: Type<'db>,
|
||||
inferable: InferableTypeVars<'_, 'db>,
|
||||
relation: TypeRelation,
|
||||
relation: TypeRelation<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
self.has_relation_to_impl(
|
||||
db,
|
||||
@@ -1666,7 +1685,7 @@ impl<'db> Type<'db> {
|
||||
db: &'db dyn Db,
|
||||
target: Type<'db>,
|
||||
inferable: InferableTypeVars<'_, 'db>,
|
||||
relation: TypeRelation,
|
||||
relation: TypeRelation<'db>,
|
||||
relation_visitor: &HasRelationToVisitor<'db>,
|
||||
disjointness_visitor: &IsDisjointVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
@@ -1679,6 +1698,14 @@ impl<'db> Type<'db> {
|
||||
return ConstraintSet::from(true);
|
||||
}
|
||||
|
||||
// Handle constraint implication first. If either `self` or `target` is a typevar, check
|
||||
// the constraint set to see if the corresponding constraint is satisfied.
|
||||
if let TypeRelation::ConstraintImplication(constraints) = relation
|
||||
&& (self.is_type_var() || target.is_type_var())
|
||||
{
|
||||
return constraints.when_subtype_of_given(db, self, target);
|
||||
}
|
||||
|
||||
match (self, target) {
|
||||
// Everything is a subtype of `object`.
|
||||
(_, Type::NominalInstance(instance)) if instance.is_object() => {
|
||||
@@ -1758,7 +1785,7 @@ impl<'db> Type<'db> {
|
||||
"DynamicType::Divergent should have been handled in an earlier branch"
|
||||
);
|
||||
ConstraintSet::from(match relation {
|
||||
TypeRelation::Subtyping => false,
|
||||
TypeRelation::Subtyping | TypeRelation::ConstraintImplication(_) => false,
|
||||
TypeRelation::Assignability => true,
|
||||
TypeRelation::Redundancy => match target {
|
||||
Type::Dynamic(_) => true,
|
||||
@@ -1768,7 +1795,7 @@ impl<'db> Type<'db> {
|
||||
})
|
||||
}
|
||||
(_, Type::Dynamic(_)) => ConstraintSet::from(match relation {
|
||||
TypeRelation::Subtyping => false,
|
||||
TypeRelation::Subtyping | TypeRelation::ConstraintImplication(_) => false,
|
||||
TypeRelation::Assignability => true,
|
||||
TypeRelation::Redundancy => match self {
|
||||
Type::Dynamic(_) => true,
|
||||
@@ -1791,21 +1818,16 @@ impl<'db> Type<'db> {
|
||||
// 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) =>
|
||||
(Type::TypeVar(_), Type::Union(union)) if union.elements(db).contains(&self) => {
|
||||
ConstraintSet::from(true)
|
||||
}
|
||||
(Type::Intersection(intersection), Type::TypeVar(_))
|
||||
if intersection.positive(db).contains(&target) =>
|
||||
{
|
||||
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) =>
|
||||
(Type::Intersection(intersection), Type::TypeVar(_))
|
||||
if intersection.negative(db).contains(&target) =>
|
||||
{
|
||||
ConstraintSet::from(false)
|
||||
}
|
||||
@@ -1816,8 +1838,7 @@ impl<'db> Type<'db> {
|
||||
// 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) =>
|
||||
if lhs_bound_typevar.is_same_typevar_as(db, rhs_bound_typevar) =>
|
||||
{
|
||||
ConstraintSet::from(true)
|
||||
}
|
||||
@@ -1826,8 +1847,7 @@ impl<'db> Type<'db> {
|
||||
// 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() =>
|
||||
if bound_typevar.typevar(db).bound_or_constraints(db).is_some() =>
|
||||
{
|
||||
match bound_typevar.typevar(db).bound_or_constraints(db) {
|
||||
None => unreachable!(),
|
||||
@@ -1898,9 +1918,7 @@ impl<'db> Type<'db> {
|
||||
})
|
||||
}
|
||||
|
||||
(Type::TypeVar(bound_typevar), _)
|
||||
if bound_typevar.is_inferable(db, inferable) && relation.is_assignability() =>
|
||||
{
|
||||
(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.
|
||||
|
||||
@@ -1965,12 +1983,16 @@ impl<'db> Type<'db> {
|
||||
// to non-transitivity (highly undesirable); and pragmatically, a full implementation
|
||||
// of redundancy may not generally lead to simpler types in many situations.
|
||||
let self_ty = match relation {
|
||||
TypeRelation::Subtyping | TypeRelation::Redundancy => self,
|
||||
TypeRelation::Subtyping
|
||||
| TypeRelation::Redundancy
|
||||
| TypeRelation::ConstraintImplication(_) => self,
|
||||
TypeRelation::Assignability => self.bottom_materialization(db),
|
||||
};
|
||||
intersection.negative(db).iter().when_all(db, |&neg_ty| {
|
||||
let neg_ty = match relation {
|
||||
TypeRelation::Subtyping | TypeRelation::Redundancy => neg_ty,
|
||||
TypeRelation::Subtyping
|
||||
| TypeRelation::Redundancy
|
||||
| TypeRelation::ConstraintImplication(_) => neg_ty,
|
||||
TypeRelation::Assignability => neg_ty.bottom_materialization(db),
|
||||
};
|
||||
self_ty.is_disjoint_from_impl(
|
||||
@@ -2036,9 +2058,12 @@ impl<'db> Type<'db> {
|
||||
}
|
||||
|
||||
// TODO: Infer specializations here
|
||||
(Type::TypeVar(bound_typevar), _) | (_, Type::TypeVar(bound_typevar))
|
||||
if bound_typevar.is_inferable(db, inferable) =>
|
||||
{
|
||||
(_, 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)
|
||||
}
|
||||
|
||||
@@ -2492,13 +2517,8 @@ impl<'db> Type<'db> {
|
||||
disjointness_visitor,
|
||||
),
|
||||
|
||||
// Other than the special cases enumerated above, nominal-instance types,
|
||||
// newtype-instance types, and typevars are never subtypes of any other variants
|
||||
(Type::TypeVar(bound_typevar), _) => {
|
||||
// All inferable cases should have been handled above
|
||||
assert!(!bound_typevar.is_inferable(db, inferable));
|
||||
ConstraintSet::from(false)
|
||||
}
|
||||
// Other than the special cases enumerated above, nominal-instance types, and
|
||||
// newtype-instance types are never subtypes of any other variants
|
||||
(Type::NominalInstance(_), _) => ConstraintSet::from(false),
|
||||
(Type::NewTypeInstance(_), _) => ConstraintSet::from(false),
|
||||
}
|
||||
@@ -2518,7 +2538,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(
|
||||
@@ -2645,7 +2665,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(
|
||||
@@ -4837,7 +4857,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(_)
|
||||
@@ -10185,7 +10205,7 @@ impl<'db> ConstructorCallError<'db> {
|
||||
|
||||
/// A non-exhaustive enumeration of relations that can exist between types.
|
||||
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
|
||||
pub(crate) enum TypeRelation {
|
||||
pub(crate) enum TypeRelation<'db> {
|
||||
/// The "subtyping" relation.
|
||||
///
|
||||
/// A [fully static] type `B` is a subtype of a fully static type `A` if and only if
|
||||
@@ -10309,9 +10329,46 @@ pub(crate) enum TypeRelation {
|
||||
/// [fully static]: https://typing.python.org/en/latest/spec/glossary.html#term-fully-static-type
|
||||
/// [materializations]: https://typing.python.org/en/latest/spec/glossary.html#term-materialize
|
||||
Redundancy,
|
||||
|
||||
/// The "constraint implication" relationship, aka "implies subtype of".
|
||||
///
|
||||
/// This relationship tests whether one type is a [subtype][Self::Subtyping] of another,
|
||||
/// assuming that the constraints in a particular constraint set hold.
|
||||
///
|
||||
/// For concrete types (types that do not contain typevars), this relationship is the same as
|
||||
/// [subtyping][Self::Subtyping]. (Constraint sets place restrictions on typevars, so if you
|
||||
/// are not comparing typevars, the constraint set can have no effect on whether subtyping
|
||||
/// holds.)
|
||||
///
|
||||
/// If you're comparing a typevar, we have to consider what restrictions the constraint set
|
||||
/// places on that typevar to determine if subtyping holds. For instance, if you want to check
|
||||
/// whether `T ≤ int`, then answer will depend on what constraint set you are considering:
|
||||
///
|
||||
/// ```text
|
||||
/// when_subtype_of_given(T ≤ bool, T, int) ⇒ true
|
||||
/// when_subtype_of_given(T ≤ int, T, int) ⇒ true
|
||||
/// when_subtype_of_given(T ≤ str, T, int) ⇒ false
|
||||
/// ```
|
||||
///
|
||||
/// In the first two cases, the constraint set ensures that `T` will always specialize to a
|
||||
/// type that is a subtype of `int`. In the final case, the constraint set requires `T` to
|
||||
/// specialize to a subtype of `str`, and there is no such type that is also a subtype of
|
||||
/// `int`.
|
||||
///
|
||||
/// There are two constraint sets that deserve special consideration.
|
||||
///
|
||||
/// - The "always true" constraint set does not place any restrictions on any typevar. In this
|
||||
/// case, `when_subtype_of_given` will return the same result as `when_subtype_of`, even if
|
||||
/// you're comparing against a typevar.
|
||||
///
|
||||
/// - The "always false" constraint set represents an impossible situation. In this case, every
|
||||
/// subtype check will be vacuously true, even if you're comparing two concrete types that
|
||||
/// are not actually subtypes of each other. (That is,
|
||||
/// `when_subtype_of_given(false, int, str)` will return true!)
|
||||
ConstraintImplication(ConstraintSet<'db>),
|
||||
}
|
||||
|
||||
impl TypeRelation {
|
||||
impl TypeRelation<'_> {
|
||||
pub(crate) const fn is_assignability(self) -> bool {
|
||||
matches!(self, TypeRelation::Assignability)
|
||||
}
|
||||
@@ -10478,7 +10535,7 @@ impl<'db> BoundMethodType<'db> {
|
||||
db: &'db dyn Db,
|
||||
other: Self,
|
||||
inferable: InferableTypeVars<'_, 'db>,
|
||||
relation: TypeRelation,
|
||||
relation: TypeRelation<'db>,
|
||||
relation_visitor: &HasRelationToVisitor<'db>,
|
||||
disjointness_visitor: &IsDisjointVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
@@ -10645,7 +10702,7 @@ impl<'db> CallableType<'db> {
|
||||
db: &'db dyn Db,
|
||||
other: Self,
|
||||
inferable: InferableTypeVars<'_, 'db>,
|
||||
relation: TypeRelation,
|
||||
relation: TypeRelation<'db>,
|
||||
relation_visitor: &HasRelationToVisitor<'db>,
|
||||
disjointness_visitor: &IsDisjointVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
@@ -10754,7 +10811,7 @@ impl<'db> KnownBoundMethodType<'db> {
|
||||
db: &'db dyn Db,
|
||||
other: Self,
|
||||
inferable: InferableTypeVars<'_, 'db>,
|
||||
relation: TypeRelation,
|
||||
relation: TypeRelation<'db>,
|
||||
relation_visitor: &HasRelationToVisitor<'db>,
|
||||
disjointness_visitor: &IsDisjointVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
|
||||
@@ -1179,10 +1179,10 @@ impl<'db> Bindings<'db> {
|
||||
continue;
|
||||
};
|
||||
|
||||
let result = tracked.constraints(db).when_subtype_of_given(
|
||||
let result = ty_a.when_subtype_of_given(
|
||||
db,
|
||||
*ty_a,
|
||||
*ty_b,
|
||||
tracked.constraints(db),
|
||||
InferableTypeVars::None,
|
||||
);
|
||||
let tracked = TrackedConstraintSet::new(db, result);
|
||||
@@ -1608,7 +1608,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;
|
||||
@@ -1841,7 +1841,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);
|
||||
}
|
||||
@@ -1964,7 +1964,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
|
||||
@@ -2860,15 +2860,12 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> {
|
||||
argument_type = argument_type.apply_specialization(self.db, specialization);
|
||||
expected_ty = expected_ty.apply_specialization(self.db, specialization);
|
||||
}
|
||||
// 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
|
||||
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();
|
||||
@@ -3002,7 +2999,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,
|
||||
|
||||
@@ -517,7 +517,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(
|
||||
@@ -541,14 +541,16 @@ impl<'db> ClassType<'db> {
|
||||
db: &'db dyn Db,
|
||||
other: Self,
|
||||
inferable: InferableTypeVars<'_, 'db>,
|
||||
relation: TypeRelation,
|
||||
relation: TypeRelation<'db>,
|
||||
relation_visitor: &HasRelationToVisitor<'db>,
|
||||
disjointness_visitor: &IsDisjointVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
self.iter_mro(db).when_any(db, |base| {
|
||||
match base {
|
||||
ClassBase::Dynamic(_) => match relation {
|
||||
TypeRelation::Subtyping | TypeRelation::Redundancy => {
|
||||
TypeRelation::Subtyping
|
||||
| TypeRelation::Redundancy
|
||||
| TypeRelation::ConstraintImplication(_) => {
|
||||
ConstraintSet::from(other.is_object(db))
|
||||
}
|
||||
TypeRelation::Assignability => ConstraintSet::from(!other.is_final(db)),
|
||||
|
||||
@@ -66,8 +66,8 @@ use salsa::plumbing::AsId;
|
||||
use crate::Db;
|
||||
use crate::types::generics::InferableTypeVars;
|
||||
use crate::types::{
|
||||
BoundTypeVarInstance, IntersectionType, Type, TypeRelation, TypeVarBoundOrConstraints,
|
||||
UnionType,
|
||||
BoundTypeVarIdentity, BoundTypeVarInstance, IntersectionType, Type, TypeRelation,
|
||||
TypeVarBoundOrConstraints, UnionType,
|
||||
};
|
||||
|
||||
/// An extension trait for building constraint sets from [`Option`] values.
|
||||
@@ -185,11 +185,12 @@ impl<'db> ConstraintSet<'db> {
|
||||
typevar: BoundTypeVarInstance<'db>,
|
||||
lower: Type<'db>,
|
||||
upper: Type<'db>,
|
||||
relation: TypeRelation,
|
||||
relation: TypeRelation<'db>,
|
||||
) -> Self {
|
||||
let (lower, upper) = match relation {
|
||||
// TODO: Is this the correct constraint for redundancy?
|
||||
TypeRelation::Subtyping | TypeRelation::Redundancy => (
|
||||
TypeRelation::Subtyping
|
||||
| TypeRelation::Redundancy
|
||||
| TypeRelation::ConstraintImplication(_) => (
|
||||
lower.top_materialization(db),
|
||||
upper.bottom_materialization(db),
|
||||
),
|
||||
@@ -215,47 +216,16 @@ impl<'db> ConstraintSet<'db> {
|
||||
}
|
||||
|
||||
/// Returns the constraints under which `lhs` is a subtype of `rhs`, assuming that the
|
||||
/// constraints in this constraint set hold.
|
||||
///
|
||||
/// For concrete types (types that are not typevars), this returns the same result as
|
||||
/// [`when_subtype_of`][Type::when_subtype_of]. (Constraint sets place restrictions on
|
||||
/// typevars, so if you are not comparing typevars, the constraint set can have no effect on
|
||||
/// whether subtyping holds.)
|
||||
///
|
||||
/// If you're comparing a typevar, we have to consider what restrictions the constraint set
|
||||
/// places on that typevar to determine if subtyping holds. For instance, if you want to check
|
||||
/// whether `T ≤ int`, then answer will depend on what constraint set you are considering:
|
||||
///
|
||||
/// ```text
|
||||
/// when_subtype_of_given(T ≤ bool, T, int) ⇒ true
|
||||
/// when_subtype_of_given(T ≤ int, T, int) ⇒ true
|
||||
/// when_subtype_of_given(T ≤ str, T, int) ⇒ false
|
||||
/// ```
|
||||
///
|
||||
/// In the first two cases, the constraint set ensures that `T` will always specialize to a
|
||||
/// type that is a subtype of `int`. In the final case, the constraint set requires `T` to
|
||||
/// specialize to a subtype of `str`, and there is no such type that is also a subtype of
|
||||
/// `int`.
|
||||
///
|
||||
/// There are two constraint sets that deserve special consideration.
|
||||
///
|
||||
/// - The "always true" constraint set does not place any restrictions on any typevar. In this
|
||||
/// case, `when_subtype_of_given` will return the same result as `when_subtype_of`, even if
|
||||
/// you're comparing against a typevar.
|
||||
///
|
||||
/// - The "always false" constraint set represents an impossible situation. In this case, every
|
||||
/// subtype check will be vacuously true, even if you're comparing two concrete types that
|
||||
/// are not actually subtypes of each other. (That is,
|
||||
/// `when_subtype_of_given(false, int, str)` will return true!)
|
||||
/// constraints in this constraint set hold. Panics if neither of the types being compared are
|
||||
/// a typevar. (That case is handled by `Type::has_relation_to`.)
|
||||
pub(crate) fn when_subtype_of_given(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
lhs: Type<'db>,
|
||||
rhs: Type<'db>,
|
||||
inferable: InferableTypeVars<'_, 'db>,
|
||||
) -> Self {
|
||||
Self {
|
||||
node: self.node.when_subtype_of_given(db, lhs, rhs, inferable),
|
||||
node: self.node.when_subtype_of_given(db, lhs, rhs),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,6 +301,20 @@ impl<'db> ConstraintSet<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Reduces the set of inferable typevars for this constraint set. You provide an iterator of
|
||||
/// the typevars that were inferable when this constraint set was created, and which should be
|
||||
/// abstracted away. Those typevars will be removed from the constraint set, and the constraint
|
||||
/// set will return true whenever there was _any_ specialization of those typevars that
|
||||
/// returned true before.
|
||||
pub(crate) fn reduce_inferable(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
to_remove: impl IntoIterator<Item = BoundTypeVarIdentity<'db>>,
|
||||
) -> Self {
|
||||
let node = self.node.exists(db, to_remove);
|
||||
Self { node }
|
||||
}
|
||||
|
||||
pub(crate) fn range(
|
||||
db: &'db dyn Db,
|
||||
lower: Type<'db>,
|
||||
@@ -829,13 +813,7 @@ impl<'db> Node<'db> {
|
||||
simplified.and(db, domain)
|
||||
}
|
||||
|
||||
fn when_subtype_of_given(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
lhs: Type<'db>,
|
||||
rhs: Type<'db>,
|
||||
inferable: InferableTypeVars<'_, 'db>,
|
||||
) -> Self {
|
||||
fn when_subtype_of_given(self, db: &'db dyn Db, lhs: Type<'db>, rhs: Type<'db>) -> Self {
|
||||
// When checking subtyping involving a typevar, we can turn the subtyping check into a
|
||||
// constraint (i.e, "is `T` a subtype of `int` becomes the constraint `T ≤ int`), and then
|
||||
// check when the BDD implies that constraint.
|
||||
@@ -846,8 +824,7 @@ impl<'db> Node<'db> {
|
||||
(_, Type::TypeVar(bound_typevar)) => {
|
||||
ConstrainedTypeVar::new_node(db, bound_typevar, lhs, Type::object())
|
||||
}
|
||||
// If neither type is a typevar, then we fall back on a normal subtyping check.
|
||||
_ => return lhs.when_subtype_of(db, rhs, inferable).node,
|
||||
_ => panic!("at least one type should be a typevar"),
|
||||
};
|
||||
|
||||
self.satisfies(db, constraint)
|
||||
@@ -925,6 +902,29 @@ impl<'db> Node<'db> {
|
||||
true
|
||||
}
|
||||
|
||||
/// Returns a new BDD that is the _existential abstraction_ of `self` for a set of typevars.
|
||||
/// The result will return true whenever `self` returns true for _any_ assignment of those
|
||||
/// typevars. The result will not contain any constraints that mention those typevars.
|
||||
fn exists(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
bound_typevars: impl IntoIterator<Item = BoundTypeVarIdentity<'db>>,
|
||||
) -> Self {
|
||||
bound_typevars
|
||||
.into_iter()
|
||||
.fold(self.simplify(db), |abstracted, bound_typevar| {
|
||||
abstracted.exists_one(db, bound_typevar)
|
||||
})
|
||||
}
|
||||
|
||||
fn exists_one(self, db: &'db dyn Db, bound_typevar: BoundTypeVarIdentity<'db>) -> Self {
|
||||
match self {
|
||||
Node::AlwaysTrue => Node::AlwaysTrue,
|
||||
Node::AlwaysFalse => Node::AlwaysFalse,
|
||||
Node::Interior(interior) => interior.exists_one(db, bound_typevar),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a new BDD that returns the same results as `self`, but with some inputs fixed to
|
||||
/// particular values. (Those variables will not be checked when evaluating the result, and
|
||||
/// will not be present in the result.)
|
||||
@@ -1338,6 +1338,32 @@ impl<'db> InteriorNode<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
#[salsa::tracked(heap_size=ruff_memory_usage::heap_size)]
|
||||
fn exists_one(self, db: &'db dyn Db, bound_typevar: BoundTypeVarIdentity<'db>) -> Node<'db> {
|
||||
let self_constraint = self.constraint(db);
|
||||
let self_typevar = self_constraint.typevar(db).identity(db);
|
||||
match bound_typevar.cmp(&self_typevar) {
|
||||
// If the typevar that this node checks is "later" than the typevar we're abstracting
|
||||
// over, then we have reached a point in the BDD where the abstraction can no longer
|
||||
// affect the result, and we can return early.
|
||||
Ordering::Less => Node::Interior(self),
|
||||
// If the typevar that this node checks _is_ the typevar we're abstracting over, then
|
||||
// we replace this node with the OR of its if_false/if_true edges. That is, the result
|
||||
// is true if there's any assignment of this node's constraint that is true.
|
||||
Ordering::Equal => {
|
||||
let if_true = self.if_true(db).exists_one(db, bound_typevar);
|
||||
let if_false = self.if_false(db).exists_one(db, bound_typevar);
|
||||
if_true.or(db, if_false)
|
||||
}
|
||||
// Otherwise, we abstract the if_false/if_true edges recursively.
|
||||
Ordering::Greater => {
|
||||
let if_true = self.if_true(db).exists_one(db, bound_typevar);
|
||||
let if_false = self.if_false(db).exists_one(db, bound_typevar);
|
||||
Node::new(db, self_constraint, if_true, if_false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[salsa::tracked(heap_size=ruff_memory_usage::heap_size)]
|
||||
fn restrict_one(
|
||||
self,
|
||||
@@ -1464,10 +1490,12 @@ impl<'db> InteriorNode<'db> {
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
let new_node = Node::new_constraint(
|
||||
db,
|
||||
ConstrainedTypeVar::new(db, constrained_typevar, new_lower, new_upper),
|
||||
);
|
||||
let new_constraint =
|
||||
ConstrainedTypeVar::new(db, constrained_typevar, new_lower, new_upper);
|
||||
if seen_constraints.contains(&new_constraint) {
|
||||
continue;
|
||||
}
|
||||
let new_node = Node::new_constraint(db, new_constraint);
|
||||
let positive_left_node =
|
||||
Node::new_satisfied_constraint(db, left_constraint.when_true());
|
||||
let positive_right_node =
|
||||
@@ -1481,7 +1509,18 @@ impl<'db> InteriorNode<'db> {
|
||||
continue;
|
||||
}
|
||||
|
||||
// From here on out we know that both constraints constrain the same typevar.
|
||||
// From here on out we know that both constraints constrain the same typevar. The
|
||||
// clause above will propagate all that we know about the current typevar relative to
|
||||
// other typevars, producing constraints on this typevar that have concrete lower/upper
|
||||
// bounds. That means we can skip the simplifications below if any bound is another
|
||||
// typevar.
|
||||
if left_constraint.lower(db).is_type_var()
|
||||
|| left_constraint.upper(db).is_type_var()
|
||||
|| right_constraint.lower(db).is_type_var()
|
||||
|| right_constraint.upper(db).is_type_var()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Containment: The range of one constraint might completely contain the range of the
|
||||
// other. If so, there are several potential simplifications.
|
||||
|
||||
@@ -971,7 +971,7 @@ impl<'db> FunctionType<'db> {
|
||||
db: &'db dyn Db,
|
||||
other: Self,
|
||||
inferable: InferableTypeVars<'_, 'db>,
|
||||
relation: TypeRelation,
|
||||
relation: TypeRelation<'db>,
|
||||
relation_visitor: &HasRelationToVisitor<'db>,
|
||||
disjointness_visitor: &IsDisjointVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
@@ -979,8 +979,12 @@ impl<'db> FunctionType<'db> {
|
||||
// our representation of a function type includes any specialization that should be applied
|
||||
// to the signature. Different specializations of the same function type are only subtypes
|
||||
// of each other if they result in subtype signatures.
|
||||
if matches!(relation, TypeRelation::Subtyping | TypeRelation::Redundancy)
|
||||
&& self.normalized(db) == other.normalized(db)
|
||||
if matches!(
|
||||
relation,
|
||||
TypeRelation::Subtyping
|
||||
| TypeRelation::Redundancy
|
||||
| TypeRelation::ConstraintImplication(_)
|
||||
) && self.normalized(db) == other.normalized(db)
|
||||
{
|
||||
return ConstraintSet::from(true);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::cell::RefCell;
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::fmt::Display;
|
||||
|
||||
use itertools::Itertools;
|
||||
use itertools::{Either, Itertools};
|
||||
use ruff_python_ast as ast;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
||||
@@ -145,10 +145,24 @@ impl<'db> BoundTypeVarInstance<'db> {
|
||||
}
|
||||
|
||||
impl<'a, 'db> InferableTypeVars<'a, 'db> {
|
||||
pub(crate) fn merge(&'a self, other: Option<&'a InferableTypeVars<'a, 'db>>) -> Self {
|
||||
match other {
|
||||
Some(other) => InferableTypeVars::Two(self, other),
|
||||
None => *self,
|
||||
pub(crate) fn merge(&'a self, other: &'a InferableTypeVars<'a, 'db>) -> Self {
|
||||
match (self, other) {
|
||||
(InferableTypeVars::None, other) | (other, InferableTypeVars::None) => *other,
|
||||
_ => InferableTypeVars::Two(self, other),
|
||||
}
|
||||
}
|
||||
|
||||
// This is not an IntoIterator implementation because I have no desire to try to name the
|
||||
// iterator type.
|
||||
pub(crate) fn iter(self) -> impl Iterator<Item = BoundTypeVarIdentity<'db>> {
|
||||
match self {
|
||||
InferableTypeVars::None => Either::Left(Either::Left(std::iter::empty())),
|
||||
InferableTypeVars::One(typevars) => Either::Right(typevars.iter().copied()),
|
||||
InferableTypeVars::Two(left, right) => {
|
||||
let chained: Box<dyn Iterator<Item = BoundTypeVarIdentity<'db>>> =
|
||||
Box::new(left.iter().chain(right.iter()));
|
||||
Either::Left(Either::Right(chained))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -772,7 +786,7 @@ fn has_relation_in_invariant_position<'db>(
|
||||
base_type: &Type<'db>,
|
||||
base_materialization: Option<MaterializationKind>,
|
||||
inferable: InferableTypeVars<'_, 'db>,
|
||||
relation: TypeRelation,
|
||||
relation: TypeRelation<'db>,
|
||||
relation_visitor: &HasRelationToVisitor<'db>,
|
||||
disjointness_visitor: &IsDisjointVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
@@ -821,30 +835,38 @@ fn has_relation_in_invariant_position<'db>(
|
||||
)
|
||||
}),
|
||||
// For gradual types, A <: B (subtyping) is defined as Top[A] <: Bottom[B]
|
||||
(None, Some(base_mat), TypeRelation::Subtyping | TypeRelation::Redundancy) => {
|
||||
is_subtype_in_invariant_position(
|
||||
db,
|
||||
derived_type,
|
||||
MaterializationKind::Top,
|
||||
base_type,
|
||||
base_mat,
|
||||
inferable,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
}
|
||||
(Some(derived_mat), None, TypeRelation::Subtyping | TypeRelation::Redundancy) => {
|
||||
is_subtype_in_invariant_position(
|
||||
db,
|
||||
derived_type,
|
||||
derived_mat,
|
||||
base_type,
|
||||
MaterializationKind::Bottom,
|
||||
inferable,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
}
|
||||
(
|
||||
None,
|
||||
Some(base_mat),
|
||||
TypeRelation::Subtyping
|
||||
| TypeRelation::Redundancy
|
||||
| TypeRelation::ConstraintImplication(_),
|
||||
) => is_subtype_in_invariant_position(
|
||||
db,
|
||||
derived_type,
|
||||
MaterializationKind::Top,
|
||||
base_type,
|
||||
base_mat,
|
||||
inferable,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
),
|
||||
(
|
||||
Some(derived_mat),
|
||||
None,
|
||||
TypeRelation::Subtyping
|
||||
| TypeRelation::Redundancy
|
||||
| TypeRelation::ConstraintImplication(_),
|
||||
) => is_subtype_in_invariant_position(
|
||||
db,
|
||||
derived_type,
|
||||
derived_mat,
|
||||
base_type,
|
||||
MaterializationKind::Bottom,
|
||||
inferable,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
),
|
||||
// And A <~ B (assignability) is Bottom[A] <: Top[B]
|
||||
(None, Some(base_mat), TypeRelation::Assignability) => is_subtype_in_invariant_position(
|
||||
db,
|
||||
@@ -1112,7 +1134,7 @@ impl<'db> Specialization<'db> {
|
||||
db: &'db dyn Db,
|
||||
other: Self,
|
||||
inferable: InferableTypeVars<'_, 'db>,
|
||||
relation: TypeRelation,
|
||||
relation: TypeRelation<'db>,
|
||||
relation_visitor: &HasRelationToVisitor<'db>,
|
||||
disjointness_visitor: &IsDisjointVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
@@ -1465,7 +1487,7 @@ impl<'db> SpecializationBuilder<'db> {
|
||||
let assignable_elements = (formal.elements(self.db).iter()).filter(|ty| {
|
||||
actual
|
||||
.when_subtype_of(self.db, **ty, self.inferable)
|
||||
.is_always_satisfied(self.db)
|
||||
.satisfied_by_all_typevars(self.db, self.inferable)
|
||||
});
|
||||
if assignable_elements.exactly_one().is_ok() {
|
||||
return Ok(());
|
||||
@@ -1496,7 +1518,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,
|
||||
@@ -1517,7 +1539,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, filter);
|
||||
return Ok(());
|
||||
|
||||
@@ -123,7 +123,7 @@ impl<'db> Type<'db> {
|
||||
db: &'db dyn Db,
|
||||
protocol: ProtocolInstanceType<'db>,
|
||||
inferable: InferableTypeVars<'_, 'db>,
|
||||
relation: TypeRelation,
|
||||
relation: TypeRelation<'db>,
|
||||
relation_visitor: &HasRelationToVisitor<'db>,
|
||||
disjointness_visitor: &IsDisjointVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
@@ -366,7 +366,7 @@ impl<'db> NominalInstanceType<'db> {
|
||||
db: &'db dyn Db,
|
||||
other: Self,
|
||||
inferable: InferableTypeVars<'_, 'db>,
|
||||
relation: TypeRelation,
|
||||
relation: TypeRelation<'db>,
|
||||
relation_visitor: &HasRelationToVisitor<'db>,
|
||||
disjointness_visitor: &IsDisjointVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
@@ -437,7 +437,10 @@ impl<'db> NominalInstanceType<'db> {
|
||||
disjointness_visitor,
|
||||
relation_visitor,
|
||||
);
|
||||
if result.union(db, compatible).is_always_satisfied(db) {
|
||||
if result
|
||||
.union(db, compatible)
|
||||
.satisfied_by_all_typevars(db, inferable)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -671,7 +674,7 @@ impl<'db> ProtocolInstanceType<'db> {
|
||||
&HasRelationToVisitor::default(),
|
||||
&IsDisjointVisitor::default(),
|
||||
)
|
||||
.is_always_satisfied(db)
|
||||
.satisfied_by_all_typevars(db, InferableTypeVars::None)
|
||||
}
|
||||
|
||||
fn initial<'db>(
|
||||
|
||||
@@ -234,7 +234,7 @@ impl<'db> ProtocolInterface<'db> {
|
||||
db: &'db dyn Db,
|
||||
other: Self,
|
||||
inferable: InferableTypeVars<'_, 'db>,
|
||||
relation: TypeRelation,
|
||||
relation: TypeRelation<'db>,
|
||||
relation_visitor: &HasRelationToVisitor<'db>,
|
||||
disjointness_visitor: &IsDisjointVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
@@ -634,7 +634,7 @@ impl<'a, 'db> ProtocolMember<'a, 'db> {
|
||||
db: &'db dyn Db,
|
||||
other: Type<'db>,
|
||||
inferable: InferableTypeVars<'_, 'db>,
|
||||
relation: TypeRelation,
|
||||
relation: TypeRelation<'db>,
|
||||
relation_visitor: &HasRelationToVisitor<'db>,
|
||||
disjointness_visitor: &IsDisjointVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
|
||||
@@ -234,7 +234,7 @@ impl<'db> CallableSignature<'db> {
|
||||
db: &'db dyn Db,
|
||||
other: &Self,
|
||||
inferable: InferableTypeVars<'_, 'db>,
|
||||
relation: TypeRelation,
|
||||
relation: TypeRelation<'db>,
|
||||
relation_visitor: &HasRelationToVisitor<'db>,
|
||||
disjointness_visitor: &IsDisjointVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
@@ -256,7 +256,7 @@ impl<'db> CallableSignature<'db> {
|
||||
self_signatures: &[Signature<'db>],
|
||||
other_signatures: &[Signature<'db>],
|
||||
inferable: InferableTypeVars<'_, 'db>,
|
||||
relation: TypeRelation,
|
||||
relation: TypeRelation<'db>,
|
||||
relation_visitor: &HasRelationToVisitor<'db>,
|
||||
disjointness_visitor: &IsDisjointVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
@@ -628,6 +628,13 @@ impl<'db> Signature<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
fn inferable_typevars(&self, db: &'db dyn Db) -> InferableTypeVars<'db, 'db> {
|
||||
match self.generic_context {
|
||||
Some(generic_context) => generic_context.inferable_typevars(db),
|
||||
None => InferableTypeVars::None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return `true` if `self` has exactly the same set of possible static materializations as
|
||||
/// `other` (if `self` represents the same set of possible sets of possible runtime objects as
|
||||
/// `other`).
|
||||
@@ -638,15 +645,6 @@ impl<'db> Signature<'db> {
|
||||
inferable: InferableTypeVars<'_, 'db>,
|
||||
visitor: &IsEquivalentVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
// The typevars in self and other should also be considered inferable when checking whether
|
||||
// two signatures are equivalent.
|
||||
let self_inferable =
|
||||
(self.generic_context).map(|generic_context| generic_context.inferable_typevars(db));
|
||||
let other_inferable =
|
||||
(other.generic_context).map(|generic_context| generic_context.inferable_typevars(db));
|
||||
let inferable = inferable.merge(self_inferable.as_ref());
|
||||
let inferable = inferable.merge(other_inferable.as_ref());
|
||||
|
||||
let mut result = ConstraintSet::from(true);
|
||||
let mut check_types = |self_type: Option<Type<'db>>, other_type: Option<Type<'db>>| {
|
||||
let self_type = self_type.unwrap_or(Type::unknown());
|
||||
@@ -732,7 +730,41 @@ impl<'db> Signature<'db> {
|
||||
db: &'db dyn Db,
|
||||
other: &Signature<'db>,
|
||||
inferable: InferableTypeVars<'_, 'db>,
|
||||
relation: TypeRelation,
|
||||
relation: TypeRelation<'db>,
|
||||
relation_visitor: &HasRelationToVisitor<'db>,
|
||||
disjointness_visitor: &IsDisjointVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
// If either signature is generic, their typevars should also be considered inferable when
|
||||
// checking whether the signatures are equivalent, since we only need to find one
|
||||
// specialization that causes the check to succeed.
|
||||
let self_inferable = self.inferable_typevars(db);
|
||||
let other_inferable = other.inferable_typevars(db);
|
||||
let inferable = inferable.merge(&self_inferable);
|
||||
let inferable = inferable.merge(&other_inferable);
|
||||
|
||||
// `inner` will create a constraint set that references these newly inferable typevars.
|
||||
let when = self.has_relation_to_inner(
|
||||
db,
|
||||
other,
|
||||
inferable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
);
|
||||
|
||||
// But the caller does not need to consider those extra typevars. Whatever constraint set
|
||||
// we produce, we reduce it back down to the inferable set that the caller asked about.
|
||||
// If we introduced new inferable typevars, those will be existentially quantified away
|
||||
// before returning.
|
||||
when.reduce_inferable(db, self_inferable.iter().chain(other_inferable.iter()))
|
||||
}
|
||||
|
||||
fn has_relation_to_inner(
|
||||
&self,
|
||||
db: &'db dyn Db,
|
||||
other: &Signature<'db>,
|
||||
inferable: InferableTypeVars<'_, 'db>,
|
||||
relation: TypeRelation<'db>,
|
||||
relation_visitor: &HasRelationToVisitor<'db>,
|
||||
disjointness_visitor: &IsDisjointVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
@@ -797,15 +829,6 @@ impl<'db> Signature<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
// The typevars in self and other should also be considered inferable when checking whether
|
||||
// two signatures are equivalent.
|
||||
let self_inferable =
|
||||
(self.generic_context).map(|generic_context| generic_context.inferable_typevars(db));
|
||||
let other_inferable =
|
||||
(other.generic_context).map(|generic_context| generic_context.inferable_typevars(db));
|
||||
let inferable = inferable.merge(self_inferable.as_ref());
|
||||
let inferable = inferable.merge(other_inferable.as_ref());
|
||||
|
||||
let mut result = ConstraintSet::from(true);
|
||||
let mut check_types = |type1: Option<Type<'db>>, type2: Option<Type<'db>>| {
|
||||
let type1 = type1.unwrap_or(Type::unknown());
|
||||
|
||||
@@ -137,7 +137,7 @@ impl<'db> SubclassOfType<'db> {
|
||||
db: &'db dyn Db,
|
||||
other: SubclassOfType<'db>,
|
||||
inferable: InferableTypeVars<'_, 'db>,
|
||||
relation: TypeRelation,
|
||||
relation: TypeRelation<'db>,
|
||||
relation_visitor: &HasRelationToVisitor<'db>,
|
||||
disjointness_visitor: &IsDisjointVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
|
||||
@@ -260,7 +260,7 @@ impl<'db> TupleType<'db> {
|
||||
db: &'db dyn Db,
|
||||
other: Self,
|
||||
inferable: InferableTypeVars<'_, 'db>,
|
||||
relation: TypeRelation,
|
||||
relation: TypeRelation<'db>,
|
||||
relation_visitor: &HasRelationToVisitor<'db>,
|
||||
disjointness_visitor: &IsDisjointVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
@@ -442,7 +442,7 @@ impl<'db> FixedLengthTuple<Type<'db>> {
|
||||
db: &'db dyn Db,
|
||||
other: &Tuple<Type<'db>>,
|
||||
inferable: InferableTypeVars<'_, 'db>,
|
||||
relation: TypeRelation,
|
||||
relation: TypeRelation<'db>,
|
||||
relation_visitor: &HasRelationToVisitor<'db>,
|
||||
disjointness_visitor: &IsDisjointVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
@@ -799,7 +799,7 @@ impl<'db> VariableLengthTuple<Type<'db>> {
|
||||
db: &'db dyn Db,
|
||||
other: &Tuple<Type<'db>>,
|
||||
inferable: InferableTypeVars<'_, 'db>,
|
||||
relation: TypeRelation,
|
||||
relation: TypeRelation<'db>,
|
||||
relation_visitor: &HasRelationToVisitor<'db>,
|
||||
disjointness_visitor: &IsDisjointVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
@@ -1191,7 +1191,7 @@ impl<'db> Tuple<Type<'db>> {
|
||||
db: &'db dyn Db,
|
||||
other: &Self,
|
||||
inferable: InferableTypeVars<'_, 'db>,
|
||||
relation: TypeRelation,
|
||||
relation: TypeRelation<'db>,
|
||||
relation_visitor: &HasRelationToVisitor<'db>,
|
||||
disjointness_visitor: &IsDisjointVisitor<'db>,
|
||||
) -> ConstraintSet<'db> {
|
||||
|
||||
Reference in New Issue
Block a user