diff options
author | Takeshi KOMIYA <i.tkomiya@gmail.com> | 2020-05-31 20:08:57 +0300 |
---|---|---|
committer | Takeshi KOMIYA <i.tkomiya@gmail.com> | 2020-06-01 18:44:07 +0300 |
commit | 49de4ab1a16664ebb343d353cf5d54d7dbd32318 (patch) | |
tree | d3fcff215d6e4dcb88c9a5eff410bd7b2d9d53f9 | |
parent | fb2f777079506a6ed36077ae98c2b267d25b16b5 (diff) |
Support overloaded constructors (__call__, __init__ and __new__)
-rw-r--r-- | sphinx/ext/autodoc/__init__.py | 46 | ||||
-rw-r--r-- | tests/roots/test-ext-autodoc/target/overload.py | 49 | ||||
-rw-r--r-- | tests/test_ext_autodoc.py | 21 |
3 files changed, 107 insertions, 9 deletions
diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index 4fb6a4ce9..31301e01e 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -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() diff --git a/tests/roots/test-ext-autodoc/target/overload.py b/tests/roots/test-ext-autodoc/target/overload.py index 87d1c8533..da43d32eb 100644 --- a/tests/roots/test-ext-autodoc/target/overload.py +++ b/tests/roots/test-ext-autodoc/target/overload.py @@ -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""" diff --git a/tests/test_ext_autodoc.py b/tests/test_ext_autodoc.py index 0a651950b..e80e8b357 100644 --- a/tests/test_ext_autodoc.py +++ b/tests/test_ext_autodoc.py @@ -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', '', |