Repository: sphinx-doc/sphinx

Issue:
Class attribute in subclass inherits attribute docstring in Sphinx 5.0.1
### Describe the bug

As of Sphinx 5.0.1 the SymPy docs build gives lots of warnings like this:
```
docstring of sympy.polys.domains.field.Field.is_Field:14: WARNING: more than one target found for cross-reference 'is_Ring': sympy.polys.domains.domain.Domain.is_Ring, sympy.polys.domains.ring.Ring.is_Ring
```
Full output in CI can be seen here:
https://github.com/sympy/sympy/runs/6784391829?check_suite_focus=true

The problem seems to be that there is a class Domain which has a documented attribute `is_Ring`:
https://github.com/sympy/sympy/blob/a96dce8b55675a0529d9bed479e2569126e27149/sympy/polys/domains/domain.py#L245-L262
You can see that in the docs here:
https://docs.sympy.org/dev/modules/polys/domainsref.html#sympy.polys.domains.domain.Domain.is_Ring

Then there is a class Ring which is a subclass of Domain and changes the value of the class attribute but does not document it:
https://github.com/sympy/sympy/blob/a96dce8b55675a0529d9bed479e2569126e27149/sympy/polys/domains/ring.py#L13

In previous versions of Sphinx the Ring class would not show the `is_Ring` attribute:
https://docs.sympy.org/dev/modules/polys/domainsref.html#sympy.polys.domains.ring.Ring

Now with Sphinx 5.0.1 though it seems that the Sphinx build decides to document the `Ring.is_Ring` attribute with the docstring inherited from the superclass Domain. Then the see also references to `is_Ring` fail due to multiple targets.

### How to Reproduce

```
$ git clone https://github.com/sympy/sympy.git
$ cd sympy/doc
$ pip install -r requirements.txt
$ make html
$ # open _build/html/modules/polys/domainsref.html
```


### Expected behavior

The attribute `is_Ring` in the subclass `Ring` would not be documented and there would be only one target for `is_Ring` pointing to the `Domain.is_Ring` attribute.

### Your project

https://github.com/sympy/sympy

### Screenshots

_No response_

### OS

Any

### Python version

3.8-3.10

### Sphinx version

5.0.1

### Sphinx extensions

Sphinx                        5.0.1 sphinx-autobuild              2021.3.14 sphinx-basic-ng               0.0.1a11 sphinx-copybutton             0.5.0 sphinx-math-dollar            1.2.1 sphinx-press-theme            0.8.0 sphinx-reredirects            0.0.1 sphinxcontrib-applehelp       1.0.2 sphinxcontrib-devhelp         1.0.2 sphinxcontrib-htmlhelp        2.0.0 sphinxcontrib-jsmath          1.0.1 sphinxcontrib-qthelp          1.0.3 sphinxcontrib-serializinghtml 1.1.5

### Extra tools

_No response_

### Additional context

_No response_

Discussion:

Patch:
diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py
index e16ab8ce5..642e0cfd8 100644
--- a/sphinx/ext/autodoc/__init__.py
+++ b/sphinx/ext/autodoc/__init__.py
@@ -1670,7 +1670,8 @@ def add_directive_header(self, sig: str) -> None:
             self.add_line('   ' + _('Bases: %s') % ', '.join(base_classes), sourcename)
 
     def get_object_members(self, want_all: bool) -> Tuple[bool, ObjectMembers]:
-        members = get_class_members(self.object, self.objpath, self.get_attr)
+        members = get_class_members(self.object, self.objpath, self.get_attr,
+                                    self.config.autodoc_inherit_docstrings)
         if not want_all:
             if not self.options.members:
                 return False, []  # type: ignore
