Skip to content

Commit 28d1e84

Browse files
committed
Merge remote-tracking branch 'upstream/main' into TypeAliasType-extension
2 parents 07ba7b7 + 139ac68 commit 28d1e84

File tree

3 files changed

+101
-1
lines changed

3 files changed

+101
-1
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
- Fix that lists and ... could not be used for parameter expressions for `TypeAliasType`
2727
instances before Python 3.11.
2828
Patch by [Daraan](https://github.com/Daraan).
29+
- Backport of CPython PR [#124795](https://github.com/python/cpython/pull/124795)
30+
and fix that `TypeAliasType` not raising an error on non-tuple inputs for `type_params`.
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: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6192,6 +6192,10 @@ def test_typing_extensions_defers_when_possible(self):
61926192
'AsyncGenerator', 'ContextManager', 'AsyncContextManager',
61936193
'ParamSpec', 'TypeVar', 'TypeVarTuple', 'get_type_hints',
61946194
}
6195+
if sys.version_info < (3, 14):
6196+
exclude |= {
6197+
'TypeAliasType'
6198+
}
61956199
if not typing_extensions._PEP_728_IMPLEMENTED:
61966200
exclude |= {'TypedDict', 'is_typeddict'}
61976201
for item in typing_extensions.__all__:
@@ -7586,6 +7590,80 @@ def test_no_instance_subclassing(self):
75867590
class MyAlias(TypeAliasType):
75877591
pass
75887592

7593+
def test_type_var_compatibility(self):
7594+
# Regression test to assure compatibility with typing variants
7595+
typingT = typing.TypeVar('typingT')
7596+
T1 = TypeAliasType("TypingTypeVar", ..., type_params=(typingT,))
7597+
self.assertEqual(T1.__type_params__, (typingT,))
7598+
7599+
# Test typing_extensions backports
7600+
textT = TypeVar('textT')
7601+
T2 = TypeAliasType("TypingExtTypeVar", ..., type_params=(textT,))
7602+
self.assertEqual(T2.__type_params__, (textT,))
7603+
7604+
textP = ParamSpec("textP")
7605+
T3 = TypeAliasType("TypingExtParamSpec", ..., type_params=(textP,))
7606+
self.assertEqual(T3.__type_params__, (textP,))
7607+
7608+
textTs = TypeVarTuple("textTs")
7609+
T4 = TypeAliasType("TypingExtTypeVarTuple", ..., type_params=(textTs,))
7610+
self.assertEqual(T4.__type_params__, (textTs,))
7611+
7612+
@skipUnless(TYPING_3_10_0, "typing.ParamSpec is not available before 3.10")
7613+
def test_param_spec_compatibility(self):
7614+
# Regression test to assure compatibility with typing variant
7615+
typingP = typing.ParamSpec("typingP")
7616+
T5 = TypeAliasType("TypingParamSpec", ..., type_params=(typingP,))
7617+
self.assertEqual(T5.__type_params__, (typingP,))
7618+
7619+
@skipUnless(TYPING_3_12_0, "typing.TypeVarTuple is not available before 3.12")
7620+
def test_type_var_tuple_compatibility(self):
7621+
# Regression test to assure compatibility with typing variant
7622+
typingTs = typing.TypeVarTuple("typingTs")
7623+
T6 = TypeAliasType("TypingTypeVarTuple", ..., type_params=(typingTs,))
7624+
self.assertEqual(T6.__type_params__, (typingTs,))
7625+
7626+
def test_type_params_possibilities(self):
7627+
T = TypeVar('T')
7628+
# Test not a tuple
7629+
with self.assertRaisesRegex(TypeError, "type_params must be a tuple"):
7630+
TypeAliasType("InvalidTypeParams", List[T], type_params=[T])
7631+
7632+
# Test default order and other invalid inputs
7633+
T_default = TypeVar('T_default', default=int)
7634+
Ts = TypeVarTuple('Ts')
7635+
Ts_default = TypeVarTuple('Ts_default', default=Unpack[Tuple[str, int]])
7636+
P = ParamSpec('P')
7637+
P_default = ParamSpec('P_default', default=[str, int])
7638+
7639+
# NOTE: PEP 696 states: "TypeVars with defaults cannot immediately follow TypeVarTuples"
7640+
# this is currently not enforced for the type statement and is not tested.
7641+
# PEP 695: Double usage of the same name is also not enforced and not tested.
7642+
valid_cases = [
7643+
(T, P, Ts),
7644+
(T, Ts_default),
7645+
(P_default, T_default),
7646+
(P, T_default, Ts_default),
7647+
(T_default, P_default, Ts_default),
7648+
]
7649+
invalid_cases = [
7650+
((T_default, T), f"non-default type parameter {T!r} follows default"),
7651+
((P_default, P), f"non-default type parameter {P!r} follows default"),
7652+
((Ts_default, T), f"non-default type parameter {T!r} follows default"),
7653+
# Only type params are accepted
7654+
((1,), "Expected a type param, got 1"),
7655+
((str,), f"Expected a type param, got {str!r}"),
7656+
# Unpack is not a TypeVar but isinstance(Unpack[Ts], TypeVar) is True in Python < 3.12
7657+
((Unpack[Ts],), f"Expected a type param, got {re.escape(repr(Unpack[Ts]))}"),
7658+
]
7659+
7660+
for case in valid_cases:
7661+
with self.subTest(type_params=case):
7662+
TypeAliasType("OkCase", List[T], type_params=case)
7663+
for case, msg in invalid_cases:
7664+
with self.subTest(type_params=case):
7665+
with self.assertRaisesRegex(TypeError, msg):
7666+
TypeAliasType("InvalidCase", List[T], type_params=case)
75897667

75907668
class DocTests(BaseTestCase):
75917669
def test_annotation(self):

src/typing_extensions.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3528,8 +3528,9 @@ def __ror__(self, other):
35283528
return typing.Union[other, self]
35293529

35303530

3531-
if hasattr(typing, "TypeAliasType"):
3531+
if sys.version_info >= (3, 14):
35323532
TypeAliasType = typing.TypeAliasType
3533+
# 3.8-3.13
35333534
else:
35343535
def _is_unionable(obj):
35353536
"""Corresponds to is_unionable() in unionobject.c in CPython."""
@@ -3602,11 +3603,29 @@ class TypeAliasType:
36023603
def __init__(self, name: str, value, *, type_params=()):
36033604
if not isinstance(name, str):
36043605
raise TypeError("TypeAliasType name must be a string")
3606+
if not isinstance(type_params, tuple):
3607+
raise TypeError("type_params must be a tuple")
36053608
self.__value__ = value
36063609
self.__type_params__ = type_params
36073610

3611+
default_value_encountered = False
36083612
parameters = []
36093613
for type_param in type_params:
3614+
if (
3615+
not isinstance(type_param, (TypeVar, TypeVarTuple, ParamSpec))
3616+
# 3.8-3.11
3617+
# Unpack Backport passes isinstance(type_param, TypeVar)
3618+
or _is_unpack(type_param)
3619+
):
3620+
raise TypeError(f"Expected a type param, got {type_param!r}")
3621+
has_default = (
3622+
getattr(type_param, '__default__', NoDefault) is not NoDefault
3623+
)
3624+
if default_value_encountered and not has_default:
3625+
raise TypeError(f'non-default type parameter {type_param!r}'
3626+
' follows default type parameter')
3627+
if has_default:
3628+
default_value_encountered = True
36103629
if isinstance(type_param, TypeVarTuple):
36113630
parameters.extend(type_param)
36123631
else:

0 commit comments

Comments
 (0)