diff --git a/CHANGES.rst b/CHANGES.rst index ad6d698341a..03d4695f936 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -13,6 +13,11 @@ Bugs fixed English stemmer) and Dutch (which uses the Dutch Porter stemmer). Patch by Hugo van Kemenade +* #14576: autodoc: Fix ``IndexError`` when an ``autodoc-process-signature`` + handler supplies a signature for an object that has none to introspect, + as ``numpydoc`` does for signatures held only in the docstring. + Patch by Eric Larson + Release 9.1.0 (released Dec 31, 2025) ===================================== diff --git a/sphinx/ext/autodoc/_dynamic/_signatures.py b/sphinx/ext/autodoc/_dynamic/_signatures.py index de55c44fb9f..9f15430d77b 100644 --- a/sphinx/ext/autodoc/_dynamic/_signatures.py +++ b/sphinx/ext/autodoc/_dynamic/_signatures.py @@ -125,7 +125,13 @@ def _format_signatures( ): if len(result) == 2 and isinstance(result[0], str): args, retann = result - signatures[0] = (args, retann if isinstance(retann, str) else '') + entry = (args, retann if isinstance(retann, str) else '') + if signatures: + signatures[0] = entry + else: + # the object had no introspectable signature, but a handler + # supplied one (e.g. numpydoc reading it from the docstring) + signatures.append(entry) if props.obj_type in {'module', 'data', 'type'}: signatures[1:] = () # discard all signatures save the first diff --git a/tests/test_ext_autodoc/test_ext_autodoc_signatures.py b/tests/test_ext_autodoc/test_ext_autodoc_signatures.py index f3f87e4d38a..1913d4bdcff 100644 --- a/tests/test_ext_autodoc/test_ext_autodoc_signatures.py +++ b/tests/test_ext_autodoc/test_ext_autodoc_signatures.py @@ -289,6 +289,22 @@ def foo1(self, b, *c): # type: ignore[no-untyped-def] assert format_sig('method', 'bar', H.foo1, events=events) == ('42', '') +def test_format_signatures_event_handler_without_introspectable_signature() -> None: + # an object with no introspectable signature, whose signature is supplied by + # a handler instead (as numpydoc does, reading it from the docstring) + events = FakeEvents() + events.connect('autodoc-process-signature', _process_signature) + config = _AutodocConfig(autodoc_docstring_signature=False) + + # without the handler there is no signature at all + assert format_sig('function', 'int', int, config=config) == () + # the handler supplies one, which must not be discarded + assert format_sig('function', 'bar', int, config=config, events=events) == ( + '42', + '', + ) + + def test_format_functools_partial_signatures() -> None: # test functions created via functools.partial from functools import partial