Support overloaded constructors (__call__, __init__ and __new__)

This commit is contained in:
Takeshi KOMIYA 2020-06-01 02:08:57 +09:00
parent fb2f777079
commit 49de4ab1a1
3 changed files with 107 additions and 9 deletions

View File

@ -1279,6 +1279,9 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
'private-members': bool_option, 'special-members': members_option,
} # type: Dict[str, Callable]
_signature_class = None # type: Any
_signature_method_name = None # type: str
def __init__(self, *args: Any) -> None:
super().__init__(*args)
merge_special_members_option(self.options)
@ -1299,7 +1302,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
self.doc_as_attr = True
return ret
def _get_signature(self) -> Optional[Signature]:
def _get_signature(self) -> Tuple[Optional[Any], Optional[str], Optional[Signature]]:
def get_user_defined_function_or_method(obj: Any, attr: str) -> Any:
""" Get the `attr` function or method from `obj`, if it is user-defined. """
if inspect.is_builtin_class_method(obj, attr):
@ -1323,7 +1326,8 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
if call is not None:
self.env.app.emit('autodoc-before-process-signature', call, True)
try:
return inspect.signature(call, bound_method=True)
sig = inspect.signature(call, bound_method=True)
return type(self.object), '__call__', sig
except ValueError:
pass
@ -1332,7 +1336,8 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
if new is not None:
self.env.app.emit('autodoc-before-process-signature', new, True)
try:
return inspect.signature(new, bound_method=True)
sig = inspect.signature(new, bound_method=True)
return self.object, '__new__', sig
except ValueError:
pass
@ -1341,7 +1346,8 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
if init is not None:
self.env.app.emit('autodoc-before-process-signature', init, True)
try:
return inspect.signature(init, bound_method=True)
sig = inspect.signature(init, bound_method=True)
return self.object, '__init__', sig
except ValueError:
pass
@ -1351,20 +1357,21 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
# the signature from, so just pass the object itself to our hook.
self.env.app.emit('autodoc-before-process-signature', self.object, False)
try:
return inspect.signature(self.object, bound_method=False)
sig = inspect.signature(self.object, bound_method=False)
return None, None, sig
except ValueError:
pass
# Still no signature: happens e.g. for old-style classes
# with __init__ in C and no `__text_signature__`.
return None
return None, None, None
def format_args(self, **kwargs: Any) -> str:
if self.env.config.autodoc_typehints in ('none', 'description'):
kwargs.setdefault('show_annotation', False)
try:
sig = self._get_signature()
self._signature_class, self._signature_method_name, sig = self._get_signature()
except TypeError as exc:
# __signature__ attribute contained junk
logger.warning(__("Failed to get a constructor signature for %s: %s"),
@ -1380,7 +1387,30 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
if self.doc_as_attr:
return ''
return super().format_signature(**kwargs)
sig = super().format_signature()
overloaded = False
qualname = None
# TODO: recreate analyzer for the module of class (To be clear, owner of the method)
if self._signature_class and self._signature_method_name and self.analyzer:
qualname = '.'.join([self._signature_class.__qualname__,
self._signature_method_name])
if qualname in self.analyzer.overloads:
overloaded = True
sigs = []
if overloaded:
# Use signatures for overloaded methods instead of the implementation method.
for overload in self.analyzer.overloads.get(qualname):
parameters = list(overload.parameters.values())
overload = overload.replace(parameters=parameters[1:],
return_annotation=Parameter.empty)
sig = stringify_signature(overload, **kwargs)
sigs.append(sig)
else:
sigs.append(sig)
return "\n".join(sigs)
def add_directive_header(self, sig: str) -> None:
sourcename = self.get_sourcename()

View File

@ -1,4 +1,4 @@
from typing import overload
from typing import Any, overload
@overload
@ -39,3 +39,50 @@ class Math:
def sum(self, x, y):
"""docstring"""
return x + y
class Foo:
"""docstring"""
@overload
def __new__(cls, x: int, y: int) -> "Foo":
...
@overload
def __new__(cls, x: str, y: str) -> "Foo":
...
def __new__(cls, x, y):
pass
class Bar:
"""docstring"""
@overload
def __init__(cls, x: int, y: int) -> None:
...
@overload
def __init__(cls, x: str, y: str) -> None:
...
def __init__(cls, x, y):
pass
class Meta(type):
@overload
def __call__(cls, x: int, y: int) -> Any:
...
@overload
def __call__(cls, x: str, y: str) -> Any:
...
def __call__(cls, x, y):
pass
class Baz(metaclass=Meta):
"""docstring"""

View File

@ -1796,6 +1796,27 @@ def test_overload(app):
'.. py:module:: target.overload',
'',
'',
'.. py:class:: Bar(x: int, y: int)',
' Bar(x: str, y: str)',
' :module: target.overload',
'',
' docstring',
'',
'',
'.. py:class:: Baz(x: int, y: int)',
' Baz(x: str, y: str)',
' :module: target.overload',
'',
' docstring',
'',
'',
'.. py:class:: Foo(x: int, y: int)',
' Foo(x: str, y: str)',
' :module: target.overload',
'',
' docstring',
'',
'',
'.. py:class:: Math()',
' :module: target.overload',
'',