Skip to content

Commit

Permalink
Merge pull request #9157 from tk0miya/8872_stacked_singledispatch
Browse files Browse the repository at this point in the history
Fix #8872: autodoc: stacked singledispatches are wrongly rendered
  • Loading branch information
tk0miya committed May 3, 2021
2 parents 05abdad + caa6579 commit cb7b41f
Show file tree
Hide file tree
Showing 6 changed files with 46 additions and 25 deletions.
2 changes: 2 additions & 0 deletions CHANGES
Expand Up @@ -25,6 +25,8 @@ Features added
Bugs fixed
----------

* #8872: autodoc: stacked singledispatches are wrongly rendered

Testing
--------

Expand Down
63 changes: 38 additions & 25 deletions sphinx/ext/autodoc/__init__.py
Expand Up @@ -1328,12 +1328,12 @@ def format_signature(self, **kwargs: Any) -> str:
if typ is object:
pass # default implementation. skipped.
else:
self.annotate_to_first_argument(func, typ)

documenter = FunctionDocumenter(self.directive, '')
documenter.object = func
documenter.objpath = [None]
sigs.append(documenter.format_signature())
dispatchfunc = self.annotate_to_first_argument(func, typ)
if dispatchfunc:
documenter = FunctionDocumenter(self.directive, '')
documenter.object = dispatchfunc
documenter.objpath = [None]
sigs.append(documenter.format_signature())
if overloaded:
actual = inspect.signature(self.object,
type_aliases=self.config.autodoc_type_aliases)
Expand All @@ -1358,28 +1358,34 @@ def merge_default_value(self, actual: Signature, overload: Signature) -> Signatu

return overload.replace(parameters=parameters)

def annotate_to_first_argument(self, func: Callable, typ: Type) -> None:
def annotate_to_first_argument(self, func: Callable, typ: Type) -> Optional[Callable]:
"""Annotate type hint to the first argument of function if needed."""
try:
sig = inspect.signature(func, type_aliases=self.config.autodoc_type_aliases)
except TypeError as exc:
logger.warning(__("Failed to get a function signature for %s: %s"),
self.fullname, exc)
return
return None
except ValueError:
return
return None

if len(sig.parameters) == 0:
return
return None

def dummy():
pass

params = list(sig.parameters.values())
if params[0].annotation is Parameter.empty:
params[0] = params[0].replace(annotation=typ)
try:
func.__signature__ = sig.replace(parameters=params) # type: ignore
dummy.__signature__ = sig.replace(parameters=params) # type: ignore
return dummy
except (AttributeError, TypeError):
# failed to update signature (ex. built-in or extension types)
return
return None
else:
return None


class DecoratorDocumenter(FunctionDocumenter):
Expand Down Expand Up @@ -2118,13 +2124,13 @@ def format_signature(self, **kwargs: Any) -> str:
if typ is object:
pass # default implementation. skipped.
else:
self.annotate_to_first_argument(func, typ)

documenter = MethodDocumenter(self.directive, '')
documenter.parent = self.parent
documenter.object = func
documenter.objpath = [None]
sigs.append(documenter.format_signature())
dispatchmeth = self.annotate_to_first_argument(func, typ)
if dispatchmeth:
documenter = MethodDocumenter(self.directive, '')
documenter.parent = self.parent
documenter.object = dispatchmeth
documenter.objpath = [None]
sigs.append(documenter.format_signature())
if overloaded:
if inspect.isstaticmethod(self.object, cls=self.parent, name=self.object_name):
actual = inspect.signature(self.object, bound_method=False,
Expand Down Expand Up @@ -2158,27 +2164,34 @@ def merge_default_value(self, actual: Signature, overload: Signature) -> Signatu

return overload.replace(parameters=parameters)

def annotate_to_first_argument(self, func: Callable, typ: Type) -> None:
def annotate_to_first_argument(self, func: Callable, typ: Type) -> Optional[Callable]:
"""Annotate type hint to the first argument of function if needed."""
try:
sig = inspect.signature(func, type_aliases=self.config.autodoc_type_aliases)
except TypeError as exc:
logger.warning(__("Failed to get a method signature for %s: %s"),
self.fullname, exc)
return
return None
except ValueError:
return
return None

if len(sig.parameters) == 1:
return
return None

def dummy():
pass

params = list(sig.parameters.values())
if params[1].annotation is Parameter.empty:
params[1] = params[1].replace(annotation=typ)
try:
func.__signature__ = sig.replace(parameters=params) # type: ignore
dummy.__signature__ = sig.replace(parameters=params) # type: ignore
return dummy
except (AttributeError, TypeError):
# failed to update signature (ex. built-in or extension types)
return
return None
else:
return None


class NonDataDescriptorMixin(DataDocumenterMixinBase):
Expand Down
1 change: 1 addition & 0 deletions tests/roots/test-ext-autodoc/target/singledispatch.py
Expand Up @@ -15,6 +15,7 @@ def func(arg, kwarg=None):


@func.register(int)
@func.register(float)
def _func_int(arg, kwarg=None):
"""A function for int."""
pass
Expand Down
Expand Up @@ -10,6 +10,7 @@ def meth(self, arg, kwarg=None):
pass

@meth.register(int)
@meth.register(float)
def _meth_int(self, arg, kwarg=None):
"""A method for int."""
pass
Expand Down
3 changes: 3 additions & 0 deletions tests/test_ext_autodoc.py
Expand Up @@ -2080,6 +2080,7 @@ def test_singledispatch(app):
'',
'',
'.. py:function:: func(arg, kwarg=None)',
' func(arg: float, kwarg=None)',
' func(arg: int, kwarg=None)',
' func(arg: str, kwarg=None)',
' :module: target.singledispatch',
Expand Down Expand Up @@ -2107,6 +2108,7 @@ def test_singledispatchmethod(app):
'',
'',
' .. py:method:: Foo.meth(arg, kwarg=None)',
' Foo.meth(arg: float, kwarg=None)',
' Foo.meth(arg: int, kwarg=None)',
' Foo.meth(arg: str, kwarg=None)',
' :module: target.singledispatchmethod',
Expand All @@ -2125,6 +2127,7 @@ def test_singledispatchmethod_automethod(app):
assert list(actual) == [
'',
'.. py:method:: Foo.meth(arg, kwarg=None)',
' Foo.meth(arg: float, kwarg=None)',
' Foo.meth(arg: int, kwarg=None)',
' Foo.meth(arg: str, kwarg=None)',
' :module: target.singledispatchmethod',
Expand Down
1 change: 1 addition & 0 deletions tests/test_ext_autodoc_autofunction.py
Expand Up @@ -119,6 +119,7 @@ def test_singledispatch(app):
assert list(actual) == [
'',
'.. py:function:: func(arg, kwarg=None)',
' func(arg: float, kwarg=None)',
' func(arg: int, kwarg=None)',
' func(arg: str, kwarg=None)',
' :module: target.singledispatch',
Expand Down

0 comments on commit cb7b41f

Please sign in to comment.