diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index 43a1a52874e0196..d73418804a784a5 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -377,20 +377,19 @@ 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'" + def test_parameter_after_var_keyword(self): + 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): + def test_double_star_without_name(self): err = "Function 'my_test_func' has an invalid parameter declaration: '**'" block = """ /*[clinic input] @@ -398,7 +397,6 @@ def test_var_keyword_after_star(self): pos_arg: object ** - **kwds: dict [clinic start generated code]*/ """ self.expect_failure(block, err, lineno=5) @@ -1982,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 @@ -1994,11 +2037,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 " @@ -2386,38 +2424,51 @@ 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_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): @@ -2509,7 +2560,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 @@ -3120,7 +3171,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): @@ -3133,7 +3186,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): @@ -3147,7 +3202,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 b241f58711e68a4..8b84adc594f46c6 100644 --- a/Tools/clinic/libclinic/dsl_parser.py +++ b/Tools/clinic/libclinic/dsl_parser.py @@ -904,6 +904,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 '*': @@ -920,10 +923,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() @@ -974,8 +973,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: @@ -1212,9 +1212,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() @@ -1278,9 +1275,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 ...]'")