diff --git a/crates/ty_python_semantic/resources/mdtest/metaclass.md b/crates/ty_python_semantic/resources/mdtest/metaclass.md index 3c4762fe8fefe..2a05f779f1061 100644 --- a/crates/ty_python_semantic/resources/mdtest/metaclass.md +++ b/crates/ty_python_semantic/resources/mdtest/metaclass.md @@ -247,6 +247,58 @@ class A[T: str](metaclass=M): ... reveal_type(A.__class__) # revealed: ``` +## 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: +``` + +### 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 diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index 2316040ea8878..6532df131e769 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -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() { @@ -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 diff --git a/crates/ty_python_semantic/src/types/infer/builder.rs b/crates/ty_python_semantic/src/types/infer/builder.rs index 9d1c6670c624d..1d89d403771a3 100644 --- a/crates/ty_python_semantic/src/types/infer/builder.rs +++ b/crates/ty_python_semantic/src/types/infer/builder.rs @@ -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)