PY-54560 Support PEP-681 dataclass_transform

`dataclass_transform` support posed a number of challenges to the current
AST/PSI stubs separation in our architecture. For the standard "dataclasses"
module and the "attrs" package API, we could rely on well-known names
and defaults to recognize and preserve the information about decorator
arguments and field specifier arguments in PSI stubs. With `dataclass_transform`
however, effectively any decorator call or extending any base class can indicate
using a "magical" API that should generate dataclass-like entities.
At the moment of building PSI stubs we can't be sure, because we can't leave
the boundaries of the current file to resolve these names and determine if these
decorators and base classes are decorated themselves with `dataclass_transform`.

To support that, we instead rely on well-known keyword argument names documented
in the spec, e.g. "kw_only" or "frozen". In other words, whenever we encounter
any decorator call or a superclass list with such keyword arguments, we generate
a `PyDataclassStub` stub for the corresponding class.

The same thing is happening with class attribute initializers, i.e. whenever
we see a function call with argument such as "default" or "kw_only" in their
RHS, we generate a `PyDataclassFieldStub` for the corresponding target expression.
Both of these stub interfaces now can contain null values for the corresponding
properties if they were not specified directly in the class definition.

Finally, for the `dataclass_transform` decorator itself, a new custom decorator stub
was introduced -- `PyDataclassTransformDecoratorStub`, it preserves its keyword
arguments, such as "keyword_only_default" or "frozen_default" controlling
the default properties of generated dataclasses.

Later, when we need concluded information about specific dataclass properties,
e.g. in `PyDataclassTypeProvider` to generate a constructor signature, or in
`PyDataclassInspection`, we try to "resolve" this incomplete information from stubs
into finalized `PyDataclassParameters` and `PyDataclassFieldParameters` that
contain non-null versions of the same fields. The main entry points for that
are `resolveDataclassParameters` and `resolveDataclassFieldParameters`.
These methods additionally handle the situations where decorators, superclass
lists and field specifiers lack any keyword arguments, and thus, there were no
automatically created custom stubs for them.

All the existing usages of `PyDataclassStub` and `PyDataclassFieldStub`
were updated to operate on `PyDataclassParameters` and `PyDataclassFieldParameters`
instead.

Counterparts of the tests on various inspection checks for the standard dataclasses
definitions were added for dataclasses created with `dataclass_transform`, even
though the spec is unclear on some aspects the expected type checker semantics, e.g.
if combining "eq=False" and "order=True" or specifying both "default" and
"default_factory" for a field should be reported.
I tried to follow common sense when enabling existing checks for such arbitrary
user-defined dataclass APIs.

GitOrigin-RevId: 4180a1e32b5e4025fc4e3ed49bb8d67af0d60e66
This commit is contained in:
Mikhail Golubev
2024-06-25 11:57:05 +03:00
committed by intellij-monorepo-bot
parent a20dc6e783
commit 1da22d34fd
72 changed files with 2277 additions and 299 deletions

View File

@@ -0,0 +1,3 @@
from dt_class import RecordViaBaseClass
RecordViaBaseClass(<warning descr="Unexpected argument">1</warning>, <warning descr="Unexpected argument">"foo"</warning><warning descr="Parameter 'id' unfilled"><warning descr="Parameter 'name' unfilled">)</warning></warning>

View File

@@ -0,0 +1,8 @@
from typing import dataclass_transform
@dataclass_transform()
class DataclassBase:
def __init_subclass__(cls, **kwargs):
import dataclasses
cls.__dir__ = dataclasses.dataclass(**kwargs)(cls).__dir__

View File

@@ -0,0 +1,6 @@
from dt_base import DataclassBase
class RecordViaBaseClass(DataclassBase, kw_only=True):
id: int
name: str

View File

@@ -0,0 +1,3 @@
from dt_class import RecordViaDecorator
RecordViaDecorator(<warning descr="Unexpected argument">1</warning>, <warning descr="Unexpected argument">"foo"</warning><warning descr="Parameter 'id' unfilled"><warning descr="Parameter 'name' unfilled">)</warning></warning>

View File

@@ -0,0 +1,7 @@
from dt_decorator import my_dt_decorator
@my_dt_decorator(kw_only=True)
class RecordViaDecorator:
id: int
name: str

View File

@@ -0,0 +1,7 @@
from typing import dataclass_transform, Callable
@dataclass_transform()
def my_dt_decorator(**kwargs) -> Callable[[type], type]:
import dataclasses
return dataclasses.dataclass(**kwargs) # type: ignore

