Fix autodoc: Optional types are wrongly rendered

This commit is contained in:
Takeshi KOMIYA 2018-08-15 00:32:14 +09:00
parent caf57bcc89
commit e26dc81272
4 changed files with 63 additions and 36 deletions

View File

@ -27,6 +27,7 @@ Bugs fixed
* LaTeX: fix the :confval:`latex_engine` documentation regarding Latin Modern
font with XeLaTeX/LuaLateX (refs: #5251)
* #5280: autodoc: Fix wrong type annotations for complex typing
* autodoc: Optional types are wrongly rendered
Testing
--------

View File

@ -486,8 +486,11 @@ class Signature(object):
if getattr(annotation, '__args__', None):
if qualname == 'Union':
args = ', '.join(self.format_annotation(a) for a in annotation.__args__)
return '%s[%s]' % (qualname, args)
if len(annotation.__args__) == 2 and annotation.__args__[1] is NoneType:
return 'Optional[%s]' % self.format_annotation(annotation.__args__[0])
else:
args = ', '.join(self.format_annotation(a) for a in annotation.__args__)
return '%s[%s]' % (qualname, args)
elif qualname == 'Callable':
args = ', '.join(self.format_annotation(a) for a in annotation.__args__[:-1])
returns = self.format_annotation(annotation.__args__[-1])
@ -501,38 +504,39 @@ class Signature(object):
def format_annotation_old(self, annotation):
# type: (Any) -> str
"""format_annotation() for py36 or below"""
module = getattr(annotation, '__module__', None)
if isinstance(annotation, string_types):
return annotation # type: ignore
if isinstance(annotation, typing.TypeVar): # type: ignore
elif isinstance(annotation, typing.TypeVar): # type: ignore
return annotation.__name__
if annotation == Ellipsis:
return '...'
if not isinstance(annotation, type):
qualified_name = repr(annotation)
if qualified_name.startswith('typing.'): # for typing.Union
return qualified_name.split('.', 1)[1]
else:
return qualified_name
if not annotation:
qualified_name = repr(annotation)
elif annotation.__module__ == 'typing':
qualified_name = annotation.__qualname__ # type: ignore
else:
qualified_name = (annotation.__module__ + '.' + annotation.__qualname__) # type: ignore # NOQA
if annotation is NoneType: # type: ignore
elif not annotation:
return repr(annotation)
elif annotation is NoneType: # type: ignore
return 'None'
elif module == 'builtins':
return annotation.__qualname__
elif annotation is Ellipsis:
return '...'
if annotation.__module__ == 'builtins':
return annotation.__qualname__ # type: ignore
elif (hasattr(typing, 'TupleMeta') and
isinstance(annotation, typing.TupleMeta) and # type: ignore
not hasattr(annotation, '__tuple_params__')):
if module == 'typing':
if getattr(annotation, '_name', None):
qualname = annotation._name
elif getattr(annotation, '__qualname__', None):
qualname = annotation.__qualname__
else:
qualname = self.format_annotation(annotation.__origin__) # ex. Union
elif hasattr(annotation, '__qualname__'):
qualname = '%s.%s' % (module, annotation.__qualname__)
else:
qualname = repr(annotation)
if (hasattr(typing, 'TupleMeta') and
isinstance(annotation, typing.TupleMeta) and # type: ignore
not hasattr(annotation, '__tuple_params__')):
# This is for Python 3.6+, 3.5 case is handled below
params = annotation.__args__
param_str = ', '.join(self.format_annotation(p) for p in params)
return '%s[%s]' % (qualified_name, param_str)
return '%s[%s]' % (qualname, param_str)
elif (hasattr(typing, 'GenericMeta') and # for py36 or below
isinstance(annotation, typing.GenericMeta)):
# In Python 3.5.2+, all arguments are stored in __args__,
@ -548,19 +552,32 @@ class Signature(object):
args = ', '.join(self.format_annotation(arg) for arg
in annotation.__args__[:-1]) # type: ignore
result = self.format_annotation(annotation.__args__[-1]) # type: ignore
return '%s[[%s], %s]' % (qualified_name, args, result)
return '%s[[%s], %s]' % (qualname, args, result)
elif hasattr(annotation, '__parameters__'):
params = annotation.__parameters__ # type: ignore
if params is not None:
param_str = ', '.join(self.format_annotation(p) for p in params)
return '%s[%s]' % (qualified_name, param_str)
return '%s[%s]' % (qualname, param_str)
elif (hasattr(typing, 'UnionMeta') and # for py35 or below
isinstance(annotation, typing.UnionMeta) and # type: ignore
hasattr(annotation, '__union_params__')):
params = annotation.__union_params__
if params is not None:
param_str = ', '.join(self.format_annotation(p) for p in params)
return '%s[%s]' % (qualified_name, param_str)
if len(params) == 2 and params[1] is NoneType:
return 'Optional[%s]' % self.format_annotation(params[0])
else:
param_str = ', '.join(self.format_annotation(p) for p in params)
return '%s[%s]' % (qualname, param_str)
elif (hasattr(typing, 'Union') and # for py36
hasattr(annotation, '__origin__') and
annotation.__origin__ is typing.Union):
params = annotation.__args__
if params is not None:
if len(params) == 2 and params[1] is NoneType:
return 'Optional[%s]' % self.format_annotation(params[0])
else:
param_str = ', '.join(self.format_annotation(p) for p in params)
return 'Union[%s]' % param_str
elif (hasattr(typing, 'CallableMeta') and # for py36 or below
isinstance(annotation, typing.CallableMeta) and # type: ignore
getattr(annotation, '__args__', None) is not None and
@ -568,13 +585,13 @@ class Signature(object):
# Skipped in the case of plain typing.Callable
args = annotation.__args__
if args is None:
return qualified_name
return qualname
elif args is Ellipsis:
args_str = '...'
else:
formatted_args = (self.format_annotation(a) for a in args)
args_str = '[%s]' % ', '.join(formatted_args)
return '%s[%s, %s]' % (qualified_name,
return '%s[%s, %s]' % (qualname,
args_str,
self.format_annotation(annotation.__result__))
elif (hasattr(typing, 'TupleMeta') and # for py36 or below
@ -586,10 +603,10 @@ class Signature(object):
param_strings = [self.format_annotation(p) for p in params]
if annotation.__tuple_use_ellipsis__:
param_strings.append('...')
return '%s[%s]' % (qualified_name,
return '%s[%s]' % (qualname,
', '.join(param_strings))
return qualified_name
return qualname
if sys.version_info >= (3, 5):

View File

@ -232,7 +232,7 @@ def test_Signature_partialmethod():
reason='type annotation test is available on py35 or above')
def test_Signature_annotations():
from typing_test_data import (
f0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, Node)
f0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, Node)
# Class annotations
sig = inspect.Signature(f0).format_args()
@ -289,6 +289,11 @@ def test_Signature_annotations():
sig = inspect.Signature(f12).format_args()
assert sig == '() -> Tuple[int, str, int]'
# optional
sig = inspect.Signature(f13).format_args()
assert sig == '() -> Optional[str]'
# type hints by string
sig = inspect.Signature(Node.children).format_args()
assert sig == '(self) -> List[typing_test_data.Node]'

View File

@ -1,5 +1,5 @@
from numbers import Integral
from typing import List, TypeVar, Union, Callable, Tuple
from typing import List, TypeVar, Union, Callable, Tuple, Optional
def f0(x: int, y: Integral) -> None:
@ -68,6 +68,10 @@ def f12() -> Tuple[int, str, int]:
pass
def f13() -> Optional[str]:
pass
class Node:
def children(self) -> List['Node']:
pass