diff --git a/mypy/plugins/dataclasses.py b/mypy/plugins/dataclasses.py index 5d96ad90c4e7..57cca885c5b1 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) @@ -286,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.) + 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. @@ -358,24 +361,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..097222c91d8f 100644 --- a/test-data/unit/check-dataclasses.test +++ b/test-data/unit/check-dataclasses.test @@ -1109,71 +1109,37 @@ reveal_type(B) # N: Revealed type is "def (foo: builtins.int) -> __main__.B" [builtins fixtures/property.pyi] -[case testDataclassCallableProperty] +[case testDataclassCallableFieldAccess] # flags: --python-version 3.7 from dataclasses import dataclass from typing import Callable @dataclass class A: - foo: Callable[[int], int] + x: Callable[[int], int] + y: Callable[[int], int] = lambda i: i -def my_foo(x: int) -> int: - return x +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" -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] +[case testDataclassCallableFieldAssignment] # flags: --python-version 3.7 from dataclasses import dataclass from typing import Callable @dataclass class A: - foo: Callable[[int], int] + x: Callable[[int], int] -def my_foo(x: int) -> str: - return "foo" +def x(i: int) -> int: + return i +def x2(s: str) -> str: + return s -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] +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]") 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