Skip to content

Commit 85762ce

Browse files
committed
Merge branch 'ParamSpec/Generic-simple' into ParamSpec/Generic-Substitution
2 parents 2814a85 + edf7d19 commit 85762ce

File tree

4 files changed

+110
-11
lines changed

4 files changed

+110
-11
lines changed

.github/workflows/third_party.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -412,5 +412,5 @@ jobs:
412412
owner: "python",
413413
repo: "typing_extensions",
414414
title: `Third-party tests failed on ${new Date().toDateString()}`,
415-
body: "Runs listed here: https://github.com/python/typing_extensions/actions/workflows/third_party.yml",
415+
body: "Full history of runs listed here: https://github.com/python/typing_extensions/actions/workflows/third_party.yml",
416416
})

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ aliases that have a `Concatenate` special form as their argument.
2323
`Ellipsis` as an argument. Patch by [Daraan](https://github.com/Daraan).
2424
- Fix error in subscription of `Unpack` aliases causing nested Unpacks
2525
to not be resolved correctly. Patch by [Daraan](https://github.com/Daraan).
26+
- Backport CPython PR [#124795](https://github.com/python/cpython/pull/124795):
27+
fix `TypeAliasType` not raising an error on non-tuple inputs for `type_params`.
28+
Patch by [Daraan](https://github.com/Daraan).
2629

2730
# Release 4.12.2 (June 7, 2024)
2831

src/test_typing_extensions.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6321,6 +6321,10 @@ def test_typing_extensions_defers_when_possible(self):
63216321
'AsyncGenerator', 'ContextManager', 'AsyncContextManager',
63226322
'ParamSpec', 'TypeVar', 'TypeVarTuple', 'get_type_hints',
63236323
}
6324+
if sys.version_info < (3, 14):
6325+
exclude |= {
6326+
'TypeAliasType'
6327+
}
63246328
if not typing_extensions._PEP_728_IMPLEMENTED:
63256329
exclude |= {'TypedDict', 'is_typeddict'}
63266330
for item in typing_extensions.__all__:
@@ -7531,6 +7535,80 @@ def test_no_instance_subclassing(self):
75317535
class MyAlias(TypeAliasType):
75327536
pass
75337537

7538+
def test_type_var_compatibility(self):
7539+
# Regression test to assure compatibility with typing variants
7540+
typingT = typing.TypeVar('typingT')
7541+
T1 = TypeAliasType("TypingTypeVar", ..., type_params=(typingT,))
7542+
self.assertEqual(T1.__type_params__, (typingT,))
7543+
7544+
# Test typing_extensions backports
7545+
textT = TypeVar('textT')
7546+
T2 = TypeAliasType("TypingExtTypeVar", ..., type_params=(textT,))
7547+
self.assertEqual(T2.__type_params__, (textT,))
7548+
7549+
textP = ParamSpec("textP")
7550+
T3 = TypeAliasType("TypingExtParamSpec", ..., type_params=(textP,))
7551+
self.assertEqual(T3.__type_params__, (textP,))
7552+
7553+
textTs = TypeVarTuple("textTs")
7554+
T4 = TypeAliasType("TypingExtTypeVarTuple", ..., type_params=(textTs,))
7555+
self.assertEqual(T4.__type_params__, (textTs,))
7556+
7557+
@skipUnless(TYPING_3_10_0, "typing.ParamSpec is not available before 3.10")
7558+
def test_param_spec_compatibility(self):
7559+
# Regression test to assure compatibility with typing variant
7560+
typingP = typing.ParamSpec("typingP")
7561+
T5 = TypeAliasType("TypingParamSpec", ..., type_params=(typingP,))
7562+
self.assertEqual(T5.__type_params__, (typingP,))
7563+
7564+
@skipUnless(TYPING_3_12_0, "typing.TypeVarTuple is not available before 3.12")
7565+
def test_type_var_tuple_compatibility(self):
7566+
# Regression test to assure compatibility with typing variant
7567+
typingTs = typing.TypeVarTuple("typingTs")
7568+
T6 = TypeAliasType("TypingTypeVarTuple", ..., type_params=(typingTs,))
7569+
self.assertEqual(T6.__type_params__, (typingTs,))
7570+
7571+
def test_type_params_possibilities(self):
7572+
T = TypeVar('T')
7573+
# Test not a tuple
7574+
with self.assertRaisesRegex(TypeError, "type_params must be a tuple"):
7575+
TypeAliasType("InvalidTypeParams", List[T], type_params=[T])
7576+
7577+
# Test default order and other invalid inputs
7578+
T_default = TypeVar('T_default', default=int)
7579+
Ts = TypeVarTuple('Ts')
7580+
Ts_default = TypeVarTuple('Ts_default', default=Unpack[Tuple[str, int]])
7581+
P = ParamSpec('P')
7582+
P_default = ParamSpec('P_default', default=[str, int])
7583+
7584+
# NOTE: PEP 696 states: "TypeVars with defaults cannot immediately follow TypeVarTuples"
7585+
# this is currently not enforced for the type statement and is not tested.
7586+
# PEP 695: Double usage of the same name is also not enforced and not tested.
7587+
valid_cases = [
7588+
(T, P, Ts),
7589+
(T, Ts_default),
7590+
(P_default, T_default),
7591+
(P, T_default, Ts_default),
7592+
(T_default, P_default, Ts_default),
7593+
]
7594+
invalid_cases = [
7595+
((T_default, T), f"non-default type parameter '{T!r}' follows default"),
7596+
((P_default, P), f"non-default type parameter '{P!r}' follows default"),
7597+
((Ts_default, T), f"non-default type parameter '{T!r}' follows default"),
7598+
# Only type params are accepted
7599+
((1,), "Expected a type param, got 1"),
7600+
((str,), f"Expected a type param, got {str!r}"),
7601+
# Unpack is not a TypeVar but isinstance(Unpack[Ts], TypeVar) is True in Python < 3.12
7602+
((Unpack[Ts],), f"Expected a type param, got {re.escape(repr(Unpack[Ts]))}"),
7603+
]
7604+
7605+
for case in valid_cases:
7606+
with self.subTest(type_params=case):
7607+
TypeAliasType("OkCase", List[T], type_params=case)
7608+
for case, msg in invalid_cases:
7609+
with self.subTest(type_params=case):
7610+
with self.assertRaisesRegex(TypeError, msg):
7611+
TypeAliasType("InvalidCase", List[T], type_params=case)
75347612

75357613
class DocTests(BaseTestCase):
75367614
def test_annotation(self):

src/typing_extensions.py

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3155,17 +3155,16 @@ def _check_generic(cls, parameters, elen=_marker):
31553155
31563156
This gives a nice error message in case of count mismatch.
31573157
"""
3158+
# If substituting a single ParamSpec with multiple arguments
3159+
# we do not check the count
31583160
if (inspect.isclass(cls) and issubclass(cls, typing.Generic)
3159-
and any(isinstance(t, ParamSpec) for t in cls.__parameters__)
3161+
and len(cls.__parameters__) == 1
3162+
and isinstance(cls.__parameters__[0], ParamSpec)
3163+
and parameters
3164+
and not _is_param_expr(parameters[0])
31603165
):
3161-
# should actually modify parameters but is immutable
3162-
if (
3163-
len(cls.__parameters__) == 1
3164-
and parameters
3165-
and not _is_param_expr(parameters[0])
3166-
):
3167-
assert isinstance(cls.__parameters__[0], ParamSpec)
3168-
return
3166+
# Generic modifies parameters variable, but here we cannot do this
3167+
return
31693168

31703169
if not elen:
31713170
raise TypeError(f"{cls} is not a generic class")
@@ -3682,8 +3681,9 @@ def __ror__(self, other):
36823681
return typing.Union[other, self]
36833682

36843683

3685-
if hasattr(typing, "TypeAliasType"):
3684+
if sys.version_info >= (3, 14):
36863685
TypeAliasType = typing.TypeAliasType
3686+
# 3.8-3.13
36873687
else:
36883688
def _is_unionable(obj):
36893689
"""Corresponds to is_unionable() in unionobject.c in CPython."""
@@ -3756,11 +3756,29 @@ class TypeAliasType:
37563756
def __init__(self, name: str, value, *, type_params=()):
37573757
if not isinstance(name, str):
37583758
raise TypeError("TypeAliasType name must be a string")
3759+
if not isinstance(type_params, tuple):
3760+
raise TypeError("type_params must be a tuple")
37593761
self.__value__ = value
37603762
self.__type_params__ = type_params
37613763

3764+
default_value_encountered = False
37623765
parameters = []
37633766
for type_param in type_params:
3767+
if (
3768+
not isinstance(type_param, (TypeVar, TypeVarTuple, ParamSpec))
3769+
# 3.8-3.11
3770+
# Unpack Backport passes isinstance(type_param, TypeVar)
3771+
or _is_unpack(type_param)
3772+
):
3773+
raise TypeError(f"Expected a type param, got {type_param!r}")
3774+
has_default = (
3775+
getattr(type_param, '__default__', NoDefault) is not NoDefault
3776+
)
3777+
if default_value_encountered and not has_default:
3778+
raise TypeError(f"non-default type parameter '{type_param!r}'"
3779+
" follows default type parameter")
3780+
if has_default:
3781+
default_value_encountered = True
37643782
if isinstance(type_param, TypeVarTuple):
37653783
parameters.extend(type_param)
37663784
else:

0 commit comments

Comments
 (0)