Repository: python/mypy
Pull request: Add __dataclass_fields__ and __attrs_attrs__ to dataclasses
Fixes #6568.

Problem statement:
Recognizing wrapped classes as supporting a Protocol
```python
from dataclasses import dataclass
from typing import Dict

from typing_extensions import Protocol

@dataclass
class A:
    pass

class IsDataclass(Protocol):
    __dataclass_fields__: Dict

def dataclass_only(x: IsDataclass):
    pass

dataclass_only(A())
```
When running mypy on this code, I'd expect no warning, but I get 

> test.py:16: error: Argument 1 to "dataclass_only" has incompatible type "A"; expected "IsDataclass". 

While checking for `__dataclass_fields__` might not look like the best thing to go for, it's what `dataclasses.is_dataclass` checks for as well, so this seems like the most reliable condition right now.

I'd assume it happens because the dataclass-wrapper adds this attribute, and mypy only checks the class definition itself. Can this be fixed?

Hints from issue discussion:
This can be supported by just adding `__dataclass_fields__` `Var` to the symbol table in the plugin. This is quite exotic use case, so setting priority to low. On the other hand, if you have time, PRs are welcome, this shouldn't be hard to fix.
I'm not 100% sure what you mean with adding the symbol to the plugin table, do you mean this?

``` python
@dataclass
class A:
    __dataclass_fields__: Dict
```
Because that does fix it without side effects, since the dataclass wrapper just overrides it. Then again, once I start adding fields to my dataclasses only to fulfill a protocol I might as well inherit from a classic no-op interface class.

Low prio is what I think as well, I'll look if I can manage to write a proper fix.
> I'm not 100% sure what you mean with adding the symbol to the plugin table, do you mean this?

You misread what I wrote, I meant fixing this in mypy plugin for dataclasses (dataclasses in mypy are supported by a bundled/builtin plugin).
I see, thanks for the info =)
So we are talking about `mypy/plugins/dataclasses.py` here, right @ilevkivskyi ?

I would like to have that fixed because I see no other reliable way of type hinting data classes.
Have never touched mypy source code, but I can give it a try.
> So we are talking about `mypy/plugins/dataclasses.py` here

Yes.
> Have never touched mypy source code, but I can give it a try.

Please go ahead! If you feel stuck, feel free to ask here or on Gitter.
I will certainly have some questions, thanks :)

For now, in order to leave it commented here, I managed to track the type checking to the `subtypes` submodule. Today, I am simply trying to get the gist of what is mypy's flow.

The function call that isn't returning what we expect in order to validate the protocol is at `subtypes.py(481)`:
```python
subtype = find_member(member, left, left)
```
Where member is the attribute we are checking (i.e.,`__dataclass_fields__`), and left is `A` class.

`find_member` consists of:
```python
	    def get_method(self, name: str) -> Optional[FuncBase]:
	        for cls in self.mro:
	            if name in cls.names:
	                node = cls.names[name].node
	                if isinstance(node, FuncBase):
	                    return node
	                else:
	                    return None
```
Where:
```
self = <TypeInfo foo.A>
self.mro = [<TypeInfo foo.A>, <TypeInfo builtins.object>]
name = '__dataclass_fields__'
```

Given `self.names` is an empty `SymbolTable`, `name` is never in `cls.names`. Tweaking with dataclasses' symbol table should make `__dataclass_fields__` visible here, then making `A` compliant with the protocol `IsDataclass`.

Thoughts here so far?
> Tweaking with dataclasses' symbol table should make `__dataclass_fields__` visible here, then making `A` compliant with the protocol `IsDataclass`.

Yes, we already add `__init__` and other things to the symbol table in `plugins/dataclasses.py`, just add a `Var` there with a type like `Dict[str, Any]`.
It seems I was following the wrong thread there 😅 Ended up at the `semanal.analyze_class` class, which analyzes the definitions (`defn.defs.body`). Still, that was very instructive :)

Gonna go for the plugin now, thanks
How does `Var` relate to `TypeInfo`? Creating a `Var` via `Var(random_name, Dict[Any, Any]` initializes it with `VAR_NO_INFO`, for example.
I see it is assigned at some places, like `semanal.make_name_lvalue_var`

Commit message: Remove commented code from stub

