Skip to content

Commit cb6110a

Browse files
committed
gh-148945: add support for bounds on type variable tuples and parameter specifications
1 parent 448d7b9 commit cb6110a

6 files changed

Lines changed: 150 additions & 62 deletions

File tree

Doc/library/typing.rst

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2135,11 +2135,6 @@ without the dedicated syntax, as documented below.
21352135

21362136
.. versionadded:: 3.13
21372137

2138-
Type variable tuples created with ``covariant=True`` or
2139-
``contravariant=True`` can be used to declare covariant or contravariant
2140-
generic types. The ``bound`` argument is also accepted, similar to
2141-
:class:`TypeVar`, but its actual semantics are yet to be decided.
2142-
21432138
.. versionadded:: 3.11
21442139

21452140
.. versionchanged:: 3.12
@@ -2156,6 +2151,10 @@ without the dedicated syntax, as documented below.
21562151
Added support for the ``bound``, ``covariant``, ``contravariant``, and
21572152
``infer_variance`` parameters.
21582153

2154+
.. versionchanged:: 3.16
2155+
2156+
Syntax and semantics for bounds were added.
2157+
21592158
.. class:: ParamSpec(name, *, bound=None, covariant=False, contravariant=False, default=typing.NoDefault)
21602159

21612160
Parameter specification variable. A specialized version of
@@ -2282,6 +2281,10 @@ without the dedicated syntax, as documented below.
22822281

22832282
Support for default values was added.
22842283

2284+
.. versionchanged:: 3.16
2285+
2286+
Syntax and semantics for bounds were added.
2287+
22852288
.. note::
22862289
Only parameter specification variables defined in global scope can
22872290
be pickled.

