Repository: python-attrs/attrs (python)

Issue:
Type hint defaults don't work with inheritance
Consider

```
In [12]: @attr.s(auto_attribs=True)
    ...: class A:
    ...:     a: int = 10
    ...:

In [13]: @attr.s(auto_attribs=True)
    ...: class B(A):
    ...:     pass
    ...:

In [14]: A()
Out[14]: A(a=10)

In [15]: B()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-15-1c5ecc61f85b> in <module>()
----> 1 B()

TypeError: __init__() missing 1 required positional argument: 'a'

```

This should work, given that the following works:

```
In [16]: @attr.s()
    ...: class A:
    ...:     a = attr.ib(default=10)
    ...:

In [17]: @attr.s()
    ...: class B(A):
    ...:     pass
    ...:

In [18]: A()
Out[18]: A(a=10)

In [19]: B()
Out[19]: B(a=10)

```

Pull request description:
Handle inherited __annotations__
Yeah, so this makes sure that if an empty class inherits `__annotations__` from a super class, those are ignored.  If there’s a better way, let me know.

Fixes #291 

Patch:
diff --git a/changelog.d/291.change.rst b/changelog.d/291.change.rst
new file mode 100644
index 0000000..0d5438f
--- /dev/null
+++ b/changelog.d/291.change.rst
@@ -0,0 +1,1 @@
+Subclasses of ``auto_attribs=True`` can be empty now.
diff --git a/changelog.d/292.change.rst b/changelog.d/292.change.rst
new file mode 100644
index 0000000..0d5438f
--- /dev/null
+++ b/changelog.d/292.change.rst
@@ -0,0 +1,1 @@
+Subclasses of ``auto_attribs=True`` can be empty now.
diff --git a/src/attr/_make.py b/src/attr/_make.py
index a94b42b..9ea9e82 100644
--- a/src/attr/_make.py
+++ b/src/attr/_make.py
@@ -201,6 +201,22 @@ def _is_class_var(annot):
     return str(annot).startswith("typing.ClassVar")
 
 
+def _get_annotations(cls):
+    """
+    Get annotations for *cls*.
+    """
+    anns = getattr(cls, "__annotations__", None)
+    if anns is None:
+        return {}
+
+    # Verify that the annotations aren't merely inherited.
+    for super_cls in cls.__mro__[1:]:
+        if anns is getattr(super_cls, "__annotations__", None):
+            return {}
+
+    return anns
+
+
 def _transform_attrs(cls, these, auto_attribs):
     """
     Transform all `_CountingAttr`s on a class into `Attribute`s.
@@ -210,16 +226,15 @@ def _transform_attrs(cls, these, auto_attribs):
     Return an `_Attributes`.
     """
     cd = cls.__dict__
-    anns = getattr(cls, "__annotations__", {})
+    anns = _get_annotations(cls)
 
-    if these is None and auto_attribs is False:
+    if these is not None:
         ca_list = sorted((
-            (name, attr)
-            for name, attr
-            in cd.items()
-            if isinstance(attr, _CountingAttr)
+            (name, ca)
+            for name, ca
+            in iteritems(these)
         ), key=lambda e: e[1].counter)
-    elif these is None and auto_attribs is True:
+    elif auto_attribs is True:
         ca_names = {
             name
             for name, attr
@@ -251,9 +266,10 @@ def _transform_attrs(cls, these, auto_attribs):
             )
     else:
         ca_list = sorted((
-            (name, ca)
-            for name, ca
-            in iteritems(these)
+            (name, attr)
+            for name, attr
+            in cd.items()
+            if isinstance(attr, _CountingAttr)
         ), key=lambda e: e[1].counter)
 
     non_super_attrs = [


Test patch:
diff --git a/tests/test_annotations.py b/tests/test_annotations.py
index ee30944..602f21b 100644
--- a/tests/test_annotations.py
+++ b/tests/test_annotations.py
@@ -131,3 +131,26 @@ class TestAnnotations:
         assert (
             "The following `attr.ib`s lack a type annotation: v, y.",
         ) == e.value.args
+
+    @pytest.mark.parametrize("slots", [True, False])
+    def test_auto_attribs_subclassing(self, slots):
+        """
+        Attributes from super classes are inherited, it doesn't matter if the
+        subclass has annotations or not.
+
+        Ref #291
+        """
+        @attr.s(slots=slots, auto_attribs=True)
+        class A:
+            a: int = 1
+
+        @attr.s(slots=slots, auto_attribs=True)
+        class B(A):
+            b: int = 2
+
+        @attr.s(slots=slots, auto_attribs=True)
+        class C(A):
+            pass
+
+        assert "B(a=1, b=2)" == repr(B())
+        assert "C(a=1)" == repr(C())