Skip to content

Commit ebd172e

Browse files
dcreagercarljm
andauthored
[red-knot] Several failing tests for generics (#16509)
To kick off the work of supporting generics, this adds many new (currently failing) tests, showing the behavior we plan to support. This is still missing a lot! Not included: - typevar tuples - param specs - variance - `Self` But it's a good start! We can add more failing tests for those once we tackle these. --------- Co-authored-by: Carl Meyer <[email protected]>
1 parent 114abc7 commit ebd172e

File tree

6 files changed

+808
-81
lines changed

6 files changed

+808
-81
lines changed

crates/red_knot_python_semantic/resources/mdtest/generics.md

-81
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
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

Comments
 (0)