Skip to content

Autodoc type aliases in overloads #13337

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

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
1 change: 1 addition & 0 deletions AUTHORS.rst
Original file line number Diff line number Diff line change
@@ -27,6 +27,7 @@ Contributors
* Andi Albrecht -- agogo theme
* Antonio Valentino -- qthelp builder, docstring inheritance
* Antti Kaihola -- doctest extension (skipif option)
* Barak Katzir -- autodoc improvements
* Barry Warsaw -- setup command improvements
* Bart Kamphorst -- warning improvements
* Ben Egan -- Napoleon improvements & viewcode improvements
6 changes: 6 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -33,6 +33,9 @@ Deprecated
Features added
--------------

* #10351, #10359: autodoc: overloaded function or method can now be customized in the
'autodoc-before-process-signature' and 'autodoc-process-signature' events.
Patch by Barak Katzir.
* #13173: Add a new ``duplicate_declaration`` warning type,
with ``duplicate_declaration.c`` and ``duplicate_declaration.cpp`` subtypes.
Patch by Julien Lecomte and Adam Turner.
@@ -115,6 +118,9 @@ Features added
Bugs fixed
----------

* #9813: autodoc: partial bugfix, :confval:`autodoc_type_aliases` is now supported
by overload signatures of functions and methods.
Patch by Barak Katzir.
* #12463: autosummary: Respect an empty module ``__all__``.
Patch by Valentin Pratz
* #13060: HTML Search: use ``Map`` to store per-file term scores.
52 changes: 26 additions & 26 deletions sphinx/ext/autodoc/__init__.py
Original file line number Diff line number Diff line change
@@ -12,7 +12,7 @@
import re
import sys
from inspect import Parameter, Signature
from typing import TYPE_CHECKING, Any, NewType, TypeVar
from typing import TYPE_CHECKING, Any, NewType, TypeVar, get_overloads

from docutils.statemachine import StringList

@@ -1473,6 +1473,7 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ

objtype = 'function'
member_order = 30
overload_impl_sig: Signature | None = None

