Merge pull request #7930 from tk0miya/7928_resolve_typehints_for_attrs

Fix #7928: py domain: failed to resolve a type annotation for the attribute
This commit is contained in:
Takeshi KOMIYA
2020-07-11 22:38:20 +09:00
committed by GitHub
3 changed files with 44 additions and 24 deletions

View File

@@ -35,6 +35,7 @@ Bugs fixed
* #7715: LaTeX: ``numfig_secnum_depth > 1`` leads to wrong figure links * #7715: LaTeX: ``numfig_secnum_depth > 1`` leads to wrong figure links
* #7846: html theme: XML-invalid files were generated * #7846: html theme: XML-invalid files were generated
* #7894: gettext: Wrong source info is shown when using rst_epilog * #7894: gettext: Wrong source info is shown when using rst_epilog
* #7928: py domain: failed to resolve a type annotation for the attribute
* #7869: :rst:role:`abbr` role without an explanation will show the explanation * #7869: :rst:role:`abbr` role without an explanation will show the explanation
from the previous abbr role from the previous abbr role
* C and C++, removed ``noindex`` directive option as it did * C and C++, removed ``noindex`` directive option as it did

View File

@@ -77,18 +77,24 @@ ModuleEntry = NamedTuple('ModuleEntry', [('docname', str),
('deprecated', bool)]) ('deprecated', bool)])
def type_to_xref(text: str) -> addnodes.pending_xref: def type_to_xref(text: str, env: BuildEnvironment = None) -> addnodes.pending_xref:
"""Convert a type string to a cross reference node.""" """Convert a type string to a cross reference node."""
if text == 'None': if text == 'None':
reftype = 'obj' reftype = 'obj'
else: else:
reftype = 'class' reftype = 'class'
if env:
kwargs = {'py:module': env.ref_context.get('py:module'),
'py:class': env.ref_context.get('py:class')}
else:
kwargs = {}
return pending_xref('', nodes.Text(text), return pending_xref('', nodes.Text(text),
refdomain='py', reftype=reftype, reftarget=text) refdomain='py', reftype=reftype, reftarget=text, **kwargs)
def _parse_annotation(annotation: str) -> List[Node]: def _parse_annotation(annotation: str, env: BuildEnvironment = None) -> List[Node]:
"""Parse type annotation.""" """Parse type annotation."""
def unparse(node: ast.AST) -> List[Node]: def unparse(node: ast.AST) -> List[Node]:
if isinstance(node, ast.Attribute): if isinstance(node, ast.Attribute):
@@ -130,18 +136,22 @@ def _parse_annotation(annotation: str) -> List[Node]:
else: else:
raise SyntaxError # unsupported syntax raise SyntaxError # unsupported syntax
if env is None:
warnings.warn("The env parameter for _parse_annotation becomes required now.",
RemovedInSphinx50Warning, stacklevel=2)
try: try:
tree = ast_parse(annotation) tree = ast_parse(annotation)
result = unparse(tree) result = unparse(tree)
for i, node in enumerate(result): for i, node in enumerate(result):
if isinstance(node, nodes.Text): if isinstance(node, nodes.Text):
result[i] = type_to_xref(str(node)) result[i] = type_to_xref(str(node), env)
return result return result
except SyntaxError: except SyntaxError:
return [type_to_xref(annotation)] return [type_to_xref(annotation, env)]
def _parse_arglist(arglist: str) -> addnodes.desc_parameterlist: def _parse_arglist(arglist: str, env: BuildEnvironment = None) -> addnodes.desc_parameterlist:
"""Parse a list of arguments using AST parser""" """Parse a list of arguments using AST parser"""
params = addnodes.desc_parameterlist(arglist) params = addnodes.desc_parameterlist(arglist)
sig = signature_from_str('(%s)' % arglist) sig = signature_from_str('(%s)' % arglist)
@@ -167,7 +177,7 @@ def _parse_arglist(arglist: str) -> addnodes.desc_parameterlist:
node += addnodes.desc_sig_name('', param.name) node += addnodes.desc_sig_name('', param.name)
if param.annotation is not param.empty: if param.annotation is not param.empty:
children = _parse_annotation(param.annotation) children = _parse_annotation(param.annotation, env)
node += addnodes.desc_sig_punctuation('', ':') node += addnodes.desc_sig_punctuation('', ':')
node += nodes.Text(' ') node += nodes.Text(' ')
node += addnodes.desc_sig_name('', '', *children) # type: ignore node += addnodes.desc_sig_name('', '', *children) # type: ignore
@@ -415,7 +425,7 @@ class PyObject(ObjectDescription):
signode += addnodes.desc_name(name, name) signode += addnodes.desc_name(name, name)
if arglist: if arglist:
try: try:
signode += _parse_arglist(arglist) signode += _parse_arglist(arglist, self.env)
except SyntaxError: except SyntaxError:
# fallback to parse arglist original parser. # fallback to parse arglist original parser.
# it supports to represent optional arguments (ex. "func(foo [, bar])") # it supports to represent optional arguments (ex. "func(foo [, bar])")
@@ -430,7 +440,7 @@ class PyObject(ObjectDescription):
signode += addnodes.desc_parameterlist() signode += addnodes.desc_parameterlist()
if retann: if retann:
children = _parse_annotation(retann) children = _parse_annotation(retann, self.env)
signode += addnodes.desc_returns(retann, '', *children) signode += addnodes.desc_returns(retann, '', *children)
anno = self.options.get('annotation') anno = self.options.get('annotation')
@@ -626,7 +636,7 @@ class PyVariable(PyObject):
typ = self.options.get('type') typ = self.options.get('type')
if typ: if typ:
annotations = _parse_annotation(typ) annotations = _parse_annotation(typ, self.env)
signode += addnodes.desc_annotation(typ, '', nodes.Text(': '), *annotations) signode += addnodes.desc_annotation(typ, '', nodes.Text(': '), *annotations)
value = self.options.get('value') value = self.options.get('value')
@@ -872,7 +882,7 @@ class PyAttribute(PyObject):
typ = self.options.get('type') typ = self.options.get('type')
if typ: if typ:
annotations = _parse_annotation(typ) annotations = _parse_annotation(typ, self.env)
signode += addnodes.desc_annotation(typ, '', nodes.Text(': '), *annotations) signode += addnodes.desc_annotation(typ, '', nodes.Text(': '), *annotations)
value = self.options.get('value') value = self.options.get('value')

