[ty] Support field_specifiers for metaclass based transformers

This commit is contained in:
David Peter 2025-10-15 17:21:22 +02:00
parent 23543194fc
commit ea56e2415f
2 changed files with 40 additions and 3 deletions

View File

@ -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

View File

@ -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()) {