Skip to content

Commit 1f84c68

Browse files
authored
Merge branch 'main' into get_type_hints
2 parents 1eac721 + f2d0667 commit 1f84c68

File tree

3 files changed

+229
-9
lines changed

3 files changed

+229
-9
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ aliases that have a `Concatenate` special form as their argument.
3030
- Backport CPython PR [#124795](https://github.com/python/cpython/pull/124795):
3131
fix `TypeAliasType` not raising an error on non-tuple inputs for `type_params`.
3232
Patch by [Daraan](https://github.com/Daraan).
33+
- Fix that lists and ... could not be used for parameter expressions for `TypeAliasType`
34+
instances before Python 3.11.
35+
Patch by [Daraan](https://github.com/Daraan).
3336

3437
# Release 4.12.2 (June 7, 2024)
3538

src/test_typing_extensions.py

Lines changed: 191 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7356,6 +7356,80 @@ def test_attributes(self):
73567356
self.assertEqual(Variadic.__type_params__, (Ts,))
73577357
self.assertEqual(Variadic.__parameters__, tuple(iter(Ts)))
73587358

7359+
P = ParamSpec('P')
7360+
CallableP = TypeAliasType("CallableP", Callable[P, Any], type_params=(P, ))
7361+
self.assertEqual(CallableP.__name__, "CallableP")
7362+
self.assertEqual(CallableP.__value__, Callable[P, Any])
7363+
self.assertEqual(CallableP.__type_params__, (P,))
7364+
self.assertEqual(CallableP.__parameters__, (P,))
7365+
7366+
def test_alias_types_and_substitutions(self):
7367+
T = TypeVar('T')
7368+
T2 = TypeVar('T2')
7369+
T_default = TypeVar("T_default", default=int)
7370+
Ts = TypeVarTuple("Ts")
7371+
P = ParamSpec('P')
7372+
7373+
test_argument_cases = {
7374+
# arguments : expected parameters
7375+
int : (),
7376+
... : (),
7377+
None : (),
7378+
T2 : (T2,),
7379+
Union[int, List[T2]] : (T2,),
7380+
Tuple[int, str] : (),
7381+
Tuple[T, T_default, T2] : (T, T_default, T2),
7382+
Tuple[Unpack[Ts]] : (Ts,),
7383+
Callable[[Unpack[Ts]], T2] : (Ts, T2),
7384+
Callable[P, T2] : (P, T2),
7385+
Callable[Concatenate[T2, P], T_default] : (T2, P, T_default),
7386+
TypeAliasType("NestedAlias", List[T], type_params=(T,))[T2] : (T2,),
7387+
Unpack[Ts] : (Ts,),
7388+
Unpack[Tuple[int, T2]] : (T2,),
7389+
Concatenate[int, P] : (P,),
7390+
# Not tested usage of bare TypeVarTuple, would need 3.11+
7391+
# Ts : (Ts,), # invalid case
7392+
}
7393+
7394+
test_alias_cases = [
7395+
# Simple cases
7396+
TypeAliasType("ListT", List[T], type_params=(T,)),
7397+
TypeAliasType("UnionT", Union[int, List[T]], type_params=(T,)),
7398+
# Value has no parameter but in type_param
7399+
TypeAliasType("ValueWithoutT", int, type_params=(T,)),
7400+
# Callable
7401+
TypeAliasType("CallableP", Callable[P, Any], type_params=(P, )),
7402+
TypeAliasType("CallableT", Callable[..., T], type_params=(T, )),
7403+
TypeAliasType("CallableTs", Callable[[Unpack[Ts]], Any], type_params=(Ts, )),
7404+
# TypeVarTuple
7405+
TypeAliasType("Variadic", Tuple[int, Unpack[Ts]], type_params=(Ts,)),
7406+
# TypeVar with default
7407+
TypeAliasType("TupleT_default", Tuple[T_default, T], type_params=(T, T_default)),
7408+
TypeAliasType("CallableT_default", Callable[[T], T_default], type_params=(T, T_default)),
7409+
]
7410+
7411+
for alias in test_alias_cases:
7412+
with self.subTest(alias=alias, args=[]):
7413+
subscripted = alias[[]]
7414+
self.assertEqual(get_args(subscripted), ([],))
7415+
self.assertEqual(subscripted.__parameters__, ())
7416+
with self.subTest(alias=alias, args=()):
7417+
subscripted = alias[()]
7418+
self.assertEqual(get_args(subscripted), ())
7419+
self.assertEqual(subscripted.__parameters__, ())
7420+
with self.subTest(alias=alias, args=(int, float)):
7421+
subscripted = alias[int, float]
7422+
self.assertEqual(get_args(subscripted), (int, float))
7423+
self.assertEqual(subscripted.__parameters__, ())
7424+
with self.subTest(alias=alias, args=[int, float]):
7425+
subscripted = alias[[int, float]]
7426+
self.assertEqual(get_args(subscripted), ([int, float],))
7427+
self.assertEqual(subscripted.__parameters__, ())
7428+
for expected_args, expected_parameters in test_argument_cases.items():
7429+
with self.subTest(alias=alias, args=expected_args):
7430+
self.assertEqual(get_args(alias[expected_args]), (expected_args,))
7431+
self.assertEqual(alias[expected_args].__parameters__, expected_parameters)
7432+
73597433
def test_cannot_set_attributes(self):
73607434
Simple = TypeAliasType("Simple", int)
73617435
with self.assertRaisesRegex(AttributeError, "readonly attribute"):
@@ -7416,12 +7490,19 @@ def test_or(self):
74167490
Alias | "Ref"
74177491

74187492
def test_getitem(self):
7493+
T = TypeVar('T')
74197494
ListOrSetT = TypeAliasType("ListOrSetT", Union[List[T], Set[T]], type_params=(T,))
74207495
subscripted = ListOrSetT[int]
74217496
self.assertEqual(get_args(subscripted), (int,))
74227497
self.assertIs(get_origin(subscripted), ListOrSetT)
7423-
with self.assertRaises(TypeError):
7424-
subscripted[str]
7498+
with self.assertRaisesRegex(TypeError,
7499+
"not a generic class"
7500+
# types.GenericAlias raises a different error in 3.10
7501+
if sys.version_info[:2] != (3, 10)
7502+
else "There are no type variables left in ListOrSetT"
7503+
):
7504+
subscripted[int]
7505+
74257506

74267507
still_generic = ListOrSetT[Iterable[T]]
74277508
self.assertEqual(get_args(still_generic), (Iterable[T],))
@@ -7430,6 +7511,114 @@ def test_getitem(self):
74307511
self.assertEqual(get_args(fully_subscripted), (Iterable[float],))
74317512
self.assertIs(get_origin(fully_subscripted), ListOrSetT)
74327513

7514+
ValueWithoutTypeVar = TypeAliasType("ValueWithoutTypeVar", int, type_params=(T,))
7515+
still_subscripted = ValueWithoutTypeVar[str]
7516+
self.assertEqual(get_args(still_subscripted), (str,))
7517+
7518+
def test_callable_without_concatenate(self):
7519+
P = ParamSpec('P')
7520+
CallableP = TypeAliasType("CallableP", Callable[P, Any], type_params=(P,))
7521+
get_args_test_cases = [
7522+
# List of (alias, expected_args)
7523+
# () -> Any
7524+
(CallableP[()], ()),
7525+
(CallableP[[]], ([],)),
7526+
# (int) -> Any
7527+
(CallableP[int], (int,)),
7528+
(CallableP[[int]], ([int],)),
7529+
# (int, int) -> Any
7530+
(CallableP[int, int], (int, int)),
7531+
(CallableP[[int, int]], ([int, int],)),
7532+
# (...) -> Any
7533+
(CallableP[...], (...,)),
7534+
# (int, ...) -> Any
7535+
(CallableP[[int, ...]], ([int, ...],)),
7536+
]
7537+
7538+
for index, (expression, expected_args) in enumerate(get_args_test_cases):
7539+
with self.subTest(index=index, expression=expression):
7540+
self.assertEqual(get_args(expression), expected_args)
7541+
7542+
self.assertEqual(CallableP[...], CallableP[(...,)])
7543+
# (T) -> Any
7544+
CallableT = CallableP[T]
7545+
self.assertEqual(get_args(CallableT), (T,))
7546+
self.assertEqual(CallableT.__parameters__, (T,))
7547+
7548+
def test_callable_with_concatenate(self):
7549+
P = ParamSpec('P')
7550+
P2 = ParamSpec('P2')
7551+
CallableP = TypeAliasType("CallableP", Callable[P, Any], type_params=(P,))
7552+
7553+
callable_concat = CallableP[Concatenate[int, P2]]
7554+
self.assertEqual(callable_concat.__parameters__, (P2,))
7555+
concat_usage = callable_concat[str]
7556+
with self.subTest("get_args of Concatenate in TypeAliasType"):
7557+
if not TYPING_3_9_0:
7558+
# args are: ([<class 'int'>, ~P2],)
7559+
self.skipTest("Nested ParamSpec is not substituted")
7560+
if sys.version_info < (3, 10, 2):
7561+
self.skipTest("GenericAlias keeps Concatenate in __args__ prior to 3.10.2")
7562+
self.assertEqual(get_args(concat_usage), ((int, str),))
7563+
with self.subTest("Equality of parameter_expression without []"):
7564+
if not TYPING_3_10_0:
7565+
self.skipTest("Nested list is invalid type form")
7566+
self.assertEqual(concat_usage, callable_concat[[str]])
7567+
7568+
def test_substitution(self):
7569+
T = TypeVar('T')
7570+
Ts = TypeVarTuple("Ts")
7571+
7572+
CallableTs = TypeAliasType("CallableTs", Callable[[Unpack[Ts]], Any], type_params=(Ts, ))
7573+
unpack_callable = CallableTs[Unpack[Tuple[int, T]]]
7574+
self.assertEqual(get_args(unpack_callable), (Unpack[Tuple[int, T]],))
7575+
7576+
P = ParamSpec('P')
7577+
CallableP = TypeAliasType("CallableP", Callable[P, T], type_params=(P, T))
7578+
callable_concat = CallableP[Concatenate[int, P], Any]
7579+
self.assertEqual(get_args(callable_concat), (Concatenate[int, P], Any))
7580+
7581+
def test_wrong_amount_of_parameters(self):
7582+
T = TypeVar('T')
7583+
T2 = TypeVar("T2")
7584+
P = ParamSpec('P')
7585+
ListOrSetT = TypeAliasType("ListOrSetT", Union[List[T], Set[T]], type_params=(T,))
7586+
TwoT = TypeAliasType("TwoT", Union[List[T], Set[T2]], type_params=(T, T2))
7587+
CallablePT = TypeAliasType("CallablePT", Callable[P, T], type_params=(P, T))
7588+
7589+
# Not enough parameters
7590+
test_cases = [
7591+
# not_enough
7592+
(TwoT[int], [(int,), ()]),
7593+
(TwoT[T], [(T,), (T,)]),
7594+
# callable and not enough
7595+
(CallablePT[int], [(int,), ()]),
7596+
# too many
7597+
(ListOrSetT[int, bool], [(int, bool), ()]),
7598+
# callable and too many
7599+
(CallablePT[str, float, int], [(str, float, int), ()]),
7600+
# Check if TypeVar is still present even if over substituted
7601+
(ListOrSetT[int, T], [(int, T), (T,)]),
7602+
# With and without list for ParamSpec
7603+
(CallablePT[str, float, T], [(str, float, T), (T,)]),
7604+
(CallablePT[[str], float, int, T2], [([str], float, int, T2), (T2,)]),
7605+
]
7606+
7607+
for index, (alias, [expected_args, expected_params]) in enumerate(test_cases):
7608+
with self.subTest(index=index, alias=alias):
7609+
self.assertEqual(get_args(alias), expected_args)
7610+
self.assertEqual(alias.__parameters__, expected_params)
7611+
7612+
# The condition should align with the version of GeneriAlias usage in __getitem__ or be 3.11+
7613+
@skipIf(TYPING_3_10_0, "Most arguments are allowed in 3.11+ or with GenericAlias")
7614+
def test_invalid_cases_before_3_10(self):
7615+
T = TypeVar('T')
7616+
ListOrSetT = TypeAliasType("ListOrSetT", Union[List[T], Set[T]], type_params=(T,))
7617+
with self.assertRaises(TypeError):
7618+
ListOrSetT[Generic[T]]
7619+
with self.assertRaises(TypeError):
7620+
ListOrSetT[(Generic[T], )]
7621+
74337622
def test_unpack_parameter_collection(self):
74347623
Ts = TypeVarTuple("Ts")
74357624

src/typing_extensions.py

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3742,6 +3742,33 @@ def _raise_attribute_error(self, name: str) -> Never:
37423742
def __repr__(self) -> str:
37433743
return self.__name__
37443744

3745+
if sys.version_info < (3, 11):
3746+
def _check_single_param(self, param, recursion=0):
3747+
# Allow [], [int], [int, str], [int, ...], [int, T]
3748+
if param is ...:
3749+
return ...
3750+
if param is None:
3751+
return None
3752+
# Note in <= 3.9 _ConcatenateGenericAlias inherits from list
3753+
if isinstance(param, list) and recursion == 0:
3754+
return [self._check_single_param(arg, recursion+1)
3755+
for arg in param]
3756+
return typing._type_check(
3757+
param, f'Subscripting {self.__name__} requires a type.'
3758+
)
3759+
3760+
def _check_parameters(self, parameters):
3761+
if sys.version_info < (3, 11):
3762+
return tuple(
3763+
self._check_single_param(item)
3764+
for item in parameters
3765+
)
3766+
return tuple(typing._type_check(
3767+
item, f'Subscripting {self.__name__} requires a type.'
3768+
)
3769+
for item in parameters
3770+
)
3771+
37453772
def __getitem__(self, parameters):
37463773
if not self.__type_params__:
37473774
raise TypeError("Only generic type aliases are subscriptable")
@@ -3750,13 +3777,14 @@ def __getitem__(self, parameters):
37503777
# Using 3.9 here will create problems with Concatenate
37513778
if sys.version_info >= (3, 10):
37523779
return _types.GenericAlias(self, parameters)
3753-
parameters = tuple(
3754-
typing._type_check(
3755-
item, f'Subscripting {self.__name__} requires a type.'
3756-
)
3757-
for item in parameters
3758-
)
3759-
return _TypeAliasGenericAlias(self, parameters)
3780+
type_vars = _collect_type_vars(parameters)
3781+
parameters = self._check_parameters(parameters)
3782+
alias = _TypeAliasGenericAlias(self, parameters)
3783+
# alias.__parameters__ is not complete if Concatenate is present
3784+
# as it is converted to a list from which no parameters are extracted.
3785+
if alias.__parameters__ != type_vars:
3786+
alias.__parameters__ = type_vars
3787+
return alias
37603788

37613789
def __reduce__(self):
37623790
return self.__name__

0 commit comments

Comments
 (0)