diff --git a/sphinx/ext/autodoc/importer.py b/sphinx/ext/autodoc/importer.py
index 85c1e81be..d392ae75d 100644
--- a/sphinx/ext/autodoc/importer.py
+++ b/sphinx/ext/autodoc/importer.py
@@ -205,8 +205,8 @@ def get_object_members(subject: Any, objpath: List[str], attrgetter: Callable,
     return members
 
 
-def get_class_members(subject: Any, objpath: List[str], attrgetter: Callable
-                      ) -> Dict[str, "ObjectMember"]:
+def get_class_members(subject: Any, objpath: List[str], attrgetter: Callable,
+                      inherit_docstrings: bool = True) -> Dict[str, "ObjectMember"]:
     """Get members and attributes of target class."""
     from sphinx.ext.autodoc import INSTANCEATTR, ObjectMember
 
@@ -290,6 +290,11 @@ def get_class_members(subject: Any, objpath: List[str], attrgetter: Callable
                     elif (ns == qualname and docstring and
                           isinstance(members[name], ObjectMember) and
                           not members[name].docstring):
+                        if cls != subject and not inherit_docstrings:
+                            # If we are in the MRO of the class and not the class itself,
+                            # and we do not want to inherit docstrings, then skip setting
+                            # the docstring below
+                            continue
                         # attribute is already known, because dir(subject) enumerates it.
                         # But it has no docstring yet
                         members[name].docstring = '\n'.join(docstring)


Test patch:
diff --git a/tests/roots/test-ext-autodoc/target/inheritance.py b/tests/roots/test-ext-autodoc/target/inheritance.py
index 485a943c8..85d95cd74 100644
--- a/tests/roots/test-ext-autodoc/target/inheritance.py
+++ b/tests/roots/test-ext-autodoc/target/inheritance.py
@@ -1,4 +1,7 @@
 class Base(object):
+    #: docstring
+    inheritedattr = None
+
     def inheritedmeth(self):
         """Inherited function."""
 
diff --git a/tests/test_ext_autodoc.py b/tests/test_ext_autodoc.py
index ecd2bbce6..a35c6f640 100644
--- a/tests/test_ext_autodoc.py
+++ b/tests/test_ext_autodoc.py
@@ -549,6 +549,7 @@ def test_autodoc_members(app):
     actual = do_autodoc(app, 'class', 'target.inheritance.Base', options)
     assert list(filter(lambda l: '::' in l, actual)) == [
         '.. py:class:: Base()',
+        '   .. py:attribute:: Base.inheritedattr',
         '   .. py:method:: Base.inheritedclassmeth()',
         '   .. py:method:: Base.inheritedmeth()',
         '   .. py:method:: Base.inheritedstaticmeth(cls)'
@@ -569,6 +570,7 @@ def test_autodoc_members(app):
     actual = do_autodoc(app, 'class', 'target.inheritance.Base', options)
     assert list(filter(lambda l: '::' in l, actual)) == [
         '.. py:class:: Base()',
+        '   .. py:attribute:: Base.inheritedattr',
         '   .. py:method:: Base.inheritedclassmeth()',
         '   .. py:method:: Base.inheritedmeth()',
         '   .. py:method:: Base.inheritedstaticmeth(cls)'
@@ -601,6 +603,7 @@ def test_autodoc_exclude_members(app):
     actual = do_autodoc(app, 'class', 'target.inheritance.Base', options)
     assert list(filter(lambda l: '::' in l, actual)) == [
         '.. py:class:: Base()',
+        '   .. py:attribute:: Base.inheritedattr',
         '   .. py:method:: Base.inheritedclassmeth()'
     ]
 
@@ -618,6 +621,7 @@ def test_autodoc_exclude_members(app):
     actual = do_autodoc(app, 'class', 'target.inheritance.Base', options)
     assert list(filter(lambda l: '::' in l, actual)) == [
         '.. py:class:: Base()',
+        '   .. py:attribute:: Base.inheritedattr',
         '   .. py:method:: Base.inheritedclassmeth()'
     ]
 
@@ -628,6 +632,7 @@ def test_autodoc_exclude_members(app):
     actual = do_autodoc(app, 'class', 'target.inheritance.Base', options)
     assert list(filter(lambda l: '::' in l, actual)) == [
         '.. py:class:: Base()',
+        '   .. py:attribute:: Base.inheritedattr',
         '   .. py:method:: Base.inheritedclassmeth()',
         '   .. py:method:: Base.inheritedstaticmeth(cls)'
     ]
@@ -639,6 +644,7 @@ def test_autodoc_exclude_members(app):
     actual = do_autodoc(app, 'class', 'target.inheritance.Base', options)
     assert list(filter(lambda l: '::' in l, actual)) == [
         '.. py:class:: Base()',
+        '   .. py:attribute:: Base.inheritedattr',
         '   .. py:method:: Base.inheritedclassmeth()',
     ]
 
@@ -648,6 +654,7 @@ def test_autodoc_exclude_members(app):
     actual = do_autodoc(app, 'class', 'target.inheritance.Base', options)
     assert list(filter(lambda l: '::' in l, actual)) == [
         '.. py:class:: Base()',
+        '   .. py:attribute:: Base.inheritedattr',
         '   .. py:method:: Base.inheritedclassmeth()',
     ]
 
@@ -658,6 +665,7 @@ def test_autodoc_exclude_members(app):
     actual = do_autodoc(app, 'class', 'target.inheritance.Base', options)
     assert list(filter(lambda l: '::' in l, actual)) == [
         '.. py:class:: Base()',
+        '   .. py:attribute:: Base.inheritedattr',
         '   .. py:method:: Base.inheritedclassmeth()',
         '   .. py:method:: Base.inheritedmeth()',
         '   .. py:method:: Base.inheritedstaticmeth(cls)'
diff --git a/tests/test_ext_autodoc_automodule.py b/tests/test_ext_autodoc_automodule.py
index 8208d86f6..71b23679d 100644
--- a/tests/test_ext_autodoc_automodule.py
+++ b/tests/test_ext_autodoc_automodule.py
@@ -133,6 +133,13 @@ def test_automodule_inherited_members(app):
         '   :module: target.inheritance',
         '',
         '',
+        '   .. py:attribute:: Base.inheritedattr',
+        '      :module: target.inheritance',
+        '      :value: None',
+        '',
+        '      docstring',
+        '',
+        '',
         '   .. py:method:: Base.inheritedclassmeth()',
         '      :module: target.inheritance',
         '      :classmethod:',
diff --git a/tests/test_ext_autodoc_configs.py b/tests/test_ext_autodoc_configs.py
index 0011f450b..f40f3354e 100644
--- a/tests/test_ext_autodoc_configs.py
+++ b/tests/test_ext_autodoc_configs.py
@@ -277,6 +277,72 @@ def test_autodoc_inherit_docstrings(app):
     ]
 
 
+@pytest.mark.sphinx('html', testroot='ext-autodoc')
+def test_autodoc_inherit_docstrings_for_inherited_members(app):
+    options = {"members": None,
+               "inherited-members": None}
+
+    assert app.config.autodoc_inherit_docstrings is True  # default
+    actual = do_autodoc(app, 'class', 'target.inheritance.Derived', options)
+    assert list(actual) == [
+        '',
+        '.. py:class:: Derived()',
+        '   :module: target.inheritance',
+        '',
+        '',
+        '   .. py:attribute:: Derived.inheritedattr',
+        '      :module: target.inheritance',
+        '      :value: None',
+        '',
+        '      docstring',
+        '',
+        '',
+        '   .. py:method:: Derived.inheritedclassmeth()',
+        '      :module: target.inheritance',
+        '      :classmethod:',
+        '',
+        '      Inherited class method.',
+        '',
+        '',
+        '   .. py:method:: Derived.inheritedmeth()',
+        '      :module: target.inheritance',
+        '',
+        '      Inherited function.',
+        '',
+        '',
+        '   .. py:method:: Derived.inheritedstaticmeth(cls)',
+        '      :module: target.inheritance',
+        '      :staticmethod:',
+        '',
+        '      Inherited static method.',
+        '',
+    ]
+
+    # disable autodoc_inherit_docstrings
+    app.config.autodoc_inherit_docstrings = False
+    actual = do_autodoc(app, 'class', 'target.inheritance.Derived', options)
+    assert list(actual) == [
+        '',
+        '.. py:class:: Derived()',
+        '   :module: target.inheritance',
+        '',
+        '',
+        '   .. py:method:: Derived.inheritedclassmeth()',
+        '      :module: target.inheritance',
+        '      :classmethod:',
+        '',
+        '      Inherited class method.',
+        '',
+        '',
+        '   .. py:method:: Derived.inheritedstaticmeth(cls)',
+        '      :module: target.inheritance',
+        '      :staticmethod:',
+        '',
+        '      Inherited static method.',
+        '',
+    ]
+
+
 @pytest.mark.sphinx('html', testroot='ext-autodoc')
 def test_autodoc_docstring_signature(app):
     options = {"members": None, "special-members": "__init__, __new__"}