From 2c2b8c1154f1a1488ac629f0ad477bb2d0acb8a4 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 31 Jan 2021 22:29:25 +0900 Subject: [PATCH] py domain: Support type union operator (PEP-604) (refs: #8775) Upgrade annotation parser for python domain to support type union operator introduced in PEP-604. It's available on all python interpreters. --- CHANGES | 1 + sphinx/domains/python.py | 11 +++++++++-- tests/test_domain_py.py | 28 ++++++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index ebdd331ed09..9e5b8f9cbd2 100644 --- a/CHANGES +++ b/CHANGES @@ -54,6 +54,7 @@ Features added * #8004: napoleon: Type definitions in Google style docstrings are rendered as references when :confval:`napoleon_preprocess_types` enabled * #6241: mathjax: Include mathjax.js only on the document using equations +* #8775: py domain: Support type union operator (PEP-604) * #8651: std domain: cross-reference for a rubric having inline item is broken * #7642: std domain: Optimize case-insensitive match of term * #8681: viewcode: Support incremental build diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index c2af9886f82..552742bb86f 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -100,12 +100,19 @@ def _parse_annotation(annotation: str, env: BuildEnvironment = None) -> List[Nod def unparse(node: ast.AST) -> List[Node]: if isinstance(node, ast.Attribute): return [nodes.Text("%s.%s" % (unparse(node.value)[0], node.attr))] + elif isinstance(node, ast.BinOp): + result = unparse(node.left) # type: List[Node] + result.extend(unparse(node.op)) + result.extend(unparse(node.right)) + return result + elif isinstance(node, ast.BitOr): + return [nodes.Text(' '), addnodes.desc_sig_punctuation('', '|'), nodes.Text(' ')] elif isinstance(node, ast.Expr): return unparse(node.value) elif isinstance(node, ast.Index): return unparse(node.value) elif isinstance(node, ast.List): - result = [addnodes.desc_sig_punctuation('', '[')] # type: List[Node] + result = [addnodes.desc_sig_punctuation('', '[')] for elem in node.elts: result.extend(unparse(elem)) result.append(addnodes.desc_sig_punctuation('', ', ')) @@ -158,7 +165,7 @@ def unparse(node: ast.AST) -> List[Node]: tree = ast_parse(annotation) result = unparse(tree) for i, node in enumerate(result): - if isinstance(node, nodes.Text): + if isinstance(node, nodes.Text) and node.strip(): result[i] = type_to_xref(str(node), env) return result except SyntaxError: diff --git a/tests/test_domain_py.py b/tests/test_domain_py.py index fe117659ae1..b09021073e4 100644 --- a/tests/test_domain_py.py +++ b/tests/test_domain_py.py @@ -429,6 +429,20 @@ def test_pyfunction_with_number_literals(app): [nodes.inline, "1_6_0"])])]) +def test_pyfunction_with_union_type_operator(app): + text = ".. py:function:: hello(age: int | None)" + doctree = restructuredtext.parse(app, text) + assert_node(doctree[1][0][1], + [desc_parameterlist, ([desc_parameter, ([desc_sig_name, "age"], + [desc_sig_punctuation, ":"], + " ", + [desc_sig_name, ([pending_xref, "int"], + " ", + [desc_sig_punctuation, "|"], + " ", + [pending_xref, "None"])])])]) + + def test_optional_pyfunction_signature(app): text = ".. py:function:: compile(source [, filename [, symbol]]) -> ast object" doctree = restructuredtext.parse(app, text) @@ -496,6 +510,20 @@ def test_pydata_signature_old(app): domain="py", objtype="data", noindex=False) +def test_pydata_with_union_type_operator(app): + text = (".. py:data:: version\n" + " :type: int | str") + doctree = restructuredtext.parse(app, text) + assert_node(doctree[1][0], + ([desc_name, "version"], + [desc_annotation, (": ", + [pending_xref, "int"], + " ", + [desc_sig_punctuation, "|"], + " ", + [pending_xref, "str"])])) + + def test_pyobject_prefix(app): text = (".. py:class:: Foo\n" "\n"