From 2abeb6ddd30b69e87332ac00313af6936015d757 Mon Sep 17 00:00:00 2001 From: Wesley Collin Wright Date: Mon, 13 Feb 2023 21:58:51 +0000 Subject: [PATCH 1/5] [dataclass_transform] detect parameter changes in incremental mode --- mypy/server/astdiff.py | 4 + .../fine-grained-dataclass-transform.test | 91 +++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 test-data/unit/fine-grained-dataclass-transform.test diff --git a/mypy/server/astdiff.py b/mypy/server/astdiff.py index 40b60f1a69d8..3a35be446c5a 100644 --- a/mypy/server/astdiff.py +++ b/mypy/server/astdiff.py @@ -73,6 +73,7 @@ class level -- these are handled at attribute level (say, 'mod.Cls.method' TypeVarTupleExpr, Var, ) +from mypy.semanal_shared import find_dataclass_transform_spec from mypy.types import ( AnyType, CallableType, @@ -230,6 +231,7 @@ def snapshot_definition(node: SymbolNode | None, common: SymbolSnapshot) -> Symb elif isinstance(node, OverloadedFuncDef) and node.impl: impl = node.impl.func if isinstance(node.impl, Decorator) else node.impl is_trivial_body = impl.is_trivial_body if impl else False + dataclass_transform_spec = find_dataclass_transform_spec(node) return ( "Func", common, @@ -239,6 +241,7 @@ def snapshot_definition(node: SymbolNode | None, common: SymbolSnapshot) -> Symb node.is_static, signature, is_trivial_body, + dataclass_transform_spec, ) elif isinstance(node, Var): return ("Var", common, snapshot_optional_type(node.type), node.is_final) @@ -280,6 +283,7 @@ def snapshot_definition(node: SymbolNode | None, common: SymbolSnapshot) -> Symb tuple(snapshot_type(tdef) for tdef in node.defn.type_vars), [snapshot_type(base) for base in node.bases], [snapshot_type(p) for p in node._promote], + node.dataclass_transform_spec or find_dataclass_transform_spec(node), ) prefix = node.fullname symbol_table = snapshot_symbol_table(prefix, node.names) diff --git a/test-data/unit/fine-grained-dataclass-transform.test b/test-data/unit/fine-grained-dataclass-transform.test new file mode 100644 index 000000000000..d4ed0ef645c9 --- /dev/null +++ b/test-data/unit/fine-grained-dataclass-transform.test @@ -0,0 +1,91 @@ +[case updateDataclassTransformParameterViaDecorator] +# flags: --python-version 3.11 +from m import my_dataclass + +@my_dataclass +class Foo: + x: int + +foo = Foo(1) +foo.x = 2 + +[file m.py] +from typing import dataclass_transform + +@dataclass_transform(frozen_default=False) +def my_dataclass(cls): return cls + +[file m.py.2] +from typing import dataclass_transform + +@dataclass_transform(frozen_default=True) +def my_dataclass(cls): return cls + +[typing fixtures/typing-full.pyi] +[builtins fixtures/dataclasses.pyi] + +[out] +== +main:9: error: Property "x" defined in "Foo" is read-only + +[case updateDataclassTransformParameterViaParentClass] +# flags: --python-version 3.11 +from m import Dataclass + +class Foo(Dataclass): + x: int + +foo = Foo(1) +foo.x = 2 + +[file m.py] +from typing import dataclass_transform + +@dataclass_transform(frozen_default=False) +class Dataclass: ... + +[file m.py.2] +from typing import dataclass_transform + +@dataclass_transform(frozen_default=True) +class Dataclass: ... + +[typing fixtures/typing-full.pyi] +[builtins fixtures/dataclasses.pyi] + +[out] +== +main:8: error: Property "x" defined in "Foo" is read-only + +[case updateBaseClassToUseDataclassTransform] +# flags: --python-version 3.11 +from m import A + +class B(A): + y: int + +B(x=1, y=2) + +[file m.py] +class A: + x: int + +[file m.py.2] +from typing import dataclass_transform + +@dataclass_transform() +class Dataclass: ... + +class A(Dataclass): + x: int + +[typing fixtures/typing-full.pyi] +[builtins fixtures/dataclasses.pyi] + +[out] +main:7: error: Unexpected keyword argument "x" for "B" +builtins.pyi:12: note: "B" defined here +main:7: error: Unexpected keyword argument "y" for "B" +builtins.pyi:12: note: "B" defined here +== + From 78a7092aefaa0957708dfa8048fdf871c49eb659 Mon Sep 17 00:00:00 2001 From: Wesley Collin Wright Date: Mon, 13 Feb 2023 22:30:01 +0000 Subject: [PATCH 2/5] always use tuple for deserializing field_specifiers --- mypy/nodes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mypy/nodes.py b/mypy/nodes.py index 94fc8f08f068..a87963895998 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -3909,7 +3909,7 @@ def serialize(self) -> JsonDict: "order_default": self.order_default, "kw_only_default": self.kw_only_default, "frozen_only_default": self.frozen_default, - "field_specifiers": self.field_specifiers, + "field_specifiers": list(self.field_specifiers), } @classmethod @@ -3919,7 +3919,7 @@ def deserialize(cls, data: JsonDict) -> DataclassTransformSpec: order_default=data.get("order_default"), kw_only_default=data.get("kw_only_default"), frozen_default=data.get("frozen_default"), - field_specifiers=data.get("field_specifiers"), + field_specifiers=tuple(data.get("field_specifiers", [])), ) From 432c0a4682b69ae212874469a454b278bbd9031b Mon Sep 17 00:00:00 2001 From: Wesley Collin Wright Date: Thu, 16 Feb 2023 16:04:24 +0000 Subject: [PATCH 3/5] use serialized form for snapshots --- mypy/server/astdiff.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/mypy/server/astdiff.py b/mypy/server/astdiff.py index 3a35be446c5a..c942a5eb3b0f 100644 --- a/mypy/server/astdiff.py +++ b/mypy/server/astdiff.py @@ -241,7 +241,7 @@ def snapshot_definition(node: SymbolNode | None, common: SymbolSnapshot) -> Symb node.is_static, signature, is_trivial_body, - dataclass_transform_spec, + dataclass_transform_spec.serialize() if dataclass_transform_spec is not None else None, ) elif isinstance(node, Var): return ("Var", common, snapshot_optional_type(node.type), node.is_final) @@ -259,6 +259,10 @@ def snapshot_definition(node: SymbolNode | None, common: SymbolSnapshot) -> Symb snapshot_definition(node.func, common), ) elif isinstance(node, TypeInfo): + dataclass_transform_spec = node.dataclass_transform_spec + if dataclass_transform_spec is None: + dataclass_transform_spec = find_dataclass_transform_spec(node) + attrs = ( node.is_abstract, node.is_enum, @@ -283,7 +287,7 @@ def snapshot_definition(node: SymbolNode | None, common: SymbolSnapshot) -> Symb tuple(snapshot_type(tdef) for tdef in node.defn.type_vars), [snapshot_type(base) for base in node.bases], [snapshot_type(p) for p in node._promote], - node.dataclass_transform_spec or find_dataclass_transform_spec(node), + dataclass_transform_spec.serialize() if dataclass_transform_spec is not None else None, ) prefix = node.fullname symbol_table = snapshot_symbol_table(prefix, node.names) From 5cb117bd8b2c5a023e0c87a683c938513c54a3e8 Mon Sep 17 00:00:00 2001 From: Wesley Collin Wright Date: Thu, 16 Feb 2023 16:07:19 +0000 Subject: [PATCH 4/5] fix updateBaseClassToUseDataclassTransform test --- test-data/unit/fine-grained-dataclass-transform.test | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test-data/unit/fine-grained-dataclass-transform.test b/test-data/unit/fine-grained-dataclass-transform.test index d4ed0ef645c9..8d09635b77bc 100644 --- a/test-data/unit/fine-grained-dataclass-transform.test +++ b/test-data/unit/fine-grained-dataclass-transform.test @@ -67,7 +67,9 @@ class B(A): B(x=1, y=2) [file m.py] -class A: +class Dataclass: ... + +class A(Dataclass): x: int [file m.py.2] From 33ac67f79ca6f3fd8776483d9304514f31ab6cbf Mon Sep 17 00:00:00 2001 From: Wesley Collin Wright Date: Thu, 16 Feb 2023 16:11:19 +0000 Subject: [PATCH 5/5] remove dangling newline --- test-data/unit/fine-grained-dataclass-transform.test | 1 - 1 file changed, 1 deletion(-) diff --git a/test-data/unit/fine-grained-dataclass-transform.test b/test-data/unit/fine-grained-dataclass-transform.test index 8d09635b77bc..7dc852f1d733 100644 --- a/test-data/unit/fine-grained-dataclass-transform.test +++ b/test-data/unit/fine-grained-dataclass-transform.test @@ -90,4 +90,3 @@ builtins.pyi:12: note: "B" defined here main:7: error: Unexpected keyword argument "y" for "B" builtins.pyi:12: note: "B" defined here == -