Repository: sphinx-doc/sphinx

Issue:
enable removal of type hints from autodoc
The proposed #6353 solution to #6311 presents an interesting opportunity to consistently _and_ conveniently enable autodoc users to remove type hints.  This can be achieved in one of two ways

1. An autodoc config variable such as `autodoc_remove_type_hints` or something, which globally removes type hints from signatures.  And/or
2. An additional parameter to `"autodoc-process-signature"` event that is the documenter itself.

Type hints are great, but currently some of my signatures become very confused in the docs making it hard to understand.

**Describe the solution you'd like**

If we make the `documenter` available, this becomes quite simple.  A breaking change would be to just tack a `self` on at the end of the event.  I took a look around, but recovering the documenter from the information currently provided to `"autodoc-process-signature"` is not easy to do.

https://github.com/sphinx-doc/sphinx/blob/165897a74951fb03e497d6e05496ce02e897f820/sphinx/ext/autodoc/__init__.py#L406-L408

```diff
--- a/sphinx/ext/autodoc/__init__.py
+++ b/sphinx/ext/autodoc/__init__.py
@@ -413,7 +413,8 @@ class Documenter:
 
         result = self.env.events.emit_firstresult('autodoc-process-signature',
                                                   self.objtype, self.fullname,
-                                                  self.object, self.options, args, retann)
+                                                  self.object, self.options, args, retann,
+                                                  self)
         if result:
             args, retann = result
```

Giving the `documenter` (`self`) directly gives us a very clean way to remove type hints:

```py
# NOTE: only valid with #6353 (added show_annotation ability)
def autodoc_process_signature(app, what, name, obj, options, signature, return_annotation, documenter):
    # We are only modifying functions / methods.  Return None signals no change.
    if what not in {"function", "method"}:
        return None

    try:
        return documenter.format_args(show_annotation=False), return_annotation
    except:
        return None
```

**Considerations**

Adding an additional parameter is a breaking change.

```py
TypeError: autodoc_process_signature() takes 7 positional arguments but 8 were given
```

So the implementation should call with 8 and fallback to calling with 7, issuing a warning that 8 are required now (?).

I'm happy to implement this with some discussion.  For example, with (1) adding a global option, would it be better to do something like

```py
autodoc_remove_type_hints = [
    "function", "method"
]
```

so that it's only done for specific types?  To be honest, I'd be thrilled with just (2) and add documentation showing users how to remove type hints if they want since it's very little code on their part, and trying to autopopulate the proposed **kwargs from #6353 may add a lot of complexity for not very much benefit.

Discussion:

Patch:
diff --git a/CHANGES b/CHANGES
index 961c91fe5..93adee35a 100644
--- a/CHANGES
+++ b/CHANGES
@@ -85,6 +85,8 @@ Features added
 * #744: autodoc: Support abstractmethod
 * #6325: autodoc: Support attributes in __slots__.  For dict-style __slots__,
   autodoc considers values as a docstring of the attribute
+* #6361: autodoc: Add :confval:`autodoc_typehints` to suppress typehints from
+  signature
 * #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/usage/extensions/autodoc.rst b/doc/usage/extensions/autodoc.rst
index 0b6061e78..d85fc050f 100644
--- a/doc/usage/extensions/autodoc.rst
+++ b/doc/usage/extensions/autodoc.rst
@@ -431,6 +431,16 @@ There are also config values that you can set:
       This config value only requires to declare the top-level modules that
       should be mocked.
 
+.. confval:: autodoc_typehints
+
+   This value controls how to represents typehints.  The setting takes the
+   following values:
+
+   * ``'signature'`` -- Show typehints as its signature (default)
+   * ``'none'`` -- Do not show typehints
+
+   .. versionadded: 2.1
+
 .. confval:: autodoc_warningiserror
 
    This value controls the behavior of :option:`sphinx-build -W` during
diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py
index 65a292b6a..7172553ac 100644
--- a/sphinx/ext/autodoc/__init__.py
+++ b/sphinx/ext/autodoc/__init__.py
@@ -17,6 +17,7 @@
 from docutils.statemachine import StringList
 
 import sphinx
+from sphinx.config import ENUM
 from sphinx.deprecation import (
     RemovedInSphinx30Warning, RemovedInSphinx40Warning, deprecated_alias
 )
@@ -1007,6 +1008,9 @@ def can_document_member(cls, member, membername, isattr, parent):
 
     def format_args(self, **kwargs):
         # type: (Any) -> str