View File

@@ -0,0 +1,3 @@
from dt_class import RecordViaMetaClass
RecordViaMetaClass(<warning descr="Unexpected argument">1</warning>, <warning descr="Unexpected argument">"foo"</warning><warning descr="Parameter 'id' unfilled"><warning descr="Parameter 'name' unfilled">)</warning></warning>

View File

@@ -0,0 +1,5 @@
from dt_meta import DataclassMeta
class DataclassBase(metaclass=DataclassMeta):
pass

View File

@@ -0,0 +1,6 @@
from dt_base import DataclassBase
class RecordViaMetaClass(DataclassBase, kw_only=True):
id: int
name: str

View File

@@ -0,0 +1,17 @@
from typing import dataclass_transform
@dataclass_transform()
class DataclassMeta(type):
def __new__(
cls,
name,
bases,
namespace,
**kwargs,
):
import dataclasses
return dataclasses.dataclass(**kwargs)(super().__new__(cls, name, bases, namespace)) # type: ignore

View File

@@ -0,0 +1,26 @@
from decorator import my_dataclass
@my_dataclass(order=False)
class Test1:
def __gt__(self, other):
pass
@my_dataclass()
class Test2:
def __gt__(self, other):
pass
print(Test1() < Test1())
print(Test2() < Test2())
print(Test1() > Test1())
print(Test2() > Test2())
print(Test1 < Test1)
print(Test2 < Test2)
print(Test1 > Test1)
print(Test2 > Test2)

View File

@@ -0,0 +1,13 @@
from typing import dataclass_transform, Callable, TypeVar
T = TypeVar("T")
@dataclass_transform()
def my_dataclass(**kwargs) -> Callable[[type[T]], type[T]]:
...
@dataclass_transform(order_default=True)
def my_dataclass_order_default(**kwargs) -> Callable[[type[T]], type[T]]:
...

View File

@@ -0,0 +1,23 @@
from decorator import my_dataclass_order_default, my_dataclass
@my_dataclass_order_default()
class A1:
x: int = 0
@my_dataclass()
class A2:
y: int = 0
print(A1(1) <error descr="'__lt__' not supported between instances of 'A1' and 'A2'"><</error> A2(2))
print(A1(1) <error descr="'__le__' not supported between instances of 'A1' and 'A2'"><=</error> A2(2))
print(A1(1) <error descr="'__gt__' not supported between instances of 'A1' and 'A2'">></error> A2(2))
print(A1(1) <error descr="'__ge__' not supported between instances of 'A1' and 'A2'">>=</error> A2(2))
print(A1 < A2)
print(A1 <= A2)
print(A1 > A2)
print(A1 >= A2)

View File

@@ -0,0 +1,13 @@
from typing import dataclass_transform, Callable, TypeVar
T = TypeVar("T")
@dataclass_transform()
def my_dataclass(**kwargs) -> Callable[[type[T]], type[T]]:
...
@dataclass_transform(order_default=True)
def my_dataclass_order_default(**kwargs) -> Callable[[type[T]], type[T]]:
...

View File

@@ -0,0 +1,35 @@
from decorator import my_dataclass_order_default, my_dataclass
@my_dataclass(order=True)
class A1:
x: int = 0
@my_dataclass_order_default()
class A2:
y: int = 0
print(A1(1) < A1(2))
print(A1(1) <= A1(2))
print(A1(1) > A1(2))
print(A1(1) >= A1(2))
print(A1(1) <error descr="'__lt__' not supported between instances of 'A1' and 'A2'"><</error> A2(2))
print(A1(1) <error descr="'__le__' not supported between instances of 'A1' and 'A2'"><=</error> A2(2))
print(A1(1) <error descr="'__gt__' not supported between instances of 'A1' and 'A2'">></error> A2(2))
print(A1(1) <error descr="'__ge__' not supported between instances of 'A1' and 'A2'">>=</error> A2(2))
print(A1 < A1)
print(A1 <= A1)
print(A1 > A1)
print(A1 >= A1)
print(A1 < A2)
print(A1 <= A2)
print(A1 > A2)
print(A1 >= A2)

View File

@@ -0,0 +1,13 @@
from typing import dataclass_transform, Callable, TypeVar
T = TypeVar("T")
@dataclass_transform()
def my_dataclass(**kwargs) -> Callable[[type[T]], type[T]]:
...
@dataclass_transform(order_default=True)
def my_dataclass_order_default(**kwargs) -> Callable[[type[T]], type[T]]:
...

