Skip to content

Autodoc: Support typing_extensions.overload #13509

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
May 20, 2025
3 changes: 3 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ Features added
* #13497: Support C domain objects in the table of contents.
* #13535: html search: Update to the latest version of Snowball (v3.0.1).
Patch by Adam Turner.
* #13704: autodoc: Detect :py:func:`typing_extensions.overload <typing.overload>`
and :py:func:`~typing.final` decorators.
Patch by Spencer Brown.

Bugs fixed
----------
Expand Down
42 changes: 19 additions & 23 deletions sphinx/pycode/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,9 +247,9 @@ def __init__(self, buffers: list[str], encoding: str) -> None:
self.deforders: dict[str, int] = {}
self.finals: list[str] = []
self.overloads: dict[str, list[Signature]] = {}
self.typing: str | None = None
self.typing_final: str | None = None
self.typing_overload: str | None = None
self.typing_mods: set[str] = set()
self.typing_final_names: set[str] = set()
self.typing_overload_names: set[str] = set()
super().__init__()

def get_qualname_for(self, name: str) -> list[str] | None:
Expand Down Expand Up @@ -295,11 +295,8 @@ def add_variable_annotation(self, name: str, annotation: ast.AST) -> None:
self.annotations[basename, name] = ast_unparse(annotation)

def is_final(self, decorators: list[ast.expr]) -> bool:
final = []
if self.typing:
final.append('%s.final' % self.typing)
if self.typing_final:
final.append(self.typing_final)
final = {f'{modname}.final' for modname in self.typing_mods}
final |= self.typing_final_names

for decorator in decorators:
try:
Expand All @@ -311,11 +308,8 @@ def is_final(self, decorators: list[ast.expr]) -> bool:
return False

def is_overload(self, decorators: list[ast.expr]) -> bool:
overload = []
if self.typing:
overload.append('%s.overload' % self.typing)
if self.typing_overload:
overload.append(self.typing_overload)
overload = {f'{modname}.overload' for modname in self.typing_mods}
overload |= self.typing_overload_names

for decorator in decorators:
try:
Expand Down Expand Up @@ -348,22 +342,24 @@ def visit_Import(self, node: ast.Import) -> None:
for name in node.names:
self.add_entry(name.asname or name.name)

if name.name == 'typing':
self.typing = name.asname or name.name
elif name.name == 'typing.final':
self.typing_final = name.asname or name.name
elif name.name == 'typing.overload':
self.typing_overload = name.asname or name.name
if name.name in {'typing', 'typing_extensions'}:
self.typing_mods.add(name.asname or name.name)
elif name.name in {'typing.final', 'typing_extensions.final'}:
self.typing_final_names.add(name.asname or name.name)
elif name.name in {'typing.overload', 'typing_extensions.overload'}:
self.typing_overload_names.add(name.asname or name.name)

def visit_ImportFrom(self, node: ast.ImportFrom) -> None:
"""Handles Import node and record the order of definitions."""
for name in node.names:
self.add_entry(name.asname or name.name)

if node.module == 'typing' and name.name == 'final':
self.typing_final = name.asname or name.name
elif node.module == 'typing' and name.name == 'overload':
self.typing_overload = name.asname or name.name
if node.module not in {'typing', 'typing_extensions'}:
continue
if name.name == 'final':
self.typing_final_names.add(name.asname or name.name)
elif name.name == 'overload':
self.typing_overload_names.add(name.asname or name.name)

def visit_Assign(self, node: ast.Assign) -> None:
"""Handles Assign node and pick up a variable comment."""
Expand Down
11 changes: 11 additions & 0 deletions tests/roots/test-ext-autodoc/target/final.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
import typing
from typing import final

import typing_extensions
from typing_extensions import final as final_ext # noqa: UP035


@typing.final
class Class:
Expand All @@ -14,3 +17,11 @@ def meth1(self):

def meth2(self):
"""docstring"""

@final_ext
def meth3(self):
"""docstring"""

@typing_extensions.final
def meth4(self):
"""docstring"""
18 changes: 18 additions & 0 deletions tests/roots/test-ext-autodoc/target/overload3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import typing
from typing import TYPE_CHECKING, overload

import typing_extensions
from typing_extensions import overload as over_ext # noqa: UP035


@overload
def test(x: int) -> int: ...
@typing.overload
def test(x: list[int]) -> list[int]: ...
@over_ext
def test(x: str) -> str: ...
@typing_extensions.overload
def test(x: float) -> float: ...
def test(x):
"""Documentation."""
return x
34 changes: 34 additions & 0 deletions tests/test_extensions/test_ext_autodoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -2823,6 +2823,20 @@ def test_final(app):
'',
' docstring',
'',
'',
' .. py:method:: Class.meth3()',
' :module: target.final',
' :final:',
'',
' docstring',
'',
'',
' .. py:method:: Class.meth4()',
' :module: target.final',
' :final:',
'',
' docstring',
'',
]


Expand Down Expand Up @@ -2896,6 +2910,26 @@ def test_overload2(app):
]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_overload3(app):
options = {'members': None}
actual = do_autodoc(app, 'module', 'target.overload3', options)
assert list(actual) == [
'',
'.. py:module:: target.overload3',
'',
'',
'.. py:function:: test(x: int) -> int',
' test(x: list[int]) -> list[int]',
' test(x: str) -> str',
' test(x: float) -> float',
' :module: target.overload3',
'',
' Documentation.',
'',
]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_pymodule_for_ModuleLevelDocumenter(app):
app.env.ref_context['py:module'] = 'target.classes'
Expand Down
Loading