Repository: python/mypy

Issue:
Invalid definition of __eq__ on dataclasses
[This code](https://github.com/python/mypy/blob/master/mypy/plugins/dataclasses.py#L127) does not work. It defines `__eq__` method like this:
```python
def __eq__(self: T, other: T) -> bool: pass
```
But on this example
```python
from dataclasses import dataclass

@dataclass
class A: pass

reveal_type(A.__eq__)
```
latest version of mypy outputs
```
test.py:6: note: Revealed type is 'def (builtins.object, builtins.object) -> builtins.bool'
```

And it is right, I think dataclasses shouldn't override `__eq__` signature of base `object` type and we should remove this broken code completely.



Discussion:

Resolving patch:
diff --git a/mypy/plugins/attrs.py b/mypy/plugins/attrs.py
--- a/mypy/plugins/attrs.py
+++ b/mypy/plugins/attrs.py
@@ -176,10 +176,10 @@ def deserialize(cls, info: TypeInfo, data: JsonDict) -> 'Attribute':
         )
 
 
-def _determine_eq_order(ctx: 'mypy.plugin.ClassDefContext') -> Tuple[bool, bool]:
+def _determine_eq_order(ctx: 'mypy.plugin.ClassDefContext') -> bool:
     """
     Validate the combination of *cmp*, *eq*, and *order*. Derive the effective
-    values of eq and order.
+    value of order.
     """
     cmp = _get_decorator_optional_bool_argument(ctx, 'cmp')
     eq = _get_decorator_optional_bool_argument(ctx, 'eq')
@@ -190,7 +190,7 @@ def _determine_eq_order(ctx: 'mypy.plugin.ClassDefContext') -> Tuple[bool, bool]
 
     # cmp takes precedence due to bw-compatibility.
     if cmp is not None:
-        return cmp, cmp
+        return cmp
 
     # If left None, equality is on and ordering mirrors equality.
     if eq is None:
@@ -202,7 +202,7 @@ def _determine_eq_order(ctx: 'mypy.plugin.ClassDefContext') -> Tuple[bool, bool]
     if eq is False and order is True:
         ctx.api.fail("eq must be True if order is True", ctx.reason)
 