@classmethod
def can_document_member(
@@ -1498,6 +1499,8 @@ def format_args(self, **kwargs: Any) -> str:
sig = inspect.signature(
self.object, type_aliases=self.config.autodoc_type_aliases
)
if self.overload_impl_sig is not None:
sig = self.merge_default_value(self.overload_impl_sig, sig)
args = stringify_signature(sig, **kwargs)
except TypeError as exc:
logger.warning(
@@ -1559,15 +1562,14 @@ def format_signature(self, **kwargs: Any) -> str:
actual = inspect.signature(
self.object, type_aliases=self.config.autodoc_type_aliases
)
__globals__ = safe_getattr(self.object, '__globals__', {})
for overload in self.analyzer.overloads['.'.join(self.objpath)]:
overload = self.merge_default_value(actual, overload)
overload = evaluate_signature(
overload, __globals__, self.config.autodoc_type_aliases
)

sig = stringify_signature(overload, **kwargs)
sigs.append(sig)
overload_kwargs = kwargs | {'show_annotation': True}
for overload_func in get_overloads(self.object):
documenter = type(self)(self.directive, '')
documenter.object = overload_func
documenter.objpath = ['']
# pass actual implementation signature to merge default values later
documenter.overload_impl_sig = actual
sigs.append(documenter.format_signature(**overload_kwargs))

return '\n'.join(sigs)

@@ -1576,7 +1578,7 @@ def merge_default_value(self, actual: Signature, overload: Signature) -> Signatu
parameters = list(overload.parameters.values())
for i, param in enumerate(parameters):
actual_param = actual.parameters.get(param.name)
if actual_param and param.default == '...':
if actual_param and param.default in {'...', ...}:
parameters[i] = param.replace(default=actual_param.default)

return overload.replace(parameters=parameters)
@@ -2390,6 +2392,7 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
directivetype = 'method'
member_order = 50
priority = 1 # must be more than FunctionDocumenter
overload_impl_sig: Signature | None = None

@classmethod
def can_document_member(
@@ -2449,6 +2452,8 @@ def format_args(self, **kwargs: Any) -> str:
bound_method=True,
type_aliases=self.config.autodoc_type_aliases,
)
if self.overload_impl_sig is not None:
sig = self.merge_default_value(self.overload_impl_sig, sig)
args = stringify_signature(sig, **kwargs)
except TypeError as exc:
logger.warning(
@@ -2537,20 +2542,15 @@ def format_signature(self, **kwargs: Any) -> str:
type_aliases=self.config.autodoc_type_aliases,
)

__globals__ = safe_getattr(self.object, '__globals__', {})
for overload in self.analyzer.overloads['.'.join(self.objpath)]:
overload = self.merge_default_value(actual, overload)
overload = evaluate_signature(
overload, __globals__, self.config.autodoc_type_aliases
)

if not inspect.isstaticmethod(
self.object, cls=self.parent, name=self.object_name
):
parameters = list(overload.parameters.values())
overload = overload.replace(parameters=parameters[1:])
sig = stringify_signature(overload, **kwargs)
sigs.append(sig)
overload_kwargs = kwargs | {'show_annotation': True}
for overload_func in get_overloads(self.object):
documenter = type(self)(self.directive, '')
documenter.object = overload_func
documenter.objpath = ['']
documenter.parent = self.parent
# pass actual implementation signature to merge default values later
documenter.overload_impl_sig = actual
sigs.append(documenter.format_signature(**overload_kwargs))

return '\n'.join(sigs)

@@ -2559,7 +2559,7 @@ def merge_default_value(self, actual: Signature, overload: Signature) -> Signatu
parameters = list(overload.parameters.values())
for i, param in enumerate(parameters):
actual_param = actual.parameters.get(param.name)
if actual_param and param.default == '...':
if actual_param and param.default in {'...', Ellipsis}:
parameters[i] = param.replace(default=actual_param.default)

return overload.replace(parameters=parameters)
50 changes: 45 additions & 5 deletions tests/roots/test-ext-autodoc/target/autodoc_type_aliases.py
Original file line number Diff line number Diff line change
@@ -4,9 +4,11 @@
from typing import TYPE_CHECKING, overload

if TYPE_CHECKING:
from typing import Optional
import fractions as frac
from typing import Optional, Union

myint = int
myfrac = float

#: docstring
variable: myint
@@ -22,17 +24,17 @@ def read(r: io.BytesIO) -> io.StringIO:
"""docstring"""


def sum(x: myint, y: myint) -> myint:
def sum(x: myfrac, y: myfrac) -> myfrac:
"""docstring"""
return x + y


@overload
def mult(x: myint, y: myint) -> myint: ...
def mult(x: int, y: int) -> int: ...


@overload
def mult(x: float, y: float) -> float: ...
def mult(x: myfrac, y: myfrac) -> myfrac: ...


def mult(x, y):
@@ -44,7 +46,45 @@ class Foo:
"""docstring"""

#: docstring
attr1: myint
attr1: Union[frac.Fraction, myint] # NoQA: UP007

def __init__(self):
self.attr2: myint = None #: docstring

def method1(self, x: Union[frac.Fraction, myfrac]) -> Union[frac.Fraction, myfrac]: # NoQA: UP007
"""docstring"""
return self.attr1 * x

@overload
def method2(self, x: frac.Fraction) -> frac.Fraction: ...

@overload
def method2(self, x: myfrac) -> myfrac: ...

@overload
def method2(
self,
x: Union[frac.Fraction, myfrac], # NoQA: UP007
) -> Union[frac.Fraction, myfrac]: ... # NoQA: UP007

def method2(self, x):
"""docstring"""
return self.attr2 * x


@overload
def prod(x: tuple[float, myfrac]) -> float: ...


@overload
def prod(x: tuple[frac.Fraction, myfrac]) -> frac.Fraction: ...


def prod(x):
"""docstring"""
return x[0] * x[1]


def print_value(x: Union[frac.Fraction, myfrac]) -> None: # NoQA: UP007
"""docstring"""
print('value:', x)
75 changes: 65 additions & 10 deletions tests/test_extensions/test_ext_autodoc_configs.py
Original file line number Diff line number Diff line change
@@ -1361,7 +1361,7 @@ def test_autodoc_type_aliases(app: SphinxTestApp) -> None:
'',
' .. py:attribute:: Foo.attr1',
' :module: target.autodoc_type_aliases',
' :type: int',
' :type: ~fractions.Fraction | int',
'',
' docstring',
'',
@@ -1373,20 +1373,47 @@ def test_autodoc_type_aliases(app: SphinxTestApp) -> None:
' docstring',
'',
'',
' .. py:method:: Foo.method1(x: ~fractions.Fraction | float) -> ~fractions.Fraction | float',
' :module: target.autodoc_type_aliases',
'',
' docstring',
'',
'',
' .. py:method:: Foo.method2(x: ~fractions.Fraction) -> ~fractions.Fraction',
' Foo.method2(x: float) -> float',
' Foo.method2(x: ~fractions.Fraction | float) -> ~fractions.Fraction | float',
' :module: target.autodoc_type_aliases',
'',
' docstring',
'',
'',
'.. py:function:: mult(x: int, y: int) -> int',
' mult(x: float, y: float) -> float',
' :module: target.autodoc_type_aliases',
'',
' docstring',
'',
'',
'.. py:function:: print_value(x: ~fractions.Fraction | float) -> None',
' :module: target.autodoc_type_aliases',
'',
' docstring',
'',
'',
'.. py:function:: prod(x: tuple[float, float]) -> float',
' prod(x: tuple[~fractions.Fraction, float]) -> ~fractions.Fraction',
' :module: target.autodoc_type_aliases',
'',
' docstring',
'',
'',
'.. py:function:: read(r: ~io.BytesIO) -> ~io.StringIO',
' :module: target.autodoc_type_aliases',
'',
' docstring',
'',
'',
'.. py:function:: sum(x: int, y: int) -> int',
'.. py:function:: sum(x: float, y: float) -> float',
' :module: target.autodoc_type_aliases',
'',
' docstring',
@@ -1418,6 +1445,7 @@ def test_autodoc_type_aliases(app: SphinxTestApp) -> None:
# define aliases
app.config.autodoc_type_aliases = {
'myint': 'myint',
'myfrac': 'my.module.myfrac',
'io.StringIO': 'my.module.StringIO',
}
actual = do_autodoc(app, 'module', 'target.autodoc_type_aliases', options)
@@ -1434,7 +1462,7 @@ def test_autodoc_type_aliases(app: SphinxTestApp) -> None:
'',
' .. py:attribute:: Foo.attr1',
' :module: target.autodoc_type_aliases',
' :type: myint',
" :type: ~fractions.Fraction | TypeAliasForwardRef('myint')",
'',
' docstring',
'',
@@ -1446,8 +1474,35 @@ def test_autodoc_type_aliases(app: SphinxTestApp) -> None:
' docstring',
'',
'',
'.. py:function:: mult(x: myint, y: myint) -> myint',
' mult(x: float, y: float) -> float',
" .. py:method:: Foo.method1(x: ~fractions.Fraction | TypeAliasForwardRef('my.module.myfrac')) -> ~fractions.Fraction | TypeAliasForwardRef('my.module.myfrac')",
' :module: target.autodoc_type_aliases',
'',
' docstring',
'',
'',
' .. py:method:: Foo.method2(x: ~fractions.Fraction) -> ~fractions.Fraction',
' Foo.method2(x: my.module.myfrac) -> my.module.myfrac',
" Foo.method2(x: ~fractions.Fraction | TypeAliasForwardRef('my.module.myfrac')) -> ~fractions.Fraction | TypeAliasForwardRef('my.module.myfrac')",
' :module: target.autodoc_type_aliases',
'',
' docstring',
'',
'',
'.. py:function:: mult(x: int, y: int) -> int',
' mult(x: my.module.myfrac, y: my.module.myfrac) -> my.module.myfrac',
' :module: target.autodoc_type_aliases',
'',
' docstring',
'',
'',
".. py:function:: print_value(x: ~fractions.Fraction | TypeAliasForwardRef('my.module.myfrac')) -> None",
' :module: target.autodoc_type_aliases',
'',
' docstring',
'',
'',
".. py:function:: prod(x: tuple[float, TypeAliasForwardRef('my.module.myfrac')]) -> float",
" prod(x: tuple[~fractions.Fraction, TypeAliasForwardRef('my.module.myfrac')]) -> ~fractions.Fraction",
' :module: target.autodoc_type_aliases',
'',
' docstring',
@@ -1459,7 +1514,7 @@ def test_autodoc_type_aliases(app: SphinxTestApp) -> None:
' docstring',
'',
'',
'.. py:function:: sum(x: myint, y: myint) -> myint',
'.. py:function:: sum(x: my.module.myfrac, y: my.module.myfrac) -> my.module.myfrac',
' :module: target.autodoc_type_aliases',
'',
' docstring',
@@ -1495,7 +1550,7 @@ def test_autodoc_type_aliases(app: SphinxTestApp) -> None:
srcdir='autodoc_typehints_description_and_type_aliases',
confoverrides={
'autodoc_typehints': 'description',
'autodoc_type_aliases': {'myint': 'myint'},
'autodoc_type_aliases': {'myfrac': 'my.module.myfrac'},
},
)
def test_autodoc_typehints_description_and_type_aliases(app: SphinxTestApp) -> None:
@@ -1511,12 +1566,12 @@ def test_autodoc_typehints_description_and_type_aliases(app: SphinxTestApp) -> N
' docstring\n'
'\n'
' Parameters:\n'
' * **x** (*myint*)\n'
' * **x** (*my.module.myfrac*)\n'
'\n'
' * **y** (*myint*)\n'
' * **y** (*my.module.myfrac*)\n'
'\n'
' Return type:\n'
' myint\n'
' my.module.myfrac\n'
)