Repository: python/mypy

Issue:
Dataclass inheritance: Missing override.
### Version 0.630

### Description
The problem seems to be that mypy derived classes do not override base classes. Looking at the dataclass plugin `collect_attributes` method, it is not clear that it does a reverse MRO lookup as mentioned in the dataclass spec (https://docs.python.org/3/library/dataclasses.html#inheritance). Let me know if you would like me to suggest a pull request.

### To reproduce
```from typing import Any, Dict

from dataclasses import dataclass


@dataclass
class Base:
    some_int: Any
    some_str: str = 'foo'


@dataclass
class C(Base):
    some_int: int
```

> error: Attributes without a default cannot follow attributes with one



Discussion:
I think mypy should match whatever happens at runtime here. Feel free to submit a PR.

Resolving patch:
diff --git a/mypy/plugins/dataclasses.py b/mypy/plugins/dataclasses.py
--- a/mypy/plugins/dataclasses.py
+++ b/mypy/plugins/dataclasses.py
@@ -234,12 +234,15 @@ def collect_attributes(self) -> List[DataclassAttribute]:
         # 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.
-        super_attrs = []
+        # copy() because we potentially modify all_attrs below and if this code requires debugging
+        # we'll have unmodified attrs laying around.
+        all_attrs = attrs.copy()
         init_method = cls.info.get_method('__init__')
         for info in cls.info.mro[1:-1]:
             if 'dataclass' not in info.metadata:
                 continue
 
+            super_attrs = []
             # Each class depends on the set of attributes in its dataclass ancestors.
             ctx.api.add_plugin_dependency(make_wildcard_trigger(info.fullname()))
 
@@ -258,8 +261,15 @@ def collect_attributes(self) -> List[DataclassAttribute]:
 
                     known_attrs.add(name)
                     super_attrs.append(attr)
-
-        all_attrs = super_attrs + attrs
+                else:
+                    # How early in the attribute list an attribute appears is determined by the
+                    # reverse MRO, not simply MRO.
+                    # See https://docs.python.org/3/library/dataclasses.html#inheritance for
+                    # details.
+                    (attr,) = [a for a in all_attrs if a.name == name]
+                    all_attrs.remove(attr)
+                    super_attrs.append(attr)
+            all_attrs = super_attrs + all_attrs
 
         # Ensure that arguments without a default don't follow
         # arguments that have a default.


Test patch:
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
@@ -78,9 +78,43 @@ class Person(Mammal):
     name: str
     age: int
 
-reveal_type(Person)  # E: Revealed type is 'def (name: builtins.str, age: builtins.int) -> __main__.Person'
-Person('John', 32)
-Person('John', 21, None)  # E: Too many arguments for "Person"
+@dataclass
+class SpecialPerson(Person):
+    special_factor: float
+
+@dataclass
+class ExtraSpecialPerson(SpecialPerson):
+    age: int
+    special_factor: float
+    name: str
+
+reveal_type(Person)  # E: Revealed type is 'def (age: builtins.int, name: builtins.str) -> __main__.Person'
+reveal_type(SpecialPerson)  # E: Revealed type is 'def (age: builtins.int, name: builtins.str, special_factor: builtins.float) -> __main__.SpecialPerson'
+reveal_type(ExtraSpecialPerson)  # E: Revealed type is 'def (age: builtins.int, name: builtins.str, special_factor: builtins.float) -> __main__.ExtraSpecialPerson'
+Person(32, 'John')
+Person(21, 'John', None)  # E: Too many arguments for "Person"
+SpecialPerson(21, 'John', 0.5)
+ExtraSpecialPerson(21, 'John', 0.5)
+
+[builtins fixtures/list.pyi]
+
+[case testDataclassesOverridingWithDefaults]
+# Issue #5681 https://github.com/python/mypy/issues/5681
+# flags: --python-version 3.6
+from dataclasses import dataclass
+from typing import Any
+
+@dataclass
+class Base:
+    some_int: Any
+    some_str: str = 'foo'
+
+
+@dataclass
+class C(Base):
+    some_int: int
+
+reveal_type(C)  # E: Revealed type is 'def (some_int: builtins.int, some_str: builtins.str =) -> __main__.C'
 
 [builtins fixtures/list.pyi]
 
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
@@ -4625,7 +4625,7 @@ main:2: error: Argument 2 to "B" has incompatible type "str"; expected "int"
 
 [case testIncrementalDataclassesThreeFiles]
 from c import C
-C(5, 'foo', True)
+C('foo', 5, True)
 
 [file a.py]
 from dataclasses import dataclass
@@ -4662,6 +4662,7 @@ class C(A, B):
 [out1]
 [out2]
 tmp/c.py:7: error: Incompatible types in assignment (expression has type "bool", base class "B" defined the type as "str")
+main:2: error: Argument 2 to "C" has incompatible type "int"; expected "bool"
 
 [case testIncrementalDataclassesThreeRuns]
 from a import A