-
Notifications
You must be signed in to change notification settings - Fork 1.5k
[red-knot] Add Type::TypeVar
variant
#17102
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
178ee89
feb1ea9
3c1ad79
59430b6
77ffa80
6f86720
cf81967
6615df1
b2f5a2a
590680c
e6b7d40
debd60a
aa391fd
e57e62e
3df79cc
5b08e93
82e810f
a3d7253
15682d5
9e07efe
fb63c22
3459056
71d425e
233e938
9785202
c86af50
aa00895
d99d1d7
58f0995
42fd54a
6bd69f1
1d6a917
37692f1
3869dc6
0c1745b
8bdd9e2
39244dd
30172a0
7803ec3
b4d2a0c
a3493cd
d2c7647
f70d565
f3afc91
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -48,4 +48,120 @@ class C[T]: | |
reveal_type(x) # revealed: T | ||
``` | ||
|
||
## Subtyping and assignability | ||
|
||
An unbounded, unconstrained typevar is assignable to itself, but is not a subtype of itself (or any | ||
other type), since it might be specialized to `Any`, which does not participate in the subtyping | ||
relationship. | ||
|
||
It is neither assignable to or a subtype of any other type (including other typevars), since we can | ||
make no assumption about what type it will be specialized to. | ||
|
||
```py | ||
from knot_extensions import is_assignable_to, is_subtype_of, static_assert | ||
|
||
def unbounded_unconstrained[T, U](t: T, u: U) -> None: | ||
static_assert(is_assignable_to(T, T)) | ||
static_assert(is_assignable_to(U, U)) | ||
static_assert(not is_assignable_to(T, U)) | ||
static_assert(not is_assignable_to(U, T)) | ||
|
||
static_assert(not is_subtype_of(T, T)) | ||
static_assert(not is_subtype_of(U, U)) | ||
static_assert(not is_subtype_of(T, U)) | ||
static_assert(not is_subtype_of(U, T)) | ||
``` | ||
|
||
A bounded typevar is assignable to its bound, but the bound is not assignable to the typevar (since | ||
the typevar might be specialized to a smaller type). (TODO: Unless the bound is final, in which case | ||
the final class is also assignable to the typevar.) | ||
dcreager marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
```py | ||
from typing_extensions import final | ||
|
||
def bounded[T: int](t: T) -> None: | ||
static_assert(is_assignable_to(T, int)) | ||
static_assert(not is_assignable_to(int, T)) | ||
|
||
static_assert(not is_subtype_of(T, int)) | ||
static_assert(not is_subtype_of(int, T)) | ||
|
||
@final | ||
class FinalClass: ... | ||
|
||
def bounded_final[T: FinalClass](t: T) -> None: | ||
static_assert(is_assignable_to(T, FinalClass)) | ||
# TODO: is_assignable_to | ||
static_assert(not is_assignable_to(FinalClass, T)) | ||
dcreager marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
static_assert(not is_subtype_of(T, FinalClass)) | ||
static_assert(not is_subtype_of(FinalClass, T)) | ||
``` | ||
|
||
Two distinct typevars are not assignable to each other, even if they have the same bounds, since | ||
there is (still) no guarantee that they will be specialized to the same type. (TODO: Unless the | ||
bound is final, in which case we _can_ assume that the two typevars must always be specialized to | ||
that final class.) | ||
|
||
```py | ||
def two_bounded[T: int, U: int](t: T, u: U) -> None: | ||
static_assert(not is_assignable_to(T, U)) | ||
static_assert(not is_assignable_to(U, T)) | ||
|
||
static_assert(not is_subtype_of(T, U)) | ||
static_assert(not is_subtype_of(U, T)) | ||
|
||
def two_final_bounded[T: FinalClass, U: FinalClass](t: T, u: U) -> None: | ||
# TODO: is_assignable_to | ||
static_assert(not is_assignable_to(T, U)) | ||
# TODO: is_assignable_to | ||
static_assert(not is_assignable_to(U, T)) | ||
|
||
static_assert(not is_subtype_of(T, U)) | ||
static_assert(not is_subtype_of(U, T)) | ||
``` | ||
|
||
A constrained typevar is assignable to the union of its constraints, but not to any of the | ||
constraints individually. None of the constraints are assignable to the typevar. | ||
|
||
```py | ||
def constrained[T: (int, str)](t: T) -> None: | ||
static_assert(not is_assignable_to(T, int)) | ||
static_assert(not is_assignable_to(T, str)) | ||
static_assert(is_assignable_to(T, int | str)) | ||
static_assert(not is_assignable_to(int, T)) | ||
static_assert(not is_assignable_to(str, T)) | ||
static_assert(not is_assignable_to(int | str, T)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is! I had to reorder some of the subtype/assignable clauses to make this pass. The correct order is:
which aligns with what the extended DNF representation for types would be if/when we introduce There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Per another comment, this description of the "correct order" is not accurate anymore. It's more subtle — LHS constrained typevars have to be handled first, then the order described above |
||
|
||
static_assert(not is_subtype_of(T, int)) | ||
static_assert(not is_subtype_of(T, str)) | ||
static_assert(not is_subtype_of(T, int | str)) | ||
static_assert(not is_subtype_of(int, T)) | ||
static_assert(not is_subtype_of(str, T)) | ||
static_assert(not is_subtype_of(int | str, T)) | ||
``` | ||
carljm marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
Two distinct typevars are not assignable to each other, even if they have the same constraints, and | ||
even if any of the constraints are final. There must always be at least two distinct constraints, | ||
meaning that there is (still) no guarantee that they will be specialized to the same type. | ||
|
||
```py | ||
def two_constrainted[T: (int, str), U: (int, str)](t: T, u: U) -> None: | ||
static_assert(not is_assignable_to(T, U)) | ||
static_assert(not is_assignable_to(U, T)) | ||
|
||
static_assert(not is_subtype_of(T, U)) | ||
static_assert(not is_subtype_of(U, T)) | ||
|
||
@final | ||
class AnotherFinalClass: ... | ||
|
||
def two_final_constrainted[T: (FinalClass, AnotherFinalClass), U: (FinalClass, AnotherFinalClass)](t: T, u: U) -> None: | ||
static_assert(not is_assignable_to(T, U)) | ||
static_assert(not is_assignable_to(U, T)) | ||
|
||
static_assert(not is_subtype_of(T, U)) | ||
static_assert(not is_subtype_of(U, T)) | ||
``` | ||
|
||
[pep 695]: https://peps.python.org/pep-0695/ |
Uh oh!
There was an error while loading. Please reload this page.