mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Fix #8872: autodoc: stacked singledispatches are wrongly rendered
When multiple singledispatch decorators are stacked, the first typehints are copied to the subsequent definitions unexpectedly. Now autodoc generates a dummy function not to affect typehints to subsequent functions.
This commit is contained in:
parent
05abdad749
commit
caa6579dbd
2
CHANGES
2
CHANGES
@ -25,6 +25,8 @@ Features added
|
|||||||
Bugs fixed
|
Bugs fixed
|
||||||
----------
|
----------
|
||||||
|
|
||||||
|
* #8872: autodoc: stacked singledispatches are wrongly rendered
|
||||||
|
|
||||||
Testing
|
Testing
|
||||||
--------
|
--------
|
||||||
|
|
||||||
|
@ -1328,12 +1328,12 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ
|
|||||||
if typ is object:
|
if typ is object:
|
||||||
pass # default implementation. skipped.
|
pass # default implementation. skipped.
|
||||||
else:
|
else:
|
||||||
self.annotate_to_first_argument(func, typ)
|
dispatchfunc = self.annotate_to_first_argument(func, typ)
|
||||||
|
if dispatchfunc:
|
||||||
documenter = FunctionDocumenter(self.directive, '')
|
documenter = FunctionDocumenter(self.directive, '')
|
||||||
documenter.object = func
|
documenter.object = dispatchfunc
|
||||||
documenter.objpath = [None]
|
documenter.objpath = [None]
|
||||||
sigs.append(documenter.format_signature())
|
sigs.append(documenter.format_signature())
|
||||||
if overloaded:
|
if overloaded:
|
||||||
actual = inspect.signature(self.object,
|
actual = inspect.signature(self.object,
|
||||||
type_aliases=self.config.autodoc_type_aliases)
|
type_aliases=self.config.autodoc_type_aliases)
|
||||||
@ -1358,28 +1358,34 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ
|
|||||||
|
|
||||||
return overload.replace(parameters=parameters)
|
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."""
|
"""Annotate type hint to the first argument of function if needed."""
|
||||||
try:
|
try:
|
||||||
sig = inspect.signature(func, type_aliases=self.config.autodoc_type_aliases)
|
sig = inspect.signature(func, type_aliases=self.config.autodoc_type_aliases)
|
||||||
except TypeError as exc:
|
except TypeError as exc:
|
||||||
logger.warning(__("Failed to get a function signature for %s: %s"),
|
logger.warning(__("Failed to get a function signature for %s: %s"),
|
||||||
self.fullname, exc)
|
self.fullname, exc)
|
||||||
return
|
return None
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return
|
return None
|
||||||
|
|
||||||
if len(sig.parameters) == 0:
|
if len(sig.parameters) == 0:
|
||||||
return
|
return None
|
||||||
|
|
||||||
|
def dummy():
|
||||||
|
pass
|
||||||
|
|
||||||
params = list(sig.parameters.values())
|
params = list(sig.parameters.values())
|
||||||
if params[0].annotation is Parameter.empty:
|
if params[0].annotation is Parameter.empty:
|
||||||
params[0] = params[0].replace(annotation=typ)
|
params[0] = params[0].replace(annotation=typ)
|
||||||
try:
|
try:
|
||||||
func.__signature__ = sig.replace(parameters=params) # type: ignore
|
dummy.__signature__ = sig.replace(parameters=params) # type: ignore
|
||||||
|
return dummy
|
||||||
except (AttributeError, TypeError):
|
except (AttributeError, TypeError):
|
||||||
# failed to update signature (ex. built-in or extension types)
|
# failed to update signature (ex. built-in or extension types)
|
||||||
return
|
return None
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class DecoratorDocumenter(FunctionDocumenter):
|
class DecoratorDocumenter(FunctionDocumenter):
|
||||||
@ -2118,13 +2124,13 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
|
|||||||
if typ is object:
|
if typ is object:
|
||||||
pass # default implementation. skipped.
|
pass # default implementation. skipped.
|
||||||
else:
|
else:
|
||||||
self.annotate_to_first_argument(func, typ)
|
dispatchmeth = self.annotate_to_first_argument(func, typ)
|
||||||
|
if dispatchmeth:
|
||||||
documenter = MethodDocumenter(self.directive, '')
|
documenter = MethodDocumenter(self.directive, '')
|
||||||
documenter.parent = self.parent
|
documenter.parent = self.parent
|
||||||
documenter.object = func
|
documenter.object = dispatchmeth
|
||||||
documenter.objpath = [None]
|
documenter.objpath = [None]
|
||||||
sigs.append(documenter.format_signature())
|
sigs.append(documenter.format_signature())
|
||||||
if overloaded:
|
if overloaded:
|
||||||
if inspect.isstaticmethod(self.object, cls=self.parent, name=self.object_name):
|
if inspect.isstaticmethod(self.object, cls=self.parent, name=self.object_name):
|
||||||
actual = inspect.signature(self.object, bound_method=False,
|
actual = inspect.signature(self.object, bound_method=False,
|
||||||
@ -2158,27 +2164,34 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
|
|||||||
|
|
||||||
return overload.replace(parameters=parameters)
|
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."""
|
"""Annotate type hint to the first argument of function if needed."""
|
||||||
try:
|
try:
|
||||||
sig = inspect.signature(func, type_aliases=self.config.autodoc_type_aliases)
|
sig = inspect.signature(func, type_aliases=self.config.autodoc_type_aliases)
|
||||||
except TypeError as exc:
|
except TypeError as exc:
|
||||||
logger.warning(__("Failed to get a method signature for %s: %s"),
|
logger.warning(__("Failed to get a method signature for %s: %s"),
|
||||||
self.fullname, exc)
|
self.fullname, exc)
|
||||||
return
|
return None
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return
|
return None
|
||||||
|
|
||||||
if len(sig.parameters) == 1:
|
if len(sig.parameters) == 1:
|
||||||
return
|
return None
|
||||||
|
|
||||||
|
def dummy():
|
||||||
|
pass
|
||||||
|
|
||||||
params = list(sig.parameters.values())
|
params = list(sig.parameters.values())
|
||||||
if params[1].annotation is Parameter.empty:
|
if params[1].annotation is Parameter.empty:
|
||||||
params[1] = params[1].replace(annotation=typ)
|
params[1] = params[1].replace(annotation=typ)
|
||||||
try:
|
try:
|
||||||
func.__signature__ = sig.replace(parameters=params) # type: ignore
|
dummy.__signature__ = sig.replace(parameters=params) # type: ignore
|
||||||
|
return dummy
|
||||||
except (AttributeError, TypeError):
|
except (AttributeError, TypeError):
|
||||||
# failed to update signature (ex. built-in or extension types)
|
# failed to update signature (ex. built-in or extension types)
|
||||||
return
|
return None
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class NonDataDescriptorMixin(DataDocumenterMixinBase):
|
class NonDataDescriptorMixin(DataDocumenterMixinBase):
|
||||||
|
@ -15,6 +15,7 @@ def func(arg, kwarg=None):
|
|||||||
|
|
||||||
|
|
||||||
@func.register(int)
|
@func.register(int)
|
||||||
|
@func.register(float)
|
||||||
def _func_int(arg, kwarg=None):
|
def _func_int(arg, kwarg=None):
|
||||||
"""A function for int."""
|
"""A function for int."""
|
||||||
pass
|
pass
|
||||||
|
@ -10,6 +10,7 @@ class Foo:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
@meth.register(int)
|
@meth.register(int)
|
||||||
|
@meth.register(float)
|
||||||
def _meth_int(self, arg, kwarg=None):
|
def _meth_int(self, arg, kwarg=None):
|
||||||
"""A method for int."""
|
"""A method for int."""
|
||||||
pass
|
pass
|
||||||
|
@ -2080,6 +2080,7 @@ def test_singledispatch(app):
|
|||||||
'',
|
'',
|
||||||
'',
|
'',
|
||||||
'.. py:function:: func(arg, kwarg=None)',
|
'.. py:function:: func(arg, kwarg=None)',
|
||||||
|
' func(arg: float, kwarg=None)',
|
||||||
' func(arg: int, kwarg=None)',
|
' func(arg: int, kwarg=None)',
|
||||||
' func(arg: str, kwarg=None)',
|
' func(arg: str, kwarg=None)',
|
||||||
' :module: target.singledispatch',
|
' :module: target.singledispatch',
|
||||||
@ -2107,6 +2108,7 @@ def test_singledispatchmethod(app):
|
|||||||
'',
|
'',
|
||||||
'',
|
'',
|
||||||
' .. py:method:: Foo.meth(arg, kwarg=None)',
|
' .. py:method:: Foo.meth(arg, kwarg=None)',
|
||||||
|
' Foo.meth(arg: float, kwarg=None)',
|
||||||
' Foo.meth(arg: int, kwarg=None)',
|
' Foo.meth(arg: int, kwarg=None)',
|
||||||
' Foo.meth(arg: str, kwarg=None)',
|
' Foo.meth(arg: str, kwarg=None)',
|
||||||
' :module: target.singledispatchmethod',
|
' :module: target.singledispatchmethod',
|
||||||
@ -2125,6 +2127,7 @@ def test_singledispatchmethod_automethod(app):
|
|||||||
assert list(actual) == [
|
assert list(actual) == [
|
||||||
'',
|
'',
|
||||||
'.. py:method:: Foo.meth(arg, kwarg=None)',
|
'.. py:method:: Foo.meth(arg, kwarg=None)',
|
||||||
|
' Foo.meth(arg: float, kwarg=None)',
|
||||||
' Foo.meth(arg: int, kwarg=None)',
|
' Foo.meth(arg: int, kwarg=None)',
|
||||||
' Foo.meth(arg: str, kwarg=None)',
|
' Foo.meth(arg: str, kwarg=None)',
|
||||||
' :module: target.singledispatchmethod',
|
' :module: target.singledispatchmethod',
|
||||||
|
@ -119,6 +119,7 @@ def test_singledispatch(app):
|
|||||||
assert list(actual) == [
|
assert list(actual) == [
|
||||||
'',
|
'',
|
||||||
'.. py:function:: func(arg, kwarg=None)',
|
'.. py:function:: func(arg, kwarg=None)',
|
||||||
|
' func(arg: float, kwarg=None)',
|
||||||
' func(arg: int, kwarg=None)',
|
' func(arg: int, kwarg=None)',
|
||||||
' func(arg: str, kwarg=None)',
|
' func(arg: str, kwarg=None)',
|
||||||
' :module: target.singledispatch',
|
' :module: target.singledispatch',
|
||||||
|
Loading…
Reference in New Issue
Block a user