Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

py domain: Support lambda functions in function signature #7159

Merged
merged 1 commit into from
Feb 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ Features added
* #6558: glossary: emit a warning for duplicated glossary entry
* #6558: std domain: emit a warning for duplicated generic objects
* #6830: py domain: Add new event: :event:`object-description-transform`
* py domain: Support lambda functions in function signature
* Support priority of event handlers. For more detail, see
:py:meth:`.Sphinx.connect()`
* #3077: Implement the scoping for :rst:dir:`productionlist` as indicated
Expand Down
68 changes: 67 additions & 1 deletion sphinx/pycode/ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"""

import sys
from typing import List

if sys.version_info > (3, 8):
import ast
Expand Down Expand Up @@ -40,6 +41,13 @@ def unparse(node: ast.AST) -> str:
return None
elif isinstance(node, str):
return node
elif isinstance(node, ast.arg):
if node.annotation:
return "%s: %s" % (node.arg, unparse(node.annotation))
else:
return node.arg
elif isinstance(node, ast.arguments):
return unparse_arguments(node)
elif isinstance(node, ast.Attribute):
return "%s.%s" % (unparse(node.value), node.attr)
elif isinstance(node, ast.Bytes):
Expand All @@ -58,7 +66,7 @@ def unparse(node: ast.AST) -> str:
elif isinstance(node, ast.Index):
return unparse(node.value)
elif isinstance(node, ast.Lambda):
return "<function <lambda>>" # TODO
return "lambda %s: ..." % unparse(node.args)
elif isinstance(node, ast.List):
return "[" + ", ".join(unparse(e) for e in node.elts) + "]"
elif isinstance(node, ast.Name):
Expand All @@ -80,3 +88,61 @@ def unparse(node: ast.AST) -> str:
return repr(node.value)
else:
raise NotImplementedError('Unable to parse %s object' % type(node).__name__)


def unparse_arguments(node: ast.arguments) -> str:
"""Unparse an arguments to string."""
defaults = list(node.defaults)
positionals = len(node.args)
posonlyargs = 0
if hasattr(node, "posonlyargs"): # for py38+
posonlyargs += len(node.posonlyargs) # type:ignore
positionals += posonlyargs
for _ in range(len(defaults), positionals):
defaults.insert(0, None)

kw_defaults = list(node.kw_defaults)
for _ in range(len(kw_defaults), len(node.kwonlyargs)):
kw_defaults.insert(0, None)

args = [] # type: List[str]
if hasattr(node, "posonlyargs"): # for py38+
for i, arg in enumerate(node.posonlyargs): # type: ignore
name = unparse(arg)
if defaults[i]:
if arg.annotation:
name += " = %s" % unparse(defaults[i])
else:
name += "=%s" % unparse(defaults[i])
args.append(name)

if node.posonlyargs: # type: ignore
args.append('/')

for i, arg in enumerate(node.args):
name = unparse(arg)
if defaults[i + posonlyargs]:
if arg.annotation:
name += " = %s" % unparse(defaults[i + posonlyargs])
else:
name += "=%s" % unparse(defaults[i + posonlyargs])
args.append(name)

if node.vararg:
args.append("*" + unparse(node.vararg))

if node.kwonlyargs and not node.vararg:
args.append('*')
for i, arg in enumerate(node.kwonlyargs):
name = unparse(arg)
if kw_defaults[i]:
if arg.annotation:
name += " = %s" % unparse(kw_defaults[i])
else:
name += "=%s" % unparse(kw_defaults[i])
args.append(name)

if node.kwarg:
args.append("**" + unparse(node.kwarg))

return ", ".join(args)
12 changes: 11 additions & 1 deletion tests/test_pycode_ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
:license: BSD, see LICENSE for details.
"""

import sys

import pytest

from sphinx.pycode import ast
Expand All @@ -23,7 +25,7 @@
("...", "..."), # Ellipsis
("Tuple[int, int]", "Tuple[int, int]"), # Index, Subscript
("lambda x, y: x + y",
"<function <lambda>>"), # Lambda
"lambda x, y: ..."), # Lambda
("[1, 2, 3]", "[1, 2, 3]"), # List
("sys", "sys"), # Name, NameConstant
("1234", "1234"), # Num
Expand All @@ -38,3 +40,11 @@ def test_unparse(source, expected):

def test_unparse_None():
assert ast.unparse(None) is None


@pytest.mark.skipif(sys.version_info < (3, 8), reason='python 3.8+ is required.')
def test_unparse_py38():
source = "lambda x=0, /, y=1, *args, z, **kwargs: x + y + z"
expected = "lambda x=0, /, y=1, *args, z, **kwargs: ..."
module = ast.parse(source)
assert ast.unparse(module.body[0].value) == expected