View File

@@ -236,18 +236,18 @@ def test_get_full_qualified_name():
assert domain.get_full_qualified_name(node) == 'module1.Class.func' assert domain.get_full_qualified_name(node) == 'module1.Class.func'
def test_parse_annotation(): def test_parse_annotation(app):
doctree = _parse_annotation("int") doctree = _parse_annotation("int", app.env)
assert_node(doctree, ([pending_xref, "int"],)) assert_node(doctree, ([pending_xref, "int"],))
assert_node(doctree[0], pending_xref, refdomain="py", reftype="class", reftarget="int") assert_node(doctree[0], pending_xref, refdomain="py", reftype="class", reftarget="int")
doctree = _parse_annotation("List[int]") doctree = _parse_annotation("List[int]", app.env)
assert_node(doctree, ([pending_xref, "List"], assert_node(doctree, ([pending_xref, "List"],
[desc_sig_punctuation, "["], [desc_sig_punctuation, "["],
[pending_xref, "int"], [pending_xref, "int"],
[desc_sig_punctuation, "]"])) [desc_sig_punctuation, "]"]))
doctree = _parse_annotation("Tuple[int, int]") doctree = _parse_annotation("Tuple[int, int]", app.env)
assert_node(doctree, ([pending_xref, "Tuple"], assert_node(doctree, ([pending_xref, "Tuple"],
[desc_sig_punctuation, "["], [desc_sig_punctuation, "["],
[pending_xref, "int"], [pending_xref, "int"],
@@ -255,14 +255,14 @@ def test_parse_annotation():
[pending_xref, "int"], [pending_xref, "int"],
[desc_sig_punctuation, "]"])) [desc_sig_punctuation, "]"]))
doctree = _parse_annotation("Tuple[()]") doctree = _parse_annotation("Tuple[()]", app.env)
assert_node(doctree, ([pending_xref, "Tuple"], assert_node(doctree, ([pending_xref, "Tuple"],
[desc_sig_punctuation, "["], [desc_sig_punctuation, "["],
[desc_sig_punctuation, "("], [desc_sig_punctuation, "("],
[desc_sig_punctuation, ")"], [desc_sig_punctuation, ")"],
[desc_sig_punctuation, "]"])) [desc_sig_punctuation, "]"]))
doctree = _parse_annotation("Callable[[int, int], int]") doctree = _parse_annotation("Callable[[int, int], int]", app.env)
assert_node(doctree, ([pending_xref, "Callable"], assert_node(doctree, ([pending_xref, "Callable"],
[desc_sig_punctuation, "["], [desc_sig_punctuation, "["],
[desc_sig_punctuation, "["], [desc_sig_punctuation, "["],
@@ -275,12 +275,11 @@ def test_parse_annotation():
[desc_sig_punctuation, "]"])) [desc_sig_punctuation, "]"]))
# None type makes an object-reference (not a class reference) # None type makes an object-reference (not a class reference)
doctree = _parse_annotation("None") doctree = _parse_annotation("None", app.env)
assert_node(doctree, ([pending_xref, "None"],)) assert_node(doctree, ([pending_xref, "None"],))
assert_node(doctree[0], pending_xref, refdomain="py", reftype="obj", reftarget="None") assert_node(doctree[0], pending_xref, refdomain="py", reftype="obj", reftarget="None")
def test_pyfunction_signature(app): def test_pyfunction_signature(app):
text = ".. py:function:: hello(name: str) -> str" text = ".. py:function:: hello(name: str) -> str"
doctree = restructuredtext.parse(app, text) doctree = restructuredtext.parse(app, text)
@@ -458,14 +457,22 @@ def test_pyobject_prefix(app):
def test_pydata(app): def test_pydata(app):
text = ".. py:data:: var\n" text = (".. py:module:: example\n"
".. py:data:: var\n"
" :type: int\n")
domain = app.env.get_domain('py') domain = app.env.get_domain('py')
doctree = restructuredtext.parse(app, text) doctree = restructuredtext.parse(app, text)
assert_node(doctree, (addnodes.index, assert_node(doctree, (nodes.target,
[desc, ([desc_signature, desc_name, "var"], addnodes.index,
addnodes.index,
[desc, ([desc_signature, ([desc_addname, "example."],
[desc_name, "var"],
[desc_annotation, (": ",
[pending_xref, "int"])])],
[desc_content, ()])])) [desc_content, ()])]))
assert 'var' in domain.objects assert_node(doctree[3][0][2][1], pending_xref, **{"py:module": "example"})
assert domain.objects['var'] == ('index', 'var', 'data') assert 'example.var' in domain.objects
assert domain.objects['example.var'] == ('index', 'example.var', 'data')
def test_pyfunction(app): def test_pyfunction(app):
@@ -698,6 +705,8 @@ def test_pyattribute(app):
[desc_sig_punctuation, "]"])], [desc_sig_punctuation, "]"])],
[desc_annotation, " = ''"])], [desc_annotation, " = ''"])],
[desc_content, ()])) [desc_content, ()]))
assert_node(doctree[1][1][1][0][1][1], pending_xref, **{"py:class": "Class"})
assert_node(doctree[1][1][1][0][1][3], pending_xref, **{"py:class": "Class"})
assert 'Class.attr' in domain.objects assert 'Class.attr' in domain.objects
assert domain.objects['Class.attr'] == ('index', 'Class.attr', 'attribute') assert domain.objects['Class.attr'] == ('index', 'Class.attr', 'attribute')