From 7e1295159d5c737dc7a922692fd6968a3d1414ae Mon Sep 17 00:00:00 2001 From: Joseph Perez Date: Wed, 26 May 2021 17:21:53 +0200 Subject: [PATCH 1/5] Revert "Extend the dataclass plugin to deal with callable properties (#10292)" This reverts commit 3996f433e5efec57cfa6176256733b553109cd1c. --- mypy/plugins/dataclasses.py | 25 +--------- test-data/unit/check-dataclasses.test | 69 --------------------------- 2 files changed, 1 insertion(+), 93 deletions(-) diff --git a/mypy/plugins/dataclasses.py b/mypy/plugins/dataclasses.py index 5d96ad90c4e7..5765e0599759 100644 --- a/mypy/plugins/dataclasses.py +++ b/mypy/plugins/dataclasses.py @@ -13,10 +13,7 @@ add_method, _get_decorator_bool_argument, deserialize_and_fixup_type, ) from mypy.typeops import map_type_from_supertype -from mypy.types import ( - Type, Instance, NoneType, TypeVarDef, TypeVarType, CallableType, - get_proper_type -) +from mypy.types import Type, Instance, NoneType, TypeVarDef, TypeVarType, get_proper_type from mypy.server.trigger import make_wildcard_trigger # The set of decorators that generate dataclasses. @@ -173,8 +170,6 @@ def transform(self) -> None: if decorator_arguments['frozen']: self._freeze(attributes) - else: - self._propertize_callables(attributes) self.reset_init_only_vars(info, attributes) @@ -358,24 +353,6 @@ 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: - """Converts all attributes with callable types to @property methods. - - This avoids the typechecker getting confused and thinking that - `my_dataclass_instance.callable_attr(foo)` is going to receive a - `self` argument (it is not). - - """ - info = self._ctx.cls.info - for attr in attributes: - if isinstance(get_proper_type(attr.type), CallableType): - var = attr.to_var() - var.info = info - var.is_property = True - var.is_settable_property = True - var._fullname = info.fullname + '.' + var.name - info.names[var.name] = SymbolTableNode(MDEF, var) - def dataclass_class_maker_callback(ctx: ClassDefContext) -> None: """Hooks into the class typechecking process to add support for dataclasses. diff --git a/test-data/unit/check-dataclasses.test b/test-data/unit/check-dataclasses.test index f3a9d607f0b0..f45af0100ec8 100644 --- a/test-data/unit/check-dataclasses.test +++ b/test-data/unit/check-dataclasses.test @@ -1108,72 +1108,3 @@ class B(A): reveal_type(B) # N: Revealed type is "def (foo: builtins.int) -> __main__.B" [builtins fixtures/property.pyi] - -[case testDataclassCallableProperty] -# flags: --python-version 3.7 -from dataclasses import dataclass -from typing import Callable - -@dataclass -class A: - foo: Callable[[int], int] - -def my_foo(x: int) -> int: - return x - -a = A(foo=my_foo) -a.foo(1) -reveal_type(a.foo) # N: Revealed type is "def (builtins.int) -> builtins.int" -reveal_type(A.foo) # N: Revealed type is "def (builtins.int) -> builtins.int" -[typing fixtures/typing-medium.pyi] -[case testDataclassCallableAssignment] -# flags: --python-version 3.7 -from dataclasses import dataclass -from typing import Callable - -@dataclass -class A: - foo: Callable[[int], int] - -def my_foo(x: int) -> int: - return x - -a = A(foo=my_foo) - -def another_foo(x: int) -> int: - return x + 1 - -a.foo = another_foo -[case testDataclassCallablePropertyWrongType] -# flags: --python-version 3.7 -from dataclasses import dataclass -from typing import Callable - -@dataclass -class A: - foo: Callable[[int], int] - -def my_foo(x: int) -> str: - return "foo" - -a = A(foo=my_foo) # E: Argument "foo" to "A" has incompatible type "Callable[[int], str]"; expected "Callable[[int], int]" -[typing fixtures/typing-medium.pyi] -[case testDataclassCallablePropertyWrongTypeAssignment] -# flags: --python-version 3.7 -from dataclasses import dataclass -from typing import Callable - -@dataclass -class A: - foo: Callable[[int], int] - -def my_foo(x: int) -> int: - return x - -a = A(foo=my_foo) - -def another_foo(x: int) -> str: - return "foo" - -a.foo = another_foo # E: Incompatible types in assignment (expression has type "Callable[[int], str]", variable has type "Callable[[int], int]") -[typing fixtures/typing-medium.pyi] From e450d14f8c537d54dc92d89a2b5acd4fe9882b9f Mon Sep 17 00:00:00 2001 From: Joseph Perez Date: Wed, 26 May 2021 17:51:55 +0200 Subject: [PATCH 2/5] Fix dataclasses plugin to support callable fields --- mypy/plugins/dataclasses.py | 8 ++++++++ test-data/unit/check-dataclasses.test | 17 +++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/mypy/plugins/dataclasses.py b/mypy/plugins/dataclasses.py index 5765e0599759..7de0ff0c2000 100644 --- a/mypy/plugins/dataclasses.py +++ b/mypy/plugins/dataclasses.py @@ -281,6 +281,14 @@ def collect_attributes(self) -> Optional[List[DataclassAttribute]]: info=cls.info, )) + # Make attribute non initialized in class, as if they were defined in + # __init__ method and not in class definition. It allows callable fields + # to be accessed without being considered as methods. + # (As a side-effect, fields' default value which are kept as class variables + # after dataclass processing are no more considered as initialized in class, + # but it's ok because it doesn't impact their resolution.) + cls.info[lhs.name].node.is_initialized_in_class = False + # Next, collect attributes belonging to any class in the MRO # as long as those attributes weren't already collected. This # makes it possible to overwrite attributes in subclasses. diff --git a/test-data/unit/check-dataclasses.test b/test-data/unit/check-dataclasses.test index f45af0100ec8..87451c2245ec 100644 --- a/test-data/unit/check-dataclasses.test +++ b/test-data/unit/check-dataclasses.test @@ -1108,3 +1108,20 @@ class B(A): reveal_type(B) # N: Revealed type is "def (foo: builtins.int) -> __main__.B" [builtins fixtures/property.pyi] + +[case testDataclassCallableField] +# flags: --python-version 3.7 +from dataclasses import dataclass +from typing import Callable + +@dataclass +class A: + x: Callable[[int], int] + y: Callable[[int], int] = lambda i: i + +a = A(lambda i:i) +x: int = a.x(0) +y: str = a.y(0) # E: Incompatible types in assignment (expression has type "int", variable has type "str") +reveal_type(a.x) # N: Revealed type is "def (builtins.int) -> builtins.int" +reveal_type(a.y) # N: Revealed type is "def (builtins.int) -> builtins.int" +reveal_type(A.y) # N: Revealed type is "def (builtins.int) -> builtins.int" From f1d592e42c2ae43fe49661fd5c831fc9cfae4044 Mon Sep 17 00:00:00 2001 From: Joseph Perez Date: Wed, 26 May 2021 18:29:55 +0200 Subject: [PATCH 3/5] Add assignment test --- test-data/unit/check-dataclasses.test | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/test-data/unit/check-dataclasses.test b/test-data/unit/check-dataclasses.test index 87451c2245ec..097222c91d8f 100644 --- a/test-data/unit/check-dataclasses.test +++ b/test-data/unit/check-dataclasses.test @@ -1109,7 +1109,7 @@ reveal_type(B) # N: Revealed type is "def (foo: builtins.int) -> __main__.B" [builtins fixtures/property.pyi] -[case testDataclassCallableField] +[case testDataclassCallableFieldAccess] # flags: --python-version 3.7 from dataclasses import dataclass from typing import Callable @@ -1125,3 +1125,21 @@ y: str = a.y(0) # E: Incompatible types in assignment (expression has type "int" reveal_type(a.x) # N: Revealed type is "def (builtins.int) -> builtins.int" reveal_type(a.y) # N: Revealed type is "def (builtins.int) -> builtins.int" reveal_type(A.y) # N: Revealed type is "def (builtins.int) -> builtins.int" + +[case testDataclassCallableFieldAssignment] +# flags: --python-version 3.7 +from dataclasses import dataclass +from typing import Callable + +@dataclass +class A: + x: Callable[[int], int] + +def x(i: int) -> int: + return i +def x2(s: str) -> str: + return s + +a = A(lambda i:i) +a.x = x +a.x = x2 # E: Incompatible types in assignment (expression has type "Callable[[str], str]", variable has type "Callable[[int], int]") From 9ef61e77cc662efba0e2e41b8b52eb94c684315f Mon Sep 17 00:00:00 2001 From: Joseph Perez Date: Wed, 26 May 2021 21:06:11 +0200 Subject: [PATCH 4/5] Fix linter error by using already retrieved node --- mypy/plugins/dataclasses.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/plugins/dataclasses.py b/mypy/plugins/dataclasses.py index 7de0ff0c2000..57cca885c5b1 100644 --- a/mypy/plugins/dataclasses.py +++ b/mypy/plugins/dataclasses.py @@ -287,7 +287,7 @@ def collect_attributes(self) -> Optional[List[DataclassAttribute]]: # (As a side-effect, fields' default value which are kept as class variables # after dataclass processing are no more considered as initialized in class, # but it's ok because it doesn't impact their resolution.) - cls.info[lhs.name].node.is_initialized_in_class = False + node.is_initialized_in_class = False # Next, collect attributes belonging to any class in the MRO # as long as those attributes weren't already collected. This From ebf2d354201128f65cd53d31f6467b817623efbe Mon Sep 17 00:00:00 2001 From: Joseph Perez Date: Wed, 26 May 2021 21:42:28 +0200 Subject: [PATCH 5/5] Fix dependency test --- test-data/unit/deps.test | 1 - 1 file changed, 1 deletion(-) diff --git a/test-data/unit/deps.test b/test-data/unit/deps.test index 8c074abc83a2..3d43b880f91f 100644 --- a/test-data/unit/deps.test +++ b/test-data/unit/deps.test @@ -1441,7 +1441,6 @@ class B(A): -> -> m, m.A, m.B -> m - -> m -> m.B -> m -> m