summaryrefslogtreecommitdiff
path: root/sphinx/util/typing.py
diff options
context:
space:
mode:
authorTakeshi KOMIYA <i.tkomiya@gmail.com>2020-11-09 02:48:55 +0900
committerGitHub <noreply@github.com>2020-11-09 02:48:55 +0900
commit7db3633778a24c4fc90c8a0ceb2318d6df7cf4e3 (patch)
tree974466008fe9453f5171721c370a984e9f25f014 /sphinx/util/typing.py
parent5337e3848cce988d18a048aaa7a258b275d67e6d (diff)
parente2bf9166da3e89eb7cbe7abb9c1e47e34351bbf8 (diff)
downloadsphinx-git-7db3633778a24c4fc90c8a0ceb2318d6df7cf4e3.tar.gz
Merge pull request #8231 from tk0miya/8219_params_for_base_generic_class_is_not_shown
Fix #8219: autodoc: Parameters for generic base class are not shown
Diffstat (limited to 'sphinx/util/typing.py')
-rw-r--r--sphinx/util/typing.py195
1 files changed, 194 insertions, 1 deletions
diff --git a/sphinx/util/typing.py b/sphinx/util/typing.py
index 76438889b..7b3509b62 100644
--- a/sphinx/util/typing.py
+++ b/sphinx/util/typing.py
@@ -10,7 +10,7 @@
import sys
import typing
-from typing import Any, Callable, Dict, Generator, List, Tuple, TypeVar, Union
+from typing import Any, Callable, Dict, Generator, List, Optional, Tuple, TypeVar, Union
from docutils import nodes
from docutils.parsers.rst.states import Inliner
@@ -30,6 +30,10 @@ else:
ref = _ForwardRef(self.arg)
return ref._eval_type(globalns, localns)
+if False:
+ # For type annotation
+ from typing import Type # NOQA # for python3.5.1
+
# An entry of Directive.option_spec
DirectiveOption = Callable[[str], Any]
@@ -60,6 +64,195 @@ def is_system_TypeVar(typ: Any) -> bool:
return modname == 'typing' and isinstance(typ, TypeVar)
+def restify(cls: Optional["Type"]) -> str:
+ """Convert python class to a reST reference."""
+ if cls is None or cls is NoneType:
+ return ':obj:`None`'
+ elif cls is Ellipsis:
+ return '...'
+ elif cls.__module__ in ('__builtin__', 'builtins'):
+ return ':class:`%s`' % cls.__name__
+ else:
+ if sys.version_info >= (3, 7): # py37+
+ return _restify_py37(cls)
+ else:
+ return _restify_py36(cls)
+
+
+def _restify_py37(cls: Optional["Type"]) -> str:
+ """Convert python class to a reST reference."""
+ from sphinx.util import inspect # lazy loading
+
+ if (inspect.isgenericalias(cls) and
+ cls.__module__ == 'typing' and cls.__origin__ is Union):
+ # Union
+ if len(cls.__args__) > 1 and cls.__args__[-1] is NoneType:
+ if len(cls.__args__) > 2:
+ args = ', '.join(restify(a) for a in cls.__args__[:-1])
+ return ':obj:`Optional`\\ [:obj:`Union`\\ [%s]]' % args
+ else:
+ return ':obj:`Optional`\\ [%s]' % restify(cls.__args__[0])
+ else:
+ args = ', '.join(restify(a) for a in cls.__args__)
+ return ':obj:`Union`\\ [%s]' % args
+ elif inspect.isgenericalias(cls):
+ if getattr(cls, '_name', None):
+ if cls.__module__ == 'typing':
+ text = ':class:`%s`' % cls._name
+ else:
+ text = ':class:`%s.%s`' % (cls.__module__, cls._name)
+ else:
+ text = restify(cls.__origin__)
+
+ if not hasattr(cls, '__args__'):
+ pass
+ elif all(is_system_TypeVar(a) for a in cls.__args__):
+ # Suppress arguments if all system defined TypeVars (ex. Dict[KT, VT])
+ pass
+ elif cls.__module__ == 'typing' and cls._name == 'Callable':
+ args = ', '.join(restify(a) for a in cls.__args__[:-1])
+ text += r"\ [[%s], %s]" % (args, restify(cls.__args__[-1]))
+ elif cls.__args__:
+ text += r"\ [%s]" % ", ".join(restify(a) for a in cls.__args__)
+
+ return text
+ elif hasattr(cls, '__qualname__'):
+ if cls.__module__ == 'typing':
+ return ':class:`%s`' % cls.__qualname__
+ else:
+ return ':class:`%s.%s`' % (cls.__module__, cls.__qualname__)
+ elif hasattr(cls, '_name'):
+ # SpecialForm
+ if cls.__module__ == 'typing':
+ return ':obj:`%s`' % cls._name
+ else:
+ return ':obj:`%s.%s`' % (cls.__module__, cls._name)
+ else:
+ # not a class (ex. TypeVar)
+ return ':obj:`%s.%s`' % (cls.__module__, cls.__name__)
+
+
+def _restify_py36(cls: Optional["Type"]) -> str:
+ module = getattr(cls, '__module__', None)
+ if module == 'typing':
+ if getattr(cls, '_name', None):
+ qualname = cls._name
+ elif getattr(cls, '__qualname__', None):
+ qualname = cls.__qualname__
+ elif getattr(cls, '__forward_arg__', None):
+ qualname = cls.__forward_arg__
+ elif getattr(cls, '__origin__', None):
+ qualname = stringify(cls.__origin__) # ex. Union
+ else:
+ qualname = repr(cls).replace('typing.', '')
+ elif hasattr(cls, '__qualname__'):
+ qualname = '%s.%s' % (module, cls.__qualname__)
+ else:
+ qualname = repr(cls)
+
+ if (isinstance(cls, typing.TupleMeta) and # type: ignore
+ not hasattr(cls, '__tuple_params__')): # for Python 3.6
+ params = cls.__args__
+ if params:
+ param_str = ', '.join(restify(p) for p in params)
+ return ':class:`%s`\\ [%s]' % (qualname, param_str)
+ else:
+ return ':class:`%s`' % qualname
+ elif isinstance(cls, typing.GenericMeta):
+ params = None
+ if hasattr(cls, '__args__'):
+ # for Python 3.5.2+
+ if cls.__args__ is None or len(cls.__args__) <= 2: # type: ignore # NOQA
+ params = cls.__args__ # type: ignore
+ elif cls.__origin__ == Generator: # type: ignore
+ params = cls.__args__ # type: ignore
+ else: # typing.Callable
+ args = ', '.join(restify(arg) for arg in cls.__args__[:-1]) # type: ignore
+ result = restify(cls.__args__[-1]) # type: ignore
+ return ':class:`%s`\\ [[%s], %s]' % (qualname, args, result)
+ elif hasattr(cls, '__parameters__'):
+ # for Python 3.5.0 and 3.5.1
+ params = cls.__parameters__ # type: ignore
+
+ if params:
+ param_str = ', '.join(restify(p) for p in params)
+ return ':class:`%s`\\ [%s]' % (qualname, param_str)
+ else:
+ return ':class:`%s`' % qualname
+ elif (hasattr(typing, 'UnionMeta') and
+ isinstance(cls, typing.UnionMeta) and # type: ignore
+ hasattr(cls, '__union_params__')): # for Python 3.5
+ params = cls.__union_params__
+ if params is not None:
+ if len(params) == 2 and params[1] is NoneType:
+ return ':obj:`Optional`\\ [%s]' % restify(params[0])
+ else:
+ param_str = ', '.join(restify(p) for p in params)
+ return ':obj:`%s`\\ [%s]' % (qualname, param_str)
+ else:
+ return ':obj:`%s`' % qualname
+ elif (hasattr(cls, '__origin__') and
+ cls.__origin__ is typing.Union): # for Python 3.5.2+
+ params = cls.__args__
+ if params is not None:
+ if len(params) > 1 and params[-1] is NoneType:
+ if len(params) > 2:
+ param_str = ", ".join(restify(p) for p in params[:-1])
+ return ':obj:`Optional`\\ [:obj:`Union`\\ [%s]]' % param_str
+ else:
+ return ':obj:`Optional`\\ [%s]' % restify(params[0])
+ else:
+ param_str = ', '.join(restify(p) for p in params)
+ return ':obj:`Union`\\ [%s]' % param_str
+ else:
+ return ':obj:`Union`'
+ elif (isinstance(cls, typing.CallableMeta) and # type: ignore
+ getattr(cls, '__args__', None) is not None and
+ hasattr(cls, '__result__')): # for Python 3.5
+ # Skipped in the case of plain typing.Callable
+ args = cls.__args__
+ if args is None:
+ return qualname
+ elif args is Ellipsis:
+ args_str = '...'
+ else:
+ formatted_args = (restify(a) for a in args) # type: ignore
+ args_str = '[%s]' % ', '.join(formatted_args)
+
+ return ':class:`%s`\\ [%s, %s]' % (qualname, args_str, stringify(cls.__result__))
+ elif (isinstance(cls, typing.TupleMeta) and # type: ignore
+ hasattr(cls, '__tuple_params__') and
+ hasattr(cls, '__tuple_use_ellipsis__')): # for Python 3.5
+ params = cls.__tuple_params__
+ if params is not None:
+ param_strings = [restify(p) for p in params]
+ if cls.__tuple_use_ellipsis__:
+ param_strings.append('...')
+ return ':class:`%s`\\ [%s]' % (qualname, ', '.join(param_strings))
+ else:
+ return ':class:`%s`' % qualname
+ elif hasattr(cls, '__qualname__'):
+ if cls.__module__ == 'typing':
+ return ':class:`%s`' % cls.__qualname__
+ else:
+ return ':class:`%s.%s`' % (cls.__module__, cls.__qualname__)
+ elif hasattr(cls, '_name'):
+ # SpecialForm
+ if cls.__module__ == 'typing':
+ return ':obj:`%s`' % cls._name
+ else:
+ return ':obj:`%s.%s`' % (cls.__module__, cls._name)
+ elif hasattr(cls, '__name__'):
+ # not a class (ex. TypeVar)
+ return ':obj:`%s.%s`' % (cls.__module__, cls.__name__)
+ else:
+ # others (ex. Any)
+ if cls.__module__ == 'typing':
+ return ':obj:`%s`' % qualname
+ else:
+ return ':obj:`%s.%s`' % (cls.__module__, qualname)
+
+
def stringify(annotation: Any) -> str:
"""Stringify type annotation object."""
if isinstance(annotation, str):