Skip to content

Commit

Permalink
gh-101015: Fix typing.ForwardRef with *tuple unpack
Browse files Browse the repository at this point in the history
  • Loading branch information
sobolevn committed Jan 14, 2023
1 parent ef633e5 commit b28ee58
Show file tree
Hide file tree
Showing 3 changed files with 29 additions and 2 deletions.
16 changes: 16 additions & 0 deletions Lib/test/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -1224,6 +1224,22 @@ class D(Generic[Unpack[Ts]]): pass
self.assertIs(D[T].__origin__, D)
self.assertIs(D[Unpack[Ts]].__origin__, D)

def test_get_type_hints_on_unpack_args(self):
Ts = TypeVarTuple('Ts')

def func1(*args: '*Ts'): pass
self.assertEqual(gth(func1, localns={'Ts': Ts}),
{'args': Unpack[Ts]})

def func2(*args: '*tuple[int, str]'): pass
self.assertEqual(gth(func2), {'args': Unpack[tuple[int, str]]})

class CustomVariadic(Generic[*Ts]): pass

def func3(*args: '*CustomVariadic[int, str]'): pass
self.assertEqual(gth(func3, localns={'CustomVariadic': CustomVariadic}),
{'args': Unpack[CustomVariadic[int, str]]})

def test_tuple_args_are_correct(self):
Ts = TypeVarTuple('Ts')

Expand Down
13 changes: 11 additions & 2 deletions Lib/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -818,7 +818,7 @@ class ForwardRef(_Final, _root=True):
__slots__ = ('__forward_arg__', '__forward_code__',
'__forward_evaluated__', '__forward_value__',
'__forward_is_argument__', '__forward_is_class__',
'__forward_module__')
'__forward_module__', '__forward_is_unpack__')

def __init__(self, arg, is_argument=True, module=None, *, is_class=False):
if not isinstance(arg, str):
Expand All @@ -828,9 +828,11 @@ def __init__(self, arg, is_argument=True, module=None, *, is_class=False):
# Unfortunately, this isn't a valid expression on its own, so we
# do the unpacking manually.
if arg[0] == '*':
arg_to_compile = f'({arg},)[0]' # E.g. (*Ts,)[0]
arg_to_compile = f'({arg},)[0]' # E.g. (*Ts,)[0] or (tuple[int, int],)[0]
is_unpack = True
else:
arg_to_compile = arg
is_unpack = False
try:
code = compile(arg_to_compile, '<string>', 'eval')
except SyntaxError:
Expand All @@ -843,6 +845,7 @@ def __init__(self, arg, is_argument=True, module=None, *, is_class=False):
self.__forward_is_argument__ = is_argument
self.__forward_is_class__ = is_class
self.__forward_module__ = module
self.__forward_is_unpack__ = is_unpack

def _evaluate(self, globalns, localns, recursive_guard):
if self.__forward_arg__ in recursive_guard:
Expand All @@ -867,6 +870,11 @@ def _evaluate(self, globalns, localns, recursive_guard):
self.__forward_value__ = _eval_type(
type_, globalns, localns, recursive_guard | {self.__forward_arg__}
)
if (
self.__forward_is_unpack__ and
not isinstance(self.__forward_value__, _UnpackGenericAlias)
):
self.__forward_value__ = Unpack[self.__forward_value__]
self.__forward_evaluated__ = True
return self.__forward_value__

Expand Down Expand Up @@ -1949,6 +1957,7 @@ def _caller(depth=1, default='__main__'):
pass
return None


def _allow_reckless_class_checks(depth=3):
"""Allow instance and class checks for special stdlib modules.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix :class:`typing.ForwardRef` evaluation of ``'*tuple[...]'`` string. It
must not drop the ``Unpack`` part.

0 comments on commit b28ee58

Please sign in to comment.