mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Merge pull request #10021 from tk0miya/10015_typehints_format_with_typehints_in_description
Fix #10015: autodoc: autodoc_typehints_format='short' does not work when autodoc_typehints='description'
This commit is contained in:
2
CHANGES
2
CHANGES
@@ -62,6 +62,8 @@ Bugs fixed
|
||||
* #9944: LaTeX: extra vertical whitespace for some nested declarations
|
||||
* #9940: LaTeX: Multi-function declaration in Python domain has cramped
|
||||
vertical spacing in latexpdf output
|
||||
* #10015: py domain: types under the "typing" module are not hyperlinked defined
|
||||
at info-field-list
|
||||
* #9390: texinfo: Do not emit labels inside footnotes
|
||||
* #9979: Error level messages were displayed as warning messages
|
||||
|
||||
|
||||
@@ -80,45 +80,53 @@ class ModuleEntry(NamedTuple):
|
||||
deprecated: bool
|
||||
|
||||
|
||||
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' or target.startswith('typing.'):
|
||||
def parse_reftarget(reftarget: str, suppress_prefix: bool = False
|
||||
) -> Tuple[str, str, str, bool]:
|
||||
"""Parse a type string and return (reftype, reftarget, title, refspecific flag)"""
|
||||
refspecific = False
|
||||
if reftarget.startswith('.'):
|
||||
reftarget = reftarget[1:]
|
||||
title = reftarget
|
||||
refspecific = True
|
||||
elif reftarget.startswith('~'):
|
||||
reftarget = reftarget[1:]
|
||||
title = reftarget.split('.')[-1]
|
||||
elif suppress_prefix:
|
||||
title = reftarget.split('.')[-1]
|
||||
elif reftarget.startswith('typing.'):
|
||||
title = reftarget[7:]
|
||||
else:
|
||||
title = reftarget
|
||||
|
||||
if reftarget == 'None' or reftarget.startswith('typing.'):
|
||||
# typing module provides non-class types. Obj reference is good to refer them.
|
||||
reftype = 'obj'
|
||||
else:
|
||||
reftype = 'class'
|
||||
|
||||
return reftype, reftarget, title, refspecific
|
||||
|
||||
|
||||
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 env:
|
||||
kwargs = {'py:module': env.ref_context.get('py:module'),
|
||||
'py:class': env.ref_context.get('py:class')}
|
||||
else:
|
||||
kwargs = {}
|
||||
|
||||
refspecific = False
|
||||
if target.startswith('.'):
|
||||
target = target[1:]
|
||||
text = target
|
||||
refspecific = True
|
||||
elif target.startswith('~'):
|
||||
target = target[1:]
|
||||
text = target.split('.')[-1]
|
||||
elif suppress_prefix:
|
||||
text = target.split('.')[-1]
|
||||
elif target.startswith('typing.'):
|
||||
text = target[7:]
|
||||
else:
|
||||
text = target
|
||||
reftype, target, title, refspecific = parse_reftarget(target, suppress_prefix)
|
||||
|
||||
if env.config.python_use_unqualified_type_names:
|
||||
# Note: It would be better to use qualname to describe the object to support support
|
||||
# nested classes. But python domain can't access the real python object because this
|
||||
# module should work not-dynamically.
|
||||
shortname = text.split('.')[-1]
|
||||
shortname = title.split('.')[-1]
|
||||
contnodes: List[Node] = [pending_xref_condition('', shortname, condition='resolved'),
|
||||
pending_xref_condition('', text, condition='*')]
|
||||
pending_xref_condition('', title, condition='*')]
|
||||
else:
|
||||
contnodes = [nodes.Text(text)]
|
||||
contnodes = [nodes.Text(title)]
|
||||
|
||||
return pending_xref('', *contnodes,
|
||||
refdomain='py', reftype=reftype, reftarget=target,
|
||||
@@ -354,27 +362,27 @@ class PyXrefMixin:
|
||||
result = super().make_xref(rolename, domain, target, # type: ignore
|
||||
innernode, contnode,
|
||||
env, inliner=None, location=None)
|
||||
result['refspecific'] = True
|
||||
result['py:module'] = env.ref_context.get('py:module')
|
||||
result['py:class'] = env.ref_context.get('py:class')
|
||||
if target.startswith(('.', '~')):
|
||||
prefix, result['reftarget'] = target[0], target[1:]
|
||||
if prefix == '.':
|
||||
text = target[1:]
|
||||
elif prefix == '~':
|
||||
text = target.split('.')[-1]
|
||||
for node in list(result.traverse(nodes.Text)):
|
||||
node.parent[node.parent.index(node)] = nodes.Text(text)
|
||||
break
|
||||
elif isinstance(result, pending_xref) and env.config.python_use_unqualified_type_names:
|
||||
children = result.children
|
||||
result.clear()
|
||||
if isinstance(result, pending_xref):
|
||||
result['refspecific'] = True
|
||||
result['py:module'] = env.ref_context.get('py:module')
|
||||
result['py:class'] = env.ref_context.get('py:class')
|
||||
|
||||
shortname = target.split('.')[-1]
|
||||
textnode = innernode('', shortname)
|
||||
contnodes = [pending_xref_condition('', '', textnode, condition='resolved'),
|
||||
pending_xref_condition('', '', *children, condition='*')]
|
||||
result.extend(contnodes)
|
||||
reftype, reftarget, reftitle, _ = parse_reftarget(target)
|
||||
if reftarget != reftitle:
|
||||
result['reftype'] = reftype
|
||||
result['reftarget'] = reftarget
|
||||
|
||||
result.clear()
|
||||
result += innernode(reftitle, reftitle)
|
||||
elif env.config.python_use_unqualified_type_names:
|
||||
children = result.children
|
||||
result.clear()
|
||||
|
||||
shortname = target.split('.')[-1]
|
||||
textnode = innernode('', shortname)
|
||||
contnodes = [pending_xref_condition('', '', textnode, condition='resolved'),
|
||||
pending_xref_condition('', '', *children, condition='*')]
|
||||
result.extend(contnodes)
|
||||
|
||||
return result
|
||||
|
||||
@@ -407,16 +415,7 @@ class PyXrefMixin:
|
||||
|
||||
|
||||
class PyField(PyXrefMixin, Field):
|
||||
def make_xref(self, rolename: str, domain: str, target: str,
|
||||
innernode: Type[TextlikeNode] = nodes.emphasis,
|
||||
contnode: Node = None, env: BuildEnvironment = None,
|
||||
inliner: Inliner = None, location: Node = None) -> Node:
|
||||
if rolename == 'class' and target == 'None':
|
||||
# None is not a type, so use obj role instead.
|
||||
rolename = 'obj'
|
||||
|
||||
return super().make_xref(rolename, domain, target, innernode, contnode,
|
||||
env, inliner, location)
|
||||
pass
|
||||
|
||||
|
||||
class PyGroupedField(PyXrefMixin, GroupedField):
|
||||
@@ -424,16 +423,7 @@ class PyGroupedField(PyXrefMixin, GroupedField):
|
||||
|
||||
|
||||
class PyTypedField(PyXrefMixin, TypedField):
|
||||
def make_xref(self, rolename: str, domain: str, target: str,
|
||||
innernode: Type[TextlikeNode] = nodes.emphasis,
|
||||
contnode: Node = None, env: BuildEnvironment = None,
|
||||
inliner: Inliner = None, location: Node = None) -> Node:
|
||||
if rolename == 'class' and target == 'None':
|
||||
# None is not a type, so use obj role instead.
|
||||
rolename = 'obj'
|
||||
|
||||
return super().make_xref(rolename, domain, target, innernode, contnode,
|
||||
env, inliner, location)
|
||||
pass
|
||||
|
||||
|
||||
class PyObject(ObjectDescription[Tuple[str, str]]):
|
||||
|
||||
@@ -23,6 +23,11 @@ from sphinx.util import inspect, typing
|
||||
def record_typehints(app: Sphinx, objtype: str, name: str, obj: Any,
|
||||
options: Dict, args: str, retann: str) -> None:
|
||||
"""Record type hints to env object."""
|
||||
if app.config.autodoc_typehints_format == 'short':
|
||||
mode = 'smart'
|
||||
else:
|
||||
mode = 'fully-qualified'
|
||||
|
||||
try:
|
||||
if callable(obj):
|
||||
annotations = app.env.temp_data.setdefault('annotations', {})
|
||||
@@ -30,9 +35,9 @@ def record_typehints(app: Sphinx, objtype: str, name: str, obj: Any,
|
||||
sig = inspect.signature(obj, type_aliases=app.config.autodoc_type_aliases)
|
||||
for param in sig.parameters.values():
|
||||
if param.annotation is not param.empty:
|
||||
annotation[param.name] = typing.stringify(param.annotation)
|
||||
annotation[param.name] = typing.stringify(param.annotation, mode)
|
||||
if sig.return_annotation is not sig.empty:
|
||||
annotation['return'] = typing.stringify(sig.return_annotation)
|
||||
annotation['return'] = typing.stringify(sig.return_annotation, mode)
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
|
||||
|
||||
@@ -1196,7 +1196,9 @@ def test_type_field(app):
|
||||
text = (".. py:data:: var1\n"
|
||||
" :type: .int\n"
|
||||
".. py:data:: var2\n"
|
||||
" :type: ~builtins.int\n")
|
||||
" :type: ~builtins.int\n"
|
||||
".. py:data:: var3\n"
|
||||
" :type: typing.Optional[typing.Tuple[int, typing.Any]]\n")
|
||||
doctree = restructuredtext.parse(app, text)
|
||||
assert_node(doctree, (addnodes.index,
|
||||
[desc, ([desc_signature, ([desc_name, "var1"],
|
||||
@@ -1209,9 +1211,28 @@ def test_type_field(app):
|
||||
[desc_annotation, ([desc_sig_punctuation, ':'],
|
||||
desc_sig_space,
|
||||
[pending_xref, "int"])])],
|
||||
[desc_content, ()])],
|
||||
addnodes.index,
|
||||
[desc, ([desc_signature, ([desc_name, "var3"],
|
||||
[desc_annotation, ([desc_sig_punctuation, ":"],
|
||||
desc_sig_space,
|
||||
[pending_xref, "Optional"],
|
||||
[desc_sig_punctuation, "["],
|
||||
[pending_xref, "Tuple"],
|
||||
[desc_sig_punctuation, "["],
|
||||
[pending_xref, "int"],
|
||||
[desc_sig_punctuation, ","],
|
||||
desc_sig_space,
|
||||
[pending_xref, "Any"],
|
||||
[desc_sig_punctuation, "]"],
|
||||
[desc_sig_punctuation, "]"])])],
|
||||
[desc_content, ()])]))
|
||||
assert_node(doctree[1][0][1][2], pending_xref, reftarget='int', refspecific=True)
|
||||
assert_node(doctree[3][0][1][2], pending_xref, reftarget='builtins.int', refspecific=False)
|
||||
assert_node(doctree[5][0][1][2], pending_xref, reftarget='typing.Optional', refspecific=False)
|
||||
assert_node(doctree[5][0][1][4], pending_xref, reftarget='typing.Tuple', refspecific=False)
|
||||
assert_node(doctree[5][0][1][6], pending_xref, reftarget='int', refspecific=False)
|
||||
assert_node(doctree[5][0][1][9], pending_xref, reftarget='typing.Any', refspecific=False)
|
||||
|
||||
|
||||
@pytest.mark.sphinx(freshenv=True)
|
||||
|
||||
@@ -835,7 +835,7 @@ def test_autodoc_typehints_description(app):
|
||||
' **x** (*Tuple**[**int**, **Union**[**int**, **str**]**]*) --\n'
|
||||
'\n'
|
||||
' Return type:\n'
|
||||
' Tuple[int, int]\n'
|
||||
' *Tuple*[int, int]\n'
|
||||
in context)
|
||||
|
||||
# Overloads still get displayed in the signature
|
||||
@@ -887,7 +887,7 @@ def test_autodoc_typehints_description_no_undoc(app):
|
||||
' another tuple\n'
|
||||
'\n'
|
||||
' Return type:\n'
|
||||
' Tuple[int, int]\n'
|
||||
' *Tuple*[int, int]\n'
|
||||
in context)
|
||||
|
||||
|
||||
@@ -978,7 +978,7 @@ def test_autodoc_typehints_both(app):
|
||||
' **x** (*Tuple**[**int**, **Union**[**int**, **str**]**]*) --\n'
|
||||
'\n'
|
||||
' Return type:\n'
|
||||
' Tuple[int, int]\n'
|
||||
' *Tuple*[int, int]\n'
|
||||
in context)
|
||||
|
||||
# Overloads still get displayed in the signature
|
||||
|
||||
Reference in New Issue
Block a user