You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
[red-knot] Allow explicit specialization of generic classes (#17023)
This PR lets you explicitly specialize a generic class using a subscript
expression. It introduces three new Rust types for representing classes:
- `NonGenericClass`
- `GenericClass` (not specialized)
- `GenericAlias` (specialized)
and two enum wrappers:
- `ClassType` (a non-generic class or generic alias, represents a class
_type_ at runtime)
- `ClassLiteralType` (a non-generic class or generic class, represents a
class body in the AST)
We also add internal support for specializing callables, in particular
function literals. (That is, the internal `Type` representation now
attaches an optional specialization to a function literal.) This is used
in this PR for the methods of a generic class, but should also give us
most of what we need for specializing generic _functions_ (which this PR
does not yet tackle).
---------
Co-authored-by: Alex Waygood <[email protected]>
Co-authored-by: Carl Meyer <[email protected]>
We can infer the type parameter from a type context:
75
120
76
121
```py
122
+
class C[T]:
123
+
x: T
124
+
77
125
c: C[int] = C()
78
126
#TODO: revealed: C[int]
79
-
reveal_type(c) # revealed: C
127
+
reveal_type(c) # revealed: C[Unknown]
80
128
```
81
129
82
130
The typevars of a fully specialized generic class should no longer be visible:
83
131
84
132
```py
85
133
#TODO: revealed: int
86
-
reveal_type(c.x) # revealed: T
134
+
reveal_type(c.x) # revealed: Unknown
87
135
```
88
136
89
137
If the type parameter is not specified explicitly, and there are no constraints that let us infer a
@@ -92,15 +140,13 @@ specific type, we infer the typevar's default type:
92
140
```py
93
141
class D[T = int]: ...
94
142
95
-
#TODO: revealed: D[int]
96
-
reveal_type(D()) # revealed: D
143
+
reveal_type(D()) # revealed: D[int]
97
144
```
98
145
99
146
If a typevar does not provide a default, we use `Unknown`:
100
147
101
148
```py
102
-
#TODO: revealed: C[Unknown]
103
-
reveal_type(C()) # revealed: C
149
+
reveal_type(C()) # revealed: C[Unknown]
104
150
```
105
151
106
152
If the type of a constructor parameter is a class typevar, we can use that to infer the type
@@ -111,17 +157,14 @@ class E[T]:
111
157
def__init__(self, x: T) -> None: ...
112
158
113
159
#TODO: revealed: E[int] or E[Literal[1]]
114
-
#TODO should not emit an error
115
-
# error: [invalid-argument-type] "Object of type `Literal[1]` cannot be assigned to parameter 2 (`x`) of bound method `__init__`; expected type `T`"
116
-
reveal_type(E(1)) # revealed: E
160
+
reveal_type(E(1)) # revealed: E[Unknown]
117
161
```
118
162
119
163
The types inferred from a type context and from a constructor parameter must be consistent with each
120
164
other:
121
165
122
166
```py
123
-
#TODO: the error should not leak the `T` typevar and should mention `E[int]`
124
-
# error: [invalid-argument-type] "Object of type `Literal["five"]` cannot be assigned to parameter 2 (`x`) of bound method `__init__`; expected type `T`"
167
+
#TODO: error: [invalid-argument-type]
125
168
wrong_innards: E[int] = E("five")
126
169
```
127
170
@@ -134,17 +177,33 @@ propagate through:
134
177
class Base[T]:
135
178
x: T |None=None
136
179
137
-
#TODO: no error
138
-
# error: [non-subscriptable]
139
180
class Sub[U](Base[U]): ...
140
181
182
+
reveal_type(Base[int].x) # revealed: int | None
183
+
reveal_type(Sub[int].x) # revealed: int | None
184
+
```
185
+
186
+
## Generic methods
187
+
188
+
Generic classes can contain methods that are themselves generic. The generic methods can refer to
189
+
the typevars of the enclosing generic class, and introduce new (distinct) typevars that are only in
Copy file name to clipboardExpand all lines: crates/red_knot_python_semantic/resources/mdtest/generics/scoping.md
+40-8Lines changed: 40 additions & 8 deletions
Original file line number
Diff line number
Diff line change
@@ -82,18 +82,51 @@ class C[T]:
82
82
defm2(self, x: T) -> T:
83
83
return x
84
84
85
-
c: C[int] = C()
86
-
#TODO: no error
87
-
# error: [invalid-argument-type]
85
+
c: C[int] = C[int]()
88
86
c.m1(1)
89
-
#TODO: no error
90
-
# error: [invalid-argument-type]
91
87
c.m2(1)
92
-
#TODO: expected type `int`
93
-
# error: [invalid-argument-type] "Object of type `Literal["string"]` cannot be assigned to parameter 2 (`x`) of bound method `m2`; expected type `T`"
88
+
# error: [invalid-argument-type] "Object of type `Literal["string"]` cannot be assigned to parameter 2 (`x`) of bound method `m2`; expected type `int`"
94
89
c.m2("string")
95
90
```
96
91
92
+
## Functions on generic classes are descriptors
93
+
94
+
This repeats the tests in the [Functions as descriptors](./call/methods.md) test suite, but on a
95
+
generic class. This ensures that we are carrying any specializations through the entirety of the
96
+
descriptor protocol, which is how `self` parameters are bound to instance methods.
0 commit comments