Skip to content

Commit

Permalink
Merge pull request #7159 from tk0miya/render_lambda_in_signature
Browse files Browse the repository at this point in the history
py domain: Support lambda functions in function signature
  • Loading branch information
tk0miya authored Feb 22, 2020
2 parents 294858e + 76b492a commit 9b06f40
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,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

0 comments on commit 9b06f40

Please sign in to comment.