From 86091934db5ec593b4b0c982b7f08f3231ef995b Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Mon, 10 May 2021 01:22:38 +0900 Subject: [PATCH] Fix #9195: autodoc: The args of `typing.Literal` are wrongly rendered They should be rendered as "repr" form. --- CHANGES | 1 + sphinx/util/typing.py | 6 ++++++ tests/test_util_typing.py | 12 ++++++++++++ 3 files changed, 19 insertions(+) diff --git a/CHANGES b/CHANGES index 026215a95de..c29e475e2be 100644 --- a/CHANGES +++ b/CHANGES @@ -24,6 +24,7 @@ Features added allows you to define an alias for a class with module name like ``foo.bar.BazClass`` * #9175: autodoc: Special member is not documented in the module +* #9195: autodoc: The arguments of ``typing.Literal`` are wrongly rendered * #3257: autosummary: Support instance attributes for classes * #9129: html search: Show search summaries when html_copy_source = False * #9120: html theme: Eliminate prompt characters of code-block from copyable diff --git a/sphinx/util/typing.py b/sphinx/util/typing.py index fb8daa62335..af6edcae001 100644 --- a/sphinx/util/typing.py +++ b/sphinx/util/typing.py @@ -153,6 +153,7 @@ def _restify_py37(cls: Optional[Type]) -> str: else: text = restify(cls.__origin__) + origin = getattr(cls, '__origin__', None) if not hasattr(cls, '__args__'): pass elif all(is_system_TypeVar(a) for a in cls.__args__): @@ -161,6 +162,8 @@ def _restify_py37(cls: Optional[Type]) -> str: 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.__module__ == 'typing' and getattr(origin, '_name', None) == 'Literal': + text += r"\ [%s]" % ', '.join(repr(a) for a in cls.__args__) elif cls.__args__: text += r"\ [%s]" % ", ".join(restify(a) for a in cls.__args__) @@ -362,6 +365,9 @@ def _stringify_py37(annotation: Any) -> str: args = ', '.join(stringify(a) for a in annotation.__args__[:-1]) returns = stringify(annotation.__args__[-1]) return '%s[[%s], %s]' % (qualname, args, returns) + elif qualname == 'Literal': + args = ', '.join(repr(a) for a in annotation.__args__) + return '%s[%s]' % (qualname, args) elif str(annotation).startswith('typing.Annotated'): # for py39+ return stringify(annotation.__args__[0]) elif all(is_system_TypeVar(a) for a in annotation.__args__): diff --git a/tests/test_util_typing.py b/tests/test_util_typing.py index d85eb0849bd..d3672f2cc64 100644 --- a/tests/test_util_typing.py +++ b/tests/test_util_typing.py @@ -133,6 +133,12 @@ def test_restify_type_ForwardRef(): assert restify(ForwardRef("myint")) == ":class:`myint`" +@pytest.mark.skipif(sys.version_info < (3, 8), reason='python 3.8+ is required.') +def test_restify_type_Literal(): + from typing import Literal # type: ignore + assert restify(Literal[1, "2", "\r"]) == ":obj:`~typing.Literal`\\ [1, '2', '\\r']" + + @pytest.mark.skipif(sys.version_info < (3, 10), reason='python 3.10+ is required.') def test_restify_type_union_operator(): assert restify(int | None) == "Optional[:class:`int`]" # type: ignore @@ -237,6 +243,12 @@ def test_stringify_type_hints_alias(): assert stringify(MyTuple) == "Tuple[str, str]" # type: ignore +@pytest.mark.skipif(sys.version_info < (3, 8), reason='python 3.8+ is required.') +def test_stringify_type_Literal(): + from typing import Literal # type: ignore + assert stringify(Literal[1, "2", "\r"]) == "Literal[1, '2', '\\r']" + + @pytest.mark.skipif(sys.version_info < (3, 10), reason='python 3.10+ is required.') def test_stringify_type_union_operator(): assert stringify(int | None) == "Optional[int]" # type: ignore