Repository: sphinx-doc/sphinx

Issue:
Allow for slots to be documented in the 3.8 mentioned dictionary style
**Is your feature request related to a problem? Please describe.**
To an extent. Sphinx's support of slots and conflicts between slots and the ideas of member types vs class variables has been a long standing issue (see context).

**Describe the solution you'd like**
*If* slots is a dictionary, the values of the dictionary can be used as docstrings for that slot.

**Describe alternatives you've considered**
Current documentation methods for slots, but it is lacking and has issues with conflicts and more.

**Additional context**
- https://docs.python.org/dev/whatsnew/3.8.html#inspect
- #4678
- https://github.com/sphinx-doc/sphinx/issues/4678#issuecomment-426334192
- https://github.com/sphinx-doc/sphinx/issues/2549#issuecomment-408709869


Discussion:

Patch:
diff --git a/CHANGES b/CHANGES
index 2040dac03..961c91fe5 100644
--- a/CHANGES
+++ b/CHANGES
@@ -83,6 +83,8 @@ Features added
   ``imported-members`` option
 * #4777: autodoc: Support coroutine
 * #744: autodoc: Support abstractmethod
+* #6325: autodoc: Support attributes in __slots__.  For dict-style __slots__,
+  autodoc considers values as a docstring of the attribute
 * #6212 autosummary: Add :confval:`autosummary_imported_members` to display
   imported members on autosummary
 * #6271: ``make clean`` is catastrophically broken if building into '.'
diff --git a/doc/intro.rst b/doc/intro.rst
index f6f6218bd..622d16b83 100644
--- a/doc/intro.rst
+++ b/doc/intro.rst
@@ -17,10 +17,10 @@ docs have a look at `Epydoc <http://epydoc.sourceforge.net/>`_, which also
 understands reST.
 
 For a great "introduction" to writing docs in general -- the whys and hows, see
-also `Write the docs`_, written by Eric Holscher.
+also `Write the docs <https://write-the-docs.readthedocs.io/>`_, written by Eric
+Holscher.
 
 .. _rinohtype: https://github.com/brechtm/rinohtype
-.. _Write the docs: http://www.writethedocs.org/guide/writing/beginners-guide-to-docs/
 
 Conversion from other systems
 -----------------------------
diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py
index 3048b8e48..65a292b6a 100644
--- a/sphinx/ext/autodoc/__init__.py
+++ b/sphinx/ext/autodoc/__init__.py
@@ -67,6 +67,7 @@ def identity(x):
 
 ALL = object()
 INSTANCEATTR = object()
+SLOTSATTR = object()
 
 
 def members_option(arg):
@@ -1493,6 +1494,55 @@ def add_content(self, more_content, no_docstring=False):
         super().add_content(more_content, no_docstring=True)
 
 
+class SlotsAttributeDocumenter(AttributeDocumenter):
+    """
+    Specialized Documenter subclass for attributes that cannot be imported
+    because they are attributes in __slots__.
+    """
+    objtype = 'slotsattribute'
+    directivetype = 'attribute'
+    member_order = 60
+
+    # must be higher than AttributeDocumenter
+    priority = 11
+
+    @classmethod
+    def can_document_member(cls, member, membername, isattr, parent):
+        # type: (Any, str, bool, Any) -> bool
+        """This documents only SLOTSATTR members."""
+        return member is SLOTSATTR
+
+    def import_object(self):
+        # type: () -> bool
+        """Never import anything."""
+        # disguise as an attribute
+        self.objtype = 'attribute'
+        self._datadescriptor = True
+
+        with mock(self.env.config.autodoc_mock_imports):
+            try:
+                ret = import_object(self.modname, self.objpath[:-1], 'class',
+                                    attrgetter=self.get_attr,
+                                    warningiserror=self.env.config.autodoc_warningiserror)
+                self.module, _, _, self.parent = ret
+                return True
+            except ImportError as exc:
+                logger.warning(exc.args[0], type='autodoc', subtype='import_object')
+                self.env.note_reread()
+                return False
+
+    def get_doc(self, encoding=None, ignore=1):
+        # type: (str, int) -> List[List[str]]
+        """Decode and return lines of the docstring(s) for the object."""
+        name = self.objpath[-1]
+        __slots__ = safe_getattr(self.parent, '__slots__', [])
+        if isinstance(__slots__, dict) and isinstance(__slots__.get(name), str):
+            docstring = prepare_docstring(__slots__[name])
+            return [docstring]
+        else:
+            return []
+
+
 def get_documenters(app):
     # type: (Sphinx) -> Dict[str, Type[Documenter]]
     """Returns registered Documenter classes"""
