diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 1cae1b0de7140f6..f5f89e8a6794918 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -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') diff --git a/Lib/typing.py b/Lib/typing.py index 4675af12d087b6c..500ef328ff4462c 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -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): @@ -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, '', 'eval') except SyntaxError: @@ -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: @@ -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__ @@ -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. diff --git a/Misc/NEWS.d/next/Library/2023-01-14-12-58-21.gh-issue-101015.stWFid.rst b/Misc/NEWS.d/next/Library/2023-01-14-12-58-21.gh-issue-101015.stWFid.rst new file mode 100644 index 000000000000000..ca2736a4b3da940 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-01-14-12-58-21.gh-issue-101015.stWFid.rst @@ -0,0 +1,2 @@ +Fix :class:`typing.ForwardRef` evaluation of ``'*tuple[...]'`` string. It +must not drop the ``Unpack`` part.