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 8e11117
Show file tree
Hide file tree
Showing 3 changed files with 30 additions and 8 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
20 changes: 12 additions & 8 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 @@ -1939,15 +1947,11 @@ def _no_init_or_replace_init(self, *args, **kwargs):


def _caller(depth=1, default='__main__'):
try:
return sys._getframemodulename(depth + 1) or default
except AttributeError: # For platforms without _getframemodulename()
pass
try:
return sys._getframe(depth + 1).f_globals.get('__name__', default)
except (AttributeError, ValueError): # For platforms without _getframe()
pass
return None
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 8e11117

Please sign in to comment.