Repository: python-attrs/attrs (python)

Issue:
`attr.resolve_types` caches badly with inheritence
Reproduction case:

```py
from __future__ import annotations

import attr

@attr.define()
class A:
  n: int

@attr.define()
class B(A):
  pass

attr.resolve_types(A)
attr.resolve_types(B)

print(attr.fields(B))
```

I believe this is because `B` is somehow inheriting `A`'s `__attrs_types_resolved__`, even though it gets set later -- which means that it never had its types resolved, but it thinks it has.

This came up when I was using cattrs (I raised an issue there about a trouble I had debugging too :^), which means I do not control the resolution order of types without applying a `attr.resolve_types` decorator to every class (which might not work -- I'm not sure all my class declarations are in order either...)

Pull request:
Fix bug in resolve_types with subclasses
# Pull Request Check List

This is just a friendly reminder about the most common mistakes.  Please make sure that you tick all boxes.  But please read our [contribution guide](https://www.attrs.org/en/latest/contributing.html) at least once, it will save you unnecessary review cycles!

If an item doesn't apply to your pull request, **check it anyway** to make it apparent that there's nothing left to do.  If your pull request is a documentation fix or a trivial typo, feel free to delete the whole thing.

Fixes #842

- [x] Added **tests** for changed code.
- [ ] New features have been added to our [Hypothesis testing strategy](https://github.com/python-attrs/attrs/blob/main/tests/strategies.py).
- [ ] Changes or additions to public APIs are reflected in our type stubs (files ending in ``.pyi``).
    - [ ] ...and used in the stub test file `tests/typing_example.py`.
- [ ] Updated **documentation** for changed code.
    - [ ] New functions/classes have to be added to `docs/api.rst` by hand.
    - [ ] Changes to the signature of `@attr.s()` have to be added by hand too.
    - [ ] Changed/added classes/methods/functions have appropriate `versionadded`, `versionchanged`, or `deprecated` [directives](http://www.sphinx-doc.org/en/stable/markup/para.html#directive-versionadded).  Find the appropriate next version in our [``__init__.py``](https://github.com/python-attrs/attrs/blob/main/src/attr/__init__.py) file.
- [ ] Documentation in `.rst` files is written using [semantic newlines](https://rhodesmill.org/brandon/2012/one-sentence-per-line/).
- [x] Changes (and possible deprecations) have news fragments in [`changelog.d`](https://github.com/python-attrs/attrs/blob/main/changelog.d).

If you have *any* questions to *any* of the points above, just **submit and ask**!  This checklist is here to *help* you, not to deter you from contributing!


Patch:
diff --git a/changelog.d/843.change.rst b/changelog.d/843.change.rst
new file mode 100644
index 0000000..7469501
--- /dev/null
+++ b/changelog.d/843.change.rst
@@ -0,0 +1,2 @@
+``attr.resolve_types()`` now resolves types of subclasses after the parents are resolved.
+`#842 <https://github.com/python-attrs/attrs/issues/842>`_
diff --git a/src/attr/_funcs.py b/src/attr/_funcs.py
index fda508c..73271c5 100644
--- a/src/attr/_funcs.py
+++ b/src/attr/_funcs.py
@@ -377,11 +377,9 @@ def resolve_types(cls, globalns=None, localns=None, attribs=None):
     ..  versionadded:: 21.1.0 *attribs*
 
     """
-    try:
-        # Since calling get_type_hints is expensive we cache whether we've
-        # done it already.
-        cls.__attrs_types_resolved__
-    except AttributeError:
+    # Since calling get_type_hints is expensive we cache whether we've
+    # done it already.
+    if getattr(cls, "__attrs_types_resolved__", None) != cls:
         import typing
 
         hints = typing.get_type_hints(cls, globalns=globalns, localns=localns)
@@ -389,7 +387,9 @@ def resolve_types(cls, globalns=None, localns=None, attribs=None):
             if field.name in hints:
                 # Since fields have been frozen we must work around it.
                 _obj_setattr(field, "type", hints[field.name])
-        cls.__attrs_types_resolved__ = True
+        # We store the class we resolved so that subclasses know they haven't
+        # been resolved.
+        cls.__attrs_types_resolved__ = cls
 
     # Return the class so you can use it as a decorator too.
     return cls


Test patch:
diff --git a/tests/test_annotations.py b/tests/test_annotations.py
index 2df6311..dd81522 100644
--- a/tests/test_annotations.py
+++ b/tests/test_annotations.py
@@ -618,6 +618,40 @@ class TestAnnotations:
         with pytest.raises(NameError):
             typing.get_type_hints(C.__init__)
 
+    def test_inheritance(self):
+        """
+        Subclasses can be resolved after the parent is resolved.
+        """
+
+        @attr.define()
+        class A:
+            n: "int"
+
+        @attr.define()
+        class B(A):
+            pass
+
+        attr.resolve_types(A)
+        attr.resolve_types(B)
+
+        assert int == attr.fields(A).n.type
+        assert int == attr.fields(B).n.type
+
+    def test_resolve_twice(self):
+        """
+        You can call resolve_types as many times as you like.
+        This test is here mostly for coverage.
+        """
+
+        @attr.define()
+        class A:
+            n: "int"
+
+        attr.resolve_types(A)
+        assert int == attr.fields(A).n.type
+        attr.resolve_types(A)
+        assert int == attr.fields(A).n.type
+
 
 @pytest.mark.parametrize(
     "annot",