Skip to content

Commit 2c6693a

Browse files
committed
Only consider inferred constructor specializations from class, not base classes
1 parent 11487cf commit 2c6693a

File tree

3 files changed

+58
-17
lines changed

3 files changed

+58
-17
lines changed

crates/red_knot_python_semantic/resources/mdtest/generics/legacy/classes.md

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ If the type of a constructor parameter is a class typevar, we can use that to in
194194
parameter. The types inferred from a type context and from a constructor parameter must be
195195
consistent with each other.
196196

197-
## `__new__` only
197+
### `__new__` only
198198

199199
```py
200200
from typing import Generic, TypeVar
@@ -211,7 +211,7 @@ reveal_type(C(1)) # revealed: C[Literal[1]]
211211
wrong_innards: C[int] = C("five")
212212
```
213213

214-
## `__init__` only
214+
### `__init__` only
215215

216216
```py
217217
from typing import Generic, TypeVar
@@ -227,7 +227,7 @@ reveal_type(C(1)) # revealed: C[Literal[1]]
227227
wrong_innards: C[int] = C("five")
228228
```
229229

230-
## Identical `__new__` and `__init__` signatures
230+
### Identical `__new__` and `__init__` signatures
231231

232232
```py
233233
from typing import Generic, TypeVar
@@ -246,7 +246,7 @@ reveal_type(C(1)) # revealed: C[Literal[1]]
246246
wrong_innards: C[int] = C("five")
247247
```
248248

249-
## Compatible `__new__` and `__init__` signatures
249+
### Compatible `__new__` and `__init__` signatures
250250

251251
```py
252252
from typing import Generic, TypeVar
@@ -276,9 +276,29 @@ reveal_type(D(1)) # revealed: D[Literal[1]]
276276
wrong_innards: D[int] = D("five")
277277
```
278278

279-
## `__init__` is itself generic
279+
### Both present, `__new__` inherited from a generic base class fwomp
280280

281-
TODO: These do not currently work yet, because we don't correctly model the nested generic contexts.
281+
If either method comes from a generic base class, we don't currently use its inferred specialization
282+
to specialize the class.
283+
284+
```py
285+
from typing import Generic, TypeVar
286+
287+
T = TypeVar("T")
288+
U = TypeVar("U")
289+
V = TypeVar("V")
290+
291+
class C(Generic[T, U]):
292+
def __new__(cls, *args, **kwargs) -> "C[T, U]":
293+
return object.__new__(cls)
294+
295+
class D(C[V, int]):
296+
def __init__(self, x: V) -> None: ...
297+
298+
reveal_type(D(1)) # revealed: D[Literal[1]]
299+
```
300+
301+
### `__init__` is itself generic
282302

283303
```py
284304
from typing import Generic, TypeVar

crates/red_knot_python_semantic/resources/mdtest/generics/pep695/classes.md

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ If the type of a constructor parameter is a class typevar, we can use that to in
171171
parameter. The types inferred from a type context and from a constructor parameter must be
172172
consistent with each other.
173173

174-
## `__new__` only
174+
### `__new__` only
175175

176176
```py
177177
class C[T]:
@@ -184,7 +184,7 @@ reveal_type(C(1)) # revealed: C[Literal[1]]
184184
wrong_innards: C[int] = C("five")
185185
```
186186

187-
## `__init__` only
187+
### `__init__` only
188188

189189
```py
190190
class C[T]:
@@ -196,7 +196,7 @@ reveal_type(C(1)) # revealed: C[Literal[1]]
196196
wrong_innards: C[int] = C("five")
197197
```
198198

199-
## Identical `__new__` and `__init__` signatures
199+
### Identical `__new__` and `__init__` signatures
200200

201201
```py
202202
class C[T]:
@@ -211,7 +211,7 @@ reveal_type(C(1)) # revealed: C[Literal[1]]
211211
wrong_innards: C[int] = C("five")
212212
```
213213

214-
## Compatible `__new__` and `__init__` signatures
214+
### Compatible `__new__` and `__init__` signatures
215215

216216
```py
217217
class C[T]:
@@ -237,9 +237,23 @@ reveal_type(D(1)) # revealed: D[Literal[1]]
237237
wrong_innards: D[int] = D("five")
238238
```
239239

240-
## `__init__` is itself generic
240+
### Both present, `__new__` inherited from a generic base class fwomp
241241

242-
TODO: These do not currently work yet, because we don't correctly model the nested generic contexts.
242+
If either method comes from a generic base class, we don't currently use its inferred specialization
243+
to specialize the class.
244+
245+
```py
246+
class C[T, U]:
247+
def __new__(cls, *args, **kwargs) -> "C[T, U]":
248+
return object.__new__(cls)
249+
250+
class D[V](C[V, int]):
251+
def __init__(self, x: V) -> None: ...
252+
253+
reveal_type(D(1)) # revealed: D[Literal[1]]
254+
```
255+
256+
### `__init__` is itself generic
243257

244258
```py
245259
class C[T]:

crates/red_knot_python_semantic/src/types.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4423,18 +4423,19 @@ impl<'db> Type<'db> {
44234423
// have the class's typevars still in the method signature when we attempt to call it. To
44244424
// do this, we instead use the _identity_ specialization, which maps each of the class's
44254425
// generic typevars to itself.
4426-
let (generic_origin, self_type) = match self {
4426+
let (generic_origin, generic_context, self_type) = match self {
44274427
Type::ClassLiteral(class) => match class.generic_context(db) {
44284428
Some(generic_context) => {
44294429
let specialization = generic_context.identity_specialization(db);
44304430
(
44314431
Some(class),
4432+
Some(generic_context),
44324433
Type::GenericAlias(GenericAlias::new(db, class, specialization)),
44334434
)
44344435
}
4435-
_ => (None, self),
4436+
_ => (None, None, self),
44364437
},
4437-
_ => (None, self),
4438+
_ => (None, None, self),
44384439
};
44394440

44404441
// As of now we do not model custom `__call__` on meta-classes, so the code below
@@ -4550,12 +4551,18 @@ impl<'db> Type<'db> {
45504551
.and_then(Result::ok)
45514552
.as_ref()
45524553
.and_then(Bindings::single_element)
4553-
.and_then(|binding| combine_binding_specialization(db, binding));
4554+
.and_then(|binding| combine_binding_specialization(db, binding))
4555+
.filter(|specialization| {
4556+
Some(specialization.generic_context(db)) == generic_context
4557+
});
45544558
let init_specialization = init_call_outcome
45554559
.and_then(Result::ok)
45564560
.as_ref()
45574561
.and_then(Bindings::single_element)
4558-
.and_then(|binding| combine_binding_specialization(db, binding));
4562+
.and_then(|binding| combine_binding_specialization(db, binding))
4563+
.filter(|specialization| {
4564+
Some(specialization.generic_context(db)) == generic_context
4565+
});
45594566
let specialization =
45604567
combine_specializations(db, new_specialization, init_specialization);
45614568
let specialized = specialization

0 commit comments

Comments
 (0)