ruff/crates/red_knot_python_semantic/resources/mdtest/generics.md
Carl Meyer a48d779c4e
[red-knot] function signature representation (#14304)
## Summary

Add a typed representation of function signatures (parameters and return
type) and infer it correctly from a function.

Convert existing usage of function return types to use the signature
representation.

This does not yet add inferred types for parameters within function body
scopes based on the annotations, but it should be easy to add as a next
step.

Part of #14161 and #13693.

## Test Plan

Added tests.
2024-11-14 23:34:24 +00:00

3 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: T
    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: U
    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: V
    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: W
    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: X
    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[()]