Repository: tox-dev/sphinx-autodoc-typehints
Pull request #297 (2023-01-17 06:24:28)

Add fix for #296
See the explanation in the diff and in issue #296.

hoodmane: Well that doesn't seem to work...
gaborbernat: Almost just need to fix CI on 3.7 and 3.8!

Patch:
diff --git a/src/sphinx_autodoc_typehints/__init__.py b/src/sphinx_autodoc_typehints/__init__.py
index 4551e25..c08720d 100644
--- a/src/sphinx_autodoc_typehints/__init__.py
+++ b/src/sphinx_autodoc_typehints/__init__.py
@@ -6,6 +6,7 @@ import sys
 import textwrap
 import types
 from ast import FunctionDef, Module, stmt
+from functools import lru_cache
 from typing import Any, AnyStr, Callable, ForwardRef, NewType, TypeVar, get_type_hints
 
 from sphinx.application import Sphinx
@@ -694,6 +695,29 @@ def validate_config(app: Sphinx, env: BuildEnvironment, docnames: list[str]) ->
         raise ValueError(f"typehints_formatter needs to be callable or `None`, not {formatter}")
 
 
+@lru_cache()  # A cute way to make sure the function only runs once.
+def fix_autodoc_typehints_for_overloaded_methods() -> None:
+    """
+    sphinx-autodoc-typehints responds to the "autodoc-process-signature" event
+    to remove types from the signature line of functions.
+
+    Normally, `FunctionDocumenter.format_signature` and
+    `MethodDocumenter.format_signature` call `super().format_signature` which
+    ends up going to `Documenter.format_signature`, and this last method emits
+    the `autodoc-process-signature` event. However, if there are overloads,
+    `FunctionDocumenter.format_signature` does something else and the event
+    never occurs.
+
+    Here we remove this alternative code path by brute force.
+
+    See https://github.com/tox-dev/sphinx-autodoc-typehints/issues/296
+    """
+    from sphinx.ext.autodoc import FunctionDocumenter, MethodDocumenter
+
+    del FunctionDocumenter.format_signature
+    del MethodDocumenter.format_signature
+
+
 def setup(app: Sphinx) -> dict[str, bool]:
     app.add_config_value("always_document_param_types", False, "html")
     app.add_config_value("typehints_fully_qualified", False, "env")
@@ -707,6 +731,7 @@ def setup(app: Sphinx) -> dict[str, bool]:
     app.connect("env-before-read-docs", validate_config)  # config may be changed after “config-inited” event
     app.connect("autodoc-process-signature", process_signature)
     app.connect("autodoc-process-docstring", process_docstring)
+    fix_autodoc_typehints_for_overloaded_methods()
     return {"parallel_read_safe": True}
 
 
diff --git a/whitelist.txt b/whitelist.txt
index d32e390..fd0b8eb 100644
--- a/whitelist.txt
+++ b/whitelist.txt
@@ -11,6 +11,7 @@ dedent
 delattr
 dirname
 docnames
+Documenter
 dunder
 eval
 exc
@@ -31,6 +32,7 @@ isfunction
 iterdir
 kwonlyargs
 libs
+lru
 metaclass
 ModuleType
 multiline


Test patch:
diff --git a/tests/roots/test-dummy/dummy_module.py b/tests/roots/test-dummy/dummy_module.py
index 4cde5ff..5cea4d5 100644
--- a/tests/roots/test-dummy/dummy_module.py
+++ b/tests/roots/test-dummy/dummy_module.py
@@ -1,7 +1,6 @@
 from dataclasses import dataclass
 from mailbox import Mailbox
-from typing import Union  # noqa: F401 # needed for expansion of Optional
-from typing import Callable, Optional
+from typing import Callable, Optional, Union, overload
 
 
 def get_local_function():
@@ -286,3 +285,27 @@ def func_with_examples() -> int:
 
     Here are a couple of examples of how to use this function.
     """
+
+
+@overload
+def func_with_overload(a: int, b: int) -> None:  # noqa: U100
+    ...
+
+
+@overload
+def func_with_overload(a: str, b: str) -> None:  # noqa: U100
+    ...
+
+
+def func_with_overload(a: Union[int, str], b: Union[int, str]) -> None:  # noqa: U100
+    """
+    f does the thing. The arguments can either be ints or strings but they must
+    both have the same type.
+
+    Parameters
+    ----------
+    a:
+        The first thing
+    b:
+        The second thing
+    """
diff --git a/tests/roots/test-dummy/index.rst b/tests/roots/test-dummy/index.rst
index d4eadb9..50f06f5 100644
--- a/tests/roots/test-dummy/index.rst
+++ b/tests/roots/test-dummy/index.rst
@@ -40,3 +40,5 @@ Dummy Module
 .. autofunction:: dummy_module.mocked_import
 
 .. autofunction:: dummy_module.func_with_examples
+
+.. autofunction:: dummy_module.func_with_overload
diff --git a/tests/test_sphinx_autodoc_typehints.py b/tests/test_sphinx_autodoc_typehints.py
index 7899ce6..4042d09 100644
--- a/tests/test_sphinx_autodoc_typehints.py
+++ b/tests/test_sphinx_autodoc_typehints.py
@@ -770,6 +770,18 @@ def test_sphinx_output(
            -[ Examples ]-
 
            Here are a couple of examples of how to use this function.
+
+        dummy_module.func_with_overload(a, b)
+
+           f does the thing. The arguments can either be ints or strings but they must both have the same type.
+
+           Parameters:
+              * **a** ("Union"["int", "str"]) -- The first thing
+
+              * **b** ("Union"["int", "str"]) -- The second thing
+
+           Return type:
+              "None"
         """
         expected_contents = dedent(expected_contents).format(**format_args).replace("–", "--")
         assert text_contents == maybe_fix_py310(expected_contents)