View File

@@ -0,0 +1,35 @@
from decorator import my_dataclass_order_default, my_dataclass
@my_dataclass()
class A1:
x: int = 0
@my_dataclass_order_default(order=False)
class A2:
y: int = 0
print(A1(1) <error descr="'__lt__' not supported between instances of 'A1'"><</error> A1(2))
print(A1(1) <error descr="'__le__' not supported between instances of 'A1'"><=</error> A1(2))
print(A1(1) <error descr="'__gt__' not supported between instances of 'A1'">></error> A1(2))
print(A1(1) <error descr="'__ge__' not supported between instances of 'A1'">>=</error> A1(2))
print(A1(1) <error descr="'__lt__' not supported between instances of 'A1' and 'A2'"><</error> A2(2))
print(A1(1) <error descr="'__le__' not supported between instances of 'A1' and 'A2'"><=</error> A2(2))
print(A1(1) <error descr="'__gt__' not supported between instances of 'A1' and 'A2'">></error> A2(2))
print(A1(1) <error descr="'__ge__' not supported between instances of 'A1' and 'A2'">>=</error> A2(2))
print(A1 < A1)
print(A1 <= A1)
print(A1 > A1)
print(A1 >= A1)
print(A1 < A2)
print(A1 <= A2)
print(A1 > A2)
print(A1 >= A2)

View File

@@ -0,0 +1,13 @@
from typing import dataclass_transform, Callable, TypeVar
T = TypeVar("T")
@dataclass_transform()
def my_dataclass(**kwargs) -> Callable[[type[T]], type[T]]:
...
@dataclass_transform(order_default=True)
def my_dataclass_order_default(**kwargs) -> Callable[[type[T]], type[T]]:
...

View File

@@ -0,0 +1,8 @@
import dataclasses
from mod import Base
@dataclasses.dataclass
class <error descr="Non-default argument(s) follows default argument(s) defined in 'Base'">Sub</error>(Base):
field_no_default: int

View File

@@ -0,0 +1,7 @@
import dataclasses
@dataclasses.dataclass
class Base:
base_field_no_default: int = dataclasses.MISSING
base_field_default: int = 42

View File

@@ -0,0 +1,13 @@
from typing import ClassVar
from decorator import my_field, my_dataclass
@my_dataclass()
class E1:
a: int = my_field(default=1)
b1: int = my_field(default_factory=int)
b2: int = my_field(factory=int)
c1: int = my_field<error descr="Cannot specify both 'default' and 'default_factory'">(default=1, default_factory=int)</error>
c2: int = my_field<error descr="Cannot specify both 'default' and 'default_factory'">(default=1, factory=int)</error>
d: ClassVar[int] = my_field(default_factory=<error descr="Field cannot have a default factory">int</error>)

View File

@@ -0,0 +1,12 @@
from typing import dataclass_transform, Callable, TypeVar
T = TypeVar("T")
def my_field(**kwargs):
...
@dataclass_transform(field_specifiers=(my_field,))
def my_dataclass(**kwargs) -> Callable[[type[T]], type[T]]:
...

View File

@@ -0,0 +1,8 @@
from decorator import my_dataclass, my_field
@my_dataclass()
class A1:
<error descr="Attribute 'a' lacks a type annotation">a</error> = my_field()
b = 1
c: int = 1

View File

@@ -0,0 +1,12 @@
from typing import dataclass_transform, Callable, TypeVar
T = TypeVar("T")
def my_field(**kwargs):
...
@dataclass_transform(field_specifiers=(my_field,))
def my_dataclass(**kwargs) -> Callable[[type[T]], type[T]]:
...

View File

@@ -0,0 +1,20 @@
from bases import A1, A3, A41, A42
from decorator import my_dataclass
@my_dataclass()
class <error descr="Non-default argument(s) follows default argument(s) defined in 'A1'">B1</error>(A1):
y1: str
y2: str = "1"
@my_dataclass()
class B3(A3):
y1: str
@my_dataclass()
class B4<error descr="Inherited non-default argument(s) defined in A41 follows inherited default argument defined in A42">(A41, A42)</error>:
pass

View File

