Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 479ac33

Browse filesBrowse files
authored
Allow type parameters without default values to follow those with default values in some situations (python#392)
1 parent 028035e commit 479ac33
Copy full SHA for 479ac33

File tree

Expand file treeCollapse file tree

3 files changed

+71
-22
lines changed
Filter options
Expand file treeCollapse file tree

3 files changed

+71
-22
lines changed

‎CHANGELOG.md

Copy file name to clipboardExpand all lines: CHANGELOG.md
+4Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111
at runtime rather than `types.NoneType`.
1212
- Fix most tests for `TypeVar`, `ParamSpec` and `TypeVarTuple` on Python
1313
3.13.0b1 and newer.
14+
- Backport CPython PR [#118774](https://github.com/python/cpython/pull/118774),
15+
allowing type parameters without default values to follow those with
16+
default values in some type parameter lists. Patch by Alex Waygood,
17+
backporting a CPython PR by Jelle Zijlstra.
1418
- It is now disallowed to use a `TypeVar` with a default value after a
1519
`TypeVarTuple` in a type parameter list. This matches the CPython
1620
implementation of PEP 696 on Python 3.13+.

‎src/test_typing_extensions.py

Copy file name to clipboardExpand all lines: src/test_typing_extensions.py
+19Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6457,6 +6457,25 @@ def test_pickle(self):
64576457
self.assertEqual(z.__bound__, typevar.__bound__)
64586458
self.assertEqual(z.__default__, typevar.__default__)
64596459

6460+
@skip_if_py313_beta_1
6461+
def test_allow_default_after_non_default_in_alias(self):
6462+
T_default = TypeVar('T_default', default=int)
6463+
T = TypeVar('T')
6464+
Ts = TypeVarTuple('Ts')
6465+
6466+
a1 = Callable[[T_default], T]
6467+
self.assertEqual(a1.__args__, (T_default, T))
6468+
6469+
if sys.version_info >= (3, 9):
6470+
a2 = dict[T_default, T]
6471+
self.assertEqual(a2.__args__, (T_default, T))
6472+
6473+
a3 = typing.Dict[T_default, T]
6474+
self.assertEqual(a3.__args__, (T_default, T))
6475+
6476+
a4 = Callable[[Unpack[Ts]], T]
6477+
self.assertEqual(a4.__args__, (Unpack[Ts], T))
6478+
64606479

64616480
class NoDefaultTests(BaseTestCase):
64626481
@skip_if_py313_beta_1

‎src/typing_extensions.py

Copy file name to clipboardExpand all lines: src/typing_extensions.py
+48-22Lines changed: 48 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2857,6 +2857,18 @@ def _check_generic(cls, parameters, elen):
28572857
typing._check_generic = _check_generic
28582858

28592859

2860+
def _has_generic_or_protocol_as_origin() -> bool:
2861+
try:
2862+
frame = sys._getframe(2)
2863+
# not all platforms have sys._getframe()
2864+
except AttributeError:
2865+
return False # err on the side of leniency
2866+
else:
2867+
return frame.f_locals.get("origin") in {
2868+
typing.Generic, Protocol, typing.Protocol
2869+
}
2870+
2871+
28602872
_TYPEVARTUPLE_TYPES = {TypeVarTuple, getattr(typing, "TypeVarTuple", None)}
28612873

28622874

@@ -2882,23 +2894,29 @@ def _collect_type_vars(types, typevar_types=None):
28822894
if typevar_types is None:
28832895
typevar_types = typing.TypeVar
28842896
tvars = []
2885-
# required TypeVarLike cannot appear after TypeVarLike with default
2897+
2898+
# A required TypeVarLike cannot appear after a TypeVarLike with a default
2899+
# if it was a direct call to `Generic[]` or `Protocol[]`
2900+
enforce_default_ordering = _has_generic_or_protocol_as_origin()
28862901
default_encountered = False
2887-
# or after TypeVarTuple
2902+
2903+
# Also, a TypeVarLike with a default cannot appear after a TypeVarTuple
28882904
type_var_tuple_encountered = False
2905+
28892906
for t in types:
28902907
if _is_unpacked_typevartuple(t):
28912908
type_var_tuple_encountered = True
28922909
elif isinstance(t, typevar_types) and t not in tvars:
2893-
has_default = getattr(t, '__default__', NoDefault) is not NoDefault
2894-
if has_default:
2895-
if type_var_tuple_encountered:
2896-
raise TypeError('Type parameter with a default'
2897-
' follows TypeVarTuple')
2898-
default_encountered = True
2899-
elif default_encountered:
2900-
raise TypeError(f'Type parameter {t!r} without a default'
2901-
' follows type parameter with a default')
2910+
if enforce_default_ordering:
2911+
has_default = getattr(t, '__default__', NoDefault) is not NoDefault
2912+
if has_default:
2913+
if type_var_tuple_encountered:
2914+
raise TypeError('Type parameter with a default'
2915+
' follows TypeVarTuple')
2916+
default_encountered = True
2917+
elif default_encountered:
2918+
raise TypeError(f'Type parameter {t!r} without a default'
2919+
' follows type parameter with a default')
29022920

29032921
tvars.append(t)
29042922
if _should_collect_from_parameters(t):
@@ -2916,10 +2934,15 @@ def _collect_parameters(args):
29162934
assert _collect_parameters((T, Callable[P, T])) == (T, P)
29172935
"""
29182936
parameters = []
2919-
# required TypeVarLike cannot appear after TypeVarLike with default
2937+
2938+
# A required TypeVarLike cannot appear after a TypeVarLike with default
2939+
# if it was a direct call to `Generic[]` or `Protocol[]`
2940+
enforce_default_ordering = _has_generic_or_protocol_as_origin()
29202941
default_encountered = False
2921-
# or after TypeVarTuple
2942+
2943+
# Also, a TypeVarLike with a default cannot appear after a TypeVarTuple
29222944
type_var_tuple_encountered = False
2945+
29232946
for t in args:
29242947
if isinstance(t, type):
29252948
# We don't want __parameters__ descriptor of a bare Python class.
@@ -2933,17 +2956,20 @@ def _collect_parameters(args):
29332956
parameters.append(collected)
29342957
elif hasattr(t, '__typing_subst__'):
29352958
if t not in parameters:
2936-
has_default = getattr(t, '__default__', NoDefault) is not NoDefault
2959+
if enforce_default_ordering:
2960+
has_default = (
2961+
getattr(t, '__default__', NoDefault) is not NoDefault
2962+
)
29372963

2938-
if type_var_tuple_encountered and has_default:
2939-
raise TypeError('Type parameter with a default'
2940-
' follows TypeVarTuple')
2964+
if type_var_tuple_encountered and has_default:
2965+
raise TypeError('Type parameter with a default'
2966+
' follows TypeVarTuple')
29412967

2942-
if has_default:
2943-
default_encountered = True
2944-
elif default_encountered:
2945-
raise TypeError(f'Type parameter {t!r} without a default'
2946-
' follows type parameter with a default')
2968+
if has_default:
2969+
default_encountered = True
2970+
elif default_encountered:
2971+
raise TypeError(f'Type parameter {t!r} without a default'
2972+
' follows type parameter with a default')
29472973

29482974
parameters.append(t)
29492975
else:

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.