-    return eq, order
+    return order
 
 
 def _get_decorator_optional_bool_argument(
@@ -248,7 +248,7 @@ def attr_class_maker_callback(ctx: 'mypy.plugin.ClassDefContext',
 
     init = _get_decorator_bool_argument(ctx, 'init', True)
     frozen = _get_frozen(ctx)
-    eq, order = _determine_eq_order(ctx)
+    order = _determine_eq_order(ctx)
 
     auto_attribs = _get_decorator_bool_argument(ctx, 'auto_attribs', auto_attribs_default)
     kw_only = _get_decorator_bool_argument(ctx, 'kw_only', False)
@@ -287,8 +287,6 @@ def attr_class_maker_callback(ctx: 'mypy.plugin.ClassDefContext',
     adder = MethodAdder(ctx)
     if init:
         _add_init(ctx, attributes, adder)
-    if eq:
-        _add_eq(ctx, adder)
     if order:
         _add_order(ctx, adder)
     if frozen:
@@ -587,17 +585,6 @@ def _parse_assignments(
     return lvalues, rvalues
 
 
-def _add_eq(ctx: 'mypy.plugin.ClassDefContext', adder: 'MethodAdder') -> None:
-    """Generate __eq__ and __ne__ for this class."""
-    # For __ne__ and __eq__ the type is:
-    #     def __ne__(self, other: object) -> bool
-    bool_type = ctx.api.named_type('__builtins__.bool')
-    object_type = ctx.api.named_type('__builtins__.object')
-    args = [Argument(Var('other', object_type), object_type, None, ARG_POS)]
-    for method in ['__ne__', '__eq__']:
-        adder.add_method(method, args, bool_type)
-
-
 def _add_order(ctx: 'mypy.plugin.ClassDefContext', adder: 'MethodAdder') -> None:
     """Generate all the ordering methods for this class."""
     bool_type = ctx.api.named_type('__builtins__.bool')
diff --git a/mypy/plugins/dataclasses.py b/mypy/plugins/dataclasses.py
--- a/mypy/plugins/dataclasses.py
+++ b/mypy/plugins/dataclasses.py
@@ -123,27 +123,6 @@ def transform(self) -> None:
                                          [], obj_type)
             info.names[SELF_TVAR_NAME] = SymbolTableNode(MDEF, self_tvar_expr)
 
-        # Add an eq method, but only if the class doesn't already have one.
-        if decorator_arguments['eq'] and info.get('__eq__') is None:
-            for method_name in ['__eq__', '__ne__']:
-                # The TVar is used to enforce that "other" must have
-                # the same type as self (covariant).  Note the
-                # "self_type" parameter to add_method.
-                obj_type = ctx.api.named_type('__builtins__.object')
-                cmp_tvar_def = TypeVarDef(SELF_TVAR_NAME, info.fullname + '.' + SELF_TVAR_NAME,
-                                          -1, [], obj_type)
-                cmp_other_type = TypeVarType(cmp_tvar_def)
-                cmp_return_type = ctx.api.named_type('__builtins__.bool')
-
-                add_method(
-                    ctx,
-                    method_name,
-                    args=[Argument(Var('other', cmp_other_type), cmp_other_type, None, ARG_POS)],
-                    return_type=cmp_return_type,
-                    self_type=cmp_other_type,
-                    tvar_def=cmp_tvar_def,
-                )
-
         # Add <, >, <=, >=, but only if the class has an eq method.
         if decorator_arguments['order']:
             if not decorator_arguments['eq']:


Test patch:
diff --git a/test-data/unit/check-attr.test b/test-data/unit/check-attr.test
--- a/test-data/unit/check-attr.test
+++ b/test-data/unit/check-attr.test
@@ -185,8 +185,6 @@ from attr import attrib, attrs
 class A:
     a: int
 reveal_type(A)  # N: Revealed type is 'def (a: builtins.int) -> __main__.A'
-reveal_type(A.__eq__)  # N: Revealed type is 'def (self: __main__.A, other: builtins.object) -> builtins.bool'
-reveal_type(A.__ne__)  # N: Revealed type is 'def (self: __main__.A, other: builtins.object) -> builtins.bool'
 reveal_type(A.__lt__)  # N: Revealed type is 'def [_AT] (self: _AT`-1, other: _AT`-1) -> builtins.bool'
 reveal_type(A.__le__)  # N: Revealed type is 'def [_AT] (self: _AT`-1, other: _AT`-1) -> builtins.bool'
 reveal_type(A.__gt__)  # N: Revealed type is 'def [_AT] (self: _AT`-1, other: _AT`-1) -> builtins.bool'
@@ -251,8 +249,6 @@ from attr import attrib, attrs
 class A:
     a: int
 reveal_type(A)  # N: Revealed type is 'def (a: builtins.int) -> __main__.A'
-reveal_type(A.__eq__)  # N: Revealed type is 'def (self: __main__.A, other: builtins.object) -> builtins.bool'
-reveal_type(A.__ne__)  # N: Revealed type is 'def (self: __main__.A, other: builtins.object) -> builtins.bool'
 
 A(1) < A(2)  # E: Unsupported left operand type for < ("A")
 A(1) <= A(2)  # E: Unsupported left operand type for <= ("A")
@@ -771,15 +767,6 @@ B() <= 1  # E: Unsupported operand types for <= ("B" and "int")
 C() <= 1  # E: Unsupported operand types for <= ("C" and "int")
 D() <= 1  # E: Unsupported operand types for <= ("D" and "int")
 
-A() == A()
-B() == A()
-C() == A()
-D() == A()
-
-A() == int
-B() == int
-C() == int
-D() == int
 [builtins fixtures/list.pyi]
 
 [case testAttrsComplexSuperclass]
diff --git a/test-data/unit/check-dataclasses.test b/test-data/unit/check-dataclasses.test
--- a/test-data/unit/check-dataclasses.test
+++ b/test-data/unit/check-dataclasses.test
@@ -377,48 +377,6 @@ Application.COUNTER = 1
 
 [builtins fixtures/list.pyi]
 
-[case testDataclassEquality]
-# flags: --python-version 3.6
-from dataclasses import dataclass
-
-@dataclass
-class Application:
-  name: str
-  rating: int
-
-app1 = Application("example-1", 5)
-app2 = Application("example-2", 5)
-app1 == app2
-app1 != app2
-app1 == None  # E: Unsupported operand types for == ("Application" and "None")
-
-[builtins fixtures/list.pyi]
-
-[case testDataclassCustomEquality]
-# flags: --python-version 3.6
-from dataclasses import dataclass
-
-@dataclass
-class Application:
-  name: str
-  rating: int
-
-  def __eq__(self, other: 'Application') -> bool:
-     ...
-
-app1 = Application("example-1", 5)
-app2 = Application("example-2", 5)
-app1 == app2
-app1 != app2  # E: Unsupported left operand type for != ("Application")
-app1 == None  # E: Unsupported operand types for == ("Application" and "None")
-
-class SpecializedApplication(Application):
-  ...
-
-app1 == SpecializedApplication("example-3", 5)
-
-[builtins fixtures/list.pyi]
-
 [case testDataclassOrdering]
 # flags: --python-version 3.6
 from dataclasses import dataclass
diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test
--- a/test-data/unit/check-incremental.test
+++ b/test-data/unit/check-incremental.test
@@ -2993,8 +2993,6 @@ main:15: error: Unsupported left operand type for >= ("NoCmp")
 [case testAttrsIncrementalDunder]
 from a import A
 reveal_type(A)  # N: Revealed type is 'def (a: builtins.int) -> a.A'
-reveal_type(A.__eq__)  # N: Revealed type is 'def (self: a.A, other: builtins.object) -> builtins.bool'
-reveal_type(A.__ne__)  # N: Revealed type is 'def (self: a.A, other: builtins.object) -> builtins.bool'
 reveal_type(A.__lt__)  # N: Revealed type is 'def [_AT] (self: _AT`-1, other: _AT`-1) -> builtins.bool'
 reveal_type(A.__le__)  # N: Revealed type is 'def [_AT] (self: _AT`-1, other: _AT`-1) -> builtins.bool'
 reveal_type(A.__gt__)  # N: Revealed type is 'def [_AT] (self: _AT`-1, other: _AT`-1) -> builtins.bool'
@@ -3032,20 +3030,18 @@ class A:
 [stale]
 [out2]
 main:2: note: Revealed type is 'def (a: builtins.int) -> a.A'
-main:3: note: Revealed type is 'def (self: a.A, other: builtins.object) -> builtins.bool'
-main:4: note: Revealed type is 'def (self: a.A, other: builtins.object) -> builtins.bool'
+main:3: note: Revealed type is 'def [_AT] (self: _AT`-1, other: _AT`-1) -> builtins.bool'
+main:4: note: Revealed type is 'def [_AT] (self: _AT`-1, other: _AT`-1) -> builtins.bool'
 main:5: note: Revealed type is 'def [_AT] (self: _AT`-1, other: _AT`-1) -> builtins.bool'
 main:6: note: Revealed type is 'def [_AT] (self: _AT`-1, other: _AT`-1) -> builtins.bool'
-main:7: note: Revealed type is 'def [_AT] (self: _AT`-1, other: _AT`-1) -> builtins.bool'
-main:8: note: Revealed type is 'def [_AT] (self: _AT`-1, other: _AT`-1) -> builtins.bool'
-main:17: error: Unsupported operand types for < ("A" and "int")
-main:18: error: Unsupported operand types for <= ("A" and "int")
-main:19: error: Unsupported operand types for > ("A" and "int")
-main:20: error: Unsupported operand types for >= ("A" and "int")
-main:24: error: Unsupported operand types for > ("A" and "int")
-main:25: error: Unsupported operand types for >= ("A" and "int")
-main:26: error: Unsupported operand types for < ("A" and "int")
-main:27: error: Unsupported operand types for <= ("A" and "int")
+main:15: error: Unsupported operand types for < ("A" and "int")
+main:16: error: Unsupported operand types for <= ("A" and "int")
+main:17: error: Unsupported operand types for > ("A" and "int")
+main:18: error: Unsupported operand types for >= ("A" and "int")
+main:22: error: Unsupported operand types for > ("A" and "int")
+main:23: error: Unsupported operand types for >= ("A" and "int")
+main:24: error: Unsupported operand types for < ("A" and "int")
+main:25: error: Unsupported operand types for <= ("A" and "int")
 
 [case testAttrsIncrementalSubclassModified]
 from b import B
diff --git a/test-data/unit/deps.test b/test-data/unit/deps.test
--- a/test-data/unit/deps.test
+++ b/test-data/unit/deps.test
@@ -1435,9 +1435,7 @@ class B(A):
 [out]
 <m.A.(abstract)> -> <m.B.__init__>, m
 <m.A._DT> -> <m.B._DT>
-<m.A.__eq__> -> <m.B.__eq__>
 <m.A.__init__> -> <m.B.__init__>, m.B.__init__
-<m.A.__ne__> -> <m.B.__ne__>
 <m.A.__new__> -> <m.B.__new__>
 <m.A.x> -> <m.B.x>
 <m.A.y> -> <m.B.y>
diff --git a/test-data/unit/stubgen.test b/test-data/unit/stubgen.test
--- a/test-data/unit/stubgen.test
+++ b/test-data/unit/stubgen.test
@@ -2162,8 +2162,6 @@ from typing import Any
 class C:
     x: Any = ...
     def __init__(self, x: Any) -> None: ...
-    def __ne__(self, other: Any) -> Any: ...
-    def __eq__(self, other: Any) -> Any: ...
     def __lt__(self, other: Any) -> Any: ...
     def __le__(self, other: Any) -> Any: ...
     def __gt__(self, other: Any) -> Any: ...