Patch to review:
diff --git a/mypy/plugins/attrs.py b/mypy/plugins/attrs.py
index 540905839992..a91ab8e4cc08 100644
--- a/mypy/plugins/attrs.py
+++ b/mypy/plugins/attrs.py
@@ -278,6 +278,8 @@ def attr_class_maker_callback(ctx: 'mypy.plugin.ClassDefContext',
             ctx.api.defer()
             return
 
+    _add_attrs_magic_attribute(ctx)
+
     # Save the attributes so that subclasses can reuse them.
     ctx.cls.info.metadata['attrs'] = {
         'attributes': [attr.serialize() for attr in attributes],
@@ -656,6 +658,23 @@ def _add_init(ctx: 'mypy.plugin.ClassDefContext', attributes: List[Attribute],
     adder.add_method('__init__', args, NoneType())
 
 
+def _add_attrs_magic_attribute(ctx: 'mypy.plugin.ClassDefContext') -> None:
+    attr_name = '__attrs_attrs__'
+    any_type = AnyType(TypeOfAny.explicit)
+    attribute_type = ctx.api.named_type_or_none('attr.Attribute', [any_type])
+    attribute_type = attribute_type or any_type
+    var = Var(name=attr_name, type=ctx.api.named_type('__builtins__.tuple', [
+        attribute_type,
+    ]))
+    var.info = ctx.cls.info
+    var._fullname = ctx.cls.info.fullname + '.' + attr_name
+    ctx.cls.info.names[attr_name] = SymbolTableNode(
+        kind=MDEF,
+        node=var,
+        plugin_generated=True,
+    )
+
+
 class MethodAdder:
     """Helper to add methods to a TypeInfo.
 
diff --git a/mypy/plugins/dataclasses.py b/mypy/plugins/dataclasses.py
index 81f50d1a4c4d..ecbe8c4236f1 100644
--- a/mypy/plugins/dataclasses.py
+++ b/mypy/plugins/dataclasses.py
@@ -12,7 +12,10 @@
 from mypy.plugins.common import (
     add_method, _get_decorator_bool_argument, deserialize_and_fixup_type,
 )
-from mypy.types import Type, Instance, NoneType, TypeVarDef, TypeVarType, get_proper_type
+from mypy.types import (
+    Type, Instance, NoneType, TypeVarDef, TypeVarType, get_proper_type,
+    AnyType, TypeOfAny,
+)
 from mypy.server.trigger import make_wildcard_trigger
 
 # The set of decorators that generate dataclasses.
@@ -183,6 +186,8 @@ def transform(self) -> None:
 
         self.reset_init_only_vars(info, attributes)
 
+        self._add_dataclass_fields_magic_attribute()
+
         info.metadata['dataclass'] = {
             'attributes': [attr.serialize() for attr in attributes],
             'frozen': decorator_arguments['frozen'],
@@ -361,6 +366,24 @@ def _freeze(self, attributes: List[DataclassAttribute]) -> None:
                 var._fullname = info.fullname + '.' + var.name
                 info.names[var.name] = SymbolTableNode(MDEF, var)
 
+    def _add_dataclass_fields_magic_attribute(self) -> None:
+        attr_name = '__dataclass_fields__'
+        any_type = AnyType(TypeOfAny.explicit)
+        field_type = self._ctx.api.named_type_or_none('dataclasses.Field', [any_type])
+        field_type = field_type or any_type
+        attr_type = self._ctx.api.named_type('__builtins__.dict', [
+            self._ctx.api.named_type('__builtins__.str'),
+            field_type,
+        ])
+        var = Var(name=attr_name, type=attr_type)
+        var.info = self._ctx.cls.info
+        var._fullname = self._ctx.cls.info.fullname + '.' + attr_name
+        self._ctx.cls.info.names[attr_name] = SymbolTableNode(
+            kind=MDEF,
+            node=var,
+            plugin_generated=True,
+        )
+
 
 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-attr.test b/test-data/unit/check-attr.test
index fd6fc2b3755e..a1591c45928c 100644
--- a/test-data/unit/check-attr.test
+++ b/test-data/unit/check-attr.test
@@ -1259,3 +1259,14 @@ class B(A):
 
 reveal_type(B)  # N: Revealed type is 'def (foo: builtins.int) -> __main__.B'
 [builtins fixtures/bool.pyi]
+
+[case testAttrsClassHasAttributeWithAttributes]
+import attr
+
+@attr.s
+class A:
+    pass
+
+reveal_type(A.__attrs_attrs__)  # N: Revealed type is 'builtins.tuple[attr.Attribute[Any]]'
+
+[builtins fixtures/attr.pyi]
diff --git a/test-data/unit/check-dataclasses.test b/test-data/unit/check-dataclasses.test
index 97bb9954ec29..ce38becbfd26 100644
--- a/test-data/unit/check-dataclasses.test
+++ b/test-data/unit/check-dataclasses.test
@@ -14,7 +14,7 @@ reveal_type(Person)  # N: Revealed type is 'def (name: builtins.str, age: builti
 Person('John', 32)
 Person('Jonh', 21, None)  # E: Too many arguments for "Person"
 
-[builtins fixtures/list.pyi]
+[builtins fixtures/dataclasses.pyi]
 [typing fixtures/typing-medium.pyi]
 
 [case testDataclassesCustomInit]
@@ -30,7 +30,7 @@ class A:
 
 A('1')
 
-[builtins fixtures/list.pyi]
+[builtins fixtures/dataclasses.pyi]
 
 [case testDataclassesBasicInheritance]
 # flags: --python-version 3.6
@@ -52,7 +52,7 @@ Mammal(10)
 Person(32, 'John')
 Person(21, 'Jonh', None)  # E: Too many arguments for "Person"
 
-[builtins fixtures/list.pyi]
+[builtins fixtures/dataclasses.pyi]
 [typing fixtures/typing-medium.pyi]
 
 [case testDataclassesDeepInheritance]
@@ -80,7 +80,7 @@ reveal_type(B)  # N: Revealed type is 'def (a: builtins.int, b: builtins.int) ->
 reveal_type(C)  # N: Revealed type is 'def (a: builtins.int, b: builtins.int, c: builtins.int) -> __main__.C'
 reveal_type(D)  # N: Revealed type is 'def (a: builtins.int, b: builtins.int, c: builtins.int, d: builtins.int) -> __main__.D'
 
-[builtins fixtures/list.pyi]
+[builtins fixtures/dataclasses.pyi]
 
 [case testDataclassesMultipleInheritance]
 from dataclasses import dataclass, field, InitVar
@@ -102,7 +102,7 @@ class C(A, B):
 
 reveal_type(C)  # N: Revealed type is 'def (b: builtins.bool, a: builtins.bool) -> __main__.C'
 
-[builtins fixtures/bool.pyi]
+[builtins fixtures/dataclasses.pyi]
 
 [case testDataclassesDeepInitVarInheritance]
 from dataclasses import dataclass, field, InitVar
@@ -130,7 +130,7 @@ class D(C):
 reveal_type(C)  # N: Revealed type is 'def () -> __main__.C'
 reveal_type(D)  # N: Revealed type is 'def (b: builtins.bool) -> __main__.D'
 
-[builtins fixtures/bool.pyi]
+[builtins fixtures/dataclasses.pyi]
 
 [case testDataclassesOverriding]
 # flags: --python-version 3.6
@@ -163,7 +163,7 @@ Person(21, 'John', None)  # E: Too many arguments for "Person"
 SpecialPerson(21, 'John', 0.5)
 ExtraSpecialPerson(21, 'John', 0.5)
 
-[builtins fixtures/list.pyi]
+[builtins fixtures/dataclasses.pyi]
 
 [case testDataclassesOverridingWithDefaults]
 # Issue #5681 https://github.com/python/mypy/issues/5681
@@ -183,7 +183,7 @@ class C(Base):
 
 reveal_type(C)  # N: Revealed type is 'def (some_int: builtins.int, some_str: builtins.str =) -> __main__.C'
 
-[builtins fixtures/list.pyi]
+[builtins fixtures/dataclasses.pyi]
 
 [case testDataclassesFreezing]
 # flags: --python-version 3.6
@@ -196,7 +196,7 @@ class Person:
 john = Person('John')
 john.name = 'Ben'  # E: Property "name" defined in "Person" is read-only
 
-[builtins fixtures/list.pyi]
+[builtins fixtures/dataclasses.pyi]
 
 [case testDataclassesFields]
 # flags: --python-version 3.6
@@ -212,7 +212,7 @@ john = Person('John')
 john.age = 'invalid'  # E: Incompatible types in assignment (expression has type "str", variable has type "int")
 john.age = 24
 
-[builtins fixtures/list.pyi]
+[builtins fixtures/dataclasses.pyi]
 
 [case testDataclassesBadInit]
 # flags: --python-version 3.6
@@ -226,7 +226,7 @@ class Person:
                                  # N:     def field(*, init: bool = ..., repr: bool = ..., hash: Optional[bool] = ..., compare: bool = ..., metadata: Optional[Mapping[str, Any]] = ...) -> Any \
                                  # N:     <2 more non-matching overloads not shown>
 
-[builtins fixtures/list.pyi]
+[builtins fixtures/dataclasses.pyi]
 
 [case testDataclassesMultiInit]
 # flags: --python-version 3.6
@@ -242,7 +242,7 @@ class Person:
 
 reveal_type(Person)  # N: Revealed type is 'def (name: builtins.str, friend_names: builtins.list[builtins.str], enemy_names: builtins.list[builtins.str]) -> __main__.Person'
 
-[builtins fixtures/list.pyi]
+[builtins fixtures/dataclasses.pyi]
 
 [case testDataclassesMultiInitDefaults]
 # flags: --python-version 3.6
@@ -259,7 +259,7 @@ class Person:
 
 reveal_type(Person)  # N: Revealed type is 'def (name: builtins.str, friend_names: builtins.list[builtins.str], enemy_names: builtins.list[builtins.str], nickname: Union[builtins.str, None] =) -> __main__.Person'
 
-[builtins fixtures/list.pyi]
+[builtins fixtures/dataclasses.pyi]
 
 [case testDataclassesDefaults]
 # flags: --python-version 3.6
@@ -273,7 +273,7 @@ class Application:
 reveal_type(Application)  # N: Revealed type is 'def (name: builtins.str =, rating: builtins.int =) -> __main__.Application'
 app = Application()
 
-[builtins fixtures/list.pyi]
+[builtins fixtures/dataclasses.pyi]
 
 [case testDataclassesDefaultFactories]
 # flags: --python-version 3.6
@@ -285,7 +285,7 @@ class Application:
     rating: int = field(default_factory=int)
     rating_count: int = field()  # E: Attributes without a default cannot follow attributes with one
 
-[builtins fixtures/list.pyi]
+[builtins fixtures/dataclasses.pyi]
 
 [case testDataclassesDefaultFactoryTypeChecking]
 # flags: --python-version 3.6
@@ -296,7 +296,7 @@ class Application:
     name: str = 'Unnamed'
     rating: int = field(default_factory=str)  # E: Incompatible types in assignment (expression has type "str", variable has type "int")
 
-[builtins fixtures/list.pyi]
+[builtins fixtures/dataclasses.pyi]
 
 [case testDataclassesDefaultOrdering]
 # flags: --python-version 3.6
@@ -307,7 +307,7 @@ class Application:
     name: str = 'Unnamed'
     rating: int  # E: Attributes without a default cannot follow attributes with one
 
-[builtins fixtures/list.pyi]
+[builtins fixtures/dataclasses.pyi]
 
 [case testDataclassesClassmethods]
 # flags: --python-version 3.6
@@ -323,8 +323,7 @@ class Application:
 
 app = Application.parse('')
 
-[builtins fixtures/list.pyi]
-[builtins fixtures/classmethod.pyi]
+[builtins fixtures/dataclasses.pyi]
 
 [case testDataclassesOverloadsAndClassmethods]
 # flags: --python-version 3.6
@@ -357,7 +356,7 @@ class A:
 reveal_type(A.foo(3))      # N: Revealed type is 'builtins.int'
 reveal_type(A.foo("foo"))  # N: Revealed type is 'builtins.str'
 
-[builtins fixtures/classmethod.pyi]
+[builtins fixtures/dataclasses.pyi]
 
 [case testDataclassesClassVars]
 # flags: --python-version 3.6
@@ -375,7 +374,7 @@ application = Application("example")
 application.COUNTER = 1  # E: Cannot assign to class variable "COUNTER" via instance
 Application.COUNTER = 1
 
-[builtins fixtures/list.pyi]
+[builtins fixtures/dataclasses.pyi]
 
 [case testDataclassEquality]
 # flags: --python-version 3.6
@@ -390,9 +389,10 @@ app1 = Application("example-1", 5)
 app2 = Application("example-2", 5)
 app1 == app2
 app1 != app2
-app1 == None  # E: Unsupported operand types for == ("Application" and "None")
+app1 == None
+reveal_type(app1.__eq__)  # N: Revealed type is 'def (builtins.object) -> builtins.bool'
 
-[builtins fixtures/list.pyi]
+[builtins fixtures/dataclasses.pyi]
 
 [case testDataclassCustomEquality]
 # flags: --python-version 3.6
@@ -409,15 +409,24 @@ class Application:
 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")
+app1 != app2
+app1 == None
 
 class SpecializedApplication(Application):
   ...
 
 app1 == SpecializedApplication("example-3", 5)
 
-[builtins fixtures/list.pyi]
+[builtins fixtures/dataclasses.pyi]
+
+[out1]
+main:9: error: Argument 1 of "__eq__" is incompatible with supertype "object"; supertype defines the argument type as "object"
+main:9: note: It is recommended for "__eq__" to work with arbitrary objects, for example:
+main:9: note:     def __eq__(self, other: object) -> bool:
+main:9: note:         if not isinstance(other, Application):
+main:9: note:             return NotImplemented
+main:9: note:         return <logic to compare two Application instances>
+main:16: error: Unsupported operand types for == ("Application" and "None")
 
 [case testDataclassOrdering]
 # flags: --python-version 3.6
@@ -448,7 +457,7 @@ app1 > app3
 app1 <= app3
 app1 >= app3
 
-[builtins fixtures/list.pyi]
+[builtins fixtures/dataclasses.pyi]
 
 [case testDataclassOrderingWithoutEquality]
 # flags: --python-version 3.6
@@ -458,7 +467,7 @@ from dataclasses import dataclass
 class Application:  # E: eq must be True if order is True
    ...
 
-[builtins fixtures/list.pyi]
+[builtins fixtures/dataclasses.pyi]
 
 [case testDataclassOrderingWithCustomMethods]
 # flags: --python-version 3.6
@@ -469,7 +478,7 @@ class Application:
   def __lt__(self, other: 'Application') -> bool: # E: You may not have a custom __lt__ method when order=True
     ...
 
-[builtins fixtures/list.pyi]
+[builtins fixtures/dataclasses.pyi]
 
 [case testDataclassDefaultsInheritance]
 # flags: --python-version 3.6
@@ -487,7 +496,7 @@ class SpecializedApplication(Application):
 
 reveal_type(SpecializedApplication)  # N: Revealed type is 'def (id: Union[builtins.int, None], name: builtins.str, rating: builtins.int =) -> __main__.SpecializedApplication'
 
-[builtins fixtures/list.pyi]
+[builtins fixtures/dataclasses.pyi]
 
 [case testDataclassGenerics]
 # flags: --python-version 3.6
@@ -520,7 +529,7 @@ reveal_type(a.y)  # N: Revealed type is 'builtins.int*'
 reveal_type(a.z)  # N: Revealed type is 'builtins.list[builtins.int*]'
 s: str = a.bar()  # E: Incompatible types in assignment (expression has type "int", variable has type "str")
 
-[builtins fixtures/list.pyi]
+[builtins fixtures/dataclasses.pyi]
 
 [case testDataclassGenericsClassmethod]
 # flags: --python-version 3.6
@@ -542,7 +551,7 @@ class A(Generic[T]):
   def other(cls, x: T) -> A[T]: ...
 
 reveal_type(A(0).other)  # N: Revealed type is 'def (x: builtins.int*) -> __main__.A[builtins.int*]'
-[builtins fixtures/classmethod.pyi]
+[builtins fixtures/dataclasses.pyi]
 
 [case testDataclassesForwardRefs]
 from dataclasses import dataclass
@@ -559,7 +568,7 @@ reveal_type(A)  # N: Revealed type is 'def (b: __main__.B) -> __main__.A'
 A(b=B(42))
 A(b=42)  # E: Argument "b" to "A" has incompatible type "int"; expected "B"
 
-[builtins fixtures/list.pyi]
+[builtins fixtures/dataclasses.pyi]
 
 
 [case testDataclassesInitVars]
@@ -588,7 +597,7 @@ app.name
 app.rating
 app.database_name  # E: "SpecializedApplication" has no attribute "database_name"
 
-[builtins fixtures/list.pyi]
+[builtins fixtures/dataclasses.pyi]
 
 [case testDataclassesInitVarsAndDefer]
 
@@ -608,7 +617,7 @@ app.name
 app.database_name  # E: "Application" has no attribute "database_name"
 
 class Yes: ...
-[builtins fixtures/list.pyi]
+[builtins fixtures/dataclasses.pyi]
 
 [case testDataclassesNoInitInitVarInheritance]
 from dataclasses import dataclass, field, InitVar
@@ -624,7 +633,7 @@ class Sub(Super):
 sub = Sub(5)
 sub.foo  # E: "Sub" has no attribute "foo"
 sub.bar
-[builtins fixtures/bool.pyi]
+[builtins fixtures/dataclasses.pyi]
 
 [case testDataclassFactory]
 from typing import Type, TypeVar
@@ -639,7 +648,7 @@ class A:
         reveal_type(cls)  # N: Revealed type is 'Type[T`-1]'
         reveal_type(cls())  # N: Revealed type is 'T`-1'
         return cls()
-[builtins fixtures/classmethod.pyi]
+[builtins fixtures/dataclasses.pyi]
 
 [case testDataclassesInitVarOverride]
 import dataclasses
@@ -661,7 +670,7 @@ class B(A):
         super().__init__(b+1)
         self._b = b
 
-[builtins fixtures/bool.pyi]
+[builtins fixtures/dataclasses.pyi]
 
 [case testDataclassesInitVarNoOverride]
 import dataclasses
@@ -686,7 +695,7 @@ class B(A):
 B(1, 2)
 B(1, 'a') # E: Argument 2 to "B" has incompatible type "str"; expected "int"
 
-[builtins fixtures/bool.pyi]
+[builtins fixtures/dataclasses.pyi]
 
 [case testDataclassesInitVarPostInitOverride]
 import dataclasses
@@ -777,7 +786,7 @@ from typing import Optional
 @dataclass
 class Foo:
     bar: Optional[int] = field(default=None)
-[builtins fixtures/list.pyi]
+[builtins fixtures/dataclasses.pyi]
 [out]
 
 [case testNoComplainFieldNoneStrict]
@@ -789,7 +798,7 @@ from typing import Optional
 @dataclass
 class Foo:
     bar: Optional[int] = field(default=None)
-[builtins fixtures/list.pyi]
+[builtins fixtures/dataclasses.pyi]
 [out]
 
 [case testDisallowUntypedWorksForward]
@@ -805,7 +814,7 @@ class C(List[C]):
     pass
 
 reveal_type(B)  # N: Revealed type is 'def (x: __main__.C) -> __main__.B'
-[builtins fixtures/list.pyi]
+[builtins fixtures/dataclasses.pyi]
 
 [case testDisallowUntypedWorksForwardBad]
 # flags: --disallow-untyped-defs
@@ -817,7 +826,7 @@ class B:
     y = undefined()  # E: Name 'undefined' is not defined
 
 reveal_type(B)  # N: Revealed type is 'def (x: Any) -> __main__.B'
-[builtins fixtures/list.pyi]
+[builtins fixtures/dataclasses.pyi]
 
 [case testMemberExprWorksAsField]
 import dataclasses
@@ -854,7 +863,7 @@ b = Application('', 0)
 a < b
 
 class Yes: ...
-[builtins fixtures/list.pyi]
+[builtins fixtures/dataclasses.pyi]
 
 [case testDataclassFieldDeferred]
 from dataclasses import field, dataclass
@@ -865,7 +874,7 @@ class C:
 
 def func() -> int: ...
 C('no')  # E: Argument 1 to "C" has incompatible type "str"; expected "int"
-[builtins fixtures/bool.pyi]
+[builtins fixtures/dataclasses.pyi]
 
 [case testDataclassFieldDeferredFrozen]
 from dataclasses import field, dataclass
@@ -877,7 +886,7 @@ class C:
 def func() -> int: ...
 c: C
 c.x = 1  # E: Property "x" defined in "C" is read-only
-[builtins fixtures/bool.pyi]
+[builtins fixtures/dataclasses.pyi]
 
 [case testTypeInDataclassDeferredStar]
 import lib
@@ -895,7 +904,7 @@ C()  # E: Too few arguments for "C"
 C('no')  # E: Argument 1 to "C" has incompatible type "str"; expected "int"
 [file other.py]
 import lib
-[builtins fixtures/bool.pyi]
+[builtins fixtures/dataclasses.pyi]
 
 [case testDeferredDataclassInitSignature]
 from dataclasses import dataclass
@@ -911,7 +920,7 @@ class C:
         return cls(x=None, y=None)
 
 class Deferred: pass
-[builtins fixtures/classmethod.pyi]
+[builtins fixtures/dataclasses.pyi]
 
 [case testDeferredDataclassInitSignatureSubclass]
 # flags: --strict-optional
@@ -927,7 +936,7 @@ class C(B):
     y: str
 
 a = C(None, 'abc')
-[builtins fixtures/bool.pyi]
+[builtins fixtures/dataclasses.pyi]
 
 [case testDataclassesDefaultsIncremental]
 # flags: --python-version 3.6
@@ -959,7 +968,7 @@ class Person:
     b: int
     a: str = 'test'
 
-[builtins fixtures/list.pyi]
+[builtins fixtures/dataclasses.pyi]
 
 [case testDataclassesDefaultsMroOtherFile]
 # flags: --python-version 3.6
@@ -988,7 +997,7 @@ class A1:
 class A2:
     b: str = 'test'
 
-[builtins fixtures/list.pyi]
+[builtins fixtures/dataclasses.pyi]
 
 [case testDataclassesInheritingDuplicateField]
 # see mypy issue #7792
@@ -1003,6 +1012,8 @@ class A:  # E: Name 'x' already defined (possibly by an import)
 class B(A):
     pass
 
+[builtins fixtures/dataclasses.pyi]
+
 [case testDataclassInheritanceNoAnnotation]
 from dataclasses import dataclass
 
@@ -1017,6 +1028,8 @@ class B(A):
 
 reveal_type(B)  # N: Revealed type is 'def (foo: builtins.int) -> __main__.B'
 
+[builtins fixtures/dataclasses.pyi]
+
 [case testDataclassInheritanceNoAnnotation2]
 from dataclasses import dataclass
 
@@ -1031,4 +1044,15 @@ class B(A):
 
 reveal_type(B)  # N: Revealed type is 'def (foo: builtins.int) -> __main__.B'
 
-[builtins fixtures/property.pyi]
+[builtins fixtures/dataclasses.pyi]
+
+[case testDataclassHasAttributeWithFields]
+from dataclasses import dataclass
+
+@dataclass
+class A:
+    pass
+
+reveal_type(A.__dataclass_fields__)  # N: Revealed type is 'builtins.dict[builtins.str, dataclasses.Field[Any]]'
+
+[builtins fixtures/dict.pyi]
diff --git a/test-data/unit/check-flags.test b/test-data/unit/check-flags.test
index a2c36c0ca0cb..08a55a81d603 100644
--- a/test-data/unit/check-flags.test
+++ b/test-data/unit/check-flags.test
@@ -1038,6 +1038,8 @@ import attr
 class Unannotated:
     foo = attr.ib()
 
+[builtins fixtures/attr.pyi]
+
 [case testDisallowIncompleteDefsAttrsWithAnnotations]
 # flags: --disallow-incomplete-defs
 import attr
@@ -1046,6 +1048,8 @@ import attr
 class Annotated:
     bar: int = attr.ib()
 
+[builtins fixtures/attr.pyi]
+
 [case testDisallowIncompleteDefsAttrsPartialAnnotations]
 # flags: --disallow-incomplete-defs
 import attr
@@ -1055,6 +1059,8 @@ class PartiallyAnnotated:  # E: Function is missing a type annotation for one or
     bar: int = attr.ib()
     baz = attr.ib()
 
+[builtins fixtures/attr.pyi]
+
 [case testAlwaysTrueAlwaysFalseFlags]
 # flags: --always-true=YOLO --always-true=YOLO1 --always-false=BLAH1 --always-false BLAH --ignore-missing-imports
 from somewhere import YOLO, BLAH
diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test
index 8a887685da05..2400b58c9fcf 100644
--- a/test-data/unit/check-incremental.test
+++ b/test-data/unit/check-incremental.test
@@ -3781,7 +3781,7 @@ class A:
     E = 7
     F: ClassVar[int] = 22
 
-[builtins fixtures/list.pyi]
+[builtins fixtures/dataclasses.pyi]
 [out1]
 [out2]
 
@@ -3813,7 +3813,7 @@ from dataclasses import dataclass
 class A:
     x: int
 
-[builtins fixtures/list.pyi]
+[builtins fixtures/dataclasses.pyi]
 [out1]
 [out2]
 tmp/b.py:8: note: Revealed type is 'def (x: builtins.int) -> b.B'
@@ -3857,7 +3857,7 @@ class NoInit:
 class NoCmp:
     x: int
 
-[builtins fixtures/list.pyi]
+[builtins fixtures/dataclasses.pyi]
 [out1]
 [out2]
 tmp/b.py:4: error: Property "x" defined in "Frozen" is read-only
@@ -3911,7 +3911,7 @@ from dataclasses import dataclass
 class A:
     a: int
 
-[builtins fixtures/attr.pyi]
+[builtins fixtures/dataclasses.pyi]
 [out1]
 [out2]
 tmp/b.py:3: note: Revealed type is 'def (a: builtins.int) -> a.A'
@@ -3957,7 +3957,7 @@ from dataclasses import dataclass
 class B(A):
     y: int
 
-[builtins fixtures/list.pyi]
+[builtins fixtures/dataclasses.pyi]
 [out1]
 [out2]
 main:2: error: Argument 2 to "B" has incompatible type "str"; expected "int"
@@ -3990,7 +3990,7 @@ from dataclasses import dataclass
 class B(A):
     y: str
 
-[builtins fixtures/list.pyi]
+[builtins fixtures/dataclasses.pyi]
 [out1]
 main:2: error: Argument 2 to "B" has incompatible type "str"; expected "int"
 
@@ -4032,7 +4032,7 @@ from dataclasses import dataclass
 class C(A, B):
     c: bool
 
-[builtins fixtures/list.pyi]
+[builtins fixtures/dataclasses.pyi]
 [out1]
 [out2]
 tmp/c.py:7: error: Incompatible types in assignment (expression has type "bool", base class "B" defined the type as "str")
@@ -4063,7 +4063,7 @@ from dataclasses import dataclass
 class A:
     a: int = 6
 
-[builtins fixtures/list.pyi]
+[builtins fixtures/dataclasses.pyi]
 [out1]
 [out2]
 main:2: error: Argument 1 to "A" has incompatible type "int"; expected "str"
diff --git a/test-data/unit/deps.test b/test-data/unit/deps.test
index 62ddeac07bc7..d833a05e201a 100644
--- a/test-data/unit/deps.test
+++ b/test-data/unit/deps.test
@@ -1430,14 +1430,12 @@ class A:
 @dataclass
 class B(A):
     y: int
-[builtins fixtures/list.pyi]
+[builtins fixtures/dataclasses.pyi]
 
 [out]
 <m.A.(abstract)> -> <m.B.__init__>, m
-<m.A._DT> -> <m.B._DT>
-<m.A.__eq__> -> <m.B.__eq__>
+<m.A.__dataclass_fields__> -> <m.B.__dataclass_fields__>
 <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/fine-grained.test b/test-data/unit/fine-grained.test
index 3ea6b31f379a..2a08f5724e6c 100644
--- a/test-data/unit/fine-grained.test
+++ b/test-data/unit/fine-grained.test
@@ -688,7 +688,7 @@ class A:
 ==
 b.py:8: error: Argument 1 to "B" has incompatible type "int"; expected "str"
 ==
-[builtins fixtures/list.pyi]
+[builtins fixtures/dataclasses.pyi]
 
 [case testDataclassUpdate2]
 # flags: --python-version 3.7
@@ -719,7 +719,7 @@ B(1, 2)
 [out]
 ==
 b.py:8: error: Argument 1 to "B" has incompatible type "int"; expected "str"
-[builtins fixtures/list.pyi]
+[builtins fixtures/dataclasses.pyi]
 
 [case testDataclassUpdate3]
 # flags: --python-version 3.7
@@ -743,7 +743,7 @@ from dataclasses import dataclass
 class A:
     a: int
     other: int
-[builtins fixtures/list.pyi]
+[builtins fixtures/dataclasses.pyi]
 [out]
 ==
 main:3: error: Too few arguments for "B"
@@ -770,7 +770,7 @@ from dataclasses import dataclass
 class A:
     a: int
     other: int
-[builtins fixtures/list.pyi]
+[builtins fixtures/dataclasses.pyi]
 [out]
 ==
 main:3: error: Too few arguments for "B"
@@ -804,7 +804,7 @@ from dataclasses import dataclass
 class A:
     a: int
 
-[builtins fixtures/list.pyi]
+[builtins fixtures/dataclasses.pyi]
 [out]
 ==
 main:3: error: Too few arguments for "B"
@@ -831,7 +831,7 @@ from dataclasses import dataclass
 @dataclass
 class A:
     a: int
-[builtins fixtures/list.pyi]
+[builtins fixtures/dataclasses.pyi]
 [out]
 ==
 main:3: error: Unsupported left operand type for < ("B")
@@ -864,7 +864,7 @@ from dataclasses import dataclass
 class A:
     a: int
     other: int
-[builtins fixtures/list.pyi]
+[builtins fixtures/dataclasses.pyi]
 [out]
 ==
 main:3: error: Too few arguments for "C"
@@ -904,7 +904,7 @@ from dataclasses import dataclass
 class A:
     a: int
 
-[builtins fixtures/list.pyi]
+[builtins fixtures/dataclasses.pyi]
 [out]
 ==
 main:3: error: Too few arguments for "C"
diff --git a/test-data/unit/fixtures/attr.pyi b/test-data/unit/fixtures/attr.pyi
index deb1906d931e..c209abfef0d9 100644
--- a/test-data/unit/fixtures/attr.pyi
+++ b/test-data/unit/fixtures/attr.pyi
@@ -25,3 +25,4 @@ class complex:
 class str: pass
 class unicode: pass
 class ellipsis: pass
+class tuple: pass
diff --git a/test-data/unit/fixtures/dataclasses.pyi b/test-data/unit/fixtures/dataclasses.pyi
new file mode 100644
index 000000000000..fb0053c80b25
--- /dev/null
+++ b/test-data/unit/fixtures/dataclasses.pyi
@@ -0,0 +1,22 @@
+from typing import Generic, Sequence, TypeVar
+
+_T = TypeVar('_T')
+_U = TypeVar('_U')
+
+class object:
+    def __init__(self) -> None: pass
+    def __eq__(self, o: object) -> bool: pass
+    def __ne__(self, o: object) -> bool: pass
+
+class type: pass
+class ellipsis: pass
+class tuple(Generic[_T]): pass
+class int: pass
+class float: pass
+class str: pass
+class bool(int): pass
+class dict(Generic[_T, _U]): pass
+class list(Generic[_T], Sequence[_T]): pass
+class function: pass
+class classmethod: pass
+property = object()
diff --git a/test-data/unit/lib-stub/attr.pyi b/test-data/unit/lib-stub/attr.pyi
index 7399eb442594..495e54da350b 100644
--- a/test-data/unit/lib-stub/attr.pyi
+++ b/test-data/unit/lib-stub/attr.pyi
@@ -1,4 +1,4 @@
-from typing import TypeVar, overload, Callable, Any, Type, Optional, Union, Sequence, Mapping
+from typing import TypeVar, overload, Callable, Any, Type, Optional, Union, Sequence, Mapping, Generic
 
 _T = TypeVar('_T')
 _C = TypeVar('_C', bound=type)
@@ -115,6 +115,8 @@ def attrs(maybe_cls: None = ...,
           ) -> Callable[[_C], _C]: ...
 
 
+class Attribute(Generic[_T]): pass
+
 # aliases
 s = attributes = attrs
 ib = attr = attrib
diff --git a/test-data/unit/lib-stub/dataclasses.pyi b/test-data/unit/lib-stub/dataclasses.pyi
index 160cfcd066ba..c92775e9ee74 100644
--- a/test-data/unit/lib-stub/dataclasses.pyi
+++ b/test-data/unit/lib-stub/dataclasses.pyi
@@ -28,3 +28,6 @@ def field(*, default_factory: Callable[[], _T],
 def field(*,
     init: bool = ..., repr: bool = ..., hash: Optional[bool] = ..., compare: bool = ...,
     metadata: Optional[Mapping[str, Any]] = ...) -> Any: ...
+
+
+class Field(Generic[_T]): pass


Code review comments:
diff_hunk: @@ -390,9 +389,10 @@ app1 = Application("example-1", 5)
 app2 = Application("example-2", 5)
 app1 == app2
 app1 != app2
-app1 == None  # E: Unsupported operand types for == ("Application" and "None")
+app1 == None
+reveal_type(app1.__eq__)  # N: Revealed type is 'def (builtins.object) -> builtins.bool'
path: test-data/unit/check-dataclasses.test
text: @author:
@user1 maybe this test is not valid. Possible it passes on master because of incomplete `object` definition in fixtures/list.pyi stub, in dataclasses.pyi `object` has `__eq__` and `__ne__` methods.

Example:
```python
from dataclasses import dataclass

@user2
class A:
    pass

reveal_type(A.__eq__)
```
On latest version of mypy output is:
```
test.py:7: note: Revealed type is 'def (builtins.object, builtins.object) -> builtins.bool'
```
Maybe this code doesn't work: https://github.com/python/mypy/blob/master/mypy/plugins/dataclasses.py#L127
And I don't understand, why `__eq__` has this type: `(self: T, other: T) -> bool`. Maybe need to create new issue for this?