mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Merge pull request #7716 from tk0miya/3610_support_overload
Close #3610: autodoc: Support overloaded functions
This commit is contained in:
commit
bd2caee82f
1
CHANGES
1
CHANGES
@ -51,6 +51,7 @@ Features added
|
|||||||
builtin base classes
|
builtin base classes
|
||||||
* #2106: autodoc: Support multiple signatures on docstring
|
* #2106: autodoc: Support multiple signatures on docstring
|
||||||
* #4422: autodoc: Support GenericAlias in Python 3.7 or above
|
* #4422: autodoc: Support GenericAlias in Python 3.7 or above
|
||||||
|
* #3610: autodoc: Support overloaded functions
|
||||||
* #7466: autosummary: headings in generated documents are not translated
|
* #7466: autosummary: headings in generated documents are not translated
|
||||||
* #7490: autosummary: Add ``:caption:`` option to autosummary directive to set a
|
* #7490: autosummary: Add ``:caption:`` option to autosummary directive to set a
|
||||||
caption to the toctree
|
caption to the toctree
|
||||||
|
@ -1192,8 +1192,14 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ
|
|||||||
self.add_line(' :async:', sourcename)
|
self.add_line(' :async:', sourcename)
|
||||||
|
|
||||||
def format_signature(self, **kwargs: Any) -> str:
|
def format_signature(self, **kwargs: Any) -> str:
|
||||||
sig = super().format_signature(**kwargs)
|
sigs = []
|
||||||
sigs = [sig]
|
if self.analyzer and '.'.join(self.objpath) in self.analyzer.overloads:
|
||||||
|
# Use signatures for overloaded functions instead of the implementation function.
|
||||||
|
overloaded = True
|
||||||
|
else:
|
||||||
|
overloaded = False
|
||||||
|
sig = super().format_signature(**kwargs)
|
||||||
|
sigs.append(sig)
|
||||||
|
|
||||||
if inspect.is_singledispatch_function(self.object):
|
if inspect.is_singledispatch_function(self.object):
|
||||||
# append signature of singledispatch'ed functions
|
# append signature of singledispatch'ed functions
|
||||||
@ -1207,6 +1213,10 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ
|
|||||||
documenter.object = func
|
documenter.object = func
|
||||||
documenter.objpath = [None]
|
documenter.objpath = [None]
|
||||||
sigs.append(documenter.format_signature())
|
sigs.append(documenter.format_signature())
|
||||||
|
if overloaded:
|
||||||
|
for overload in self.analyzer.overloads.get('.'.join(self.objpath)):
|
||||||
|
sig = stringify_signature(overload, **kwargs)
|
||||||
|
sigs.append(sig)
|
||||||
|
|
||||||
return "\n".join(sigs)
|
return "\n".join(sigs)
|
||||||
|
|
||||||
@ -1269,6 +1279,9 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
|
|||||||
'private-members': bool_option, 'special-members': members_option,
|
'private-members': bool_option, 'special-members': members_option,
|
||||||
} # type: Dict[str, Callable]
|
} # type: Dict[str, Callable]
|
||||||
|
|
||||||
|
_signature_class = None # type: Any
|
||||||
|
_signature_method_name = None # type: str
|
||||||
|
|
||||||
def __init__(self, *args: Any) -> None:
|
def __init__(self, *args: Any) -> None:
|
||||||
super().__init__(*args)
|
super().__init__(*args)
|
||||||
merge_special_members_option(self.options)
|
merge_special_members_option(self.options)
|
||||||
@ -1289,7 +1302,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
|
|||||||
self.doc_as_attr = True
|
self.doc_as_attr = True
|
||||||
return ret
|
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:
|
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. """
|
""" Get the `attr` function or method from `obj`, if it is user-defined. """
|
||||||
if inspect.is_builtin_class_method(obj, attr):
|
if inspect.is_builtin_class_method(obj, attr):
|
||||||
@ -1313,7 +1326,8 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
|
|||||||
if call is not None:
|
if call is not None:
|
||||||
self.env.app.emit('autodoc-before-process-signature', call, True)
|
self.env.app.emit('autodoc-before-process-signature', call, True)
|
||||||
try:
|
try:
|
||||||
return inspect.signature(call, bound_method=True)
|
sig = inspect.signature(call, bound_method=True)
|
||||||
|
return type(self.object), '__call__', sig
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -1322,7 +1336,8 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
|
|||||||
if new is not None:
|
if new is not None:
|
||||||
self.env.app.emit('autodoc-before-process-signature', new, True)
|
self.env.app.emit('autodoc-before-process-signature', new, True)
|
||||||
try:
|
try:
|
||||||
return inspect.signature(new, bound_method=True)
|
sig = inspect.signature(new, bound_method=True)
|
||||||
|
return self.object, '__new__', sig
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -1331,7 +1346,8 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
|
|||||||
if init is not None:
|
if init is not None:
|
||||||
self.env.app.emit('autodoc-before-process-signature', init, True)
|
self.env.app.emit('autodoc-before-process-signature', init, True)
|
||||||
try:
|
try:
|
||||||
return inspect.signature(init, bound_method=True)
|
sig = inspect.signature(init, bound_method=True)
|
||||||
|
return self.object, '__init__', sig
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -1341,20 +1357,21 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
|
|||||||
# the signature from, so just pass the object itself to our hook.
|
# the signature from, so just pass the object itself to our hook.
|
||||||
self.env.app.emit('autodoc-before-process-signature', self.object, False)
|
self.env.app.emit('autodoc-before-process-signature', self.object, False)
|
||||||
try:
|
try:
|
||||||
return inspect.signature(self.object, bound_method=False)
|
sig = inspect.signature(self.object, bound_method=False)
|
||||||
|
return None, None, sig
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Still no signature: happens e.g. for old-style classes
|
# Still no signature: happens e.g. for old-style classes
|
||||||
# with __init__ in C and no `__text_signature__`.
|
# with __init__ in C and no `__text_signature__`.
|
||||||
return None
|
return None, None, None
|
||||||
|
|
||||||
def format_args(self, **kwargs: Any) -> str:
|
def format_args(self, **kwargs: Any) -> str:
|
||||||
if self.env.config.autodoc_typehints in ('none', 'description'):
|
if self.env.config.autodoc_typehints in ('none', 'description'):
|
||||||
kwargs.setdefault('show_annotation', False)
|
kwargs.setdefault('show_annotation', False)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
sig = self._get_signature()
|
self._signature_class, self._signature_method_name, sig = self._get_signature()
|
||||||
except TypeError as exc:
|
except TypeError as exc:
|
||||||
# __signature__ attribute contained junk
|
# __signature__ attribute contained junk
|
||||||
logger.warning(__("Failed to get a constructor signature for %s: %s"),
|
logger.warning(__("Failed to get a constructor signature for %s: %s"),
|
||||||
@ -1370,7 +1387,30 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
|
|||||||
if self.doc_as_attr:
|
if self.doc_as_attr:
|
||||||
return ''
|
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:
|
def add_directive_header(self, sig: str) -> None:
|
||||||
sourcename = self.get_sourcename()
|
sourcename = self.get_sourcename()
|
||||||
@ -1693,8 +1733,14 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def format_signature(self, **kwargs: Any) -> str:
|
def format_signature(self, **kwargs: Any) -> str:
|
||||||
sig = super().format_signature(**kwargs)
|
sigs = []
|
||||||
sigs = [sig]
|
if self.analyzer and '.'.join(self.objpath) in self.analyzer.overloads:
|
||||||
|
# Use signatures for overloaded methods instead of the implementation method.
|
||||||
|
overloaded = True
|
||||||
|
else:
|
||||||
|
overloaded = False
|
||||||
|
sig = super().format_signature(**kwargs)
|
||||||
|
sigs.append(sig)
|
||||||
|
|
||||||
meth = self.parent.__dict__.get(self.objpath[-1])
|
meth = self.parent.__dict__.get(self.objpath[-1])
|
||||||
if inspect.is_singledispatch_method(meth):
|
if inspect.is_singledispatch_method(meth):
|
||||||
@ -1710,6 +1756,14 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
|
|||||||
documenter.object = func
|
documenter.object = func
|
||||||
documenter.objpath = [None]
|
documenter.objpath = [None]
|
||||||
sigs.append(documenter.format_signature())
|
sigs.append(documenter.format_signature())
|
||||||
|
if overloaded:
|
||||||
|
for overload in self.analyzer.overloads.get('.'.join(self.objpath)):
|
||||||
|
if not inspect.isstaticmethod(self.object, cls=self.parent,
|
||||||
|
name=self.object_name):
|
||||||
|
parameters = list(overload.parameters.values())
|
||||||
|
overload = overload.replace(parameters=parameters[1:])
|
||||||
|
sig = stringify_signature(overload, **kwargs)
|
||||||
|
sigs.append(sig)
|
||||||
|
|
||||||
return "\n".join(sigs)
|
return "\n".join(sigs)
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ import re
|
|||||||
import tokenize
|
import tokenize
|
||||||
import warnings
|
import warnings
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
|
from inspect import Signature
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
from os import path
|
from os import path
|
||||||
from typing import Any, Dict, IO, List, Tuple, Optional
|
from typing import Any, Dict, IO, List, Tuple, Optional
|
||||||
@ -145,6 +146,7 @@ class ModuleAnalyzer:
|
|||||||
self.annotations = None # type: Dict[Tuple[str, str], str]
|
self.annotations = None # type: Dict[Tuple[str, str], str]
|
||||||
self.attr_docs = None # type: Dict[Tuple[str, str], List[str]]
|
self.attr_docs = None # type: Dict[Tuple[str, str], List[str]]
|
||||||
self.finals = None # type: List[str]
|
self.finals = None # type: List[str]
|
||||||
|
self.overloads = None # type: Dict[str, List[Signature]]
|
||||||
self.tagorder = None # type: Dict[str, int]
|
self.tagorder = None # type: Dict[str, int]
|
||||||
self.tags = None # type: Dict[str, Tuple[str, int, int]]
|
self.tags = None # type: Dict[str, Tuple[str, int, int]]
|
||||||
|
|
||||||
@ -163,6 +165,7 @@ class ModuleAnalyzer:
|
|||||||
|
|
||||||
self.annotations = parser.annotations
|
self.annotations = parser.annotations
|
||||||
self.finals = parser.finals
|
self.finals = parser.finals
|
||||||
|
self.overloads = parser.overloads
|
||||||
self.tags = parser.definitions
|
self.tags = parser.definitions
|
||||||
self.tagorder = parser.deforders
|
self.tagorder = parser.deforders
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
|
@ -12,12 +12,14 @@ import itertools
|
|||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import tokenize
|
import tokenize
|
||||||
|
from inspect import Signature
|
||||||
from token import NAME, NEWLINE, INDENT, DEDENT, NUMBER, OP, STRING
|
from token import NAME, NEWLINE, INDENT, DEDENT, NUMBER, OP, STRING
|
||||||
from tokenize import COMMENT, NL
|
from tokenize import COMMENT, NL
|
||||||
from typing import Any, Dict, List, Optional, Tuple
|
from typing import Any, Dict, List, Optional, Tuple
|
||||||
|
|
||||||
from sphinx.pycode.ast import ast # for py37 or older
|
from sphinx.pycode.ast import ast # for py37 or older
|
||||||
from sphinx.pycode.ast import parse, unparse
|
from sphinx.pycode.ast import parse, unparse
|
||||||
|
from sphinx.util.inspect import signature_from_ast
|
||||||
|
|
||||||
|
|
||||||
comment_re = re.compile('^\\s*#: ?(.*)\r?\n?$')
|
comment_re = re.compile('^\\s*#: ?(.*)\r?\n?$')
|
||||||
@ -232,8 +234,10 @@ class VariableCommentPicker(ast.NodeVisitor):
|
|||||||
self.previous = None # type: ast.AST
|
self.previous = None # type: ast.AST
|
||||||
self.deforders = {} # type: Dict[str, int]
|
self.deforders = {} # type: Dict[str, int]
|
||||||
self.finals = [] # type: List[str]
|
self.finals = [] # type: List[str]
|
||||||
|
self.overloads = {} # type: Dict[str, List[Signature]]
|
||||||
self.typing = None # type: str
|
self.typing = None # type: str
|
||||||
self.typing_final = None # type: str
|
self.typing_final = None # type: str
|
||||||
|
self.typing_overload = None # type: str
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
def get_qualname_for(self, name: str) -> Optional[List[str]]:
|
def get_qualname_for(self, name: str) -> Optional[List[str]]:
|
||||||
@ -257,6 +261,12 @@ class VariableCommentPicker(ast.NodeVisitor):
|
|||||||
if qualname:
|
if qualname:
|
||||||
self.finals.append(".".join(qualname))
|
self.finals.append(".".join(qualname))
|
||||||
|
|
||||||
|
def add_overload_entry(self, func: ast.FunctionDef) -> None:
|
||||||
|
qualname = self.get_qualname_for(func.name)
|
||||||
|
if qualname:
|
||||||
|
overloads = self.overloads.setdefault(".".join(qualname), [])
|
||||||
|
overloads.append(signature_from_ast(func))
|
||||||
|
|
||||||
def add_variable_comment(self, name: str, comment: str) -> None:
|
def add_variable_comment(self, name: str, comment: str) -> None:
|
||||||
qualname = self.get_qualname_for(name)
|
qualname = self.get_qualname_for(name)
|
||||||
if qualname:
|
if qualname:
|
||||||
@ -285,6 +295,22 @@ class VariableCommentPicker(ast.NodeVisitor):
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def is_overload(self, decorators: List[ast.expr]) -> bool:
|
||||||
|
overload = []
|
||||||
|
if self.typing:
|
||||||
|
overload.append('%s.overload' % self.typing)
|
||||||
|
if self.typing_overload:
|
||||||
|
overload.append(self.typing_overload)
|
||||||
|
|
||||||
|
for decorator in decorators:
|
||||||
|
try:
|
||||||
|
if unparse(decorator) in overload:
|
||||||
|
return True
|
||||||
|
except NotImplementedError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
def get_self(self) -> ast.arg:
|
def get_self(self) -> ast.arg:
|
||||||
"""Returns the name of first argument if in function."""
|
"""Returns the name of first argument if in function."""
|
||||||
if self.current_function and self.current_function.args.args:
|
if self.current_function and self.current_function.args.args:
|
||||||
@ -310,6 +336,8 @@ class VariableCommentPicker(ast.NodeVisitor):
|
|||||||
self.typing = name.asname or name.name
|
self.typing = name.asname or name.name
|
||||||
elif name.name == 'typing.final':
|
elif name.name == 'typing.final':
|
||||||
self.typing_final = name.asname or name.name
|
self.typing_final = name.asname or name.name
|
||||||
|
elif name.name == 'typing.overload':
|
||||||
|
self.typing_overload = name.asname or name.name
|
||||||
|
|
||||||
def visit_ImportFrom(self, node: ast.ImportFrom) -> None:
|
def visit_ImportFrom(self, node: ast.ImportFrom) -> None:
|
||||||
"""Handles Import node and record it to definition orders."""
|
"""Handles Import node and record it to definition orders."""
|
||||||
@ -318,6 +346,8 @@ class VariableCommentPicker(ast.NodeVisitor):
|
|||||||
|
|
||||||
if node.module == 'typing' and name.name == 'final':
|
if node.module == 'typing' and name.name == 'final':
|
||||||
self.typing_final = name.asname or name.name
|
self.typing_final = name.asname or name.name
|
||||||
|
elif node.module == 'typing' and name.name == 'overload':
|
||||||
|
self.typing_overload = name.asname or name.name
|
||||||
|
|
||||||
def visit_Assign(self, node: ast.Assign) -> None:
|
def visit_Assign(self, node: ast.Assign) -> None:
|
||||||
"""Handles Assign node and pick up a variable comment."""
|
"""Handles Assign node and pick up a variable comment."""
|
||||||
@ -417,6 +447,8 @@ class VariableCommentPicker(ast.NodeVisitor):
|
|||||||
self.add_entry(node.name) # should be called before setting self.current_function
|
self.add_entry(node.name) # should be called before setting self.current_function
|
||||||
if self.is_final(node.decorator_list):
|
if self.is_final(node.decorator_list):
|
||||||
self.add_final_entry(node.name)
|
self.add_final_entry(node.name)
|
||||||
|
if self.is_overload(node.decorator_list):
|
||||||
|
self.add_overload_entry(node)
|
||||||
self.context.append(node.name)
|
self.context.append(node.name)
|
||||||
self.current_function = node
|
self.current_function = node
|
||||||
for child in node.body:
|
for child in node.body:
|
||||||
@ -518,6 +550,7 @@ class Parser:
|
|||||||
self.deforders = {} # type: Dict[str, int]
|
self.deforders = {} # type: Dict[str, int]
|
||||||
self.definitions = {} # type: Dict[str, Tuple[str, int, int]]
|
self.definitions = {} # type: Dict[str, Tuple[str, int, int]]
|
||||||
self.finals = [] # type: List[str]
|
self.finals = [] # type: List[str]
|
||||||
|
self.overloads = {} # type: Dict[str, List[Signature]]
|
||||||
|
|
||||||
def parse(self) -> None:
|
def parse(self) -> None:
|
||||||
"""Parse the source code."""
|
"""Parse the source code."""
|
||||||
@ -533,6 +566,7 @@ class Parser:
|
|||||||
self.comments = picker.comments
|
self.comments = picker.comments
|
||||||
self.deforders = picker.deforders
|
self.deforders = picker.deforders
|
||||||
self.finals = picker.finals
|
self.finals = picker.finals
|
||||||
|
self.overloads = picker.overloads
|
||||||
|
|
||||||
def parse_definition(self) -> None:
|
def parse_definition(self) -> None:
|
||||||
"""Parse the location of definitions from the code."""
|
"""Parse the location of definitions from the code."""
|
||||||
|
@ -527,10 +527,14 @@ def stringify_signature(sig: inspect.Signature, show_annotation: bool = True,
|
|||||||
def signature_from_str(signature: str) -> inspect.Signature:
|
def signature_from_str(signature: str) -> inspect.Signature:
|
||||||
"""Create a Signature object from string."""
|
"""Create a Signature object from string."""
|
||||||
module = ast.parse('def func' + signature + ': pass')
|
module = ast.parse('def func' + signature + ': pass')
|
||||||
definition = cast(ast.FunctionDef, module.body[0]) # type: ignore
|
function = cast(ast.FunctionDef, module.body[0]) # type: ignore
|
||||||
|
|
||||||
# parameters
|
return signature_from_ast(function)
|
||||||
args = definition.args
|
|
||||||
|
|
||||||
|
def signature_from_ast(node: ast.FunctionDef) -> inspect.Signature:
|
||||||
|
"""Create a Signature object from AST *node*."""
|
||||||
|
args = node.args
|
||||||
defaults = list(args.defaults)
|
defaults = list(args.defaults)
|
||||||
params = []
|
params = []
|
||||||
if hasattr(args, "posonlyargs"):
|
if hasattr(args, "posonlyargs"):
|
||||||
@ -580,7 +584,7 @@ def signature_from_str(signature: str) -> inspect.Signature:
|
|||||||
params.append(Parameter(args.kwarg.arg, Parameter.VAR_KEYWORD,
|
params.append(Parameter(args.kwarg.arg, Parameter.VAR_KEYWORD,
|
||||||
annotation=annotation))
|
annotation=annotation))
|
||||||
|
|
||||||
return_annotation = ast_unparse(definition.returns) or Parameter.empty
|
return_annotation = ast_unparse(node.returns) or Parameter.empty
|
||||||
|
|
||||||
return inspect.Signature(params, return_annotation=return_annotation)
|
return inspect.Signature(params, return_annotation=return_annotation)
|
||||||
|
|
||||||
|
88
tests/roots/test-ext-autodoc/target/overload.py
Normal file
88
tests/roots/test-ext-autodoc/target/overload.py
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
from typing import Any, overload
|
||||||
|
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def sum(x: int, y: int) -> int:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def sum(x: float, y: float) -> float:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def sum(x: str, y: str) -> str:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
def sum(x, y):
|
||||||
|
"""docstring"""
|
||||||
|
return x + y
|
||||||
|
|
||||||
|
|
||||||
|
class Math:
|
||||||
|
"""docstring"""
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def sum(self, x: int, y: int) -> int:
|
||||||
|
...
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def sum(self, x: float, y: float) -> float:
|
||||||
|
...
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def sum(self, x: str, y: str) -> str:
|
||||||
|
...
|
||||||
|
|
||||||
|
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"""
|
@ -1787,6 +1787,60 @@ def test_final(app):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||||
|
def test_overload(app):
|
||||||
|
options = {"members": None}
|
||||||
|
actual = do_autodoc(app, 'module', 'target.overload', options)
|
||||||
|
assert list(actual) == [
|
||||||
|
'',
|
||||||
|
'.. 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',
|
||||||
|
'',
|
||||||
|
' docstring',
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
' .. py:method:: Math.sum(x: int, y: int) -> int',
|
||||||
|
' Math.sum(x: float, y: float) -> float',
|
||||||
|
' Math.sum(x: str, y: str) -> str',
|
||||||
|
' :module: target.overload',
|
||||||
|
'',
|
||||||
|
' docstring',
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
'.. py:function:: sum(x: int, y: int) -> int',
|
||||||
|
' sum(x: float, y: float) -> float',
|
||||||
|
' sum(x: str, y: str) -> str',
|
||||||
|
' :module: target.overload',
|
||||||
|
'',
|
||||||
|
' docstring',
|
||||||
|
'',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.sphinx('dummy', testroot='ext-autodoc')
|
@pytest.mark.sphinx('dummy', testroot='ext-autodoc')
|
||||||
def test_autodoc(app, status, warning):
|
def test_autodoc(app, status, warning):
|
||||||
app.builder.build_all()
|
app.builder.build_all()
|
||||||
|
@ -13,6 +13,7 @@ import sys
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from sphinx.pycode.parser import Parser
|
from sphinx.pycode.parser import Parser
|
||||||
|
from sphinx.util.inspect import signature_from_str
|
||||||
|
|
||||||
|
|
||||||
def test_comment_picker_basic():
|
def test_comment_picker_basic():
|
||||||
@ -452,3 +453,80 @@ def test_typing_final_not_imported():
|
|||||||
parser = Parser(source)
|
parser = Parser(source)
|
||||||
parser.parse()
|
parser.parse()
|
||||||
assert parser.finals == []
|
assert parser.finals == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_typing_overload():
|
||||||
|
source = ('import typing\n'
|
||||||
|
'\n'
|
||||||
|
'@typing.overload\n'
|
||||||
|
'def func(x: int, y: int) -> int: pass\n'
|
||||||
|
'\n'
|
||||||
|
'@typing.overload\n'
|
||||||
|
'def func(x: str, y: str) -> str: pass\n'
|
||||||
|
'\n'
|
||||||
|
'def func(x, y): pass\n')
|
||||||
|
parser = Parser(source)
|
||||||
|
parser.parse()
|
||||||
|
assert parser.overloads == {'func': [signature_from_str('(x: int, y: int) -> int'),
|
||||||
|
signature_from_str('(x: str, y: str) -> str')]}
|
||||||
|
|
||||||
|
|
||||||
|
def test_typing_overload_from_import():
|
||||||
|
source = ('from typing import overload\n'
|
||||||
|
'\n'
|
||||||
|
'@overload\n'
|
||||||
|
'def func(x: int, y: int) -> int: pass\n'
|
||||||
|
'\n'
|
||||||
|
'@overload\n'
|
||||||
|
'def func(x: str, y: str) -> str: pass\n'
|
||||||
|
'\n'
|
||||||
|
'def func(x, y): pass\n')
|
||||||
|
parser = Parser(source)
|
||||||
|
parser.parse()
|
||||||
|
assert parser.overloads == {'func': [signature_from_str('(x: int, y: int) -> int'),
|
||||||
|
signature_from_str('(x: str, y: str) -> str')]}
|
||||||
|
|
||||||
|
|
||||||
|
def test_typing_overload_import_as():
|
||||||
|
source = ('import typing as foo\n'
|
||||||
|
'\n'
|
||||||
|
'@foo.overload\n'
|
||||||
|
'def func(x: int, y: int) -> int: pass\n'
|
||||||
|
'\n'
|
||||||
|
'@foo.overload\n'
|
||||||
|
'def func(x: str, y: str) -> str: pass\n'
|
||||||
|
'\n'
|
||||||
|
'def func(x, y): pass\n')
|
||||||
|
parser = Parser(source)
|
||||||
|
parser.parse()
|
||||||
|
assert parser.overloads == {'func': [signature_from_str('(x: int, y: int) -> int'),
|
||||||
|
signature_from_str('(x: str, y: str) -> str')]}
|
||||||
|
|
||||||
|
|
||||||
|
def test_typing_overload_from_import_as():
|
||||||
|
source = ('from typing import overload as bar\n'
|
||||||
|
'\n'
|
||||||
|
'@bar\n'
|
||||||
|
'def func(x: int, y: int) -> int: pass\n'
|
||||||
|
'\n'
|
||||||
|
'@bar\n'
|
||||||
|
'def func(x: str, y: str) -> str: pass\n'
|
||||||
|
'\n'
|
||||||
|
'def func(x, y): pass\n')
|
||||||
|
parser = Parser(source)
|
||||||
|
parser.parse()
|
||||||
|
assert parser.overloads == {'func': [signature_from_str('(x: int, y: int) -> int'),
|
||||||
|
signature_from_str('(x: str, y: str) -> str')]}
|
||||||
|
|
||||||
|
|
||||||
|
def test_typing_overload_not_imported():
|
||||||
|
source = ('@typing.final\n'
|
||||||
|
'def func(x: int, y: int) -> int: pass\n'
|
||||||
|
'\n'
|
||||||
|
'@typing.final\n'
|
||||||
|
'def func(x: str, y: str) -> str: pass\n'
|
||||||
|
'\n'
|
||||||
|
'def func(x, y): pass\n')
|
||||||
|
parser = Parser(source)
|
||||||
|
parser.parse()
|
||||||
|
assert parser.overloads == {}
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import _testcapi
|
import _testcapi
|
||||||
|
import ast
|
||||||
import datetime
|
import datetime
|
||||||
import functools
|
import functools
|
||||||
import sys
|
import sys
|
||||||
@ -350,6 +351,38 @@ def test_signature_from_str_invalid():
|
|||||||
inspect.signature_from_str('')
|
inspect.signature_from_str('')
|
||||||
|
|
||||||
|
|
||||||
|
def test_signature_from_ast():
|
||||||
|
signature = 'def func(a, b, *args, c=0, d="blah", **kwargs): pass'
|
||||||
|
tree = ast.parse(signature)
|
||||||
|
sig = inspect.signature_from_ast(tree.body[0])
|
||||||
|
assert list(sig.parameters.keys()) == ['a', 'b', 'args', 'c', 'd', 'kwargs']
|
||||||
|
assert sig.parameters['a'].name == 'a'
|
||||||
|
assert sig.parameters['a'].kind == Parameter.POSITIONAL_OR_KEYWORD
|
||||||
|
assert sig.parameters['a'].default == Parameter.empty
|
||||||
|
assert sig.parameters['a'].annotation == Parameter.empty
|
||||||
|
assert sig.parameters['b'].name == 'b'
|
||||||
|
assert sig.parameters['b'].kind == Parameter.POSITIONAL_OR_KEYWORD
|
||||||
|
assert sig.parameters['b'].default == Parameter.empty
|
||||||
|
assert sig.parameters['b'].annotation == Parameter.empty
|
||||||
|
assert sig.parameters['args'].name == 'args'
|
||||||
|
assert sig.parameters['args'].kind == Parameter.VAR_POSITIONAL
|
||||||
|
assert sig.parameters['args'].default == Parameter.empty
|
||||||
|
assert sig.parameters['args'].annotation == Parameter.empty
|
||||||
|
assert sig.parameters['c'].name == 'c'
|
||||||
|
assert sig.parameters['c'].kind == Parameter.KEYWORD_ONLY
|
||||||
|
assert sig.parameters['c'].default == '0'
|
||||||
|
assert sig.parameters['c'].annotation == Parameter.empty
|
||||||
|
assert sig.parameters['d'].name == 'd'
|
||||||
|
assert sig.parameters['d'].kind == Parameter.KEYWORD_ONLY
|
||||||
|
assert sig.parameters['d'].default == "'blah'"
|
||||||
|
assert sig.parameters['d'].annotation == Parameter.empty
|
||||||
|
assert sig.parameters['kwargs'].name == 'kwargs'
|
||||||
|
assert sig.parameters['kwargs'].kind == Parameter.VAR_KEYWORD
|
||||||
|
assert sig.parameters['kwargs'].default == Parameter.empty
|
||||||
|
assert sig.parameters['kwargs'].annotation == Parameter.empty
|
||||||
|
assert sig.return_annotation == Parameter.empty
|
||||||
|
|
||||||
|
|
||||||
def test_safe_getattr_with_default():
|
def test_safe_getattr_with_default():
|
||||||
class Foo:
|
class Foo:
|
||||||
def __getattr__(self, item):
|
def __getattr__(self, item):
|
||||||
|
Loading…
Reference in New Issue
Block a user