Skip to content

Commit 3bb3975

Browse files
committed
gh-101015: Fix typing.ForwardRef with *tuple unpack
1 parent ef633e5 commit 3bb3975

File tree

3 files changed

+28
-2
lines changed

3 files changed

+28
-2
lines changed

Lib/test/test_typing.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1224,6 +1224,22 @@ class D(Generic[Unpack[Ts]]): pass
12241224
self.assertIs(D[T].__origin__, D)
12251225
self.assertIs(D[Unpack[Ts]].__origin__, D)
12261226

1227+
def test_get_type_hints_on_unpack_args(self):
1228+
Ts = TypeVarTuple('Ts')
1229+
1230+
def func1(*args: '*Ts'): pass
1231+
self.assertEqual(gth(func1, localns={'Ts': Ts}),
1232+
{'args': Unpack[Ts]})
1233+
1234+
def func2(*args: '*tuple[int, str]'): pass
1235+
self.assertEqual(gth(func2), {'args': Unpack[tuple[int, str]]})
1236+
1237+
class CustomVariadic(Generic[*Ts]): pass
1238+
1239+
def func3(*args: '*CustomVariadic[int, str]'): pass
1240+
self.assertEqual(gth(func3, localns={'CustomVariadic': CustomVariadic}),
1241+
{'args': Unpack[CustomVariadic[int, str]]})
1242+
12271243
def test_tuple_args_are_correct(self):
12281244
Ts = TypeVarTuple('Ts')
12291245

Lib/typing.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -818,7 +818,7 @@ class ForwardRef(_Final, _root=True):
818818
__slots__ = ('__forward_arg__', '__forward_code__',
819819
'__forward_evaluated__', '__forward_value__',
820820
'__forward_is_argument__', '__forward_is_class__',
821-
'__forward_module__')
821+
'__forward_module__', '__forward_is_unpack__')
822822

823823
def __init__(self, arg, is_argument=True, module=None, *, is_class=False):
824824
if not isinstance(arg, str):
@@ -828,9 +828,11 @@ def __init__(self, arg, is_argument=True, module=None, *, is_class=False):
828828
# Unfortunately, this isn't a valid expression on its own, so we
829829
# do the unpacking manually.
830830
if arg[0] == '*':
831-
arg_to_compile = f'({arg},)[0]' # E.g. (*Ts,)[0]
831+
arg_to_compile = f'({arg},)[0]' # E.g. (*Ts,)[0] or (tuple[int, int],)[0]
832+
is_unpack = True
832833
else:
833834
arg_to_compile = arg
835+
is_unpack = False
834836
try:
835837
code = compile(arg_to_compile, '<string>', 'eval')
836838
except SyntaxError:
@@ -843,6 +845,7 @@ def __init__(self, arg, is_argument=True, module=None, *, is_class=False):
843845
self.__forward_is_argument__ = is_argument
844846
self.__forward_is_class__ = is_class
845847
self.__forward_module__ = module
848+
self.__forward_is_unpack__ = is_unpack
846849

847850
def _evaluate(self, globalns, localns, recursive_guard):
848851
if self.__forward_arg__ in recursive_guard:
@@ -867,6 +870,11 @@ def _evaluate(self, globalns, localns, recursive_guard):
867870
self.__forward_value__ = _eval_type(
868871
type_, globalns, localns, recursive_guard | {self.__forward_arg__}
869872
)
873+
if (
874+
self.__forward_is_unpack__ and
875+
not isinstance(self.__forward_value__, _UnpackGenericAlias)
876+
):
877+
self.__forward_value__ = Unpack[self.__forward_value__]
870878
self.__forward_evaluated__ = True
871879
return self.__forward_value__
872880

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix :class:`typing.ForwardRef` evaluation of ``'*tuple[...]'`` string. It
2+
must not drop the ``Unpack`` part.

0 commit comments

Comments
 (0)