mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 05:15:12 +00:00

## Summary Create definitions and infer types for PEP 695 type variables. This just gives us the type of the type variable itself (the type of `T` as a runtime object in the body of `def f[T](): ...`), with special handling for its attributes `__name__`, `__bound__`, `__constraints__`, and `__default__`. Mostly the support for these attributes exists because it is easy to implement and allows testing that we are internally representing the typevar correctly. This PR doesn't yet have support for interpreting a typevar as a type annotation, which is of course the primary use of a typevar. But the information we store in the typevar's type in this PR gives us everything we need to handle it correctly in a future PR when the typevar appears in an annotation. ## Test Plan Added mdtest.
3.1 KiB
3.1 KiB
PEP 695 Generics
Class Declarations
Basic PEP 695 generics
class MyBox[T]:
data: T
box_model_number = 695
def __init__(self, data: T):
self.data = data
box: MyBox[int] = MyBox(5)
# TODO should emit a diagnostic here (str is not assignable to int)
wrong_innards: MyBox[int] = MyBox("five")
# TODO reveal int
reveal_type(box.data) # revealed: @Todo
reveal_type(MyBox.box_model_number) # revealed: Literal[695]
Subclassing
class MyBox[T]:
data: T
def __init__(self, data: T):
self.data = data
# TODO not error on the subscripting
# error: [non-subscriptable]
class MySecureBox[T](MyBox[T]): ...
secure_box: MySecureBox[int] = MySecureBox(5)
reveal_type(secure_box) # revealed: MySecureBox
# TODO reveal int
reveal_type(secure_box.data) # revealed: @Todo
Cyclical class definition
In type stubs, classes can reference themselves in their base class definitions. For example, in
typeshed
, we have class str(Sequence[str]): ...
.
This should hold true even with generics at play.
class Seq[T]: ...
# TODO not error on the subscripting
class S[T](Seq[S]): ... # error: [non-subscriptable]
reveal_type(S) # revealed: Literal[S]
Type params
A PEP695 type variable defines a value of type typing.TypeVar
with attributes __name__
,
__bounds__
, __constraints__
, and __default__
(the latter three all lazily evaluated):
def f[T, U: A, V: (A, B), W = A, X: A = A1]():
reveal_type(T) # revealed: TypeVar
reveal_type(T.__name__) # revealed: Literal["T"]
reveal_type(T.__bound__) # revealed: None
reveal_type(T.__constraints__) # revealed: tuple[()]
reveal_type(T.__default__) # revealed: NoDefault
reveal_type(U) # revealed: TypeVar
reveal_type(U.__name__) # revealed: Literal["U"]
reveal_type(U.__bound__) # revealed: type[A]
reveal_type(U.__constraints__) # revealed: tuple[()]
reveal_type(U.__default__) # revealed: NoDefault
reveal_type(V) # revealed: TypeVar
reveal_type(V.__name__) # revealed: Literal["V"]
reveal_type(V.__bound__) # revealed: None
reveal_type(V.__constraints__) # revealed: tuple[type[A], type[B]]
reveal_type(V.__default__) # revealed: NoDefault
reveal_type(W) # revealed: TypeVar
reveal_type(W.__name__) # revealed: Literal["W"]
reveal_type(W.__bound__) # revealed: None
reveal_type(W.__constraints__) # revealed: tuple[()]
reveal_type(W.__default__) # revealed: type[A]
reveal_type(X) # revealed: TypeVar
reveal_type(X.__name__) # revealed: Literal["X"]
reveal_type(X.__bound__) # revealed: type[A]
reveal_type(X.__constraints__) # revealed: tuple[()]
reveal_type(X.__default__) # revealed: type[A1]
class A: ...
class B: ...
class A1(A): ...
Minimum two constraints
A typevar with less than two constraints emits a diagnostic and is treated as unconstrained:
# error: [invalid-typevar-constraints] "TypeVar must have at least two constrained types"
def f[T: (int,)]():
reveal_type(T.__constraints__) # revealed: tuple[()]