From 2bd8891ac65f144795dae1e99ab2c948818110b9 Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 4 Oct 2024 12:26:02 +0200 Subject: [PATCH 1/7] Fix Unpack Alias not being subscriptable, causing nested Unpacks to fail --- src/test_typing_extensions.py | 43 +++++++++++++++++++++++++++++++++++ src/typing_extensions.py | 22 ++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 8c2726f8..c9f0dbf9 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -5779,6 +5779,49 @@ 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] + with self.subTest("Check full unpack"): + self.assertIs(unpacked_str, str) + + @skipUnless(TYPING_3_11_0, "Needs Issue #103 first") + def test_nested_unpack(self): + T = TypeVar('T') + Ts = TypeVarTuple("Ts") + Variadic = TypeAliasType("Variadic", Tuple[int, Unpack[Ts]], type_params=(Ts,)) + Variadic[int, Tuple[str, int]] + + TupleAliasTs = Variadic[Tuple[Unpack[Ts], int]] + + # if this fails all below are likely to fail too + # Tuple[int, Tuple[str, int]] + TupleAliasTs[str] + + TupleAliasTsT = Variadic[Tuple[Unpack[Ts], T]] + with self.subTest("Single parameter for tuple alias"): + # Tuple[int, Tuple[str, object]] + nested_tuple_A = TupleAliasTsT[str, object] + nested_tuple_A_unpack = TupleAliasTsT[Unpack[Tuple[str]], object] + self.assertEqual(nested_tuple_A, nested_tuple_A_unpack) + + with self.subTest("Test invalid args", args=([str, int], object)): + # TypeError on some versions as types should be passed + invalid_nested_tuple = TupleAliasTsT[[str, int], object] # invalid form + with self.subTest("With Callable Ts"): + # Tuple[int, (str, int) -> object] + CallableAliasTsT = Variadic[Callable[[Unpack[Ts]], T]] + CallableAliasTsT[[str, int], object] # valid nested tuple + + # Equivalent Forms + with self.subTest("Equivalence of variadic arguments"): + nested_tuple_bare = TupleAliasTsT[str, int, object] + nested_tuple_B_1xUnpack = TupleAliasTsT[Unpack[Tuple[str, int]], object] + nested_tuple_B_2xUnpack = TupleAliasTsT[Unpack[Tuple[str]], Unpack[Tuple[int]], object] + self.assertEqual(nested_tuple_B_1xUnpack, nested_tuple_bare) + self.assertEqual(nested_tuple_B_1xUnpack, nested_tuple_B_2xUnpack) + self.assertNotEqual(invalid_nested_tuple, nested_tuple_B_1xUnpack) + class TypeVarTupleTests(BaseTestCase): diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 5bf4f2dc..d194d623 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -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.') @@ -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, From 8021de6e679aad6d74ed67e22542bbfa7f1194e3 Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 4 Oct 2024 12:26:37 +0200 Subject: [PATCH 2/7] remove test for invalid type form --- src/test_typing_extensions.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index c9f0dbf9..0c141aa8 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -5805,9 +5805,6 @@ def test_nested_unpack(self): nested_tuple_A_unpack = TupleAliasTsT[Unpack[Tuple[str]], object] self.assertEqual(nested_tuple_A, nested_tuple_A_unpack) - with self.subTest("Test invalid args", args=([str, int], object)): - # TypeError on some versions as types should be passed - invalid_nested_tuple = TupleAliasTsT[[str, int], object] # invalid form with self.subTest("With Callable Ts"): # Tuple[int, (str, int) -> object] CallableAliasTsT = Variadic[Callable[[Unpack[Ts]], T]] From 23feb2ea6ef4ac09d7fd16fcbf3829187a96063b Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 4 Oct 2024 13:34:15 +0200 Subject: [PATCH 3/7] Rewording and removed test related to invalid form --- src/test_typing_extensions.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 0c141aa8..e3fae1f4 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -5782,7 +5782,7 @@ class D(Protocol[T1, T2, Unpack[Ts]]): pass def test_substitution(self): Ts = TypeVarTuple("Ts") unpacked_str = Unpack[Ts][str] - with self.subTest("Check full unpack"): + with self.subTest("Check substitution result"): self.assertIs(unpacked_str, str) @skipUnless(TYPING_3_11_0, "Needs Issue #103 first") @@ -5805,10 +5805,11 @@ def test_nested_unpack(self): nested_tuple_A_unpack = TupleAliasTsT[Unpack[Tuple[str]], object] self.assertEqual(nested_tuple_A, nested_tuple_A_unpack) - with self.subTest("With Callable Ts"): + with self.subTest("With Callable and Unpack"): # Tuple[int, (str, int) -> object] CallableAliasTsT = Variadic[Callable[[Unpack[Ts]], T]] - CallableAliasTsT[[str, int], object] # valid nested tuple + callable_fully_subscripted = CallableAliasTsT[Unpack[Tuple[str, int]], object] + self.assertEqual(get_args(callable_fully_subscripted), (Callable[[str, int], object],)) # Equivalent Forms with self.subTest("Equivalence of variadic arguments"): @@ -5817,7 +5818,7 @@ def test_nested_unpack(self): nested_tuple_B_2xUnpack = TupleAliasTsT[Unpack[Tuple[str]], Unpack[Tuple[int]], object] self.assertEqual(nested_tuple_B_1xUnpack, nested_tuple_bare) self.assertEqual(nested_tuple_B_1xUnpack, nested_tuple_B_2xUnpack) - self.assertNotEqual(invalid_nested_tuple, nested_tuple_B_1xUnpack) + self.assertEqual(get_args(nested_tuple_B_2xUnpack), (Tuple[str, int, object], )) class TypeVarTupleTests(BaseTestCase): From ae699f2b267a335bcb5df9a5c9a4cbb5ec97c4bf Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 4 Oct 2024 13:52:49 +0200 Subject: [PATCH 4/7] rewording and trimming --- src/test_typing_extensions.py | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index e3fae1f4..67f810d5 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -5790,20 +5790,24 @@ def test_nested_unpack(self): T = TypeVar('T') Ts = TypeVarTuple("Ts") Variadic = TypeAliasType("Variadic", Tuple[int, Unpack[Ts]], type_params=(Ts,)) - Variadic[int, Tuple[str, int]] - - TupleAliasTs = Variadic[Tuple[Unpack[Ts], int]] + # Tuple[int, Tuple[str, int]] + direct_subscription = Variadic[int, Tuple[str, int]] + # Tuple[int, Tuple[*Ts, int]] + TupleAliasTs = Variadic[int, Tuple[Unpack[Ts], int]] # if this fails all below are likely to fail too # Tuple[int, Tuple[str, int]] - TupleAliasTs[str] + recursive_unpack = TupleAliasTs[str] + self.assertEqual(direct_subscription, recursive_unpack) + self.assertEqual(get_args(recursive_unpack), (int, Tuple[str, int])) TupleAliasTsT = Variadic[Tuple[Unpack[Ts], T]] - with self.subTest("Single parameter for tuple alias"): - # Tuple[int, Tuple[str, object]] - nested_tuple_A = TupleAliasTsT[str, object] - nested_tuple_A_unpack = TupleAliasTsT[Unpack[Tuple[str]], object] + with self.subTest("Two type vars"): + # Tuple[int, Tuple[List[str], object]] + nested_tuple_A = TupleAliasTsT[List[str], object] + nested_tuple_A_unpack = TupleAliasTsT[Unpack[Tuple[List[str]]], object] self.assertEqual(nested_tuple_A, nested_tuple_A_unpack) + self.assertEqual(get_args(nested_tuple_A), (Tuple[List[str], object],)) with self.subTest("With Callable and Unpack"): # Tuple[int, (str, int) -> object] @@ -5814,11 +5818,10 @@ def test_nested_unpack(self): # Equivalent Forms with self.subTest("Equivalence of variadic arguments"): nested_tuple_bare = TupleAliasTsT[str, int, object] - nested_tuple_B_1xUnpack = TupleAliasTsT[Unpack[Tuple[str, int]], object] - nested_tuple_B_2xUnpack = TupleAliasTsT[Unpack[Tuple[str]], Unpack[Tuple[int]], object] - self.assertEqual(nested_tuple_B_1xUnpack, nested_tuple_bare) - self.assertEqual(nested_tuple_B_1xUnpack, nested_tuple_B_2xUnpack) - self.assertEqual(get_args(nested_tuple_B_2xUnpack), (Tuple[str, int, object], )) + 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]) + self.assertEqual(get_args(nested_tuple_bare), (Tuple[str, int, object],)) class TypeVarTupleTests(BaseTestCase): From 681258648a52451eb835c6daebc77d4081e460eb Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 4 Oct 2024 14:04:12 +0200 Subject: [PATCH 5/7] reduced tests and no longer based on TypeAliasType --- src/test_typing_extensions.py | 32 ++++++++++++-------------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 67f810d5..4719830b 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -5785,44 +5785,36 @@ def test_substitution(self): with self.subTest("Check substitution result"): self.assertIs(unpacked_str, str) - @skipUnless(TYPING_3_11_0, "Needs Issue #103 first") + @skipUnless(TYPING_3_11_0, "Needs Issue #103 for <3.11") def test_nested_unpack(self): T = TypeVar('T') Ts = TypeVarTuple("Ts") - Variadic = TypeAliasType("Variadic", Tuple[int, Unpack[Ts]], type_params=(Ts,)) - # Tuple[int, Tuple[str, int]] + Variadic = Tuple[int, Unpack[Ts]] + # Tuple[int, int, Tuple[str, int]] direct_subscription = Variadic[int, Tuple[str, int]] - # Tuple[int, Tuple[*Ts, int]] + # Tuple[int, int, Tuple[*Ts, int]] TupleAliasTs = Variadic[int, Tuple[Unpack[Ts], int]] # if this fails all below are likely to fail too - # Tuple[int, Tuple[str, int]] + # Tuple[int, int, Tuple[str, int]] recursive_unpack = TupleAliasTs[str] self.assertEqual(direct_subscription, recursive_unpack) - self.assertEqual(get_args(recursive_unpack), (int, Tuple[str, int])) + self.assertEqual(get_args(recursive_unpack), (int, int, Tuple[str, int])) TupleAliasTsT = Variadic[Tuple[Unpack[Ts], T]] - with self.subTest("Two type vars"): - # Tuple[int, Tuple[List[str], object]] - nested_tuple_A = TupleAliasTsT[List[str], object] - nested_tuple_A_unpack = TupleAliasTsT[Unpack[Tuple[List[str]]], object] - self.assertEqual(nested_tuple_A, nested_tuple_A_unpack) - self.assertEqual(get_args(nested_tuple_A), (Tuple[List[str], object],)) - - with self.subTest("With Callable and Unpack"): - # Tuple[int, (str, int) -> object] - CallableAliasTsT = Variadic[Callable[[Unpack[Ts]], T]] - callable_fully_subscripted = CallableAliasTsT[Unpack[Tuple[str, int]], object] - self.assertEqual(get_args(callable_fully_subscripted), (Callable[[str, int], object],)) - # Equivalent Forms with self.subTest("Equivalence of variadic arguments"): nested_tuple_bare = TupleAliasTsT[str, int, object] 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]) - self.assertEqual(get_args(nested_tuple_bare), (Tuple[str, int, object],)) + self.assertEqual(get_args(nested_tuple_bare), (int, Tuple[str, int, object],)) + with self.subTest("With Callable and Unpack"): + # Tuple[int, (str, int) -> object] + CallableAliasTsT = Variadic[Callable[[Unpack[Ts]], T]] + callable_fully_subscripted = CallableAliasTsT[Unpack[Tuple[str, int]], object] + self.assertEqual(get_args(callable_fully_subscripted), (int, Callable[[str, int], object],)) class TypeVarTupleTests(BaseTestCase): From 6f3f1a6bde4839b8ba688226ee402b44d48c65f6 Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 4 Oct 2024 14:15:32 +0200 Subject: [PATCH 6/7] Updated changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index db6719c6..0779b262 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) From 90c4f38f2c88d49981016208018eab490665f5f4 Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 21 Oct 2024 18:07:41 +0200 Subject: [PATCH 7/7] Removed subTests or made to their own function formatting to changelog Co-authored-by: Jelle Zijlstra Co-authored-by: Daraan --- CHANGELOG.md | 2 +- src/test_typing_extensions.py | 41 ++++++++++++++++++++--------------- 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0779b262..f127ada0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,7 @@ 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 +- 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) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 4719830b..528763d6 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -5781,13 +5781,11 @@ class D(Protocol[T1, T2, Unpack[Ts]]): pass def test_substitution(self): Ts = TypeVarTuple("Ts") - unpacked_str = Unpack[Ts][str] - with self.subTest("Check substitution result"): - self.assertIs(unpacked_str, str) + 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): - T = TypeVar('T') Ts = TypeVarTuple("Ts") Variadic = Tuple[int, Unpack[Ts]] # Tuple[int, int, Tuple[str, int]] @@ -5795,26 +5793,33 @@ def test_nested_unpack(self): # Tuple[int, int, Tuple[*Ts, int]] TupleAliasTs = Variadic[int, Tuple[Unpack[Ts], int]] - # if this fails all below are likely to fail too # 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]] - # Equivalent Forms - with self.subTest("Equivalence of variadic arguments"): - nested_tuple_bare = TupleAliasTsT[str, int, object] - 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]) - self.assertEqual(get_args(nested_tuple_bare), (int, Tuple[str, int, object],)) - - with self.subTest("With Callable and Unpack"): - # Tuple[int, (str, int) -> object] - CallableAliasTsT = Variadic[Callable[[Unpack[Ts]], T]] - callable_fully_subscripted = CallableAliasTsT[Unpack[Tuple[str, int]], object] - self.assertEqual(get_args(callable_fully_subscripted), (int, Callable[[str, int], object],)) + 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):