Make sphinx.util.typing.stringify render optional unions better

Without this change, stringify(Optional[Union[int, str]]) returns
'Union[int, str, None]' rather than the expected 'Optional[...]'.

This change fixes that.

fixes: #7654
This commit is contained in:
John R. Lenton 2020-05-11 17:20:45 +01:00
parent 1771bbb927
commit 61378fe048
3 changed files with 22 additions and 6 deletions

View File

@ -91,11 +91,15 @@ def _stringify_py37(annotation: Any) -> str:
if getattr(annotation, '__args__', None): if getattr(annotation, '__args__', None):
if qualname == 'Union': if qualname == 'Union':
if len(annotation.__args__) == 2 and annotation.__args__[1] is NoneType: # type: ignore # NOQA if len(annotation.__args__) > 1 and annotation.__args__[-1] is NoneType: # type: ignore # NOQA
return 'Optional[%s]' % stringify(annotation.__args__[0]) if len(annotation.__args__) > 2:
args = ', '.join(stringify(a) for a in annotation.__args__[:-1])
return 'Optional[Union[%s]]' % args
else:
return 'Optional[%s]' % stringify(annotation.__args__[0])
else: else:
args = ', '.join(stringify(a) for a in annotation.__args__) args = ', '.join(stringify(a) for a in annotation.__args__)
return '%s[%s]' % (qualname, args) return 'Union[%s]' % args
elif qualname == 'Callable': elif qualname == 'Callable':
args = ', '.join(stringify(a) for a in annotation.__args__[:-1]) args = ', '.join(stringify(a) for a in annotation.__args__[:-1])
returns = stringify(annotation.__args__[-1]) returns = stringify(annotation.__args__[-1])
@ -170,8 +174,12 @@ def _stringify_py36(annotation: Any) -> str:
annotation.__origin__ is typing.Union): # for Python 3.5.2+ annotation.__origin__ is typing.Union): # for Python 3.5.2+
params = annotation.__args__ params = annotation.__args__
if params is not None: if params is not None:
if len(params) == 2 and params[1] is NoneType: # type: ignore if len(params) > 1 and params[-1] is NoneType: # type: ignore
return 'Optional[%s]' % stringify(params[0]) if len(params) > 2:
param_str = ", ".join(stringify(p) for p in params[:-1])
return 'Optional[Union[%s]]' % param_str
else:
return 'Optional[%s]' % stringify(params[0])
else: else:
param_str = ', '.join(stringify(p) for p in params) param_str = ', '.join(stringify(p) for p in params)
return 'Union[%s]' % param_str return 'Union[%s]' % param_str

View File

@ -127,7 +127,7 @@ def test_signature_partialmethod():
def test_signature_annotations(): def test_signature_annotations():
from typing_test_data import (f0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, from typing_test_data import (f0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10,
f11, f12, f13, f14, f15, f16, f17, f18, f19, Node) f11, f12, f13, f14, f15, f16, f17, f18, f19, f20, Node)
# Class annotations # Class annotations
sig = inspect.signature(f0) sig = inspect.signature(f0)
@ -184,6 +184,10 @@ def test_signature_annotations():
sig = inspect.signature(f13) sig = inspect.signature(f13)
assert stringify_signature(sig) == '() -> Optional[str]' assert stringify_signature(sig) == '() -> Optional[str]'
# optional union
sig = inspect.signature(f20)
assert stringify_signature(sig) == '() -> Optional[Union[int, str]]'
# Any # Any
sig = inspect.signature(f14) sig = inspect.signature(f14)
assert stringify_signature(sig) == '() -> Any' assert stringify_signature(sig) == '() -> Any'

View File

@ -96,6 +96,10 @@ def f19(*args: int, **kwargs: str):
pass pass
def f20() -> Optional[Union[int, str]]:
pass
class Node: class Node:
def __init__(self, parent: Optional['Node']) -> None: def __init__(self, parent: Optional['Node']) -> None: