Skip to content

Commit

Permalink
fix Unpack
Browse files Browse the repository at this point in the history
  • Loading branch information
picnixz committed Apr 10, 2024
1 parent 410a67c commit d6c47f7
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 11 deletions.
11 changes: 9 additions & 2 deletions sphinx/util/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,13 @@ def _is_unpack_form(obj: Any) -> bool: # cannot use type guards
"""Check if the object is :class:`typing.Unpack` or equivalent."""
origin = typing.get_origin(obj)
__module__ = getattr(origin, '__module__', None)
__qualname__ = getattr(origin, '__qualname__', None)
return __module__ in {'typing', 'typing_extensions'} and __qualname__ == 'Unpack'
if __module__ == 'typing':
return getattr(origin, '__qualname__', None) == 'Unpack'

if __module__ == 'typing_extensions':
return getattr(origin, '_name', None) == 'Unpack'

return False


def _is_annotated_form(obj: Any) -> TypeGuard[Annotated[Any, ...]]:
Expand Down Expand Up @@ -412,6 +417,8 @@ def stringify_annotation(
module_prefix = '~' + module_prefix
if annotation_module_is_typing and mode == 'fully-qualified-except-typing':
module_prefix = ''
elif _is_unpack_form(annotation) and annotation_module == 'typing_extensions':
module_prefix = '~' if mode == 'smart' else ''
else:
module_prefix = ''

Expand Down
34 changes: 25 additions & 9 deletions tests/test_util/test_util_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,15 +303,20 @@ def test_restify_Unpack():

from typing_extensions import Unpack as UnpackCompat

class X(typing.TypedDict):
x: int
y: int
label: str

# Unpack is considered as typing special form so we always have '~'
expect = rf':py:obj:`~{UnpackCompat.__module__}.Unpack`\ [:py:class:`X`]'
assert restify(UnpackCompat['X'], 'fully-qualified-except-typing') == expect # NoQA: F821
assert restify(UnpackCompat['X'], 'smart') == expect # NoQA: F821
assert restify(UnpackCompat['X'], 'fully-qualified-except-typing') == expect
assert restify(UnpackCompat['X'], 'smart') == expect

if NativeUnpack := getattr(typing, 'Unpack', None):
expect = r':py:obj:`~typing.Unpack`\ [:py:class:`X`]'
assert restify(NativeUnpack['X'], 'fully-qualified-except-typing') == expect # NoQA: F821
assert restify(NativeUnpack['X'], 'smart') == expect # NoQA: F821
assert restify(NativeUnpack['X'], 'fully-qualified-except-typing') == expect
assert restify(NativeUnpack['X'], 'smart') == expect


@pytest.mark.skipif(sys.version_info[:2] <= (3, 9), reason='python 3.10+ is required.')
Expand Down Expand Up @@ -460,13 +465,24 @@ def test_stringify_Unpack():

from typing_extensions import Unpack as UnpackCompat

qualname = rf'{UnpackCompat.__module__}.Unpack[X]'
assert stringify_annotation(UnpackCompat['X']) == qualname # NoQA: F821
assert stringify_annotation(UnpackCompat['X'], 'smart') == f'~{qualname}' # NoQA: F821
class X(typing.TypedDict):
x: int
y: int
label: str

# typing.Unpack is introduced in 3.11 but typing_extensions.Unpack
# is only using typing.Unpack since 3.12, so those objects are not
# synchronized with each other.
if hasattr(typing, 'Unpack') and typing.Unpack is UnpackCompat:
assert stringify_annotation(UnpackCompat['X']) == 'Unpack[X]'
assert stringify_annotation(UnpackCompat['X'], 'smart') == '~typing.Unpack[X]'
else:
assert stringify_annotation(UnpackCompat['X']) == 'typing_extensions.Unpack[X]'
assert stringify_annotation(UnpackCompat['X'], 'smart') == '~typing_extensions.Unpack[X]'

if NativeUnpack := getattr(typing, 'Unpack', None):
assert stringify_annotation(NativeUnpack['X']) == 'Unpack[X]' # NoQA: F821
assert stringify_annotation(NativeUnpack['X'], 'smart') == '~typing.Unpack[X]' # NoQA: F821
assert stringify_annotation(NativeUnpack['X']) == 'Unpack[X]'
assert stringify_annotation(NativeUnpack['X'], 'smart') == '~typing.Unpack[X]'


def test_stringify_type_hints_string():
Expand Down

0 comments on commit d6c47f7

Please sign in to comment.