diff --git a/CHANGES.rst b/CHANGES.rst index ad6d698341a..4598ce153c3 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 returns a signature for a data or type object, which are documented + without one. The object was then rendered without its docstring. + Patch by Jhon Alvarez + 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..1a8563c84c6 100644 --- a/sphinx/ext/autodoc/_dynamic/_signatures.py +++ b/sphinx/ext/autodoc/_dynamic/_signatures.py @@ -125,7 +125,10 @@ def _format_signatures( ): if len(result) == 2 and isinstance(result[0], str): args, retann = result - signatures[0] = (args, retann if isinstance(retann, str) else '') + # Data and type objects skip signature extraction, so *signatures* + # may still be empty here. A handler is free to return a signature + # for them anyway, and slice assignment stores it either way. + signatures[:1] = [(args, retann if isinstance(retann, str) else '')] 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..cc1d6e696a5 100644 --- a/tests/test_ext_autodoc/test_ext_autodoc_signatures.py +++ b/tests/test_ext_autodoc/test_ext_autodoc_signatures.py @@ -289,6 +289,24 @@ 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_on_data() -> None: + # A data object skips signature extraction, so *signatures* is still empty + # when the event fires. A handler may return a signature for it anyway. + def process_signature(*args: Any) -> tuple[str, str | None]: + return '()', None + + events = FakeEvents() + events.connect('autodoc-process-signature', process_signature) + + class CallableData: + class_var: Any + + def __call__(self) -> None: + pass + + assert format_sig('data', 'sig_bug', CallableData(), events=events) == ('()', '') + + def test_format_functools_partial_signatures() -> None: # test functions created via functools.partial from functools import partial