Skip to content

Commit

Permalink
Fix rendering of type annotations with Literal enumeration values
Browse files Browse the repository at this point in the history
  • Loading branch information
picnixz committed Jul 25, 2023
1 parent 5cf3dce commit 1173b31
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 2 deletions.
3 changes: 3 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ Features added
Bugs fixed
----------

* #11473: Type annotations containing :py:data:`~typing.Literal` enumeration
values now render correctly. Patch by Bénédikt Tran.

Testing
-------

Expand Down
20 changes: 18 additions & 2 deletions sphinx/util/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,16 @@ def restify(cls: type | None, mode: str = 'fully-qualified-except-typing') -> st
args = ', '.join(restify(a, mode) for a in cls.__args__[:-1])
text += fr"\ [[{args}], {restify(cls.__args__[-1], mode)}]"
elif cls.__module__ == 'typing' and getattr(origin, '_name', None) == 'Literal':
text += r"\ [%s]" % ', '.join(repr(a) for a in cls.__args__)
def format_literal_arg(arg):
if inspect.isenumattribute(arg):
enumcls = arg.__class__
refname = f'{enumcls.__module__}.{enumcls.__name__}.{arg.name}'
if enumcls.__module__ == 'typing':
return f':py:attr:`~{refname}`'
return f':py:attr:`{modprefix}{refname}`'
return repr(arg)

text += r"\ [%s]" % ', '.join(map(format_literal_arg, cls.__args__))
elif cls.__args__:
text += r"\ [%s]" % ", ".join(restify(a, mode) for a in cls.__args__)

Expand Down Expand Up @@ -329,7 +338,14 @@ def stringify_annotation(
returns = stringify_annotation(annotation_args[-1], mode)
return f'{module_prefix}Callable[[{args}], {returns}]'
elif qualname == 'Literal':
args = ', '.join(repr(a) for a in annotation_args)
from sphinx.util.inspect import isenumattribute # lazy loading

def format_literal_arg(arg):
if isenumattribute(arg):
return stringify_annotation(arg.__class__, mode) + '.' + arg.name
return repr(arg)

args = ', '.join(map(format_literal_arg, annotation_args))
return f'{module_prefix}Literal[{args}]'
elif str(annotation).startswith('typing.Annotated'): # for py39+
return stringify_annotation(annotation_args[0], mode)
Expand Down
14 changes: 14 additions & 0 deletions tests/test_util_typing.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Tests util.typing functions."""

import sys
from enum import Enum
from numbers import Integral
from struct import Struct
from types import TracebackType
Expand Down Expand Up @@ -31,6 +32,10 @@ class MyClass2(MyClass1):
__qualname__ = '<MyClass2>'


class MyEnum(Enum):
a = 1


T = TypeVar('T')
MyInt = NewType('MyInt', int)

Expand Down Expand Up @@ -183,8 +188,12 @@ def test_restify_type_ForwardRef():

def test_restify_type_Literal():
from typing import Literal # type: ignore

assert restify(Literal[1, "2", "\r"]) == ":py:obj:`~typing.Literal`\\ [1, '2', '\\r']"

assert restify(Literal[MyEnum.a]) == ':py:obj:`~typing.Literal`\\ [:py:attr:`tests.test_util_typing.MyEnum.a`]'
assert restify(Literal[MyEnum.a], 'smart') == ':py:obj:`~typing.Literal`\\ [:py:attr:`~tests.test_util_typing.MyEnum.a`]'


def test_restify_pep_585():
assert restify(list[str]) == ":py:class:`list`\\ [:py:class:`str`]" # type: ignore
Expand Down Expand Up @@ -430,10 +439,15 @@ def test_stringify_type_hints_alias():

def test_stringify_type_Literal():
from typing import Literal # type: ignore

assert stringify_annotation(Literal[1, "2", "\r"], 'fully-qualified-except-typing') == "Literal[1, '2', '\\r']"
assert stringify_annotation(Literal[1, "2", "\r"], "fully-qualified") == "typing.Literal[1, '2', '\\r']"
assert stringify_annotation(Literal[1, "2", "\r"], "smart") == "~typing.Literal[1, '2', '\\r']"

assert stringify_annotation(Literal[MyEnum.a], 'fully-qualified-except-typing') == 'Literal[tests.test_util_typing.MyEnum.a]'
assert stringify_annotation(Literal[MyEnum.a], 'fully-qualified') == 'typing.Literal[tests.test_util_typing.MyEnum.a]'
assert stringify_annotation(Literal[MyEnum.a], 'smart') == '~typing.Literal[~tests.test_util_typing.MyEnum.a]'


@pytest.mark.skipif(sys.version_info[:2] <= (3, 9), reason='python 3.10+ is required.')
def test_stringify_type_union_operator():
Expand Down

0 comments on commit 1173b31

Please sign in to comment.