Skip to content
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

Autodoc type aliases in overloads #13337

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
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
52 changes: 26 additions & 26 deletions sphinx/ext/autodoc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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(
Expand All @@ -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(
Expand Down Expand Up @@ -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)

Expand All @@ -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)
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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)

Expand All @@ -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)
Expand Down
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
Expand Up @@ -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
Expand All @@ -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):
Expand All @@ -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
Expand Up @@ -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',
'',
Expand All @@ -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',
Expand Down Expand Up @@ -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)
Expand All @@ -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',
'',
Expand All @@ -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',
Expand All @@ -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',
Expand Down Expand Up @@ -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:
Expand All @@ -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'
)


Expand Down