Repository: tox-dev/sphinx-autodoc-typehints
Pull request #311 (2023-01-20 19:21:43)

Fix napoleon handling of numpy docstrings with no explicitly provided return type
If there is no explicitly provided return type, napoleon will incorrectly parse the entire return type explanation. It can be fixed by inserting a line with a single colon at the beginning of the returns section.

hoodmane: Done.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cf8871f..d8f4831 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,12 @@
 # Changelog
 
+## 1.21.7
+
+- Fixed a bug where if a class has an attribute and a constructor argument with the same name, the constructor argument
+  type would be rendered incorrectly (issue 308)
+
+- Fixed napoleon handling of numpy docstrings with no specified return type.
+
 ## 1.21.6
 
 - Fix a `Field list ends without a blank line` warning (issue 305).
diff --git a/src/sphinx_autodoc_typehints/__init__.py b/src/sphinx_autodoc_typehints/__init__.py
index 9185819..4a2bbee 100644
--- a/src/sphinx_autodoc_typehints/__init__.py
+++ b/src/sphinx_autodoc_typehints/__init__.py
@@ -809,6 +809,44 @@ def fix_autodoc_typehints_for_overloaded_methods() -> None:
     del MethodDocumenter.format_signature
 
 
+def napoleon_numpy_docstring_return_type_processor(
+    app: Sphinx, what: str, name: str, obj: Any, options: Options | None, lines: list[str]  # noqa: U100
+) -> None:
+    """Insert a : under Returns: to tell napoleon not to look for a return type."""
+    if what not in ["function", "method"]:
+        return
+    if not getattr(app.config, "napoleon_numpy_docstring", False):
+        return
+
+    # Search for the returns header:
+    # Returns:
+    # --------
+    for idx, line in enumerate(lines[:-2]):
+        if line.lower().strip(":") not in ["return", "returns"]:
+            continue
+        # Underline detection.
+        chars = set(lines[idx + 1].strip())
+        # Napoleon allows the underline to consist of a bunch of weirder things...
+        if len(chars) != 1 or list(chars)[0] not in "=-~_*+#":
+            continue
+        idx = idx + 2
+        break
+    else:
+        return
+
+    lines.insert(idx, ":")
+
+
+def fix_napoleon_numpy_docstring_return_type(app: Sphinx) -> None:
+    """
+    If no return type is explicitly provided, numpy docstrings will mess up and
+    use the return type text as return types.
+    """
+    # standard priority is 500. Setting priority to 499 ensures this runs before
+    # napoleon's docstring processor.
+    app.connect("autodoc-process-docstring", napoleon_numpy_docstring_return_type_processor, priority=499)
+
+
 def patched_lookup_annotation(*_args: Any) -> str:  # noqa: U101
     """GoogleDocstring._lookup_annotation sometimes adds incorrect type
     annotations to constructor parameters (and otherwise does nothing). Disable
@@ -818,6 +856,9 @@ def patched_lookup_annotation(*_args: Any) -> str:  # noqa: U101
 
 
 def patch_google_docstring_lookup_annotation() -> None:
+    """Fix issue 308:
+    https://github.com/tox-dev/sphinx-autodoc-typehints/issues/308
+    """
     GoogleDocstring._lookup_annotation = patched_lookup_annotation  # type: ignore[assignment]
 
 
@@ -837,6 +878,7 @@ def setup(app: Sphinx) -> dict[str, bool]:
     fix_autodoc_typehints_for_overloaded_methods()
     patch_attribute_handling(app)
     patch_google_docstring_lookup_annotation()
+    fix_napoleon_numpy_docstring_return_type(app)
     return {"parallel_read_safe": True}
 
 
diff --git a/whitelist.txt b/whitelist.txt
index 0ef2f94..7a2e21d 100644
--- a/whitelist.txt
+++ b/whitelist.txt
@@ -1,4 +1,6 @@
 addnodes
+arg1
+arg2
 ast3
 astext
 autodoc
@@ -14,6 +16,7 @@ delattr
 desc
 dirname
 docnames
+docstrings
 Documenter
 docutils
 dunder
@@ -41,6 +44,7 @@ metaclass
 ModuleType
 multiline
 newtype
+numpy
 nptyping
 param
 parametrized


Test patch:
diff --git a/tests/test_integration.py b/tests/test_integration.py
index b1d614e..0413022 100644
--- a/tests/test_integration.py
+++ b/tests/test_integration.py
@@ -941,6 +941,64 @@ class ParamAndAttributeHaveSameName:
     """Description of attribute blah"""
 
 
+@expected(
+    """
+    mod.napoleon_returns()
+
+       A function.
+
+       Return type:
+          "CodeType"
+
+       Returns:
+          The info about the whatever.
+    """
+)
+def napoleon_returns() -> CodeType:
+    """
+    A function.
+
+    Returns
+    -------
+        The info about the whatever.
+    """
+
+
+@expected(
+    """
+    mod.google_docstrings(arg1, arg2)
+
+       Summary line.
+
+       Extended description of function.
+
+       Parameters:
+          * **arg1** ("CodeType") -- Description of arg1
+
+          * **arg2** ("ModuleType") -- Description of arg2
+
+       Return type:
+          "CodeType"
+
+       Returns:
+          Description of return value
+
+    """
+)
+def google_docstrings(arg1: CodeType, arg2: ModuleType) -> CodeType:  # noqa: U100
+    """Summary line.
+
+    Extended description of function.
+
+    Args:
+        arg1: Description of arg1
+        arg2: Description of arg2
+
+    Returns:
+        Description of return value
+    """
+
+
 AUTO_FUNCTION = ".. autofunction:: mod.{}"
 AUTO_CLASS = """\
 .. autoclass:: mod.{}