Skip to content

Commit

Permalink
gh-118772: Allow TypeVars without a default to follow those with a de…
Browse files Browse the repository at this point in the history
…fault when constructing aliases (#118774)
  • Loading branch information
JelleZijlstra committed May 8, 2024
1 parent 6d419db commit aac6b01
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 10 deletions.
17 changes: 17 additions & 0 deletions Lib/test/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,23 @@ class X(Generic[*Ts, T]): ...
with self.assertRaises(TypeError):
class Y(Generic[*Ts_default, T]): ...

def test_allow_default_after_non_default_in_alias(self):
T_default = TypeVar('T_default', default=int)
T = TypeVar('T')
Ts = TypeVarTuple('Ts')

a1 = Callable[[T_default], T]
self.assertEqual(a1.__args__, (T_default, T))

a2 = dict[T_default, T]
self.assertEqual(a2.__args__, (T_default, T))

a3 = typing.Dict[T_default, T]
self.assertEqual(a3.__args__, (T_default, T))

a4 = Callable[*Ts, T]
self.assertEqual(a4.__args__, (*Ts, T))

def test_paramspec_specialization(self):
T = TypeVar("T")
P = ParamSpec('P', default=[str, int])
Expand Down
25 changes: 15 additions & 10 deletions Lib/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ def _type_repr(obj):
return repr(obj)


def _collect_parameters(args):
def _collect_parameters(args, *, enforce_default_ordering: bool = True):
"""Collect all type variables and parameter specifications in args
in order of first appearance (lexicographic order).
Expand Down Expand Up @@ -286,15 +286,16 @@ def _collect_parameters(args):
parameters.append(collected)
elif hasattr(t, '__typing_subst__'):
if t not in parameters:
if type_var_tuple_encountered and t.has_default():
raise TypeError('Type parameter with a default'
' follows TypeVarTuple')
if enforce_default_ordering:
if type_var_tuple_encountered and t.has_default():
raise TypeError('Type parameter with a default'
' follows TypeVarTuple')

if t.has_default():
default_encountered = True
elif default_encountered:
raise TypeError(f'Type parameter {t!r} without a default'
' follows type parameter with a default')
if t.has_default():
default_encountered = True
elif default_encountered:
raise TypeError(f'Type parameter {t!r} without a default'
' follows type parameter with a default')

parameters.append(t)
else:
Expand Down Expand Up @@ -1416,7 +1417,11 @@ def __init__(self, origin, args, *, inst=True, name=None):
args = (args,)
self.__args__ = tuple(... if a is _TypingEllipsis else
a for a in args)
self.__parameters__ = _collect_parameters(args)
enforce_default_ordering = origin in (Generic, Protocol)
self.__parameters__ = _collect_parameters(
args,
enforce_default_ordering=enforce_default_ordering,
)
if not name:
self.__module__ = origin.__module__

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Allow :class:`typing.TypeVar` instances without a default to follow
instances without a default in some cases. Patch by Jelle Zijlstra.

0 comments on commit aac6b01

Please sign in to comment.