@@ -0,0 +1,22 @@
from decorator import my_dataclass
@my_dataclass()
class A1:
x1: int
x2: int = 1
@my_dataclass()
class A3:
x1: int
@my_dataclass()
class A41:
field1: int
@my_dataclass()
class A42:
field2: str = "1"

View File

@@ -0,0 +1,12 @@
from typing import dataclass_transform, Callable, TypeVar
T = TypeVar("T")
def my_field(**kwargs):
...
@dataclass_transform(field_specifiers=(my_field,))
def my_dataclass(**kwargs) -> Callable[[type[T]], type[T]]:
...

View File

@@ -0,0 +1,93 @@
import dataclasses
from typing import ClassVar
from decorator import my_dataclass, my_field
@my_dataclass()
class A1:
bar1: int
<error descr="Fields with a default value must come after any fields without a default.">baz1</error>: int = 1
foo1: int
<error descr="Fields with a default value must come after any fields without a default.">bar2</error>: int = 2
baz2: int
foo2: int = 3
@my_dataclass()
class A2:
bar: int
baz: str = ""
foo: int = 5
@my_dataclass()
class A3:
bar1: int
baz1: ClassVar[int] = 1
foo1: int
bar2: ClassVar[int] = 2
baz2: int
foo2: int = 3
@my_dataclass()
class A4:
bar1: int
baz1: ClassVar = 1
foo1: int
bar2: ClassVar = 2
baz2: int
foo2: int = 3
@my_dataclass()
class B1:
a: int = my_field()
b: int
@my_dataclass()
class B2:
<error descr="Fields with a default value must come after any fields without a default.">a</error>: int = my_field(default=1)
b: int = my_field()
@my_dataclass()
class B3:
<error descr="Fields with a default value must come after any fields without a default.">a</error>: int = my_field(default_factory=int)
b: int = my_field()
@my_dataclass()
class C1:
x: int = dataclasses.MISSING
y: int
@my_dataclass()
class C2:
x: int = my_field(default=dataclasses.MISSING)
y: int
C2(1, 2)
@my_dataclass()
class C3:
x: int = my_field(default_factory=dataclasses.MISSING)
y: int
C3(1, 2)
@my_dataclass()
class D1:
x: int = 0
y: int = my_field(init=False)
@my_dataclass()
class E1:
foo = "bar" # <- has no type annotation, so doesn't count.
baz: str

View File

@@ -0,0 +1,12 @@
from typing import dataclass_transform, Callable, TypeVar
T = TypeVar("T")
def my_field(**kwargs):
...
@dataclass_transform(field_specifiers=(my_field,))
def my_dataclass(**kwargs) -> Callable[[type[T]], type[T]]:
...

View File

@@ -0,0 +1,75 @@
from decorator import my_dataclass, my_dataclass_kw_only_default, my_field, my_filed_kw_only_default
@my_dataclass(kw_only=True)
class KwOnlyExplicitClassParam:
kw_only_default: int = 42
kw_only_no_default: int
@my_dataclass()
class C1(KwOnlyExplicitClassParam):
not_kw_only_no_default: int
@my_dataclass_kw_only_default()
class KwOnlyImplicitClassParam:
kw_only_default: int = 42
kw_only_no_default: int
@my_dataclass()
class C2(KwOnlyImplicitClassParam):
not_kw_only_no_default: int
@my_dataclass()
class KwOnlyExplicitFieldParam:
kw_only_default: int = my_field(default=42, kw_only=True)
kw_only_no_default: int
@my_dataclass()
class C3(KwOnlyExplicitFieldParam):
not_kw_only_no_default: int
@my_dataclass()
class KwOnlyImplicitFieldParam:
kw_only_default: int = my_filed_kw_only_default(default=42)
kw_only_no_default: int
@my_dataclass()
class C3(KwOnlyImplicitFieldParam):
not_kw_only_no_default: int
@my_dataclass()
class BaseNotKwOnlyDefault:
not_kw_only_default: int = 42
@my_dataclass()
class <error descr="Non-default argument(s) follows default argument(s) defined in 'BaseNotKwOnlyDefault'">SubNotKwOnly</error>(BaseNotKwOnlyDefault):
not_kw_only_no_default: int
@my_dataclass(kw_only=True)
class SubKwOnlyExplicitClassParam(BaseNotKwOnlyDefault):
kw_only_no_default: int
@my_dataclass_kw_only_default()
class SubKwOnlyImplicitClassParam(BaseNotKwOnlyDefault):
kw_only_no_default: int
@my_dataclass()
class SubKwOnlyExplicitFieldParam(BaseNotKwOnlyDefault):
kw_only_no_default: int = my_field(kw_only=True)
@my_dataclass()
class SubKwOnlyExplicitFieldParam(BaseNotKwOnlyDefault):
kw_only_no_default: int = my_filed_kw_only_default()

