[ty] Support class-arguments for dataclass transformers (#21457)

## Summary

Allow metaclass-based and baseclass-based dataclass-transformers to
overwrite the default behavior using class arguments:

```py
class Person(Model, order=True):
    # ...
```

## Conformance tests

Four new tests passing!

## Test Plan

New Markdown tests
This commit is contained in:
David Peter
2025-11-15 17:47:48 +01:00
committed by GitHub
parent 698231a47a
commit 29acc1e860
3 changed files with 63 additions and 8 deletions

View File

@@ -356,13 +356,17 @@ model < model # No error
### Overwriting of default parameters on the dataclass-like class
In the following examples, we show how a model can overwrite the default parameters set by the
`dataclass_transform` decorator. In particular, we change from `frozen=True` to `frozen=False`, and
from `order=False` (default) to `order=True`:
#### Using function-based transformers
```py
from typing import dataclass_transform
@dataclass_transform(frozen_default=True)
def default_frozen_model(*, frozen: bool = True): ...
def default_frozen_model(*, frozen: bool = True, order: bool = False): ...
@default_frozen_model()
class Frozen:
name: str
@@ -370,12 +374,16 @@ class Frozen:
f = Frozen(name="test")
f.name = "new" # error: [invalid-assignment]
@default_frozen_model(frozen=False)
Frozen(name="A") < Frozen(name="B") # error: [unsupported-operator]
@default_frozen_model(frozen=False, order=True)
class Mutable:
name: str
m = Mutable(name="test")
m.name = "new" # No error
reveal_type(Mutable(name="A") < Mutable(name="B")) # revealed: bool
```
#### Using metaclass-based transformers
@@ -392,6 +400,7 @@ class DefaultFrozenMeta(type):
namespace,
*,
frozen: bool = True,
order: bool = False,
): ...
class DefaultFrozenModel(metaclass=DefaultFrozenMeta): ...
@@ -402,12 +411,17 @@ class Frozen(DefaultFrozenModel):
f = Frozen(name="test")
f.name = "new" # error: [invalid-assignment]
class Mutable(DefaultFrozenModel, frozen=False):
Frozen(name="A") < Frozen(name="B") # error: [unsupported-operator]
class Mutable(DefaultFrozenModel, frozen=False, order=True):
name: str
m = Mutable(name="test")
# TODO: no error here
# TODO: This should not be an error. In order to support this, we need to implement the precise `frozen` semantics of
# `dataclass_transform` described here: https://typing.python.org/en/latest/spec/dataclasses.html#dataclass-semantics
m.name = "new" # error: [invalid-assignment]
reveal_type(Mutable(name="A") < Mutable(name="B")) # revealed: bool
```
#### Using base-class-based transformers
@@ -421,6 +435,7 @@ class DefaultFrozenModel:
cls,
*,
frozen: bool = True,
order: bool = False,
): ...
class Frozen(DefaultFrozenModel):
@@ -429,12 +444,15 @@ class Frozen(DefaultFrozenModel):
f = Frozen(name="test")
f.name = "new" # error: [invalid-assignment]
class Mutable(DefaultFrozenModel, frozen=False):
Frozen(name="A") < Frozen(name="B") # error: [unsupported-operator]
class Mutable(DefaultFrozenModel, frozen=False, order=True):
name: str
m = Mutable(name="test")
# TODO: This should not be an error
m.name = "new" # error: [invalid-assignment]
m.name = "new" # No error
reveal_type(Mutable(name="A") < Mutable(name="B")) # revealed: bool
```
## `field_specifiers`