Skip to content

Commit 482b14a

Browse files
committed
Reject Generic and TypedDict
1 parent 87799bd commit 482b14a

File tree

2 files changed

+17
-16
lines changed

2 files changed

+17
-16
lines changed

crates/ty_python_semantic/resources/mdtest/call/new_class.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -194,8 +194,8 @@ reveal_type(MyEnum) # revealed: <class 'MyEnum'>
194194

195195
### Generic and TypedDict bases
196196

197-
`type()` doesn't support `__mro_entries__`, so `Generic[T]` and `TypedDict` fail as bases for
198-
`type()`. `types.new_class()` handles `__mro_entries__` properly, so these are valid:
197+
Even though `types.new_class()` handles `__mro_entries__` at runtime, ty does not yet model the full
198+
typing semantics of dynamically-created generic classes or TypedDicts, so these bases are rejected:
199199

200200
```py
201201
import types
@@ -204,11 +204,11 @@ from typing_extensions import TypedDict
204204

205205
T = TypeVar("T")
206206

207+
# error: [invalid-base] "Invalid base for class created via `types.new_class()`"
207208
GenericClass = types.new_class("GenericClass", (Generic[T],))
208-
reveal_type(GenericClass) # revealed: <class 'GenericClass'>
209209

210+
# error: [invalid-base] "Invalid base for class created via `types.new_class()`"
210211
TypedDictClass = types.new_class("TypedDictClass", (TypedDict,))
211-
reveal_type(TypedDictClass) # revealed: <class 'TypedDictClass'>
212212
```
213213

214214
### `type[X]` bases

crates/ty_python_semantic/src/types/infer/builder.rs

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3456,33 +3456,37 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
34563456

34573457
// Check for special bases that are not allowed for dynamic classes.
34583458
//
3459-
// `type()` doesn't support `__mro_entries__`, so Generic and TypedDict bases
3460-
// are invalid. `types.new_class()` handles `__mro_entries__` properly, so
3461-
// these are allowed.
3459+
// Generic and TypedDict bases rely on special typing semantics that ty cannot yet
3460+
// model for dynamically-created classes, so we reject them for both `type()` and
3461+
// `types.new_class()`.
34623462
//
34633463
// Protocol works with both, but ty can't yet represent a dynamically-created
34643464
// protocol class, so we emit a warning.
34653465
//
34663466
// (`NamedTuple` is rejected earlier: `try_from_type` returns `None`
34673467
// without a concrete subclass, so it's reported as an `InvalidBases` MRO error.)
34683468
match class_base {
3469-
ClassBase::Generic | ClassBase::TypedDict if kind == DynamicClassKind::TypeCall => {
3469+
ClassBase::Generic | ClassBase::TypedDict => {
34703470
if let Some(builder) = self.context.report_lint(&INVALID_BASE, diagnostic_node)
34713471
{
3472-
let mut diagnostic =
3473-
builder.into_diagnostic("Invalid base for class created via `type()`");
3472+
let mut diagnostic = builder.into_diagnostic(format_args!(
3473+
"Invalid base for class created via `{fn_name}`"
3474+
));
34743475
diagnostic
34753476
.set_primary_message(format_args!("Has type `{}`", base.display(db)));
34763477
match class_base {
34773478
ClassBase::Generic => {
3478-
diagnostic.info("Classes created via `type()` cannot be generic");
3479+
diagnostic.info(format_args!(
3480+
"Classes created via `{fn_name}` cannot be generic"
3481+
));
34793482
diagnostic.info(format_args!(
34803483
"Consider using `class {name}(Generic[...]): ...` instead"
34813484
));
34823485
}
34833486
ClassBase::TypedDict => {
3484-
diagnostic
3485-
.info("Classes created via `type()` cannot be TypedDicts");
3487+
diagnostic.info(format_args!(
3488+
"Classes created via `{fn_name}` cannot be TypedDicts"
3489+
));
34863490
diagnostic.info(format_args!(
34873491
"Consider using `TypedDict(\"{name}\", {{}})` instead"
34883492
));
@@ -3491,9 +3495,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
34913495
}
34923496
}
34933497
}
3494-
ClassBase::Generic | ClassBase::TypedDict => {
3495-
// types.new_class() handles __mro_entries__, so these are valid.
3496-
}
34973498
ClassBase::Protocol => {
34983499
if let Some(builder) = self
34993500
.context

0 commit comments

Comments
 (0)