View File

@@ -0,0 +1,22 @@
import dataclasses
from typing import dataclass_transform, Callable, TypeVar
T = TypeVar("T")
def my_field(*, default=dataclasses.MISSING, kw_only=False):
...
def my_filed_kw_only_default(*, default=dataclasses.MISSING, kw_only=True):
...
@dataclass_transform(field_specifiers=(my_field, my_filed_kw_only_default))
def my_dataclass(**kwargs) -> Callable[[type[T]], type[T]]:
...
@dataclass_transform(kw_only_default=True, field_specifiers=(my_field, my_filed_kw_only_default))
def my_dataclass_kw_only_default(**kwargs) -> Callable[[type[T]], type[T]]:
...

View File

@@ -0,0 +1,84 @@
from decorator import my_dataclass, my_dataclass_order_default, my_dataclass_frozen_default, my_dataclass_eq_default
@my_dataclass()
class ImplicitInit:
field: int
def __init__(self, field):
...
@my_dataclass(<warning descr="'init' is ignored if the class already defines '__init__' method">init=True</warning>)
class ExplicitInit:
field: int
def __init__(self, field):
...
@my_dataclass()
class ImplicitEq:
field: int
def __eq__(self, other):
pass
# Unclear semantic
@my_dataclass_eq_default()
class ImplicitEq2:
field: int
def __eq__(self, other):
pass
@my_dataclass(<warning descr="'eq' is ignored if the class already defines '__eq__' method">eq=True</warning>)
class ExplicitEq:
field: int
def __eq__(self, other):
pass
# Unclear semantic
@my_dataclass_order_default()
class ImplicitOrder:
field: int
def __gt__(self, other):
pass
@my_dataclass(<error descr="'order' should be False if the class defines one of order methods">order=True</error>)
class ExplicitOrder:
field: int
def __gt__(self, other):
pass
@my_dataclass(<error descr="'unsafe_hash' should be False if the class defines '__hash__'">unsafe_hash=True</error>)
class ExplicitUnsafeHash:
field: int
def __hash__(self):
pass
# Unclear semantic
@my_dataclass_frozen_default()
class ImplicitFrozen:
field: int
def __setattr__(self, name, val):
pass
@my_dataclass(<error descr="'frozen' should be False if the class defines '__setattr__' or '__delattr__'">frozen=True</error>)
class ExplicitFrozen:
field: int
def __setattr__(self, name, val):
pass

View File

@@ -0,0 +1,23 @@
from typing import dataclass_transform, Callable, TypeVar
T = TypeVar("T")
@dataclass_transform()
def my_dataclass(**kwargs) -> Callable[[type[T]], type[T]]:
...
@dataclass_transform(frozen_default=True)
def my_dataclass_frozen_default(**kwargs) -> Callable[[type[T]], type[T]]:
...
@dataclass_transform(order_default=True)
def my_dataclass_order_default(**kwargs) -> Callable[[type[T]], type[T]]:
...
@dataclass_transform(eq_default=True)
def my_dataclass_eq_default(**kwargs) -> Callable[[type[T]], type[T]]:
...

View File

@@ -0,0 +1,8 @@
import dataclasses
from mod import Base
@dataclasses.dataclass
class <error descr="Non-default argument(s) follows default argument(s) defined in 'Base'">Sub</error>(Base):
field_no_default: int

View File

@@ -0,0 +1,6 @@
import dataclasses
@dataclasses.dataclass
class Base:
field_default: int = 42

View File

@@ -0,0 +1,22 @@
from bases import BaseFrozenExplicit, BaseNonFrozenImplicit, BaseFrozenWithFrozenDefault
from decorator import my_dataclass, my_dataclass_frozen_default
@my_dataclass()
class <error descr="Frozen dataclasses can not inherit non-frozen one and vice versa">B1</error>(BaseFrozenExplicit):
b: str = "2"
@my_dataclass(<error descr="Frozen dataclasses can not inherit non-frozen one and vice versa">frozen=True</error>)
class B2(BaseNonFrozenImplicit):
b: str = "2"
@my_dataclass()
class <error descr="Frozen dataclasses can not inherit non-frozen one and vice versa">B1</error>(BaseFrozenWithFrozenDefault):
b: str = "2"
@my_dataclass_frozen_default()
class <error descr="Frozen dataclasses can not inherit non-frozen one and vice versa">B2</error>(BaseNonFrozenImplicit):
b: str = "2"

View File

@@ -0,0 +1,16 @@
from decorator import my_dataclass, my_dataclass_frozen_default
@my_dataclass(frozen=True)
class BaseFrozenExplicit:
a: int = 1
@my_dataclass()
class BaseNonFrozenImplicit:
a: int = 1
@my_dataclass_frozen_default()
class BaseFrozenWithFrozenDefault:
a: int = 1

View File

@@ -0,0 +1,12 @@
from typing import dataclass_transform, Callable, TypeVar
T = TypeVar("T")
@dataclass_transform()
def my_dataclass(**kwargs) -> Callable[[type[T]], type[T]]:
...
@dataclass_transform(frozen_default=True)
def my_dataclass_frozen_default(**kwargs) -> Callable[[type[T]], type[T]]:
...

View File

@@ -0,0 +1,26 @@
from collections import OrderedDict
from typing import ClassVar
from decorator import my_dataclass, field
@my_dataclass()
class A:
a: list[int] = <error descr="Mutable default '[]' is not allowed. Use 'default_factory'">[]</error>
b: list[int] = <error descr="Mutable default 'list()' is not allowed. Use 'default_factory'">list()</error>
c: set[int] = <error descr="Mutable default '{1}' is not allowed. Use 'default_factory'">{1}</error>
d: set[int] = <error descr="Mutable default 'set()' is not allowed. Use 'default_factory'">set()</error>
e: tuple[int, ...] = ()
f: tuple[int, ...] = tuple()
g: ClassVar[list[int]] = []
h: ClassVar = []
i: dict[int, int] = <error descr="Mutable default '{1: 2}' is not allowed. Use 'default_factory'">{1: 2}</error>
j: dict[int, int] = <error descr="Mutable default 'dict()' is not allowed. Use 'default_factory'">dict()</error>
k = []
l = list()
m: dict[int, int] = <error descr="Mutable default 'OrderedDict()' is not allowed. Use 'default_factory'">OrderedDict()</error>
n: frozenset[int] = frozenset()
o: list = field(default_factory=list)
a2: type[list[int]] = list
b2: type[set[int]] = set
c2: type[tuple[int, ...]] = tuple

View File

@@ -0,0 +1,12 @@
from typing import dataclass_transform, Callable, TypeVar
T = TypeVar("T")
def field(**kwargs):
...
@dataclass_transform(field_specifiers=(field,))
def my_dataclass(**kwargs) -> Callable[[type[T]], type[T]]:
...

View File

@@ -0,0 +1,69 @@
from decorator import my_dataclass, my_dataclass_frozen_default
@my_dataclass()
class B1:
x: int
y: str
z: float = 0.0
B1.x = 5
b1 = B1(1, "2")
b1.x = 2
b1.y = "3"
b1.z = 1.0
del b1.x
del b1.y
del b1.z
@my_dataclass(frozen=False)
class B2:
x: int
y: str
z: float = 0.0
B2.x = 5
b2 = B2(1, "2")
b2.x = 2
b2.y = "3"
b2.z = 1.0
del b2.x
del b2.y
del b2.z
@my_dataclass(frozen=True)
class B3:
x: int
y: str
z: float = 0.0
B3.x = 5
b3 = B3(1, "2")
<error descr="'B3' object attribute 'x' is read-only">b3.x</error> = 2
<error descr="'B3' object attribute 'y' is read-only">b3.y</error> = "3"
<error descr="'B3' object attribute 'z' is read-only">b3.z</error> = 1.0
del <error descr="'B3' object attribute 'x' is read-only">b3.x</error>
del <error descr="'B3' object attribute 'y' is read-only">b3.y</error>
del <error descr="'B3' object attribute 'z' is read-only">b3.z</error>
@my_dataclass_frozen_default()
class B4:
x: int
y: str
z: float = 0.0
B4.x = 5
b4 = B4(1, "2")
<error descr="'B4' object attribute 'x' is read-only">b4.x</error> = 2
<error descr="'B4' object attribute 'y' is read-only">b4.y</error> = "3"
<error descr="'B4' object attribute 'z' is read-only">b4.z</error> = 1.0
del <error descr="'B4' object attribute 'x' is read-only">b4.x</error>
del <error descr="'B4' object attribute 'y' is read-only">b4.y</error>
del <error descr="'B4' object attribute 'z' is read-only">b4.z</error>

View File

@@ -0,0 +1,13 @@
from typing import dataclass_transform, Callable, TypeVar
T = TypeVar("T")
@dataclass_transform()
def my_dataclass(**kwargs) -> Callable[[type[T]], type[T]]:
...
@dataclass_transform(frozen_default=True)
def my_dataclass_frozen_default(**kwargs) -> Callable[[type[T]], type[T]]:
...

View File

@@ -0,0 +1,11 @@
from bases import BaseFrozenWithFrozenDefault
from decorator import my_dataclass
@my_dataclass(frozen=True)
class B1(BaseFrozenWithFrozenDefault):
b: str = "1"
<error descr="'BaseFrozenWithFrozenDefault' object attribute 'a' is read-only">BaseFrozenWithFrozenDefault().a</error> = 2
<error descr="'B1' object attribute 'a' is read-only">B1().a</error> = 2
<error descr="'B1' object attribute 'b' is read-only">B1().b</error> = "2"

View File

@@ -0,0 +1,6 @@
from decorator import my_dataclass, my_dataclass_frozen_default
@my_dataclass_frozen_default()
class BaseFrozenWithFrozenDefault:
a: int = 1

View File

@@ -0,0 +1,12 @@
from typing import dataclass_transform, Callable, TypeVar
T = TypeVar("T")
@dataclass_transform()
def my_dataclass(**kwargs) -> Callable[[type[T]], type[T]]:
...
@dataclass_transform(frozen_default=True)
def my_dataclass_frozen_default(**kwargs) -> Callable[[type[T]], type[T]]:
...

View File

@@ -0,0 +1,21 @@
from decorator import my_dataclass_order_default, my_dataclass
@my_dataclass(<error descr="'eq' must be true if 'order' is true">eq=False</error>, order=True)
class A1:
x: int
@my_dataclass(eq=False)
class A2:
x: int
@my_dataclass(eq=False, order=False)
class A3:
x: int
@my_dataclass_order_default(<error descr="'eq' must be true if 'order' is true">eq=False</error>)
class A4:
x: int

View File

@@ -0,0 +1,13 @@
from typing import dataclass_transform, Callable, TypeVar
T = TypeVar("T")
@dataclass_transform()
def my_dataclass(**kwargs) -> Callable[[type[T]], type[T]]:
...
@dataclass_transform(order_default=True)
def my_dataclass_order_default(**kwargs) -> Callable[[type[T]], type[T]]:
...

View File

@@ -30,3 +30,10 @@ class Derived3(Base3):
Derived3(<arg3>)
Base3(<arg4>)
@dataclass(kw_only=True)
class Base4:
a: int = field(kw_only=False)
b: int
Base4(<arg5>)

View File

@@ -0,0 +1,24 @@
from typing import Callable, dataclass_transform
def field(**kwargs):
...
def not_field(**kwargs):
...
@dataclass_transform(field_specifiers=(field,))
def my_dataclass(**kwargs) -> Callable[[type], type]:
...
@my_dataclass()
class Dataclass:
field1: int = field()
field2: int = field(default=42)
field3: int = not_field()
field4: int = not_field(default=42)
Dataclass(<arg1>)

View File

@@ -0,0 +1,21 @@
from typing import Callable, dataclass_transform
def field_init_default_false(init: bool = False):
...
def field(**kwargs):
...
@dataclass_transform(field_specifiers=(field_init_default_false, field))
def my_dataclass(**kwargs) -> Callable[[type], type]:
...
@my_dataclass()
class Dataclass:
not_init_spec_default: int = field_init_default_false()
not_init_spec_param: int = field(init=False)
init_spec_param: int = field(init=True)
init_inferred: int = field()
Dataclass(<arg1>)

View File

@@ -0,0 +1,76 @@
from typing import Callable, dataclass_transform
def field_kw_only_default_false(kw_only: bool = False):
...
def field_kw_only_default_true(kw_only: bool = True):
...
def field_no_own_params():
...
@dataclass_transform(field_specifiers=(field_kw_only_default_false, field_kw_only_default_true, field_no_own_params))
def my_dataclass(**kwargs) -> Callable[[type], type]:
...
@dataclass_transform(kw_only_default=True, field_specifiers=(field_kw_only_default_false, field_kw_only_default_true, field_no_own_params))
def my_dataclass_kw_only_default_true(**kwargs) -> Callable[[type], type]:
...
@dataclass_transform(kw_only_default=False, field_specifiers=(field_kw_only_default_false, field_kw_only_default_true, field_no_own_params))
def my_dataclass_kw_only_default_false(**kwargs) -> Callable[[type], type]:
...
@my_dataclass(kw_only=True)
class DataclassKwOnlyTrue:
not_kw_only_spec_default: int = field_kw_only_default_false()
not_kw_only_spec_arg: int = field_kw_only_default_true(kw_only=False)
kw_only_inferred: int = field_no_own_params()
kw_only_spec_default: int = field_kw_only_default_true()
kw_only_spec_arg: int = field_kw_only_default_false(kw_only=True)
@my_dataclass_kw_only_default_true()
class DataclassKwOnlyDefaultTrue:
not_kw_only_spec_default: int = field_kw_only_default_false()
not_kw_only_spec_arg: int = field_kw_only_default_true(kw_only=False)
kw_only_inferred: int = field_no_own_params()
kw_only_spec_default: int = field_kw_only_default_true()
kw_only_spec_arg: int = field_kw_only_default_false(kw_only=True)
@my_dataclass(kw_only=False)
class DataclassKwOnlyFalse:
not_kw_only_spec_default: int = field_kw_only_default_false()
not_kw_only_spec_arg: int = field_kw_only_default_true(kw_only=False)
not_kw_only_inferred: int = field_no_own_params()
kw_only_spec_default: int = field_kw_only_default_true()
kw_only_spec_arg: int = field_kw_only_default_false(kw_only=True)
@my_dataclass_kw_only_default_false()
class DataclassKwOnlyDefaultFalse:
not_kw_only_spec_default: int = field_kw_only_default_false()
not_kw_only_spec_arg: int = field_kw_only_default_true(kw_only=False)
not_kw_only_inferred: int = field_no_own_params()
kw_only_spec_default: int = field_kw_only_default_true()
kw_only_spec_arg: int = field_kw_only_default_false(kw_only=True)
@my_dataclass()
class DataclassImplicitKwOnlyFalse:
not_kw_only_spec_default: int = field_kw_only_default_false()
not_kw_only_spec_arg: int = field_kw_only_default_true(kw_only=False)
not_kw_only_inferred: int = field_no_own_params()
kw_only_spec_default: int = field_kw_only_default_true()
kw_only_spec_arg: int = field_kw_only_default_false(kw_only=True)
DataclassKwOnlyTrue(<arg1>)
DataclassKwOnlyDefaultTrue(<arg2>)
DataclassKwOnlyFalse(<arg3>)
DataclassKwOnlyDefaultFalse(<arg4>)
DataclassImplicitKwOnlyFalse(<arg5>)

View File

@@ -0,0 +1,22 @@
from typing import dataclass_transform, Callable, TypeVar
T = TypeVar("T")
@dataclass_transform()
def my_dataclass(**kwargs) -> Callable[[type[T]], type[T]]:
...
@my_dataclass()
class Super:
super_attr: int
@my_dataclass()
class Sub(Super):
sub_attr: int
super_attr: str
Sub(<arg1>)

View File

@@ -0,0 +1,2 @@
class Customer:
attr = field(default_factory=list, init=False, kw_only=False, alias="alias")

View File

@@ -0,0 +1,13 @@
import typing
@typing.dataclass_transform(kw_only_default=True, order_default=True)
def create_model(
*,
frozen: bool = False,
kw_only: bool = True,
) -> Callable[[Type[_T]], Type[_T]]: ...
@create_model(frozen=True, kw_only=False)
class CustomerModel:
id: int
name: str

View File

@@ -0,0 +1,23 @@
import typing
@typing.dataclass_transform(eq_default=True, order_default=True)
class ModelBase:
def __init_subclass__(
cls,
*,
init: bool = True,
frozen: bool = False,
eq: bool = True,
order: bool = True,
):
...
class CustomerModel(
ModelBase,
init=False,
frozen=True,
eq=False,
order=False,
):
id: int
name: str

View File

@@ -0,0 +1,8 @@
from typing import dataclass_transform
from mod1 import field1
import mod2
from mod3 import *
@dataclass_transform(eq_default=False, order_default=True, field_specifiers=(field1, mod2.field2, field3)
def func()
...