[ty] support kw_only=True for dataclass() and field() (#19677)
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks-instrumented (push) Blocked by required conditions
CI / benchmarks-walltime (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run

## Summary
https://github.com/astral-sh/ty/issues/111

adds support for `@dataclass(kw_only=True)`
(https://docs.python.org/3/library/dataclasses.html)

## Test Plan
- new mdtests
- triaged conformance diffs (notes here:
https://diffswarm.dev/d-01k2gknwyq82f6x17zqf3apjxc)
- `mypy_primer` no-op
This commit is contained in:
justin 2025-08-14 11:02:55 -04:00 committed by GitHub
parent 9aaa82d037
commit dc2e8ab377
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 155 additions and 10 deletions

View file

@ -195,7 +195,41 @@ OrderTrueOverwritten(1) < OrderTrueOverwritten(2)
### `kw_only_default`
To do
When provided, sets the default value for the `kw_only` parameter of `field()`.
```py
from typing import dataclass_transform
from dataclasses import field
@dataclass_transform(kw_only_default=True)
def create_model(*, init=True): ...
@create_model()
class A:
name: str
a = A(name="Harry")
# error: [missing-argument]
# error: [too-many-positional-arguments]
a = A("Harry")
```
TODO: This can be overridden by the call to the decorator function.
```py
from typing import dataclass_transform
@dataclass_transform(kw_only_default=True)
def create_model(*, kw_only: bool = True): ...
@create_model(kw_only=False)
class CustomerModel:
id: int
name: str
# TODO: Should not emit errors
# error: [missing-argument]
# error: [too-many-positional-arguments]
c = CustomerModel(1, "Harry")
```
### `field_specifiers`

View file

@ -465,7 +465,84 @@ To do
### `kw_only`
To do
An error is emitted if a dataclass is defined with `kw_only=True` and positional arguments are
passed to the constructor.
```toml
[environment]
python-version = "3.10"
```
```py
from dataclasses import dataclass
@dataclass(kw_only=True)
class A:
x: int
y: int
# error: [missing-argument] "No arguments provided for required parameters `x`, `y`"
# error: [too-many-positional-arguments] "Too many positional arguments: expected 0, got 2"
a = A(1, 2)
a = A(x=1, y=2)
```
The class-level parameter can be overridden per-field.
```py
from dataclasses import dataclass, field
@dataclass(kw_only=True)
class A:
a: str = field(kw_only=False)
b: int = 0
A("hi")
```
If some fields are `kw_only`, they should appear after all positional fields in the `__init__`
signature.
```py
@dataclass
class A:
b: int = field(kw_only=True, default=3)
a: str
A("hi")
```
The field-level `kw_only` value takes precedence over the `KW_ONLY` pseudo-type.
```py
from dataclasses import field, dataclass, KW_ONLY
@dataclass
class C:
_: KW_ONLY
x: int = field(kw_only=False)
C(x=1)
C(1)
```
### `kw_only` - Python < 3.10
For Python < 3.10, `kw_only` is not supported.
```toml
[environment]
python-version = "3.9"
```
```py
from dataclasses import dataclass
@dataclass(kw_only=True) # TODO: Emit a diagnostic here
class A:
x: int
y: int
```
### `slots`

View file

@ -63,13 +63,12 @@ class Person:
age: int | None = field(default=None, kw_only=True)
role: str = field(default="user", kw_only=True)
# TODO: the `age` and `role` fields should be keyword-only
# revealed: (self: Person, name: str, age: int | None = None, role: str = Literal["user"]) -> None
# revealed: (self: Person, name: str, *, age: int | None = None, role: str = Literal["user"]) -> None
reveal_type(Person.__init__)
alice = Person(role="admin", name="Alice")
# TODO: this should be an error
# error: [too-many-positional-arguments] "Too many positional arguments: expected 1, got 2"
bob = Person("Bob", 30)
```