From 6d016f18ced0e46353bb501226144228689afd7c Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Thu, 18 Sep 2025 15:26:04 +0100 Subject: [PATCH 1/4] Address Serhiy's follow-up comments --- Lib/test/test_clinic.py | 63 ++++++++++++---------------- Tools/clinic/libclinic/dsl_parser.py | 18 +++----- 2 files changed, 32 insertions(+), 49 deletions(-) diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index d54dd546ea36fb0..9c7b6eb81ee58e1 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -358,30 +358,16 @@ def test_vararg_after_star(self): self.expect_failure(block, err, lineno=6) def test_double_star_after_var_keyword(self): - err = "Function 'my_test_func' has an invalid parameter declaration (**kwargs?): '**kwds: dict'" + err = "parameters cannot follow var-keyword parameter: 'invalid_arg: object'" block = """ /*[clinic input] my_test_func - pos_arg: object **kwds: dict - ** + invalid_arg: object [clinic start generated code]*/ """ - self.expect_failure(block, err, lineno=5) - - def test_var_keyword_after_star(self): - err = "Function 'my_test_func' has an invalid parameter declaration: '**'" - block = """ - /*[clinic input] - my_test_func - - pos_arg: object - ** - **kwds: dict - [clinic start generated code]*/ - """ - self.expect_failure(block, err, lineno=5) + self.expect_failure(block, err, lineno=7) def test_module_already_got_one(self): err = "Already defined module 'm'!" @@ -1644,11 +1630,6 @@ def test_disallowed_grouping__must_be_position_only(self): [ a: object ] - """, """ - with_kwds - [ - **kwds: dict - ] """) err = ( "You cannot use optional groups ('[' and ']') unless all " @@ -2036,38 +2017,40 @@ def test_slash_after_var_keyword(self): block = """ module foo foo.bar - x: int - y: int **kwds: dict - z: int / """ - err = "Function 'bar' has an invalid parameter declaration (**kwargs?): '**kwds: dict'" + err = "parameters cannot follow var-keyword parameter: '/'" self.expect_failure(block, err) def test_star_after_var_keyword(self): block = """ module foo foo.bar - x: int - y: int **kwds: dict - z: int * """ - err = "Function 'bar' has an invalid parameter declaration (**kwargs?): '**kwds: dict'" + err = "parameters cannot follow var-keyword parameter: '*'" self.expect_failure(block, err) def test_parameter_after_var_keyword(self): block = """ module foo foo.bar - x: int - y: int **kwds: dict z: int """ - err = "Function 'bar' has an invalid parameter declaration (**kwargs?): '**kwds: dict'" + err = "parameters cannot follow var-keyword parameter: 'z: int'" + self.expect_failure(block, err) + + def test_group_with_var_keyword(self): + block = """ + with_kwds + [ + **kwds: dict + ] + """ + err = "parameters cannot follow var-keyword parameter: ']'" self.expect_failure(block, err) def test_depr_star_must_come_after_slash(self): @@ -2159,7 +2142,7 @@ def test_parameters_no_more_than_one_vararg(self): self.expect_failure(block, err, lineno=3) def test_parameters_no_more_than_one_var_keyword(self): - err = "Encountered parameter line when not expecting parameters: **var_keyword_2: dict" + err = "parameters cannot follow var-keyword parameter: '**var_keyword_2: dict'" block = """ module foo foo.bar @@ -2714,7 +2697,9 @@ def test_var_keyword_with_pos_or_kw(self): x: int **kwds: dict """ - err = "Function 'bar' has an invalid parameter declaration (**kwargs?): '**kwds: dict'" + err = ("Function 'bar' uses a var-keyword parameter and other " + "non-positional parameters, which Argument Clinic does " + "not currently support: '**kwds: dict'") self.expect_failure(block, err) def test_var_keyword_with_kw_only(self): @@ -2727,7 +2712,9 @@ def test_var_keyword_with_kw_only(self): y: int **kwds: dict """ - err = "Function 'bar' has an invalid parameter declaration (**kwargs?): '**kwds: dict'" + err = ("Function 'bar' uses a var-keyword parameter and other " + "non-positional parameters, which Argument Clinic does " + "not currently support: '**kwds: dict'") self.expect_failure(block, err) def test_var_keyword_with_pos_or_kw_and_kw_only(self): @@ -2741,7 +2728,9 @@ def test_var_keyword_with_pos_or_kw_and_kw_only(self): z: int **kwds: dict """ - err = "Function 'bar' has an invalid parameter declaration (**kwargs?): '**kwds: dict'" + err = ("Function 'bar' uses a var-keyword parameter and other " + "non-positional parameters, which Argument Clinic does " + "not currently support: '**kwds: dict'") self.expect_failure(block, err) def test_allow_negative_accepted_by_py_ssize_t_converter_only(self): diff --git a/Tools/clinic/libclinic/dsl_parser.py b/Tools/clinic/libclinic/dsl_parser.py index 0d83baeba9e5080..8ea4151fe7851cb 100644 --- a/Tools/clinic/libclinic/dsl_parser.py +++ b/Tools/clinic/libclinic/dsl_parser.py @@ -862,6 +862,9 @@ def state_parameter(self, line: str) -> None: line = match[1] version = self.parse_version(match[2]) + if not self.expecting_parameters: + fail(f'parameters cannot follow var-keyword parameter: {line!r}') + func = self.function match line: case '*': @@ -878,10 +881,6 @@ def state_parameter(self, line: str) -> None: def parse_parameter(self, line: str) -> None: assert self.function is not None - if not self.expecting_parameters: - fail('Encountered parameter line when not expecting ' - f'parameters: {line}') - match self.parameter_state: case ParamState.START | ParamState.REQUIRED: self.to_required() @@ -932,8 +931,9 @@ def parse_parameter(self, line: str) -> None: for p in self.function.parameters.values() ) if has_non_positional_param: - fail(f"Function {self.function.name!r} has an " - f"invalid parameter declaration (**kwargs?): {line!r}") + fail(f'Function {self.function.name!r} uses a var-keyword parameter ' + f'and other non-positional parameters, which Argument Clinic ' + f'does not currently support: {line!r}') is_var_keyword = True parameter = function_args.kwarg else: @@ -1182,9 +1182,6 @@ def parse_star(self, function: Function, version: VersionTuple | None) -> None: The 'version' parameter signifies the future version from which the marker will take effect (None means it is already in effect). """ - if not self.expecting_parameters: - fail("Encountered '*' when not expecting parameters") - if version is None: self.check_previous_star() self.check_remaining_star() @@ -1240,9 +1237,6 @@ def parse_slash(self, function: Function, version: VersionTuple | None) -> None: The 'version' parameter signifies the future version from which the marker will take effect (None means it is already in effect). """ - if not self.expecting_parameters: - fail("Encountered '/' when not expecting parameters") - if version is None: if self.deprecated_keyword: fail(f"Function {function.name!r}: '/' must precede '/ [from ...]'") From 88db6df8a861a6f94d5acdb1acff966a9d0ca420 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Thu, 18 Sep 2025 16:59:28 +0100 Subject: [PATCH 2/4] fixup! Address Serhiy's follow-up comments --- Lib/test/test_clinic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index 9c7b6eb81ee58e1..fc510101d3726b3 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -367,7 +367,7 @@ def test_double_star_after_var_keyword(self): invalid_arg: object [clinic start generated code]*/ """ - self.expect_failure(block, err, lineno=7) + self.expect_failure(block, err, lineno=5) def test_module_already_got_one(self): err = "Already defined module 'm'!" From c77c41232d4e8effaafaee19adeb20fe013cad7b Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sat, 22 Aug 2026 11:48:30 +0300 Subject: [PATCH 3/4] Add tests for a bare ** and for var-positional in an optional group --- Lib/test/test_clinic.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index 4c480f2d7d9f83d..75dbe263b9591c0 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -377,7 +377,7 @@ def test_vararg_after_star(self): """ self.expect_failure(block, err, lineno=6) - def test_double_star_after_var_keyword(self): + def test_parameter_after_var_keyword(self): err = "parameters cannot follow var-keyword parameter: 'invalid_arg: object'" block = """ /*[clinic input] @@ -389,6 +389,18 @@ def test_double_star_after_var_keyword(self): """ self.expect_failure(block, err, lineno=5) + def test_double_star_without_name(self): + err = "Function 'my_test_func' has an invalid parameter declaration: '**'" + block = """ + /*[clinic input] + my_test_func + + pos_arg: object + ** + [clinic start generated code]*/ + """ + self.expect_failure(block, err, lineno=5) + def test_module_already_got_one(self): err = "Already defined module 'm'!" block = """ @@ -2403,6 +2415,17 @@ def test_group_with_var_keyword(self): err = "parameters cannot follow var-keyword parameter: ']'" self.expect_failure(block, err) + def test_group_with_var_positional(self): + block = """ + with_varpos + [ + *args: tuple + ] + """ + err = ("You cannot use optional groups ('[' and ']') unless all " + "parameters are positional-only ('/')") + self.expect_failure(block, err) + def test_depr_star_must_come_after_slash(self): block = """ module foo From 7938b67cffe09d4cee44df85fc6c19d6aeb0e372 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sat, 22 Aug 2026 11:56:17 +0300 Subject: [PATCH 4/4] Add tests for parameters after an optional group --- Lib/test/test_clinic.py | 45 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index 75dbe263b9591c0..d73418804a784a5 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -1980,6 +1980,51 @@ def test_disallowed_grouping__no_matching_bracket(self): err = "Function 'empty_group' has a ']' without a matching '['" self.expect_failure(block, err) + def test_disallowed_grouping__parameter_after_group(self): + # Only positional-only parameters can follow an optional group. + group_err = ("You cannot use optional groups ('[' and ']') unless all " + "parameters are positional-only ('/')") + kwds_err = ("Function 'bar' uses a var-keyword parameter and other " + "non-positional parameters, which Argument Clinic does " + "not currently support: '**kwds: dict'") + dataset = ((""" + module foo + foo.bar + [ + a: int + b: int + ] + y: int + """, group_err), (""" + module foo + foo.bar + [ + a: int + b: int + ] + * + y: int + """, group_err), (""" + module foo + foo.bar + [ + a: int + b: int + ] + *args: tuple + """, group_err), (""" + module foo + foo.bar + [ + a: int + b: int + ] + **kwds: dict + """, kwds_err)) + for block, err in dataset: + with self.subTest(block=block): + self.expect_failure(block, err) + def test_disallowed_grouping__must_be_position_only(self): dataset = (""" with_kwds