From 69f6d8d58f1b6fcd3673b45dce9d51e6f72a87f5 Mon Sep 17 00:00:00 2001 From: Jordan Speicher Date: Sat, 19 Mar 2022 13:01:14 -0500 Subject: [PATCH 1/3] propertize Callable attributes before freezing dataclasses --- mypy/plugins/dataclasses.py | 4 ++-- test-data/unit/check-dataclasses.test | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/mypy/plugins/dataclasses.py b/mypy/plugins/dataclasses.py index 091c627f5c1b..240d51ae178b 100644 --- a/mypy/plugins/dataclasses.py +++ b/mypy/plugins/dataclasses.py @@ -209,10 +209,10 @@ def transform(self) -> None: tvar_def=order_tvar_def, ) + self._propertize_callables(attributes) + if decorator_arguments['frozen']: self._freeze(attributes) - else: - self._propertize_callables(attributes) if decorator_arguments['slots']: self.add_slots(info, attributes, correct_version=py_version >= (3, 10)) diff --git a/test-data/unit/check-dataclasses.test b/test-data/unit/check-dataclasses.test index eed329bb59c7..cc8efb5b4657 100644 --- a/test-data/unit/check-dataclasses.test +++ b/test-data/unit/check-dataclasses.test @@ -1536,3 +1536,18 @@ A(a=1, b=2) A(1) A(a="foo") # E: Argument "a" to "A" has incompatible type "str"; expected "int" [builtins fixtures/dataclasses.pyi] + +[case testDataclassesCallableFrozen] +# flags: --python-version 3.7 +from dataclasses import dataclass +from typing import Any, Callable +@dataclass(frozen=True) +class A: + a: Callable[..., None] + +def func() -> None: + pass + +reveal_type(A.a) # N: Revealed type is "def (*Any, **Any)" +A(a=func).a() +[builtins fixtures/dataclasses.pyi] From 1b01d41dd819e8780ddc111938fb49d9e473d911 Mon Sep 17 00:00:00 2001 From: Jordan Speicher Date: Mon, 21 Mar 2022 12:04:03 -0500 Subject: [PATCH 2/3] dataclasses: make propertized callables not settable when frozen --- mypy/plugins/dataclasses.py | 9 +++++---- test-data/unit/check-dataclasses.test | 1 + 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/mypy/plugins/dataclasses.py b/mypy/plugins/dataclasses.py index 240d51ae178b..f71e617521fd 100644 --- a/mypy/plugins/dataclasses.py +++ b/mypy/plugins/dataclasses.py @@ -209,10 +209,11 @@ def transform(self) -> None: tvar_def=order_tvar_def, ) - self._propertize_callables(attributes) - if decorator_arguments['frozen']: + self._propertize_callables(attributes, settable=False) self._freeze(attributes) + else: + self._propertize_callables(attributes) if decorator_arguments['slots']: self.add_slots(info, attributes, correct_version=py_version >= (3, 10)) @@ -466,7 +467,7 @@ def _freeze(self, attributes: List[DataclassAttribute]) -> None: var._fullname = info.fullname + '.' + var.name info.names[var.name] = SymbolTableNode(MDEF, var) - def _propertize_callables(self, attributes: List[DataclassAttribute]) -> None: + def _propertize_callables(self, attributes: List[DataclassAttribute], settable: bool = True) -> None: """Converts all attributes with callable types to @property methods. This avoids the typechecker getting confused and thinking that @@ -480,7 +481,7 @@ def _propertize_callables(self, attributes: List[DataclassAttribute]) -> None: var = attr.to_var() var.info = info var.is_property = True - var.is_settable_property = True + var.is_settable_property = settable var._fullname = info.fullname + '.' + var.name info.names[var.name] = SymbolTableNode(MDEF, var) diff --git a/test-data/unit/check-dataclasses.test b/test-data/unit/check-dataclasses.test index cc8efb5b4657..935e03694611 100644 --- a/test-data/unit/check-dataclasses.test +++ b/test-data/unit/check-dataclasses.test @@ -1550,4 +1550,5 @@ def func() -> None: reveal_type(A.a) # N: Revealed type is "def (*Any, **Any)" A(a=func).a() +A(a=func).a = func # E: Property "a" defined in "A" is read-only [builtins fixtures/dataclasses.pyi] From d6d8844a799a7e1c3eebca0e8174abb3e25959d7 Mon Sep 17 00:00:00 2001 From: Jordan Speicher Date: Mon, 21 Mar 2022 12:09:20 -0500 Subject: [PATCH 3/3] fix lint error --- mypy/plugins/dataclasses.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mypy/plugins/dataclasses.py b/mypy/plugins/dataclasses.py index f71e617521fd..500fd71677fc 100644 --- a/mypy/plugins/dataclasses.py +++ b/mypy/plugins/dataclasses.py @@ -467,7 +467,9 @@ def _freeze(self, attributes: List[DataclassAttribute]) -> None: var._fullname = info.fullname + '.' + var.name info.names[var.name] = SymbolTableNode(MDEF, var) - def _propertize_callables(self, attributes: List[DataclassAttribute], settable: bool = True) -> None: + def _propertize_callables(self, + attributes: List[DataclassAttribute], + settable: bool = True) -> None: """Converts all attributes with callable types to @property methods. This avoids the typechecker getting confused and thinking that