From db648761eebeea7ef1b7864d871e1e65216f2918 Mon Sep 17 00:00:00 2001 From: "Aryan Singh K." <70511529+aryansk@users.noreply.github.com> Date: Sun, 16 Aug 2026 09:59:02 +0530 Subject: [PATCH] autodoc: fix crash when a signature handler returns a signature for a data object Data objects never get a signature, but an ``autodoc-process-signature`` event handler may still return a replacement one (e.g. for a callable data object). The handler's result was assigned to ``signatures[0]`` on an empty list, raising ``list assignment index out of range`` and dropping the object's docstring. Append the replacement when no signature exists. Fixes #14576. --- CHANGES.rst | 5 +++++ sphinx/ext/autodoc/_dynamic/_signatures.py | 9 ++++++++- .../test_ext_autodoc_signatures.py | 20 ++++++++++++++++++- 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index ad6d698341a..83545b95d89 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 a crash (``list assignment index out of range``) + when an ``autodoc-process-signature`` event handler returns a + replacement signature for an object without one (e.g. a callable + data object). + 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..38075f23567 100644 --- a/sphinx/ext/autodoc/_dynamic/_signatures.py +++ b/sphinx/ext/autodoc/_dynamic/_signatures.py @@ -125,7 +125,14 @@ def _format_signatures( ): if len(result) == 2 and isinstance(result[0], str): args, retann = result - signatures[0] = (args, retann if isinstance(retann, str) else '') + signature = (args, retann if isinstance(retann, str) else '') + if signatures: + signatures[0] = signature + else: + # Some objects (e.g. data objects) never have a signature, but + # an event listener may still provide a replacement one. + # (https://github.com/sphinx-doc/sphinx/issues/14576) + signatures.append(signature) 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..27333cca16e 100644 --- a/tests/test_ext_autodoc/test_ext_autodoc_signatures.py +++ b/tests/test_ext_autodoc/test_ext_autodoc_signatures.py @@ -16,7 +16,7 @@ from sphinx.ext.autodoc._shared import _AutodocConfig from sphinx.util.inspect import safe_getattr -from tests.test_ext_autodoc.autodoc_util import FakeEvents +from tests.test_ext_autodoc.autodoc_util import FakeEvents, do_autodoc TYPE_CHECKING = False if TYPE_CHECKING: @@ -289,6 +289,24 @@ def foo1(self, b, *c): # type: ignore[no-untyped-def] assert format_sig('method', 'bar', H.foo1, events=events) == ('42', '') +@pytest.mark.usefixtures('inject_autodoc_root_into_sys_path') +def test_data_object_signature_from_event_handler() -> None: + # https://github.com/sphinx-doc/sphinx/issues/14576 + # Data objects never get a signature, but an ``autodoc-process-signature`` + # listener may still provide a replacement one. That replacement must be + # appended rather than crashing on ``signatures[0]`` assignment. + def process_signature(app, what, name, obj, options, args, retann): + if what == 'data': + return '()', None + return None + + events = FakeEvents() + events.connect('autodoc-process-signature', process_signature) + + content = do_autodoc('data', 'target.callable.function', events=events) + assert '.. py:data:: function()' in content + + def test_format_functools_partial_signatures() -> None: # test functions created via functools.partial from functools import partial