Skip to content

Commit b8cf3fd

Browse files
committed
Merge remote-tracking branch 'upstream/main' into ParamSpec/Generic-Substitution
2 parents f155a46 + f2d0667 commit b8cf3fd

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
@@ -26,6 +26,9 @@ aliases that have a `Concatenate` special form as their argument.
2626
- Backport CPython PR [#124795](https://github.com/python/cpython/pull/124795):
2727
fix `TypeAliasType` not raising an error on non-tuple inputs for `type_params`.
2828
Patch by [Daraan](https://github.com/Daraan).
29+
- Fix that lists and ... could not be used for parameter expressions for `TypeAliasType`
30+
instances before Python 3.11.
31+
Patch by [Daraan](https://github.com/Daraan).
2932

3033
# Release 4.12.2 (June 7, 2024)
3134

src/test_typing_extensions.py

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

7399+
P = ParamSpec('P')
7400+
CallableP = TypeAliasType("CallableP", Callable[P, Any], type_params=(P, ))
7401+
self.assertEqual(CallableP.__name__, "CallableP")
7402+
self.assertEqual(CallableP.__value__, Callable[P, Any])
7403+
self.assertEqual(CallableP.__type_params__, (P,))
7404+
self.assertEqual(CallableP.__parameters__, (P,))
7405+
7406+
def test_alias_types_and_substitutions(self):
7407+
T = TypeVar('T')
7408+
T2 = TypeVar('T2')
7409+
T_default = TypeVar("T_default", default=int)
7410+
Ts = TypeVarTuple("Ts")
7411+
P = ParamSpec('P')
7412+
7413+
test_argument_cases = {
7414+
# arguments : expected parameters
7415+
int : (),
7416+
... : (),
7417+
None : (),
7418+
T2 : (T2,),
7419+
Union[int, List[T2]] : (T2,),
7420+
Tuple[int, str] : (),
7421+
Tuple[T, T_default, T2] : (T, T_default, T2),
7422+
Tuple[Unpack[Ts]] : (Ts,),
7423+
Callable[[Unpack[Ts]], T2] : (Ts, T2),
7424+
Callable[P, T2] : (P, T2),
7425+
Callable[Concatenate[T2, P], T_default] : (T2, P, T_default),
7426+
TypeAliasType("NestedAlias", List[T], type_params=(T,))[T2] : (T2,),
7427+
Unpack[Ts] : (Ts,),
7428+
Unpack[Tuple[int, T2]] : (T2,),
7429+
Concatenate[int, P] : (P,),
7430+
# Not tested usage of bare TypeVarTuple, would need 3.11+
7431+
# Ts : (Ts,), # invalid case
7432+
}
7433+
7434+
test_alias_cases = [
7435+
# Simple cases
7436+
TypeAliasType("ListT", List[T], type_params=(T,)),
7437+
TypeAliasType("UnionT", Union[int, List[T]], type_params=(T,)),
7438+
# Value has no parameter but in type_param
7439+
TypeAliasType("ValueWithoutT", int, type_params=(T,)),
7440+
# Callable
7441+
TypeAliasType("CallableP", Callable[P, Any], type_params=(P, )),
7442+
TypeAliasType("CallableT", Callable[..., T], type_params=(T, )),
7443+
TypeAliasType("CallableTs", Callable[[Unpack[Ts]], Any], type_params=(Ts, )),
7444+
# TypeVarTuple
7445+
TypeAliasType("Variadic", Tuple[int, Unpack[Ts]], type_params=(Ts,)),
7446+
# TypeVar with default
7447+
TypeAliasType("TupleT_default", Tuple[T_default, T], type_params=(T, T_default)),
7448+
TypeAliasType("CallableT_default", Callable[[T], T_default], type_params=(T, T_default)),
7449+
]
7450+
7451+
for alias in test_alias_cases:
7452+
with self.subTest(alias=alias, args=[]):
7453+
subscripted = alias[[]]
7454+
self.assertEqual(get_args(subscripted), ([],))
7455+
self.assertEqual(subscripted.__parameters__, ())
7456+
with self.subTest(alias=alias, args=()):
7457+
subscripted = alias[()]
7458+
self.assertEqual(get_args(subscripted), ())
7459+
self.assertEqual(subscripted.__parameters__, ())
7460+
with self.subTest(alias=alias, args=(int, float)):
7461+
subscripted = alias[int, float]
7462+
self.assertEqual(get_args(subscripted), (int, float))
7463+
self.assertEqual(subscripted.__parameters__, ())
7464+
with self.subTest(alias=alias, args=[int, float]):
7465+
subscripted = alias[[int, float]]
7466+
self.assertEqual(get_args(subscripted), ([int, float],))
7467+
self.assertEqual(subscripted.__parameters__, ())
7468+
for expected_args, expected_parameters in test_argument_cases.items():
7469+
with self.subTest(alias=alias, args=expected_args):
7470+
self.assertEqual(get_args(alias[expected_args]), (expected_args,))
7471+
self.assertEqual(alias[expected_args].__parameters__, expected_parameters)
7472+
73997473
def test_cannot_set_attributes(self):
74007474
Simple = TypeAliasType("Simple", int)
74017475
with self.assertRaisesRegex(AttributeError, "readonly attribute"):
@@ -7456,12 +7530,19 @@ def test_or(self):
74567530
Alias | "Ref"
74577531

74587532
def test_getitem(self):
7533+
T = TypeVar('T')
74597534
ListOrSetT = TypeAliasType("ListOrSetT", Union[List[T], Set[T]], type_params=(T,))
74607535
subscripted = ListOrSetT[int]
74617536
self.assertEqual(get_args(subscripted), (int,))
74627537
self.assertIs(get_origin(subscripted), ListOrSetT)
7463-
with self.assertRaises(TypeError):
7464-
subscripted[str]
7538+
with self.assertRaisesRegex(TypeError,
7539+
"not a generic class"
7540+
# types.GenericAlias raises a different error in 3.10
7541+
if sys.version_info[:2] != (3, 10)
7542+
else "There are no type variables left in ListOrSetT"
7543+
):
7544+
subscripted[int]
7545+
74657546

74667547
still_generic = ListOrSetT[Iterable[T]]
74677548
self.assertEqual(get_args(still_generic), (Iterable[T],))
@@ -7470,6 +7551,114 @@ def test_getitem(self):
74707551
self.assertEqual(get_args(fully_subscripted), (Iterable[float],))
74717552
self.assertIs(get_origin(fully_subscripted), ListOrSetT)
74727553

7554+
ValueWithoutTypeVar = TypeAliasType("ValueWithoutTypeVar", int, type_params=(T,))
7555+
still_subscripted = ValueWithoutTypeVar[str]
7556+
self.assertEqual(get_args(still_subscripted), (str,))
7557+
7558+
def test_callable_without_concatenate(self):
7559+
P = ParamSpec('P')
7560+
CallableP = TypeAliasType("CallableP", Callable[P, Any], type_params=(P,))
7561+
get_args_test_cases = [
7562+
# List of (alias, expected_args)
7563+
# () -> Any
7564+
(CallableP[()], ()),
7565+
(CallableP[[]], ([],)),
7566+
# (int) -> Any
7567+
(CallableP[int], (int,)),
7568+
(CallableP[[int]], ([int],)),
7569+
# (int, int) -> Any
7570+
(CallableP[int, int], (int, int)),
7571+
(CallableP[[int, int]], ([int, int],)),
7572+
# (...) -> Any
7573+
(CallableP[...], (...,)),
7574+
# (int, ...) -> Any
7575+
(CallableP[[int, ...]], ([int, ...],)),
7576+
]
7577+
7578+
for index, (expression, expected_args) in enumerate(get_args_test_cases):
7579+
with self.subTest(index=index, expression=expression):
7580+
self.assertEqual(get_args(expression), expected_args)
7581+
7582+
self.assertEqual(CallableP[...], CallableP[(...,)])
7583+
# (T) -> Any
7584+
CallableT = CallableP[T]
7585+
self.assertEqual(get_args(CallableT), (T,))
7586+
self.assertEqual(CallableT.__parameters__, (T,))
7587+
7588+
def test_callable_with_concatenate(self):
7589+
P = ParamSpec('P')
7590+
P2 = ParamSpec('P2')
7591+
CallableP = TypeAliasType("CallableP", Callable[P, Any], type_params=(P,))
7592+
7593+
callable_concat = CallableP[Concatenate[int, P2]]
7594+
self.assertEqual(callable_concat.__parameters__, (P2,))
7595+
concat_usage = callable_concat[str]
7596+
with self.subTest("get_args of Concatenate in TypeAliasType"):
7597+
if not TYPING_3_9_0:
7598+
# args are: ([<class 'int'>, ~P2],)
7599+
self.skipTest("Nested ParamSpec is not substituted")
7600+
if sys.version_info < (3, 10, 2):
7601+
self.skipTest("GenericAlias keeps Concatenate in __args__ prior to 3.10.2")
7602+
self.assertEqual(get_args(concat_usage), ((int, str),))
7603+
with self.subTest("Equality of parameter_expression without []"):
7604+
if not TYPING_3_10_0:
7605+
self.skipTest("Nested list is invalid type form")
7606+
self.assertEqual(concat_usage, callable_concat[[str]])
7607+
7608+
def test_substitution(self):
7609+
T = TypeVar('T')
7610+
Ts = TypeVarTuple("Ts")
7611+
7612+
CallableTs = TypeAliasType("CallableTs", Callable[[Unpack[Ts]], Any], type_params=(Ts, ))
7613+
unpack_callable = CallableTs[Unpack[Tuple[int, T]]]
7614+
self.assertEqual(get_args(unpack_callable), (Unpack[Tuple[int, T]],))
7615+
7616+
P = ParamSpec('P')
7617+
CallableP = TypeAliasType("CallableP", Callable[P, T], type_params=(P, T))
7618+
callable_concat = CallableP[Concatenate[int, P], Any]
7619+
self.assertEqual(get_args(callable_concat), (Concatenate[int, P], Any))
7620+
7621+
def test_wrong_amount_of_parameters(self):
7622+
T = TypeVar('T')
7623+
T2 = TypeVar("T2")
7624+
P = ParamSpec('P')
7625+
ListOrSetT = TypeAliasType("ListOrSetT", Union[List[T], Set[T]], type_params=(T,))
7626+
TwoT = TypeAliasType("TwoT", Union[List[T], Set[T2]], type_params=(T, T2))
7627+
CallablePT = TypeAliasType("CallablePT", Callable[P, T], type_params=(P, T))
7628+
7629+
# Not enough parameters
7630+
test_cases = [
7631+
# not_enough
7632+
(TwoT[int], [(int,), ()]),
7633+
(TwoT[T], [(T,), (T,)]),
7634+
# callable and not enough
7635+
(CallablePT[int], [(int,), ()]),
7636+
# too many
7637+
(ListOrSetT[int, bool], [(int, bool), ()]),
7638+
# callable and too many
7639+
(CallablePT[str, float, int], [(str, float, int), ()]),
7640+
# Check if TypeVar is still present even if over substituted
7641+
(ListOrSetT[int, T], [(int, T), (T,)]),
7642+
# With and without list for ParamSpec
7643+
(CallablePT[str, float, T], [(str, float, T), (T,)]),
7644+
(CallablePT[[str], float, int, T2], [([str], float, int, T2), (T2,)]),
7645+
]
7646+
7647+
for index, (alias, [expected_args, expected_params]) in enumerate(test_cases):
7648+
with self.subTest(index=index, alias=alias):
7649+
self.assertEqual(get_args(alias), expected_args)
7650+
self.assertEqual(alias.__parameters__, expected_params)
7651+
7652+
# The condition should align with the version of GeneriAlias usage in __getitem__ or be 3.11+
7653+
@skipIf(TYPING_3_10_0, "Most arguments are allowed in 3.11+ or with GenericAlias")
7654+
def test_invalid_cases_before_3_10(self):
7655+
T = TypeVar('T')
7656+
ListOrSetT = TypeAliasType("ListOrSetT", Union[List[T], Set[T]], type_params=(T,))
7657+
with self.assertRaises(TypeError):
7658+
ListOrSetT[Generic[T]]
7659+
with self.assertRaises(TypeError):
7660+
ListOrSetT[(Generic[T], )]
7661+
74737662
def test_unpack_parameter_collection(self):
74747663
Ts = TypeVarTuple("Ts")
74757664

src/typing_extensions.py

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

3818+
if sys.version_info < (3, 11):
3819+
def _check_single_param(self, param, recursion=0):
3820+
# Allow [], [int], [int, str], [int, ...], [int, T]
3821+
if param is ...:
3822+
return ...
3823+
if param is None:
3824+
return None
3825+
# Note in <= 3.9 _ConcatenateGenericAlias inherits from list
3826+
if isinstance(param, list) and recursion == 0:
3827+
return [self._check_single_param(arg, recursion+1)
3828+
for arg in param]
3829+
return typing._type_check(
3830+
param, f'Subscripting {self.__name__} requires a type.'
3831+
)
3832+
3833+
def _check_parameters(self, parameters):
3834+
if sys.version_info < (3, 11):
3835+
return tuple(
3836+
self._check_single_param(item)
3837+
for item in parameters
3838+
)
3839+
return tuple(typing._type_check(
3840+
item, f'Subscripting {self.__name__} requires a type.'
3841+
)
3842+
for item in parameters
3843+
)
3844+
38183845
def __getitem__(self, parameters):
38193846
if not self.__type_params__:
38203847
raise TypeError("Only generic type aliases are subscriptable")
@@ -3823,13 +3850,14 @@ def __getitem__(self, parameters):
38233850
# Using 3.9 here will create problems with Concatenate
38243851
if sys.version_info >= (3, 10):
38253852
return _types.GenericAlias(self, parameters)
3826-
parameters = tuple(
3827-
typing._type_check(
3828-
item, f'Subscripting {self.__name__} requires a type.'
3829-
)
3830-
for item in parameters
3831-
)
3832-
return _TypeAliasGenericAlias(self, parameters)
3853+
type_vars = _collect_type_vars(parameters)
3854+
parameters = self._check_parameters(parameters)
3855+
alias = _TypeAliasGenericAlias(self, parameters)
3856+
# alias.__parameters__ is not complete if Concatenate is present
3857+
# as it is converted to a list from which no parameters are extracted.
3858+
if alias.__parameters__ != type_vars:
3859+
alias.__parameters__ = type_vars
3860+
return alias
38333861

38343862
def __reduce__(self):
38353863
return self.__name__

0 commit comments

Comments
 (0)