@@ -1554,6 +1604,7 @@ def setup(app):
     app.add_autodocumenter(AttributeDocumenter)
     app.add_autodocumenter(PropertyDocumenter)
     app.add_autodocumenter(InstanceAttributeDocumenter)
+    app.add_autodocumenter(SlotsAttributeDocumenter)
 
     app.add_config_value('autoclass_content', 'class', True)
     app.add_config_value('autodoc_member_order', 'alphabetic', True)
diff --git a/sphinx/ext/autodoc/importer.py b/sphinx/ext/autodoc/importer.py
index a06d93d52..37bdae8a9 100644
--- a/sphinx/ext/autodoc/importer.py
+++ b/sphinx/ext/autodoc/importer.py
@@ -15,7 +15,7 @@
 
 from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias
 from sphinx.util import logging
-from sphinx.util.inspect import isenumclass, safe_getattr
+from sphinx.util.inspect import isclass, isenumclass, safe_getattr
 
 if False:
     # For type annotation
@@ -127,6 +127,13 @@ def get_object_members(subject, objpath, attrgetter, analyzer=None):
             if name not in superclass.__dict__:
                 members[name] = Attribute(name, True, value)
 
+    # members in __slots__
+    if isclass(subject) and hasattr(subject, '__slots__'):
+        from sphinx.ext.autodoc import SLOTSATTR
+
+        for name in subject.__slots__:
+            members[name] = Attribute(name, True, SLOTSATTR)
+
     # other members
     for name in dir(subject):
         try:


Test patch:
diff --git a/tests/roots/test-ext-autodoc/target/slots.py b/tests/roots/test-ext-autodoc/target/slots.py
new file mode 100644
index 000000000..44e750320
--- /dev/null
+++ b/tests/roots/test-ext-autodoc/target/slots.py
@@ -0,0 +1,11 @@
+class Foo:
+    __slots__ = ['attr']
+
+
+class Bar:
+    __slots__ = {'attr1': 'docstring of attr1',
+                 'attr2': 'docstring of attr2',
+                 'attr3': None}
+
+    def __init__(self):
+        self.attr2 = None  #: docstring of instance attr2
diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py
index 3d3a5943c..8f08bb7fa 100644
--- a/tests/test_autodoc.py
+++ b/tests/test_autodoc.py
@@ -1320,6 +1320,46 @@ def test_instance_attributes(app):
     ]
 
 
+@pytest.mark.sphinx('html', testroot='ext-autodoc')
+def test_slots(app):
+    options = {"members": None,
+               "undoc-members": True}
+    actual = do_autodoc(app, 'module', 'target.slots', options)
+    assert list(actual) == [
+        '',
+        '.. py:module:: target.slots',
+        '',
+        '',
+        '.. py:class:: Bar()',
+        '   :module: target.slots',
+        '',
+        '   ',
+        '   .. py:attribute:: Bar.attr1',
+        '      :module: target.slots',
+        '   ',
+        '      docstring of attr1',
+        '      ',
+        '   ',
+        '   .. py:attribute:: Bar.attr2',
+        '      :module: target.slots',
+        '   ',
+        '      docstring of instance attr2',
+        '      ',
+        '   ',
+        '   .. py:attribute:: Bar.attr3',
+        '      :module: target.slots',
+        '   ',
+        '',
+        '.. py:class:: Foo',
+        '   :module: target.slots',
+        '',
+        '   ',
+        '   .. py:attribute:: Foo.attr',
+        '      :module: target.slots',
+        '   ',
+    ]
+
+
 @pytest.mark.sphinx('html', testroot='ext-autodoc')
 def test_enum_class(app):
     options = {"members": None,