mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Fix #7901: autodoc: annotations for overloaded functions are not resolved
So far, type annotations for overloaded functions are not resolved because they are obtained from AST directly. This tries to evaluate them using a context of its function or method.
This commit is contained in:
parent
98a854deca
commit
916cd4c844
1
CHANGES
1
CHANGES
@ -25,6 +25,7 @@ Bugs fixed
|
||||
----------
|
||||
|
||||
* #7886: autodoc: TypeError is raised on mocking generic-typed classes
|
||||
* #7901: autodoc: type annotations for overloaded functions are not resolved
|
||||
* #7839: autosummary: cannot handle umlauts in function names
|
||||
* #7865: autosummary: Failed to extract summary line when abbreviations found
|
||||
* #7866: autosummary: Failed to extract correct summary line when docstring
|
||||
|
@ -33,7 +33,9 @@ from sphinx.pycode import ModuleAnalyzer, PycodeError
|
||||
from sphinx.util import inspect
|
||||
from sphinx.util import logging
|
||||
from sphinx.util.docstrings import extract_metadata, prepare_docstring
|
||||
from sphinx.util.inspect import getdoc, object_description, safe_getattr, stringify_signature
|
||||
from sphinx.util.inspect import (
|
||||
evaluate_signature, getdoc, object_description, safe_getattr, stringify_signature
|
||||
)
|
||||
from sphinx.util.typing import stringify as stringify_typehint
|
||||
|
||||
if False:
|
||||
@ -1204,7 +1206,9 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ
|
||||
documenter.objpath = [None]
|
||||
sigs.append(documenter.format_signature())
|
||||
if overloaded:
|
||||
__globals__ = safe_getattr(self.object, '__globals__', {})
|
||||
for overload in self.analyzer.overloads.get('.'.join(self.objpath)):
|
||||
overload = evaluate_signature(overload, __globals__)
|
||||
sig = stringify_signature(overload, **kwargs)
|
||||
sigs.append(sig)
|
||||
|
||||
@ -1403,7 +1407,11 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
|
||||
sigs = []
|
||||
if overloaded:
|
||||
# Use signatures for overloaded methods instead of the implementation method.
|
||||
method = safe_getattr(self._signature_class, self._signature_method_name, None)
|
||||
__globals__ = safe_getattr(method, '__globals__', {})
|
||||
for overload in self.analyzer.overloads.get(qualname):
|
||||
overload = evaluate_signature(overload, __globals__)
|
||||
|
||||
parameters = list(overload.parameters.values())
|
||||
overload = overload.replace(parameters=parameters[1:],
|
||||
return_annotation=Parameter.empty)
|
||||
@ -1796,7 +1804,9 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
|
||||
documenter.objpath = [None]
|
||||
sigs.append(documenter.format_signature())
|
||||
if overloaded:
|
||||
__globals__ = safe_getattr(self.object, '__globals__', {})
|
||||
for overload in self.analyzer.overloads.get('.'.join(self.objpath)):
|
||||
overload = evaluate_signature(overload, __globals__)
|
||||
if not inspect.isstaticmethod(self.object, cls=self.parent,
|
||||
name=self.object_name):
|
||||
parameters = list(overload.parameters.values())
|
||||
|
@ -22,13 +22,14 @@ from inspect import ( # NOQA
|
||||
Parameter, isclass, ismethod, ismethoddescriptor, ismodule
|
||||
)
|
||||
from io import StringIO
|
||||
from typing import Any, Callable, Mapping, List, Optional, Tuple
|
||||
from typing import Any, Callable, Dict, Mapping, List, Optional, Tuple
|
||||
from typing import cast
|
||||
|
||||
from sphinx.deprecation import RemovedInSphinx40Warning, RemovedInSphinx50Warning
|
||||
from sphinx.pycode.ast import ast # for py35-37
|
||||
from sphinx.pycode.ast import unparse as ast_unparse
|
||||
from sphinx.util import logging
|
||||
from sphinx.util.typing import ForwardRef
|
||||
from sphinx.util.typing import stringify as stringify_annotation
|
||||
|
||||
if sys.version_info > (3, 7):
|
||||
@ -487,6 +488,46 @@ def signature(subject: Callable, bound_method: bool = False, follow_wrapped: boo
|
||||
return inspect.Signature(parameters, return_annotation=return_annotation)
|
||||
|
||||
|
||||
def evaluate_signature(sig: inspect.Signature, globalns: Dict = None, localns: Dict = None
|
||||
) -> inspect.Signature:
|
||||
"""Evaluate unresolved type annotations in a signature object."""
|
||||
def evaluate(annotation: Any, globalns: Dict, localns: Dict) -> Any:
|
||||
"""Evaluate unresolved type annotation."""
|
||||
try:
|
||||
if isinstance(annotation, str):
|
||||
ref = ForwardRef(annotation, True)
|
||||
annotation = ref._evaluate(globalns, localns)
|
||||
|
||||
if isinstance(annotation, ForwardRef):
|
||||
annotation = annotation._evaluate(globalns, localns)
|
||||
elif isinstance(annotation, str):
|
||||
# might be a ForwardRef'ed annotation in overloaded functions
|
||||
ref = ForwardRef(annotation, True)
|
||||
annotation = ref._evaluate(globalns, localns)
|
||||
except (NameError, TypeError):
|
||||
# failed to evaluate type. skipped.
|
||||
pass
|
||||
|
||||
return annotation
|
||||
|
||||
if globalns is None:
|
||||
globalns = {}
|
||||
if localns is None:
|
||||
localns = globalns
|
||||
|
||||
parameters = list(sig.parameters.values())
|
||||
for i, param in enumerate(parameters):
|
||||
if param.annotation:
|
||||
annotation = evaluate(param.annotation, globalns, localns)
|
||||
parameters[i] = param.replace(annotation=annotation)
|
||||
|
||||
return_annotation = sig.return_annotation
|
||||
if return_annotation:
|
||||
return_annotation = evaluate(return_annotation, globalns, localns)
|
||||
|
||||
return sig.replace(parameters=parameters, return_annotation=return_annotation)
|
||||
|
||||
|
||||
def stringify_signature(sig: inspect.Signature, show_annotation: bool = True,
|
||||
show_return_annotation: bool = True) -> str:
|
||||
"""Stringify a Signature object.
|
||||
|
@ -16,6 +16,21 @@ from docutils import nodes
|
||||
from docutils.parsers.rst.states import Inliner
|
||||
|
||||
|
||||
if sys.version_info > (3, 7):
|
||||
from typing import ForwardRef
|
||||
else:
|
||||
from typing import _ForwardRef # type: ignore
|
||||
|
||||
class ForwardRef:
|
||||
"""A pseudo ForwardRef class for py35 and py36."""
|
||||
def __init__(self, arg: Any, is_argument: bool = True) -> None:
|
||||
self.arg = arg
|
||||
|
||||
def _evaluate(self, globalns: Dict, localns: Dict) -> Any:
|
||||
ref = _ForwardRef(self.arg)
|
||||
return ref._eval_type(globalns, localns)
|
||||
|
||||
|
||||
# An entry of Directive.option_spec
|
||||
DirectiveOption = Callable[[str], Any]
|
||||
|
||||
|
@ -7,7 +7,7 @@ def sum(x: int, y: int) -> int:
|
||||
|
||||
|
||||
@overload
|
||||
def sum(x: float, y: float) -> float:
|
||||
def sum(x: "float", y: "float") -> "float":
|
||||
...
|
||||
|
||||
|
||||
@ -29,7 +29,7 @@ class Math:
|
||||
...
|
||||
|
||||
@overload
|
||||
def sum(self, x: float, y: float) -> float:
|
||||
def sum(self, x: "float", y: "float") -> "float":
|
||||
...
|
||||
|
||||
@overload
|
||||
@ -49,7 +49,7 @@ class Foo:
|
||||
...
|
||||
|
||||
@overload
|
||||
def __new__(cls, x: str, y: str) -> "Foo":
|
||||
def __new__(cls, x: "str", y: "str") -> "Foo":
|
||||
...
|
||||
|
||||
def __new__(cls, x, y):
|
||||
@ -64,7 +64,7 @@ class Bar:
|
||||
...
|
||||
|
||||
@overload
|
||||
def __init__(cls, x: str, y: str) -> None:
|
||||
def __init__(cls, x: "str", y: "str") -> "None":
|
||||
...
|
||||
|
||||
def __init__(cls, x, y):
|
||||
@ -77,7 +77,7 @@ class Meta(type):
|
||||
...
|
||||
|
||||
@overload
|
||||
def __call__(cls, x: str, y: str) -> Any:
|
||||
def __call__(cls, x: "str", y: "str") -> "Any":
|
||||
...
|
||||
|
||||
def __call__(cls, x, y):
|
||||
|
Loading…
Reference in New Issue
Block a user