|
| 1 | +# Generic classes |
| 2 | + |
| 3 | +## PEP 695 syntax |
| 4 | + |
| 5 | +TODO: Add a `red_knot_extension` function that asserts whether a function or class is generic. |
| 6 | + |
| 7 | +This is a generic class defined using PEP 695 syntax: |
| 8 | + |
| 9 | +```py |
| 10 | +class C[T]: ... |
| 11 | +``` |
| 12 | + |
| 13 | +A class that inherits from a generic class, and fills its type parameters with typevars, is generic: |
| 14 | + |
| 15 | +```py |
| 16 | +# TODO: no error |
| 17 | +# error: [non-subscriptable] |
| 18 | +class D[U](C[U]): ... |
| 19 | +``` |
| 20 | + |
| 21 | +A class that inherits from a generic class, but fills its type parameters with concrete types, is |
| 22 | +_not_ generic: |
| 23 | + |
| 24 | +```py |
| 25 | +# TODO: no error |
| 26 | +# error: [non-subscriptable] |
| 27 | +class E(C[int]): ... |
| 28 | +``` |
| 29 | + |
| 30 | +A class that inherits from a generic class, and doesn't fill its type parameters at all, implicitly |
| 31 | +uses the default value for the typevar. In this case, that default type is `Unknown`, so `F` |
| 32 | +inherits from `C[Unknown]` and is not itself generic. |
| 33 | + |
| 34 | +```py |
| 35 | +class F(C): ... |
| 36 | +``` |
| 37 | + |
| 38 | +## Legacy syntax |
| 39 | + |
| 40 | +This is a generic class defined using the legacy syntax: |
| 41 | + |
| 42 | +```py |
| 43 | +from typing import Generic, TypeVar |
| 44 | + |
| 45 | +T = TypeVar("T") |
| 46 | + |
| 47 | +# TODO: no error |
| 48 | +# error: [invalid-base] |
| 49 | +class C(Generic[T]): ... |
| 50 | +``` |
| 51 | + |
| 52 | +A class that inherits from a generic class, and fills its type parameters with typevars, is generic. |
| 53 | + |
| 54 | +```py |
| 55 | +class D(C[T]): ... |
| 56 | +``` |
| 57 | + |
| 58 | +(Examples `E` and `F` from above do not have analogues in the legacy syntax.) |
| 59 | + |
| 60 | +## Inferring generic class parameters |
| 61 | + |
| 62 | +The type parameter can be specified explicitly: |
| 63 | + |
| 64 | +```py |
| 65 | +class C[T]: |
| 66 | + x: T |
| 67 | + |
| 68 | +# TODO: no error |
| 69 | +# TODO: revealed: C[int] |
| 70 | +# error: [non-subscriptable] |
| 71 | +reveal_type(C[int]()) # revealed: Unknown |
| 72 | +``` |
| 73 | + |
| 74 | +We can infer the type parameter from a type context: |
| 75 | + |
| 76 | +```py |
| 77 | +c: C[int] = C() |
| 78 | +# TODO: revealed: C[int] |
| 79 | +reveal_type(c) # revealed: C |
| 80 | +``` |
| 81 | + |
| 82 | +The typevars of a fully specialized generic class should no longer be visible: |
| 83 | + |
| 84 | +```py |
| 85 | +# TODO: revealed: int |
| 86 | +reveal_type(c.x) # revealed: T |
| 87 | +``` |
| 88 | + |
| 89 | +If the type parameter is not specified explicitly, and there are no constraints that let us infer a |
| 90 | +specific type, we infer the typevar's default type: |
| 91 | + |
| 92 | +```py |
| 93 | +class D[T = int]: ... |
| 94 | + |
| 95 | +# TODO: revealed: D[int] |
| 96 | +reveal_type(D()) # revealed: D |
| 97 | +``` |
| 98 | + |
| 99 | +If a typevar does not provide a default, we use `Unknown`: |
| 100 | + |
| 101 | +```py |
| 102 | +# TODO: revealed: C[Unknown] |
| 103 | +reveal_type(C()) # revealed: C |
| 104 | +``` |
| 105 | + |
| 106 | +If the type of a constructor parameter is a class typevar, we can use that to infer the type |
| 107 | +parameter: |
| 108 | + |
| 109 | +```py |
| 110 | +class E[T]: |
| 111 | + def __init__(self, x: T) -> None: ... |
| 112 | + |
| 113 | +# TODO: revealed: E[int] or E[Literal[1]] |
| 114 | +reveal_type(E(1)) # revealed: E |
| 115 | +``` |
| 116 | + |
| 117 | +The types inferred from a type context and from a constructor parameter must be consistent with each |
| 118 | +other: |
| 119 | + |
| 120 | +```py |
| 121 | +# TODO: error |
| 122 | +wrong_innards: E[int] = E("five") |
| 123 | +``` |
| 124 | + |
| 125 | +## Generic subclass |
| 126 | + |
| 127 | +When a generic subclass fills its superclass's type parameter with one of its own, the actual types |
| 128 | +propagate through: |
| 129 | + |
| 130 | +```py |
| 131 | +class Base[T]: |
| 132 | + x: T |
| 133 | + |
| 134 | +# TODO: no error |
| 135 | +# error: [non-subscriptable] |
| 136 | +class Sub[U](Base[U]): ... |
| 137 | + |
| 138 | +# TODO: no error |
| 139 | +# TODO: revealed: int |
| 140 | +# error: [non-subscriptable] |
| 141 | +reveal_type(Base[int].x) # revealed: Unknown |
| 142 | +# TODO: revealed: int |
| 143 | +reveal_type(Sub[int].x) # revealed: Unknown |
| 144 | +``` |
| 145 | + |
| 146 | +## Cyclic class definition |
| 147 | + |
| 148 | +A class can use itself as the type parameter of one of its superclasses. (This is also known as the |
| 149 | +[curiously recurring template pattern][crtp] or [F-bounded quantification][f-bound].) |
| 150 | + |
| 151 | +Here, `Sub` is not a generic class, since it fills its superclass's type parameter (with itself). |
| 152 | + |
| 153 | +`stub.pyi`: |
| 154 | + |
| 155 | +```pyi |
| 156 | +class Base[T]: ... |
| 157 | +# TODO: no error |
| 158 | +# error: [non-subscriptable] |
| 159 | +class Sub(Base[Sub]): ... |
| 160 | + |
| 161 | +reveal_type(Sub) # revealed: Literal[Sub] |
| 162 | +``` |
| 163 | + |
| 164 | +`string_annotation.py`: |
| 165 | + |
| 166 | +```py |
| 167 | +class Base[T]: ... |
| 168 | + |
| 169 | +# TODO: no error |
| 170 | +# error: [non-subscriptable] |
| 171 | +class Sub(Base["Sub"]): ... |
| 172 | + |
| 173 | +reveal_type(Sub) # revealed: Literal[Sub] |
| 174 | +``` |
| 175 | + |
| 176 | +`bare_annotation.py`: |
| 177 | + |
| 178 | +```py |
| 179 | +class Base[T]: ... |
| 180 | + |
| 181 | +# TODO: error: [unresolved-reference] |
| 182 | +class Sub(Base[Sub]): ... |
| 183 | +``` |
| 184 | + |
| 185 | +[crtp]: https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern |
| 186 | +[f-bound]: https://en.wikipedia.org/wiki/Bounded_quantification#F-bounded_quantification |
0 commit comments