Grammar/python.gram

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -695,10 +695,11 @@ type_param_seq[asdl_type_param_seq*]: a[asdl_type_param_seq*]=','.type_param+ ['
695695
type_param[type_param_ty] (memo):
696696
| a=NAME b=[type_param_bound] c=[type_param_default] { _PyAST_TypeVar(a->v.Name.id, b, c, EXTRA) }
697697
| invalid_type_param
698-
| '*' a=NAME b=[type_param_starred_default] { _PyAST_TypeVarTuple(a->v.Name.id, b, EXTRA) }
699-
| '**' a=NAME b=[type_param_default] { _PyAST_ParamSpec(a->v.Name.id, b, EXTRA) }
698+
| '*' a=NAME b=[type_param_stared_bound] c=[type_param_starred_default] { _PyAST_TypeVarTuple(a->v.Name.id, b, c, EXTRA) }
699+
| '**' a=NAME b=[type_param_bound] c=[type_param_default] { _PyAST_ParamSpec(a->v.Name.id, b, c, EXTRA) }
700700

701701
type_param_bound[expr_ty]: ':' e=expression { e }
702+
type_param_stared_bound[expr_ty]: ':' e=star_expression { e }
702703
type_param_default[expr_ty]: '=' e=expression {
703704
CHECK_VERSION(expr_ty, 13, "Type parameter defaults are", e) }
704705
type_param_starred_default[expr_ty]: '=' e=star_expression {

Include/internal/pycore_ast.h

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Lib/test/test_type_params.py

Lines changed: 38 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1075,52 +1075,43 @@ async def coroutine[B]():
10751075

10761076
class TypeParamsTypeVarTupleTest(unittest.TestCase):
10771077
def test_typevartuple_01(self):
1078-
code = """def func1[*A: str](): pass"""
1079-
check_syntax_error(self, code, "cannot use bound with TypeVarTuple")
1080-
code = """def func1[*A: (int, str)](): pass"""
1081-
check_syntax_error(self, code, "cannot use constraints with TypeVarTuple")
1082-
code = """class X[*A: str]: pass"""
1083-
check_syntax_error(self, code, "cannot use bound with TypeVarTuple")
1084-
code = """class X[*A: (int, str)]: pass"""
1085-
check_syntax_error(self, code, "cannot use constraints with TypeVarTuple")
1086-
code = """type X[*A: str] = int"""
1087-
check_syntax_error(self, code, "cannot use bound with TypeVarTuple")
1088-
code = """type X[*A: (int, str)] = int"""
1089-
check_syntax_error(self, code, "cannot use constraints with TypeVarTuple")
1090-
1091-
def test_typevartuple_02(self):
1092-
def func1[*A]():
1093-
return A
1094-
1095-
a = func1()
1078+
def func1[*A: str, *B: str | int]():
1079+
return A, B
1080+
1081+
a, b = func1()
1082+
10961083
self.assertIsInstance(a, TypeVarTuple)
1084+
self.assertEqual(a.__bound__, str)
1085+
self.assertTrue(a.__infer_variance__)
1086+
self.assertFalse(a.__covariant__)
1087+
self.assertFalse(a.__contravariant__)
1088+
1089+
self.assertIsInstance(b, TypeVarTuple)
1090+
self.assertEqual(b.__bound__, str | int)
1091+
self.assertTrue(b.__infer_variance__)
1092+
self.assertFalse(b.__covariant__)
1093+
self.assertFalse(b.__contravariant__)
10971094

10981095

10991096
class TypeParamsTypeVarParamSpecTest(unittest.TestCase):
11001097
def test_paramspec_01(self):
1101-
code = """def func1[**A: str](): pass"""
1102-
check_syntax_error(self, code, "cannot use bound with ParamSpec")
1103-
code = """def func1[**A: (int, str)](): pass"""
1104-
check_syntax_error(self, code, "cannot use constraints with ParamSpec")
1105-
code = """class X[**A: str]: pass"""
1106-
check_syntax_error(self, code, "cannot use bound with ParamSpec")
1107-
code = """class X[**A: (int, str)]: pass"""
1108-
check_syntax_error(self, code, "cannot use constraints with ParamSpec")
1109-
code = """type X[**A: str] = int"""
1110-
check_syntax_error(self, code, "cannot use bound with ParamSpec")
1111-
code = """type X[**A: (int, str)] = int"""
1112-
check_syntax_error(self, code, "cannot use constraints with ParamSpec")
1113-
1114-
def test_paramspec_02(self):
1115-
def func1[**A]():
1116-
return A
1117-
1118-
a = func1()
1098+
def func1[**A: [str], **B: [str | int]]():
1099+
return A, B
1100+
1101+
a, b = func1()
1102+
11191103
self.assertIsInstance(a, ParamSpec)
1104+
self.assertEqual(a.__bound__, [str])
11201105
self.assertTrue(a.__infer_variance__)
11211106
self.assertFalse(a.__covariant__)
11221107
self.assertFalse(a.__contravariant__)
11231108

1109+
self.assertIsInstance(b, ParamSpec)
1110+
self.assertEqual(b.__bound__, [str | int])
1111+
self.assertTrue(b.__infer_variance__)
1112+
self.assertFalse(b.__covariant__)
1113+
self.assertFalse(b.__contravariant__)
1114+
11241115

11251116
class TypeParamsTypeParamsDunder(unittest.TestCase):
11261117
def test_typeparams_dunder_class_01(self):
@@ -1264,7 +1255,7 @@ class NewStyle[T]:
12641255
P,
12651256
P.args,
12661257
P.kwargs,
1267-
TypeVarTuple('Ts'),
1258+
TypeVarTuple('Ts', bound=int),
12681259
OldStyle,
12691260
OldStyle[int],
12701261
OldStyle(),
@@ -1422,22 +1413,26 @@ class TestEvaluateFunctions(unittest.TestCase):
14221413
def test_general(self):
14231414
type Alias = int
14241415
Alias2 = TypeAliasType("Alias2", int)
1425-
def f[T: int = int, **P = int, *Ts = int](): pass
1426-
T, P, Ts = f.__type_params__
1416+
def f[T: int = int, *Ts: int = int, **P: [int] = int](): pass
1417+
T, Ts, P = f.__type_params__
14271418
T2 = TypeVar("T2", bound=int, default=int)
1428-
P2 = ParamSpec("P2", default=int)
1429-
Ts2 = TypeVarTuple("Ts2", default=int)
1419+
Ts2 = TypeVarTuple("Ts2", bound=int, default=int)
1420+
P2 = ParamSpec("P2", bound=[int], default=int)
14301421
cases = [
14311422
Alias.evaluate_value,
14321423
Alias2.evaluate_value,
14331424
T.evaluate_bound,
14341425
T.evaluate_default,
1435-
P.evaluate_default,
1426+
Ts.evaluate_bound,
14361427
Ts.evaluate_default,
1428+
P.evaluate_bound,
1429+
P.evaluate_default,
14371430
T2.evaluate_bound,
14381431
T2.evaluate_default,
1439-
P2.evaluate_default,
1432+
Ts2.evaluate_bound,
14401433
Ts2.evaluate_default,
1434+
P2.evaluate_bound,
1435+
P2.evaluate_default,
14411436
]
14421437
for case in cases:
14431438
with self.subTest(case=case):

Parser/parser.c

Lines changed: 59 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)