Skip to content
Merged
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
subscripted objects) had wrong parameters if they were directly
subscripted with an `Unpack` object.
Patch by [Daraan](https://github.com/Daraan).
- Fix error in subscription of `Unpack` aliases causing nested Unpacks
to not be resolved correctly. Patch by [Daraan](https://github.com/Daraan).

# Release 4.12.2 (June 7, 2024)

Expand Down
41 changes: 41 additions & 0 deletions src/test_typing_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -5779,6 +5779,47 @@ class D(Protocol[T1, T2, Unpack[Ts]]): pass
with self.assertRaises(TypeError):
klass[int]

def test_substitution(self):
Ts = TypeVarTuple("Ts")
unpacked_str = Unpack[Ts][str] # This should not raise an error
self.assertIs(unpacked_str, str)

@skipUnless(TYPING_3_11_0, "Needs Issue #103 for <3.11")
def test_nested_unpack(self):
Ts = TypeVarTuple("Ts")
Variadic = Tuple[int, Unpack[Ts]]
# Tuple[int, int, Tuple[str, int]]
direct_subscription = Variadic[int, Tuple[str, int]]
# Tuple[int, int, Tuple[*Ts, int]]
TupleAliasTs = Variadic[int, Tuple[Unpack[Ts], int]]

# Tuple[int, int, Tuple[str, int]]
recursive_unpack = TupleAliasTs[str]
self.assertEqual(direct_subscription, recursive_unpack)
self.assertEqual(get_args(recursive_unpack), (int, int, Tuple[str, int]))

# Test with Callable
T = TypeVar("T")
# Tuple[int, (*Ts) -> T]
CallableAliasTsT = Variadic[Callable[[Unpack[Ts]], T]]
# Tuple[int, (str, int) -> object]
callable_fully_subscripted = CallableAliasTsT[Unpack[Tuple[str, int]], object]
self.assertEqual(get_args(callable_fully_subscripted), (int, Callable[[str, int], object]))

@skipUnless(TYPING_3_11_0, "Needs Issue #103 for <3.11")
def test_equivalent_nested_variadics(self):
T = TypeVar("T")
Ts = TypeVarTuple("Ts")
Variadic = Tuple[int, Unpack[Ts]]
TupleAliasTsT = Variadic[Tuple[Unpack[Ts], T]]
nested_tuple_bare = TupleAliasTsT[str, int, object]

self.assertEqual(get_args(nested_tuple_bare), (int, Tuple[str, int, object]))
# Variants
self.assertEqual(nested_tuple_bare, TupleAliasTsT[Unpack[Tuple[str, int, object]]])
self.assertEqual(nested_tuple_bare, TupleAliasTsT[Unpack[Tuple[str, int]], object])
self.assertEqual(nested_tuple_bare, TupleAliasTsT[Unpack[Tuple[str]], Unpack[Tuple[int]], object])


class TypeVarTupleTests(BaseTestCase):

Expand Down
22 changes: 22 additions & 0 deletions src/typing_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2424,6 +2424,17 @@ def __typing_unpacked_tuple_args__(self):
return arg.__args__
return None

@property
def __typing_is_unpacked_typevartuple__(self):
assert self.__origin__ is Unpack
assert len(self.__args__) == 1
return isinstance(self.__args__[0], TypeVarTuple)

def __getitem__(self, args):
if self.__typing_is_unpacked_typevartuple__:
return args
return super().__getitem__(args)

@_UnpackSpecialForm
def Unpack(self, parameters):
item = typing._type_check(parameters, f'{self._name} accepts only a single type.')
Expand All @@ -2436,6 +2447,17 @@ def _is_unpack(obj):
class _UnpackAlias(typing._GenericAlias, _root=True):
__class__ = typing.TypeVar

@property
def __typing_is_unpacked_typevartuple__(self):
assert self.__origin__ is Unpack
assert len(self.__args__) == 1
return isinstance(self.__args__[0], TypeVarTuple)

def __getitem__(self, args):
if self.__typing_is_unpacked_typevartuple__:
return args
return super().__getitem__(args)

class _UnpackForm(_ExtensionsSpecialForm, _root=True):
def __getitem__(self, parameters):
item = typing._type_check(parameters,
Expand Down
Loading