diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index bcf312d7e..5402ce37a 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -80,7 +80,8 @@ class ModuleEntry(NamedTuple): deprecated: bool -def type_to_xref(target: str, env: BuildEnvironment = None) -> addnodes.pending_xref: +def type_to_xref(target: str, env: BuildEnvironment = None, suppress_prefix: bool = False + ) -> addnodes.pending_xref: """Convert a type string to a cross reference node.""" if target == 'None': reftype = 'obj' @@ -101,6 +102,8 @@ def type_to_xref(target: str, env: BuildEnvironment = None) -> addnodes.pending_ elif target.startswith('~'): target = target[1:] text = target.split('.')[-1] + elif suppress_prefix: + text = target.split('.')[-1] else: text = target @@ -150,6 +153,8 @@ def _parse_annotation(annotation: str, env: BuildEnvironment = None) -> List[Nod return unparse(node.value) elif isinstance(node, ast.Index): return unparse(node.value) + elif isinstance(node, ast.Invert): + return [addnodes.desc_sig_punctuation('', '~')] elif isinstance(node, ast.List): result = [addnodes.desc_sig_punctuation('', '[')] if node.elts: @@ -180,6 +185,8 @@ def _parse_annotation(annotation: str, env: BuildEnvironment = None) -> List[Nod if isinstance(subnode, nodes.Text): result[i] = nodes.literal('', '', subnode) return result + elif isinstance(node, ast.UnaryOp): + return unparse(node.op) + unparse(node.operand) elif isinstance(node, ast.Tuple): if node.elts: result = [] @@ -209,12 +216,19 @@ def _parse_annotation(annotation: str, env: BuildEnvironment = None) -> List[Nod try: tree = ast_parse(annotation) - result = unparse(tree) - for i, node in enumerate(result): + result: List[Node] = [] + for node in unparse(tree): if isinstance(node, nodes.literal): - result[i] = node[0] + result.append(node[0]) elif isinstance(node, nodes.Text) and node.strip(): - result[i] = type_to_xref(str(node), env) + if (result and isinstance(result[-1], addnodes.desc_sig_punctuation) and + result[-1].astext() == '~'): + result.pop() + result.append(type_to_xref(str(node), env, suppress_prefix=True)) + else: + result.append(type_to_xref(str(node), env)) + else: + result.append(node) return result except SyntaxError: return [type_to_xref(annotation, env)] diff --git a/tests/test_domain_py.py b/tests/test_domain_py.py index 353640cda..dbd594f83 100644 --- a/tests/test_domain_py.py +++ b/tests/test_domain_py.py @@ -350,6 +350,18 @@ def test_parse_annotation(app): assert_node(doctree[0], pending_xref, refdomain="py", reftype="obj", reftarget="None") +def test_parse_annotation_suppress(app): + doctree = _parse_annotation("~typing.Dict[str, str]", app.env) + assert_node(doctree, ([pending_xref, "Dict"], + [desc_sig_punctuation, "["], + [pending_xref, "str"], + [desc_sig_punctuation, ","], + desc_sig_space, + [pending_xref, "str"], + [desc_sig_punctuation, "]"])) + assert_node(doctree[0], pending_xref, refdomain="py", reftype="class", reftarget="typing.Dict") + + @pytest.mark.skipif(sys.version_info < (3, 8), reason='python 3.8+ is required.') def test_parse_annotation_Literal(app): doctree = _parse_annotation("Literal[True, False]", app.env)