Skip to content

Commit c6680cd

Browse files
[3.13] gh-118772: Allow TypeVars without a default to follow those with a default when constructing aliases (GH-118774) (#118776)
(cherry picked from commit aac6b01) Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
1 parent 02d49af commit c6680cd

File tree

3 files changed

+34
-10
lines changed

3 files changed

+34
-10
lines changed

Lib/test/test_typing.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -668,6 +668,23 @@ class X(Generic[*Ts, T]): ...
668668
with self.assertRaises(TypeError):
669669
class Y(Generic[*Ts_default, T]): ...
670670

671+
def test_allow_default_after_non_default_in_alias(self):
672+
T_default = TypeVar('T_default', default=int)
673+
T = TypeVar('T')
674+
Ts = TypeVarTuple('Ts')
675+
676+
a1 = Callable[[T_default], T]
677+
self.assertEqual(a1.__args__, (T_default, T))
678+
679+
a2 = dict[T_default, T]
680+
self.assertEqual(a2.__args__, (T_default, T))
681+
682+
a3 = typing.Dict[T_default, T]
683+
self.assertEqual(a3.__args__, (T_default, T))
684+
685+
a4 = Callable[*Ts, T]
686+
self.assertEqual(a4.__args__, (*Ts, T))
687+
671688
def test_paramspec_specialization(self):
672689
T = TypeVar("T")
673690
P = ParamSpec('P', default=[str, int])

Lib/typing.py

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ def _type_repr(obj):
257257
return repr(obj)
258258

259259

260-
def _collect_parameters(args):
260+
def _collect_parameters(args, *, enforce_default_ordering: bool = True):
261261
"""Collect all type variables and parameter specifications in args
262262
in order of first appearance (lexicographic order).
263263
@@ -286,15 +286,16 @@ def _collect_parameters(args):
286286
parameters.append(collected)
287287
elif hasattr(t, '__typing_subst__'):
288288
if t not in parameters:
289-
if type_var_tuple_encountered and t.has_default():
290-
raise TypeError('Type parameter with a default'
291-
' follows TypeVarTuple')
289+
if enforce_default_ordering:
290+
if type_var_tuple_encountered and t.has_default():
291+
raise TypeError('Type parameter with a default'
292+
' follows TypeVarTuple')
292293

293-
if t.has_default():
294-
default_encountered = True
295-
elif default_encountered:
296-
raise TypeError(f'Type parameter {t!r} without a default'
297-
' follows type parameter with a default')
294+
if t.has_default():
295+
default_encountered = True
296+
elif default_encountered:
297+
raise TypeError(f'Type parameter {t!r} without a default'
298+
' follows type parameter with a default')
298299

299300
parameters.append(t)
300301
else:
@@ -1416,7 +1417,11 @@ def __init__(self, origin, args, *, inst=True, name=None):
14161417
args = (args,)
14171418
self.__args__ = tuple(... if a is _TypingEllipsis else
14181419
a for a in args)
1419-
self.__parameters__ = _collect_parameters(args)
1420+
enforce_default_ordering = origin in (Generic, Protocol)
1421+
self.__parameters__ = _collect_parameters(
1422+
args,
1423+
enforce_default_ordering=enforce_default_ordering,
1424+
)
14201425
if not name:
14211426
self.__module__ = origin.__module__
14221427

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Allow :class:`typing.TypeVar` instances without a default to follow
2+
instances without a default in some cases. Patch by Jelle Zijlstra.

0 commit comments

Comments
 (0)