Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions crates/ty_python_semantic/resources/mdtest/metaclass.md
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,58 @@ class A[T: str](metaclass=M): ...
reveal_type(A.__class__) # revealed: <class 'M'>
```

## Generic metaclass

### Fully specialized

A generic metaclass fully specialized with concrete types is fine:

```toml
[environment]
python-version = "3.13"
```

```py
class Foo[T](type):
x: T

class Bar(metaclass=Foo[int]): ...

reveal_type(Bar.__class__) # revealed: <class 'Foo[int]'>
```

### Parameterized by type variables (legacy)

A generic metaclass parameterized by type variables is not supported:

```py
from typing import TypeVar, Generic

T = TypeVar("T")

class GenericMeta(type, Generic[T]): ...

# error: [invalid-metaclass] "Generic metaclasses are not supported"
class GenericMetaInstance(metaclass=GenericMeta[T]): ...
```

### Parameterized by type variables (PEP 695)

The same applies using PEP 695 syntax:

```toml
[environment]
python-version = "3.13"
```

```py
class Foo[T](type):
x: T

# error: [invalid-metaclass]
class Bar[T](metaclass=Foo[T]): ...
```

## Metaclasses of metaclasses

```py
Expand Down
19 changes: 19 additions & 0 deletions crates/ty_python_semantic/src/types/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2883,6 +2883,23 @@ impl<'db> StaticClassLiteral<'db> {
let module = parsed_module(db, self.file(db)).load(db);

let explicit_metaclass = self.explicit_metaclass(db, &module);

// Generic metaclasses parameterized by type variables are not supported.
// `metaclass=Meta[int]` is fine, but `metaclass=Meta[T]` is not.
// See: https://typing.python.org/en/latest/spec/generics.html#generic-metaclasses
if let Some(Type::GenericAlias(alias)) = explicit_metaclass {
let specialization_has_typevars = alias
.specialization(db)
.types(db)
.iter()
.any(|ty| ty.has_typevar_or_typevar_instance(db));
if specialization_has_typevars {
return Err(MetaclassError {
kind: MetaclassErrorKind::GenericMetaclass,
});
}
}

let (metaclass, class_metaclass_was_from) = if let Some(metaclass) = explicit_metaclass {
(metaclass, self)
} else if let Some(base_class) = base_classes.next() {
Expand Down Expand Up @@ -8276,6 +8293,8 @@ pub(super) enum MetaclassErrorKind<'db> {
/// inferred metaclass of a base class. This helps us give better error messages in diagnostics.
candidate1_is_base_class: bool,
},
/// The metaclass is a parameterized generic class, which is not supported.
GenericMetaclass,
/// The metaclass is not callable
NotCallable(Type<'db>),
/// The metaclass is of a union type whose some members are not callable
Expand Down
7 changes: 7 additions & 0 deletions crates/ty_python_semantic/src/types/infer/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1222,6 +1222,13 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
));
}
}
MetaclassErrorKind::GenericMetaclass => {
if let Some(builder) =
self.context.report_lint(&INVALID_METACLASS, class_node)
{
builder.into_diagnostic("Generic metaclasses are not supported");
}
}
MetaclassErrorKind::NotCallable(ty) => {
if let Some(builder) =
self.context.report_lint(&INVALID_METACLASS, class_node)
Expand Down
Loading