diff --git a/crates/ty_python_semantic/resources/mdtest/dataclasses/dataclass_transform.md b/crates/ty_python_semantic/resources/mdtest/dataclasses/dataclass_transform.md index 99f75f6517..99c13a690e 100644 --- a/crates/ty_python_semantic/resources/mdtest/dataclasses/dataclass_transform.md +++ b/crates/ty_python_semantic/resources/mdtest/dataclasses/dataclass_transform.md @@ -461,7 +461,7 @@ The [`typing.dataclass_transform`] specification also allows classes (such as `d to be listed in `field_specifiers`, but it is currently unclear how this should work, and other type checkers do not seem to support this either. -### Basic example +### For function-based transformers ```py from typing_extensions import dataclass_transform, Any @@ -487,6 +487,34 @@ reveal_type(alice.name) # revealed: str reveal_type(alice.age) # revealed: int | None ``` +### For metaclass-based transformers + +```py +from typing_extensions import dataclass_transform, Any + +def fancy_field(*, init: bool = True, kw_only: bool = False) -> Any: ... +@dataclass_transform(field_specifiers=(fancy_field,)) +class FancyMeta(type): + def __new__(cls, name, bases, namespace): + ... + return super().__new__(cls, name, bases, namespace) + +class FancyBase(metaclass=FancyMeta): ... + +class Person(FancyBase): + id: int = fancy_field(init=False) + name: str = fancy_field() + age: int | None = fancy_field(kw_only=True) + +reveal_type(Person.__init__) # revealed: (self: Person, name: str = Unknown, *, age: int | None = Unknown) -> None + +alice = Person("Alice", age=30) + +reveal_type(alice.id) # revealed: int +reveal_type(alice.name) # revealed: str +reveal_type(alice.age) # revealed: int | None +``` + ## Overloaded dataclass-like decorators In the case of an overloaded decorator, the `dataclass_transform` decorator can be applied to the diff --git a/crates/ty_python_semantic/src/types/infer/builder.rs b/crates/ty_python_semantic/src/types/infer/builder.rs index a5588d7140..081623551f 100644 --- a/crates/ty_python_semantic/src/types/infer/builder.rs +++ b/crates/ty_python_semantic/src/types/infer/builder.rs @@ -4532,12 +4532,21 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { let class_node = enclosing_scope.node().as_class()?; let class_definition = index.expect_single_definition(class_node); - infer_definition_types(db, class_definition) + let class_literal = infer_definition_types(db, class_definition) .declaration_type(class_definition) .inner_type() - .as_class_literal()? + .as_class_literal()?; + + class_literal .dataclass_params(db) .map(|params| SmallVec::from(params.field_specifiers(db))) + .or_else(|| { + class_literal + .try_metaclass(db) + .ok() + .and_then(|(_, params)| params) + .map(|params| SmallVec::from(params.field_specifiers(db))) + }) } if let Some(specifiers) = field_specifiers(self.db(), self.index, self.scope()) {