From 8b6d2300c3bd5f2e9f066c653d21e037aff19df7 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 17 Jan 2021 01:14:28 +0900 Subject: [PATCH] Close #8514: autodoc: Default values of overloads are taken from actual implementation As a well-known idiom, mypy recommends to use ellipsis ("...") for default argument values as a elided style. This allows to write the style and helps to document it with copying the default argument values from actual implementation. Note: This does not copy the default argument value when the argument of overloaded function has its own default value. --- CHANGES | 2 ++ sphinx/ext/autodoc/__init__.py | 31 +++++++++++++++++++ sphinx/util/inspect.py | 3 ++ .../roots/test-ext-autodoc/target/overload.py | 4 +-- tests/test_ext_autodoc.py | 4 +-- tests/test_ext_autodoc_configs.py | 4 +-- 6 files changed, 42 insertions(+), 6 deletions(-) diff --git a/CHANGES b/CHANGES index a289f5ebc96..5a7c980b1eb 100644 --- a/CHANGES +++ b/CHANGES @@ -20,6 +20,8 @@ Features added * #8022: autodoc: autodata and autoattribute directives does not show right-hand value of the variable if docstring contains ``:meta hide-value:`` in info-field-list +* #8514: autodoc: Default values of overloaded functions are taken from actual + implementation if they're ellipsis * #8619: html: kbd role generates customizable HTML tags for compound keys * #8634: html: Allow to change the order of JS/CSS via ``priority`` parameter for :meth:`Sphinx.add_js_file()` and :meth:`Sphinx.add_css_file()` diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index bf80ef4a832..83c7d28c498 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -1355,8 +1355,11 @@ def format_signature(self, **kwargs: Any) -> str: documenter.objpath = [None] sigs.append(documenter.format_signature()) if overloaded: + actual = inspect.signature(self.object, + type_aliases=self.config.autodoc_type_aliases) __globals__ = safe_getattr(self.object, '__globals__', {}) for overload in self.analyzer.overloads.get('.'.join(self.objpath)): + overload = self.merge_default_value(actual, overload) overload = evaluate_signature(overload, __globals__, self.config.autodoc_type_aliases) @@ -1365,6 +1368,16 @@ def format_signature(self, **kwargs: Any) -> str: return "\n".join(sigs) + def merge_default_value(self, actual: Signature, overload: Signature) -> Signature: + """Merge default values of actual implementation to the overload variants.""" + parameters = list(overload.parameters.values()) + for i, param in enumerate(parameters): + actual_param = actual.parameters.get(param.name) + if actual_param and param.default == '...': + parameters[i] = param.replace(default=actual_param.default) + + return overload.replace(parameters=parameters) + def annotate_to_first_argument(self, func: Callable, typ: Type) -> None: """Annotate type hint to the first argument of function if needed.""" try: @@ -2117,8 +2130,16 @@ def format_signature(self, **kwargs: Any) -> str: 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, + type_aliases=self.config.autodoc_type_aliases) + else: + actual = inspect.signature(self.object, bound_method=True, + type_aliases=self.config.autodoc_type_aliases) + __globals__ = safe_getattr(self.object, '__globals__', {}) for overload in self.analyzer.overloads.get('.'.join(self.objpath)): + overload = self.merge_default_value(actual, overload) overload = evaluate_signature(overload, __globals__, self.config.autodoc_type_aliases) @@ -2131,6 +2152,16 @@ def format_signature(self, **kwargs: Any) -> str: return "\n".join(sigs) + def merge_default_value(self, actual: Signature, overload: Signature) -> Signature: + """Merge default values of actual implementation to the overload variants.""" + parameters = list(overload.parameters.values()) + for i, param in enumerate(parameters): + actual_param = actual.parameters.get(param.name) + if actual_param and param.default == '...': + parameters[i] = param.replace(default=actual_param.default) + + return overload.replace(parameters=parameters) + def annotate_to_first_argument(self, func: Callable, typ: Type) -> None: """Annotate type hint to the first argument of function if needed.""" try: diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index ffd2ed5dc79..50333718f5d 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -508,6 +508,9 @@ def __init__(self, value: str) -> None: def __repr__(self) -> str: return self.value + def __eq__(self, other: object) -> bool: + return self.value == other + def _should_unwrap(subject: Callable) -> bool: """Check the function should be unwrapped on getting signature.""" diff --git a/tests/roots/test-ext-autodoc/target/overload.py b/tests/roots/test-ext-autodoc/target/overload.py index 35d078b666a..1b395ee5b34 100644 --- a/tests/roots/test-ext-autodoc/target/overload.py +++ b/tests/roots/test-ext-autodoc/target/overload.py @@ -16,7 +16,7 @@ def sum(x: str, y: str = ...) -> str: ... -def sum(x, y): +def sum(x, y=None): """docstring""" return x + y @@ -36,7 +36,7 @@ def sum(self, x: "float", y: "float" = 0.0) -> "float": def sum(self, x: str, y: str = ...) -> str: ... - def sum(self, x, y): + def sum(self, x, y=None): """docstring""" return x + y diff --git a/tests/test_ext_autodoc.py b/tests/test_ext_autodoc.py index 3ff33cea2f7..d555359cfce 100644 --- a/tests/test_ext_autodoc.py +++ b/tests/test_ext_autodoc.py @@ -2080,7 +2080,7 @@ def test_overload(app): '', ' .. py:method:: Math.sum(x: int, y: int = 0) -> int', ' Math.sum(x: float, y: float = 0.0) -> float', - ' Math.sum(x: str, y: str = ...) -> str', + ' Math.sum(x: str, y: str = None) -> str', ' :module: target.overload', '', ' docstring', @@ -2088,7 +2088,7 @@ def test_overload(app): '', '.. py:function:: sum(x: int, y: int = 0) -> int', ' sum(x: float, y: float = 0.0) -> float', - ' sum(x: str, y: str = ...) -> str', + ' sum(x: str, y: str = None) -> str', ' :module: target.overload', '', ' docstring', diff --git a/tests/test_ext_autodoc_configs.py b/tests/test_ext_autodoc_configs.py index 9cd5f5e32ed..bae684397b5 100644 --- a/tests/test_ext_autodoc_configs.py +++ b/tests/test_ext_autodoc_configs.py @@ -647,13 +647,13 @@ def test_autodoc_typehints_none_for_overload(app): ' docstring', '', '', - ' .. py:method:: Math.sum(x, y)', + ' .. py:method:: Math.sum(x, y=None)', ' :module: target.overload', '', ' docstring', '', '', - '.. py:function:: sum(x, y)', + '.. py:function:: sum(x, y=None)', ' :module: target.overload', '', ' docstring',