mirror of
https://github.com/astral-sh/ruff.git
synced 2025-11-20 04:29:47 +00:00
[ty] Add synthetic members to completions on dataclasses (#21446)
## Summary Add synthetic members to completions on dataclasses and dataclass instances. Also, while we're at it, add support for `__weakref__` and `__match_args__`. closes https://github.com/astral-sh/ty/issues/1542 ## Test Plan New Markdown tests
This commit is contained in:
parent
66e9d57797
commit
696d7a5d68
5 changed files with 342 additions and 39 deletions
|
|
@ -461,7 +461,51 @@ del frozen.x # TODO this should emit an [invalid-assignment]
|
|||
|
||||
### `match_args`
|
||||
|
||||
To do
|
||||
If `match_args` is set to `True` (the default), the `__match_args__` attribute is a tuple created
|
||||
from the list of non keyword-only parameters to the synthesized `__init__` method (even if
|
||||
`__init__` is not actually generated).
|
||||
|
||||
```py
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
@dataclass
|
||||
class WithMatchArgs:
|
||||
normal_a: str
|
||||
normal_b: int
|
||||
kw_only: int = field(kw_only=True)
|
||||
|
||||
reveal_type(WithMatchArgs.__match_args__) # revealed: tuple[Literal["normal_a"], Literal["normal_b"]]
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class KwOnlyDefaultMatchArgs:
|
||||
normal_a: str = field(kw_only=False)
|
||||
normal_b: int = field(kw_only=False)
|
||||
kw_only: int
|
||||
|
||||
reveal_type(KwOnlyDefaultMatchArgs.__match_args__) # revealed: tuple[Literal["normal_a"], Literal["normal_b"]]
|
||||
|
||||
@dataclass(match_args=True)
|
||||
class ExplicitMatchArgs:
|
||||
normal: str
|
||||
|
||||
reveal_type(ExplicitMatchArgs.__match_args__) # revealed: tuple[Literal["normal"]]
|
||||
|
||||
@dataclass
|
||||
class Empty: ...
|
||||
|
||||
reveal_type(Empty.__match_args__) # revealed: tuple[()]
|
||||
```
|
||||
|
||||
When `match_args` is explicitly set to `False`, the `__match_args__` attribute is not available:
|
||||
|
||||
```py
|
||||
@dataclass(match_args=False)
|
||||
class NoMatchArgs:
|
||||
x: int
|
||||
y: str
|
||||
|
||||
NoMatchArgs.__match_args__ # error: [unresolved-attribute]
|
||||
```
|
||||
|
||||
### `kw_only`
|
||||
|
||||
|
|
@ -623,7 +667,18 @@ reveal_type(B.__slots__) # revealed: tuple[Literal["x"], Literal["y"]]
|
|||
|
||||
### `weakref_slot`
|
||||
|
||||
To do
|
||||
When a dataclass is defined with `weakref_slot=True`, the `__weakref__` attribute is generated. For
|
||||
now, we do not attempt to infer a more precise type for it.
|
||||
|
||||
```py
|
||||
from dataclasses import dataclass
|
||||
|
||||
@dataclass(slots=True, weakref_slot=True)
|
||||
class C:
|
||||
x: int
|
||||
|
||||
reveal_type(C.__weakref__) # revealed: Any | None
|
||||
```
|
||||
|
||||
## `Final` fields
|
||||
|
||||
|
|
|
|||
|
|
@ -548,13 +548,20 @@ static_assert(not has_member(c, "dynamic_attr"))
|
|||
|
||||
### Dataclasses
|
||||
|
||||
So far, we do not include synthetic members of dataclasses.
|
||||
#### Basic
|
||||
|
||||
For dataclasses, we make sure to include all synthesized members:
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.9"
|
||||
```
|
||||
|
||||
```py
|
||||
from ty_extensions import has_member, static_assert
|
||||
from dataclasses import dataclass
|
||||
|
||||
@dataclass(order=True)
|
||||
@dataclass
|
||||
class Person:
|
||||
age: int
|
||||
name: str
|
||||
|
|
@ -562,13 +569,177 @@ class Person:
|
|||
static_assert(has_member(Person, "name"))
|
||||
static_assert(has_member(Person, "age"))
|
||||
|
||||
static_assert(has_member(Person, "__dataclass_fields__"))
|
||||
static_assert(has_member(Person, "__dataclass_params__"))
|
||||
|
||||
# These are always available, since they are also defined on `object`:
|
||||
static_assert(has_member(Person, "__init__"))
|
||||
static_assert(has_member(Person, "__repr__"))
|
||||
static_assert(has_member(Person, "__eq__"))
|
||||
static_assert(has_member(Person, "__ne__"))
|
||||
|
||||
# TODO: this should ideally be available:
|
||||
static_assert(has_member(Person, "__lt__")) # error: [static-assert-error]
|
||||
# There are not available, unless `order=True` is set:
|
||||
static_assert(not has_member(Person, "__lt__"))
|
||||
static_assert(not has_member(Person, "__le__"))
|
||||
static_assert(not has_member(Person, "__gt__"))
|
||||
static_assert(not has_member(Person, "__ge__"))
|
||||
|
||||
# These are not available, unless `slots=True`, `weakref_slot=True` are set:
|
||||
static_assert(not has_member(Person, "__slots__"))
|
||||
static_assert(not has_member(Person, "__weakref__"))
|
||||
|
||||
# Not available before Python 3.13:
|
||||
static_assert(not has_member(Person, "__replace__"))
|
||||
```
|
||||
|
||||
The same behavior applies to instances of dataclasses:
|
||||
|
||||
```py
|
||||
def _(person: Person):
|
||||
static_assert(has_member(person, "name"))
|
||||
static_assert(has_member(person, "age"))
|
||||
|
||||
static_assert(has_member(person, "__dataclass_fields__"))
|
||||
static_assert(has_member(person, "__dataclass_params__"))
|
||||
|
||||
static_assert(has_member(person, "__init__"))
|
||||
static_assert(has_member(person, "__repr__"))
|
||||
static_assert(has_member(person, "__eq__"))
|
||||
static_assert(has_member(person, "__ne__"))
|
||||
|
||||
static_assert(not has_member(person, "__lt__"))
|
||||
static_assert(not has_member(person, "__le__"))
|
||||
static_assert(not has_member(person, "__gt__"))
|
||||
static_assert(not has_member(person, "__ge__"))
|
||||
|
||||
static_assert(not has_member(person, "__slots__"))
|
||||
|
||||
static_assert(not has_member(person, "__replace__"))
|
||||
```
|
||||
|
||||
#### `__init__`, `__repr__` and `__eq__`
|
||||
|
||||
`__init__`, `__repr__` and `__eq__` are always available (via `object`), even when `init=False`,
|
||||
`repr=False` and `eq=False` are set:
|
||||
|
||||
```py
|
||||
from ty_extensions import has_member, static_assert
|
||||
from dataclasses import dataclass
|
||||
|
||||
@dataclass(init=False, repr=False, eq=False)
|
||||
class C:
|
||||
x: int
|
||||
|
||||
static_assert(has_member(C, "__init__"))
|
||||
static_assert(has_member(C, "__repr__"))
|
||||
static_assert(has_member(C, "__eq__"))
|
||||
static_assert(has_member(C, "__ne__"))
|
||||
static_assert(has_member(C(), "__init__"))
|
||||
static_assert(has_member(C(), "__repr__"))
|
||||
static_assert(has_member(C(), "__eq__"))
|
||||
static_assert(has_member(C(), "__ne__"))
|
||||
```
|
||||
|
||||
#### `order=True`
|
||||
|
||||
When `order=True` is set, comparison dunder methods become available:
|
||||
|
||||
```py
|
||||
from ty_extensions import has_member, static_assert
|
||||
from dataclasses import dataclass
|
||||
|
||||
@dataclass(order=True)
|
||||
class C:
|
||||
x: int
|
||||
|
||||
static_assert(has_member(C, "__lt__"))
|
||||
static_assert(has_member(C, "__le__"))
|
||||
static_assert(has_member(C, "__gt__"))
|
||||
static_assert(has_member(C, "__ge__"))
|
||||
|
||||
def _(c: C):
|
||||
static_assert(has_member(c, "__lt__"))
|
||||
static_assert(has_member(c, "__le__"))
|
||||
static_assert(has_member(c, "__gt__"))
|
||||
static_assert(has_member(c, "__ge__"))
|
||||
```
|
||||
|
||||
#### `slots=True`
|
||||
|
||||
When `slots=True`, the corresponding dunder attribute becomes available:
|
||||
|
||||
```py
|
||||
from ty_extensions import has_member, static_assert
|
||||
from dataclasses import dataclass
|
||||
|
||||
@dataclass(slots=True)
|
||||
class C:
|
||||
x: int
|
||||
|
||||
static_assert(has_member(C, "__slots__"))
|
||||
static_assert(has_member(C(1), "__slots__"))
|
||||
```
|
||||
|
||||
#### `weakref_slot=True`
|
||||
|
||||
When `weakref_slot=True`, the corresponding dunder attribute becomes available:
|
||||
|
||||
```py
|
||||
from ty_extensions import has_member, static_assert
|
||||
from dataclasses import dataclass
|
||||
|
||||
@dataclass(slots=True, weakref_slot=True)
|
||||
class C:
|
||||
x: int
|
||||
|
||||
static_assert(has_member(C, "__weakref__"))
|
||||
static_assert(has_member(C(1), "__weakref__"))
|
||||
```
|
||||
|
||||
#### `__replace__` in Python 3.13+
|
||||
|
||||
Since Python 3.13, dataclasses have a `__replace__` method:
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.13"
|
||||
```
|
||||
|
||||
```py
|
||||
from ty_extensions import has_member, static_assert
|
||||
from dataclasses import dataclass
|
||||
|
||||
@dataclass
|
||||
class C:
|
||||
x: int
|
||||
|
||||
static_assert(has_member(C, "__replace__"))
|
||||
|
||||
def _(c: C):
|
||||
static_assert(has_member(c, "__replace__"))
|
||||
```
|
||||
|
||||
#### `__match_args__`
|
||||
|
||||
Since Python 3.10, dataclasses have a `__match_args__` attribute:
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.10"
|
||||
```
|
||||
|
||||
```py
|
||||
from ty_extensions import has_member, static_assert
|
||||
from dataclasses import dataclass
|
||||
|
||||
@dataclass
|
||||
class C:
|
||||
x: int
|
||||
|
||||
static_assert(has_member(C, "__match_args__"))
|
||||
|
||||
def _(c: C):
|
||||
static_assert(has_member(c, "__match_args__"))
|
||||
```
|
||||
|
||||
### Attributes not available at runtime
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue