mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-19 13:02:30 +07:00
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:
committed by
intellij-monorepo-bot
parent
a20dc6e783
commit
1da22d34fd
@@ -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>
|
||||
@@ -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__
|
||||
@@ -0,0 +1,6 @@
|
||||
from dt_base import DataclassBase
|
||||
|
||||
|
||||
class RecordViaBaseClass(DataclassBase, kw_only=True):
|
||||
id: int
|
||||
name: str
|
||||
@@ -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>
|
||||
@@ -0,0 +1,7 @@
|
||||
from dt_decorator import my_dt_decorator
|
||||
|
||||
|
||||
@my_dt_decorator(kw_only=True)
|
||||
class RecordViaDecorator:
|
||||
id: int
|
||||
name: str
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -0,0 +1,5 @@
|
||||
from dt_meta import DataclassMeta
|
||||
|
||||
|
||||
class DataclassBase(metaclass=DataclassMeta):
|
||||
pass
|
||||
@@ -0,0 +1,6 @@
|
||||
from dt_base import DataclassBase
|
||||
|
||||
|
||||
class RecordViaMetaClass(DataclassBase, kw_only=True):
|
||||
id: int
|
||||
name: str
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -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]]:
|
||||
...
|
||||
@@ -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)
|
||||
@@ -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]]:
|
||||
...
|
||||
@@ -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)
|
||||
@@ -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]]:
|
||||
...
|
||||
@@ -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)
|
||||
@@ -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]]:
|
||||
...
|
||||
@@ -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
|
||||
@@ -0,0 +1,7 @@
|
||||
import dataclasses
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class Base:
|
||||
base_field_no_default: int = dataclasses.MISSING
|
||||
base_field_default: int = 42
|
||||
@@ -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>)
|
||||
@@ -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]]:
|
||||
...
|
||||
@@ -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
|
||||
@@ -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]]:
|
||||
...
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
@@ -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]]:
|
||||
...
|
||||
@@ -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
|
||||
@@ -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]]:
|
||||
...
|
||||
@@ -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()
|
||||
@@ -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]]:
|
||||
...
|
||||
@@ -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
|
||||
@@ -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]]:
|
||||
...
|
||||
@@ -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
|
||||
@@ -0,0 +1,6 @@
|
||||
import dataclasses
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class Base:
|
||||
field_default: int = 42
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -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]]:
|
||||
...
|
||||
@@ -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
|
||||
@@ -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]]:
|
||||
...
|
||||
@@ -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>
|
||||
@@ -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]]:
|
||||
...
|
||||
@@ -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"
|
||||
@@ -0,0 +1,6 @@
|
||||
from decorator import my_dataclass, my_dataclass_frozen_default
|
||||
|
||||
|
||||
@my_dataclass_frozen_default()
|
||||
class BaseFrozenWithFrozenDefault:
|
||||
a: int = 1
|
||||
@@ -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]]:
|
||||
...
|
||||
@@ -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
|
||||
@@ -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]]:
|
||||
...
|
||||
@@ -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>)
|
||||
@@ -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>)
|
||||
@@ -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>)
|
||||
@@ -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>)
|
||||
@@ -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>)
|
||||
2
python/testData/stubs/DataclassFieldSpecifierStub.py
Normal file
2
python/testData/stubs/DataclassFieldSpecifierStub.py
Normal file
@@ -0,0 +1,2 @@
|
||||
class Customer:
|
||||
attr = field(default_factory=list, init=False, kw_only=False, alias="alias")
|
||||
@@ -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
|
||||
@@ -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
|
||||
8
python/testData/stubs/DataclassTransformDecoratorStub.py
Normal file
8
python/testData/stubs/DataclassTransformDecoratorStub.py
Normal 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()
|
||||
...
|
||||
Reference in New Issue
Block a user