Skip to content

Fix subscription of Unpack causing nested Unpacks to not be resolved correctly #480

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Oct 21, 2024
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