+        if self.env.config.autodoc_typehints == 'none':
+            kwargs.setdefault('show_annotation', False)
+
         if inspect.isbuiltin(self.object) or inspect.ismethoddescriptor(self.object):
             # cannot introspect arguments of a C function or method
             return None
@@ -1106,6 +1110,9 @@ def import_object(self):
 
     def format_args(self, **kwargs):
         # type: (Any) -> str
+        if self.env.config.autodoc_typehints == 'none':
+            kwargs.setdefault('show_annotation', False)
+
         # for classes, the relevant signature is the __init__ method's
         initmeth = self.get_attr(self.object, '__init__', None)
         # classes without __init__ method, default __init__ or
@@ -1320,6 +1327,9 @@ def import_object(self):
 
     def format_args(self, **kwargs):
         # type: (Any) -> str
+        if self.env.config.autodoc_typehints == 'none':
+            kwargs.setdefault('show_annotation', False)
+
         if inspect.isbuiltin(self.object) or inspect.ismethoddescriptor(self.object):
             # can never get arguments of a C function or method
             return None
@@ -1612,6 +1622,7 @@ def setup(app):
     app.add_config_value('autodoc_default_options', {}, True)
     app.add_config_value('autodoc_docstring_signature', True, True)
     app.add_config_value('autodoc_mock_imports', [], True)
+    app.add_config_value('autodoc_typehints', "signature", True, ENUM("signature", "none"))
     app.add_config_value('autodoc_warningiserror', True, True)
     app.add_config_value('autodoc_inherit_docstrings', True, True)
     app.add_event('autodoc-process-docstring')


Test patch:
diff --git a/tests/roots/test-ext-autodoc/target/typehints.py b/tests/roots/test-ext-autodoc/target/typehints.py
new file mode 100644
index 000000000..eedaab3b9
--- /dev/null
+++ b/tests/roots/test-ext-autodoc/target/typehints.py
@@ -0,0 +1,10 @@
+def incr(a: int, b: int = 1) -> int:
+    return a + b
+
+
+class Math:
+    def __init__(self, s: str, o: object = None) -> None:
+        pass
+
+    def incr(self, a: int, b: int = 1) -> int:
+        return a + b
diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py
index 8f08bb7fa..f6f5a618c 100644
--- a/tests/test_autodoc.py
+++ b/tests/test_autodoc.py
@@ -1689,6 +1689,58 @@ def test_partialmethod(app):
     assert list(actual) == expected
 
 
+@pytest.mark.sphinx('html', testroot='ext-autodoc')
+def test_autodoc_typehints_signature(app):
+    app.config.autodoc_typehints = "signature"
+
+    options = {"members": None,
+               "undoc-members": True}
+    actual = do_autodoc(app, 'module', 'target.typehints', options)
+    assert list(actual) == [
+        '',
+        '.. py:module:: target.typehints',
+        '',
+        '',
+        '.. py:class:: Math(s: str, o: object = None)',
+        '   :module: target.typehints',
+        '',
+        '   ',
+        '   .. py:method:: Math.incr(a: int, b: int = 1) -> int',
+        '      :module: target.typehints',
+        '   ',
+        '',
+        '.. py:function:: incr(a: int, b: int = 1) -> int',
+        '   :module: target.typehints',
+        ''
+    ]
+
+
+@pytest.mark.sphinx('html', testroot='ext-autodoc')
+def test_autodoc_typehints_none(app):
+    app.config.autodoc_typehints = "none"
+
+    options = {"members": None,
+               "undoc-members": True}
+    actual = do_autodoc(app, 'module', 'target.typehints', options)
+    assert list(actual) == [
+        '',
+        '.. py:module:: target.typehints',
+        '',
+        '',
+        '.. py:class:: Math(s, o = None)',
+        '   :module: target.typehints',
+        '',
+        '   ',
+        '   .. py:method:: Math.incr(a, b = 1) -> int',
+        '      :module: target.typehints',
+        '   ',
+        '',
+        '.. py:function:: incr(a, b = 1) -> int',
+        '   :module: target.typehints',
+        ''
+    ]
+
+
 @pytest.mark.sphinx('html', testroot='ext-autodoc')
 @pytest.mark.filterwarnings('ignore:autodoc_default_flags is now deprecated.')
 def test_merge_autodoc_default_flags1(app):