From b6bc3232511150bcbdf219e36b4f74b082b14487 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 22 Aug 2024 01:33:34 +0200 Subject: [PATCH 01/47] Support ParamSpec for TypeAliasType --- src/test_typing_extensions.py | 28 ++++++++++++++++++++++ src/typing_extensions.py | 45 +++++++++++++++++++++++++++-------- 2 files changed, 63 insertions(+), 10 deletions(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 868e7938..4fd1f1f4 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -7207,6 +7207,34 @@ def test_getitem(self): fully_subscripted = still_generic[float] self.assertEqual(get_args(fully_subscripted), (Iterable[float],)) self.assertIs(get_origin(fully_subscripted), ListOrSetT) + + # Test ParamSpec and Ellipsis + P = ParamSpec('P') + CallableP = TypeAliasType("CallableP", Callable[P, Any], type_params=(P,)) + # () -> Any + callable_no_arg = CallableP[[]] + self.assertEqual(get_args(callable_no_arg), ([],)) + # (int) -> Any + callable_arg_raw = CallableP[int] + self.assertEqual(get_args(callable_arg_raw), (int,)) + callable_arg = CallableP[[int]] + self.assertEqual(get_args(callable_arg), ([int],)) + # (int, int) -> Any + callable_arg2 = CallableP[[int, int]] + self.assertEqual(get_args(callable_arg2), ([int, int],)) + # (...) -> Any + callable_ellipsis = CallableP[...] + self.assertEqual(get_args(callable_ellipsis), (...,)) + callable_ellipsis2 = CallableP[(...,)] + self.assertEqual(callable_ellipsis, callable_ellipsis2) + # (int, ...) -> Any + callable_arg_more = CallableP[[int, ...]] + self.assertEqual(get_args(callable_arg_more), ([int, ...],)) + # (T) -> Any + callable_generic = CallableP[[T]] + self.assertEqual(get_args(callable_generic), ([T],)) + callable_generic_raw = CallableP[T] + self.assertEqual(get_args(callable_generic_raw), (T,)) def test_pickle(self): global Alias diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 8046dae1..a3cdd3f5 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -3516,16 +3516,41 @@ def _raise_attribute_error(self, name: str) -> Never: def __repr__(self) -> str: return self.__name__ - def __getitem__(self, parameters): - if not isinstance(parameters, tuple): - parameters = (parameters,) - parameters = [ - typing._type_check( - item, f'Subscripting {self.__name__} requires a type.' - ) - for item in parameters - ] - return typing._GenericAlias(self, tuple(parameters)) + if sys.version_info >= (3, 11): + def __getitem__(self, parameters): + if not isinstance(parameters, tuple): + parameters = (parameters,) + parameters = [ + typing._type_check( + item, f'Subscripting {self.__name__} requires a type.' + ) + for item in parameters + ] + return typing._GenericAlias(self, tuple(parameters)) + else: + def _check_parameter(self, item, typ=_marker): + # Allow [], [int], [int, str], [int, ...], [int, T] + if not isinstance(typ, ParamSpec) and typ is not _marker: + return typing._type_check( + item, f'Subscripting {self.__name__} requires a type.' + ) + if item is ...: + return ... + elif isinstance(item, list) and typ is not _marker: + return [self._check_parameter(arg) for arg in item] + else: + return typing._type_check( + item, f'Subscripting {self.__name__} requires a type.' + ) + + def __getitem__(self, parameters): + if not isinstance(parameters, tuple): + parameters = (parameters,) + parameters = [ + self._check_parameter(item, typ) + for item, typ in zip(parameters, self.__type_params__) + ] + return typing._GenericAlias(self, tuple(parameters)) def __reduce__(self): return self.__name__ From 79985f3d259d1c2dfede85d12947a7c502af05f9 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 22 Aug 2024 01:51:38 +0200 Subject: [PATCH 02/47] Removed trailing whitespaces --- src/test_typing_extensions.py | 2 +- src/typing_extensions.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 4fd1f1f4..136d21c8 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -7207,7 +7207,7 @@ def test_getitem(self): fully_subscripted = still_generic[float] self.assertEqual(get_args(fully_subscripted), (Iterable[float],)) self.assertIs(get_origin(fully_subscripted), ListOrSetT) - + # Test ParamSpec and Ellipsis P = ParamSpec('P') CallableP = TypeAliasType("CallableP", Callable[P, Any], type_params=(P,)) diff --git a/src/typing_extensions.py b/src/typing_extensions.py index a3cdd3f5..b8b3035b 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -1,3 +1,4 @@ +# pyright: reportShadowedImports=false import abc import collections import collections.abc @@ -3541,7 +3542,7 @@ def _check_parameter(self, item, typ=_marker): else: return typing._type_check( item, f'Subscripting {self.__name__} requires a type.' - ) + ) def __getitem__(self, parameters): if not isinstance(parameters, tuple): From 3199b7ba53f1cfdad7217e933034643eba03fe15 Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 18 Sep 2024 17:26:05 +0200 Subject: [PATCH 03/47] raise TypeError for invalid type_params like typing.TypeAliasType --- src/typing_extensions.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/typing_extensions.py b/src/typing_extensions.py index bb4688a0..863ea85b 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -3488,6 +3488,8 @@ def __init__(self, name: str, value, *, type_params=()): self.__type_params__ = type_params parameters = [] + if not isinstance(type_params, tuple): + raise TypeError("type_params must be a tuple") for type_param in type_params: if isinstance(type_param, TypeVarTuple): parameters.extend(type_param) From f7d79d9e3470828fef0c19c931d6375be4275d4a Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 18 Sep 2024 17:29:40 +0200 Subject: [PATCH 04/47] Keep dunder attributes like typing.TypeAliasType --- src/typing_extensions.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 863ea85b..677045d7 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -3537,7 +3537,10 @@ def __getitem__(self, parameters): ) for item in parameters ] - return typing._GenericAlias(self, tuple(parameters)) + alias = typing._GenericAlias(self, tuple(parameters)) + alias.__value__ = self.__value__ + alias.__type_params__ = self.__type_params__ + return alias else: def _check_parameter(self, item, typ=_marker): # Allow [], [int], [int, str], [int, ...], [int, T] @@ -3561,7 +3564,10 @@ def __getitem__(self, parameters): self._check_parameter(item, typ) for item, typ in zip(parameters, self.__type_params__) ] - return typing._GenericAlias(self, tuple(parameters)) + alias = typing._GenericAlias(self, tuple(parameters)) + alias.__value__ = self.__value__ + alias.__type_params__ = self.__type_params__ + return alias def __reduce__(self): return self.__name__ From c4c0e6806bdd5ab2f1a5e656a510b7ac748d2de4 Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 18 Sep 2024 18:19:52 +0200 Subject: [PATCH 05/47] Added test to catch invalid cases, fix wrong intendation --- src/test_typing_extensions.py | 10 +++++++++- src/typing_extensions.py | 4 ++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 161b9825..546dfe30 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -7237,7 +7237,7 @@ def test_getitem(self): self.assertEqual(get_args(subscripted), (int,)) self.assertIs(get_origin(subscripted), ListOrSetT) with self.assertRaises(TypeError): - subscripted[str] + subscripted[int] # TypeError: ListOrSetT[int] is not a generic class still_generic = ListOrSetT[Iterable[T]] self.assertEqual(get_args(still_generic), (Iterable[T],)) @@ -7273,6 +7273,14 @@ def test_getitem(self): self.assertEqual(get_args(callable_generic), ([T],)) callable_generic_raw = CallableP[T] self.assertEqual(get_args(callable_generic_raw), (T,)) + + # test invalid usage + if not TYPING_3_11_0: + with self.assertRaises(TypeError): + ListOrSetT[Generic[T]] + with self.assertRaises(TypeError): + ListOrSetT[(Generic[T], )] + def test_pickle(self): global Alias diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 677045d7..f21f4343 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -3531,7 +3531,7 @@ def __repr__(self) -> str: def __getitem__(self, parameters): if not isinstance(parameters, tuple): parameters = (parameters,) - parameters = [ + parameters = [ typing._type_check( item, f'Subscripting {self.__name__} requires a type.' ) @@ -3560,7 +3560,7 @@ def _check_parameter(self, item, typ=_marker): def __getitem__(self, parameters): if not isinstance(parameters, tuple): parameters = (parameters,) - parameters = [ + parameters = [ self._check_parameter(item, typ) for item, typ in zip(parameters, self.__type_params__) ] From d6af9835b0db741cd1fc99665787411b377d136b Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 18 Sep 2024 18:21:03 +0200 Subject: [PATCH 06/47] removed pyright pragma --- src/typing_extensions.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/typing_extensions.py b/src/typing_extensions.py index f21f4343..daed8ae2 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -1,4 +1,3 @@ -# pyright: reportShadowedImports=false import abc import collections import collections.abc From 0a5039b707343da28ae2ba88abcc1b961550c364 Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 18 Sep 2024 18:21:51 +0200 Subject: [PATCH 07/47] fix whitespaces --- src/test_typing_extensions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 546dfe30..2ad38dde 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -7273,14 +7273,14 @@ def test_getitem(self): self.assertEqual(get_args(callable_generic), ([T],)) callable_generic_raw = CallableP[T] self.assertEqual(get_args(callable_generic_raw), (T,)) - + # test invalid usage if not TYPING_3_11_0: with self.assertRaises(TypeError): ListOrSetT[Generic[T]] with self.assertRaises(TypeError): ListOrSetT[(Generic[T], )] - + def test_pickle(self): global Alias From e532429519fed195e61a6cc1e1d15fbb96b78d53 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 19 Sep 2024 14:35:00 +0200 Subject: [PATCH 08/47] Avoid incompatible global types --- 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 2ad38dde..e7995fd7 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -3572,13 +3572,13 @@ class P(Protocol): self.assertEqual(Alias, Alias2) def test_protocols_pickleable(self): - global P, CP # pickle wants to reference the class by name + global GlobalProto, CP # pickle wants to reference the class by name T = TypeVar('T') @runtime_checkable - class P(Protocol[T]): + class GlobalProto(Protocol[T]): x = 1 - class CP(P[int]): + class CP(GlobalProto[int]): pass c = CP() @@ -3591,7 +3591,7 @@ class CP(P[int]): self.assertEqual(x.bar, 'abc') self.assertEqual(x.x, 1) self.assertEqual(x.__dict__, {'foo': 42, 'bar': 'abc'}) - s = pickle.dumps(P) + s = pickle.dumps(GlobalProto) D = pickle.loads(s) class E: x = 1 @@ -7232,6 +7232,7 @@ def test_or(self): Alias | "Ref" def test_getitem(self): + T = TypeVar("T") ListOrSetT = TypeAliasType("ListOrSetT", Union[List[T], Set[T]], type_params=(T,)) subscripted = ListOrSetT[int] self.assertEqual(get_args(subscripted), (int,)) From 9911ac7acd1bad2b9dddc35d733eeed296471f78 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 19 Sep 2024 16:33:45 +0200 Subject: [PATCH 09/47] Add __name__ for TypeAliasType for <=3.10 --- src/test_typing_extensions.py | 43 +++++++++++++++++++++++++++++++++++ src/typing_extensions.py | 1 + 2 files changed, 44 insertions(+) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index e7995fd7..8c8a5408 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -7165,6 +7165,19 @@ def test_attributes(self): self.assertEqual(ListOrSetT.__type_params__, (T,)) self.assertEqual(ListOrSetT.__parameters__, (T,)) + subscripted = ListOrSetT[int] + self.assertEqual(subscripted.__name__, "ListOrSetT") + self.assertEqual(subscripted.__value__, Union[List[T], Set[T]],) + self.assertEqual(subscripted.__type_params__, (T, )) + self.assertEqual(subscripted.__parameters__, ()) + + T2 = TypeVar("T2") + subscriptedT = ListOrSetT[T2] + self.assertEqual(subscriptedT.__name__, "ListOrSetT") + self.assertEqual(subscriptedT.__value__, Union[List[T], Set[T]],) + self.assertEqual(subscriptedT.__type_params__, (T, )) + self.assertEqual(subscriptedT.__parameters__, (T2, )) + Ts = TypeVarTuple("Ts") Variadic = TypeAliasType("Variadic", Tuple[int, Unpack[Ts]], type_params=(Ts,)) self.assertEqual(Variadic.__name__, "Variadic") @@ -7172,6 +7185,18 @@ def test_attributes(self): self.assertEqual(Variadic.__type_params__, (Ts,)) self.assertEqual(Variadic.__parameters__, tuple(iter(Ts))) + subscripted_tuple = Variadic[Unpack[Tuple[int, float]]] + self.assertEqual(subscripted_tuple.__name__, "Variadic") + self.assertEqual(subscripted_tuple.__value__, Tuple[int, Unpack[Ts]]) + self.assertEqual(subscripted_tuple.__type_params__, (Ts,)) + self.assertEqual(subscripted_tuple.__parameters__, ()) + + subscripted_tupleT = Variadic[Unpack[Tuple[int, T]]] + self.assertEqual(subscripted_tupleT.__name__, "Variadic") + self.assertEqual(subscripted_tupleT.__value__, Tuple[int, Unpack[Ts]]) + self.assertEqual(subscripted_tupleT.__type_params__, (Ts,)) + self.assertEqual(subscripted_tupleT.__parameters__, (T, )) + def test_cannot_set_attributes(self): Simple = TypeAliasType("Simple", int) with self.assertRaisesRegex(AttributeError, "readonly attribute"): @@ -7282,6 +7307,24 @@ def test_getitem(self): with self.assertRaises(TypeError): ListOrSetT[(Generic[T], )] + def test_invalid_cases(self): + # If these cases fail the specificiation might have changed + T = TypeVar("T") + T2 = TypeVar("T2") + ListOrSetT = TypeAliasType("ListOrSetT", Union[List[T], Set[T]], type_params=(T,)) + too_many = ListOrSetT[int, bool] + + self.assertEqual(get_args(too_many), (int, bool)) + self.assertEqual(too_many.__parameters__, ()) + + ListOrSet2T = TypeAliasType("ListOrSetT", Union[List[T], Set[T2]], type_params=(T, T2)) + not_enough = ListOrSet2T[int] + self.assertEqual(get_args(not_enough), (int,)) + self.assertEqual(not_enough.__parameters__, ()) + + not_enough2 = ListOrSet2T[T] + self.assertEqual(get_args(not_enough2), (T,)) + self.assertEqual(not_enough2.__parameters__, (T,)) def test_pickle(self): global Alias diff --git a/src/typing_extensions.py b/src/typing_extensions.py index daed8ae2..ccc8426d 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -3564,6 +3564,7 @@ def __getitem__(self, parameters): for item, typ in zip(parameters, self.__type_params__) ] alias = typing._GenericAlias(self, tuple(parameters)) + alias.__name__ = self.__name__ alias.__value__ = self.__value__ alias.__type_params__ = self.__type_params__ return alias From e1b3095f5593b8715c74ed6a6b51373bf6b68f2a Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 19 Sep 2024 16:35:37 +0200 Subject: [PATCH 10/47] Added missing support for Unpack to TypeAliasType variant --- src/typing_extensions.py | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/src/typing_extensions.py b/src/typing_extensions.py index ccc8426d..c08fc2b3 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -3543,25 +3543,40 @@ def __getitem__(self, parameters): else: def _check_parameter(self, item, typ=_marker): # Allow [], [int], [int, str], [int, ...], [int, T] - if not isinstance(typ, ParamSpec) and typ is not _marker: - return typing._type_check( - item, f'Subscripting {self.__name__} requires a type.' + if isinstance(item, _UnpackAlias): + args = ( + item.__args__ # e.g. (int, str) + if sys.version_info[:2] >= (3, 9) + else item.__args__ # (typing.Tuple[int, float],) ) - if item is ...: - return ... + # Unpack + yield from [checked + for arg in args + for checked in self._check_parameter(arg)] + elif item is ...: + yield ... elif isinstance(item, list) and typ is not _marker: - return [self._check_parameter(arg) for arg in item] + yield [checked + for arg in item + for checked in self._check_parameter(arg)] else: - return typing._type_check( + yield typing._type_check( item, f'Subscripting {self.__name__} requires a type.' ) def __getitem__(self, parameters): if not isinstance(parameters, tuple): parameters = (parameters,) + param_difference = (len(parameters) - len(self.__type_params__)) + if param_difference > 0: + # invalid case that does not raise an error, fill with dummys + type_params = [*self.__type_params__, *[Any] * param_difference] + else: + type_params = self.__type_params__ parameters = [ - self._check_parameter(item, typ) - for item, typ in zip(parameters, self.__type_params__) + checked + for item, typ in zip(parameters, type_params) + for checked in self._check_parameter(item, typ) ] alias = typing._GenericAlias(self, tuple(parameters)) alias.__name__ = self.__name__ From 408ae2e793336926efdba2d405df543a323da13b Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 19 Sep 2024 16:42:19 +0200 Subject: [PATCH 11/47] Added more invalid test cases --- src/test_typing_extensions.py | 36 ++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 8c8a5408..233accfa 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -7317,7 +7317,7 @@ def test_invalid_cases(self): self.assertEqual(get_args(too_many), (int, bool)) self.assertEqual(too_many.__parameters__, ()) - ListOrSet2T = TypeAliasType("ListOrSetT", Union[List[T], Set[T2]], type_params=(T, T2)) + ListOrSet2T = TypeAliasType("ListOrSet2T", Union[List[T], Set[T2]], type_params=(T, T2)) not_enough = ListOrSet2T[int] self.assertEqual(get_args(not_enough), (int,)) self.assertEqual(not_enough.__parameters__, ()) @@ -7325,6 +7325,40 @@ def test_invalid_cases(self): not_enough2 = ListOrSet2T[T] self.assertEqual(get_args(not_enough2), (T,)) self.assertEqual(not_enough2.__parameters__, (T,)) + # ParamSpec + P = ParamSpec('P') + CallableP = TypeAliasType("CallableP", Callable[P, T], type_params=(P,)) + + callable_not_enough = CallableP[int] + self.assertEqual(callable_not_enough.__parameters__, ()) + self.assertEqual(get_args(callable_not_enough), (int, )) + + callable_too_many = CallableP[str, float, T2, int] + self.assertEqual(callable_too_many.__parameters__, (T2, )) + self.assertEqual(get_args(callable_too_many), (str, float, T2, int, )) + + # Test with Concatenate + callable_concat = CallableP[Concatenate[Any, T2, P], Any] + reveal_type(callable_concat) + self.assertEqual(callable_concat.__parameters__, (T2, P)) + self.assertEqual(get_args(callable_concat), (Concatenate[Any, T2, P], Any)) + + # TypeVarTuple + Ts = TypeVarTuple("Ts") + Variadic = TypeAliasType("Variadic", Tuple[int, Unpack[Ts]], type_params=(Ts,)) + # No Unpack + invalid_tuple_A = Variadic[Tuple[int, T]] + self.assertEqual(invalid_tuple_A.__parameters__, (T, )) + self.assertEqual(get_args(invalid_tuple_A), (Tuple[int, T], )) + + # To type tuple + invalid_tuple_B = Variadic[int, T] + self.assertEqual(invalid_tuple_B.__parameters__, (T, )) + + # No Tuple, but list + invalud_tuple_C = Variadic[[int, T]] + self.assertEqual(invalud_tuple_C.__parameters__, ()) + self.assertEqual(get_args(invalud_tuple_C), ([int, T],)) def test_pickle(self): global Alias From b3e6b7ad527bb25ac2284e1fdbab54338408222c Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 19 Sep 2024 17:34:48 +0200 Subject: [PATCH 12/47] Unpack invalid Concatenate correctly --- src/test_typing_extensions.py | 11 +++++++++-- src/typing_extensions.py | 16 ++++++++-------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 233accfa..385b6812 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -7339,9 +7339,7 @@ def test_invalid_cases(self): # Test with Concatenate callable_concat = CallableP[Concatenate[Any, T2, P], Any] - reveal_type(callable_concat) self.assertEqual(callable_concat.__parameters__, (T2, P)) - self.assertEqual(get_args(callable_concat), (Concatenate[Any, T2, P], Any)) # TypeVarTuple Ts = TypeVarTuple("Ts") @@ -7359,6 +7357,15 @@ def test_invalid_cases(self): invalud_tuple_C = Variadic[[int, T]] self.assertEqual(invalud_tuple_C.__parameters__, ()) self.assertEqual(get_args(invalud_tuple_C), ([int, T],)) + + @skipUnless(TYPING_3_11_0, "Concatenate not unpacked anymore") + def test_further_invalid_cases(self): + P = ParamSpec('P') + T = TypeVar("T") + T2 = TypeVar("T2") + CallableP = TypeAliasType("CallableP", Callable[P, T], type_params=(P,)) + callable_concat = CallableP[Concatenate[Any, T2, P], Any] + self.assertEqual(get_args(callable_concat), (Concatenate[Any, T2, P], Any)) def test_pickle(self): global Alias diff --git a/src/typing_extensions.py b/src/typing_extensions.py index c08fc2b3..43eca149 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -3543,15 +3543,10 @@ def __getitem__(self, parameters): else: def _check_parameter(self, item, typ=_marker): # Allow [], [int], [int, str], [int, ...], [int, T] - if isinstance(item, _UnpackAlias): - args = ( - item.__args__ # e.g. (int, str) - if sys.version_info[:2] >= (3, 9) - else item.__args__ # (typing.Tuple[int, float],) - ) + if isinstance(item, (_UnpackAlias, _ConcatenateGenericAlias)): # Unpack yield from [checked - for arg in args + for arg in item.__args__ for checked in self._check_parameter(arg)] elif item is ...: yield ... @@ -3578,7 +3573,12 @@ def __getitem__(self, parameters): for item, typ in zip(parameters, type_params) for checked in self._check_parameter(item, typ) ] - alias = typing._GenericAlias(self, tuple(parameters)) + if sys.version_info[:2] == (3, 10): + alias = typing._GenericAlias(self, tuple(parameters), + _typevar_types=(TypeVar, ParamSpec) + ) + else: + alias = typing._GenericAlias(self, tuple(parameters)) alias.__name__ = self.__name__ alias.__value__ = self.__value__ alias.__type_params__ = self.__type_params__ From c3d98c621d454a8828cf4d977f72026851a4ec3f Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 23 Sep 2024 14:53:35 +0200 Subject: [PATCH 13/47] Removed parameter checking from TypeAliasType._check_parameter --- src/typing_extensions.py | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 43eca149..c2d84b83 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -3541,19 +3541,19 @@ def __getitem__(self, parameters): alias.__type_params__ = self.__type_params__ return alias else: - def _check_parameter(self, item, typ=_marker): + def _check_parameter(self, item, recursion=0): # Allow [], [int], [int, str], [int, ...], [int, T] if isinstance(item, (_UnpackAlias, _ConcatenateGenericAlias)): # Unpack yield from [checked for arg in item.__args__ - for checked in self._check_parameter(arg)] + for checked in self._check_parameter(arg, recursion+1)] elif item is ...: yield ... - elif isinstance(item, list) and typ is not _marker: + elif isinstance(item, list) and recursion == 0: yield [checked for arg in item - for checked in self._check_parameter(arg)] + for checked in self._check_parameter(arg, recursion+1)] else: yield typing._type_check( item, f'Subscripting {self.__name__} requires a type.' @@ -3562,16 +3562,10 @@ def _check_parameter(self, item, typ=_marker): def __getitem__(self, parameters): if not isinstance(parameters, tuple): parameters = (parameters,) - param_difference = (len(parameters) - len(self.__type_params__)) - if param_difference > 0: - # invalid case that does not raise an error, fill with dummys - type_params = [*self.__type_params__, *[Any] * param_difference] - else: - type_params = self.__type_params__ parameters = [ checked - for item, typ in zip(parameters, type_params) - for checked in self._check_parameter(item, typ) + for item in parameters + for checked in self._check_parameter(item) ] if sys.version_info[:2] == (3, 10): alias = typing._GenericAlias(self, tuple(parameters), From 8255667f0fac72d133b82175f56f2f0423588634 Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 23 Sep 2024 16:02:26 +0200 Subject: [PATCH 14/47] extended and reworked tests --- src/test_typing_extensions.py | 179 +++++++++++++++++++++++++++++----- 1 file changed, 152 insertions(+), 27 deletions(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 385b6812..e4baa721 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -7193,10 +7193,24 @@ def test_attributes(self): subscripted_tupleT = Variadic[Unpack[Tuple[int, T]]] self.assertEqual(subscripted_tupleT.__name__, "Variadic") - self.assertEqual(subscripted_tupleT.__value__, Tuple[int, Unpack[Ts]]) - self.assertEqual(subscripted_tupleT.__type_params__, (Ts,)) self.assertEqual(subscripted_tupleT.__parameters__, (T, )) + # Use with Callable + # Use with Callable+Concatenate + subscripted_callable_concat = Variadic[Callable[Concatenate[Literal["s"], P], T]] + self.assertEqual(subscripted_callable_concat.__parameters__, (P, T)) + + subcriped_callable_tvt = Variadic[Callable[[Unpack[Ts]], T]] + self.assertEqual(subcriped_callable_tvt.__parameters__, (Ts, T)) + + # Use with Callable+Unpack + CallableTs = TypeAliasType("CallableTs", Callable[[Unpack[Ts]], Any], type_params=(Ts, )) + self.assertEqual(CallableTs.__type_params__, (Ts,)) + self.assertEqual(CallableTs.__parameters__, (*Ts,)) + + unpack_callable = CallableTs[Unpack[Tuple[int, T]]] + self.assertEqual(unpack_callable.__parameters__, (T,)) + def test_cannot_set_attributes(self): Simple = TypeAliasType("Simple", int) with self.assertRaisesRegex(AttributeError, "readonly attribute"): @@ -7262,8 +7276,8 @@ def test_getitem(self): subscripted = ListOrSetT[int] self.assertEqual(get_args(subscripted), (int,)) self.assertIs(get_origin(subscripted), ListOrSetT) - with self.assertRaises(TypeError): - subscripted[int] # TypeError: ListOrSetT[int] is not a generic class + with self.assertRaises(TypeError, msg="not a generic class"): + subscripted[int] still_generic = ListOrSetT[Iterable[T]] self.assertEqual(get_args(still_generic), (Iterable[T],)) @@ -7279,36 +7293,116 @@ def test_getitem(self): callable_no_arg = CallableP[[]] self.assertEqual(get_args(callable_no_arg), ([],)) # (int) -> Any - callable_arg_raw = CallableP[int] - self.assertEqual(get_args(callable_arg_raw), (int,)) - callable_arg = CallableP[[int]] - self.assertEqual(get_args(callable_arg), ([int],)) + callable_arg = CallableP[int] + self.assertEqual(get_args(callable_arg), (int,)) + + callable_arg_list = CallableP[[int]] + self.assertEqual(get_args(callable_arg_list), ([int],)) + # (int, int) -> Any - callable_arg2 = CallableP[[int, int]] - self.assertEqual(get_args(callable_arg2), ([int, int],)) + callable_arg2 = CallableP[int, int] + self.assertEqual(get_args(callable_arg2), (int, int,)) + + callable_arg2_list = CallableP[[int, int]] + self.assertEqual(get_args(callable_arg2_list), ([int, int],)) # (...) -> Any callable_ellipsis = CallableP[...] self.assertEqual(get_args(callable_ellipsis), (...,)) + callable_ellipsis2 = CallableP[(...,)] self.assertEqual(callable_ellipsis, callable_ellipsis2) # (int, ...) -> Any callable_arg_more = CallableP[[int, ...]] self.assertEqual(get_args(callable_arg_more), ([int, ...],)) # (T) -> Any - callable_generic = CallableP[[T]] - self.assertEqual(get_args(callable_generic), ([T],)) callable_generic_raw = CallableP[T] self.assertEqual(get_args(callable_generic_raw), (T,)) + self.assertEqual(callable_generic_raw.__parameters__, (T,)) - # test invalid usage - if not TYPING_3_11_0: - with self.assertRaises(TypeError): - ListOrSetT[Generic[T]] - with self.assertRaises(TypeError): - ListOrSetT[(Generic[T], )] + # Usage with Concatenate + callable_concat = CallableP[Concatenate[int, P]] + self.assertEqual(callable_concat.__parameters__, (P,)) + if TYPING_3_11_0: + self.assertEqual(get_args(callable_concat), (Concatenate[int, P],)) + concat_usage = callable_concat[str] + self.assertEqual(get_args(concat_usage), ((int, str),)) + self.assertEqual(concat_usage, callable_concat[[str]]) + elif TYPING_3_10_0: + self.assertEqual(get_args(callable_concat), (int, P,)) + with self.assertRaises(TypeError, msg="Parameters to generic types must be types"): + callable_concat[str] + concat_usage = callable_concat[[str]] + self.assertEqual(get_args(concat_usage), (int, [str])) + else: + self.assertEqual(get_args(callable_concat), (int, P,)) + with self.assertRaises(TypeError, msg="Parameters to generic types must be types"): + callable_concat[[str]] + concat_usage = callable_concat[str] + self.assertEqual(get_args(concat_usage), (int, str)) + + # More complex cases + Ts = TypeVarTuple("Ts") + CallableTs = TypeAliasType("CallableTs", Callable[[Unpack[Ts]], Any], type_params=(Ts, )) + unpack_callable = CallableTs[Unpack[Tuple[int, T]]] + if TYPING_3_11_0: + self.assertEqual(get_args(unpack_callable), (Unpack[Tuple[int, T]],)) + else: + self.assertEqual(get_args(unpack_callable), (Tuple[int, T],)) + self.assertEqual(unpack_callable.__parameters__, (T,)) + + Variadic = TypeAliasType("Variadic", Tuple[int, Unpack[Ts]], type_params=(Ts,)) + mixed_subscripedPT = Variadic[Callable[Concatenate[int, P], T]] + self.assertEqual(mixed_subscripedPT.__parameters__, (P, T)) + self.assertEqual(get_args(mixed_subscripedPT), (Callable[Concatenate[int, P], T],)) + + done_subscripted_no_list = mixed_subscripedPT[T, Any] # Expected ParamSpec, ellipsis, or list of types + if TYPING_3_10_0: + done_subscripted_list = mixed_subscripedPT[[T], Any] + self.assertEqual(done_subscripted_list, done_subscripted_no_list) + else: + with self.assertRaises(TypeError, msg="Parameters to generic types must be types."): + mixed_subscripedPT[[T], Any] + + @skipUnless(TYPING_3_11_0, "__args__ behaves differently") + def test_311_substitution(self): + # To pass these tests alias.__args__ in TypeAliasType.__getitem__ needs adjustment + # Unpack and Concatenate are unpacked in versions before + T = TypeVar("T") + Ts = TypeVarTuple("Ts") + + CallableTs = TypeAliasType("CallableTs", Callable[[Unpack[Ts]], Any], type_params=(Ts, )) + unpack_callable = CallableTs[Unpack[Tuple[int, T]]] + self.assertEqual(get_args(unpack_callable), (Unpack[Tuple[int, T]],)) + + P = ParamSpec('P') + CallableP = TypeAliasType("CallableP", Callable[P, T], type_params=(P, T)) + callable_concat = CallableP[Concatenate[int, P], Any] + self.assertEqual(get_args(callable_concat), (Concatenate[int, P], Any)) + + @skipUnless(TYPING_3_12_0, "__args__ behaves differently") + def test_312_substitution(self): + # To pass these tests alias.__args__ in TypeAliasType.__getitem__ needs to be adjustment + # Would raise: TypeError: Substitution of bare TypeVarTuple is not supported + T = TypeVar("T") + Ts = TypeVarTuple("Ts") + Variadic = TypeAliasType("Variadic", Tuple[int, Unpack[Ts]], type_params=(Ts,)) + + subcriped_callable_tvt = Variadic[Callable[[Unpack[Ts]], T]] + variadic_tvt_callableA = subcriped_callable_tvt[str, object] + variadic_tvt_callableA2 = subcriped_callable_tvt[Unpack[Tuple[str]], object] + self.assertEqual(variadic_tvt_callableA, variadic_tvt_callableA2) + + variadic_tvt_callableB = subcriped_callable_tvt[[str, int], object] + variadic_tvt_callableB2 = subcriped_callable_tvt[Unpack[Tuple[str, int]], object] + variadic_tvt_callableB3 = subcriped_callable_tvt[str, int, object] + self.assertNotEqual(variadic_tvt_callableB, variadic_tvt_callableB2) + self.assertEqual(variadic_tvt_callableB2, variadic_tvt_callableB3) def test_invalid_cases(self): - # If these cases fail the specificiation might have changed + # NOTE: If these cases fail the specificiation might have changed + # some of the cases could be seen as valid but are currently not + + # More parameters T = TypeVar("T") T2 = TypeVar("T2") ListOrSetT = TypeAliasType("ListOrSetT", Union[List[T], Set[T]], type_params=(T,)) @@ -7317,6 +7411,7 @@ def test_invalid_cases(self): self.assertEqual(get_args(too_many), (int, bool)) self.assertEqual(too_many.__parameters__, ()) + # Not enough parameters ListOrSet2T = TypeAliasType("ListOrSet2T", Union[List[T], Set[T2]], type_params=(T, T2)) not_enough = ListOrSet2T[int] self.assertEqual(get_args(not_enough), (int,)) @@ -7357,15 +7452,45 @@ def test_invalid_cases(self): invalud_tuple_C = Variadic[[int, T]] self.assertEqual(invalud_tuple_C.__parameters__, ()) self.assertEqual(get_args(invalud_tuple_C), ([int, T],)) - - @skipUnless(TYPING_3_11_0, "Concatenate not unpacked anymore") - def test_further_invalid_cases(self): - P = ParamSpec('P') + + # Callable + # NOTE: This these cases seem to be more like a limitation in the typing variant + # The final variable is parameterless if using a list here. + callable_T = CallableP[[T]] + self.assertEqual(get_args(callable_T), ([T],)) + self.assertEqual(callable_T.__parameters__, ()) + with self.assertRaises(TypeError, msg="is not a generic class"): + callable_T[str] + + InvalidConcatP = CallableP[[int, P]] + self.assertEqual(get_args(InvalidConcatP), ([int, P],)) + self.assertEqual(InvalidConcatP.__parameters__, ()) + with self.assertRaises(TypeError, msg="is not a generic class"): + InvalidConcatP[str] + + # Callable + # NOTE: This these cases seem to be more like a limitation in the typing variant + callable_T = CallableP[[T]] + self.assertEqual(get_args(callable_T), ([T],)) + self.assertEqual(callable_T.__parameters__, ()) + with self.assertRaises(TypeError, msg="is not a generic class"): + callable_T[str] + + InvalidConcatP = CallableP[[int, P]] + self.assertEqual(get_args(InvalidConcatP), ([int, P],)) + self.assertEqual(InvalidConcatP.__parameters__, ()) + with self.assertRaises(TypeError, msg="is not a generic class"): + InvalidConcatP[str] + + @skipIf(TYPING_3_11_0, "Most cases are allowed in 3.11+") + def test_invalid_cases_before_3_11(self): T = TypeVar("T") - T2 = TypeVar("T2") - CallableP = TypeAliasType("CallableP", Callable[P, T], type_params=(P,)) - callable_concat = CallableP[Concatenate[Any, T2, P], Any] - self.assertEqual(get_args(callable_concat), (Concatenate[Any, T2, P], Any)) + ListOrSetT = TypeAliasType("ListOrSetT", Union[List[T], Set[T]], type_params=(T,)) + with self.assertRaises(TypeError): + ListOrSetT[Generic[T]] + with self.assertRaises(TypeError): + ListOrSetT[(Generic[T], )] + def test_pickle(self): global Alias From 255de764981e18ab853c61e968ac974f50d05e66 Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 23 Sep 2024 16:23:40 +0200 Subject: [PATCH 15/47] Clean duplicated tests --- src/test_typing_extensions.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index e4baa721..09ca2466 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -7342,26 +7342,10 @@ def test_getitem(self): # More complex cases Ts = TypeVarTuple("Ts") - CallableTs = TypeAliasType("CallableTs", Callable[[Unpack[Ts]], Any], type_params=(Ts, )) - unpack_callable = CallableTs[Unpack[Tuple[int, T]]] - if TYPING_3_11_0: - self.assertEqual(get_args(unpack_callable), (Unpack[Tuple[int, T]],)) - else: - self.assertEqual(get_args(unpack_callable), (Tuple[int, T],)) - self.assertEqual(unpack_callable.__parameters__, (T,)) - Variadic = TypeAliasType("Variadic", Tuple[int, Unpack[Ts]], type_params=(Ts,)) mixed_subscripedPT = Variadic[Callable[Concatenate[int, P], T]] - self.assertEqual(mixed_subscripedPT.__parameters__, (P, T)) self.assertEqual(get_args(mixed_subscripedPT), (Callable[Concatenate[int, P], T],)) - done_subscripted_no_list = mixed_subscripedPT[T, Any] # Expected ParamSpec, ellipsis, or list of types - if TYPING_3_10_0: - done_subscripted_list = mixed_subscripedPT[[T], Any] - self.assertEqual(done_subscripted_list, done_subscripted_no_list) - else: - with self.assertRaises(TypeError, msg="Parameters to generic types must be types."): - mixed_subscripedPT[[T], Any] @skipUnless(TYPING_3_11_0, "__args__ behaves differently") def test_311_substitution(self): From 3037923cf1502beebbf08fb410f97b2afff0a9df Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 23 Sep 2024 16:39:35 +0200 Subject: [PATCH 16/47] Removed duplicated or valid cases from invalid --- src/test_typing_extensions.py | 60 +++++++++++------------------------ 1 file changed, 19 insertions(+), 41 deletions(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 09ca2466..2a0fda83 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -7390,8 +7390,8 @@ def test_invalid_cases(self): T = TypeVar("T") T2 = TypeVar("T2") ListOrSetT = TypeAliasType("ListOrSetT", Union[List[T], Set[T]], type_params=(T,)) - too_many = ListOrSetT[int, bool] + too_many = ListOrSetT[int, bool] self.assertEqual(get_args(too_many), (int, bool)) self.assertEqual(too_many.__parameters__, ()) @@ -7416,55 +7416,33 @@ def test_invalid_cases(self): self.assertEqual(callable_too_many.__parameters__, (T2, )) self.assertEqual(get_args(callable_too_many), (str, float, T2, int, )) - # Test with Concatenate - callable_concat = CallableP[Concatenate[Any, T2, P], Any] - self.assertEqual(callable_concat.__parameters__, (T2, P)) + # Cases that result in parameterless variable + + # Callable + CallableT = CallableP[[T]] + self.assertEqual(get_args(CallableT), ([T],)) + self.assertEqual(CallableT.__parameters__, ()) + with self.assertRaises(TypeError, msg="is not a generic class"): + CallableT[str] + + ImplicitConcatP = CallableP[[int, P]] + self.assertEqual(get_args(ImplicitConcatP), ([int, P],)) + self.assertEqual(ImplicitConcatP.__parameters__, ()) + with self.assertRaises(TypeError, msg="is not a generic class"): + ImplicitConcatP[str] # TypeVarTuple Ts = TypeVarTuple("Ts") Variadic = TypeAliasType("Variadic", Tuple[int, Unpack[Ts]], type_params=(Ts,)) - # No Unpack - invalid_tuple_A = Variadic[Tuple[int, T]] - self.assertEqual(invalid_tuple_A.__parameters__, (T, )) - self.assertEqual(get_args(invalid_tuple_A), (Tuple[int, T], )) - - # To type tuple - invalid_tuple_B = Variadic[int, T] - self.assertEqual(invalid_tuple_B.__parameters__, (T, )) # No Tuple, but list - invalud_tuple_C = Variadic[[int, T]] - self.assertEqual(invalud_tuple_C.__parameters__, ()) - self.assertEqual(get_args(invalud_tuple_C), ([int, T],)) + invalid_tupleT = Variadic[[int, T]] + self.assertEqual(invalid_tupleT.__parameters__, ()) + self.assertEqual(get_args(invalid_tupleT), ([int, T],)) - # Callable - # NOTE: This these cases seem to be more like a limitation in the typing variant - # The final variable is parameterless if using a list here. - callable_T = CallableP[[T]] - self.assertEqual(get_args(callable_T), ([T],)) - self.assertEqual(callable_T.__parameters__, ()) - with self.assertRaises(TypeError, msg="is not a generic class"): - callable_T[str] - - InvalidConcatP = CallableP[[int, P]] - self.assertEqual(get_args(InvalidConcatP), ([int, P],)) - self.assertEqual(InvalidConcatP.__parameters__, ()) with self.assertRaises(TypeError, msg="is not a generic class"): - InvalidConcatP[str] + invalid_tupleT[str] - # Callable - # NOTE: This these cases seem to be more like a limitation in the typing variant - callable_T = CallableP[[T]] - self.assertEqual(get_args(callable_T), ([T],)) - self.assertEqual(callable_T.__parameters__, ()) - with self.assertRaises(TypeError, msg="is not a generic class"): - callable_T[str] - - InvalidConcatP = CallableP[[int, P]] - self.assertEqual(get_args(InvalidConcatP), ([int, P],)) - self.assertEqual(InvalidConcatP.__parameters__, ()) - with self.assertRaises(TypeError, msg="is not a generic class"): - InvalidConcatP[str] @skipIf(TYPING_3_11_0, "Most cases are allowed in 3.11+") def test_invalid_cases_before_3_11(self): From b8ae82ed356493c874a67916187e82f0851dc82f Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 23 Sep 2024 16:54:04 +0200 Subject: [PATCH 17/47] Slightly more refined tests covering more cases --- src/test_typing_extensions.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 2a0fda83..aa9e1e96 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -7185,16 +7185,22 @@ def test_attributes(self): self.assertEqual(Variadic.__type_params__, (Ts,)) self.assertEqual(Variadic.__parameters__, tuple(iter(Ts))) - subscripted_tuple = Variadic[Unpack[Tuple[int, float]]] + # Test bare + subscripted_tuple = Variadic[int, float] self.assertEqual(subscripted_tuple.__name__, "Variadic") self.assertEqual(subscripted_tuple.__value__, Tuple[int, Unpack[Ts]]) self.assertEqual(subscripted_tuple.__type_params__, (Ts,)) self.assertEqual(subscripted_tuple.__parameters__, ()) + # Test with Unpack subscripted_tupleT = Variadic[Unpack[Tuple[int, T]]] self.assertEqual(subscripted_tupleT.__name__, "Variadic") self.assertEqual(subscripted_tupleT.__parameters__, (T, )) + # Test with Unpack and TypeVarTuple + subscripted_Ts = Variadic[Unpack[Ts]] + self.assertEqual(subscripted_Ts.__parameters__, (Ts, )) + # Use with Callable # Use with Callable+Concatenate subscripted_callable_concat = Variadic[Callable[Concatenate[Literal["s"], P], T]] From 8d2ec0a082aebeadcb369d0cfdc811df12e124d3 Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 23 Sep 2024 16:58:32 +0200 Subject: [PATCH 18/47] Removed cases that is mentioned elsewhere --- 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 aa9e1e96..25695465 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -7329,18 +7329,15 @@ def test_getitem(self): callable_concat = CallableP[Concatenate[int, P]] self.assertEqual(callable_concat.__parameters__, (P,)) if TYPING_3_11_0: - self.assertEqual(get_args(callable_concat), (Concatenate[int, P],)) concat_usage = callable_concat[str] self.assertEqual(get_args(concat_usage), ((int, str),)) self.assertEqual(concat_usage, callable_concat[[str]]) elif TYPING_3_10_0: - self.assertEqual(get_args(callable_concat), (int, P,)) with self.assertRaises(TypeError, msg="Parameters to generic types must be types"): callable_concat[str] concat_usage = callable_concat[[str]] self.assertEqual(get_args(concat_usage), (int, [str])) else: - self.assertEqual(get_args(callable_concat), (int, P,)) with self.assertRaises(TypeError, msg="Parameters to generic types must be types"): callable_concat[[str]] concat_usage = callable_concat[str] From 7029d51b17da936d9031a9d2aa7e93b0d510b5d9 Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 24 Sep 2024 10:49:56 +0200 Subject: [PATCH 19/47] Revert change of global Protocol variables --- src/test_typing_extensions.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 25695465..53b2a41f 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -3572,13 +3572,13 @@ class P(Protocol): self.assertEqual(Alias, Alias2) def test_protocols_pickleable(self): - global GlobalProto, CP # pickle wants to reference the class by name + global P, CP # pickle wants to reference the class by name T = TypeVar('T') @runtime_checkable - class GlobalProto(Protocol[T]): + class P(Protocol[T]): x = 1 - class CP(GlobalProto[int]): + class CP(P[int]): pass c = CP() @@ -3591,7 +3591,7 @@ class CP(GlobalProto[int]): self.assertEqual(x.bar, 'abc') self.assertEqual(x.x, 1) self.assertEqual(x.__dict__, {'foo': 42, 'bar': 'abc'}) - s = pickle.dumps(GlobalProto) + s = pickle.dumps(P) D = pickle.loads(s) class E: x = 1 From 02fd0bae34a5aca613622032c36da87c4e71ca1a Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 24 Sep 2024 14:32:56 +0200 Subject: [PATCH 20/47] Raise TypeError on parameterless alias + formatting --- src/typing_extensions.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/typing_extensions.py b/src/typing_extensions.py index c2d84b83..f4c8f774 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -3528,14 +3528,16 @@ def __repr__(self) -> str: if sys.version_info >= (3, 11): def __getitem__(self, parameters): + if len(self.__type_params__) == 0: + raise TypeError("Only generic type aliases are subscriptable") if not isinstance(parameters, tuple): parameters = (parameters,) parameters = [ - typing._type_check( - item, f'Subscripting {self.__name__} requires a type.' - ) - for item in parameters - ] + typing._type_check( + item, f'Subscripting {self.__name__} requires a type.' + ) + for item in parameters + ] alias = typing._GenericAlias(self, tuple(parameters)) alias.__value__ = self.__value__ alias.__type_params__ = self.__type_params__ @@ -3560,12 +3562,14 @@ def _check_parameter(self, item, recursion=0): ) def __getitem__(self, parameters): + if len(self.__type_params__) == 0: + raise TypeError("Only generic type aliases are subscriptable") if not isinstance(parameters, tuple): parameters = (parameters,) parameters = [ - checked - for item in parameters - for checked in self._check_parameter(item) + checked + for item in parameters + for checked in self._check_parameter(item) ] if sys.version_info[:2] == (3, 10): alias = typing._GenericAlias(self, tuple(parameters), From b6fefb0204458a7ae5dcd309dc567ca981d05527 Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 24 Sep 2024 16:26:58 +0200 Subject: [PATCH 21/47] Correct subscription handling when type_params are empty --- src/test_typing_extensions.py | 18 ++++++++++++++++++ src/typing_extensions.py | 17 +++++++++++++++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 0442387e..63a5dfea 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -7457,6 +7457,24 @@ def test_invalid_cases_before_3_11(self): with self.assertRaises(TypeError): ListOrSetT[(Generic[T], )] + def test_subscription_without_type_params(self): + Simple = TypeAliasType("Simple", int) + with self.assertRaises(TypeError, msg="Only generic type aliases are subscriptable"): + Simple[int] + with self.assertRaises(TypeError, msg="Only generic type aliases are subscriptable"): + Simple[[]] + with self.assertRaises(TypeError, msg="Only generic type aliases are subscriptable"): + Simple[()] + + # no TypeVar in type_params, however in value still allows subscription + T = TypeVar("T") + MissingTypeParams = TypeAliasType("MissingTypeParams", List[T], type_params=()) + self.assertEqual(MissingTypeParams.__type_params__, ()) + self.assertEqual(MissingTypeParams.__parameters__, ()) + # These should not raise: + MissingTypeParams[int] + MissingTypeParams[[]] + MissingTypeParams[()] def test_pickle(self): global Alias diff --git a/src/typing_extensions.py b/src/typing_extensions.py index f4c8f774..44b58b5c 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -3526,9 +3526,22 @@ def _raise_attribute_error(self, name: str) -> Never: def __repr__(self) -> str: return self.__name__ + def _is_subscriptable(self): + if len(self.__parameters__) > 0: + return True + if _should_collect_from_parameters(self.__value__): + if hasattr(typing, '_collect_type_vars'): + more_parameters = _collect_type_vars((self.__value__,), + (TypeVar, ParamSpec)) + else: + more_parameters = _collect_parameters((self.__value__,)) + if more_parameters: + return True + return False + if sys.version_info >= (3, 11): def __getitem__(self, parameters): - if len(self.__type_params__) == 0: + if len(self.__parameters__) == 0 and not self._is_subscriptable(): raise TypeError("Only generic type aliases are subscriptable") if not isinstance(parameters, tuple): parameters = (parameters,) @@ -3562,7 +3575,7 @@ def _check_parameter(self, item, recursion=0): ) def __getitem__(self, parameters): - if len(self.__type_params__) == 0: + if len(self.__parameters__) == 0 and not self._is_subscriptable(): raise TypeError("Only generic type aliases are subscriptable") if not isinstance(parameters, tuple): parameters = (parameters,) From af0a133bd41ef6d3dbdc9620009726da8fcfd0da Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 24 Sep 2024 18:15:34 +0200 Subject: [PATCH 22/47] Restructured test cases --- src/test_typing_extensions.py | 237 +++++++++++++++++----------------- 1 file changed, 119 insertions(+), 118 deletions(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 63a5dfea..0d48a01c 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -7166,19 +7166,6 @@ def test_attributes(self): self.assertEqual(ListOrSetT.__type_params__, (T,)) self.assertEqual(ListOrSetT.__parameters__, (T,)) - subscripted = ListOrSetT[int] - self.assertEqual(subscripted.__name__, "ListOrSetT") - self.assertEqual(subscripted.__value__, Union[List[T], Set[T]],) - self.assertEqual(subscripted.__type_params__, (T, )) - self.assertEqual(subscripted.__parameters__, ()) - - T2 = TypeVar("T2") - subscriptedT = ListOrSetT[T2] - self.assertEqual(subscriptedT.__name__, "ListOrSetT") - self.assertEqual(subscriptedT.__value__, Union[List[T], Set[T]],) - self.assertEqual(subscriptedT.__type_params__, (T, )) - self.assertEqual(subscriptedT.__parameters__, (T2, )) - Ts = TypeVarTuple("Ts") Variadic = TypeAliasType("Variadic", Tuple[int, Unpack[Ts]], type_params=(Ts,)) self.assertEqual(Variadic.__name__, "Variadic") @@ -7186,37 +7173,47 @@ def test_attributes(self): self.assertEqual(Variadic.__type_params__, (Ts,)) self.assertEqual(Variadic.__parameters__, tuple(iter(Ts))) - # Test bare - subscripted_tuple = Variadic[int, float] - self.assertEqual(subscripted_tuple.__name__, "Variadic") - self.assertEqual(subscripted_tuple.__value__, Tuple[int, Unpack[Ts]]) - self.assertEqual(subscripted_tuple.__type_params__, (Ts,)) - self.assertEqual(subscripted_tuple.__parameters__, ()) - - # Test with Unpack - subscripted_tupleT = Variadic[Unpack[Tuple[int, T]]] - self.assertEqual(subscripted_tupleT.__name__, "Variadic") - self.assertEqual(subscripted_tupleT.__parameters__, (T, )) + P = ParamSpec('P') + CallableP = TypeAliasType("CallableP", Callable[P, Any], type_params=(P, )) + self.assertEqual(CallableP.__name__, "CallableP") + self.assertEqual(CallableP.__value__, Callable[P, Any]) + self.assertEqual(CallableP.__type_params__, (P,)) + self.assertEqual(CallableP.__parameters__, (P,)) - # Test with Unpack and TypeVarTuple - subscripted_Ts = Variadic[Unpack[Ts]] - self.assertEqual(subscripted_Ts.__parameters__, (Ts, )) + def test_attributes_from_origin(self): + T = TypeVar('T') + ListOrSetT = TypeAliasType("ListOrSetT", Union[List[T], Set[T]], type_params=(T,)) + subscripted = ListOrSetT[int] + self.assertIs(get_origin(subscripted), ListOrSetT) + self.assertEqual(subscripted.__name__, "ListOrSetT") + self.assertEqual(subscripted.__value__, Union[List[T], Set[T]],) + self.assertEqual(subscripted.__type_params__, (T, )) - # Use with Callable - # Use with Callable+Concatenate - subscripted_callable_concat = Variadic[Callable[Concatenate[Literal["s"], P], T]] - self.assertEqual(subscripted_callable_concat.__parameters__, (P, T)) + still_generic = ListOrSetT[Iterable[T]] + self.assertIs(get_origin(still_generic), ListOrSetT) + fully_subscripted = still_generic[float] + self.assertIs(get_origin(fully_subscripted), ListOrSetT) + # __name__ needs Python 3.10+ + # __value__ and __type_params__ need Python 3.12+ + # Further tests are below - subcriped_callable_tvt = Variadic[Callable[[Unpack[Ts]], T]] - self.assertEqual(subcriped_callable_tvt.__parameters__, (Ts, T)) + @skipUnless(TYPING_3_10_0, "__name__ not added to GenericAlias") + def test_attributes_from_origin_3_10_plus(self): + T = TypeVar('T') + ListOrSetT = TypeAliasType("ListOrSetT", Union[List[T], Set[T]], type_params=(T,)) + fully_subscripted = ListOrSetT[Iterable[T]][float] + self.assertEqual(fully_subscripted.__name__, "ListOrSetT") + # __value__ and __type_params__ need Python 3.12+ - # Use with Callable+Unpack - CallableTs = TypeAliasType("CallableTs", Callable[[Unpack[Ts]], Any], type_params=(Ts, )) - self.assertEqual(CallableTs.__type_params__, (Ts,)) - self.assertEqual(CallableTs.__parameters__, (*Ts,)) + @skipUnless(TYPING_3_12_0, "attributes not added to GenericAlias") + def test_attributes_from_origin_3_12_plus(self): + T = TypeVar('T') + ListOrSetT = TypeAliasType("ListOrSetT", Union[List[T], Set[T]], type_params=(T,)) + fully_subscripted = ListOrSetT[Iterable[T]][float] + self.assertEqual(fully_subscripted.__name__, "ListOrSetT") + self.assertEqual(fully_subscripted.__value__, Union[List[T], Set[T]],) + self.assertEqual(fully_subscripted.__type_params__, (T, )) - unpack_callable = CallableTs[Unpack[Tuple[int, T]]] - self.assertEqual(unpack_callable.__parameters__, (T,)) def test_cannot_set_attributes(self): Simple = TypeAliasType("Simple", int) @@ -7278,55 +7275,56 @@ def test_or(self): Alias | "Ref" def test_getitem(self): - T = TypeVar("T") + T = TypeVar('T') + ValueWithoutT = TypeAliasType("ValueWithoutT", int, type_params=(T,)) + still_subscripted = ValueWithoutT[str] + self.assertEqual(get_args(still_subscripted), (str,)) + ListOrSetT = TypeAliasType("ListOrSetT", Union[List[T], Set[T]], type_params=(T,)) subscripted = ListOrSetT[int] self.assertEqual(get_args(subscripted), (int,)) - self.assertIs(get_origin(subscripted), ListOrSetT) with self.assertRaises(TypeError, msg="not a generic class"): subscripted[int] still_generic = ListOrSetT[Iterable[T]] self.assertEqual(get_args(still_generic), (Iterable[T],)) - self.assertIs(get_origin(still_generic), ListOrSetT) fully_subscripted = still_generic[float] self.assertEqual(get_args(fully_subscripted), (Iterable[float],)) - self.assertIs(get_origin(fully_subscripted), ListOrSetT) - # Test ParamSpec and Ellipsis + def test_callable_without_concatenate(self): P = ParamSpec('P') CallableP = TypeAliasType("CallableP", Callable[P, Any], type_params=(P,)) - # () -> Any - callable_no_arg = CallableP[[]] - self.assertEqual(get_args(callable_no_arg), ([],)) - # (int) -> Any - callable_arg = CallableP[int] - self.assertEqual(get_args(callable_arg), (int,)) - - callable_arg_list = CallableP[[int]] - self.assertEqual(get_args(callable_arg_list), ([int],)) - - # (int, int) -> Any - callable_arg2 = CallableP[int, int] - self.assertEqual(get_args(callable_arg2), (int, int,)) - - callable_arg2_list = CallableP[[int, int]] - self.assertEqual(get_args(callable_arg2_list), ([int, int],)) - # (...) -> Any - callable_ellipsis = CallableP[...] - self.assertEqual(get_args(callable_ellipsis), (...,)) - - callable_ellipsis2 = CallableP[(...,)] - self.assertEqual(callable_ellipsis, callable_ellipsis2) - # (int, ...) -> Any - callable_arg_more = CallableP[[int, ...]] - self.assertEqual(get_args(callable_arg_more), ([int, ...],)) + get_args_test_cases = [ + # List of (alias, expected_args) + # () -> Any + (CallableP[()], ()), + (CallableP[[]], ([],)), + # (int) -> Any + (CallableP[int], (int,)), + (CallableP[[int]], ([int],)), + # (int, int) -> Any + (CallableP[int, int], (int, int)), + (CallableP[[int, int]], ([int, int],)), + # (...) -> Any + (CallableP[...], (...,)), + # (int, ...) -> Any + (CallableP[[int, ...]], ([int, ...],)), + ] + + for index, (expression, expected_args) in enumerate(get_args_test_cases): + with self.subTest(index=index, expression=expression): + self.assertEqual(get_args(expression), expected_args) + + self.assertEqual(CallableP[...], CallableP[(...,)]) # (T) -> Any - callable_generic_raw = CallableP[T] - self.assertEqual(get_args(callable_generic_raw), (T,)) - self.assertEqual(callable_generic_raw.__parameters__, (T,)) + CallableT = CallableP[T] + self.assertEqual(get_args(CallableT), (T,)) + self.assertEqual(CallableT.__parameters__, (T,)) + + def test_callable_with_concatenate(self): + P = ParamSpec('P') + CallableP = TypeAliasType("CallableP", Callable[P, Any], type_params=(P,)) - # Usage with Concatenate callable_concat = CallableP[Concatenate[int, P]] self.assertEqual(callable_concat.__parameters__, (P,)) if TYPING_3_11_0: @@ -7344,18 +7342,11 @@ def test_getitem(self): concat_usage = callable_concat[str] self.assertEqual(get_args(concat_usage), (int, str)) - # More complex cases - Ts = TypeVarTuple("Ts") - Variadic = TypeAliasType("Variadic", Tuple[int, Unpack[Ts]], type_params=(Ts,)) - mixed_subscripedPT = Variadic[Callable[Concatenate[int, P], T]] - self.assertEqual(get_args(mixed_subscripedPT), (Callable[Concatenate[int, P], T],)) - - @skipUnless(TYPING_3_11_0, "__args__ behaves differently") - def test_311_substitution(self): + def test_substitution_311_plus(self): # To pass these tests alias.__args__ in TypeAliasType.__getitem__ needs adjustment # Unpack and Concatenate are unpacked in versions before - T = TypeVar("T") + T = TypeVar('T') Ts = TypeVarTuple("Ts") CallableTs = TypeAliasType("CallableTs", Callable[[Unpack[Ts]], Any], type_params=(Ts, )) @@ -7368,10 +7359,10 @@ def test_311_substitution(self): self.assertEqual(get_args(callable_concat), (Concatenate[int, P], Any)) @skipUnless(TYPING_3_12_0, "__args__ behaves differently") - def test_312_substitution(self): - # To pass these tests alias.__args__ in TypeAliasType.__getitem__ needs to be adjustment + def test_substitution_312_plus(self): + # To pass these tests alias.__args__ in TypeAliasType.__getitem__ needs adjustment # Would raise: TypeError: Substitution of bare TypeVarTuple is not supported - T = TypeVar("T") + T = TypeVar('T') Ts = TypeVarTuple("Ts") Variadic = TypeAliasType("Variadic", Tuple[int, Unpack[Ts]], type_params=(Ts,)) @@ -7386,48 +7377,62 @@ def test_312_substitution(self): self.assertNotEqual(variadic_tvt_callableB, variadic_tvt_callableB2) self.assertEqual(variadic_tvt_callableB2, variadic_tvt_callableB3) - def test_invalid_cases(self): - # NOTE: If these cases fail the specificiation might have changed - # some of the cases could be seen as valid but are currently not - - # More parameters - T = TypeVar("T") + def test_wrong_amount_of_parameters(self): + T = TypeVar('T') T2 = TypeVar("T2") + P = ParamSpec('P') ListOrSetT = TypeAliasType("ListOrSetT", Union[List[T], Set[T]], type_params=(T,)) - - too_many = ListOrSetT[int, bool] - self.assertEqual(get_args(too_many), (int, bool)) - self.assertEqual(too_many.__parameters__, ()) + TwoT = TypeAliasType("TwoT", Union[List[T], Set[T2]], type_params=(T, T2)) + CallablePT = TypeAliasType("CallablePT", Callable[P, T], type_params=(P, T)) # Not enough parameters - ListOrSet2T = TypeAliasType("ListOrSet2T", Union[List[T], Set[T2]], type_params=(T, T2)) - not_enough = ListOrSet2T[int] + not_enough = TwoT[int] self.assertEqual(get_args(not_enough), (int,)) self.assertEqual(not_enough.__parameters__, ()) - not_enough2 = ListOrSet2T[T] + not_enough2 = TwoT[T] self.assertEqual(get_args(not_enough2), (T,)) self.assertEqual(not_enough2.__parameters__, (T,)) - # ParamSpec - P = ParamSpec('P') - CallableP = TypeAliasType("CallableP", Callable[P, T], type_params=(P,)) - callable_not_enough = CallableP[int] - self.assertEqual(callable_not_enough.__parameters__, ()) + callable_not_enough = CallablePT[int] self.assertEqual(get_args(callable_not_enough), (int, )) + self.assertEqual(callable_not_enough.__parameters__, ()) + + # Too many + too_many = ListOrSetT[int, bool] + self.assertEqual(get_args(too_many), (int, bool)) + self.assertEqual(too_many.__parameters__, ()) - callable_too_many = CallableP[str, float, T2, int] - self.assertEqual(callable_too_many.__parameters__, (T2, )) - self.assertEqual(get_args(callable_too_many), (str, float, T2, int, )) + callable_too_many = CallablePT[str, float, int] + self.assertEqual(get_args(callable_too_many), (str, float, int)) + self.assertEqual(callable_too_many.__parameters__, ()) - # Cases that result in parameterless variable + # Check if TypeVar is still present even if over substituted + too_manyT = ListOrSetT[int, T] + self.assertEqual(get_args(too_manyT), (int, T)) + self.assertEqual(too_manyT.__parameters__, (T, )) + + # With and without list for ParamSpec + callable_too_manyT = CallablePT[str, float, T] + self.assertEqual(get_args(callable_too_manyT), (str, float, T)) + self.assertEqual(callable_too_manyT.__parameters__, (T, )) + + callable_too_manyT2 = CallablePT[[str], float, int, T2] + self.assertEqual(get_args(callable_too_manyT2), ([str], float, int, T2)) + self.assertEqual(callable_too_manyT2.__parameters__, (T2, )) + + def test_list_argument(self): + # NOTE: These cases could be seen as valid but result in a parameterless + # variable. If these tests fail the specificiation might have changed # Callable - CallableT = CallableP[[T]] - self.assertEqual(get_args(CallableT), ([T],)) - self.assertEqual(CallableT.__parameters__, ()) + P = ParamSpec('P') + CallableP = TypeAliasType("CallableP", Callable[P, Any], type_params=(P,)) + CallableT_list = CallableP[[T]] + self.assertEqual(get_args(CallableT_list), ([T],)) + self.assertEqual(CallableT_list.__parameters__, ()) with self.assertRaises(TypeError, msg="is not a generic class"): - CallableT[str] + CallableT_list[str] ImplicitConcatP = CallableP[[int, P]] self.assertEqual(get_args(ImplicitConcatP), ([int, P],)) @@ -7438,19 +7443,15 @@ def test_invalid_cases(self): # TypeVarTuple Ts = TypeVarTuple("Ts") Variadic = TypeAliasType("Variadic", Tuple[int, Unpack[Ts]], type_params=(Ts,)) - - # No Tuple, but list invalid_tupleT = Variadic[[int, T]] - self.assertEqual(invalid_tupleT.__parameters__, ()) self.assertEqual(get_args(invalid_tupleT), ([int, T],)) - + self.assertEqual(invalid_tupleT.__parameters__, ()) with self.assertRaises(TypeError, msg="is not a generic class"): invalid_tupleT[str] - @skipIf(TYPING_3_11_0, "Most cases are allowed in 3.11+") def test_invalid_cases_before_3_11(self): - T = TypeVar("T") + T = TypeVar('T') ListOrSetT = TypeAliasType("ListOrSetT", Union[List[T], Set[T]], type_params=(T,)) with self.assertRaises(TypeError): ListOrSetT[Generic[T]] @@ -7467,7 +7468,7 @@ def test_subscription_without_type_params(self): Simple[()] # no TypeVar in type_params, however in value still allows subscription - T = TypeVar("T") + T = TypeVar('T') MissingTypeParams = TypeAliasType("MissingTypeParams", List[T], type_params=()) self.assertEqual(MissingTypeParams.__type_params__, ()) self.assertEqual(MissingTypeParams.__parameters__, ()) From f2aa35c38cfded86df6f2d81bfe2315be7f262dd Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 24 Sep 2024 18:15:50 +0200 Subject: [PATCH 23/47] Added general and comprehensive test --- src/test_typing_extensions.py | 70 +++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 0d48a01c..a556e447 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -7214,6 +7214,76 @@ def test_attributes_from_origin_3_12_plus(self): self.assertEqual(fully_subscripted.__value__, Union[List[T], Set[T]],) self.assertEqual(fully_subscripted.__type_params__, (T, )) + def test_alias_types_and_substitutions(self): + T = TypeVar('T') + T2 = TypeVar('T2') + T_default = TypeVar("T_default", default=int) + Ts = TypeVarTuple("Ts") + P = ParamSpec('P') + + test_argument_cases = { + # arguments : expected parameters + int : (), + ... : (), + T2 : (T2,), + Union[int, List[T2]] : (T2,), + Ts : (Ts,), + Tuple[int, str] : (), + Tuple[T, T_default, T2] : (T, T_default, T2), + Tuple[Unpack[Ts]] : (Ts,), + Callable[[Unpack[Ts]], T2] : (Ts, T2), + Callable[P, T2] : (P, T2), + Callable[Concatenate[T2, P], T_default] : (T2, P, T_default), + TypeAliasType("NestedAlias", List[T], type_params=(T,))[T2] : (T2,), + } + # currently a limitation, these args are no longer unpacked in 3.11 + test_argument_cases_311_plus = { + Unpack[Ts] : (Ts,), + Unpack[Tuple[int, T2]] : (T2,), + Concatenate[int, P] : (P,), + } + test_argument_cases.update(test_argument_cases_311_plus) + + test_alias_cases = [ + # Simple cases + TypeAliasType("ListT", List[T], type_params=(T,)), + TypeAliasType("UnionT", Union[int, List[T]], type_params=(T,)), + # Either value or type_params contain generic + TypeAliasType("ValueWithoutT", int, type_params=(T,)), + TypeAliasType("ValueTNoParams", List[T], type_params=()), + # Callable + TypeAliasType("CallableP", Callable[P, Any], type_params=(P, )), + TypeAliasType("CallableT", Callable[..., T], type_params=(T, )), + TypeAliasType("CallableTs", Callable[[Unpack[Ts]], Any], type_params=(Ts, )), + # TypeVarTuple + TypeAliasType("Variadic", Tuple[int, Unpack[Ts]], type_params=(Ts,)), + # TypeVar with default + TypeAliasType("TupleT_default", Tuple[T_default, T], type_params=(T, T_default)), + TypeAliasType("CallableT_default", Callable[[T], T_default], type_params=(T, T_default)), + # default order reversed + TypeAliasType("TupleT_default_reversed", Tuple[T_default, T], type_params=(T_default, T)), + TypeAliasType("CallableT_default_reversed", Callable[[T], T_default], type_params=(T_default, T)), + ] + + for alias in test_alias_cases: + with self.subTest(alias=alias, args=[]): + subscripted = alias[[]] + self.assertEqual(get_args(subscripted), ([],)) + self.assertEqual(subscripted.__parameters__, ()) + with self.subTest(alias=alias, args=()): + subscripted = alias[()] + self.assertEqual(get_args(subscripted), ()) + self.assertEqual(subscripted.__parameters__, ()) + with self.subTest(alias=alias, args=(int, float)): + subscripted = alias[int, float] + self.assertEqual(get_args(subscripted), (int, float)) + self.assertEqual(subscripted.__parameters__, ()) + for expected_args, expected_parameters in test_argument_cases.items(): + with self.subTest(alias=alias, args=expected_args): + if expected_args in test_argument_cases_311_plus and sys.version_info < (3, 11): + self.skipTest("args are unpacked before 3.11") + self.assertEqual(get_args(alias[expected_args]), (expected_args,)) + self.assertEqual(alias[expected_args].__parameters__, expected_parameters) def test_cannot_set_attributes(self): Simple = TypeAliasType("Simple", int) From 6b1bafbd78ec239dbfa3cf61eb880fd14e3aeb11 Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 24 Sep 2024 18:51:09 +0200 Subject: [PATCH 24/47] Subtests to test parameter amount --- src/test_typing_extensions.py | 55 +++++++++++++---------------------- 1 file changed, 21 insertions(+), 34 deletions(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index a556e447..0e0eea70 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -7456,40 +7456,27 @@ def test_wrong_amount_of_parameters(self): CallablePT = TypeAliasType("CallablePT", Callable[P, T], type_params=(P, T)) # Not enough parameters - not_enough = TwoT[int] - self.assertEqual(get_args(not_enough), (int,)) - self.assertEqual(not_enough.__parameters__, ()) - - not_enough2 = TwoT[T] - self.assertEqual(get_args(not_enough2), (T,)) - self.assertEqual(not_enough2.__parameters__, (T,)) - - callable_not_enough = CallablePT[int] - self.assertEqual(get_args(callable_not_enough), (int, )) - self.assertEqual(callable_not_enough.__parameters__, ()) - - # Too many - too_many = ListOrSetT[int, bool] - self.assertEqual(get_args(too_many), (int, bool)) - self.assertEqual(too_many.__parameters__, ()) - - callable_too_many = CallablePT[str, float, int] - self.assertEqual(get_args(callable_too_many), (str, float, int)) - self.assertEqual(callable_too_many.__parameters__, ()) - - # Check if TypeVar is still present even if over substituted - too_manyT = ListOrSetT[int, T] - self.assertEqual(get_args(too_manyT), (int, T)) - self.assertEqual(too_manyT.__parameters__, (T, )) - - # With and without list for ParamSpec - callable_too_manyT = CallablePT[str, float, T] - self.assertEqual(get_args(callable_too_manyT), (str, float, T)) - self.assertEqual(callable_too_manyT.__parameters__, (T, )) - - callable_too_manyT2 = CallablePT[[str], float, int, T2] - self.assertEqual(get_args(callable_too_manyT2), ([str], float, int, T2)) - self.assertEqual(callable_too_manyT2.__parameters__, (T2, )) + test_cases = [ + # not_enough + (TwoT[int], [(int,), ()]), + (TwoT[T], [(T,), (T,)]), + # callable and not enough + (CallablePT[int], [(int,), ()]), + # too many + (ListOrSetT[int, bool], [(int, bool), ()]), + # callable and too many + (CallablePT[str, float, int], [(str, float, int), ()]), + # Check if TypeVar is still present even if over substituted + (ListOrSetT[int, T], [(int, T), (T,)]), + # With and without list for ParamSpec + (CallablePT[str, float, T], [(str, float, T), (T,)]), + (CallablePT[[str], float, int, T2], [([str], float, int, T2), (T2,)]), + ] + + for index, (alias, [expected_args, expected_params]) in enumerate(test_cases): + with self.subTest(index=index, alias=alias): + self.assertEqual(get_args(alias), expected_args) + self.assertEqual(alias.__parameters__, expected_params) def test_list_argument(self): # NOTE: These cases could be seen as valid but result in a parameterless From 7c1fea70b8bd53b1b18dc4a8190395e6bddb16b9 Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 24 Sep 2024 20:47:31 +0200 Subject: [PATCH 25/47] unify __getitem__ again --- src/typing_extensions.py | 78 +++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 42 deletions(-) diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 44b58b5c..80b6d778 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -3539,61 +3539,55 @@ def _is_subscriptable(self): return True return False - if sys.version_info >= (3, 11): - def __getitem__(self, parameters): - if len(self.__parameters__) == 0 and not self._is_subscriptable(): - raise TypeError("Only generic type aliases are subscriptable") - if not isinstance(parameters, tuple): - parameters = (parameters,) - parameters = [ - typing._type_check( - item, f'Subscripting {self.__name__} requires a type.' - ) - for item in parameters - ] - alias = typing._GenericAlias(self, tuple(parameters)) - alias.__value__ = self.__value__ - alias.__type_params__ = self.__type_params__ - return alias - else: - def _check_parameter(self, item, recursion=0): + if sys.version_info < (3, 11): + def _check_single_param(self, param, recursion=0): # Allow [], [int], [int, str], [int, ...], [int, T] - if isinstance(item, (_UnpackAlias, _ConcatenateGenericAlias)): + if isinstance(param, (_UnpackAlias, _ConcatenateGenericAlias)): # Unpack yield from [checked - for arg in item.__args__ - for checked in self._check_parameter(arg, recursion+1)] - elif item is ...: + for arg in param.__args__ + for checked in self._check_single_param(arg, recursion+1)] + elif param is ...: yield ... - elif isinstance(item, list) and recursion == 0: + elif isinstance(param, list) and recursion == 0: yield [checked - for arg in item - for checked in self._check_parameter(arg, recursion+1)] + for arg in param + for checked in self._check_single_param(arg, recursion+1)] else: yield typing._type_check( - item, f'Subscripting {self.__name__} requires a type.' + param, f'Subscripting {self.__name__} requires a type.' ) - def __getitem__(self, parameters): - if len(self.__parameters__) == 0 and not self._is_subscriptable(): - raise TypeError("Only generic type aliases are subscriptable") - if not isinstance(parameters, tuple): - parameters = (parameters,) - parameters = [ + def _check_parameters(self, parameters): + if sys.version_info < (3, 11): + return [ checked for item in parameters - for checked in self._check_parameter(item) + for checked in self._check_single_param(item) ] - if sys.version_info[:2] == (3, 10): - alias = typing._GenericAlias(self, tuple(parameters), - _typevar_types=(TypeVar, ParamSpec) - ) - else: - alias = typing._GenericAlias(self, tuple(parameters)) + return [typing._type_check( + item, f'Subscripting {self.__name__} requires a type.' + ) + for item in parameters + ] + + def __getitem__(self, parameters): + if len(self.__parameters__) == 0 and not self._is_subscriptable(): + raise TypeError("Only generic type aliases are subscriptable") + if not isinstance(parameters, tuple): + parameters = (parameters,) + parameters = self._check_parameters(parameters) + if sys.version_info[:2] == (3, 10): + alias = typing._GenericAlias(self, tuple(parameters), + _typevar_types=(TypeVar, ParamSpec) + ) + else: + alias = typing._GenericAlias(self, tuple(parameters)) + alias.__value__ = self.__value__ + alias.__type_params__ = self.__type_params__ + if not hasattr(alias, '__name__'): # < 3.11 alias.__name__ = self.__name__ - alias.__value__ = self.__value__ - alias.__type_params__ = self.__type_params__ - return alias + return alias def __reduce__(self): return self.__name__ From efa121454549262cab89d05ecf9ad5a93a4178e3 Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 25 Sep 2024 10:41:36 +0200 Subject: [PATCH 26/47] covered all cases for TypeErrors during subscription --- src/test_typing_extensions.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 0e0eea70..9f1c015a 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -7523,16 +7523,32 @@ def test_subscription_without_type_params(self): Simple[[]] with self.assertRaises(TypeError, msg="Only generic type aliases are subscriptable"): Simple[()] - - # no TypeVar in type_params, however in value still allows subscription + + # A TypeVar in the value does not allow subscription T = TypeVar('T') + MissingTypeParamsErr = TypeAliasType("MissingTypeParamsErr", List[T]) + self.assertEqual(MissingTypeParamsErr.__type_params__, ()) + self.assertEqual(MissingTypeParamsErr.__parameters__, ()) + with self.assertRaises(TypeError, msg="Only generic type aliases are subscriptable"): + MissingTypeParamsErr[int] + with self.assertRaises(TypeError, msg="Only generic type aliases are subscriptable"): + MissingTypeParamsErr[[]] + with self.assertRaises(TypeError, msg="Only generic type aliases are subscriptable"): + MissingTypeParamsErr[()] + + # However, providing type_params=() argument allows subscription MissingTypeParams = TypeAliasType("MissingTypeParams", List[T], type_params=()) self.assertEqual(MissingTypeParams.__type_params__, ()) self.assertEqual(MissingTypeParams.__parameters__, ()) - # These should not raise: + # These do not raise MissingTypeParams[int] MissingTypeParams[[]] MissingTypeParams[()] + # These do not raise + Simple2 = TypeAliasType("Simple2", int, type_params=()) + Simple2[int] + Simple2[[]] + Simple2[()] def test_pickle(self): global Alias From 66eebb19687f433a18e151e365d725d92afa8d31 Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 25 Sep 2024 10:43:13 +0200 Subject: [PATCH 27/47] Removed code addressing #468 --- src/test_typing_extensions.py | 34 ---------------------------------- src/typing_extensions.py | 15 --------------- 2 files changed, 49 deletions(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 9f1c015a..1f49c463 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -7515,40 +7515,6 @@ def test_invalid_cases_before_3_11(self): with self.assertRaises(TypeError): ListOrSetT[(Generic[T], )] - def test_subscription_without_type_params(self): - Simple = TypeAliasType("Simple", int) - with self.assertRaises(TypeError, msg="Only generic type aliases are subscriptable"): - Simple[int] - with self.assertRaises(TypeError, msg="Only generic type aliases are subscriptable"): - Simple[[]] - with self.assertRaises(TypeError, msg="Only generic type aliases are subscriptable"): - Simple[()] - - # A TypeVar in the value does not allow subscription - T = TypeVar('T') - MissingTypeParamsErr = TypeAliasType("MissingTypeParamsErr", List[T]) - self.assertEqual(MissingTypeParamsErr.__type_params__, ()) - self.assertEqual(MissingTypeParamsErr.__parameters__, ()) - with self.assertRaises(TypeError, msg="Only generic type aliases are subscriptable"): - MissingTypeParamsErr[int] - with self.assertRaises(TypeError, msg="Only generic type aliases are subscriptable"): - MissingTypeParamsErr[[]] - with self.assertRaises(TypeError, msg="Only generic type aliases are subscriptable"): - MissingTypeParamsErr[()] - - # However, providing type_params=() argument allows subscription - MissingTypeParams = TypeAliasType("MissingTypeParams", List[T], type_params=()) - self.assertEqual(MissingTypeParams.__type_params__, ()) - self.assertEqual(MissingTypeParams.__parameters__, ()) - # These do not raise - MissingTypeParams[int] - MissingTypeParams[[]] - MissingTypeParams[()] - # These do not raise - Simple2 = TypeAliasType("Simple2", int, type_params=()) - Simple2[int] - Simple2[[]] - Simple2[()] def test_pickle(self): global Alias diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 80b6d778..8308cb74 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -3526,19 +3526,6 @@ def _raise_attribute_error(self, name: str) -> Never: def __repr__(self) -> str: return self.__name__ - def _is_subscriptable(self): - if len(self.__parameters__) > 0: - return True - if _should_collect_from_parameters(self.__value__): - if hasattr(typing, '_collect_type_vars'): - more_parameters = _collect_type_vars((self.__value__,), - (TypeVar, ParamSpec)) - else: - more_parameters = _collect_parameters((self.__value__,)) - if more_parameters: - return True - return False - if sys.version_info < (3, 11): def _check_single_param(self, param, recursion=0): # Allow [], [int], [int, str], [int, ...], [int, T] @@ -3572,8 +3559,6 @@ def _check_parameters(self, parameters): ] def __getitem__(self, parameters): - if len(self.__parameters__) == 0 and not self._is_subscriptable(): - raise TypeError("Only generic type aliases are subscriptable") if not isinstance(parameters, tuple): parameters = (parameters,) parameters = self._check_parameters(parameters) From df10751b48b66ab4f6b90c3c87a53b91f228f2d8 Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 25 Sep 2024 15:03:59 +0200 Subject: [PATCH 28/47] Use _types.GenericAlias for TypeAliasType in 3.10+ --- src/test_typing_extensions.py | 36 ++++++++++++++++++----------------- src/typing_extensions.py | 12 ++++-------- 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 1f49c463..410dff9d 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -7227,7 +7227,6 @@ def test_alias_types_and_substitutions(self): ... : (), T2 : (T2,), Union[int, List[T2]] : (T2,), - Ts : (Ts,), Tuple[int, str] : (), Tuple[T, T_default, T2] : (T, T_default, T2), Tuple[Unpack[Ts]] : (Ts,), @@ -7237,11 +7236,16 @@ def test_alias_types_and_substitutions(self): TypeAliasType("NestedAlias", List[T], type_params=(T,))[T2] : (T2,), } # currently a limitation, these args are no longer unpacked in 3.11 - test_argument_cases_311_plus = { - Unpack[Ts] : (Ts,), + # also valid on 310 if GenericAlias is used + test_argument_cases_310_plus = { Unpack[Tuple[int, T2]] : (T2,), Concatenate[int, P] : (P,), + Unpack[Ts] : (Ts,), } + test_argument_cases_311_plus = { + Ts : (Ts,), + } + test_argument_cases.update(test_argument_cases_310_plus) test_argument_cases.update(test_argument_cases_311_plus) test_alias_cases = [ @@ -7280,8 +7284,10 @@ def test_alias_types_and_substitutions(self): self.assertEqual(subscripted.__parameters__, ()) for expected_args, expected_parameters in test_argument_cases.items(): with self.subTest(alias=alias, args=expected_args): + if expected_args in test_argument_cases_310_plus and sys.version_info < (3, 10): + self.skipTest("args are unpacked before 3.11 or need GenericAlias") if expected_args in test_argument_cases_311_plus and sys.version_info < (3, 11): - self.skipTest("args are unpacked before 3.11") + self.skipTest("Case is not valid before 3.11") self.assertEqual(get_args(alias[expected_args]), (expected_args,)) self.assertEqual(alias[expected_args].__parameters__, expected_parameters) @@ -7397,20 +7403,15 @@ def test_callable_with_concatenate(self): callable_concat = CallableP[Concatenate[int, P]] self.assertEqual(callable_concat.__parameters__, (P,)) - if TYPING_3_11_0: - concat_usage = callable_concat[str] + concat_usage = callable_concat[str] + with self.subTest("get_args of Concatenate in TypeAliasType"): + if sys.version_info < (3, 10, 2): + self.skipTest("GenericAlias keeps Concatenate in __args__ prior to 3.10.2") self.assertEqual(get_args(concat_usage), ((int, str),)) + with self.subTest("Equality of parameter_expression without []"): + if not TYPING_3_10_0: + self.skipTest("Nested list is invalid type form") self.assertEqual(concat_usage, callable_concat[[str]]) - elif TYPING_3_10_0: - with self.assertRaises(TypeError, msg="Parameters to generic types must be types"): - callable_concat[str] - concat_usage = callable_concat[[str]] - self.assertEqual(get_args(concat_usage), (int, [str])) - else: - with self.assertRaises(TypeError, msg="Parameters to generic types must be types"): - callable_concat[[str]] - concat_usage = callable_concat[str] - self.assertEqual(get_args(concat_usage), (int, str)) @skipUnless(TYPING_3_11_0, "__args__ behaves differently") def test_substitution_311_plus(self): @@ -7506,7 +7507,8 @@ def test_list_argument(self): with self.assertRaises(TypeError, msg="is not a generic class"): invalid_tupleT[str] - @skipIf(TYPING_3_11_0, "Most cases are allowed in 3.11+") + # The condition should align with the version of GeneriAlias usage in __getitem__ + @skipIf(TYPING_3_9_0, "Most cases are allowed in 3.11+ or with GenericAlias") def test_invalid_cases_before_3_11(self): T = TypeVar('T') ListOrSetT = TypeAliasType("ListOrSetT", Union[List[T], Set[T]], type_params=(T,)) diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 8308cb74..8200fde3 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -3561,17 +3561,13 @@ def _check_parameters(self, parameters): def __getitem__(self, parameters): if not isinstance(parameters, tuple): parameters = (parameters,) + if sys.version_info >= (3, 10): + return _types.GenericAlias(self, tuple(parameters)) parameters = self._check_parameters(parameters) - if sys.version_info[:2] == (3, 10): - alias = typing._GenericAlias(self, tuple(parameters), - _typevar_types=(TypeVar, ParamSpec) - ) - else: - alias = typing._GenericAlias(self, tuple(parameters)) + alias = typing._GenericAlias(self, tuple(parameters)) alias.__value__ = self.__value__ alias.__type_params__ = self.__type_params__ - if not hasattr(alias, '__name__'): # < 3.11 - alias.__name__ = self.__name__ + alias.__name__ = self.__name__ return alias def __reduce__(self): From 889e9ae88ea87a6b4b9317ea314bbe540dc4123a Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 25 Sep 2024 19:24:58 +0200 Subject: [PATCH 29/47] Assure that args and parameter tests pass --- src/test_typing_extensions.py | 12 ++++++------ src/typing_extensions.py | 11 +++++------ 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 410dff9d..1df5dba9 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -7236,14 +7236,14 @@ def test_alias_types_and_substitutions(self): TypeAliasType("NestedAlias", List[T], type_params=(T,))[T2] : (T2,), } # currently a limitation, these args are no longer unpacked in 3.11 - # also valid on 310 if GenericAlias is used + # OK if GenericAlias in __getitem__ is used test_argument_cases_310_plus = { + Unpack[Ts] : (Ts,), Unpack[Tuple[int, T2]] : (T2,), Concatenate[int, P] : (P,), - Unpack[Ts] : (Ts,), } test_argument_cases_311_plus = { - Ts : (Ts,), + Ts : (Ts,), # invalid case } test_argument_cases.update(test_argument_cases_310_plus) test_argument_cases.update(test_argument_cases_311_plus) @@ -7413,8 +7413,7 @@ def test_callable_with_concatenate(self): self.skipTest("Nested list is invalid type form") self.assertEqual(concat_usage, callable_concat[[str]]) - @skipUnless(TYPING_3_11_0, "__args__ behaves differently") - def test_substitution_311_plus(self): + def test_substitution(self): # To pass these tests alias.__args__ in TypeAliasType.__getitem__ needs adjustment # Unpack and Concatenate are unpacked in versions before T = TypeVar('T') @@ -7428,6 +7427,7 @@ def test_substitution_311_plus(self): CallableP = TypeAliasType("CallableP", Callable[P, T], type_params=(P, T)) callable_concat = CallableP[Concatenate[int, P], Any] self.assertEqual(get_args(callable_concat), (Concatenate[int, P], Any)) + self.assertEqual(callable_concat.__parameters__, (P,)) @skipUnless(TYPING_3_12_0, "__args__ behaves differently") def test_substitution_312_plus(self): @@ -7508,7 +7508,7 @@ def test_list_argument(self): invalid_tupleT[str] # The condition should align with the version of GeneriAlias usage in __getitem__ - @skipIf(TYPING_3_9_0, "Most cases are allowed in 3.11+ or with GenericAlias") + @skipIf(TYPING_3_10_0, "Most cases are allowed in 3.11+ or with GenericAlias") def test_invalid_cases_before_3_11(self): T = TypeVar('T') ListOrSetT = TypeAliasType("ListOrSetT", Union[List[T], Set[T]], type_params=(T,)) diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 8200fde3..7b88ccd9 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -3529,13 +3529,9 @@ def __repr__(self) -> str: if sys.version_info < (3, 11): def _check_single_param(self, param, recursion=0): # Allow [], [int], [int, str], [int, ...], [int, T] - if isinstance(param, (_UnpackAlias, _ConcatenateGenericAlias)): - # Unpack - yield from [checked - for arg in param.__args__ - for checked in self._check_single_param(arg, recursion+1)] - elif param is ...: + if param is ...: yield ... + # Note in < 3.9 _ConcatenateGenericAlias inherits from list elif isinstance(param, list) and recursion == 0: yield [checked for arg in param @@ -3563,8 +3559,11 @@ def __getitem__(self, parameters): parameters = (parameters,) if sys.version_info >= (3, 10): return _types.GenericAlias(self, tuple(parameters)) + type_vars = _collect_type_vars(parameters) parameters = self._check_parameters(parameters) alias = typing._GenericAlias(self, tuple(parameters)) + if len(alias.__parameters__) < len(type_vars): + alias.__parameters__ = tuple(type_vars) alias.__value__ = self.__value__ alias.__type_params__ = self.__type_params__ alias.__name__ = self.__name__ From b6b5a14bc47dbdcebffbaca4b9542c62d84e5e39 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 26 Sep 2024 11:06:56 +0200 Subject: [PATCH 30/47] Remove code to fix dunder attributes -> other PR --- src/test_typing_extensions.py | 37 +++-------------------------------- src/typing_extensions.py | 3 --- 2 files changed, 3 insertions(+), 37 deletions(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 1df5dba9..f48f29e2 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -7180,40 +7180,6 @@ def test_attributes(self): self.assertEqual(CallableP.__type_params__, (P,)) self.assertEqual(CallableP.__parameters__, (P,)) - def test_attributes_from_origin(self): - T = TypeVar('T') - ListOrSetT = TypeAliasType("ListOrSetT", Union[List[T], Set[T]], type_params=(T,)) - subscripted = ListOrSetT[int] - self.assertIs(get_origin(subscripted), ListOrSetT) - self.assertEqual(subscripted.__name__, "ListOrSetT") - self.assertEqual(subscripted.__value__, Union[List[T], Set[T]],) - self.assertEqual(subscripted.__type_params__, (T, )) - - still_generic = ListOrSetT[Iterable[T]] - self.assertIs(get_origin(still_generic), ListOrSetT) - fully_subscripted = still_generic[float] - self.assertIs(get_origin(fully_subscripted), ListOrSetT) - # __name__ needs Python 3.10+ - # __value__ and __type_params__ need Python 3.12+ - # Further tests are below - - @skipUnless(TYPING_3_10_0, "__name__ not added to GenericAlias") - def test_attributes_from_origin_3_10_plus(self): - T = TypeVar('T') - ListOrSetT = TypeAliasType("ListOrSetT", Union[List[T], Set[T]], type_params=(T,)) - fully_subscripted = ListOrSetT[Iterable[T]][float] - self.assertEqual(fully_subscripted.__name__, "ListOrSetT") - # __value__ and __type_params__ need Python 3.12+ - - @skipUnless(TYPING_3_12_0, "attributes not added to GenericAlias") - def test_attributes_from_origin_3_12_plus(self): - T = TypeVar('T') - ListOrSetT = TypeAliasType("ListOrSetT", Union[List[T], Set[T]], type_params=(T,)) - fully_subscripted = ListOrSetT[Iterable[T]][float] - self.assertEqual(fully_subscripted.__name__, "ListOrSetT") - self.assertEqual(fully_subscripted.__value__, Union[List[T], Set[T]],) - self.assertEqual(fully_subscripted.__type_params__, (T, )) - def test_alias_types_and_substitutions(self): T = TypeVar('T') T2 = TypeVar('T2') @@ -7359,13 +7325,16 @@ def test_getitem(self): ListOrSetT = TypeAliasType("ListOrSetT", Union[List[T], Set[T]], type_params=(T,)) subscripted = ListOrSetT[int] self.assertEqual(get_args(subscripted), (int,)) + self.assertIs(get_origin(subscripted), ListOrSetT) with self.assertRaises(TypeError, msg="not a generic class"): subscripted[int] still_generic = ListOrSetT[Iterable[T]] self.assertEqual(get_args(still_generic), (Iterable[T],)) + self.assertIs(get_origin(still_generic), ListOrSetT) fully_subscripted = still_generic[float] self.assertEqual(get_args(fully_subscripted), (Iterable[float],)) + self.assertIs(get_origin(fully_subscripted), ListOrSetT) def test_callable_without_concatenate(self): P = ParamSpec('P') diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 7b88ccd9..3af921d2 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -3564,9 +3564,6 @@ def __getitem__(self, parameters): alias = typing._GenericAlias(self, tuple(parameters)) if len(alias.__parameters__) < len(type_vars): alias.__parameters__ = tuple(type_vars) - alias.__value__ = self.__value__ - alias.__type_params__ = self.__type_params__ - alias.__name__ = self.__name__ return alias def __reduce__(self): From 956263569426f0c0969caa75d2081a8b97bda6d4 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 26 Sep 2024 11:07:14 +0200 Subject: [PATCH 31/47] small reordering --- src/test_typing_extensions.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index f48f29e2..ff52406b 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -7318,10 +7318,6 @@ def test_or(self): def test_getitem(self): T = TypeVar('T') - ValueWithoutT = TypeAliasType("ValueWithoutT", int, type_params=(T,)) - still_subscripted = ValueWithoutT[str] - self.assertEqual(get_args(still_subscripted), (str,)) - ListOrSetT = TypeAliasType("ListOrSetT", Union[List[T], Set[T]], type_params=(T,)) subscripted = ListOrSetT[int] self.assertEqual(get_args(subscripted), (int,)) @@ -7336,6 +7332,10 @@ def test_getitem(self): self.assertEqual(get_args(fully_subscripted), (Iterable[float],)) self.assertIs(get_origin(fully_subscripted), ListOrSetT) + ValueWithoutTypeVar = TypeAliasType("ValueWithoutTypeParam", int, type_params=(T,)) + still_subscripted = ValueWithoutTypeVar[str] + self.assertEqual(get_args(still_subscripted), (str,)) + def test_callable_without_concatenate(self): P = ParamSpec('P') CallableP = TypeAliasType("CallableP", Callable[P, Any], type_params=(P,)) @@ -7396,7 +7396,6 @@ def test_substitution(self): CallableP = TypeAliasType("CallableP", Callable[P, T], type_params=(P, T)) callable_concat = CallableP[Concatenate[int, P], Any] self.assertEqual(get_args(callable_concat), (Concatenate[int, P], Any)) - self.assertEqual(callable_concat.__parameters__, (P,)) @skipUnless(TYPING_3_12_0, "__args__ behaves differently") def test_substitution_312_plus(self): From 014109c3f7a94ba277d2c014f290d573831a2d14 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 26 Sep 2024 19:48:31 +0200 Subject: [PATCH 32/47] Remove invalid case --- src/test_typing_extensions.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 6cea1ce7..f0189802 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -7218,9 +7218,8 @@ def test_alias_types_and_substitutions(self): # Simple cases TypeAliasType("ListT", List[T], type_params=(T,)), TypeAliasType("UnionT", Union[int, List[T]], type_params=(T,)), - # Either value or type_params contain generic + # Value has no parameter but in type_param TypeAliasType("ValueWithoutT", int, type_params=(T,)), - TypeAliasType("ValueTNoParams", List[T], type_params=()), # Callable TypeAliasType("CallableP", Callable[P, Any], type_params=(P, )), TypeAliasType("CallableT", Callable[..., T], type_params=(T, )), From 0b3ce7d336570b06ea62c5ccea22a91b5c736f41 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 26 Sep 2024 20:17:10 +0200 Subject: [PATCH 33/47] Updated to latest changes from main --- src/typing_extensions.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/typing_extensions.py b/src/typing_extensions.py index dfae8d60..cdee274e 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -3593,9 +3593,10 @@ def __getitem__(self, parameters): # Using 3.9 here will create problems with Concatenate if sys.version_info >= (3, 10): return _types.GenericAlias(self, parameters) - parameters = tuple(self._check_parameters(parameters)) - return typing._GenericAlias(self, tuple(parameters)) type_vars = _collect_type_vars(parameters) + parameters = self._check_parameters(parameters) + alias = _TypeAliasGenericAlias(self, parameters) + # If Concatenate is present its parameters were not collected if len(alias.__parameters__) < len(type_vars): alias.__parameters__ = tuple(type_vars) return alias From 0ae4c6347748d3ee7bc289c62269128874d30fcd Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 26 Sep 2024 20:30:20 +0200 Subject: [PATCH 34/47] revert mistakes of wrong merge --- src/test_typing_extensions.py | 16 ++++++++++++++++ src/typing_extensions.py | 5 ++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index f0189802..f842f381 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -7484,6 +7484,22 @@ def test_invalid_cases_before_3_11(self): with self.assertRaises(TypeError): ListOrSetT[(Generic[T], )] + def test_unpack_parameter_collection(self): + Ts = TypeVarTuple("Ts") + + class Foo(Generic[Unpack[Ts]]): + bar: Tuple[Unpack[Ts]] + + FooAlias = TypeAliasType("FooAlias", Foo[Unpack[Ts]], type_params=(Ts,)) + self.assertEqual(FooAlias[Unpack[Tuple[str]]].__parameters__, ()) + self.assertEqual(FooAlias[Unpack[Tuple[T]]].__parameters__, (T,)) + + P = ParamSpec("P") + CallableP = TypeAliasType("CallableP", Callable[P, Any], type_params=(P,)) + call_int_T = CallableP[Unpack[Tuple[int, T]]] + self.assertEqual(call_int_T.__parameters__, (T,)) + + def test_alias_attributes(self): T = TypeVar('T') T2 = TypeVar('T2') diff --git a/src/typing_extensions.py b/src/typing_extensions.py index cdee274e..fe8915ef 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -3068,7 +3068,10 @@ def _collect_type_vars(types, typevar_types=None): for t in types: if _is_unpacked_typevartuple(t): type_var_tuple_encountered = True - elif isinstance(t, typevar_types) and t not in tvars: + elif ( + isinstance(t, typevar_types) and not isinstance(t, _UnpackAlias) + and t not in tvars + ): if enforce_default_ordering: has_default = getattr(t, '__default__', NoDefault) is not NoDefault if has_default: From e613294a1bf5c995144c5819687b2630e0386b4d Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 26 Sep 2024 20:34:11 +0200 Subject: [PATCH 35/47] No need to skip tests anymore --- src/test_typing_extensions.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index f842f381..67371080 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -7200,10 +7200,6 @@ def test_alias_types_and_substitutions(self): Callable[P, T2] : (P, T2), Callable[Concatenate[T2, P], T_default] : (T2, P, T_default), TypeAliasType("NestedAlias", List[T], type_params=(T,))[T2] : (T2,), - } - # currently a limitation, these args are no longer unpacked in 3.11 - # OK if GenericAlias in __getitem__ is used - test_argument_cases_310_plus = { Unpack[Ts] : (Ts,), Unpack[Tuple[int, T2]] : (T2,), Concatenate[int, P] : (P,), @@ -7211,7 +7207,6 @@ def test_alias_types_and_substitutions(self): test_argument_cases_311_plus = { Ts : (Ts,), # invalid case } - test_argument_cases.update(test_argument_cases_310_plus) test_argument_cases.update(test_argument_cases_311_plus) test_alias_cases = [ @@ -7249,8 +7244,6 @@ def test_alias_types_and_substitutions(self): self.assertEqual(subscripted.__parameters__, ()) for expected_args, expected_parameters in test_argument_cases.items(): with self.subTest(alias=alias, args=expected_args): - if expected_args in test_argument_cases_310_plus and sys.version_info < (3, 10): - self.skipTest("args are unpacked before 3.11 or need GenericAlias") if expected_args in test_argument_cases_311_plus and sys.version_info < (3, 11): self.skipTest("Case is not valid before 3.11") self.assertEqual(get_args(alias[expected_args]), (expected_args,)) From 6bc1f57141e605e6f6caa21f2417bb3b7a9174f2 Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 30 Sep 2024 15:58:00 +0200 Subject: [PATCH 36/47] removed tests related to: https://github.com/python/cpython/issues/124445 --- src/test_typing_extensions.py | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 17dadd16..18c08d61 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -7439,34 +7439,6 @@ def test_wrong_amount_of_parameters(self): self.assertEqual(get_args(alias), expected_args) self.assertEqual(alias.__parameters__, expected_params) - def test_list_argument(self): - # NOTE: These cases could be seen as valid but result in a parameterless - # variable. If these tests fail the specificiation might have changed - - # Callable - P = ParamSpec('P') - CallableP = TypeAliasType("CallableP", Callable[P, Any], type_params=(P,)) - CallableT_list = CallableP[[T]] - self.assertEqual(get_args(CallableT_list), ([T],)) - self.assertEqual(CallableT_list.__parameters__, ()) - with self.assertRaises(TypeError, msg="is not a generic class"): - CallableT_list[str] - - ImplicitConcatP = CallableP[[int, P]] - self.assertEqual(get_args(ImplicitConcatP), ([int, P],)) - self.assertEqual(ImplicitConcatP.__parameters__, ()) - with self.assertRaises(TypeError, msg="is not a generic class"): - ImplicitConcatP[str] - - # TypeVarTuple - Ts = TypeVarTuple("Ts") - Variadic = TypeAliasType("Variadic", Tuple[int, Unpack[Ts]], type_params=(Ts,)) - invalid_tupleT = Variadic[[int, T]] - self.assertEqual(get_args(invalid_tupleT), ([int, T],)) - self.assertEqual(invalid_tupleT.__parameters__, ()) - with self.assertRaises(TypeError, msg="is not a generic class"): - invalid_tupleT[str] - # The condition should align with the version of GeneriAlias usage in __getitem__ @skipIf(TYPING_3_10_0, "Most cases are allowed in 3.11+ or with GenericAlias") def test_invalid_cases_before_3_11(self): From e8bfa30a5d8059941ecd64e38ac2e0de449049bd Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 30 Sep 2024 16:13:17 +0200 Subject: [PATCH 37/47] Removed tests related to #474 --- src/test_typing_extensions.py | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 18c08d61..02902e3a 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -7389,25 +7389,6 @@ def test_substitution(self): callable_concat = CallableP[Concatenate[int, P], Any] self.assertEqual(get_args(callable_concat), (Concatenate[int, P], Any)) - @skipUnless(TYPING_3_12_0, "__args__ behaves differently") - def test_substitution_312_plus(self): - # To pass these tests alias.__args__ in TypeAliasType.__getitem__ needs adjustment - # Would raise: TypeError: Substitution of bare TypeVarTuple is not supported - T = TypeVar('T') - Ts = TypeVarTuple("Ts") - Variadic = TypeAliasType("Variadic", Tuple[int, Unpack[Ts]], type_params=(Ts,)) - - subcriped_callable_tvt = Variadic[Callable[[Unpack[Ts]], T]] - variadic_tvt_callableA = subcriped_callable_tvt[str, object] - variadic_tvt_callableA2 = subcriped_callable_tvt[Unpack[Tuple[str]], object] - self.assertEqual(variadic_tvt_callableA, variadic_tvt_callableA2) - - variadic_tvt_callableB = subcriped_callable_tvt[[str, int], object] - variadic_tvt_callableB2 = subcriped_callable_tvt[Unpack[Tuple[str, int]], object] - variadic_tvt_callableB3 = subcriped_callable_tvt[str, int, object] - self.assertNotEqual(variadic_tvt_callableB, variadic_tvt_callableB2) - self.assertEqual(variadic_tvt_callableB2, variadic_tvt_callableB3) - def test_wrong_amount_of_parameters(self): T = TypeVar('T') T2 = TypeVar("T2") From 617656de7a2bc9825471c99cd00c38aa39317275 Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 30 Sep 2024 16:22:29 +0200 Subject: [PATCH 38/47] Removed invalid tests Related to: python/cpython#124787 --- 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 02902e3a..80c88a56 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -7224,9 +7224,6 @@ def test_alias_types_and_substitutions(self): # TypeVar with default TypeAliasType("TupleT_default", Tuple[T_default, T], type_params=(T, T_default)), TypeAliasType("CallableT_default", Callable[[T], T_default], type_params=(T, T_default)), - # default order reversed - TypeAliasType("TupleT_default_reversed", Tuple[T_default, T], type_params=(T_default, T)), - TypeAliasType("CallableT_default_reversed", Callable[[T], T_default], type_params=(T_default, T)), ] for alias in test_alias_cases: From 41a87b82e9fb992d0ac59560b3248583cfd3becb Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 30 Sep 2024 16:40:54 +0200 Subject: [PATCH 39/47] minor comment update --- src/test_typing_extensions.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 80c88a56..847fbc17 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -7372,8 +7372,6 @@ def test_callable_with_concatenate(self): self.assertEqual(concat_usage, callable_concat[[str]]) def test_substitution(self): - # To pass these tests alias.__args__ in TypeAliasType.__getitem__ needs adjustment - # Unpack and Concatenate are unpacked in versions before T = TypeVar('T') Ts = TypeVarTuple("Ts") @@ -7418,7 +7416,7 @@ def test_wrong_amount_of_parameters(self): self.assertEqual(alias.__parameters__, expected_params) # The condition should align with the version of GeneriAlias usage in __getitem__ - @skipIf(TYPING_3_10_0, "Most cases are allowed in 3.11+ or with GenericAlias") + @skipIf(TYPING_3_10_0, "Most arguments are allowed in 3.11+ or with GenericAlias") def test_invalid_cases_before_3_11(self): T = TypeVar('T') ListOrSetT = TypeAliasType("ListOrSetT", Union[List[T], Set[T]], type_params=(T,)) From 249b86942fad73c3eb1d8536a0bfc9b6ecfdd375 Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 30 Sep 2024 17:00:51 +0200 Subject: [PATCH 40/47] More refined skip reason --- src/test_typing_extensions.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 03a1f45f..11c5e5fe 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -7357,12 +7357,16 @@ def test_callable_without_concatenate(self): def test_callable_with_concatenate(self): P = ParamSpec('P') + P2 = ParamSpec('P2') CallableP = TypeAliasType("CallableP", Callable[P, Any], type_params=(P,)) - callable_concat = CallableP[Concatenate[int, P]] - self.assertEqual(callable_concat.__parameters__, (P,)) + callable_concat = CallableP[Concatenate[int, P2]] + self.assertEqual(callable_concat.__parameters__, (P2,)) concat_usage = callable_concat[str] with self.subTest("get_args of Concatenate in TypeAliasType"): + if not TYPING_3_9_0: + # args are: ([, ~P2],) + self.skipTest("Nested ParamSpec is not substituted") if sys.version_info < (3, 10, 2): self.skipTest("GenericAlias keeps Concatenate in __args__ prior to 3.10.2") self.assertEqual(get_args(concat_usage), ((int, str),)) From e71902e00eb8c0d15cdfa883f55655e74ac8bd65 Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 1 Oct 2024 14:20:43 +0200 Subject: [PATCH 41/47] Remove type check for tuples; handled by #477 --- src/typing_extensions.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 5ce7163f..3b7a23dc 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -3535,8 +3535,6 @@ def __init__(self, name: str, value, *, type_params=()): self.__type_params__ = type_params parameters = [] - if not isinstance(type_params, tuple): - raise TypeError("type_params must be a tuple") for type_param in type_params: if isinstance(type_param, TypeVarTuple): parameters.extend(type_param) From b8799ce06365f839716e8c03b78e0a455e9adb0b Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 1 Oct 2024 15:09:59 +0200 Subject: [PATCH 42/47] updated changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index db6719c6..6b2ea64f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,9 @@ subscripted objects) had wrong parameters if they were directly subscripted with an `Unpack` object. Patch by [Daraan](https://github.com/Daraan). +- Fix that lists and ... could not be used for parameter expressions for `TypeAliasType` + instances before Python 3.11. + Patch by [Daraan](https://github.com/Daraan). # Release 4.12.2 (June 7, 2024) From 5bc136023ac67f2c8528c4374a40b14f74edb05d Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 1 Oct 2024 16:00:22 +0200 Subject: [PATCH 43/47] 3.8, 3.9 use collected parameters more explicitly minor comment corrections --- src/test_typing_extensions.py | 12 ++++-------- src/typing_extensions.py | 9 +++++---- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 11c5e5fe..01a7039e 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -7203,11 +7203,9 @@ def test_alias_types_and_substitutions(self): Unpack[Ts] : (Ts,), Unpack[Tuple[int, T2]] : (T2,), Concatenate[int, P] : (P,), + # Not tested usage of bare TypeVarTuple, would need 3.11+ + # Ts : (Ts,), # invalid case } - test_argument_cases_311_plus = { - Ts : (Ts,), # invalid case - } - test_argument_cases.update(test_argument_cases_311_plus) test_alias_cases = [ # Simple cases @@ -7241,8 +7239,6 @@ def test_alias_types_and_substitutions(self): self.assertEqual(subscripted.__parameters__, ()) for expected_args, expected_parameters in test_argument_cases.items(): with self.subTest(alias=alias, args=expected_args): - if expected_args in test_argument_cases_311_plus and sys.version_info < (3, 11): - self.skipTest("Case is not valid before 3.11") self.assertEqual(get_args(alias[expected_args]), (expected_args,)) self.assertEqual(alias[expected_args].__parameters__, expected_parameters) @@ -7419,9 +7415,9 @@ def test_wrong_amount_of_parameters(self): self.assertEqual(get_args(alias), expected_args) self.assertEqual(alias.__parameters__, expected_params) - # The condition should align with the version of GeneriAlias usage in __getitem__ + # The condition should align with the version of GeneriAlias usage in __getitem__ or be 3.11+ @skipIf(TYPING_3_10_0, "Most arguments are allowed in 3.11+ or with GenericAlias") - def test_invalid_cases_before_3_11(self): + def test_invalid_cases_before_3_10(self): T = TypeVar('T') ListOrSetT = TypeAliasType("ListOrSetT", Union[List[T], Set[T]], type_params=(T,)) with self.assertRaises(TypeError): diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 3b7a23dc..2223b424 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -3577,7 +3577,7 @@ def _check_single_param(self, param, recursion=0): # Allow [], [int], [int, str], [int, ...], [int, T] if param is ...: yield ... - # Note in < 3.9 _ConcatenateGenericAlias inherits from list + # Note in <= 3.9 _ConcatenateGenericAlias inherits from list elif isinstance(param, list) and recursion == 0: yield [checked for arg in param @@ -3611,9 +3611,10 @@ def __getitem__(self, parameters): type_vars = _collect_type_vars(parameters) parameters = self._check_parameters(parameters) alias = _TypeAliasGenericAlias(self, parameters) - # If Concatenate is present its parameters were not collected - if len(alias.__parameters__) < len(type_vars): - alias.__parameters__ = tuple(type_vars) + # alias.__parameters__ is not complete if Concatenate is present + # as it is converted to a list from which no parameters are extracted. + if alias.__parameters__ != type_vars: + alias.__parameters__ = type_vars return alias def __reduce__(self): From 3ae9e35a56dff654489dcc904240af320e69c0d4 Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 2 Oct 2024 12:36:40 +0200 Subject: [PATCH 44/47] Correct error message for 3.10 --- src/test_typing_extensions.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 01a7039e..a16ddc3a 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -7307,9 +7307,15 @@ def test_getitem(self): subscripted = ListOrSetT[int] self.assertEqual(get_args(subscripted), (int,)) self.assertIs(get_origin(subscripted), ListOrSetT) - with self.assertRaises(TypeError, msg="not a generic class"): + with self.assertRaisesRegex(TypeError, + "not a generic class" + # types.GenericAlias raises a different error in 3.10 + if sys.version_info[:2] != (3, 10) + else "There are no type variables left in ListOrSetT" + ): subscripted[int] + still_generic = ListOrSetT[Iterable[T]] self.assertEqual(get_args(still_generic), (Iterable[T],)) self.assertIs(get_origin(still_generic), ListOrSetT) From b3aa5987ba775838f0c0211fefe3c42ba26c0260 Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 22 Oct 2024 15:36:03 +0200 Subject: [PATCH 45/47] Generator not necessary anymore --- src/typing_extensions.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 4b3e6386..65bd4074 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -3647,23 +3647,20 @@ def __repr__(self) -> str: def _check_single_param(self, param, recursion=0): # Allow [], [int], [int, str], [int, ...], [int, T] if param is ...: - yield ... + return ... # Note in <= 3.9 _ConcatenateGenericAlias inherits from list - elif isinstance(param, list) and recursion == 0: - yield [checked - for arg in param - for checked in self._check_single_param(arg, recursion+1)] - else: - yield typing._type_check( + if isinstance(param, list) and recursion == 0: + return [self._check_single_param(arg, recursion+1) + for arg in param] + return typing._type_check( param, f'Subscripting {self.__name__} requires a type.' ) def _check_parameters(self, parameters): if sys.version_info < (3, 11): return tuple( - checked + self._check_single_param(item) for item in parameters - for checked in self._check_single_param(item) ) return tuple(typing._type_check( item, f'Subscripting {self.__name__} requires a type.' From fb55b88a863535c68a014602cc4da7c256931ee0 Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 25 Oct 2024 18:00:20 +0200 Subject: [PATCH 46/47] TODO: handle None --- src/test_typing_extensions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 383d75a7..4fc4e12d 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -7285,6 +7285,7 @@ def test_alias_types_and_substitutions(self): # arguments : expected parameters int : (), ... : (), + None : (), T2 : (T2,), Union[int, List[T2]] : (T2,), Tuple[int, str] : (), From eb840ef3f325716a78280d5a8ebf0701de7ebc17 Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 28 Oct 2024 11:53:06 +0100 Subject: [PATCH 47/47] Handle None case correctly, also check list --- src/test_typing_extensions.py | 4 ++++ src/typing_extensions.py | 2 ++ 2 files changed, 6 insertions(+) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index f60804bd..a7e6885e 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -7332,6 +7332,10 @@ def test_alias_types_and_substitutions(self): subscripted = alias[int, float] self.assertEqual(get_args(subscripted), (int, float)) self.assertEqual(subscripted.__parameters__, ()) + with self.subTest(alias=alias, args=[int, float]): + subscripted = alias[[int, float]] + self.assertEqual(get_args(subscripted), ([int, float],)) + self.assertEqual(subscripted.__parameters__, ()) for expected_args, expected_parameters in test_argument_cases.items(): with self.subTest(alias=alias, args=expected_args): self.assertEqual(get_args(alias[expected_args]), (expected_args,)) diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 85e8fe07..dc35b3d4 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -3667,6 +3667,8 @@ def _check_single_param(self, param, recursion=0): # Allow [], [int], [int, str], [int, ...], [int, T] if param is ...: return ... + if param is None: + return None # Note in <= 3.9 _ConcatenateGenericAlias inherits from list if isinstance(param, list) and recursion == 0: return [self._check_single_param(arg, recursion+1)