From 91961623153d5ad4e6cba0480db11e32db8bedcd Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Fri, 24 Jun 2022 23:30:39 +0200 Subject: [PATCH 01/71] PoC --- Tools/clinic/clinic.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 36bfc7050507e5..161ce5aef33634 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -1050,6 +1050,16 @@ def parser_body(prototype, *fields, declarations=''): goto %s; }} """ % add_label, indent=4)) + if p.deprecated_positional: + parser_code.append(normalize_snippet(""" + if (nargs == %s) {{ + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "Using '%s' as positional argument is deprecated", 2)) + {{ + goto exit; + }} + }} + """ % (str(i+1), p.name), indent=4)) if i + 1 == len(parameters): parser_code.append(normalize_snippet(parsearg, indent=4)) else: @@ -4017,6 +4027,7 @@ def reset(self): self.parameter_indent = None self.keyword_only = False self.positional_only = False + self.deprecated_positional = False self.group = 0 self.parameter_state = self.ps_start self.seen_positional_with_default = False @@ -4457,7 +4468,7 @@ def state_parameter(self, line): line = line.lstrip() - if line in ('*', '/', '[', ']'): + if line in ('x', '*', '/', '[', ']'): self.parse_special_symbol(line) return @@ -4698,6 +4709,7 @@ def bad_node(self, node): p = Parameter(parameter_name, kind, function=self.function, converter=converter, default=value, group=self.group) + p.deprecated_positional = self.deprecated_positional names = [k.name for k in self.function.parameters.values()] if parameter_name in names[1:]: @@ -4730,6 +4742,12 @@ def parse_converter(self, annotation): return name, False, kwargs def parse_special_symbol(self, symbol): + if symbol == 'x': + if self.keyword_only: + fail("Function " + self.function.name + ": 'x' must come before '*'") + if self.deprecated_positional: + fail("Function " + self.function.name + " uses 'x' more than once.") + self.deprecated_positional = True if symbol == '*': if self.keyword_only: fail("Function " + self.function.name + " uses '*' more than once.") @@ -5095,6 +5113,8 @@ def state_terminal(self, line): if no_parameter_after_star: fail("Function " + self.function.name + " specifies '*' without any parameters afterwards.") + # EAA: Add check here + # remove trailing whitespace from all parameter docstrings for name, value in self.function.parameters.items(): if not value: From 6a8c14fe209a05b6fe701395c9938c8c90fa1c4f Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 23 Jul 2022 00:12:56 +0200 Subject: [PATCH 02/71] Add tests --- Lib/test/clinic.test | 62 +++++++++++++++++++++++++++++++++++++++++ Lib/test/test_clinic.py | 19 +++++++++++++ Tools/clinic/clinic.py | 24 ++++++++++------ 3 files changed, 96 insertions(+), 9 deletions(-) diff --git a/Lib/test/clinic.test b/Lib/test/clinic.test index e981b5a28504ef..5df3633e97a146 100644 --- a/Lib/test/clinic.test +++ b/Lib/test/clinic.test @@ -3559,3 +3559,65 @@ exit: static PyObject * test_paramname_module_impl(PyObject *module, PyObject *mod) /*[clinic end generated code: output=23379a7ffa65c514 input=afefe259667f13ba]*/ + +/*[clinic input] +test_deprecate_positional_use + + pos: object + x + optarg: int = 5 +[clinic start generated code]*/ + +PyDoc_STRVAR(test_deprecate_positional_use__doc__, +"test_deprecate_positional_use($module, /, pos, optarg=5)\n" +"--\n" +"\n"); + +#define TEST_DEPRECATE_POSITIONAL_USE_METHODDEF \ + {"test_deprecate_positional_use", _PyCFunction_CAST(test_deprecate_positional_use), METH_FASTCALL|METH_KEYWORDS, test_deprecate_positional_use__doc__}, + +static PyObject * +test_deprecate_positional_use_impl(PyObject *module, PyObject *pos, + int optarg); + +static PyObject * +test_deprecate_positional_use(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + static const char * const _keywords[] = {"pos", "optarg", NULL}; + static _PyArg_Parser _parser = {NULL, _keywords, "test_deprecate_positional_use", 0}; + PyObject *argsbuf[2]; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1; + PyObject *pos; + int optarg = 5; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 2, 0, argsbuf); + if (!args) { + goto exit; + } + pos = args[0]; + if (!noptargs) { + goto skip_optional_pos; + } + if (nargs == 2) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "Using 'optarg' as positional argument is deprecated", 2)) + { + goto exit; + } + } + optarg = _PyLong_AsInt(args[1]); + if (optarg == -1 && PyErr_Occurred()) { + goto exit; + } +skip_optional_pos: + return_value = test_deprecate_positional_use_impl(module, pos, optarg); + +exit: + return return_value; +} + +static PyObject * +test_deprecate_positional_use_impl(PyObject *module, PyObject *pos, + int optarg) +/*[clinic end generated code: output=fde7240aec7013a3 input=462e9741eb865665]*/ diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index 4aa9691a4829d1..d9eb1977cbab1f 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -701,6 +701,25 @@ def test_parameters_required_after_star_with_initial_parameters_and_docstring(se this: int * Docstring. +""") + + def test_parameters_required_after_cross(self): + self.parse_function_should_fail(""" +module foo +foo.bar + this: int + x +Docstring. +""") + + def test_cross_must_come_before_star(self): + self.parse_function_should_fail(""" +module foo +foo.bar + this: int + * + x +Docstring. """) def test_single_slash(self): diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 161ce5aef33634..d48172340417d3 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -2382,7 +2382,7 @@ class Parameter: def __init__(self, name, kind, *, default=inspect.Parameter.empty, function, converter, annotation=inspect.Parameter.empty, - docstring=None, group=0): + docstring=None, group=0, deprecated_positional=False): self.name = name self.kind = kind self.default = default @@ -2391,6 +2391,7 @@ def __init__(self, name, kind, *, default=inspect.Parameter.empty, self.annotation = annotation self.docstring = docstring or '' self.group = group + self.deprecated_positional = deprecated_positional def __repr__(self): return '' @@ -4708,8 +4709,9 @@ def bad_node(self, node): fail("A 'defining_class' parameter, if specified, must either be the first thing in the parameter block, or come just after 'self'.") - p = Parameter(parameter_name, kind, function=self.function, converter=converter, default=value, group=self.group) - p.deprecated_positional = self.deprecated_positional + p = Parameter(parameter_name, kind, function=self.function, + converter=converter, default=value, group=self.group, + deprecated_positional=self.deprecated_positional) names = [k.name for k in self.function.parameters.values()] if parameter_name in names[1:]: @@ -5103,17 +5105,21 @@ def state_terminal(self, line): if not self.function: return - if self.keyword_only: + def check_remaining_params(symbol, condition): values = self.function.parameters.values() if not values: - no_parameter_after_star = True + no_param_after_symbol = True else: last_parameter = next(reversed(list(values))) - no_parameter_after_star = last_parameter.kind != inspect.Parameter.KEYWORD_ONLY - if no_parameter_after_star: - fail("Function " + self.function.name + " specifies '*' without any parameters afterwards.") + no_param_after_symbol = condition(last_parameter) + if no_param_after_symbol: + fail(f"Function {self.function.name} specifies {symbol} without any parameters afterwards.") + + if self.keyword_only: + check_remaining_params("*", lambda p: p.kind != inspect.Parameter.KEYWORD_ONLY) - # EAA: Add check here + if self.deprecated_positional: + check_remaining_params("+", lambda p: not p.deprecated_positional) # remove trailing whitespace from all parameter docstrings for name, value in self.function.parameters.items(): From 457d38a1cc626224d4cd9f9175cec34eee27f1d9 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 23 Jul 2022 00:32:29 +0200 Subject: [PATCH 03/71] Improve warning wording --- Lib/test/clinic.test | 4 ++-- Tools/clinic/clinic.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/test/clinic.test b/Lib/test/clinic.test index 5df3633e97a146..05cb01311d82c1 100644 --- a/Lib/test/clinic.test +++ b/Lib/test/clinic.test @@ -3601,7 +3601,7 @@ test_deprecate_positional_use(PyObject *module, PyObject *const *args, Py_ssize_ } if (nargs == 2) { if (PyErr_WarnEx(PyExc_DeprecationWarning, - "Using 'optarg' as positional argument is deprecated", 2)) + "Using 'optarg' as a positional argument is deprecated", 2)) { goto exit; } @@ -3620,4 +3620,4 @@ exit: static PyObject * test_deprecate_positional_use_impl(PyObject *module, PyObject *pos, int optarg) -/*[clinic end generated code: output=fde7240aec7013a3 input=462e9741eb865665]*/ +/*[clinic end generated code: output=dc51108a995e63a0 input=462e9741eb865665]*/ diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index d48172340417d3..b1bba38371f2fd 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -1054,7 +1054,7 @@ def parser_body(prototype, *fields, declarations=''): parser_code.append(normalize_snippet(""" if (nargs == %s) {{ if (PyErr_WarnEx(PyExc_DeprecationWarning, - "Using '%s' as positional argument is deprecated", 2)) + "Using '%s' as a positional argument is deprecated", 2)) {{ goto exit; }} From 3687748a3c02056c291331280f5098c50bb73663 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 23 Jul 2022 00:33:34 +0200 Subject: [PATCH 04/71] Add NEWS --- .../Tools-Demos/2022-07-23-00-33-28.gh-issue-95065.NfCCpp.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Tools-Demos/2022-07-23-00-33-28.gh-issue-95065.NfCCpp.rst diff --git a/Misc/NEWS.d/next/Tools-Demos/2022-07-23-00-33-28.gh-issue-95065.NfCCpp.rst b/Misc/NEWS.d/next/Tools-Demos/2022-07-23-00-33-28.gh-issue-95065.NfCCpp.rst new file mode 100644 index 00000000000000..63d65fba772632 --- /dev/null +++ b/Misc/NEWS.d/next/Tools-Demos/2022-07-23-00-33-28.gh-issue-95065.NfCCpp.rst @@ -0,0 +1,2 @@ +Add Argument Clinic support for deprecating positional use of optional +parameters. Patch by Erlend E. Aasland. From 6bde63dd81d52d815d096a5d6988cb9f1b25ae6e Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 3 Jul 2023 22:51:03 +0200 Subject: [PATCH 05/71] Add annotation --- Tools/clinic/clinic.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index bb12cd3dc7af18..9b35f3843d0bfd 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -4311,6 +4311,7 @@ class DSLParser: state: StateKeeper keyword_only: bool positional_only: bool + deprecated_positional: bool group: int parameter_state: int seen_positional_with_default: bool From d85475e6ad647b4d7c45497ed56427afaf540dcb Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Tue, 4 Jul 2023 00:44:05 +0200 Subject: [PATCH 06/71] Amend tests --- Lib/test/test_clinic.py | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index cb72b28eda8963..ab04181b7fefc3 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -786,23 +786,27 @@ def test_parameters_required_after_star_with_initial_parameters_and_docstring(se """) def test_parameters_required_after_cross(self): - self.parse_function_should_fail(""" -module foo -foo.bar - this: int - x -Docstring. -""") + out = self.parse_function_should_fail(""" + module foo + foo.bar + this: int + x + Docstring. + """) + msg = "Function bar specifies + without any parameters afterwards." + self.assertIn(msg, out) def test_cross_must_come_before_star(self): - self.parse_function_should_fail(""" -module foo -foo.bar - this: int - * - x -Docstring. -""") + out = self.parse_function_should_fail(""" + module foo + foo.bar + this: int + * + x + Docstring. + """) + msg = "Function bar: 'x' must come before '*'" + self.assertIn(msg, out) def test_single_slash(self): self.parse_function_should_fail(""" From ca496b41e362eb240787d71a27997fbf49a2724c Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Tue, 4 Jul 2023 00:52:45 +0200 Subject: [PATCH 07/71] Amend test --- Lib/test/clinic.test | 166 +++++++++++++++++++++---------------------- 1 file changed, 83 insertions(+), 83 deletions(-) diff --git a/Lib/test/clinic.test b/Lib/test/clinic.test index ac6abc029761d7..d868ca95ce1348 100644 --- a/Lib/test/clinic.test +++ b/Lib/test/clinic.test @@ -4267,86 +4267,86 @@ mangle2_impl(PyObject *module, PyObject *args, PyObject *kwargs, /*[clinic end generated code: output=2ebb62aaefe7590a input=391766fee51bad7a]*/ /*[clinic input] - test_deprecate_positional_use - - pos: object - x - optarg: int = 5 - [clinic start generated code]*/ - - PyDoc_STRVAR(test_deprecate_positional_use__doc__, - "test_deprecate_positional_use($module, /, pos, optarg=5)\n" - "--\n" - "\n"); - - #define TEST_DEPRECATE_POSITIONAL_USE_METHODDEF \ - {"test_deprecate_positional_use", _PyCFunction_CAST(test_deprecate_positional_use), METH_FASTCALL|METH_KEYWORDS, test_deprecate_positional_use__doc__}, - - static PyObject * - test_deprecate_positional_use_impl(PyObject *module, PyObject *pos, - int optarg); - - static PyObject * - test_deprecate_positional_use(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) - { - PyObject *return_value = NULL; - #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) - - #define NUM_KEYWORDS 2 - static struct { - PyGC_Head _this_is_not_used; - PyObject_VAR_HEAD - PyObject *ob_item[NUM_KEYWORDS]; - } _kwtuple = { - .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) - .ob_item = { &_Py_ID(pos), &_Py_ID(optarg), }, - }; - #undef NUM_KEYWORDS - #define KWTUPLE (&_kwtuple.ob_base.ob_base) - - #else // !Py_BUILD_CORE - # define KWTUPLE NULL - #endif // !Py_BUILD_CORE - - static const char * const _keywords[] = {"pos", "optarg", NULL}; - static _PyArg_Parser _parser = { - .keywords = _keywords, - .fname = "test_deprecate_positional_use", - .kwtuple = KWTUPLE, - }; - #undef KWTUPLE - PyObject *argsbuf[2]; - Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1; - PyObject *pos; - int optarg = 5; - - args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 2, 0, argsbuf); - if (!args) { - goto exit; - } - pos = args[0]; - if (!noptargs) { - goto skip_optional_pos; - } - if (nargs == 2) { - if (PyErr_WarnEx(PyExc_DeprecationWarning, - "Using 'optarg' as a positional argument is deprecated", 2)) - { - goto exit; - } - } - optarg = _PyLong_AsInt(args[1]); - if (optarg == -1 && PyErr_Occurred()) { - goto exit; - } - skip_optional_pos: - return_value = test_deprecate_positional_use_impl(module, pos, optarg); - - exit: - return return_value; - } - - static PyObject * - test_deprecate_positional_use_impl(PyObject *module, PyObject *pos, - int optarg) - /*[clinic end generated code: output=b47aecf1c5245c0e input=462e9741eb865665]*/ +test_deprecate_positional_use + + pos: object + x + optarg: int = 5 +[clinic start generated code]*/ + +PyDoc_STRVAR(test_deprecate_positional_use__doc__, +"test_deprecate_positional_use($module, /, pos, optarg=5)\n" +"--\n" +"\n"); + +#define TEST_DEPRECATE_POSITIONAL_USE_METHODDEF \ + {"test_deprecate_positional_use", _PyCFunction_CAST(test_deprecate_positional_use), METH_FASTCALL|METH_KEYWORDS, test_deprecate_positional_use__doc__}, + +static PyObject * +test_deprecate_positional_use_impl(PyObject *module, PyObject *pos, + int optarg); + +static PyObject * +test_deprecate_positional_use(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 2 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(pos), &_Py_ID(optarg), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"pos", "optarg", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "test_deprecate_positional_use", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[2]; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1; + PyObject *pos; + int optarg = 5; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 2, 0, argsbuf); + if (!args) { + goto exit; + } + pos = args[0]; + if (!noptargs) { + goto skip_optional_pos; + } + if (nargs == 2) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "Using 'optarg' as a positional argument is deprecated", 2)) + { + goto exit; + } + } + optarg = _PyLong_AsInt(args[1]); + if (optarg == -1 && PyErr_Occurred()) { + goto exit; + } +skip_optional_pos: + return_value = test_deprecate_positional_use_impl(module, pos, optarg); + +exit: + return return_value; +} + +static PyObject * +test_deprecate_positional_use_impl(PyObject *module, PyObject *pos, + int optarg) +/*[clinic end generated code: output=b47aecf1c5245c0e input=462e9741eb865665]*/ From fe77d5f55c96717accf8a7d2b44782c7f8de9b83 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Tue, 4 Jul 2023 00:52:49 +0200 Subject: [PATCH 08/71] Annotate --- Tools/clinic/clinic.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 60df4c046b1f8d..f4a9da3f6af3fc 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -5104,7 +5104,9 @@ def parse_converter( "Annotations must be either a name, a function call, or a string." ) - def parse_special_symbol(self, symbol): + def parse_special_symbol(self, symbol: str) -> None: + assert isinstance(self.function, Function) + if symbol == 'x': if self.keyword_only: fail("Function " + self.function.name + ": 'x' must come before '*'") @@ -5474,7 +5476,12 @@ def state_terminal(self, line): if not self.function: return - def check_remaining_params(symbol, condition): + def check_remaining_params( + symbol: str, + condition: Callable[[Parameter], bool] + ) -> None: + assert isinstance(self.function, Function) + values = self.function.parameters.values() if not values: no_param_after_symbol = True From a149a7b98c3ea0369d2be38b7580d8bb8caccdaf Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 15 Jul 2023 00:40:07 +0200 Subject: [PATCH 09/71] Fix tests and repr --- Lib/test/test_clinic.py | 2 +- Tools/clinic/clinic.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index 90178ce2fd43e9..49bbe1ebdc59a8 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -815,7 +815,7 @@ def test_parameters_required_after_cross(self): x Docstring. """) - msg = "Function bar specifies + without any parameters afterwards." + msg = "Function bar specifies '*' without any parameters afterwards." self.assertIn(msg, out) def test_cross_must_come_before_star(self): diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 4f3bb0a8f12a6f..d1f5f3653ab7b0 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -5519,13 +5519,13 @@ def check_remaining_params( last_parameter = next(reversed(list(values))) no_param_after_symbol = condition(last_parameter) if no_param_after_symbol: - fail(f"Function {self.function.name} specifies {symbol} without any parameters afterwards.") + fail(f"Function {self.function.name} specifies {symbol!r} without any parameters afterwards.") if self.keyword_only: check_remaining_params("*", lambda p: p.kind != inspect.Parameter.KEYWORD_ONLY) if self.deprecated_positional: - check_remaining_params("+", lambda p: not p.deprecated_positional) + check_remaining_params("*", lambda p: not p.deprecated_positional) # remove trailing whitespace from all parameter docstrings for name, value in self.function.parameters.items(): From e9ad2116189d32e2df01406b2ec6030e63b20edc Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 15 Jul 2023 01:25:10 +0200 Subject: [PATCH 10/71] Get ready for '* [from ...]' syntax --- Lib/test/clinic.test.c | 4 ++-- Lib/test/test_clinic.py | 12 ++++++------ Tools/clinic/clinic.py | 28 ++++++++++++++++++++-------- 3 files changed, 28 insertions(+), 16 deletions(-) diff --git a/Lib/test/clinic.test.c b/Lib/test/clinic.test.c index fd2919515a3296..af8b8d7999fb7b 100644 --- a/Lib/test/clinic.test.c +++ b/Lib/test/clinic.test.c @@ -4824,7 +4824,7 @@ Test_meth_coexist_impl(TestObj *self) test_deprecate_positional_use pos: object - x + * [from 3.14] optarg: int = 5 [clinic start generated code]*/ @@ -4903,7 +4903,7 @@ test_deprecate_positional_use(PyObject *module, PyObject *const *args, Py_ssize_ static PyObject * test_deprecate_positional_use_impl(PyObject *module, PyObject *pos, int optarg) -/*[clinic end generated code: output=b47aecf1c5245c0e input=462e9741eb865665]*/ +/*[clinic end generated code: output=b47aecf1c5245c0e input=ab63c6aed293eb31]*/ PyDoc_STRVAR(test_deprecate_positional_use__doc__, "test_deprecate_positional_use($module, /, pos, optarg=5)\n" diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index 49bbe1ebdc59a8..b15c163f063080 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -807,27 +807,27 @@ def test_parameters_required_after_star(self): out = self.parse_function_should_fail(block) self.assertIn(msg, out) - def test_parameters_required_after_cross(self): + def test_parameters_required_after_depr_star(self): out = self.parse_function_should_fail(""" module foo foo.bar this: int - x + * [from 3.14] Docstring. """) - msg = "Function bar specifies '*' without any parameters afterwards." + msg = "Function bar specifies '* [from ...]' without any parameters afterwards." self.assertIn(msg, out) - def test_cross_must_come_before_star(self): + def test_depr_star_must_come_before_star(self): out = self.parse_function_should_fail(""" module foo foo.bar this: int * - x + * [from 3.14] Docstring. """) - msg = "Function bar: 'x' must come before '*'" + msg = "Function bar: '* [from ...]' must come before '*'" self.assertIn(msg, out) def test_single_slash(self): diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index d1f5f3653ab7b0..cd9b8de091a75f 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -4378,6 +4378,11 @@ class DSLParser: coexist: bool parameter_continuation: str preserve_output: bool + deprecate_posonly_re = create_regex( + before="* [from ", + after="]", + word=False, + ) def __init__(self, clinic: Clinic) -> None: self.clinic = clinic @@ -4852,7 +4857,11 @@ def state_parameter(self, line: str | None) -> None: line = line.lstrip() - if line in ('x', '*', '/', '[', ']'): + if match := self.deprecate_posonly_re.match(line): + self.parse_deprecated_positional(match.group(1)) + return + + if line in ('*', '/', '[', ']'): self.parse_special_symbol(line) return @@ -5134,15 +5143,18 @@ def parse_converter( "Annotations must be either a name, a function call, or a string." ) + def parse_deprecated_positional(self, arg: str): + assert isinstance(self.function, Function) + + if self.keyword_only: + fail(f"Function {self.function.name}: '* [from ...]' must come before '*'") + if self.deprecated_positional: + fail(f"Function {self.function.name} uses 'x' more than once.") + self.deprecated_positional = True + def parse_special_symbol(self, symbol: str) -> None: assert isinstance(self.function, Function) - if symbol == 'x': - if self.keyword_only: - fail("Function " + self.function.name + ": 'x' must come before '*'") - if self.deprecated_positional: - fail("Function " + self.function.name + " uses 'x' more than once.") - self.deprecated_positional = True if symbol == '*': if self.keyword_only: fail("Function " + self.function.name + " uses '*' more than once.") @@ -5525,7 +5537,7 @@ def check_remaining_params( check_remaining_params("*", lambda p: p.kind != inspect.Parameter.KEYWORD_ONLY) if self.deprecated_positional: - check_remaining_params("*", lambda p: not p.deprecated_positional) + check_remaining_params("* [from ...]", lambda p: not p.deprecated_positional) # remove trailing whitespace from all parameter docstrings for name, value in self.function.parameters.items(): From 0fcad5819f2e25b55b3af7e1ba1fa5ef94e19eee Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 15 Jul 2023 01:39:40 +0200 Subject: [PATCH 11/71] Add 'whence' to deprecation warning message --- Lib/test/clinic.test.c | 4 ++-- Tools/clinic/clinic.py | 19 ++++++++++++------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/Lib/test/clinic.test.c b/Lib/test/clinic.test.c index af8b8d7999fb7b..22ed1292ecb9c0 100644 --- a/Lib/test/clinic.test.c +++ b/Lib/test/clinic.test.c @@ -4884,7 +4884,7 @@ test_deprecate_positional_use(PyObject *module, PyObject *const *args, Py_ssize_ } if (nargs == 2) { if (PyErr_WarnEx(PyExc_DeprecationWarning, - "Using 'optarg' as a positional argument is deprecated", 2)) + "Using 'optarg' as a positional argument is deprecated. It will become a keyword-only argument in Python 3.14.", 2)) { goto exit; } @@ -4903,7 +4903,7 @@ test_deprecate_positional_use(PyObject *module, PyObject *const *args, Py_ssize_ static PyObject * test_deprecate_positional_use_impl(PyObject *module, PyObject *pos, int optarg) -/*[clinic end generated code: output=b47aecf1c5245c0e input=ab63c6aed293eb31]*/ +/*[clinic end generated code: output=965cb550b49aa7eb input=ab63c6aed293eb31]*/ PyDoc_STRVAR(test_deprecate_positional_use__doc__, "test_deprecate_positional_use($module, /, pos, optarg=5)\n" diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index cd9b8de091a75f..f64f4dabc0c4c9 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -1214,15 +1214,20 @@ def parser_body( }} """ % add_label, indent=4)) if p.deprecated_positional: + whence = p.deprecated_positional + msg = ( + f"Using {p.name!r} as a positional argument is deprecated. " + f"It will become a keyword-only argument in Python {whence}." + ) parser_code.append(normalize_snippet(""" if (nargs == %s) {{ if (PyErr_WarnEx(PyExc_DeprecationWarning, - "Using '%s' as a positional argument is deprecated", 2)) + "%s", 2)) {{ goto exit; }} }} - """ % (str(i+1), p.name), indent=4)) + """ % (str(i+1), msg), indent=4)) if i + 1 == len(parameters): parser_code.append(normalize_snippet(parsearg, indent=4)) else: @@ -2572,7 +2577,7 @@ class Parameter: annotation: object = inspect.Parameter.empty docstring: str = '' group: int = 0 - deprecated_positional: bool = False + deprecated_positional: str = "" right_bracket_count: int = dc.field(init=False, default=0) def __repr__(self) -> str: @@ -4369,7 +4374,7 @@ class DSLParser: state: StateKeeper keyword_only: bool positional_only: bool - deprecated_positional: bool + deprecated_positional: str group: int parameter_state: int seen_positional_with_default: bool @@ -4406,7 +4411,7 @@ def reset(self) -> None: self.state = self.state_dsl_start self.keyword_only = False self.positional_only = False - self.deprecated_positional = False + self.deprecated_positional = "" self.group = 0 self.parameter_state: ParamState = ParamState.START self.seen_positional_with_default = False @@ -5143,14 +5148,14 @@ def parse_converter( "Annotations must be either a name, a function call, or a string." ) - def parse_deprecated_positional(self, arg: str): + def parse_deprecated_positional(self, whence: str): assert isinstance(self.function, Function) if self.keyword_only: fail(f"Function {self.function.name}: '* [from ...]' must come before '*'") if self.deprecated_positional: fail(f"Function {self.function.name} uses 'x' more than once.") - self.deprecated_positional = True + self.deprecated_positional = whence def parse_special_symbol(self, symbol: str) -> None: assert isinstance(self.function, Function) From efb1fba8d0379350ee04dd96d3a0294b9ed46956 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 15 Jul 2023 01:50:16 +0200 Subject: [PATCH 12/71] PoC --- Lib/test/clinic.test.c | 6 +++++- Tools/clinic/clinic.py | 19 ++++++++++++------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/Lib/test/clinic.test.c b/Lib/test/clinic.test.c index 22ed1292ecb9c0..e8cffd2c47cff4 100644 --- a/Lib/test/clinic.test.c +++ b/Lib/test/clinic.test.c @@ -4882,6 +4882,10 @@ test_deprecate_positional_use(PyObject *module, PyObject *const *args, Py_ssize_ if (!noptargs) { goto skip_optional_pos; } + #if (PY_MAJOR_VERSION > 3) || + (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 14) + # error "Convert 'optarg' to a keyword-argument" + #endif if (nargs == 2) { if (PyErr_WarnEx(PyExc_DeprecationWarning, "Using 'optarg' as a positional argument is deprecated. It will become a keyword-only argument in Python 3.14.", 2)) @@ -4903,7 +4907,7 @@ test_deprecate_positional_use(PyObject *module, PyObject *const *args, Py_ssize_ static PyObject * test_deprecate_positional_use_impl(PyObject *module, PyObject *pos, int optarg) -/*[clinic end generated code: output=965cb550b49aa7eb input=ab63c6aed293eb31]*/ +/*[clinic end generated code: output=9702577e0b5804e8 input=ab63c6aed293eb31]*/ PyDoc_STRVAR(test_deprecate_positional_use__doc__, "test_deprecate_positional_use($module, /, pos, optarg=5)\n" diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index f64f4dabc0c4c9..8ec368623997cd 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -1215,19 +1215,24 @@ def parser_body( """ % add_label, indent=4)) if p.deprecated_positional: whence = p.deprecated_positional + major, minor = whence.split(".") # FIXME: validate format during parsing msg = ( f"Using {p.name!r} as a positional argument is deprecated. " f"It will become a keyword-only argument in Python {whence}." ) - parser_code.append(normalize_snippet(""" - if (nargs == %s) {{ + parser_code.append(normalize_snippet(f""" + #if (PY_MAJOR_VERSION > {major}) || + (PY_MAJOR_VERSION == {major} && PY_MINOR_VERSION >= {minor}) + # error "Convert '{p.name}' to a keyword-argument" + #endif + if (nargs == {i+1}) {{{{ if (PyErr_WarnEx(PyExc_DeprecationWarning, - "%s", 2)) - {{ + "{msg}", 2)) + {{{{ goto exit; - }} - }} - """ % (str(i+1), msg), indent=4)) + }}}} + }}}} + """, indent=4)) if i + 1 == len(parameters): parser_code.append(normalize_snippet(parsearg, indent=4)) else: From 43cec6cde95568038a852e568f4bf1a10ae7b26d Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 15 Jul 2023 10:52:45 +0200 Subject: [PATCH 13/71] Improve preprocessor warning message, and fix it on windows --- Lib/test/clinic.test.c | 10 +++++++--- Tools/clinic/clinic.py | 18 +++++++++++++----- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/Lib/test/clinic.test.c b/Lib/test/clinic.test.c index e8cffd2c47cff4..c9d984b4dbec13 100644 --- a/Lib/test/clinic.test.c +++ b/Lib/test/clinic.test.c @@ -4882,9 +4882,13 @@ test_deprecate_positional_use(PyObject *module, PyObject *const *args, Py_ssize_ if (!noptargs) { goto skip_optional_pos; } - #if (PY_MAJOR_VERSION > 3) || + #if (PY_MAJOR_VERSION > 3) || \ (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 14) - # error "Convert 'optarg' to a keyword-argument" + # ifdef _MSC_VER + # pragma message ("Using 'optarg' as a positional argument is now disallowed. Please update the clinic code in 'Lib/test/clinic.test.c'.") + # else + # warning "Using 'optarg' as a positional argument is now disallowed. Please update the clinic code in 'Lib/test/clinic.test.c'." + # endif #endif if (nargs == 2) { if (PyErr_WarnEx(PyExc_DeprecationWarning, @@ -4907,7 +4911,7 @@ test_deprecate_positional_use(PyObject *module, PyObject *const *args, Py_ssize_ static PyObject * test_deprecate_positional_use_impl(PyObject *module, PyObject *pos, int optarg) -/*[clinic end generated code: output=9702577e0b5804e8 input=ab63c6aed293eb31]*/ +/*[clinic end generated code: output=8b8ea55feb831cb9 input=ab63c6aed293eb31]*/ PyDoc_STRVAR(test_deprecate_positional_use__doc__, "test_deprecate_positional_use($module, /, pos, optarg=5)\n" diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 8ec368623997cd..ad596519f2a383 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -1216,18 +1216,26 @@ def parser_body( if p.deprecated_positional: whence = p.deprecated_positional major, minor = whence.split(".") # FIXME: validate format during parsing - msg = ( + deprecation_message = ( f"Using {p.name!r} as a positional argument is deprecated. " f"It will become a keyword-only argument in Python {whence}." ) - parser_code.append(normalize_snippet(f""" - #if (PY_MAJOR_VERSION > {major}) || + cpp_warning = ( + f"Using {p.name!r} as a positional argument is now disallowed. " + f"Please update the clinic code in {self.cpp.filename!r}." + ) + parser_code.append(normalize_snippet(fr""" + #if (PY_MAJOR_VERSION > {major}) || \ (PY_MAJOR_VERSION == {major} && PY_MINOR_VERSION >= {minor}) - # error "Convert '{p.name}' to a keyword-argument" + # ifdef _MSC_VER + # pragma message ("{cpp_warning}") + # else + # warning "{cpp_warning}" + # endif #endif if (nargs == {i+1}) {{{{ if (PyErr_WarnEx(PyExc_DeprecationWarning, - "{msg}", 2)) + "{deprecation_message}", 2)) {{{{ goto exit; }}}} From 075cf6778e4f8020b8caa9349eb2e6d5504dc258 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 15 Jul 2023 11:02:00 +0200 Subject: [PATCH 14/71] Nits --- Lib/test/clinic.test.c | 9 +++++---- Tools/clinic/clinic.py | 12 +++++------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/Lib/test/clinic.test.c b/Lib/test/clinic.test.c index c9d984b4dbec13..2e65bfc93d4673 100644 --- a/Lib/test/clinic.test.c +++ b/Lib/test/clinic.test.c @@ -4885,14 +4885,15 @@ test_deprecate_positional_use(PyObject *module, PyObject *const *args, Py_ssize_ #if (PY_MAJOR_VERSION > 3) || \ (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 14) # ifdef _MSC_VER - # pragma message ("Using 'optarg' as a positional argument is now disallowed. Please update the clinic code in 'Lib/test/clinic.test.c'.") + # pragma message ("Update 'optarg' in 'test_deprecate_positional_use' in 'Lib/test/clinic.test.c' to be keyword-only.") # else - # warning "Using 'optarg' as a positional argument is now disallowed. Please update the clinic code in 'Lib/test/clinic.test.c'." + # warning "Update 'optarg' in 'test_deprecate_positional_use' in 'Lib/test/clinic.test.c' to be keyword-only." # endif #endif if (nargs == 2) { if (PyErr_WarnEx(PyExc_DeprecationWarning, - "Using 'optarg' as a positional argument is deprecated. It will become a keyword-only argument in Python 3.14.", 2)) + "Using 'optarg' as a positional argument is deprecated. " + "It will become a keyword-only argument in Python 3.14.", 2)) { goto exit; } @@ -4911,7 +4912,7 @@ test_deprecate_positional_use(PyObject *module, PyObject *const *args, Py_ssize_ static PyObject * test_deprecate_positional_use_impl(PyObject *module, PyObject *pos, int optarg) -/*[clinic end generated code: output=8b8ea55feb831cb9 input=ab63c6aed293eb31]*/ +/*[clinic end generated code: output=6a47ac0d4ecb9de6 input=ab63c6aed293eb31]*/ PyDoc_STRVAR(test_deprecate_positional_use__doc__, "test_deprecate_positional_use($module, /, pos, optarg=5)\n" diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index ad596519f2a383..ecd642a01eff16 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -1214,15 +1214,12 @@ def parser_body( }} """ % add_label, indent=4)) if p.deprecated_positional: + arg = p.name whence = p.deprecated_positional major, minor = whence.split(".") # FIXME: validate format during parsing - deprecation_message = ( - f"Using {p.name!r} as a positional argument is deprecated. " - f"It will become a keyword-only argument in Python {whence}." - ) + source = self.cpp.filename cpp_warning = ( - f"Using {p.name!r} as a positional argument is now disallowed. " - f"Please update the clinic code in {self.cpp.filename!r}." + f"Update {p.name!r} in {f.name!r} in {source!r} to be keyword-only." ) parser_code.append(normalize_snippet(fr""" #if (PY_MAJOR_VERSION > {major}) || \ @@ -1235,7 +1232,8 @@ def parser_body( #endif if (nargs == {i+1}) {{{{ if (PyErr_WarnEx(PyExc_DeprecationWarning, - "{deprecation_message}", 2)) + "Using {p.name!r} as a positional argument is deprecated. " + "It will become a keyword-only argument in Python {whence}.", 2)) {{{{ goto exit; }}}} From 13104e8e4588946040da98893f8709080ddc6449 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 15 Jul 2023 11:20:42 +0200 Subject: [PATCH 15/71] Assert format --- Lib/test/clinic.test.c | 6 +++--- Lib/test/test_clinic.py | 32 +++++++++++++++++++++++++++++++- Tools/clinic/clinic.py | 20 ++++++++++++++++---- 3 files changed, 50 insertions(+), 8 deletions(-) diff --git a/Lib/test/clinic.test.c b/Lib/test/clinic.test.c index 2e65bfc93d4673..55ed624d8cbba3 100644 --- a/Lib/test/clinic.test.c +++ b/Lib/test/clinic.test.c @@ -4885,9 +4885,9 @@ test_deprecate_positional_use(PyObject *module, PyObject *const *args, Py_ssize_ #if (PY_MAJOR_VERSION > 3) || \ (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 14) # ifdef _MSC_VER - # pragma message ("Update 'optarg' in 'test_deprecate_positional_use' in 'Lib/test/clinic.test.c' to be keyword-only.") + # pragma message ("Update 'optarg' in 'test_deprecate_positional_use' in 'clinic.test.c' to be keyword-only.") # else - # warning "Update 'optarg' in 'test_deprecate_positional_use' in 'Lib/test/clinic.test.c' to be keyword-only." + # warning "Update 'optarg' in 'test_deprecate_positional_use' in 'clinic.test.c' to be keyword-only." # endif #endif if (nargs == 2) { @@ -4912,7 +4912,7 @@ test_deprecate_positional_use(PyObject *module, PyObject *const *args, Py_ssize_ static PyObject * test_deprecate_positional_use_impl(PyObject *module, PyObject *pos, int optarg) -/*[clinic end generated code: output=6a47ac0d4ecb9de6 input=ab63c6aed293eb31]*/ +/*[clinic end generated code: output=1bdec2a2f3c30de0 input=ab63c6aed293eb31]*/ PyDoc_STRVAR(test_deprecate_positional_use__doc__, "test_deprecate_positional_use($module, /, pos, optarg=5)\n" diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index b15c163f063080..bcecfa0a4022a4 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -807,6 +807,36 @@ def test_parameters_required_after_star(self): out = self.parse_function_should_fail(block) self.assertIn(msg, out) + def test_depr_star_invalid_format_1(self): + out = self.parse_function_should_fail(""" + module foo + foo.bar + this: int + * [from 3] + Docstring. + """) + expected = ( + "Error on line 0:\n" + "Function 'bar': '* [from ...]' format expected to be " + "'', where 'major' and 'minor' are digits.\n" + ) + self.assertEqual(out, expected) + + def test_depr_star_invalid_format_2(self): + out = self.parse_function_should_fail(""" + module foo + foo.bar + this: int + * [from a.b] + Docstring. + """) + expected = ( + "Error on line 0:\n" + "Function 'bar', '* [from ]': " + "'major' and 'minor' must be digits, not 'a' and 'b'\n" + ) + self.assertEqual(out, expected) + def test_parameters_required_after_depr_star(self): out = self.parse_function_should_fail(""" module foo @@ -827,7 +857,7 @@ def test_depr_star_must_come_before_star(self): * [from 3.14] Docstring. """) - msg = "Function bar: '* [from ...]' must come before '*'" + msg = "Function 'bar': '* [from ...]' must come before '*'" self.assertIn(msg, out) def test_single_slash(self): diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index ecd642a01eff16..09185da9ca861b 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -1216,8 +1216,8 @@ def parser_body( if p.deprecated_positional: arg = p.name whence = p.deprecated_positional - major, minor = whence.split(".") # FIXME: validate format during parsing - source = self.cpp.filename + major, minor = whence.split(".") + source = os.path.basename(self.cpp.filename) cpp_warning = ( f"Update {p.name!r} in {f.name!r} in {source!r} to be keyword-only." ) @@ -5162,10 +5162,22 @@ def parse_converter( def parse_deprecated_positional(self, whence: str): assert isinstance(self.function, Function) + if "." not in whence: + fail( + f"Function {self.function.name!r}: '* [from ...]' format " + "expected to be '', where 'major' and 'minor' " + "are digits." + ) + major, minor = whence.split(".") + if not major.isdigit() or not minor.isdigit(): + fail( + f"Function {self.function.name!r}, '* [from ]': " + f"'major' and 'minor' must be digits, not {major!r} and {minor!r}" + ) if self.keyword_only: - fail(f"Function {self.function.name}: '* [from ...]' must come before '*'") + fail(f"Function {self.function.name!r}: '* [from ...]' must come before '*'") if self.deprecated_positional: - fail(f"Function {self.function.name} uses 'x' more than once.") + fail(f"Function {self.function.name!r} uses 'x' more than once.") self.deprecated_positional = whence def parse_special_symbol(self, symbol: str) -> None: From 7864dcbaaec0f69b152f7678cbed6834825aa0a2 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 15 Jul 2023 21:49:30 +0200 Subject: [PATCH 16/71] Improve accuracy of variable name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit thenceforth | ˌðɛnsˈfɔːθ | (also from thenceforth) adverb (archaic or literary) from that time, place, or point onward: thenceforth he made his life in England. thenceforward | ˌðɛnsˈfɔːwəd | adverb another term for thenceforth. --- Tools/clinic/clinic.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index c6458bb15c677f..749eca7ed04a41 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -1215,8 +1215,8 @@ def parser_body( """ % add_label, indent=4)) if p.deprecated_positional: arg = p.name - whence = p.deprecated_positional - major, minor = whence.split(".") + thenceforth = p.deprecated_positional + major, minor = thenceforth.split(".") source = os.path.basename(self.cpp.filename) cpp_warning = ( f"Update {p.name!r} in {f.name!r} in {source!r} to be keyword-only." @@ -1233,7 +1233,7 @@ def parser_body( if (nargs == {i+1}) {{{{ if (PyErr_WarnEx(PyExc_DeprecationWarning, "Using {p.name!r} as a positional argument is deprecated. " - "It will become a keyword-only argument in Python {whence}.", 2)) + "It will become a keyword-only argument in Python {thenceforth}.", 2)) {{{{ goto exit; }}}} @@ -5167,16 +5167,16 @@ def parse_converter( "Annotations must be either a name, a function call, or a string." ) - def parse_deprecated_positional(self, whence: str): + def parse_deprecated_positional(self, thenceforth: str): assert isinstance(self.function, Function) - if "." not in whence: + if "." not in thenceforth: fail( f"Function {self.function.name!r}: '* [from ...]' format " "expected to be '', where 'major' and 'minor' " "are digits." ) - major, minor = whence.split(".") + major, minor = thenceforth.split(".") if not major.isdigit() or not minor.isdigit(): fail( f"Function {self.function.name!r}, '* [from ]': " @@ -5186,7 +5186,7 @@ def parse_deprecated_positional(self, whence: str): fail(f"Function {self.function.name!r}: '* [from ...]' must come before '*'") if self.deprecated_positional: fail(f"Function {self.function.name!r} uses 'x' more than once.") - self.deprecated_positional = whence + self.deprecated_positional = thenceforth def parse_special_symbol(self, symbol: str) -> None: assert isinstance(self.function, Function) From fbed52fec63cd96dd9872543df9340ef14f1d113 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 15 Jul 2023 22:57:01 +0200 Subject: [PATCH 17/71] Add docs --- Doc/howto/clinic.rst | 47 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/Doc/howto/clinic.rst b/Doc/howto/clinic.rst index 4620b4617e3450..f50628c83fdef7 100644 --- a/Doc/howto/clinic.rst +++ b/Doc/howto/clinic.rst @@ -1820,3 +1820,50 @@ blocks embedded in Python files look slightly different. They look like this: #[python start generated code]*/ def foo(): pass #/*[python checksum:...]*/ + + +How to deprecate positional use of optional parameters +------------------------------------------------------ + +Argument Clinic provides syntax that makes it possible to generate code that +deprecates positional use of optional parameters. +For example, say we've got a module level function :py:func:`!foo.myfunc` that +takes three :class:`int` parameters: +*a* (positional only), *b* (optional), and *c* (keyword-only):: + + /*[clinic input] + module foo + myfunc + a: int + / + b: int + * + c: int + [clinic start generated output]*/ + +We now want to make the *b* parameter keyword-only from Python 3.14 and onward, +and since :py:func:`!myfunc` is a public API, +we must follow :pep:`387` and issue a deprecation warning. +We can do that using the ``* [from ...]``` syntax, +by adding the line ``* [from 3.14]`` right above the *b* parameter:: + + /*[clinic input] + module foo + myfunc + a: int + / + * [from 3.14] + b: int + * + c: int + [clinic start generated output]*/ + +Next, regenerate Argument Clinic code (``make clinic``), +and add unit tests for the new behaviour. + +The generated code will now emit a :exc:`DeprecationWarning` +when an the optional parameter *b* is used as a positional paremeter. +C preprocessor directives are also generated for emitting +compiler warnings if the ``* [from ...]`` line has not been removed +from the Argument Clinic input when the deprecation period is over, +which means when the beta phase of the specified Python version kicks in. From 2abe7a3af1896d4428df800e4777614120cc240d Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 15 Jul 2023 23:10:25 +0200 Subject: [PATCH 18/71] Adjust CPP barking - warn if beta phase is reached, and no action has been taken - err for release candidate or anything newer --- Lib/test/clinic.test.c | 13 ++++++++++--- Tools/clinic/clinic.py | 11 +++++++++-- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/Lib/test/clinic.test.c b/Lib/test/clinic.test.c index 55ed624d8cbba3..0cdb549707c212 100644 --- a/Lib/test/clinic.test.c +++ b/Lib/test/clinic.test.c @@ -4882,13 +4882,20 @@ test_deprecate_positional_use(PyObject *module, PyObject *const *args, Py_ssize_ if (!noptargs) { goto skip_optional_pos; } - #if (PY_MAJOR_VERSION > 3) || \ - (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 14) + #if PY_MAJOR_VERSION == 3 && \ + PY_MINOR_VERSION == 14 && \ + PY_RELEASE_LEVEL == PY_RELEASE_LEVEL_BETA # ifdef _MSC_VER # pragma message ("Update 'optarg' in 'test_deprecate_positional_use' in 'clinic.test.c' to be keyword-only.") # else # warning "Update 'optarg' in 'test_deprecate_positional_use' in 'clinic.test.c' to be keyword-only." # endif + #elseif PY_MAJOR_VERSION > 3 || \ + (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION > 14) || \ + (PY_MAJOR_VERSION == 3 && \ + PY_MINOR_VERSION == 14 &&\ + PY_RELEASE_LEVEL == PY_RELEASE_LEVEL_GAMMA) + # #error Update 'optarg' in 'test_deprecate_positional_use' in 'clinic.test.c' to be keyword-only. #endif if (nargs == 2) { if (PyErr_WarnEx(PyExc_DeprecationWarning, @@ -4912,7 +4919,7 @@ test_deprecate_positional_use(PyObject *module, PyObject *const *args, Py_ssize_ static PyObject * test_deprecate_positional_use_impl(PyObject *module, PyObject *pos, int optarg) -/*[clinic end generated code: output=1bdec2a2f3c30de0 input=ab63c6aed293eb31]*/ +/*[clinic end generated code: output=c989b333083e6aa2 input=ab63c6aed293eb31]*/ PyDoc_STRVAR(test_deprecate_positional_use__doc__, "test_deprecate_positional_use($module, /, pos, optarg=5)\n" diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 749eca7ed04a41..82bd0617460104 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -1222,13 +1222,20 @@ def parser_body( f"Update {p.name!r} in {f.name!r} in {source!r} to be keyword-only." ) parser_code.append(normalize_snippet(fr""" - #if (PY_MAJOR_VERSION > {major}) || \ - (PY_MAJOR_VERSION == {major} && PY_MINOR_VERSION >= {minor}) + #if PY_MAJOR_VERSION == {major} && \ + PY_MINOR_VERSION == {minor} && \ + PY_RELEASE_LEVEL == PY_RELEASE_LEVEL_BETA # ifdef _MSC_VER # pragma message ("{cpp_warning}") # else # warning "{cpp_warning}" # endif + #elseif PY_MAJOR_VERSION > {major} || \ + (PY_MAJOR_VERSION == {major} && PY_MINOR_VERSION > {minor}) || \ + (PY_MAJOR_VERSION == {major} && \ + PY_MINOR_VERSION == {minor} &&\ + PY_RELEASE_LEVEL == PY_RELEASE_LEVEL_GAMMA) + # #error {cpp_warning} #endif if (nargs == {i+1}) {{{{ if (PyErr_WarnEx(PyExc_DeprecationWarning, From 4c0ec79ea74d636dcbbdcd8af4df271a5a0c6295 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 15 Jul 2023 23:24:24 +0200 Subject: [PATCH 19/71] Fix CPP --- Lib/test/clinic.test.c | 12 ++++++------ Tools/clinic/clinic.py | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Lib/test/clinic.test.c b/Lib/test/clinic.test.c index 0cdb549707c212..57ec222dce5c68 100644 --- a/Lib/test/clinic.test.c +++ b/Lib/test/clinic.test.c @@ -4890,11 +4890,11 @@ test_deprecate_positional_use(PyObject *module, PyObject *const *args, Py_ssize_ # else # warning "Update 'optarg' in 'test_deprecate_positional_use' in 'clinic.test.c' to be keyword-only." # endif - #elseif PY_MAJOR_VERSION > 3 || \ - (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION > 14) || \ - (PY_MAJOR_VERSION == 3 && \ - PY_MINOR_VERSION == 14 &&\ - PY_RELEASE_LEVEL == PY_RELEASE_LEVEL_GAMMA) + #elif PY_MAJOR_VERSION > 3 || \ + (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION > 14) || \ + (PY_MAJOR_VERSION == 3 && \ + PY_MINOR_VERSION == 14 && \ + PY_RELEASE_LEVEL == PY_RELEASE_LEVEL_GAMMA) # #error Update 'optarg' in 'test_deprecate_positional_use' in 'clinic.test.c' to be keyword-only. #endif if (nargs == 2) { @@ -4919,7 +4919,7 @@ test_deprecate_positional_use(PyObject *module, PyObject *const *args, Py_ssize_ static PyObject * test_deprecate_positional_use_impl(PyObject *module, PyObject *pos, int optarg) -/*[clinic end generated code: output=c989b333083e6aa2 input=ab63c6aed293eb31]*/ +/*[clinic end generated code: output=5ce511d9d8c2d450 input=ab63c6aed293eb31]*/ PyDoc_STRVAR(test_deprecate_positional_use__doc__, "test_deprecate_positional_use($module, /, pos, optarg=5)\n" diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 82bd0617460104..ac6b984a0c2d60 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -1230,11 +1230,11 @@ def parser_body( # else # warning "{cpp_warning}" # endif - #elseif PY_MAJOR_VERSION > {major} || \ - (PY_MAJOR_VERSION == {major} && PY_MINOR_VERSION > {minor}) || \ - (PY_MAJOR_VERSION == {major} && \ - PY_MINOR_VERSION == {minor} &&\ - PY_RELEASE_LEVEL == PY_RELEASE_LEVEL_GAMMA) + #elif PY_MAJOR_VERSION > {major} || \ + (PY_MAJOR_VERSION == {major} && PY_MINOR_VERSION > {minor}) || \ + (PY_MAJOR_VERSION == {major} && \ + PY_MINOR_VERSION == {minor} && \ + PY_RELEASE_LEVEL == PY_RELEASE_LEVEL_GAMMA) # #error {cpp_warning} #endif if (nargs == {i+1}) {{{{ From 35fbe80bf3c3e2c0d07ff186821137d8d9795c98 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 15 Jul 2023 23:26:54 +0200 Subject: [PATCH 20/71] Fix CPP for error case also --- Tools/clinic/clinic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index ac6b984a0c2d60..6378401fe94596 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -1235,7 +1235,7 @@ def parser_body( (PY_MAJOR_VERSION == {major} && \ PY_MINOR_VERSION == {minor} && \ PY_RELEASE_LEVEL == PY_RELEASE_LEVEL_GAMMA) - # #error {cpp_warning} + # error {cpp_warning} #endif if (nargs == {i+1}) {{{{ if (PyErr_WarnEx(PyExc_DeprecationWarning, From ccf8df83844f3ad07bb2d2d122655dd93cc1ce83 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 15 Jul 2023 23:32:30 +0200 Subject: [PATCH 21/71] Update clinic.test.c --- Lib/test/clinic.test.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Lib/test/clinic.test.c b/Lib/test/clinic.test.c index 57ec222dce5c68..297418ab70c189 100644 --- a/Lib/test/clinic.test.c +++ b/Lib/test/clinic.test.c @@ -4822,7 +4822,6 @@ Test_meth_coexist_impl(TestObj *self) /*[clinic input] test_deprecate_positional_use - pos: object * [from 3.14] optarg: int = 5 @@ -4895,7 +4894,7 @@ test_deprecate_positional_use(PyObject *module, PyObject *const *args, Py_ssize_ (PY_MAJOR_VERSION == 3 && \ PY_MINOR_VERSION == 14 && \ PY_RELEASE_LEVEL == PY_RELEASE_LEVEL_GAMMA) - # #error Update 'optarg' in 'test_deprecate_positional_use' in 'clinic.test.c' to be keyword-only. + # error Update 'optarg' in 'test_deprecate_positional_use' in 'clinic.test.c' to be keyword-only. #endif if (nargs == 2) { if (PyErr_WarnEx(PyExc_DeprecationWarning, @@ -4919,7 +4918,7 @@ test_deprecate_positional_use(PyObject *module, PyObject *const *args, Py_ssize_ static PyObject * test_deprecate_positional_use_impl(PyObject *module, PyObject *pos, int optarg) -/*[clinic end generated code: output=5ce511d9d8c2d450 input=ab63c6aed293eb31]*/ +/*[clinic end generated code: output=b800d9c96f9ffb14 input=f77517a893443884]*/ PyDoc_STRVAR(test_deprecate_positional_use__doc__, "test_deprecate_positional_use($module, /, pos, optarg=5)\n" From 4db3c8dd0591c5cb27d1736aedcdc0af8de996fc Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 15 Jul 2023 23:36:23 +0200 Subject: [PATCH 22/71] Fix and test multiple deprecation directives error message --- Lib/test/test_clinic.py | 13 +++++++++++++ Tools/clinic/clinic.py | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index 8b000898f5b66e..583c3ffcd40559 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -952,6 +952,19 @@ def test_depr_star_must_come_before_star(self): msg = "Function 'bar': '* [from ...]' must come before '*'" self.assertIn(msg, out) + def test_duplicate_depr_star(self): + out = self.parse_function_should_fail(""" + module foo + foo.bar + a: int + * [from 3.14] + * [from 3.14] + b: int + Docstring. + """) + msg = "Function 'bar' uses '[from ...]' more than once." + self.assertIn(msg, out) + def test_single_slash(self): out = self.parse_function_should_fail(""" module foo diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 6378401fe94596..51fd322f2a119d 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -5192,7 +5192,7 @@ def parse_deprecated_positional(self, thenceforth: str): if self.keyword_only: fail(f"Function {self.function.name!r}: '* [from ...]' must come before '*'") if self.deprecated_positional: - fail(f"Function {self.function.name!r} uses 'x' more than once.") + fail(f"Function {self.function.name!r} uses '[from ...]' more than once.") self.deprecated_positional = thenceforth def parse_special_symbol(self, symbol: str) -> None: From 43af475d3a6020c77b2d3af0f3ab6a7b8e7022f5 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sun, 16 Jul 2023 00:03:08 +0200 Subject: [PATCH 23/71] Extend the docs --- Doc/howto/clinic.rst | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/Doc/howto/clinic.rst b/Doc/howto/clinic.rst index f50628c83fdef7..c010a18eb67817 100644 --- a/Doc/howto/clinic.rst +++ b/Doc/howto/clinic.rst @@ -1829,13 +1829,12 @@ Argument Clinic provides syntax that makes it possible to generate code that deprecates positional use of optional parameters. For example, say we've got a module level function :py:func:`!foo.myfunc` that takes three :class:`int` parameters: -*a* (positional only), *b* (optional), and *c* (keyword-only):: +optional parameters *a* and *b*, and keyword-only parameter *c*:: /*[clinic input] module foo myfunc a: int - / b: int * c: int @@ -1851,7 +1850,6 @@ by adding the line ``* [from 3.14]`` right above the *b* parameter:: module foo myfunc a: int - / * [from 3.14] b: int * @@ -1867,3 +1865,31 @@ C preprocessor directives are also generated for emitting compiler warnings if the ``* [from ...]`` line has not been removed from the Argument Clinic input when the deprecation period is over, which means when the beta phase of the specified Python version kicks in. + +Let's return to our example and assume Python 3.14 development has entered the +beta phase, and we forgot all about updating the Argument Clinic code for +:py:func:`!myfunc`. +Luckily for us, compiler warnings are now generated: + +.. code-block:: none + + In file included from Modules/foomodule.c:139: + Modules/clinic/foomodule.c.h:83:8: error: Update 'b' in 'myfunc' in 'foomodule.c' to be keyword-only. + # error Update 'b' in 'myfunc' in 'foomodule.c' to be keyword-only. + ^ + +We now close the deprecation phase by making *b* keyword-only; +replace the ``* [from ...]``` line above *b* +with the ``*`` from the line above *c*:: + + /*[clinic input] + module foo + myfunc + a: int + * + b: int + c: int + [clinic start generated output]*/ + +Finally, run ``make clinic`` to regenerate the Argument Clinic code, +and update your unit tests to reflect the new behaviour. From 5074495ea0a7b9d85c6dbd27ac5de1fa25120f5b Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sun, 16 Jul 2023 00:04:52 +0200 Subject: [PATCH 24/71] Add note about compiler error --- Doc/howto/clinic.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Doc/howto/clinic.rst b/Doc/howto/clinic.rst index c010a18eb67817..df51bf0bfc7f36 100644 --- a/Doc/howto/clinic.rst +++ b/Doc/howto/clinic.rst @@ -1893,3 +1893,9 @@ with the ``*`` from the line above *c*:: Finally, run ``make clinic`` to regenerate the Argument Clinic code, and update your unit tests to reflect the new behaviour. + +.. note:: + + If you forget to update your clinic code during the target beta phase, the + copmiler warning will turn into a compiler error when the release candidate + phase kicks in. From ce5cd92607a8959823e46b16b5b818359753be2b Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sun, 16 Jul 2023 00:09:22 +0200 Subject: [PATCH 25/71] Fixup docs and fixup cpp error string --- Doc/howto/clinic.rst | 6 +++--- Lib/test/clinic.test.c | 4 ++-- Tools/clinic/clinic.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Doc/howto/clinic.rst b/Doc/howto/clinic.rst index df51bf0bfc7f36..eb724510de52dc 100644 --- a/Doc/howto/clinic.rst +++ b/Doc/howto/clinic.rst @@ -1874,9 +1874,9 @@ Luckily for us, compiler warnings are now generated: .. code-block:: none In file included from Modules/foomodule.c:139: - Modules/clinic/foomodule.c.h:83:8: error: Update 'b' in 'myfunc' in 'foomodule.c' to be keyword-only. - # error Update 'b' in 'myfunc' in 'foomodule.c' to be keyword-only. - ^ + Modules/clinic/foomodule.c.h:83:8: warning: Update 'b' in 'myfunc' in 'foomodule.c' to be keyword-only. [-W#warnings] + # warning "Update 'b' in 'myfunc' in 'foomodule.c' to be keyword-only." + ^ We now close the deprecation phase by making *b* keyword-only; replace the ``* [from ...]``` line above *b* diff --git a/Lib/test/clinic.test.c b/Lib/test/clinic.test.c index 297418ab70c189..6b6cb0b4c3e687 100644 --- a/Lib/test/clinic.test.c +++ b/Lib/test/clinic.test.c @@ -4894,7 +4894,7 @@ test_deprecate_positional_use(PyObject *module, PyObject *const *args, Py_ssize_ (PY_MAJOR_VERSION == 3 && \ PY_MINOR_VERSION == 14 && \ PY_RELEASE_LEVEL == PY_RELEASE_LEVEL_GAMMA) - # error Update 'optarg' in 'test_deprecate_positional_use' in 'clinic.test.c' to be keyword-only. + # error "Update 'optarg' in 'test_deprecate_positional_use' in 'clinic.test.c' to be keyword-only." #endif if (nargs == 2) { if (PyErr_WarnEx(PyExc_DeprecationWarning, @@ -4918,7 +4918,7 @@ test_deprecate_positional_use(PyObject *module, PyObject *const *args, Py_ssize_ static PyObject * test_deprecate_positional_use_impl(PyObject *module, PyObject *pos, int optarg) -/*[clinic end generated code: output=b800d9c96f9ffb14 input=f77517a893443884]*/ +/*[clinic end generated code: output=7a11cf25f54b6385 input=f77517a893443884]*/ PyDoc_STRVAR(test_deprecate_positional_use__doc__, "test_deprecate_positional_use($module, /, pos, optarg=5)\n" diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 51fd322f2a119d..cfe2f89e639a30 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -1235,7 +1235,7 @@ def parser_body( (PY_MAJOR_VERSION == {major} && \ PY_MINOR_VERSION == {minor} && \ PY_RELEASE_LEVEL == PY_RELEASE_LEVEL_GAMMA) - # error {cpp_warning} + # error "{cpp_warning}" #endif if (nargs == {i+1}) {{{{ if (PyErr_WarnEx(PyExc_DeprecationWarning, From 4dc82411db35667b19051a980a528f3ab925a290 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Thu, 20 Jul 2023 01:16:40 +0200 Subject: [PATCH 26/71] Fix mypy complain --- Tools/clinic/clinic.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 01608edffc1840..284bebd3a729df 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -1226,6 +1226,7 @@ def parser_body( arg = p.name thenceforth = p.deprecated_positional major, minor = thenceforth.split(".") + assert isinstance(self.cpp.filename, str) source = os.path.basename(self.cpp.filename) cpp_warning = ( f"Update {p.name!r} in {f.name!r} in {source!r} to be keyword-only." From 9eab8f71c36ce4417a87d98d979661dfa8c98ed0 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Thu, 20 Jul 2023 23:29:33 +0200 Subject: [PATCH 27/71] Address review: annotate deprecated_positional as str | None --- Tools/clinic/clinic.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 284bebd3a729df..31197ba77cffad 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -2623,7 +2623,8 @@ class Parameter: annotation: object = inspect.Parameter.empty docstring: str = '' group: int = 0 - deprecated_positional: str = "" + # (`None` signifies that there is no deprecation) + deprecated_positional: str | None = None right_bracket_count: int = dc.field(init=False, default=0) def __repr__(self) -> str: @@ -4420,7 +4421,7 @@ class DSLParser: state: StateKeeper keyword_only: bool positional_only: bool - deprecated_positional: str + deprecated_positional: str | None group: int parameter_state: int seen_positional_with_default: bool @@ -4457,7 +4458,7 @@ def reset(self) -> None: self.state = self.state_dsl_start self.keyword_only = False self.positional_only = False - self.deprecated_positional = "" + self.deprecated_positional = None self.group = 0 self.parameter_state: ParamState = ParamState.START self.seen_positional_with_default = False From 25aa23cfe45cd6d1ddc43360030b013624aa826f Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Thu, 20 Jul 2023 23:36:53 +0200 Subject: [PATCH 28/71] Address review: add return annotation to parse_deprecated_positional() Co-authored-by: Alex Waygood --- Tools/clinic/clinic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 31197ba77cffad..2a5045ce65fff8 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -5206,7 +5206,7 @@ def parse_converter( "Annotations must be either a name, a function call, or a string." ) - def parse_deprecated_positional(self, thenceforth: str): + def parse_deprecated_positional(self, thenceforth: str) -> None: assert isinstance(self.function, Function) if "." not in thenceforth: From 2e72b66f6e0902edb0d3dfdfcd81b94cfa2561dd Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sun, 23 Jul 2023 01:10:08 +0200 Subject: [PATCH 29/71] WIP --- Lib/test/clinic.test.c | 117 +++++++++++++++++++++++++++++++++++------ 1 file changed, 100 insertions(+), 17 deletions(-) diff --git a/Lib/test/clinic.test.c b/Lib/test/clinic.test.c index e99ce97385f959..4a979902483027 100644 --- a/Lib/test/clinic.test.c +++ b/Lib/test/clinic.test.c @@ -5007,26 +5007,31 @@ PyDoc_STRVAR(new_dest__doc__, /*[clinic input] -test_deprecate_positional_use +test_deprecate_positional_use_1 pos: object * [from 3.14] optarg: int = 5 + has a default value, unlike test_deprecate_positional_use_2 [clinic start generated code]*/ -PyDoc_STRVAR(test_deprecate_positional_use__doc__, -"test_deprecate_positional_use($module, /, pos, optarg=5)\n" +PyDoc_STRVAR(test_deprecate_positional_use_1__doc__, +"test_deprecate_positional_use_1($module, /, pos, optarg=5)\n" "--\n" -"\n"); +"\n" +"\n" +"\n" +" optarg\n" +" has a default value, unlike test_deprecate_positional_use_2"); -#define TEST_DEPRECATE_POSITIONAL_USE_METHODDEF \ - {"test_deprecate_positional_use", _PyCFunction_CAST(test_deprecate_positional_use), METH_FASTCALL|METH_KEYWORDS, test_deprecate_positional_use__doc__}, +#define TEST_DEPRECATE_POSITIONAL_USE_1_METHODDEF \ + {"test_deprecate_positional_use_1", _PyCFunction_CAST(test_deprecate_positional_use_1), METH_FASTCALL|METH_KEYWORDS, test_deprecate_positional_use_1__doc__}, static PyObject * -test_deprecate_positional_use_impl(PyObject *module, PyObject *pos, - int optarg); +test_deprecate_positional_use_1_impl(PyObject *module, PyObject *pos, + int optarg); static PyObject * -test_deprecate_positional_use(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +test_deprecate_positional_use_1(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) @@ -5050,7 +5055,7 @@ test_deprecate_positional_use(PyObject *module, PyObject *const *args, Py_ssize_ static const char * const _keywords[] = {"pos", "optarg", NULL}; static _PyArg_Parser _parser = { .keywords = _keywords, - .fname = "test_deprecate_positional_use", + .fname = "test_deprecate_positional_use_1", .kwtuple = KWTUPLE, }; #undef KWTUPLE @@ -5071,16 +5076,16 @@ test_deprecate_positional_use(PyObject *module, PyObject *const *args, Py_ssize_ PY_MINOR_VERSION == 14 && \ PY_RELEASE_LEVEL == PY_RELEASE_LEVEL_BETA # ifdef _MSC_VER - # pragma message ("Update 'optarg' in 'test_deprecate_positional_use' in 'clinic.test.c' to be keyword-only.") + # pragma message ("Update 'optarg' in 'test_deprecate_positional_use_1' in 'clinic.test.c' to be keyword-only.") # else - # warning "Update 'optarg' in 'test_deprecate_positional_use' in 'clinic.test.c' to be keyword-only." + # warning "Update 'optarg' in 'test_deprecate_positional_use_1' in 'clinic.test.c' to be keyword-only." # endif #elif PY_MAJOR_VERSION > 3 || \ (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION > 14) || \ (PY_MAJOR_VERSION == 3 && \ PY_MINOR_VERSION == 14 && \ PY_RELEASE_LEVEL == PY_RELEASE_LEVEL_GAMMA) - # error "Update 'optarg' in 'test_deprecate_positional_use' in 'clinic.test.c' to be keyword-only." + # error "Update 'optarg' in 'test_deprecate_positional_use_1' in 'clinic.test.c' to be keyword-only." #endif if (nargs == 2) { if (PyErr_WarnEx(PyExc_DeprecationWarning, @@ -5095,16 +5100,16 @@ test_deprecate_positional_use(PyObject *module, PyObject *const *args, Py_ssize_ goto exit; } skip_optional_pos: - return_value = test_deprecate_positional_use_impl(module, pos, optarg); + return_value = test_deprecate_positional_use_1_impl(module, pos, optarg); exit: return return_value; } static PyObject * -test_deprecate_positional_use_impl(PyObject *module, PyObject *pos, - int optarg) -/*[clinic end generated code: output=7a11cf25f54b6385 input=f77517a893443884]*/ +test_deprecate_positional_use_1_impl(PyObject *module, PyObject *pos, + int optarg) +/*[clinic end generated code: output=9967aec5c089cab8 input=fe31c35d217b4ed4]*/ PyDoc_STRVAR(test_deprecate_positional_use__doc__, "test_deprecate_positional_use($module, /, pos, optarg=5)\n" @@ -5120,3 +5125,81 @@ test_deprecate_positional_use_impl(PyObject *module, PyObject *pos, static PyObject * test_deprecate_positional_use(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) + + +/*[clinic input] +test_deprecate_positional_use_2 + pos: object + * [from 3.14] + optarg: int + has no default value, unlike test_deprecate_positional_use_1 +[clinic start generated code]*/ + +PyDoc_STRVAR(test_deprecate_positional_use_2__doc__, +"test_deprecate_positional_use_2($module, /, pos, optarg)\n" +"--\n" +"\n" +"\n" +"\n" +" optarg\n" +" has no default value, unlike test_deprecate_positional_use_1"); + +#define TEST_DEPRECATE_POSITIONAL_USE_2_METHODDEF \ + {"test_deprecate_positional_use_2", _PyCFunction_CAST(test_deprecate_positional_use_2), METH_FASTCALL|METH_KEYWORDS, test_deprecate_positional_use_2__doc__}, + +static PyObject * +test_deprecate_positional_use_2_impl(PyObject *module, PyObject *pos, + int optarg); + +static PyObject * +test_deprecate_positional_use_2(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 2 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(pos), &_Py_ID(optarg), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"pos", "optarg", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "test_deprecate_positional_use_2", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[2]; + PyObject *pos; + int optarg; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 2, 2, 0, argsbuf); + if (!args) { + goto exit; + } + pos = args[0]; + optarg = _PyLong_AsInt(args[1]); + if (optarg == -1 && PyErr_Occurred()) { + goto exit; + } + return_value = test_deprecate_positional_use_2_impl(module, pos, optarg); + +exit: + return return_value; +} + +static PyObject * +test_deprecate_positional_use_2_impl(PyObject *module, PyObject *pos, + int optarg) +/*[clinic end generated code: output=e9e2f564894ca1fa input=1be0f94d673e51d3]*/ From a76d63ce81b8192a05cd6417bb5fb47ca96299ef Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Fri, 4 Aug 2023 22:48:37 +0200 Subject: [PATCH 30/71] Move prototype to class level --- Tools/clinic/clinic.py | 64 ++++++++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 28 deletions(-) diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index eca6cc518eb2a5..12d7353fe34b7f 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -828,6 +828,31 @@ class CLanguage(Language): #define {methoddef_name} #endif /* !defined({methoddef_name}) */ """) + DEPRECATED_POSITIONAL_PROTOTYPE: Final[str] = r""" + #if PY_MAJOR_VERSION == {major} && \ + PY_MINOR_VERSION == {minor} && \ + PY_RELEASE_LEVEL == PY_RELEASE_LEVEL_BETA + # ifdef _MSC_VER + # pragma message ("{cpp_warning}") + # else + # warning "{cpp_warning}" + # endif + #elif PY_MAJOR_VERSION > {major} || \ + (PY_MAJOR_VERSION == {major} && PY_MINOR_VERSION > {minor}) || \ + (PY_MAJOR_VERSION == {major} && \ + PY_MINOR_VERSION == {minor} && \ + PY_RELEASE_LEVEL == PY_RELEASE_LEVEL_GAMMA) + # error "{cpp_warning}" + #endif + if (nargs == {pos}) {{{{ + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "Using {name!r} as a positional argument is deprecated. " + "It will become a keyword-only argument in Python {thenceforth}.", 2)) + {{{{ + goto exit; + }}}} + }}}} + """ def __init__(self, filename: str) -> None: super().__init__(filename) @@ -1243,39 +1268,22 @@ def parser_body( }} """ % add_label, indent=4)) if p.deprecated_positional: - arg = p.name thenceforth = p.deprecated_positional major, minor = thenceforth.split(".") assert isinstance(self.cpp.filename, str) source = os.path.basename(self.cpp.filename) - cpp_warning = ( - f"Update {p.name!r} in {f.name!r} in {source!r} to be keyword-only." + cpp_warning = (f"Update {p.name!r} in {f.name!r} in " + f"{source!r} to be keyword-only.") + code = self.DEPRECATED_POSITIONAL_PROTOTYPE.format( + name=p.name, + pos=i+1, + thenceforth=thenceforth, + major=major, + minor=minor, + cpp_warning=cpp_warning, ) - parser_code.append(normalize_snippet(fr""" - #if PY_MAJOR_VERSION == {major} && \ - PY_MINOR_VERSION == {minor} && \ - PY_RELEASE_LEVEL == PY_RELEASE_LEVEL_BETA - # ifdef _MSC_VER - # pragma message ("{cpp_warning}") - # else - # warning "{cpp_warning}" - # endif - #elif PY_MAJOR_VERSION > {major} || \ - (PY_MAJOR_VERSION == {major} && PY_MINOR_VERSION > {minor}) || \ - (PY_MAJOR_VERSION == {major} && \ - PY_MINOR_VERSION == {minor} && \ - PY_RELEASE_LEVEL == PY_RELEASE_LEVEL_GAMMA) - # error "{cpp_warning}" - #endif - if (nargs == {i+1}) {{{{ - if (PyErr_WarnEx(PyExc_DeprecationWarning, - "Using {p.name!r} as a positional argument is deprecated. " - "It will become a keyword-only argument in Python {thenceforth}.", 2)) - {{{{ - goto exit; - }}}} - }}}} - """, indent=4)) + code = normalize_snippet(code, indent=4) + parser_code.append(code) if i + 1 == len(parameters): parser_code.append(normalize_snippet(parsearg, indent=4)) else: From e5bbd7b37e5b89123724d7e214f4d56a8c3a6520 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Fri, 4 Aug 2023 22:50:09 +0200 Subject: [PATCH 31/71] Address review: start warning from alpha stage --- Lib/test/clinic.test.c | 4 ++-- Tools/clinic/clinic.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/test/clinic.test.c b/Lib/test/clinic.test.c index 27d8c7607fb8e4..99c17d3ae1bf27 100644 --- a/Lib/test/clinic.test.c +++ b/Lib/test/clinic.test.c @@ -5466,7 +5466,7 @@ test_deprecate_positional_use_1(PyObject *module, PyObject *const *args, Py_ssiz } #if PY_MAJOR_VERSION == 3 && \ PY_MINOR_VERSION == 14 && \ - PY_RELEASE_LEVEL == PY_RELEASE_LEVEL_BETA + PY_RELEASE_LEVEL == PY_RELEASE_LEVEL_ALPHA # ifdef _MSC_VER # pragma message ("Update 'optarg' in 'test_deprecate_positional_use_1' in 'clinic.test.c' to be keyword-only.") # else @@ -5501,7 +5501,7 @@ test_deprecate_positional_use_1(PyObject *module, PyObject *const *args, Py_ssiz static PyObject * test_deprecate_positional_use_1_impl(PyObject *module, PyObject *pos, int optarg) -/*[clinic end generated code: output=9967aec5c089cab8 input=fe31c35d217b4ed4]*/ +/*[clinic end generated code: output=259fc44a87bd82af input=fe31c35d217b4ed4]*/ /*[clinic input] diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 12d7353fe34b7f..9aaaf55ffe0a5f 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -831,7 +831,7 @@ class CLanguage(Language): DEPRECATED_POSITIONAL_PROTOTYPE: Final[str] = r""" #if PY_MAJOR_VERSION == {major} && \ PY_MINOR_VERSION == {minor} && \ - PY_RELEASE_LEVEL == PY_RELEASE_LEVEL_BETA + PY_RELEASE_LEVEL == PY_RELEASE_LEVEL_ALPHA # ifdef _MSC_VER # pragma message ("{cpp_warning}") # else From c981efa25a00efac246e2f981e17f1de371076da Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Fri, 4 Aug 2023 22:56:44 +0200 Subject: [PATCH 32/71] Add more test cases --- Lib/test/clinic.test.c | 80 +++++++++++++++++++++++++++++++++++++++++ Lib/test/test_clinic.py | 7 ++-- 2 files changed, 84 insertions(+), 3 deletions(-) diff --git a/Lib/test/clinic.test.c b/Lib/test/clinic.test.c index 99c17d3ae1bf27..6ca540e64e7d0c 100644 --- a/Lib/test/clinic.test.c +++ b/Lib/test/clinic.test.c @@ -5580,3 +5580,83 @@ static PyObject * test_deprecate_positional_use_2_impl(PyObject *module, PyObject *pos, int optarg) /*[clinic end generated code: output=e9e2f564894ca1fa input=1be0f94d673e51d3]*/ + + +/*[clinic input] +test_deprecate_positional_use_3 + pos: object + * [from 3.14] + optarg: int + * + kw: int +[clinic start generated code]*/ + +PyDoc_STRVAR(test_deprecate_positional_use_3__doc__, +"test_deprecate_positional_use_3($module, /, pos, optarg, *, kw)\n" +"--\n" +"\n"); + +#define TEST_DEPRECATE_POSITIONAL_USE_3_METHODDEF \ + {"test_deprecate_positional_use_3", _PyCFunction_CAST(test_deprecate_positional_use_3), METH_FASTCALL|METH_KEYWORDS, test_deprecate_positional_use_3__doc__}, + +static PyObject * +test_deprecate_positional_use_3_impl(PyObject *module, PyObject *pos, + int optarg, int kw); + +static PyObject * +test_deprecate_positional_use_3(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 3 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(pos), &_Py_ID(optarg), &_Py_ID(kw), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"pos", "optarg", "kw", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "test_deprecate_positional_use_3", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[3]; + PyObject *pos; + int optarg; + int kw; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 2, 2, 1, argsbuf); + if (!args) { + goto exit; + } + pos = args[0]; + optarg = _PyLong_AsInt(args[1]); + if (optarg == -1 && PyErr_Occurred()) { + goto exit; + } + kw = _PyLong_AsInt(args[2]); + if (kw == -1 && PyErr_Occurred()) { + goto exit; + } + return_value = test_deprecate_positional_use_3_impl(module, pos, optarg, kw); + +exit: + return return_value; +} + +static PyObject * +test_deprecate_positional_use_3_impl(PyObject *module, PyObject *pos, + int optarg, int kw) +/*[clinic end generated code: output=928a72c3ca930b7b input=42f1c1240b70ddd8]*/ diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index 964a097b715f97..dafb49bd659514 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -1537,18 +1537,19 @@ def test_depr_star_must_come_before_star(self): err = "Function 'bar': '* [from ...]' must come before '*'" self.expect_failure(block, err, lineno=4) - def test_duplicate_depr_star(self): + def test_depr_star_duplicate(self): block = """ module foo foo.bar a: int * [from 3.14] - * [from 3.14] b: int + * [from 3.14] + c: int Docstring. """ err = "Function 'bar' uses '[from ...]' more than once" - self.expect_failure(block, err, lineno=4) + self.expect_failure(block, err, lineno=5) def test_single_slash(self): block = """ From 821124e14ad24a10041b8d134def1e7e1545e827 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Fri, 4 Aug 2023 22:59:15 +0200 Subject: [PATCH 33/71] Add TODO comment --- Lib/test/clinic.test.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Lib/test/clinic.test.c b/Lib/test/clinic.test.c index 6ca540e64e7d0c..da79618004d525 100644 --- a/Lib/test/clinic.test.c +++ b/Lib/test/clinic.test.c @@ -5589,12 +5589,14 @@ test_deprecate_positional_use_3 optarg: int * kw: int +TODO: this does not generate correct code yet. [clinic start generated code]*/ PyDoc_STRVAR(test_deprecate_positional_use_3__doc__, "test_deprecate_positional_use_3($module, /, pos, optarg, *, kw)\n" "--\n" -"\n"); +"\n" +"TODO: this does not generate correct code yet."); #define TEST_DEPRECATE_POSITIONAL_USE_3_METHODDEF \ {"test_deprecate_positional_use_3", _PyCFunction_CAST(test_deprecate_positional_use_3), METH_FASTCALL|METH_KEYWORDS, test_deprecate_positional_use_3__doc__}, @@ -5659,4 +5661,4 @@ test_deprecate_positional_use_3(PyObject *module, PyObject *const *args, Py_ssiz static PyObject * test_deprecate_positional_use_3_impl(PyObject *module, PyObject *pos, int optarg, int kw) -/*[clinic end generated code: output=928a72c3ca930b7b input=42f1c1240b70ddd8]*/ +/*[clinic end generated code: output=2ccbfc730fab9bb9 input=9ed0dd3ec64c1b42]*/ From aa1b85d78a1ec86b1fcf20bca56bfedff1cce032 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 5 Aug 2023 08:58:25 +0200 Subject: [PATCH 34/71] Test case 2 already covers this test case --- Lib/test/clinic.test.c | 82 ------------------------------------------ 1 file changed, 82 deletions(-) diff --git a/Lib/test/clinic.test.c b/Lib/test/clinic.test.c index da79618004d525..99c17d3ae1bf27 100644 --- a/Lib/test/clinic.test.c +++ b/Lib/test/clinic.test.c @@ -5580,85 +5580,3 @@ static PyObject * test_deprecate_positional_use_2_impl(PyObject *module, PyObject *pos, int optarg) /*[clinic end generated code: output=e9e2f564894ca1fa input=1be0f94d673e51d3]*/ - - -/*[clinic input] -test_deprecate_positional_use_3 - pos: object - * [from 3.14] - optarg: int - * - kw: int -TODO: this does not generate correct code yet. -[clinic start generated code]*/ - -PyDoc_STRVAR(test_deprecate_positional_use_3__doc__, -"test_deprecate_positional_use_3($module, /, pos, optarg, *, kw)\n" -"--\n" -"\n" -"TODO: this does not generate correct code yet."); - -#define TEST_DEPRECATE_POSITIONAL_USE_3_METHODDEF \ - {"test_deprecate_positional_use_3", _PyCFunction_CAST(test_deprecate_positional_use_3), METH_FASTCALL|METH_KEYWORDS, test_deprecate_positional_use_3__doc__}, - -static PyObject * -test_deprecate_positional_use_3_impl(PyObject *module, PyObject *pos, - int optarg, int kw); - -static PyObject * -test_deprecate_positional_use_3(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) -{ - PyObject *return_value = NULL; - #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) - - #define NUM_KEYWORDS 3 - static struct { - PyGC_Head _this_is_not_used; - PyObject_VAR_HEAD - PyObject *ob_item[NUM_KEYWORDS]; - } _kwtuple = { - .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) - .ob_item = { &_Py_ID(pos), &_Py_ID(optarg), &_Py_ID(kw), }, - }; - #undef NUM_KEYWORDS - #define KWTUPLE (&_kwtuple.ob_base.ob_base) - - #else // !Py_BUILD_CORE - # define KWTUPLE NULL - #endif // !Py_BUILD_CORE - - static const char * const _keywords[] = {"pos", "optarg", "kw", NULL}; - static _PyArg_Parser _parser = { - .keywords = _keywords, - .fname = "test_deprecate_positional_use_3", - .kwtuple = KWTUPLE, - }; - #undef KWTUPLE - PyObject *argsbuf[3]; - PyObject *pos; - int optarg; - int kw; - - args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 2, 2, 1, argsbuf); - if (!args) { - goto exit; - } - pos = args[0]; - optarg = _PyLong_AsInt(args[1]); - if (optarg == -1 && PyErr_Occurred()) { - goto exit; - } - kw = _PyLong_AsInt(args[2]); - if (kw == -1 && PyErr_Occurred()) { - goto exit; - } - return_value = test_deprecate_positional_use_3_impl(module, pos, optarg, kw); - -exit: - return return_value; -} - -static PyObject * -test_deprecate_positional_use_3_impl(PyObject *module, PyObject *pos, - int optarg, int kw) -/*[clinic end generated code: output=2ccbfc730fab9bb9 input=9ed0dd3ec64c1b42]*/ From 44af850878b0c0d442b713791db6fc599ec71b23 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 5 Aug 2023 09:22:38 +0200 Subject: [PATCH 35/71] Handle more cases --- Lib/test/clinic.test.c | 25 ++++++++++++++++++++++++- Tools/clinic/clinic.py | 36 +++++++++++++++++++++--------------- 2 files changed, 45 insertions(+), 16 deletions(-) diff --git a/Lib/test/clinic.test.c b/Lib/test/clinic.test.c index 99c17d3ae1bf27..e15d7943273dd1 100644 --- a/Lib/test/clinic.test.c +++ b/Lib/test/clinic.test.c @@ -5570,6 +5570,29 @@ test_deprecate_positional_use_2(PyObject *module, PyObject *const *args, Py_ssiz if (optarg == -1 && PyErr_Occurred()) { goto exit; } + #if PY_MAJOR_VERSION == 3 && \ + PY_MINOR_VERSION == 14 && \ + PY_RELEASE_LEVEL == PY_RELEASE_LEVEL_ALPHA + # ifdef _MSC_VER + # pragma message ("Update 'optarg' in 'test_deprecate_positional_use_2' in 'clinic.test.c' to be keyword-only.") + # else + # warning "Update 'optarg' in 'test_deprecate_positional_use_2' in 'clinic.test.c' to be keyword-only." + # endif + #elif PY_MAJOR_VERSION > 3 || \ + (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION > 14) || \ + (PY_MAJOR_VERSION == 3 && \ + PY_MINOR_VERSION == 14 && \ + PY_RELEASE_LEVEL == PY_RELEASE_LEVEL_GAMMA) + # error "Update 'optarg' in 'test_deprecate_positional_use_2' in 'clinic.test.c' to be keyword-only." + #endif + if (nargs == 2) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "Using 'optarg' as a positional argument is deprecated. " + "It will become a keyword-only argument in Python 3.14.", 2)) + { + goto exit; + } + } return_value = test_deprecate_positional_use_2_impl(module, pos, optarg); exit: @@ -5579,4 +5602,4 @@ test_deprecate_positional_use_2(PyObject *module, PyObject *const *args, Py_ssiz static PyObject * test_deprecate_positional_use_2_impl(PyObject *module, PyObject *pos, int optarg) -/*[clinic end generated code: output=e9e2f564894ca1fa input=1be0f94d673e51d3]*/ +/*[clinic end generated code: output=574ca16f9b214d1c input=1be0f94d673e51d3]*/ diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 9aaaf55ffe0a5f..54592ef86c1d04 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -1224,6 +1224,23 @@ def parser_body( flags = 'METH_METHOD|' + flags parser_prototype = self.PARSER_PROTOTYPE_DEF_CLASS + def deprecate_positional_use(p: Parameter) -> str: + thenceforth = p.deprecated_positional + major, minor = thenceforth.split(".") + assert isinstance(self.cpp.filename, str) + source = os.path.basename(self.cpp.filename) + cpp_warning = (f"Update {p.name!r} in {f.name!r} in " + f"{source!r} to be keyword-only.") + code = self.DEPRECATED_POSITIONAL_PROTOTYPE.format( + name=p.name, + pos=i+1, + thenceforth=thenceforth, + major=major, + minor=minor, + cpp_warning=cpp_warning, + ) + return normalize_snippet(code, indent=4) + add_label: str | None = None for i, p in enumerate(parameters): if isinstance(p.converter, defining_class_converter): @@ -1239,6 +1256,9 @@ def parser_body( add_label = None if not p.is_optional(): parser_code.append(normalize_snippet(parsearg, indent=4)) + if p.deprecated_positional: + code = deprecate_positional_use(p) + parser_code.append(code) elif i < pos_only: add_label = 'skip_optional_posonly' parser_code.append(normalize_snippet(""" @@ -1268,21 +1288,7 @@ def parser_body( }} """ % add_label, indent=4)) if p.deprecated_positional: - thenceforth = p.deprecated_positional - major, minor = thenceforth.split(".") - assert isinstance(self.cpp.filename, str) - source = os.path.basename(self.cpp.filename) - cpp_warning = (f"Update {p.name!r} in {f.name!r} in " - f"{source!r} to be keyword-only.") - code = self.DEPRECATED_POSITIONAL_PROTOTYPE.format( - name=p.name, - pos=i+1, - thenceforth=thenceforth, - major=major, - minor=minor, - cpp_warning=cpp_warning, - ) - code = normalize_snippet(code, indent=4) + code = deprecate_positional_use(p) parser_code.append(code) if i + 1 == len(parameters): parser_code.append(normalize_snippet(parsearg, indent=4)) From 01f17f9577a95941a9b9f9c8c6ece98ca1469491 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 5 Aug 2023 11:03:47 +0200 Subject: [PATCH 36/71] Add one more test case as requested by Nikita --- Lib/test/clinic.test.c | 126 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) diff --git a/Lib/test/clinic.test.c b/Lib/test/clinic.test.c index e15d7943273dd1..58964b407071b0 100644 --- a/Lib/test/clinic.test.c +++ b/Lib/test/clinic.test.c @@ -5603,3 +5603,129 @@ static PyObject * test_deprecate_positional_use_2_impl(PyObject *module, PyObject *pos, int optarg) /*[clinic end generated code: output=574ca16f9b214d1c input=1be0f94d673e51d3]*/ + + +/*[clinic input] +test_deprecate_positional_use_3 + pos: object + * [from 3.14] + optarg: int + * + kw: int +[clinic start generated code]*/ + +PyDoc_STRVAR(test_deprecate_positional_use_3__doc__, +"test_deprecate_positional_use_3($module, /, pos, optarg, *, kw)\n" +"--\n" +"\n"); + +#define TEST_DEPRECATE_POSITIONAL_USE_3_METHODDEF \ + {"test_deprecate_positional_use_3", _PyCFunction_CAST(test_deprecate_positional_use_3), METH_FASTCALL|METH_KEYWORDS, test_deprecate_positional_use_3__doc__}, + +static PyObject * +test_deprecate_positional_use_3_impl(PyObject *module, PyObject *pos, + int optarg, int kw); + +static PyObject * +test_deprecate_positional_use_3(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 3 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(pos), &_Py_ID(optarg), &_Py_ID(kw), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"pos", "optarg", "kw", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "test_deprecate_positional_use_3", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[3]; + PyObject *pos; + int optarg; + int kw; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 2, 2, 1, argsbuf); + if (!args) { + goto exit; + } + pos = args[0]; + optarg = _PyLong_AsInt(args[1]); + if (optarg == -1 && PyErr_Occurred()) { + goto exit; + } + #if PY_MAJOR_VERSION == 3 && \ + PY_MINOR_VERSION == 14 && \ + PY_RELEASE_LEVEL == PY_RELEASE_LEVEL_ALPHA + # ifdef _MSC_VER + # pragma message ("Update 'optarg' in 'test_deprecate_positional_use_3' in 'clinic.test.c' to be keyword-only.") + # else + # warning "Update 'optarg' in 'test_deprecate_positional_use_3' in 'clinic.test.c' to be keyword-only." + # endif + #elif PY_MAJOR_VERSION > 3 || \ + (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION > 14) || \ + (PY_MAJOR_VERSION == 3 && \ + PY_MINOR_VERSION == 14 && \ + PY_RELEASE_LEVEL == PY_RELEASE_LEVEL_GAMMA) + # error "Update 'optarg' in 'test_deprecate_positional_use_3' in 'clinic.test.c' to be keyword-only." + #endif + if (nargs == 2) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "Using 'optarg' as a positional argument is deprecated. " + "It will become a keyword-only argument in Python 3.14.", 2)) + { + goto exit; + } + } + kw = _PyLong_AsInt(args[2]); + if (kw == -1 && PyErr_Occurred()) { + goto exit; + } + #if PY_MAJOR_VERSION == 3 && \ + PY_MINOR_VERSION == 14 && \ + PY_RELEASE_LEVEL == PY_RELEASE_LEVEL_ALPHA + # ifdef _MSC_VER + # pragma message ("Update 'kw' in 'test_deprecate_positional_use_3' in 'clinic.test.c' to be keyword-only.") + # else + # warning "Update 'kw' in 'test_deprecate_positional_use_3' in 'clinic.test.c' to be keyword-only." + # endif + #elif PY_MAJOR_VERSION > 3 || \ + (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION > 14) || \ + (PY_MAJOR_VERSION == 3 && \ + PY_MINOR_VERSION == 14 && \ + PY_RELEASE_LEVEL == PY_RELEASE_LEVEL_GAMMA) + # error "Update 'kw' in 'test_deprecate_positional_use_3' in 'clinic.test.c' to be keyword-only." + #endif + if (nargs == 3) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "Using 'kw' as a positional argument is deprecated. " + "It will become a keyword-only argument in Python 3.14.", 2)) + { + goto exit; + } + } + return_value = test_deprecate_positional_use_3_impl(module, pos, optarg, kw); + +exit: + return return_value; +} + +static PyObject * +test_deprecate_positional_use_3_impl(PyObject *module, PyObject *pos, + int optarg, int kw) +/*[clinic end generated code: output=9d7fd256b16dbc30 input=42f1c1240b70ddd8]*/ From e6860bce26242dd87ac7da8e3646fcae44db16fa Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 5 Aug 2023 11:29:24 +0200 Subject: [PATCH 37/71] Fix mypy whining --- Tools/clinic/clinic.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 54592ef86c1d04..2150439144c1eb 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -1225,6 +1225,7 @@ def parser_body( parser_prototype = self.PARSER_PROTOTYPE_DEF_CLASS def deprecate_positional_use(p: Parameter) -> str: + assert p.deprecated_positional is not None thenceforth = p.deprecated_positional major, minor = thenceforth.split(".") assert isinstance(self.cpp.filename, str) From 71a87500341adb432ff689f46760fc7181358c02 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 5 Aug 2023 19:40:37 +0200 Subject: [PATCH 38/71] Address Alex's review: solve mypy complaint more elegantly --- Tools/clinic/clinic.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 2150439144c1eb..76d182718de072 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -1224,16 +1224,17 @@ def parser_body( flags = 'METH_METHOD|' + flags parser_prototype = self.PARSER_PROTOTYPE_DEF_CLASS - def deprecate_positional_use(p: Parameter) -> str: - assert p.deprecated_positional is not None - thenceforth = p.deprecated_positional + def deprecate_positional_use( + param_name: str, + thenceforth: str + ) -> str: major, minor = thenceforth.split(".") assert isinstance(self.cpp.filename, str) source = os.path.basename(self.cpp.filename) - cpp_warning = (f"Update {p.name!r} in {f.name!r} in " + cpp_warning = (f"Update {param_name!r} in {f.name!r} in " f"{source!r} to be keyword-only.") code = self.DEPRECATED_POSITIONAL_PROTOTYPE.format( - name=p.name, + name=param_name, pos=i+1, thenceforth=thenceforth, major=major, @@ -1258,7 +1259,8 @@ def deprecate_positional_use(p: Parameter) -> str: if not p.is_optional(): parser_code.append(normalize_snippet(parsearg, indent=4)) if p.deprecated_positional: - code = deprecate_positional_use(p) + code = deprecate_positional_use( + p.name, p.deprecated_positional) parser_code.append(code) elif i < pos_only: add_label = 'skip_optional_posonly' @@ -1289,7 +1291,8 @@ def deprecate_positional_use(p: Parameter) -> str: }} """ % add_label, indent=4)) if p.deprecated_positional: - code = deprecate_positional_use(p) + code = deprecate_positional_use( + p.name, p.deprecated_positional) parser_code.append(code) if i + 1 == len(parameters): parser_code.append(normalize_snippet(parsearg, indent=4)) From e6886f051ea836693bafa775be747080bd5fbc4e Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 5 Aug 2023 20:03:16 +0200 Subject: [PATCH 39/71] Address some of Serhiy's remarks: - Simplify preprocessor code by using PY_VERSION_HEX - Terminology: + 'argument' => 'parameter' + 'using' => 'passing' - Use object converter for simplicity - Generate deprecation warning before parsing code --- Lib/test/clinic.test.c | 110 ++++++++++++++--------------------------- Tools/clinic/clinic.py | 20 +++----- 2 files changed, 44 insertions(+), 86 deletions(-) diff --git a/Lib/test/clinic.test.c b/Lib/test/clinic.test.c index 58964b407071b0..d0c696305d7e3d 100644 --- a/Lib/test/clinic.test.c +++ b/Lib/test/clinic.test.c @@ -5402,7 +5402,7 @@ docstr_fallback_to_converter_default_impl(PyObject *module, str a) test_deprecate_positional_use_1 pos: object * [from 3.14] - optarg: int = 5 + optarg: object = 5 has a default value, unlike test_deprecate_positional_use_2 [clinic start generated code]*/ @@ -5420,7 +5420,7 @@ PyDoc_STRVAR(test_deprecate_positional_use_1__doc__, static PyObject * test_deprecate_positional_use_1_impl(PyObject *module, PyObject *pos, - int optarg); + PyObject *optarg); static PyObject * test_deprecate_positional_use_1(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) @@ -5454,7 +5454,7 @@ test_deprecate_positional_use_1(PyObject *module, PyObject *const *args, Py_ssiz PyObject *argsbuf[2]; Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1; PyObject *pos; - int optarg = 5; + PyObject *optarg = 5; args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 2, 0, argsbuf); if (!args) { @@ -5464,33 +5464,24 @@ test_deprecate_positional_use_1(PyObject *module, PyObject *const *args, Py_ssiz if (!noptargs) { goto skip_optional_pos; } - #if PY_MAJOR_VERSION == 3 && \ - PY_MINOR_VERSION == 14 && \ - PY_RELEASE_LEVEL == PY_RELEASE_LEVEL_ALPHA + #if PY_VERSION_HEX >= 0x030e00C0 # ifdef _MSC_VER # pragma message ("Update 'optarg' in 'test_deprecate_positional_use_1' in 'clinic.test.c' to be keyword-only.") # else # warning "Update 'optarg' in 'test_deprecate_positional_use_1' in 'clinic.test.c' to be keyword-only." # endif - #elif PY_MAJOR_VERSION > 3 || \ - (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION > 14) || \ - (PY_MAJOR_VERSION == 3 && \ - PY_MINOR_VERSION == 14 && \ - PY_RELEASE_LEVEL == PY_RELEASE_LEVEL_GAMMA) + #elif PY_VERSION_HEX >= 0x030e00A0 # error "Update 'optarg' in 'test_deprecate_positional_use_1' in 'clinic.test.c' to be keyword-only." #endif if (nargs == 2) { if (PyErr_WarnEx(PyExc_DeprecationWarning, - "Using 'optarg' as a positional argument is deprecated. " - "It will become a keyword-only argument in Python 3.14.", 2)) + "Passing 'optarg' as a positional parameter is deprecated. " + "It will become a keyword-only parameter in Python 3.14.", 2)) { goto exit; } } - optarg = _PyLong_AsInt(args[1]); - if (optarg == -1 && PyErr_Occurred()) { - goto exit; - } + optarg = args[1]; skip_optional_pos: return_value = test_deprecate_positional_use_1_impl(module, pos, optarg); @@ -5500,15 +5491,15 @@ test_deprecate_positional_use_1(PyObject *module, PyObject *const *args, Py_ssiz static PyObject * test_deprecate_positional_use_1_impl(PyObject *module, PyObject *pos, - int optarg) -/*[clinic end generated code: output=259fc44a87bd82af input=fe31c35d217b4ed4]*/ + PyObject *optarg) +/*[clinic end generated code: output=129103413f791da9 input=84fc16857e7b0354]*/ /*[clinic input] test_deprecate_positional_use_2 pos: object * [from 3.14] - optarg: int + optarg: object has no default value, unlike test_deprecate_positional_use_1 [clinic start generated code]*/ @@ -5526,7 +5517,7 @@ PyDoc_STRVAR(test_deprecate_positional_use_2__doc__, static PyObject * test_deprecate_positional_use_2_impl(PyObject *module, PyObject *pos, - int optarg); + PyObject *optarg); static PyObject * test_deprecate_positional_use_2(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) @@ -5559,40 +5550,31 @@ test_deprecate_positional_use_2(PyObject *module, PyObject *const *args, Py_ssiz #undef KWTUPLE PyObject *argsbuf[2]; PyObject *pos; - int optarg; + PyObject *optarg; args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 2, 2, 0, argsbuf); if (!args) { goto exit; } pos = args[0]; - optarg = _PyLong_AsInt(args[1]); - if (optarg == -1 && PyErr_Occurred()) { - goto exit; - } - #if PY_MAJOR_VERSION == 3 && \ - PY_MINOR_VERSION == 14 && \ - PY_RELEASE_LEVEL == PY_RELEASE_LEVEL_ALPHA + #if PY_VERSION_HEX >= 0x030e00C0 # ifdef _MSC_VER # pragma message ("Update 'optarg' in 'test_deprecate_positional_use_2' in 'clinic.test.c' to be keyword-only.") # else # warning "Update 'optarg' in 'test_deprecate_positional_use_2' in 'clinic.test.c' to be keyword-only." # endif - #elif PY_MAJOR_VERSION > 3 || \ - (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION > 14) || \ - (PY_MAJOR_VERSION == 3 && \ - PY_MINOR_VERSION == 14 && \ - PY_RELEASE_LEVEL == PY_RELEASE_LEVEL_GAMMA) + #elif PY_VERSION_HEX >= 0x030e00A0 # error "Update 'optarg' in 'test_deprecate_positional_use_2' in 'clinic.test.c' to be keyword-only." #endif if (nargs == 2) { if (PyErr_WarnEx(PyExc_DeprecationWarning, - "Using 'optarg' as a positional argument is deprecated. " - "It will become a keyword-only argument in Python 3.14.", 2)) + "Passing 'optarg' as a positional parameter is deprecated. " + "It will become a keyword-only parameter in Python 3.14.", 2)) { goto exit; } } + optarg = args[1]; return_value = test_deprecate_positional_use_2_impl(module, pos, optarg); exit: @@ -5601,17 +5583,17 @@ test_deprecate_positional_use_2(PyObject *module, PyObject *const *args, Py_ssiz static PyObject * test_deprecate_positional_use_2_impl(PyObject *module, PyObject *pos, - int optarg) -/*[clinic end generated code: output=574ca16f9b214d1c input=1be0f94d673e51d3]*/ + PyObject *optarg) +/*[clinic end generated code: output=9afc2eb4085950b7 input=3ced298b4a297b2f]*/ /*[clinic input] test_deprecate_positional_use_3 pos: object * [from 3.14] - optarg: int + optarg: object * - kw: int + kw: object [clinic start generated code]*/ PyDoc_STRVAR(test_deprecate_positional_use_3__doc__, @@ -5624,7 +5606,7 @@ PyDoc_STRVAR(test_deprecate_positional_use_3__doc__, static PyObject * test_deprecate_positional_use_3_impl(PyObject *module, PyObject *pos, - int optarg, int kw); + PyObject *optarg, PyObject *kw); static PyObject * test_deprecate_positional_use_3(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) @@ -5657,68 +5639,50 @@ test_deprecate_positional_use_3(PyObject *module, PyObject *const *args, Py_ssiz #undef KWTUPLE PyObject *argsbuf[3]; PyObject *pos; - int optarg; - int kw; + PyObject *optarg; + PyObject *kw; args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 2, 2, 1, argsbuf); if (!args) { goto exit; } pos = args[0]; - optarg = _PyLong_AsInt(args[1]); - if (optarg == -1 && PyErr_Occurred()) { - goto exit; - } - #if PY_MAJOR_VERSION == 3 && \ - PY_MINOR_VERSION == 14 && \ - PY_RELEASE_LEVEL == PY_RELEASE_LEVEL_ALPHA + #if PY_VERSION_HEX >= 0x030e00C0 # ifdef _MSC_VER # pragma message ("Update 'optarg' in 'test_deprecate_positional_use_3' in 'clinic.test.c' to be keyword-only.") # else # warning "Update 'optarg' in 'test_deprecate_positional_use_3' in 'clinic.test.c' to be keyword-only." # endif - #elif PY_MAJOR_VERSION > 3 || \ - (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION > 14) || \ - (PY_MAJOR_VERSION == 3 && \ - PY_MINOR_VERSION == 14 && \ - PY_RELEASE_LEVEL == PY_RELEASE_LEVEL_GAMMA) + #elif PY_VERSION_HEX >= 0x030e00A0 # error "Update 'optarg' in 'test_deprecate_positional_use_3' in 'clinic.test.c' to be keyword-only." #endif if (nargs == 2) { if (PyErr_WarnEx(PyExc_DeprecationWarning, - "Using 'optarg' as a positional argument is deprecated. " - "It will become a keyword-only argument in Python 3.14.", 2)) + "Passing 'optarg' as a positional parameter is deprecated. " + "It will become a keyword-only parameter in Python 3.14.", 2)) { goto exit; } } - kw = _PyLong_AsInt(args[2]); - if (kw == -1 && PyErr_Occurred()) { - goto exit; - } - #if PY_MAJOR_VERSION == 3 && \ - PY_MINOR_VERSION == 14 && \ - PY_RELEASE_LEVEL == PY_RELEASE_LEVEL_ALPHA + optarg = args[1]; + #if PY_VERSION_HEX >= 0x030e00C0 # ifdef _MSC_VER # pragma message ("Update 'kw' in 'test_deprecate_positional_use_3' in 'clinic.test.c' to be keyword-only.") # else # warning "Update 'kw' in 'test_deprecate_positional_use_3' in 'clinic.test.c' to be keyword-only." # endif - #elif PY_MAJOR_VERSION > 3 || \ - (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION > 14) || \ - (PY_MAJOR_VERSION == 3 && \ - PY_MINOR_VERSION == 14 && \ - PY_RELEASE_LEVEL == PY_RELEASE_LEVEL_GAMMA) + #elif PY_VERSION_HEX >= 0x030e00A0 # error "Update 'kw' in 'test_deprecate_positional_use_3' in 'clinic.test.c' to be keyword-only." #endif if (nargs == 3) { if (PyErr_WarnEx(PyExc_DeprecationWarning, - "Using 'kw' as a positional argument is deprecated. " - "It will become a keyword-only argument in Python 3.14.", 2)) + "Passing 'kw' as a positional parameter is deprecated. " + "It will become a keyword-only parameter in Python 3.14.", 2)) { goto exit; } } + kw = args[2]; return_value = test_deprecate_positional_use_3_impl(module, pos, optarg, kw); exit: @@ -5727,5 +5691,5 @@ test_deprecate_positional_use_3(PyObject *module, PyObject *const *args, Py_ssiz static PyObject * test_deprecate_positional_use_3_impl(PyObject *module, PyObject *pos, - int optarg, int kw) -/*[clinic end generated code: output=9d7fd256b16dbc30 input=42f1c1240b70ddd8]*/ + PyObject *optarg, PyObject *kw) +/*[clinic end generated code: output=e067a37f03b5cea8 input=c19ac8533f05d314]*/ diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 76d182718de072..933ecee492f132 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -829,25 +829,19 @@ class CLanguage(Language): #endif /* !defined({methoddef_name}) */ """) DEPRECATED_POSITIONAL_PROTOTYPE: Final[str] = r""" - #if PY_MAJOR_VERSION == {major} && \ - PY_MINOR_VERSION == {minor} && \ - PY_RELEASE_LEVEL == PY_RELEASE_LEVEL_ALPHA + #if PY_VERSION_HEX >= 0x{major:02x}{minor:02x}00C0 # ifdef _MSC_VER # pragma message ("{cpp_warning}") # else # warning "{cpp_warning}" # endif - #elif PY_MAJOR_VERSION > {major} || \ - (PY_MAJOR_VERSION == {major} && PY_MINOR_VERSION > {minor}) || \ - (PY_MAJOR_VERSION == {major} && \ - PY_MINOR_VERSION == {minor} && \ - PY_RELEASE_LEVEL == PY_RELEASE_LEVEL_GAMMA) + #elif PY_VERSION_HEX >= 0x{major:02x}{minor:02x}00A0 # error "{cpp_warning}" #endif if (nargs == {pos}) {{{{ if (PyErr_WarnEx(PyExc_DeprecationWarning, - "Using {name!r} as a positional argument is deprecated. " - "It will become a keyword-only argument in Python {thenceforth}.", 2)) + "Passing {name!r} as a positional parameter is deprecated. " + "It will become a keyword-only parameter in Python {thenceforth}.", 2)) {{{{ goto exit; }}}} @@ -1237,8 +1231,8 @@ def deprecate_positional_use( name=param_name, pos=i+1, thenceforth=thenceforth, - major=major, - minor=minor, + major=int(major), + minor=int(minor), cpp_warning=cpp_warning, ) return normalize_snippet(code, indent=4) @@ -1257,11 +1251,11 @@ def deprecate_positional_use( parser_code.append("%s:" % add_label) add_label = None if not p.is_optional(): - parser_code.append(normalize_snippet(parsearg, indent=4)) if p.deprecated_positional: code = deprecate_positional_use( p.name, p.deprecated_positional) parser_code.append(code) + parser_code.append(normalize_snippet(parsearg, indent=4)) elif i < pos_only: add_label = 'skip_optional_posonly' parser_code.append(normalize_snippet(""" From 480a29fe4acd6c94ea36fa56d07f7a351704a7b5 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 5 Aug 2023 20:22:17 +0200 Subject: [PATCH 40/71] Address review: use stack level 1 --- Lib/test/clinic.test.c | 14 +++++++------- Tools/clinic/clinic.py | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Lib/test/clinic.test.c b/Lib/test/clinic.test.c index d0c696305d7e3d..5221d844adb007 100644 --- a/Lib/test/clinic.test.c +++ b/Lib/test/clinic.test.c @@ -5476,7 +5476,7 @@ test_deprecate_positional_use_1(PyObject *module, PyObject *const *args, Py_ssiz if (nargs == 2) { if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing 'optarg' as a positional parameter is deprecated. " - "It will become a keyword-only parameter in Python 3.14.", 2)) + "It will become a keyword-only parameter in Python 3.14.", 1)) { goto exit; } @@ -5492,7 +5492,7 @@ test_deprecate_positional_use_1(PyObject *module, PyObject *const *args, Py_ssiz static PyObject * test_deprecate_positional_use_1_impl(PyObject *module, PyObject *pos, PyObject *optarg) -/*[clinic end generated code: output=129103413f791da9 input=84fc16857e7b0354]*/ +/*[clinic end generated code: output=3dde639bffd82431 input=84fc16857e7b0354]*/ /*[clinic input] @@ -5569,7 +5569,7 @@ test_deprecate_positional_use_2(PyObject *module, PyObject *const *args, Py_ssiz if (nargs == 2) { if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing 'optarg' as a positional parameter is deprecated. " - "It will become a keyword-only parameter in Python 3.14.", 2)) + "It will become a keyword-only parameter in Python 3.14.", 1)) { goto exit; } @@ -5584,7 +5584,7 @@ test_deprecate_positional_use_2(PyObject *module, PyObject *const *args, Py_ssiz static PyObject * test_deprecate_positional_use_2_impl(PyObject *module, PyObject *pos, PyObject *optarg) -/*[clinic end generated code: output=9afc2eb4085950b7 input=3ced298b4a297b2f]*/ +/*[clinic end generated code: output=74e00bed5d1eb50f input=3ced298b4a297b2f]*/ /*[clinic input] @@ -5659,7 +5659,7 @@ test_deprecate_positional_use_3(PyObject *module, PyObject *const *args, Py_ssiz if (nargs == 2) { if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing 'optarg' as a positional parameter is deprecated. " - "It will become a keyword-only parameter in Python 3.14.", 2)) + "It will become a keyword-only parameter in Python 3.14.", 1)) { goto exit; } @@ -5677,7 +5677,7 @@ test_deprecate_positional_use_3(PyObject *module, PyObject *const *args, Py_ssiz if (nargs == 3) { if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing 'kw' as a positional parameter is deprecated. " - "It will become a keyword-only parameter in Python 3.14.", 2)) + "It will become a keyword-only parameter in Python 3.14.", 1)) { goto exit; } @@ -5692,4 +5692,4 @@ test_deprecate_positional_use_3(PyObject *module, PyObject *const *args, Py_ssiz static PyObject * test_deprecate_positional_use_3_impl(PyObject *module, PyObject *pos, PyObject *optarg, PyObject *kw) -/*[clinic end generated code: output=e067a37f03b5cea8 input=c19ac8533f05d314]*/ +/*[clinic end generated code: output=ba43d0a4835e7a75 input=c19ac8533f05d314]*/ diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 933ecee492f132..4ca39b5ef1f8f7 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -841,7 +841,7 @@ class CLanguage(Language): if (nargs == {pos}) {{{{ if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing {name!r} as a positional parameter is deprecated. " - "It will become a keyword-only parameter in Python {thenceforth}.", 2)) + "It will become a keyword-only parameter in Python {thenceforth}.", 1)) {{{{ goto exit; }}}} From b98aab3de0f5fc0e2ce9b9228e54d2927857c2c6 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 5 Aug 2023 23:40:05 +0200 Subject: [PATCH 41/71] Address Serhiy's second round of remarks: - Don't use the walrus operator for the "if match" - Fix preprocessor logic - Save save the deprecated thenceforth version string as a tuple of ints - Use sensible default value in test --- Lib/test/clinic.test.c | 28 ++++++++++++++-------------- Tools/clinic/clinic.py | 27 +++++++++++++++------------ 2 files changed, 29 insertions(+), 26 deletions(-) diff --git a/Lib/test/clinic.test.c b/Lib/test/clinic.test.c index 5221d844adb007..a628397e4ffe62 100644 --- a/Lib/test/clinic.test.c +++ b/Lib/test/clinic.test.c @@ -5402,12 +5402,12 @@ docstr_fallback_to_converter_default_impl(PyObject *module, str a) test_deprecate_positional_use_1 pos: object * [from 3.14] - optarg: object = 5 + optarg: object = None has a default value, unlike test_deprecate_positional_use_2 [clinic start generated code]*/ PyDoc_STRVAR(test_deprecate_positional_use_1__doc__, -"test_deprecate_positional_use_1($module, /, pos, optarg=5)\n" +"test_deprecate_positional_use_1($module, /, pos, optarg=None)\n" "--\n" "\n" "\n" @@ -5454,7 +5454,7 @@ test_deprecate_positional_use_1(PyObject *module, PyObject *const *args, Py_ssiz PyObject *argsbuf[2]; Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1; PyObject *pos; - PyObject *optarg = 5; + PyObject *optarg = Py_None; args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 2, 0, argsbuf); if (!args) { @@ -5465,13 +5465,13 @@ test_deprecate_positional_use_1(PyObject *module, PyObject *const *args, Py_ssiz goto skip_optional_pos; } #if PY_VERSION_HEX >= 0x030e00C0 + # error "Update 'optarg' in 'test_deprecate_positional_use_1' in 'clinic.test.c' to be keyword-only." + #elif PY_VERSION_HEX >= 0x030e00A0 # ifdef _MSC_VER # pragma message ("Update 'optarg' in 'test_deprecate_positional_use_1' in 'clinic.test.c' to be keyword-only.") # else # warning "Update 'optarg' in 'test_deprecate_positional_use_1' in 'clinic.test.c' to be keyword-only." # endif - #elif PY_VERSION_HEX >= 0x030e00A0 - # error "Update 'optarg' in 'test_deprecate_positional_use_1' in 'clinic.test.c' to be keyword-only." #endif if (nargs == 2) { if (PyErr_WarnEx(PyExc_DeprecationWarning, @@ -5492,7 +5492,7 @@ test_deprecate_positional_use_1(PyObject *module, PyObject *const *args, Py_ssiz static PyObject * test_deprecate_positional_use_1_impl(PyObject *module, PyObject *pos, PyObject *optarg) -/*[clinic end generated code: output=3dde639bffd82431 input=84fc16857e7b0354]*/ +/*[clinic end generated code: output=1a0fab8ac7ae0dc6 input=ead4a995482b22d3]*/ /*[clinic input] @@ -5558,13 +5558,13 @@ test_deprecate_positional_use_2(PyObject *module, PyObject *const *args, Py_ssiz } pos = args[0]; #if PY_VERSION_HEX >= 0x030e00C0 + # error "Update 'optarg' in 'test_deprecate_positional_use_2' in 'clinic.test.c' to be keyword-only." + #elif PY_VERSION_HEX >= 0x030e00A0 # ifdef _MSC_VER # pragma message ("Update 'optarg' in 'test_deprecate_positional_use_2' in 'clinic.test.c' to be keyword-only.") # else # warning "Update 'optarg' in 'test_deprecate_positional_use_2' in 'clinic.test.c' to be keyword-only." # endif - #elif PY_VERSION_HEX >= 0x030e00A0 - # error "Update 'optarg' in 'test_deprecate_positional_use_2' in 'clinic.test.c' to be keyword-only." #endif if (nargs == 2) { if (PyErr_WarnEx(PyExc_DeprecationWarning, @@ -5584,7 +5584,7 @@ test_deprecate_positional_use_2(PyObject *module, PyObject *const *args, Py_ssiz static PyObject * test_deprecate_positional_use_2_impl(PyObject *module, PyObject *pos, PyObject *optarg) -/*[clinic end generated code: output=74e00bed5d1eb50f input=3ced298b4a297b2f]*/ +/*[clinic end generated code: output=dd1b18342d4af153 input=3ced298b4a297b2f]*/ /*[clinic input] @@ -5648,13 +5648,13 @@ test_deprecate_positional_use_3(PyObject *module, PyObject *const *args, Py_ssiz } pos = args[0]; #if PY_VERSION_HEX >= 0x030e00C0 + # error "Update 'optarg' in 'test_deprecate_positional_use_3' in 'clinic.test.c' to be keyword-only." + #elif PY_VERSION_HEX >= 0x030e00A0 # ifdef _MSC_VER # pragma message ("Update 'optarg' in 'test_deprecate_positional_use_3' in 'clinic.test.c' to be keyword-only.") # else # warning "Update 'optarg' in 'test_deprecate_positional_use_3' in 'clinic.test.c' to be keyword-only." # endif - #elif PY_VERSION_HEX >= 0x030e00A0 - # error "Update 'optarg' in 'test_deprecate_positional_use_3' in 'clinic.test.c' to be keyword-only." #endif if (nargs == 2) { if (PyErr_WarnEx(PyExc_DeprecationWarning, @@ -5666,13 +5666,13 @@ test_deprecate_positional_use_3(PyObject *module, PyObject *const *args, Py_ssiz } optarg = args[1]; #if PY_VERSION_HEX >= 0x030e00C0 + # error "Update 'kw' in 'test_deprecate_positional_use_3' in 'clinic.test.c' to be keyword-only." + #elif PY_VERSION_HEX >= 0x030e00A0 # ifdef _MSC_VER # pragma message ("Update 'kw' in 'test_deprecate_positional_use_3' in 'clinic.test.c' to be keyword-only.") # else # warning "Update 'kw' in 'test_deprecate_positional_use_3' in 'clinic.test.c' to be keyword-only." # endif - #elif PY_VERSION_HEX >= 0x030e00A0 - # error "Update 'kw' in 'test_deprecate_positional_use_3' in 'clinic.test.c' to be keyword-only." #endif if (nargs == 3) { if (PyErr_WarnEx(PyExc_DeprecationWarning, @@ -5692,4 +5692,4 @@ test_deprecate_positional_use_3(PyObject *module, PyObject *const *args, Py_ssiz static PyObject * test_deprecate_positional_use_3_impl(PyObject *module, PyObject *pos, PyObject *optarg, PyObject *kw) -/*[clinic end generated code: output=ba43d0a4835e7a75 input=c19ac8533f05d314]*/ +/*[clinic end generated code: output=e9a2915901c0a9ca input=c19ac8533f05d314]*/ diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 4ca39b5ef1f8f7..53bd479573d07c 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -830,18 +830,18 @@ class CLanguage(Language): """) DEPRECATED_POSITIONAL_PROTOTYPE: Final[str] = r""" #if PY_VERSION_HEX >= 0x{major:02x}{minor:02x}00C0 + # error "{cpp_warning}" + #elif PY_VERSION_HEX >= 0x{major:02x}{minor:02x}00A0 # ifdef _MSC_VER # pragma message ("{cpp_warning}") # else # warning "{cpp_warning}" # endif - #elif PY_VERSION_HEX >= 0x{major:02x}{minor:02x}00A0 - # error "{cpp_warning}" #endif if (nargs == {pos}) {{{{ if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing {name!r} as a positional parameter is deprecated. " - "It will become a keyword-only parameter in Python {thenceforth}.", 1)) + "It will become a keyword-only parameter in Python {major}.{minor}.", 1)) {{{{ goto exit; }}}} @@ -1220,9 +1220,9 @@ def parser_body( def deprecate_positional_use( param_name: str, - thenceforth: str + thenceforth: VersionTuple, ) -> str: - major, minor = thenceforth.split(".") + major, minor = thenceforth assert isinstance(self.cpp.filename, str) source = os.path.basename(self.cpp.filename) cpp_warning = (f"Update {param_name!r} in {f.name!r} in " @@ -1230,9 +1230,8 @@ def deprecate_positional_use( code = self.DEPRECATED_POSITIONAL_PROTOTYPE.format( name=param_name, pos=i+1, - thenceforth=thenceforth, - major=int(major), - minor=int(minor), + major=major, + minor=minor, cpp_warning=cpp_warning, ) return normalize_snippet(code, indent=4) @@ -2649,7 +2648,7 @@ class Parameter: docstring: str = '' group: int = 0 # (`None` signifies that there is no deprecation) - deprecated_positional: str | None = None + deprecated_positional: VersionTuple | None = None right_bracket_count: int = dc.field(init=False, default=0) def __repr__(self) -> str: @@ -4466,12 +4465,15 @@ class ParamState(enum.IntEnum): RIGHT_SQUARE_AFTER = 6 +VersionTuple = tuple[int, int] + + class DSLParser: function: Function | None state: StateKeeper keyword_only: bool positional_only: bool - deprecated_positional: str | None + deprecated_positional: VersionTuple | None group: int parameter_state: ParamState indent: IndentStack @@ -4957,7 +4959,8 @@ def state_parameter(self, line: str) -> None: return line = line.lstrip() - if match := self.deprecate_posonly_re.match(line): + match = self.deprecate_posonly_re.match(line) + if match: self.parse_deprecated_positional(match.group(1)) return @@ -5289,7 +5292,7 @@ def parse_deprecated_positional(self, thenceforth: str) -> None: fail(f"Function {self.function.name!r}: '* [from ...]' must come before '*'") if self.deprecated_positional: fail(f"Function {self.function.name!r} uses '[from ...]' more than once.") - self.deprecated_positional = thenceforth + self.deprecated_positional = int(major), int(minor) def parse_star(self, function: Function) -> None: """Parse keyword-only parameter marker '*'.""" From eeb8187c6fd272d690cb37f42d29537f5ce73acf Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 5 Aug 2023 23:44:02 +0200 Subject: [PATCH 42/71] Fix merge: revert accidental removal of a clinic.test.c test method --- Lib/test/clinic.test.c | 69 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/Lib/test/clinic.test.c b/Lib/test/clinic.test.c index a628397e4ffe62..2458a83cb4f123 100644 --- a/Lib/test/clinic.test.c +++ b/Lib/test/clinic.test.c @@ -5006,6 +5006,75 @@ PyDoc_STRVAR(new_dest__doc__, /*[clinic end generated code: output=9cac703f51d90e84 input=090db8df4945576d]*/ +/*[clinic input] +mangled_c_keyword_identifier + i as int: int +The 'int' param should be mangled as 'int_value' +[clinic start generated code]*/ + +PyDoc_STRVAR(mangled_c_keyword_identifier__doc__, +"mangled_c_keyword_identifier($module, /, i)\n" +"--\n" +"\n" +"The \'int\' param should be mangled as \'int_value\'"); + +#define MANGLED_C_KEYWORD_IDENTIFIER_METHODDEF \ + {"mangled_c_keyword_identifier", _PyCFunction_CAST(mangled_c_keyword_identifier), METH_FASTCALL|METH_KEYWORDS, mangled_c_keyword_identifier__doc__}, + +static PyObject * +mangled_c_keyword_identifier_impl(PyObject *module, int int_value); + +static PyObject * +mangled_c_keyword_identifier(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 1 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(i), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"i", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "mangled_c_keyword_identifier", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + int int_value; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf); + if (!args) { + goto exit; + } + int_value = _PyLong_AsInt(args[0]); + if (int_value == -1 && PyErr_Occurred()) { + goto exit; + } + return_value = mangled_c_keyword_identifier_impl(module, int_value); + +exit: + return return_value; +} + +static PyObject * +mangled_c_keyword_identifier_impl(PyObject *module, int int_value) +/*[clinic end generated code: output=c049d7d79be26cda input=060876448ab567a2]*/ + + /*[clinic input] bool_return -> bool [clinic start generated code]*/ From 6c6dd108bdadabbea704fe8aa36d8b7ded2eba50 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sun, 6 Aug 2023 00:18:42 +0200 Subject: [PATCH 43/71] Address Alex and Serhiy's doc remarks --- Doc/howto/clinic.rst | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/Doc/howto/clinic.rst b/Doc/howto/clinic.rst index 994e18895fe391..288ce3decdda4c 100644 --- a/Doc/howto/clinic.rst +++ b/Doc/howto/clinic.rst @@ -1895,14 +1895,15 @@ blocks embedded in Python files look slightly different. They look like this: #/*[python checksum:...]*/ -How to deprecate positional use of optional parameters ------------------------------------------------------- +How to deprecate passing parameters positionally +------------------------------------------------ Argument Clinic provides syntax that makes it possible to generate code that -deprecates positional use of optional parameters. -For example, say we've got a module level function :py:func:`!foo.myfunc` that -takes three :class:`int` parameters: -optional parameters *a* and *b*, and keyword-only parameter *c*:: +deprecates passing :term:`arguments ` positionally. +For example, say we've got a module-level function :py:func:`!foo.myfunc` +that has three :term:`parameters `: +positional-or-keyword parameters *a* and *b*, +and a :ref:`keyword-only ` parameter *c*:: /*[clinic input] module foo @@ -1913,9 +1914,10 @@ optional parameters *a* and *b*, and keyword-only parameter *c*:: c: int [clinic start generated output]*/ -We now want to make the *b* parameter keyword-only from Python 3.14 and onward, -and since :py:func:`!myfunc` is a public API, -we must follow :pep:`387` and issue a deprecation warning. +We now want to make the *b* parameter keyword-only. +For this example, imagine we're in the development phase for Python 3.12, +meaning we can deprecate positional use of *b* earliest in Python 3.14 and onward +(see :pep:`387` -- *Backwards Compatibility Policy*). We can do that using the ``* [from ...]``` syntax, by adding the line ``* [from 3.14]`` right above the *b* parameter:: @@ -1933,14 +1935,14 @@ Next, regenerate Argument Clinic code (``make clinic``), and add unit tests for the new behaviour. The generated code will now emit a :exc:`DeprecationWarning` -when an the optional parameter *b* is used as a positional paremeter. +when an :term:`argument` for the :term:`parameter` *b* is passed positionally. C preprocessor directives are also generated for emitting compiler warnings if the ``* [from ...]`` line has not been removed from the Argument Clinic input when the deprecation period is over, -which means when the beta phase of the specified Python version kicks in. +which means when the alpha phase of the specified Python version kicks in. Let's return to our example and assume Python 3.14 development has entered the -beta phase, and we forgot all about updating the Argument Clinic code for +alpha phase, and we forgot all about updating the Argument Clinic code for :py:func:`!myfunc`. Luckily for us, compiler warnings are now generated: @@ -1969,6 +1971,6 @@ and update your unit tests to reflect the new behaviour. .. note:: - If you forget to update your clinic code during the target beta phase, the - copmiler warning will turn into a compiler error when the release candidate - phase kicks in. + If you forget to update your input block during the alpha and beta phases, + the compiler warning will turn into a compiler error when the + release candidate phase begins. From 47a8979fb2e39133168a530c32eb69878361c4b6 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sun, 6 Aug 2023 16:44:11 +0200 Subject: [PATCH 44/71] Further improvements: - Improve contents of the warning messages: - Pretty print parameter list - Use full path to target source file - Use fully qualified name of function/method - Consolidate preprocessor warnings/errors and deprecation messages to a single block - Refactor out a helper method for the new generated code - Place the code for deprecations before the parsing code --- Lib/test/clinic.test.c | 92 ++++++++++---------------- Modules/_sqlite/clinic/connection.c.h | 16 ++++- Modules/_sqlite/connection.c | 3 +- Tools/clinic/clinic.py | 94 ++++++++++++++++++--------- 4 files changed, 113 insertions(+), 92 deletions(-) diff --git a/Lib/test/clinic.test.c b/Lib/test/clinic.test.c index 2458a83cb4f123..2a82784b02960c 100644 --- a/Lib/test/clinic.test.c +++ b/Lib/test/clinic.test.c @@ -5525,31 +5525,28 @@ test_deprecate_positional_use_1(PyObject *module, PyObject *const *args, Py_ssiz PyObject *pos; PyObject *optarg = Py_None; - args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 2, 0, argsbuf); - if (!args) { - goto exit; - } - pos = args[0]; - if (!noptargs) { - goto skip_optional_pos; - } #if PY_VERSION_HEX >= 0x030e00C0 - # error "Update 'optarg' in 'test_deprecate_positional_use_1' in 'clinic.test.c' to be keyword-only." + # error "In /Users/erlend.aasland/src/cpython.git/Lib/test/clinic.test.c, update parameter(s) optarg in the clinic input of 'test_deprecate_positional_use_1' to be keyword-only." #elif PY_VERSION_HEX >= 0x030e00A0 # ifdef _MSC_VER - # pragma message ("Update 'optarg' in 'test_deprecate_positional_use_1' in 'clinic.test.c' to be keyword-only.") + # pragma message ("In /Users/erlend.aasland/src/cpython.git/Lib/test/clinic.test.c, update parameter(s) optarg in the clinic input of 'test_deprecate_positional_use_1' to be keyword-only.") # else - # warning "Update 'optarg' in 'test_deprecate_positional_use_1' in 'clinic.test.c' to be keyword-only." + # warning "In /Users/erlend.aasland/src/cpython.git/Lib/test/clinic.test.c, update parameter(s) optarg in the clinic input of 'test_deprecate_positional_use_1' to be keyword-only." # endif #endif if (nargs == 2) { - if (PyErr_WarnEx(PyExc_DeprecationWarning, - "Passing 'optarg' as a positional parameter is deprecated. " - "It will become a keyword-only parameter in Python 3.14.", 1)) - { + if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing optarg as a positional parameter is deprecated. It will become a keyword-only parameter in Python 3.14.", 1)) { goto exit; } } + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 2, 0, argsbuf); + if (!args) { + goto exit; + } + pos = args[0]; + if (!noptargs) { + goto skip_optional_pos; + } optarg = args[1]; skip_optional_pos: return_value = test_deprecate_positional_use_1_impl(module, pos, optarg); @@ -5561,7 +5558,7 @@ test_deprecate_positional_use_1(PyObject *module, PyObject *const *args, Py_ssiz static PyObject * test_deprecate_positional_use_1_impl(PyObject *module, PyObject *pos, PyObject *optarg) -/*[clinic end generated code: output=1a0fab8ac7ae0dc6 input=ead4a995482b22d3]*/ +/*[clinic end generated code: output=a23c5721eacca803 input=ead4a995482b22d3]*/ /*[clinic input] @@ -5621,28 +5618,25 @@ test_deprecate_positional_use_2(PyObject *module, PyObject *const *args, Py_ssiz PyObject *pos; PyObject *optarg; - args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 2, 2, 0, argsbuf); - if (!args) { - goto exit; - } - pos = args[0]; #if PY_VERSION_HEX >= 0x030e00C0 - # error "Update 'optarg' in 'test_deprecate_positional_use_2' in 'clinic.test.c' to be keyword-only." + # error "In /Users/erlend.aasland/src/cpython.git/Lib/test/clinic.test.c, update parameter(s) optarg in the clinic input of 'test_deprecate_positional_use_2' to be keyword-only." #elif PY_VERSION_HEX >= 0x030e00A0 # ifdef _MSC_VER - # pragma message ("Update 'optarg' in 'test_deprecate_positional_use_2' in 'clinic.test.c' to be keyword-only.") + # pragma message ("In /Users/erlend.aasland/src/cpython.git/Lib/test/clinic.test.c, update parameter(s) optarg in the clinic input of 'test_deprecate_positional_use_2' to be keyword-only.") # else - # warning "Update 'optarg' in 'test_deprecate_positional_use_2' in 'clinic.test.c' to be keyword-only." + # warning "In /Users/erlend.aasland/src/cpython.git/Lib/test/clinic.test.c, update parameter(s) optarg in the clinic input of 'test_deprecate_positional_use_2' to be keyword-only." # endif #endif if (nargs == 2) { - if (PyErr_WarnEx(PyExc_DeprecationWarning, - "Passing 'optarg' as a positional parameter is deprecated. " - "It will become a keyword-only parameter in Python 3.14.", 1)) - { + if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing optarg as a positional parameter is deprecated. It will become a keyword-only parameter in Python 3.14.", 1)) { goto exit; } } + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 2, 2, 0, argsbuf); + if (!args) { + goto exit; + } + pos = args[0]; optarg = args[1]; return_value = test_deprecate_positional_use_2_impl(module, pos, optarg); @@ -5653,7 +5647,7 @@ test_deprecate_positional_use_2(PyObject *module, PyObject *const *args, Py_ssiz static PyObject * test_deprecate_positional_use_2_impl(PyObject *module, PyObject *pos, PyObject *optarg) -/*[clinic end generated code: output=dd1b18342d4af153 input=3ced298b4a297b2f]*/ +/*[clinic end generated code: output=3d6f116d67f53bb9 input=3ced298b4a297b2f]*/ /*[clinic input] @@ -5711,46 +5705,26 @@ test_deprecate_positional_use_3(PyObject *module, PyObject *const *args, Py_ssiz PyObject *optarg; PyObject *kw; - args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 2, 2, 1, argsbuf); - if (!args) { - goto exit; - } - pos = args[0]; #if PY_VERSION_HEX >= 0x030e00C0 - # error "Update 'optarg' in 'test_deprecate_positional_use_3' in 'clinic.test.c' to be keyword-only." + # error "In /Users/erlend.aasland/src/cpython.git/Lib/test/clinic.test.c, update parameter(s) optarg in the clinic input of 'test_deprecate_positional_use_3' to be keyword-only." #elif PY_VERSION_HEX >= 0x030e00A0 # ifdef _MSC_VER - # pragma message ("Update 'optarg' in 'test_deprecate_positional_use_3' in 'clinic.test.c' to be keyword-only.") + # pragma message ("In /Users/erlend.aasland/src/cpython.git/Lib/test/clinic.test.c, update parameter(s) optarg in the clinic input of 'test_deprecate_positional_use_3' to be keyword-only.") # else - # warning "Update 'optarg' in 'test_deprecate_positional_use_3' in 'clinic.test.c' to be keyword-only." + # warning "In /Users/erlend.aasland/src/cpython.git/Lib/test/clinic.test.c, update parameter(s) optarg in the clinic input of 'test_deprecate_positional_use_3' to be keyword-only." # endif #endif if (nargs == 2) { - if (PyErr_WarnEx(PyExc_DeprecationWarning, - "Passing 'optarg' as a positional parameter is deprecated. " - "It will become a keyword-only parameter in Python 3.14.", 1)) - { + if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing optarg as a positional parameter is deprecated. It will become a keyword-only parameter in Python 3.14.", 1)) { goto exit; } } - optarg = args[1]; - #if PY_VERSION_HEX >= 0x030e00C0 - # error "Update 'kw' in 'test_deprecate_positional_use_3' in 'clinic.test.c' to be keyword-only." - #elif PY_VERSION_HEX >= 0x030e00A0 - # ifdef _MSC_VER - # pragma message ("Update 'kw' in 'test_deprecate_positional_use_3' in 'clinic.test.c' to be keyword-only.") - # else - # warning "Update 'kw' in 'test_deprecate_positional_use_3' in 'clinic.test.c' to be keyword-only." - # endif - #endif - if (nargs == 3) { - if (PyErr_WarnEx(PyExc_DeprecationWarning, - "Passing 'kw' as a positional parameter is deprecated. " - "It will become a keyword-only parameter in Python 3.14.", 1)) - { - goto exit; - } + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 2, 2, 1, argsbuf); + if (!args) { + goto exit; } + pos = args[0]; + optarg = args[1]; kw = args[2]; return_value = test_deprecate_positional_use_3_impl(module, pos, optarg, kw); @@ -5761,4 +5735,4 @@ test_deprecate_positional_use_3(PyObject *module, PyObject *const *args, Py_ssiz static PyObject * test_deprecate_positional_use_3_impl(PyObject *module, PyObject *pos, PyObject *optarg, PyObject *kw) -/*[clinic end generated code: output=e9a2915901c0a9ca input=c19ac8533f05d314]*/ +/*[clinic end generated code: output=32fc07795a7eb5fa input=c19ac8533f05d314]*/ diff --git a/Modules/_sqlite/clinic/connection.c.h b/Modules/_sqlite/clinic/connection.c.h index e869d7d9e9384c..11d736c40cccbb 100644 --- a/Modules/_sqlite/clinic/connection.c.h +++ b/Modules/_sqlite/clinic/connection.c.h @@ -59,6 +59,20 @@ pysqlite_connection_init(PyObject *self, PyObject *args, PyObject *kwargs) int uri = 0; enum autocommit_mode autocommit = LEGACY_TRANSACTION_CONTROL; + #if PY_VERSION_HEX >= 0x030d00C0 + # error "In /Users/erlend.aasland/src/cpython.git/Modules/_sqlite/connection.c, update parameter(s) 'timeout', 'detect_types', 'isolation_level', 'check_same_thread', 'factory', 'cached_statements' and 'uri' in the clinic input of '_sqlite3.Connection.__init__' to be keyword-only." + #elif PY_VERSION_HEX >= 0x030d00A0 + # ifdef _MSC_VER + # pragma message ("In /Users/erlend.aasland/src/cpython.git/Modules/_sqlite/connection.c, update parameter(s) 'timeout', 'detect_types', 'isolation_level', 'check_same_thread', 'factory', 'cached_statements' and 'uri' in the clinic input of '_sqlite3.Connection.__init__' to be keyword-only.") + # else + # warning "In /Users/erlend.aasland/src/cpython.git/Modules/_sqlite/connection.c, update parameter(s) 'timeout', 'detect_types', 'isolation_level', 'check_same_thread', 'factory', 'cached_statements' and 'uri' in the clinic input of '_sqlite3.Connection.__init__' to be keyword-only." + # endif + #endif + if (nargs == 2) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing 'timeout', 'detect_types', 'isolation_level', 'check_same_thread', 'factory', 'cached_statements' and 'uri' as positional parameters is deprecated. They will become keyword-only parameters in Python 3.13.", 1)) { + goto exit; + } + } fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser, 1, 8, 0, argsbuf); if (!fastargs) { goto exit; @@ -1659,4 +1673,4 @@ getconfig(pysqlite_Connection *self, PyObject *arg) #ifndef DESERIALIZE_METHODDEF #define DESERIALIZE_METHODDEF #endif /* !defined(DESERIALIZE_METHODDEF) */ -/*[clinic end generated code: output=d3c6cb9326736ea5 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=900a2af28b0f6231 input=a9049054013a1b77]*/ diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c index ddd7ace81198bb..83230ccda01787 100644 --- a/Modules/_sqlite/connection.c +++ b/Modules/_sqlite/connection.c @@ -217,6 +217,7 @@ class sqlite3_int64_converter(CConverter): _sqlite3.Connection.__init__ as pysqlite_connection_init database: object + * [from 3.13] timeout: double = 5.0 detect_types: int = 0 isolation_level: IsolationLevel = "" @@ -235,7 +236,7 @@ pysqlite_connection_init_impl(pysqlite_Connection *self, PyObject *database, int check_same_thread, PyObject *factory, int cache_size, int uri, enum autocommit_mode autocommit) -/*[clinic end generated code: output=cba057313ea7712f input=9b0ab6c12f674fa3]*/ +/*[clinic end generated code: output=cba057313ea7712f input=fd29f01d6780764d]*/ { if (PySys_Audit("sqlite3.connect", "O", database) < 0) { return -1; diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 247a6c744ed0de..c31a025bba8134 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -830,19 +830,16 @@ class CLanguage(Language): """) DEPRECATED_POSITIONAL_PROTOTYPE: Final[str] = r""" #if PY_VERSION_HEX >= 0x{major:02x}{minor:02x}00C0 - # error "{cpp_warning}" + # error "{cpp_message}" #elif PY_VERSION_HEX >= 0x{major:02x}{minor:02x}00A0 # ifdef _MSC_VER - # pragma message ("{cpp_warning}") + # pragma message ("{cpp_message}") # else - # warning "{cpp_warning}" + # warning "{cpp_message}" # endif #endif if (nargs == {pos}) {{{{ - if (PyErr_WarnEx(PyExc_DeprecationWarning, - "Passing {name!r} as a positional parameter is deprecated. " - "It will become a keyword-only parameter in Python {major}.{minor}.", 1)) - {{{{ + if (PyErr_WarnEx(PyExc_DeprecationWarning, "{depr_message}", 1)) {{{{ goto exit; }}}} }}}} @@ -869,6 +866,55 @@ def render( function = o return self.render_function(clinic, function) + def deprecate_positional_use( + self, + func: Function, + params: dict[int, Parameter], + ) -> str: + assert len(params) > 0 + # FIXME: Handle multiple deprecation levels + # FIXME: For now, assume there's only one level + code_blocks: list[str] = [] + names = [repr(p.name) for p in iter(params.values())] + first_pos, first_param = next(iter(params.items())) + + # Pretty-print list of names. + match len(params): + case 1: + pstr = first_param.name + case 2: + pstr = " and ".join(names) + case _: + pstr = ", ".join(names[:-1]) + " and " + names[-1] + + # Format the preprocessor warning and error messages. + source = self.cpp.filename + thenceforth = first_param.deprecated_positional # FIXME + major, minor = thenceforth + assert isinstance(self.cpp.filename, str) + cpp_message = ( + f"In {source}, update parameter(s) {pstr} in the clinic " + f"input of {func.full_name!r} to be keyword-only." + ) + # Format the deprecation message. + singular = ( + f"Passing {pstr} as a positional parameter is deprecated. " + f"It will become a keyword-only parameter in Python {major}.{minor}." + ) + plural = ( + f"Passing {pstr} as positional parameters is deprecated. " + f"They will become keyword-only parameters in Python {major}.{minor}." + ) + # Format and return the code block. + code = self.DEPRECATED_POSITIONAL_PROTOTYPE.format( + pos=first_pos+1, + major=major, + minor=minor, + cpp_message=cpp_message, + depr_message=singular if len(params) == 1 else plural, + ) + return normalize_snippet(code, indent=4) + def docstring_for_c_string( self, f: Function @@ -1218,24 +1264,7 @@ def parser_body( flags = 'METH_METHOD|' + flags parser_prototype = self.PARSER_PROTOTYPE_DEF_CLASS - def deprecate_positional_use( - param_name: str, - thenceforth: VersionTuple, - ) -> str: - major, minor = thenceforth - assert isinstance(self.cpp.filename, str) - source = os.path.basename(self.cpp.filename) - cpp_warning = (f"Update {param_name!r} in {f.name!r} in " - f"{source!r} to be keyword-only.") - code = self.DEPRECATED_POSITIONAL_PROTOTYPE.format( - name=param_name, - pos=i+1, - major=major, - minor=minor, - cpp_warning=cpp_warning, - ) - return normalize_snippet(code, indent=4) - + deprecated_positionals: dict[int, Parameter] = {} add_label: str | None = None for i, p in enumerate(parameters): if isinstance(p.converter, defining_class_converter): @@ -1251,9 +1280,7 @@ def deprecate_positional_use( add_label = None if not p.is_optional(): if p.deprecated_positional: - code = deprecate_positional_use( - p.name, p.deprecated_positional) - parser_code.append(code) + deprecated_positionals[i] = p parser_code.append(normalize_snippet(parsearg, indent=4)) elif i < pos_only: add_label = 'skip_optional_posonly' @@ -1284,9 +1311,7 @@ def deprecate_positional_use( }} """ % add_label, indent=4)) if p.deprecated_positional: - code = deprecate_positional_use( - p.name, p.deprecated_positional) - parser_code.append(code) + deprecated_positionals[i] = p if i + 1 == len(parameters): parser_code.append(normalize_snippet(parsearg, indent=4)) else: @@ -1302,6 +1327,12 @@ def deprecate_positional_use( }} """ % add_label, indent=4)) + if deprecated_positionals: + code = self.deprecate_positional_use(f, deprecated_positionals) + assert parser_code is not None + # Insert the deprecation code before parameter parsing. + parser_code.insert(0, code) + if parser_code is not None: if add_label: parser_code.append("%s:" % add_label) @@ -5302,6 +5333,7 @@ def parse_star(self, function: Function) -> None: """Parse keyword-only parameter marker '*'.""" if self.keyword_only: fail(f"Function {function.name!r} uses '*' more than once.") + self.deprecated_positional = False self.keyword_only = True def parse_opening_square_bracket(self, function: Function) -> None: From 02eba96951c19fbe22564298fbaab6c011642d62 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sun, 6 Aug 2023 16:48:29 +0200 Subject: [PATCH 45/71] Make mypy happy (quickfix) --- Tools/clinic/clinic.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index c31a025bba8134..d6d25f7382c5a5 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -890,6 +890,7 @@ def deprecate_positional_use( # Format the preprocessor warning and error messages. source = self.cpp.filename thenceforth = first_param.deprecated_positional # FIXME + assert thenceforth is not None major, minor = thenceforth assert isinstance(self.cpp.filename, str) cpp_message = ( @@ -5333,7 +5334,7 @@ def parse_star(self, function: Function) -> None: """Parse keyword-only parameter marker '*'.""" if self.keyword_only: fail(f"Function {function.name!r} uses '*' more than once.") - self.deprecated_positional = False + self.deprecated_positional = None self.keyword_only = True def parse_opening_square_bracket(self, function: Function) -> None: From fa774898746635496ed6890d9b87b6d35b6fc185 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sun, 6 Aug 2023 17:02:04 +0200 Subject: [PATCH 46/71] Revert sqlite3 test --- Modules/_sqlite/clinic/connection.c.h | 16 +--------------- Modules/_sqlite/connection.c | 3 +-- 2 files changed, 2 insertions(+), 17 deletions(-) diff --git a/Modules/_sqlite/clinic/connection.c.h b/Modules/_sqlite/clinic/connection.c.h index 11d736c40cccbb..e869d7d9e9384c 100644 --- a/Modules/_sqlite/clinic/connection.c.h +++ b/Modules/_sqlite/clinic/connection.c.h @@ -59,20 +59,6 @@ pysqlite_connection_init(PyObject *self, PyObject *args, PyObject *kwargs) int uri = 0; enum autocommit_mode autocommit = LEGACY_TRANSACTION_CONTROL; - #if PY_VERSION_HEX >= 0x030d00C0 - # error "In /Users/erlend.aasland/src/cpython.git/Modules/_sqlite/connection.c, update parameter(s) 'timeout', 'detect_types', 'isolation_level', 'check_same_thread', 'factory', 'cached_statements' and 'uri' in the clinic input of '_sqlite3.Connection.__init__' to be keyword-only." - #elif PY_VERSION_HEX >= 0x030d00A0 - # ifdef _MSC_VER - # pragma message ("In /Users/erlend.aasland/src/cpython.git/Modules/_sqlite/connection.c, update parameter(s) 'timeout', 'detect_types', 'isolation_level', 'check_same_thread', 'factory', 'cached_statements' and 'uri' in the clinic input of '_sqlite3.Connection.__init__' to be keyword-only.") - # else - # warning "In /Users/erlend.aasland/src/cpython.git/Modules/_sqlite/connection.c, update parameter(s) 'timeout', 'detect_types', 'isolation_level', 'check_same_thread', 'factory', 'cached_statements' and 'uri' in the clinic input of '_sqlite3.Connection.__init__' to be keyword-only." - # endif - #endif - if (nargs == 2) { - if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing 'timeout', 'detect_types', 'isolation_level', 'check_same_thread', 'factory', 'cached_statements' and 'uri' as positional parameters is deprecated. They will become keyword-only parameters in Python 3.13.", 1)) { - goto exit; - } - } fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser, 1, 8, 0, argsbuf); if (!fastargs) { goto exit; @@ -1673,4 +1659,4 @@ getconfig(pysqlite_Connection *self, PyObject *arg) #ifndef DESERIALIZE_METHODDEF #define DESERIALIZE_METHODDEF #endif /* !defined(DESERIALIZE_METHODDEF) */ -/*[clinic end generated code: output=900a2af28b0f6231 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=d3c6cb9326736ea5 input=a9049054013a1b77]*/ diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c index 83230ccda01787..ddd7ace81198bb 100644 --- a/Modules/_sqlite/connection.c +++ b/Modules/_sqlite/connection.c @@ -217,7 +217,6 @@ class sqlite3_int64_converter(CConverter): _sqlite3.Connection.__init__ as pysqlite_connection_init database: object - * [from 3.13] timeout: double = 5.0 detect_types: int = 0 isolation_level: IsolationLevel = "" @@ -236,7 +235,7 @@ pysqlite_connection_init_impl(pysqlite_Connection *self, PyObject *database, int check_same_thread, PyObject *factory, int cache_size, int uri, enum autocommit_mode autocommit) -/*[clinic end generated code: output=cba057313ea7712f input=fd29f01d6780764d]*/ +/*[clinic end generated code: output=cba057313ea7712f input=9b0ab6c12f674fa3]*/ { if (PySys_Audit("sqlite3.connect", "O", database) < 0) { return -1; From 87a7f20b3afbbf96120df4d6703be7582cb6bd1a Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sun, 6 Aug 2023 17:02:30 +0200 Subject: [PATCH 47/71] Use try...except ValueError trick for neater code --- Tools/clinic/clinic.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index d6d25f7382c5a5..d692fd6e1d9481 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -5318,17 +5318,18 @@ def parse_deprecated_positional(self, thenceforth: str) -> None: "expected to be '', where 'major' and 'minor' " "are digits." ) - major, minor = thenceforth.split(".") - if not major.isdigit() or not minor.isdigit(): - fail( - f"Function {self.function.name!r}, '* [from ]': " - f"'major' and 'minor' must be digits, not {major!r} and {minor!r}" - ) if self.keyword_only: fail(f"Function {self.function.name!r}: '* [from ...]' must come before '*'") if self.deprecated_positional: fail(f"Function {self.function.name!r} uses '[from ...]' more than once.") - self.deprecated_positional = int(major), int(minor) + try: + major, minor = thenceforth.split(".") + self.deprecated_positional = int(major), int(minor) + except ValueError: + fail( + f"Function {self.function.name!r}, '* [from ]': " + f"'major' and 'minor' must be digits, not {major!r} and {minor!r}" + ) def parse_star(self, function: Function) -> None: """Parse keyword-only parameter marker '*'.""" From f36bc0526f271a389413a394ee080f5001a4da1d Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sun, 6 Aug 2023 17:03:08 +0200 Subject: [PATCH 48/71] Move VersionTuple to above Parameter class --- Tools/clinic/clinic.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index d692fd6e1d9481..847ff04cb2dc1c 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -2669,6 +2669,9 @@ def copy(self, **overrides: Any) -> Function: return f +VersionTuple = tuple[int, int] + + @dc.dataclass(repr=False, slots=True) class Parameter: """ @@ -4501,9 +4504,6 @@ class ParamState(enum.IntEnum): RIGHT_SQUARE_AFTER = 6 -VersionTuple = tuple[int, int] - - class DSLParser: function: Function | None state: StateKeeper From 5e65020a89185a62d6e085bf7b60c7c2c63073b0 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sun, 6 Aug 2023 17:10:00 +0200 Subject: [PATCH 49/71] Add more tests for illegal formats --- Lib/test/test_clinic.py | 16 +++++++++++++++- Tools/clinic/clinic.py | 2 +- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index 682ab61c85f98a..7668937d988965 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -1507,7 +1507,21 @@ def test_depr_star_invalid_format_2(self): """ err = ( "Function 'bar', '* [from ]': " - "'major' and 'minor' must be digits, not 'a' and 'b'" + "'major' and 'minor' must be digits, not 'a.b'" + ) + self.expect_failure(block, err, lineno=3) + + def test_depr_star_invalid_format_3(self): + block = """ + module foo + foo.bar + this: int + * [from 1.2.3] + Docstring. + """ + err = ( + "Function 'bar', '* [from ]': " + "'major' and 'minor' must be digits, not '1.2.3'" ) self.expect_failure(block, err, lineno=3) diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 847ff04cb2dc1c..43ea719ab6a8c5 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -5328,7 +5328,7 @@ def parse_deprecated_positional(self, thenceforth: str) -> None: except ValueError: fail( f"Function {self.function.name!r}, '* [from ]': " - f"'major' and 'minor' must be digits, not {major!r} and {minor!r}" + f"'major' and 'minor' must be digits, not {thenceforth!r}" ) def parse_star(self, function: Function) -> None: From f1f311a03403c6b504a025f4c12638e96d6ce71c Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sun, 6 Aug 2023 17:11:30 +0200 Subject: [PATCH 50/71] Don't link to keyword-only param glossary section --- Doc/howto/clinic.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Doc/howto/clinic.rst b/Doc/howto/clinic.rst index 288ce3decdda4c..8b77b7acc8a7ee 100644 --- a/Doc/howto/clinic.rst +++ b/Doc/howto/clinic.rst @@ -1902,8 +1902,7 @@ Argument Clinic provides syntax that makes it possible to generate code that deprecates passing :term:`arguments ` positionally. For example, say we've got a module-level function :py:func:`!foo.myfunc` that has three :term:`parameters `: -positional-or-keyword parameters *a* and *b*, -and a :ref:`keyword-only ` parameter *c*:: +positional-or-keyword parameters *a* and *b*, and a keyword-only parameter *c*:: /*[clinic input] module foo From 83de0b89ab05d7d36ce4942715004d339ba061b8 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sun, 6 Aug 2023 17:13:27 +0200 Subject: [PATCH 51/71] No need for iter --- Tools/clinic/clinic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 43ea719ab6a8c5..20c21dc309e9fc 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -875,7 +875,7 @@ def deprecate_positional_use( # FIXME: Handle multiple deprecation levels # FIXME: For now, assume there's only one level code_blocks: list[str] = [] - names = [repr(p.name) for p in iter(params.values())] + names = [repr(p.name) for p in params.values()] first_pos, first_param = next(iter(params.items())) # Pretty-print list of names. From b10dc9c72d5a34c7afc95826fa976e8bfadb7fd0 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sun, 6 Aug 2023 17:47:53 +0200 Subject: [PATCH 52/71] For now, only use the basename of the source file --- Lib/test/clinic.test.c | 24 ++++++++++++------------ Tools/clinic/clinic.py | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Lib/test/clinic.test.c b/Lib/test/clinic.test.c index 2a82784b02960c..518287df5123d8 100644 --- a/Lib/test/clinic.test.c +++ b/Lib/test/clinic.test.c @@ -5526,12 +5526,12 @@ test_deprecate_positional_use_1(PyObject *module, PyObject *const *args, Py_ssiz PyObject *optarg = Py_None; #if PY_VERSION_HEX >= 0x030e00C0 - # error "In /Users/erlend.aasland/src/cpython.git/Lib/test/clinic.test.c, update parameter(s) optarg in the clinic input of 'test_deprecate_positional_use_1' to be keyword-only." + # error "In clinic.test.c, update parameter(s) optarg in the clinic input of 'test_deprecate_positional_use_1' to be keyword-only." #elif PY_VERSION_HEX >= 0x030e00A0 # ifdef _MSC_VER - # pragma message ("In /Users/erlend.aasland/src/cpython.git/Lib/test/clinic.test.c, update parameter(s) optarg in the clinic input of 'test_deprecate_positional_use_1' to be keyword-only.") + # pragma message ("In clinic.test.c, update parameter(s) optarg in the clinic input of 'test_deprecate_positional_use_1' to be keyword-only.") # else - # warning "In /Users/erlend.aasland/src/cpython.git/Lib/test/clinic.test.c, update parameter(s) optarg in the clinic input of 'test_deprecate_positional_use_1' to be keyword-only." + # warning "In clinic.test.c, update parameter(s) optarg in the clinic input of 'test_deprecate_positional_use_1' to be keyword-only." # endif #endif if (nargs == 2) { @@ -5558,7 +5558,7 @@ test_deprecate_positional_use_1(PyObject *module, PyObject *const *args, Py_ssiz static PyObject * test_deprecate_positional_use_1_impl(PyObject *module, PyObject *pos, PyObject *optarg) -/*[clinic end generated code: output=a23c5721eacca803 input=ead4a995482b22d3]*/ +/*[clinic end generated code: output=56f4e1fe9bcd8d19 input=ead4a995482b22d3]*/ /*[clinic input] @@ -5619,12 +5619,12 @@ test_deprecate_positional_use_2(PyObject *module, PyObject *const *args, Py_ssiz PyObject *optarg; #if PY_VERSION_HEX >= 0x030e00C0 - # error "In /Users/erlend.aasland/src/cpython.git/Lib/test/clinic.test.c, update parameter(s) optarg in the clinic input of 'test_deprecate_positional_use_2' to be keyword-only." + # error "In clinic.test.c, update parameter(s) optarg in the clinic input of 'test_deprecate_positional_use_2' to be keyword-only." #elif PY_VERSION_HEX >= 0x030e00A0 # ifdef _MSC_VER - # pragma message ("In /Users/erlend.aasland/src/cpython.git/Lib/test/clinic.test.c, update parameter(s) optarg in the clinic input of 'test_deprecate_positional_use_2' to be keyword-only.") + # pragma message ("In clinic.test.c, update parameter(s) optarg in the clinic input of 'test_deprecate_positional_use_2' to be keyword-only.") # else - # warning "In /Users/erlend.aasland/src/cpython.git/Lib/test/clinic.test.c, update parameter(s) optarg in the clinic input of 'test_deprecate_positional_use_2' to be keyword-only." + # warning "In clinic.test.c, update parameter(s) optarg in the clinic input of 'test_deprecate_positional_use_2' to be keyword-only." # endif #endif if (nargs == 2) { @@ -5647,7 +5647,7 @@ test_deprecate_positional_use_2(PyObject *module, PyObject *const *args, Py_ssiz static PyObject * test_deprecate_positional_use_2_impl(PyObject *module, PyObject *pos, PyObject *optarg) -/*[clinic end generated code: output=3d6f116d67f53bb9 input=3ced298b4a297b2f]*/ +/*[clinic end generated code: output=6dbf610474d56ade input=3ced298b4a297b2f]*/ /*[clinic input] @@ -5706,12 +5706,12 @@ test_deprecate_positional_use_3(PyObject *module, PyObject *const *args, Py_ssiz PyObject *kw; #if PY_VERSION_HEX >= 0x030e00C0 - # error "In /Users/erlend.aasland/src/cpython.git/Lib/test/clinic.test.c, update parameter(s) optarg in the clinic input of 'test_deprecate_positional_use_3' to be keyword-only." + # error "In clinic.test.c, update parameter(s) optarg in the clinic input of 'test_deprecate_positional_use_3' to be keyword-only." #elif PY_VERSION_HEX >= 0x030e00A0 # ifdef _MSC_VER - # pragma message ("In /Users/erlend.aasland/src/cpython.git/Lib/test/clinic.test.c, update parameter(s) optarg in the clinic input of 'test_deprecate_positional_use_3' to be keyword-only.") + # pragma message ("In clinic.test.c, update parameter(s) optarg in the clinic input of 'test_deprecate_positional_use_3' to be keyword-only.") # else - # warning "In /Users/erlend.aasland/src/cpython.git/Lib/test/clinic.test.c, update parameter(s) optarg in the clinic input of 'test_deprecate_positional_use_3' to be keyword-only." + # warning "In clinic.test.c, update parameter(s) optarg in the clinic input of 'test_deprecate_positional_use_3' to be keyword-only." # endif #endif if (nargs == 2) { @@ -5735,4 +5735,4 @@ test_deprecate_positional_use_3(PyObject *module, PyObject *const *args, Py_ssiz static PyObject * test_deprecate_positional_use_3_impl(PyObject *module, PyObject *pos, PyObject *optarg, PyObject *kw) -/*[clinic end generated code: output=32fc07795a7eb5fa input=c19ac8533f05d314]*/ +/*[clinic end generated code: output=2a31e06b3885abdd input=c19ac8533f05d314]*/ diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 20c21dc309e9fc..65083de77e7334 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -888,7 +888,7 @@ def deprecate_positional_use( pstr = ", ".join(names[:-1]) + " and " + names[-1] # Format the preprocessor warning and error messages. - source = self.cpp.filename + source = os.path.basename(self.cpp.filename) thenceforth = first_param.deprecated_positional # FIXME assert thenceforth is not None major, minor = thenceforth From c4ba02527a81d1ce646dd0ef693a53265b3724a0 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sun, 6 Aug 2023 17:48:39 +0200 Subject: [PATCH 53/71] Apply suggestions from code review Co-authored-by: Alex Waygood --- Doc/howto/clinic.rst | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/Doc/howto/clinic.rst b/Doc/howto/clinic.rst index 8b77b7acc8a7ee..62b5d0143c579d 100644 --- a/Doc/howto/clinic.rst +++ b/Doc/howto/clinic.rst @@ -1913,11 +1913,16 @@ positional-or-keyword parameters *a* and *b*, and a keyword-only parameter *c*:: c: int [clinic start generated output]*/ -We now want to make the *b* parameter keyword-only. -For this example, imagine we're in the development phase for Python 3.12, -meaning we can deprecate positional use of *b* earliest in Python 3.14 and onward -(see :pep:`387` -- *Backwards Compatibility Policy*). -We can do that using the ``* [from ...]``` syntax, +We now want to make the *b* parameter keyword-only; +however, we'll have to wait two releases before making this change, +as mandated by Python's backwards-compatibility policy (see :pep:`387`). +For this example, imagine we're in the development phase for Python 3.12: +that means we'll be allowed to introduce deprecation warnings in Python 3.12 +whenever the *b* parameter is passed positionally, +and we'll be allowed to make it keyword-only in Python 3.14 at the earliest. + +We can use Argument Clinic to emit the desired deprecation warnings +using the ``* [from ...]``` syntax, by adding the line ``* [from 3.14]`` right above the *b* parameter:: /*[clinic input] @@ -1940,9 +1945,10 @@ compiler warnings if the ``* [from ...]`` line has not been removed from the Argument Clinic input when the deprecation period is over, which means when the alpha phase of the specified Python version kicks in. -Let's return to our example and assume Python 3.14 development has entered the -alpha phase, and we forgot all about updating the Argument Clinic code for -:py:func:`!myfunc`. +Let's return to our example and skip ahead two years: +Python 3.14 development has now entered the alpha phase, +but we forgot all about updating the Argument Clinic code +for :py:func:`!myfunc`! Luckily for us, compiler warnings are now generated: .. code-block:: none From c875d600208b7f2e13e383c9de168ffbfc1710d9 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sun, 6 Aug 2023 18:39:18 +0200 Subject: [PATCH 54/71] Fix mypy error --- Tools/clinic/clinic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 65083de77e7334..b5431e67849143 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -888,11 +888,11 @@ def deprecate_positional_use( pstr = ", ".join(names[:-1]) + " and " + names[-1] # Format the preprocessor warning and error messages. + assert isinstance(self.cpp.filename, str) source = os.path.basename(self.cpp.filename) thenceforth = first_param.deprecated_positional # FIXME assert thenceforth is not None major, minor = thenceforth - assert isinstance(self.cpp.filename, str) cpp_message = ( f"In {source}, update parameter(s) {pstr} in the clinic " f"input of {func.full_name!r} to be keyword-only." From cdd927ca92e25bb173cda002444259efd5c88d0d Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sun, 6 Aug 2023 18:51:26 +0200 Subject: [PATCH 55/71] Fix deprecation condition --- Tools/clinic/clinic.py | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index b5431e67849143..1d3ab19db68c2d 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -838,7 +838,7 @@ class CLanguage(Language): # warning "{cpp_message}" # endif #endif - if (nargs == {pos}) {{{{ + if ({condition}) {{{{ if (PyErr_WarnEx(PyExc_DeprecationWarning, "{depr_message}", 1)) {{{{ goto exit; }}}} @@ -872,11 +872,13 @@ def deprecate_positional_use( params: dict[int, Parameter], ) -> str: assert len(params) > 0 - # FIXME: Handle multiple deprecation levels - # FIXME: For now, assume there's only one level code_blocks: list[str] = [] names = [repr(p.name) for p in params.values()] first_pos, first_param = next(iter(params.items())) + last_pos, last_param = next(reversed(params.items())) + # FIXME: Handle multiple deprecation levels + # FIXME: For now, assume there's only one level + assert first_param.deprecated_positional == last_param.deprecated_positional # Pretty-print list of names. match len(params): @@ -898,21 +900,25 @@ def deprecate_positional_use( f"input of {func.full_name!r} to be keyword-only." ) # Format the deprecation message. - singular = ( - f"Passing {pstr} as a positional parameter is deprecated. " - f"It will become a keyword-only parameter in Python {major}.{minor}." - ) - plural = ( - f"Passing {pstr} as positional parameters is deprecated. " - f"They will become keyword-only parameters in Python {major}.{minor}." - ) + if len(params) == 1: + condition = f"nargs == {first_pos+1}" + depr_message = ( + f"Passing {pstr} as a positional parameter is deprecated. " + f"It will become a keyword-only parameter in Python {major}.{minor}." + ) + else: + condition = f"nargs > {first_pos} && nargs <= {last_pos+1}" + depr_message = ( + f"Passing {pstr} as positional parameters is deprecated. " + f"They will become keyword-only parameters in Python {major}.{minor}." + ) # Format and return the code block. code = self.DEPRECATED_POSITIONAL_PROTOTYPE.format( - pos=first_pos+1, + condition=condition, major=major, minor=minor, cpp_message=cpp_message, - depr_message=singular if len(params) == 1 else plural, + depr_message=depr_message, ) return normalize_snippet(code, indent=4) @@ -1281,6 +1287,7 @@ def parser_body( add_label = None if not p.is_optional(): if p.deprecated_positional: + print("deprecating", i, p) deprecated_positionals[i] = p parser_code.append(normalize_snippet(parsearg, indent=4)) elif i < pos_only: @@ -1312,6 +1319,7 @@ def parser_body( }} """ % add_label, indent=4)) if p.deprecated_positional: + print("deprecating", i, p) deprecated_positionals[i] = p if i + 1 == len(parameters): parser_code.append(normalize_snippet(parsearg, indent=4)) From f0847bce96a4fcfc7ce23cbcae9bf87852cdf73b Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sun, 6 Aug 2023 19:00:25 +0200 Subject: [PATCH 56/71] Further improve warning messages --- Lib/test/clinic.test.c | 30 +++++++++++++++--------------- Tools/clinic/clinic.py | 18 +++++++----------- 2 files changed, 22 insertions(+), 26 deletions(-) diff --git a/Lib/test/clinic.test.c b/Lib/test/clinic.test.c index 518287df5123d8..2e5405476a829a 100644 --- a/Lib/test/clinic.test.c +++ b/Lib/test/clinic.test.c @@ -5526,16 +5526,16 @@ test_deprecate_positional_use_1(PyObject *module, PyObject *const *args, Py_ssiz PyObject *optarg = Py_None; #if PY_VERSION_HEX >= 0x030e00C0 - # error "In clinic.test.c, update parameter(s) optarg in the clinic input of 'test_deprecate_positional_use_1' to be keyword-only." + # error "In clinic.test.c, update parameter(s) 'optarg' in the clinic input of 'test_deprecate_positional_use_1' to be keyword-only." #elif PY_VERSION_HEX >= 0x030e00A0 # ifdef _MSC_VER - # pragma message ("In clinic.test.c, update parameter(s) optarg in the clinic input of 'test_deprecate_positional_use_1' to be keyword-only.") + # pragma message ("In clinic.test.c, update parameter(s) 'optarg' in the clinic input of 'test_deprecate_positional_use_1' to be keyword-only.") # else - # warning "In clinic.test.c, update parameter(s) optarg in the clinic input of 'test_deprecate_positional_use_1' to be keyword-only." + # warning "In clinic.test.c, update parameter(s) 'optarg' in the clinic input of 'test_deprecate_positional_use_1' to be keyword-only." # endif #endif if (nargs == 2) { - if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing optarg as a positional parameter is deprecated. It will become a keyword-only parameter in Python 3.14.", 1)) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing more than 1 positional arguments to 'test_deprecate_positional_use_1'() is deprecated. Parameter 'optarg' will become a keyword-only parameter in Python 3.14.", 1)) { goto exit; } } @@ -5558,7 +5558,7 @@ test_deprecate_positional_use_1(PyObject *module, PyObject *const *args, Py_ssiz static PyObject * test_deprecate_positional_use_1_impl(PyObject *module, PyObject *pos, PyObject *optarg) -/*[clinic end generated code: output=56f4e1fe9bcd8d19 input=ead4a995482b22d3]*/ +/*[clinic end generated code: output=1727f0eddd25ba27 input=ead4a995482b22d3]*/ /*[clinic input] @@ -5619,16 +5619,16 @@ test_deprecate_positional_use_2(PyObject *module, PyObject *const *args, Py_ssiz PyObject *optarg; #if PY_VERSION_HEX >= 0x030e00C0 - # error "In clinic.test.c, update parameter(s) optarg in the clinic input of 'test_deprecate_positional_use_2' to be keyword-only." + # error "In clinic.test.c, update parameter(s) 'optarg' in the clinic input of 'test_deprecate_positional_use_2' to be keyword-only." #elif PY_VERSION_HEX >= 0x030e00A0 # ifdef _MSC_VER - # pragma message ("In clinic.test.c, update parameter(s) optarg in the clinic input of 'test_deprecate_positional_use_2' to be keyword-only.") + # pragma message ("In clinic.test.c, update parameter(s) 'optarg' in the clinic input of 'test_deprecate_positional_use_2' to be keyword-only.") # else - # warning "In clinic.test.c, update parameter(s) optarg in the clinic input of 'test_deprecate_positional_use_2' to be keyword-only." + # warning "In clinic.test.c, update parameter(s) 'optarg' in the clinic input of 'test_deprecate_positional_use_2' to be keyword-only." # endif #endif if (nargs == 2) { - if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing optarg as a positional parameter is deprecated. It will become a keyword-only parameter in Python 3.14.", 1)) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing more than 1 positional arguments to 'test_deprecate_positional_use_2'() is deprecated. Parameter 'optarg' will become a keyword-only parameter in Python 3.14.", 1)) { goto exit; } } @@ -5647,7 +5647,7 @@ test_deprecate_positional_use_2(PyObject *module, PyObject *const *args, Py_ssiz static PyObject * test_deprecate_positional_use_2_impl(PyObject *module, PyObject *pos, PyObject *optarg) -/*[clinic end generated code: output=6dbf610474d56ade input=3ced298b4a297b2f]*/ +/*[clinic end generated code: output=9714bd9fa8453e63 input=3ced298b4a297b2f]*/ /*[clinic input] @@ -5706,16 +5706,16 @@ test_deprecate_positional_use_3(PyObject *module, PyObject *const *args, Py_ssiz PyObject *kw; #if PY_VERSION_HEX >= 0x030e00C0 - # error "In clinic.test.c, update parameter(s) optarg in the clinic input of 'test_deprecate_positional_use_3' to be keyword-only." + # error "In clinic.test.c, update parameter(s) 'optarg' in the clinic input of 'test_deprecate_positional_use_3' to be keyword-only." #elif PY_VERSION_HEX >= 0x030e00A0 # ifdef _MSC_VER - # pragma message ("In clinic.test.c, update parameter(s) optarg in the clinic input of 'test_deprecate_positional_use_3' to be keyword-only.") + # pragma message ("In clinic.test.c, update parameter(s) 'optarg' in the clinic input of 'test_deprecate_positional_use_3' to be keyword-only.") # else - # warning "In clinic.test.c, update parameter(s) optarg in the clinic input of 'test_deprecate_positional_use_3' to be keyword-only." + # warning "In clinic.test.c, update parameter(s) 'optarg' in the clinic input of 'test_deprecate_positional_use_3' to be keyword-only." # endif #endif if (nargs == 2) { - if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing optarg as a positional parameter is deprecated. It will become a keyword-only parameter in Python 3.14.", 1)) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing more than 1 positional arguments to 'test_deprecate_positional_use_3'() is deprecated. Parameter 'optarg' will become a keyword-only parameter in Python 3.14.", 1)) { goto exit; } } @@ -5735,4 +5735,4 @@ test_deprecate_positional_use_3(PyObject *module, PyObject *const *args, Py_ssiz static PyObject * test_deprecate_positional_use_3_impl(PyObject *module, PyObject *pos, PyObject *optarg, PyObject *kw) -/*[clinic end generated code: output=2a31e06b3885abdd input=c19ac8533f05d314]*/ +/*[clinic end generated code: output=e3f018e73ee1596a input=c19ac8533f05d314]*/ diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 1d3ab19db68c2d..26bca6f6bf9e96 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -883,7 +883,7 @@ def deprecate_positional_use( # Pretty-print list of names. match len(params): case 1: - pstr = first_param.name + pstr = repr(first_param.name) case 2: pstr = " and ".join(names) case _: @@ -900,18 +900,16 @@ def deprecate_positional_use( f"input of {func.full_name!r} to be keyword-only." ) # Format the deprecation message. + depr_message = (f"Passing more than 1 positional arguments to " + f"{func.full_name!r}() is deprecated. ") if len(params) == 1: condition = f"nargs == {first_pos+1}" - depr_message = ( - f"Passing {pstr} as a positional parameter is deprecated. " - f"It will become a keyword-only parameter in Python {major}.{minor}." - ) + depr_message += (f"Parameter {pstr} will become a keyword-only " + f"parameter in Python {major}.{minor}.") else: condition = f"nargs > {first_pos} && nargs <= {last_pos+1}" - depr_message = ( - f"Passing {pstr} as positional parameters is deprecated. " - f"They will become keyword-only parameters in Python {major}.{minor}." - ) + depr_message += (f"Parameters {pstr} will become keyword-only " + f"parameters in Python {major}.{minor}.") # Format and return the code block. code = self.DEPRECATED_POSITIONAL_PROTOTYPE.format( condition=condition, @@ -1287,7 +1285,6 @@ def parser_body( add_label = None if not p.is_optional(): if p.deprecated_positional: - print("deprecating", i, p) deprecated_positionals[i] = p parser_code.append(normalize_snippet(parsearg, indent=4)) elif i < pos_only: @@ -1319,7 +1316,6 @@ def parser_body( }} """ % add_label, indent=4)) if p.deprecated_positional: - print("deprecating", i, p) deprecated_positionals[i] = p if i + 1 == len(parameters): parser_code.append(normalize_snippet(parsearg, indent=4)) From ce8db9a68e03b31c87024670af8081df6711c113 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sun, 6 Aug 2023 19:08:26 +0200 Subject: [PATCH 57/71] Simplify condition --- Lib/test/clinic.test.c | 12 ++++++------ Tools/clinic/clinic.py | 6 ++---- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/Lib/test/clinic.test.c b/Lib/test/clinic.test.c index 2e5405476a829a..9b2c6b4cc8044e 100644 --- a/Lib/test/clinic.test.c +++ b/Lib/test/clinic.test.c @@ -5534,7 +5534,7 @@ test_deprecate_positional_use_1(PyObject *module, PyObject *const *args, Py_ssiz # warning "In clinic.test.c, update parameter(s) 'optarg' in the clinic input of 'test_deprecate_positional_use_1' to be keyword-only." # endif #endif - if (nargs == 2) { + if (nargs > 1) { if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing more than 1 positional arguments to 'test_deprecate_positional_use_1'() is deprecated. Parameter 'optarg' will become a keyword-only parameter in Python 3.14.", 1)) { goto exit; } @@ -5558,7 +5558,7 @@ test_deprecate_positional_use_1(PyObject *module, PyObject *const *args, Py_ssiz static PyObject * test_deprecate_positional_use_1_impl(PyObject *module, PyObject *pos, PyObject *optarg) -/*[clinic end generated code: output=1727f0eddd25ba27 input=ead4a995482b22d3]*/ +/*[clinic end generated code: output=80777b086b2e02df input=ead4a995482b22d3]*/ /*[clinic input] @@ -5627,7 +5627,7 @@ test_deprecate_positional_use_2(PyObject *module, PyObject *const *args, Py_ssiz # warning "In clinic.test.c, update parameter(s) 'optarg' in the clinic input of 'test_deprecate_positional_use_2' to be keyword-only." # endif #endif - if (nargs == 2) { + if (nargs > 1) { if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing more than 1 positional arguments to 'test_deprecate_positional_use_2'() is deprecated. Parameter 'optarg' will become a keyword-only parameter in Python 3.14.", 1)) { goto exit; } @@ -5647,7 +5647,7 @@ test_deprecate_positional_use_2(PyObject *module, PyObject *const *args, Py_ssiz static PyObject * test_deprecate_positional_use_2_impl(PyObject *module, PyObject *pos, PyObject *optarg) -/*[clinic end generated code: output=9714bd9fa8453e63 input=3ced298b4a297b2f]*/ +/*[clinic end generated code: output=5fad64678659a7d3 input=3ced298b4a297b2f]*/ /*[clinic input] @@ -5714,7 +5714,7 @@ test_deprecate_positional_use_3(PyObject *module, PyObject *const *args, Py_ssiz # warning "In clinic.test.c, update parameter(s) 'optarg' in the clinic input of 'test_deprecate_positional_use_3' to be keyword-only." # endif #endif - if (nargs == 2) { + if (nargs > 1) { if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing more than 1 positional arguments to 'test_deprecate_positional_use_3'() is deprecated. Parameter 'optarg' will become a keyword-only parameter in Python 3.14.", 1)) { goto exit; } @@ -5735,4 +5735,4 @@ test_deprecate_positional_use_3(PyObject *module, PyObject *const *args, Py_ssiz static PyObject * test_deprecate_positional_use_3_impl(PyObject *module, PyObject *pos, PyObject *optarg, PyObject *kw) -/*[clinic end generated code: output=e3f018e73ee1596a input=c19ac8533f05d314]*/ +/*[clinic end generated code: output=22998b5010086b8b input=c19ac8533f05d314]*/ diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 26bca6f6bf9e96..5aff465e4fd450 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -838,7 +838,7 @@ class CLanguage(Language): # warning "{cpp_message}" # endif #endif - if ({condition}) {{{{ + if (nargs > {pos}) {{{{ if (PyErr_WarnEx(PyExc_DeprecationWarning, "{depr_message}", 1)) {{{{ goto exit; }}}} @@ -903,16 +903,14 @@ def deprecate_positional_use( depr_message = (f"Passing more than 1 positional arguments to " f"{func.full_name!r}() is deprecated. ") if len(params) == 1: - condition = f"nargs == {first_pos+1}" depr_message += (f"Parameter {pstr} will become a keyword-only " f"parameter in Python {major}.{minor}.") else: - condition = f"nargs > {first_pos} && nargs <= {last_pos+1}" depr_message += (f"Parameters {pstr} will become keyword-only " f"parameters in Python {major}.{minor}.") # Format and return the code block. code = self.DEPRECATED_POSITIONAL_PROTOTYPE.format( - condition=condition, + pos=first_pos, major=major, minor=minor, cpp_message=cpp_message, From b45d845780bbbd3864d651a9e96af73819976f77 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sun, 6 Aug 2023 19:56:06 +0200 Subject: [PATCH 58/71] Refactor: extract pprinter --- Tools/clinic/clinic.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 5aff465e4fd450..43e83185414f0c 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -347,6 +347,18 @@ def suffix_all_lines(s: str, suffix: str) -> str: return ''.join(final) +def pprint_words(items: list[str]) -> str: + match len(items): + case 0: + return "" + case 1: + return next(iter(items)) + case 2: + return " and ".join(items) + case _: + return ", ".join(items[:-1]) + " and " + items[-1] + + def version_splitter(s: str) -> tuple[int, ...]: """Splits a version string into a tuple of integers. @@ -881,13 +893,7 @@ def deprecate_positional_use( assert first_param.deprecated_positional == last_param.deprecated_positional # Pretty-print list of names. - match len(params): - case 1: - pstr = repr(first_param.name) - case 2: - pstr = " and ".join(names) - case _: - pstr = ", ".join(names[:-1]) + " and " + names[-1] + pstr = pprint_words(names) # Format the preprocessor warning and error messages. assert isinstance(self.cpp.filename, str) From d2330c6f78d555633164389d84c0c14717cf47a6 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sun, 6 Aug 2023 20:03:11 +0200 Subject: [PATCH 59/71] Reintroduce condition and fixup deprecation message --- Lib/test/clinic.test.c | 18 +++++++++--------- Tools/clinic/clinic.py | 22 ++++++++++++++-------- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/Lib/test/clinic.test.c b/Lib/test/clinic.test.c index 9b2c6b4cc8044e..c376cad9b53435 100644 --- a/Lib/test/clinic.test.c +++ b/Lib/test/clinic.test.c @@ -5534,8 +5534,8 @@ test_deprecate_positional_use_1(PyObject *module, PyObject *const *args, Py_ssiz # warning "In clinic.test.c, update parameter(s) 'optarg' in the clinic input of 'test_deprecate_positional_use_1' to be keyword-only." # endif #endif - if (nargs > 1) { - if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing more than 1 positional arguments to 'test_deprecate_positional_use_1'() is deprecated. Parameter 'optarg' will become a keyword-only parameter in Python 3.14.", 1)) { + if (nargs == 2) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing 2 positional arguments to 'test_deprecate_positional_use_1'() is deprecated. Parameter 'optarg' will become a keyword-only parameter in Python 3.14.", 1)) { goto exit; } } @@ -5558,7 +5558,7 @@ test_deprecate_positional_use_1(PyObject *module, PyObject *const *args, Py_ssiz static PyObject * test_deprecate_positional_use_1_impl(PyObject *module, PyObject *pos, PyObject *optarg) -/*[clinic end generated code: output=80777b086b2e02df input=ead4a995482b22d3]*/ +/*[clinic end generated code: output=ece3361ebbfbbebb input=ead4a995482b22d3]*/ /*[clinic input] @@ -5627,8 +5627,8 @@ test_deprecate_positional_use_2(PyObject *module, PyObject *const *args, Py_ssiz # warning "In clinic.test.c, update parameter(s) 'optarg' in the clinic input of 'test_deprecate_positional_use_2' to be keyword-only." # endif #endif - if (nargs > 1) { - if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing more than 1 positional arguments to 'test_deprecate_positional_use_2'() is deprecated. Parameter 'optarg' will become a keyword-only parameter in Python 3.14.", 1)) { + if (nargs == 2) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing 2 positional arguments to 'test_deprecate_positional_use_2'() is deprecated. Parameter 'optarg' will become a keyword-only parameter in Python 3.14.", 1)) { goto exit; } } @@ -5647,7 +5647,7 @@ test_deprecate_positional_use_2(PyObject *module, PyObject *const *args, Py_ssiz static PyObject * test_deprecate_positional_use_2_impl(PyObject *module, PyObject *pos, PyObject *optarg) -/*[clinic end generated code: output=5fad64678659a7d3 input=3ced298b4a297b2f]*/ +/*[clinic end generated code: output=3c2c50d2f38bb188 input=3ced298b4a297b2f]*/ /*[clinic input] @@ -5714,8 +5714,8 @@ test_deprecate_positional_use_3(PyObject *module, PyObject *const *args, Py_ssiz # warning "In clinic.test.c, update parameter(s) 'optarg' in the clinic input of 'test_deprecate_positional_use_3' to be keyword-only." # endif #endif - if (nargs > 1) { - if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing more than 1 positional arguments to 'test_deprecate_positional_use_3'() is deprecated. Parameter 'optarg' will become a keyword-only parameter in Python 3.14.", 1)) { + if (nargs == 2) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing 2 positional arguments to 'test_deprecate_positional_use_3'() is deprecated. Parameter 'optarg' will become a keyword-only parameter in Python 3.14.", 1)) { goto exit; } } @@ -5735,4 +5735,4 @@ test_deprecate_positional_use_3(PyObject *module, PyObject *const *args, Py_ssiz static PyObject * test_deprecate_positional_use_3_impl(PyObject *module, PyObject *pos, PyObject *optarg, PyObject *kw) -/*[clinic end generated code: output=22998b5010086b8b input=c19ac8533f05d314]*/ +/*[clinic end generated code: output=abc970a04a0c1120 input=c19ac8533f05d314]*/ diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 43e83185414f0c..89cb7498e1387c 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -850,7 +850,7 @@ class CLanguage(Language): # warning "{cpp_message}" # endif #endif - if (nargs > {pos}) {{{{ + if ({condition}) {{{{ if (PyErr_WarnEx(PyExc_DeprecationWarning, "{depr_message}", 1)) {{{{ goto exit; }}}} @@ -906,17 +906,23 @@ def deprecate_positional_use( f"input of {func.full_name!r} to be keyword-only." ) # Format the deprecation message. - depr_message = (f"Passing more than 1 positional arguments to " - f"{func.full_name!r}() is deprecated. ") if len(params) == 1: - depr_message += (f"Parameter {pstr} will become a keyword-only " - f"parameter in Python {major}.{minor}.") + condition = f"nargs == {first_pos+1}" + depr_message = ( + f"Passing {first_pos+1} positional arguments to " + f"{func.full_name!r}() is deprecated. Parameter {pstr} will " + f"become a keyword-only parameter in Python {major}.{minor}." + ) else: - depr_message += (f"Parameters {pstr} will become keyword-only " - f"parameters in Python {major}.{minor}.") + condition = f"nargs > {first_pos} && nargs <= {last_pos+1}" + depr_message = ( + f"Passing more than {first_pos} positional arguments to " + f"{func.full_name!r}() is deprecated. Parameters {pstr} will " + f"become keyword-only parameters in Python {major}.{minor}." + ) # Format and return the code block. code = self.DEPRECATED_POSITIONAL_PROTOTYPE.format( - pos=first_pos, + condition=condition, major=major, minor=minor, cpp_message=cpp_message, From 622b5562cf14a77579442bd430477b25c1e3cd99 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sun, 6 Aug 2023 22:57:38 +0200 Subject: [PATCH 60/71] Remove unused variable, and reorder some lines for readability --- Tools/clinic/clinic.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 89cb7498e1387c..7a691111ccc452 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -884,22 +884,21 @@ def deprecate_positional_use( params: dict[int, Parameter], ) -> str: assert len(params) > 0 - code_blocks: list[str] = [] names = [repr(p.name) for p in params.values()] first_pos, first_param = next(iter(params.items())) last_pos, last_param = next(reversed(params.items())) - # FIXME: Handle multiple deprecation levels - # FIXME: For now, assume there's only one level - assert first_param.deprecated_positional == last_param.deprecated_positional # Pretty-print list of names. pstr = pprint_words(names) + # For now, assume there's only one deprecation level. + assert first_param.deprecated_positional == last_param.deprecated_positional + thenceforth = first_param.deprecated_positional + assert thenceforth is not None + # Format the preprocessor warning and error messages. assert isinstance(self.cpp.filename, str) source = os.path.basename(self.cpp.filename) - thenceforth = first_param.deprecated_positional # FIXME - assert thenceforth is not None major, minor = thenceforth cpp_message = ( f"In {source}, update parameter(s) {pstr} in the clinic " From e71fbe1b19856db1578e051a51ae6df6369d8926 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sun, 6 Aug 2023 22:58:38 +0200 Subject: [PATCH 61/71] Simplify pprint_words --- Tools/clinic/clinic.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 7a691111ccc452..95082c1c1c4bd1 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -348,15 +348,10 @@ def suffix_all_lines(s: str, suffix: str) -> str: def pprint_words(items: list[str]) -> str: - match len(items): - case 0: - return "" - case 1: - return next(iter(items)) - case 2: - return " and ".join(items) - case _: - return ", ".join(items[:-1]) + " and " + items[-1] + if len(items) <= 2: + return " and ".join(items) + else: + return ", ".join(items[:-1]) + " and " + items[-1] def version_splitter(s: str) -> tuple[int, ...]: From c4141f0bf5f534077b26ed23622bcc176dd45534 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sun, 6 Aug 2023 22:59:47 +0200 Subject: [PATCH 62/71] Fix function name formatting --- Lib/test/clinic.test.c | 12 ++++++------ Tools/clinic/clinic.py | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Lib/test/clinic.test.c b/Lib/test/clinic.test.c index c376cad9b53435..da78c5d84f01f3 100644 --- a/Lib/test/clinic.test.c +++ b/Lib/test/clinic.test.c @@ -5535,7 +5535,7 @@ test_deprecate_positional_use_1(PyObject *module, PyObject *const *args, Py_ssiz # endif #endif if (nargs == 2) { - if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing 2 positional arguments to 'test_deprecate_positional_use_1'() is deprecated. Parameter 'optarg' will become a keyword-only parameter in Python 3.14.", 1)) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing 2 positional arguments to test_deprecate_positional_use_1() is deprecated. Parameter 'optarg' will become a keyword-only parameter in Python 3.14.", 1)) { goto exit; } } @@ -5558,7 +5558,7 @@ test_deprecate_positional_use_1(PyObject *module, PyObject *const *args, Py_ssiz static PyObject * test_deprecate_positional_use_1_impl(PyObject *module, PyObject *pos, PyObject *optarg) -/*[clinic end generated code: output=ece3361ebbfbbebb input=ead4a995482b22d3]*/ +/*[clinic end generated code: output=a7117e57f8ce42b1 input=ead4a995482b22d3]*/ /*[clinic input] @@ -5628,7 +5628,7 @@ test_deprecate_positional_use_2(PyObject *module, PyObject *const *args, Py_ssiz # endif #endif if (nargs == 2) { - if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing 2 positional arguments to 'test_deprecate_positional_use_2'() is deprecated. Parameter 'optarg' will become a keyword-only parameter in Python 3.14.", 1)) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing 2 positional arguments to test_deprecate_positional_use_2() is deprecated. Parameter 'optarg' will become a keyword-only parameter in Python 3.14.", 1)) { goto exit; } } @@ -5647,7 +5647,7 @@ test_deprecate_positional_use_2(PyObject *module, PyObject *const *args, Py_ssiz static PyObject * test_deprecate_positional_use_2_impl(PyObject *module, PyObject *pos, PyObject *optarg) -/*[clinic end generated code: output=3c2c50d2f38bb188 input=3ced298b4a297b2f]*/ +/*[clinic end generated code: output=c604638791156fb9 input=3ced298b4a297b2f]*/ /*[clinic input] @@ -5715,7 +5715,7 @@ test_deprecate_positional_use_3(PyObject *module, PyObject *const *args, Py_ssiz # endif #endif if (nargs == 2) { - if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing 2 positional arguments to 'test_deprecate_positional_use_3'() is deprecated. Parameter 'optarg' will become a keyword-only parameter in Python 3.14.", 1)) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing 2 positional arguments to test_deprecate_positional_use_3() is deprecated. Parameter 'optarg' will become a keyword-only parameter in Python 3.14.", 1)) { goto exit; } } @@ -5735,4 +5735,4 @@ test_deprecate_positional_use_3(PyObject *module, PyObject *const *args, Py_ssiz static PyObject * test_deprecate_positional_use_3_impl(PyObject *module, PyObject *pos, PyObject *optarg, PyObject *kw) -/*[clinic end generated code: output=abc970a04a0c1120 input=c19ac8533f05d314]*/ +/*[clinic end generated code: output=430bebca9eeddec7 input=c19ac8533f05d314]*/ diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 95082c1c1c4bd1..8f1ca9ee7ed670 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -904,14 +904,14 @@ def deprecate_positional_use( condition = f"nargs == {first_pos+1}" depr_message = ( f"Passing {first_pos+1} positional arguments to " - f"{func.full_name!r}() is deprecated. Parameter {pstr} will " + f"{func.full_name}() is deprecated. Parameter {pstr} will " f"become a keyword-only parameter in Python {major}.{minor}." ) else: condition = f"nargs > {first_pos} && nargs <= {last_pos+1}" depr_message = ( f"Passing more than {first_pos} positional arguments to " - f"{func.full_name!r}() is deprecated. Parameters {pstr} will " + f"{func.full_name}() is deprecated. Parameters {pstr} will " f"become keyword-only parameters in Python {major}.{minor}." ) # Format and return the code block. From 87e50c7014a742ca58465fff26ccf184fc873831 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sun, 6 Aug 2023 23:03:43 +0200 Subject: [PATCH 63/71] Use fully qualified function/meth name in fail() messages --- Lib/test/test_clinic.py | 14 +++++++------- Tools/clinic/clinic.py | 15 ++++++++------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index 7668937d988965..4cf71bd2ee31f2 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -1478,7 +1478,7 @@ def test_parameters_required_after_star(self): "module foo\nfoo.bar\n this: int\n *", "module foo\nfoo.bar\n this: int\n *\nDocstring.", ) - err = "Function 'bar' specifies '*' without any parameters afterwards." + err = "Function 'foo.bar' specifies '*' without any parameters afterwards." for block in dataset: with self.subTest(block=block): self.expect_failure(block, err) @@ -1492,7 +1492,7 @@ def test_depr_star_invalid_format_1(self): Docstring. """ err = ( - "Function 'bar': '* [from ...]' format expected to be " + "Function 'foo.bar': '* [from ...]' format expected to be " "'', where 'major' and 'minor' are digits." ) self.expect_failure(block, err, lineno=3) @@ -1506,7 +1506,7 @@ def test_depr_star_invalid_format_2(self): Docstring. """ err = ( - "Function 'bar', '* [from ]': " + "Function 'foo.bar', '* [from ]': " "'major' and 'minor' must be digits, not 'a.b'" ) self.expect_failure(block, err, lineno=3) @@ -1520,7 +1520,7 @@ def test_depr_star_invalid_format_3(self): Docstring. """ err = ( - "Function 'bar', '* [from ]': " + "Function 'foo.bar', '* [from ]': " "'major' and 'minor' must be digits, not '1.2.3'" ) self.expect_failure(block, err, lineno=3) @@ -1534,7 +1534,7 @@ def test_parameters_required_after_depr_star(self): Docstring. """ err = ( - "Function 'bar' specifies '* [from ...]' without " + "Function 'foo.bar' specifies '* [from ...]' without " "any parameters afterwards" ) self.expect_failure(block, err, lineno=4) @@ -1548,7 +1548,7 @@ def test_depr_star_must_come_before_star(self): * [from 3.14] Docstring. """ - err = "Function 'bar': '* [from ...]' must come before '*'" + err = "Function 'foo.bar': '* [from ...]' must come before '*'" self.expect_failure(block, err, lineno=4) def test_depr_star_duplicate(self): @@ -1562,7 +1562,7 @@ def test_depr_star_duplicate(self): c: int Docstring. """ - err = "Function 'bar' uses '[from ...]' more than once" + err = "Function 'foo.bar' uses '[from ...]' more than once" self.expect_failure(block, err, lineno=5) def test_single_slash(self): diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 8f1ca9ee7ed670..348df7e3f0f13e 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -5319,23 +5319,23 @@ def parse_converter( def parse_deprecated_positional(self, thenceforth: str) -> None: assert isinstance(self.function, Function) + fname = self.function.full_name if "." not in thenceforth: fail( - f"Function {self.function.name!r}: '* [from ...]' format " - "expected to be '', where 'major' and 'minor' " - "are digits." + f"Function {fname!r}: '* [from ...]' format expected to be " + f"'', where 'major' and 'minor' are digits." ) if self.keyword_only: - fail(f"Function {self.function.name!r}: '* [from ...]' must come before '*'") + fail(f"Function {fname!r}: '* [from ...]' must come before '*'") if self.deprecated_positional: - fail(f"Function {self.function.name!r} uses '[from ...]' more than once.") + fail(f"Function {fname!r} uses '[from ...]' more than once.") try: major, minor = thenceforth.split(".") self.deprecated_positional = int(major), int(minor) except ValueError: fail( - f"Function {self.function.name!r}, '* [from ]': " + f"Function {fname!r}, '* [from ]': " f"'major' and 'minor' must be digits, not {thenceforth!r}" ) @@ -5730,7 +5730,8 @@ def check_remaining( else: no_param_after_symbol = True if no_param_after_symbol: - fail(f"Function {self.function.name!r} specifies {symbol!r} " + fname = self.function.full_name + fail(f"Function {fname!r} specifies {symbol!r} " "without any parameters afterwards.", line_number=lineno) if self.keyword_only: From 6e9e7b4149599d55ee5825522db70c0998b4168d Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sun, 6 Aug 2023 23:37:34 +0200 Subject: [PATCH 64/71] Add more test cases and normalise the their naming --- Lib/test/clinic.test.c | 722 +++++++++++++++++++++++++++++++++++------ 1 file changed, 630 insertions(+), 92 deletions(-) diff --git a/Lib/test/clinic.test.c b/Lib/test/clinic.test.c index da78c5d84f01f3..e245a467d3afe5 100644 --- a/Lib/test/clinic.test.c +++ b/Lib/test/clinic.test.c @@ -5468,31 +5468,26 @@ docstr_fallback_to_converter_default_impl(PyObject *module, str a) /*[clinic input] -test_deprecate_positional_use_1 - pos: object +test_deprecate_positional_pos1_len1_optional + a: object * [from 3.14] - optarg: object = None - has a default value, unlike test_deprecate_positional_use_2 + b: object = None [clinic start generated code]*/ -PyDoc_STRVAR(test_deprecate_positional_use_1__doc__, -"test_deprecate_positional_use_1($module, /, pos, optarg=None)\n" +PyDoc_STRVAR(test_deprecate_positional_pos1_len1_optional__doc__, +"test_deprecate_positional_pos1_len1_optional($module, /, a, b=None)\n" "--\n" -"\n" -"\n" -"\n" -" optarg\n" -" has a default value, unlike test_deprecate_positional_use_2"); +"\n"); -#define TEST_DEPRECATE_POSITIONAL_USE_1_METHODDEF \ - {"test_deprecate_positional_use_1", _PyCFunction_CAST(test_deprecate_positional_use_1), METH_FASTCALL|METH_KEYWORDS, test_deprecate_positional_use_1__doc__}, +#define TEST_DEPRECATE_POSITIONAL_POS1_LEN1_OPTIONAL_METHODDEF \ + {"test_deprecate_positional_pos1_len1_optional", _PyCFunction_CAST(test_deprecate_positional_pos1_len1_optional), METH_FASTCALL|METH_KEYWORDS, test_deprecate_positional_pos1_len1_optional__doc__}, static PyObject * -test_deprecate_positional_use_1_impl(PyObject *module, PyObject *pos, - PyObject *optarg); +test_deprecate_positional_pos1_len1_optional_impl(PyObject *module, + PyObject *a, PyObject *b); static PyObject * -test_deprecate_positional_use_1(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +test_deprecate_positional_pos1_len1_optional(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) @@ -5504,7 +5499,7 @@ test_deprecate_positional_use_1(PyObject *module, PyObject *const *args, Py_ssiz PyObject *ob_item[NUM_KEYWORDS]; } _kwtuple = { .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) - .ob_item = { &_Py_ID(pos), &_Py_ID(optarg), }, + .ob_item = { &_Py_ID(a), &_Py_ID(b), }, }; #undef NUM_KEYWORDS #define KWTUPLE (&_kwtuple.ob_base.ob_base) @@ -5513,29 +5508,29 @@ test_deprecate_positional_use_1(PyObject *module, PyObject *const *args, Py_ssiz # define KWTUPLE NULL #endif // !Py_BUILD_CORE - static const char * const _keywords[] = {"pos", "optarg", NULL}; + static const char * const _keywords[] = {"a", "b", NULL}; static _PyArg_Parser _parser = { .keywords = _keywords, - .fname = "test_deprecate_positional_use_1", + .fname = "test_deprecate_positional_pos1_len1_optional", .kwtuple = KWTUPLE, }; #undef KWTUPLE PyObject *argsbuf[2]; Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1; - PyObject *pos; - PyObject *optarg = Py_None; + PyObject *a; + PyObject *b = Py_None; #if PY_VERSION_HEX >= 0x030e00C0 - # error "In clinic.test.c, update parameter(s) 'optarg' in the clinic input of 'test_deprecate_positional_use_1' to be keyword-only." + # error "In clinic.test.c, update parameter(s) 'b' in the clinic input of 'test_deprecate_positional_pos1_len1_optional' to be keyword-only." #elif PY_VERSION_HEX >= 0x030e00A0 # ifdef _MSC_VER - # pragma message ("In clinic.test.c, update parameter(s) 'optarg' in the clinic input of 'test_deprecate_positional_use_1' to be keyword-only.") + # pragma message ("In clinic.test.c, update parameter(s) 'b' in the clinic input of 'test_deprecate_positional_pos1_len1_optional' to be keyword-only.") # else - # warning "In clinic.test.c, update parameter(s) 'optarg' in the clinic input of 'test_deprecate_positional_use_1' to be keyword-only." + # warning "In clinic.test.c, update parameter(s) 'b' in the clinic input of 'test_deprecate_positional_pos1_len1_optional' to be keyword-only." # endif #endif if (nargs == 2) { - if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing 2 positional arguments to test_deprecate_positional_use_1() is deprecated. Parameter 'optarg' will become a keyword-only parameter in Python 3.14.", 1)) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing 2 positional arguments to test_deprecate_positional_pos1_len1_optional() is deprecated. Parameter 'b' will become a keyword-only parameter in Python 3.14.", 1)) { goto exit; } } @@ -5543,50 +5538,45 @@ test_deprecate_positional_use_1(PyObject *module, PyObject *const *args, Py_ssiz if (!args) { goto exit; } - pos = args[0]; + a = args[0]; if (!noptargs) { goto skip_optional_pos; } - optarg = args[1]; + b = args[1]; skip_optional_pos: - return_value = test_deprecate_positional_use_1_impl(module, pos, optarg); + return_value = test_deprecate_positional_pos1_len1_optional_impl(module, a, b); exit: return return_value; } static PyObject * -test_deprecate_positional_use_1_impl(PyObject *module, PyObject *pos, - PyObject *optarg) -/*[clinic end generated code: output=a7117e57f8ce42b1 input=ead4a995482b22d3]*/ +test_deprecate_positional_pos1_len1_optional_impl(PyObject *module, + PyObject *a, PyObject *b) +/*[clinic end generated code: output=20bdea6a2960ddf3 input=89099f3dacd757da]*/ /*[clinic input] -test_deprecate_positional_use_2 - pos: object +test_deprecate_positional_pos1_len1 + a: object * [from 3.14] - optarg: object - has no default value, unlike test_deprecate_positional_use_1 + b: object [clinic start generated code]*/ -PyDoc_STRVAR(test_deprecate_positional_use_2__doc__, -"test_deprecate_positional_use_2($module, /, pos, optarg)\n" +PyDoc_STRVAR(test_deprecate_positional_pos1_len1__doc__, +"test_deprecate_positional_pos1_len1($module, /, a, b)\n" "--\n" -"\n" -"\n" -"\n" -" optarg\n" -" has no default value, unlike test_deprecate_positional_use_1"); +"\n"); -#define TEST_DEPRECATE_POSITIONAL_USE_2_METHODDEF \ - {"test_deprecate_positional_use_2", _PyCFunction_CAST(test_deprecate_positional_use_2), METH_FASTCALL|METH_KEYWORDS, test_deprecate_positional_use_2__doc__}, +#define TEST_DEPRECATE_POSITIONAL_POS1_LEN1_METHODDEF \ + {"test_deprecate_positional_pos1_len1", _PyCFunction_CAST(test_deprecate_positional_pos1_len1), METH_FASTCALL|METH_KEYWORDS, test_deprecate_positional_pos1_len1__doc__}, static PyObject * -test_deprecate_positional_use_2_impl(PyObject *module, PyObject *pos, - PyObject *optarg); +test_deprecate_positional_pos1_len1_impl(PyObject *module, PyObject *a, + PyObject *b); static PyObject * -test_deprecate_positional_use_2(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +test_deprecate_positional_pos1_len1(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) @@ -5598,7 +5588,7 @@ test_deprecate_positional_use_2(PyObject *module, PyObject *const *args, Py_ssiz PyObject *ob_item[NUM_KEYWORDS]; } _kwtuple = { .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) - .ob_item = { &_Py_ID(pos), &_Py_ID(optarg), }, + .ob_item = { &_Py_ID(a), &_Py_ID(b), }, }; #undef NUM_KEYWORDS #define KWTUPLE (&_kwtuple.ob_base.ob_base) @@ -5607,28 +5597,28 @@ test_deprecate_positional_use_2(PyObject *module, PyObject *const *args, Py_ssiz # define KWTUPLE NULL #endif // !Py_BUILD_CORE - static const char * const _keywords[] = {"pos", "optarg", NULL}; + static const char * const _keywords[] = {"a", "b", NULL}; static _PyArg_Parser _parser = { .keywords = _keywords, - .fname = "test_deprecate_positional_use_2", + .fname = "test_deprecate_positional_pos1_len1", .kwtuple = KWTUPLE, }; #undef KWTUPLE PyObject *argsbuf[2]; - PyObject *pos; - PyObject *optarg; + PyObject *a; + PyObject *b; #if PY_VERSION_HEX >= 0x030e00C0 - # error "In clinic.test.c, update parameter(s) 'optarg' in the clinic input of 'test_deprecate_positional_use_2' to be keyword-only." + # error "In clinic.test.c, update parameter(s) 'b' in the clinic input of 'test_deprecate_positional_pos1_len1' to be keyword-only." #elif PY_VERSION_HEX >= 0x030e00A0 # ifdef _MSC_VER - # pragma message ("In clinic.test.c, update parameter(s) 'optarg' in the clinic input of 'test_deprecate_positional_use_2' to be keyword-only.") + # pragma message ("In clinic.test.c, update parameter(s) 'b' in the clinic input of 'test_deprecate_positional_pos1_len1' to be keyword-only.") # else - # warning "In clinic.test.c, update parameter(s) 'optarg' in the clinic input of 'test_deprecate_positional_use_2' to be keyword-only." + # warning "In clinic.test.c, update parameter(s) 'b' in the clinic input of 'test_deprecate_positional_pos1_len1' to be keyword-only." # endif #endif if (nargs == 2) { - if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing 2 positional arguments to test_deprecate_positional_use_2() is deprecated. Parameter 'optarg' will become a keyword-only parameter in Python 3.14.", 1)) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing 2 positional arguments to test_deprecate_positional_pos1_len1() is deprecated. Parameter 'b' will become a keyword-only parameter in Python 3.14.", 1)) { goto exit; } } @@ -5636,43 +5626,396 @@ test_deprecate_positional_use_2(PyObject *module, PyObject *const *args, Py_ssiz if (!args) { goto exit; } - pos = args[0]; - optarg = args[1]; - return_value = test_deprecate_positional_use_2_impl(module, pos, optarg); + a = args[0]; + b = args[1]; + return_value = test_deprecate_positional_pos1_len1_impl(module, a, b); exit: return return_value; } static PyObject * -test_deprecate_positional_use_2_impl(PyObject *module, PyObject *pos, - PyObject *optarg) -/*[clinic end generated code: output=c604638791156fb9 input=3ced298b4a297b2f]*/ +test_deprecate_positional_pos1_len1_impl(PyObject *module, PyObject *a, + PyObject *b) +/*[clinic end generated code: output=22c70f8b36085758 input=1702bbab1e9b3b99]*/ /*[clinic input] -test_deprecate_positional_use_3 - pos: object +test_deprecate_positional_pos1_len2_with_kwd + a: object * [from 3.14] - optarg: object + b: object + c: object * - kw: object + d: object +[clinic start generated code]*/ + +PyDoc_STRVAR(test_deprecate_positional_pos1_len2_with_kwd__doc__, +"test_deprecate_positional_pos1_len2_with_kwd($module, /, a, b, c, *, d)\n" +"--\n" +"\n"); + +#define TEST_DEPRECATE_POSITIONAL_POS1_LEN2_WITH_KWD_METHODDEF \ + {"test_deprecate_positional_pos1_len2_with_kwd", _PyCFunction_CAST(test_deprecate_positional_pos1_len2_with_kwd), METH_FASTCALL|METH_KEYWORDS, test_deprecate_positional_pos1_len2_with_kwd__doc__}, + +static PyObject * +test_deprecate_positional_pos1_len2_with_kwd_impl(PyObject *module, + PyObject *a, PyObject *b, + PyObject *c, PyObject *d); + +static PyObject * +test_deprecate_positional_pos1_len2_with_kwd(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 4 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(a), &_Py_ID(b), &_Py_ID(c), &_Py_ID(d), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"a", "b", "c", "d", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "test_deprecate_positional_pos1_len2_with_kwd", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[4]; + PyObject *a; + PyObject *b; + PyObject *c; + PyObject *d; + + #if PY_VERSION_HEX >= 0x030e00C0 + # error "In clinic.test.c, update parameter(s) 'b' and 'c' in the clinic input of 'test_deprecate_positional_pos1_len2_with_kwd' to be keyword-only." + #elif PY_VERSION_HEX >= 0x030e00A0 + # ifdef _MSC_VER + # pragma message ("In clinic.test.c, update parameter(s) 'b' and 'c' in the clinic input of 'test_deprecate_positional_pos1_len2_with_kwd' to be keyword-only.") + # else + # warning "In clinic.test.c, update parameter(s) 'b' and 'c' in the clinic input of 'test_deprecate_positional_pos1_len2_with_kwd' to be keyword-only." + # endif + #endif + if (nargs > 1 && nargs <= 3) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing more than 1 positional arguments to test_deprecate_positional_pos1_len2_with_kwd() is deprecated. Parameters 'b' and 'c' will become keyword-only parameters in Python 3.14.", 1)) { + goto exit; + } + } + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 3, 3, 1, argsbuf); + if (!args) { + goto exit; + } + a = args[0]; + b = args[1]; + c = args[2]; + d = args[3]; + return_value = test_deprecate_positional_pos1_len2_with_kwd_impl(module, a, b, c, d); + +exit: + return return_value; +} + +static PyObject * +test_deprecate_positional_pos1_len2_with_kwd_impl(PyObject *module, + PyObject *a, PyObject *b, + PyObject *c, PyObject *d) +/*[clinic end generated code: output=60c1c9621739c235 input=28cdb885f6c34eab]*/ + + +/*[clinic input] +test_deprecate_positional_pos0_len1 + * [from 3.14] + a: object [clinic start generated code]*/ -PyDoc_STRVAR(test_deprecate_positional_use_3__doc__, -"test_deprecate_positional_use_3($module, /, pos, optarg, *, kw)\n" +PyDoc_STRVAR(test_deprecate_positional_pos0_len1__doc__, +"test_deprecate_positional_pos0_len1($module, /, a)\n" "--\n" "\n"); -#define TEST_DEPRECATE_POSITIONAL_USE_3_METHODDEF \ - {"test_deprecate_positional_use_3", _PyCFunction_CAST(test_deprecate_positional_use_3), METH_FASTCALL|METH_KEYWORDS, test_deprecate_positional_use_3__doc__}, +#define TEST_DEPRECATE_POSITIONAL_POS0_LEN1_METHODDEF \ + {"test_deprecate_positional_pos0_len1", _PyCFunction_CAST(test_deprecate_positional_pos0_len1), METH_FASTCALL|METH_KEYWORDS, test_deprecate_positional_pos0_len1__doc__}, static PyObject * -test_deprecate_positional_use_3_impl(PyObject *module, PyObject *pos, - PyObject *optarg, PyObject *kw); +test_deprecate_positional_pos0_len1_impl(PyObject *module, PyObject *a); static PyObject * -test_deprecate_positional_use_3(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +test_deprecate_positional_pos0_len1(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 1 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(a), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"a", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "test_deprecate_positional_pos0_len1", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + PyObject *a; + + #if PY_VERSION_HEX >= 0x030e00C0 + # error "In clinic.test.c, update parameter(s) 'a' in the clinic input of 'test_deprecate_positional_pos0_len1' to be keyword-only." + #elif PY_VERSION_HEX >= 0x030e00A0 + # ifdef _MSC_VER + # pragma message ("In clinic.test.c, update parameter(s) 'a' in the clinic input of 'test_deprecate_positional_pos0_len1' to be keyword-only.") + # else + # warning "In clinic.test.c, update parameter(s) 'a' in the clinic input of 'test_deprecate_positional_pos0_len1' to be keyword-only." + # endif + #endif + if (nargs == 1) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing 1 positional arguments to test_deprecate_positional_pos0_len1() is deprecated. Parameter 'a' will become a keyword-only parameter in Python 3.14.", 1)) { + goto exit; + } + } + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf); + if (!args) { + goto exit; + } + a = args[0]; + return_value = test_deprecate_positional_pos0_len1_impl(module, a); + +exit: + return return_value; +} + +static PyObject * +test_deprecate_positional_pos0_len1_impl(PyObject *module, PyObject *a) +/*[clinic end generated code: output=5365071086c2171b input=678206db25c0652c]*/ + + +/*[clinic input] +test_deprecate_positional_pos0_len2 + * [from 3.14] + a: object + b: object +[clinic start generated code]*/ + +PyDoc_STRVAR(test_deprecate_positional_pos0_len2__doc__, +"test_deprecate_positional_pos0_len2($module, /, a, b)\n" +"--\n" +"\n"); + +#define TEST_DEPRECATE_POSITIONAL_POS0_LEN2_METHODDEF \ + {"test_deprecate_positional_pos0_len2", _PyCFunction_CAST(test_deprecate_positional_pos0_len2), METH_FASTCALL|METH_KEYWORDS, test_deprecate_positional_pos0_len2__doc__}, + +static PyObject * +test_deprecate_positional_pos0_len2_impl(PyObject *module, PyObject *a, + PyObject *b); + +static PyObject * +test_deprecate_positional_pos0_len2(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 2 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(a), &_Py_ID(b), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"a", "b", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "test_deprecate_positional_pos0_len2", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[2]; + PyObject *a; + PyObject *b; + + #if PY_VERSION_HEX >= 0x030e00C0 + # error "In clinic.test.c, update parameter(s) 'a' and 'b' in the clinic input of 'test_deprecate_positional_pos0_len2' to be keyword-only." + #elif PY_VERSION_HEX >= 0x030e00A0 + # ifdef _MSC_VER + # pragma message ("In clinic.test.c, update parameter(s) 'a' and 'b' in the clinic input of 'test_deprecate_positional_pos0_len2' to be keyword-only.") + # else + # warning "In clinic.test.c, update parameter(s) 'a' and 'b' in the clinic input of 'test_deprecate_positional_pos0_len2' to be keyword-only." + # endif + #endif + if (nargs > 0 && nargs <= 2) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing more than 0 positional arguments to test_deprecate_positional_pos0_len2() is deprecated. Parameters 'a' and 'b' will become keyword-only parameters in Python 3.14.", 1)) { + goto exit; + } + } + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 2, 2, 0, argsbuf); + if (!args) { + goto exit; + } + a = args[0]; + b = args[1]; + return_value = test_deprecate_positional_pos0_len2_impl(module, a, b); + +exit: + return return_value; +} + +static PyObject * +test_deprecate_positional_pos0_len2_impl(PyObject *module, PyObject *a, + PyObject *b) +/*[clinic end generated code: output=3d477331ccec8298 input=fae0d0b1d480c939]*/ + + +/*[clinic input] +test_deprecate_positional_pos0_len3_with_kwdonly + * [from 3.14] + a: object + b: object + c: object + * + e: object +[clinic start generated code]*/ + +PyDoc_STRVAR(test_deprecate_positional_pos0_len3_with_kwdonly__doc__, +"test_deprecate_positional_pos0_len3_with_kwdonly($module, /, a, b, c,\n" +" *, e)\n" +"--\n" +"\n"); + +#define TEST_DEPRECATE_POSITIONAL_POS0_LEN3_WITH_KWDONLY_METHODDEF \ + {"test_deprecate_positional_pos0_len3_with_kwdonly", _PyCFunction_CAST(test_deprecate_positional_pos0_len3_with_kwdonly), METH_FASTCALL|METH_KEYWORDS, test_deprecate_positional_pos0_len3_with_kwdonly__doc__}, + +static PyObject * +test_deprecate_positional_pos0_len3_with_kwdonly_impl(PyObject *module, + PyObject *a, + PyObject *b, + PyObject *c, + PyObject *e); + +static PyObject * +test_deprecate_positional_pos0_len3_with_kwdonly(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 4 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(a), &_Py_ID(b), &_Py_ID(c), &_Py_ID(e), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"a", "b", "c", "e", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "test_deprecate_positional_pos0_len3_with_kwdonly", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[4]; + PyObject *a; + PyObject *b; + PyObject *c; + PyObject *e; + + #if PY_VERSION_HEX >= 0x030e00C0 + # error "In clinic.test.c, update parameter(s) 'a', 'b' and 'c' in the clinic input of 'test_deprecate_positional_pos0_len3_with_kwdonly' to be keyword-only." + #elif PY_VERSION_HEX >= 0x030e00A0 + # ifdef _MSC_VER + # pragma message ("In clinic.test.c, update parameter(s) 'a', 'b' and 'c' in the clinic input of 'test_deprecate_positional_pos0_len3_with_kwdonly' to be keyword-only.") + # else + # warning "In clinic.test.c, update parameter(s) 'a', 'b' and 'c' in the clinic input of 'test_deprecate_positional_pos0_len3_with_kwdonly' to be keyword-only." + # endif + #endif + if (nargs > 0 && nargs <= 3) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing more than 0 positional arguments to test_deprecate_positional_pos0_len3_with_kwdonly() is deprecated. Parameters 'a', 'b' and 'c' will become keyword-only parameters in Python 3.14.", 1)) { + goto exit; + } + } + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 3, 3, 1, argsbuf); + if (!args) { + goto exit; + } + a = args[0]; + b = args[1]; + c = args[2]; + e = args[3]; + return_value = test_deprecate_positional_pos0_len3_with_kwdonly_impl(module, a, b, c, e); + +exit: + return return_value; +} + +static PyObject * +test_deprecate_positional_pos0_len3_with_kwdonly_impl(PyObject *module, + PyObject *a, + PyObject *b, + PyObject *c, + PyObject *e) +/*[clinic end generated code: output=256beee68d1e2fb8 input=1b0121770c0c52e0]*/ + + +/*[clinic input] +test_deprecate_positional_pos2_len1 + a: object + b: object + * [from 3.14] + c: object +[clinic start generated code]*/ + +PyDoc_STRVAR(test_deprecate_positional_pos2_len1__doc__, +"test_deprecate_positional_pos2_len1($module, /, a, b, c)\n" +"--\n" +"\n"); + +#define TEST_DEPRECATE_POSITIONAL_POS2_LEN1_METHODDEF \ + {"test_deprecate_positional_pos2_len1", _PyCFunction_CAST(test_deprecate_positional_pos2_len1), METH_FASTCALL|METH_KEYWORDS, test_deprecate_positional_pos2_len1__doc__}, + +static PyObject * +test_deprecate_positional_pos2_len1_impl(PyObject *module, PyObject *a, + PyObject *b, PyObject *c); + +static PyObject * +test_deprecate_positional_pos2_len1(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) @@ -5684,7 +6027,7 @@ test_deprecate_positional_use_3(PyObject *module, PyObject *const *args, Py_ssiz PyObject *ob_item[NUM_KEYWORDS]; } _kwtuple = { .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) - .ob_item = { &_Py_ID(pos), &_Py_ID(optarg), &_Py_ID(kw), }, + .ob_item = { &_Py_ID(a), &_Py_ID(b), &_Py_ID(c), }, }; #undef NUM_KEYWORDS #define KWTUPLE (&_kwtuple.ob_base.ob_base) @@ -5693,46 +6036,241 @@ test_deprecate_positional_use_3(PyObject *module, PyObject *const *args, Py_ssiz # define KWTUPLE NULL #endif // !Py_BUILD_CORE - static const char * const _keywords[] = {"pos", "optarg", "kw", NULL}; + static const char * const _keywords[] = {"a", "b", "c", NULL}; static _PyArg_Parser _parser = { .keywords = _keywords, - .fname = "test_deprecate_positional_use_3", + .fname = "test_deprecate_positional_pos2_len1", .kwtuple = KWTUPLE, }; #undef KWTUPLE PyObject *argsbuf[3]; - PyObject *pos; - PyObject *optarg; - PyObject *kw; + PyObject *a; + PyObject *b; + PyObject *c; #if PY_VERSION_HEX >= 0x030e00C0 - # error "In clinic.test.c, update parameter(s) 'optarg' in the clinic input of 'test_deprecate_positional_use_3' to be keyword-only." + # error "In clinic.test.c, update parameter(s) 'c' in the clinic input of 'test_deprecate_positional_pos2_len1' to be keyword-only." #elif PY_VERSION_HEX >= 0x030e00A0 # ifdef _MSC_VER - # pragma message ("In clinic.test.c, update parameter(s) 'optarg' in the clinic input of 'test_deprecate_positional_use_3' to be keyword-only.") + # pragma message ("In clinic.test.c, update parameter(s) 'c' in the clinic input of 'test_deprecate_positional_pos2_len1' to be keyword-only.") # else - # warning "In clinic.test.c, update parameter(s) 'optarg' in the clinic input of 'test_deprecate_positional_use_3' to be keyword-only." + # warning "In clinic.test.c, update parameter(s) 'c' in the clinic input of 'test_deprecate_positional_pos2_len1' to be keyword-only." # endif #endif - if (nargs == 2) { - if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing 2 positional arguments to test_deprecate_positional_use_3() is deprecated. Parameter 'optarg' will become a keyword-only parameter in Python 3.14.", 1)) { + if (nargs == 3) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing 3 positional arguments to test_deprecate_positional_pos2_len1() is deprecated. Parameter 'c' will become a keyword-only parameter in Python 3.14.", 1)) { goto exit; } } - args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 2, 2, 1, argsbuf); + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 3, 3, 0, argsbuf); + if (!args) { + goto exit; + } + a = args[0]; + b = args[1]; + c = args[2]; + return_value = test_deprecate_positional_pos2_len1_impl(module, a, b, c); + +exit: + return return_value; +} + +static PyObject * +test_deprecate_positional_pos2_len1_impl(PyObject *module, PyObject *a, + PyObject *b, PyObject *c) +/*[clinic end generated code: output=ceadd05f11f7f491 input=e1d129689e69ec7c]*/ + + +/*[clinic input] +test_deprecate_positional_pos2_len2 + a: object + b: object + * [from 3.14] + c: object + d: object +[clinic start generated code]*/ + +PyDoc_STRVAR(test_deprecate_positional_pos2_len2__doc__, +"test_deprecate_positional_pos2_len2($module, /, a, b, c, d)\n" +"--\n" +"\n"); + +#define TEST_DEPRECATE_POSITIONAL_POS2_LEN2_METHODDEF \ + {"test_deprecate_positional_pos2_len2", _PyCFunction_CAST(test_deprecate_positional_pos2_len2), METH_FASTCALL|METH_KEYWORDS, test_deprecate_positional_pos2_len2__doc__}, + +static PyObject * +test_deprecate_positional_pos2_len2_impl(PyObject *module, PyObject *a, + PyObject *b, PyObject *c, + PyObject *d); + +static PyObject * +test_deprecate_positional_pos2_len2(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 4 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(a), &_Py_ID(b), &_Py_ID(c), &_Py_ID(d), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"a", "b", "c", "d", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "test_deprecate_positional_pos2_len2", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[4]; + PyObject *a; + PyObject *b; + PyObject *c; + PyObject *d; + + #if PY_VERSION_HEX >= 0x030e00C0 + # error "In clinic.test.c, update parameter(s) 'c' and 'd' in the clinic input of 'test_deprecate_positional_pos2_len2' to be keyword-only." + #elif PY_VERSION_HEX >= 0x030e00A0 + # ifdef _MSC_VER + # pragma message ("In clinic.test.c, update parameter(s) 'c' and 'd' in the clinic input of 'test_deprecate_positional_pos2_len2' to be keyword-only.") + # else + # warning "In clinic.test.c, update parameter(s) 'c' and 'd' in the clinic input of 'test_deprecate_positional_pos2_len2' to be keyword-only." + # endif + #endif + if (nargs > 2 && nargs <= 4) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing more than 2 positional arguments to test_deprecate_positional_pos2_len2() is deprecated. Parameters 'c' and 'd' will become keyword-only parameters in Python 3.14.", 1)) { + goto exit; + } + } + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 4, 4, 0, argsbuf); if (!args) { goto exit; } - pos = args[0]; - optarg = args[1]; - kw = args[2]; - return_value = test_deprecate_positional_use_3_impl(module, pos, optarg, kw); + a = args[0]; + b = args[1]; + c = args[2]; + d = args[3]; + return_value = test_deprecate_positional_pos2_len2_impl(module, a, b, c, d); + +exit: + return return_value; +} + +static PyObject * +test_deprecate_positional_pos2_len2_impl(PyObject *module, PyObject *a, + PyObject *b, PyObject *c, + PyObject *d) +/*[clinic end generated code: output=5693682e3fa1188b input=0d53533463a12792]*/ + + +/*[clinic input] +test_deprecate_positional_pos2_len3_with_kwdonly + a: object + b: object + * [from 3.14] + c: object + d: object + * + e: object +[clinic start generated code]*/ + +PyDoc_STRVAR(test_deprecate_positional_pos2_len3_with_kwdonly__doc__, +"test_deprecate_positional_pos2_len3_with_kwdonly($module, /, a, b, c,\n" +" d, *, e)\n" +"--\n" +"\n"); + +#define TEST_DEPRECATE_POSITIONAL_POS2_LEN3_WITH_KWDONLY_METHODDEF \ + {"test_deprecate_positional_pos2_len3_with_kwdonly", _PyCFunction_CAST(test_deprecate_positional_pos2_len3_with_kwdonly), METH_FASTCALL|METH_KEYWORDS, test_deprecate_positional_pos2_len3_with_kwdonly__doc__}, + +static PyObject * +test_deprecate_positional_pos2_len3_with_kwdonly_impl(PyObject *module, + PyObject *a, + PyObject *b, + PyObject *c, + PyObject *d, + PyObject *e); + +static PyObject * +test_deprecate_positional_pos2_len3_with_kwdonly(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 5 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(a), &_Py_ID(b), &_Py_ID(c), &_Py_ID(d), &_Py_ID(e), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"a", "b", "c", "d", "e", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "test_deprecate_positional_pos2_len3_with_kwdonly", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[5]; + PyObject *a; + PyObject *b; + PyObject *c; + PyObject *d; + PyObject *e; + + #if PY_VERSION_HEX >= 0x030e00C0 + # error "In clinic.test.c, update parameter(s) 'c' and 'd' in the clinic input of 'test_deprecate_positional_pos2_len3_with_kwdonly' to be keyword-only." + #elif PY_VERSION_HEX >= 0x030e00A0 + # ifdef _MSC_VER + # pragma message ("In clinic.test.c, update parameter(s) 'c' and 'd' in the clinic input of 'test_deprecate_positional_pos2_len3_with_kwdonly' to be keyword-only.") + # else + # warning "In clinic.test.c, update parameter(s) 'c' and 'd' in the clinic input of 'test_deprecate_positional_pos2_len3_with_kwdonly' to be keyword-only." + # endif + #endif + if (nargs > 2 && nargs <= 4) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing more than 2 positional arguments to test_deprecate_positional_pos2_len3_with_kwdonly() is deprecated. Parameters 'c' and 'd' will become keyword-only parameters in Python 3.14.", 1)) { + goto exit; + } + } + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 4, 4, 1, argsbuf); + if (!args) { + goto exit; + } + a = args[0]; + b = args[1]; + c = args[2]; + d = args[3]; + e = args[4]; + return_value = test_deprecate_positional_pos2_len3_with_kwdonly_impl(module, a, b, c, d, e); exit: return return_value; } static PyObject * -test_deprecate_positional_use_3_impl(PyObject *module, PyObject *pos, - PyObject *optarg, PyObject *kw) -/*[clinic end generated code: output=430bebca9eeddec7 input=c19ac8533f05d314]*/ +test_deprecate_positional_pos2_len3_with_kwdonly_impl(PyObject *module, + PyObject *a, + PyObject *b, + PyObject *c, + PyObject *d, + PyObject *e) +/*[clinic end generated code: output=00d436de747a00f3 input=154fd450448d8935]*/ From bde0f90d4b9cbd3ec590fdfaf5d7192b54697db7 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 7 Aug 2023 01:04:29 +0200 Subject: [PATCH 65/71] Improve deprecation messages --- Lib/test/clinic.test.c | 12 ++++++------ Tools/clinic/clinic.py | 12 ++++++++---- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/Lib/test/clinic.test.c b/Lib/test/clinic.test.c index e245a467d3afe5..695141d6e11f9e 100644 --- a/Lib/test/clinic.test.c +++ b/Lib/test/clinic.test.c @@ -5792,7 +5792,7 @@ test_deprecate_positional_pos0_len1(PyObject *module, PyObject *const *args, Py_ # endif #endif if (nargs == 1) { - if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing 1 positional arguments to test_deprecate_positional_pos0_len1() is deprecated. Parameter 'a' will become a keyword-only parameter in Python 3.14.", 1)) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing positional arguments to test_deprecate_positional_pos0_len1() is deprecated. Parameter 'a' will become a keyword-only parameter in Python 3.14.", 1)) { goto exit; } } @@ -5809,7 +5809,7 @@ test_deprecate_positional_pos0_len1(PyObject *module, PyObject *const *args, Py_ static PyObject * test_deprecate_positional_pos0_len1_impl(PyObject *module, PyObject *a) -/*[clinic end generated code: output=5365071086c2171b input=678206db25c0652c]*/ +/*[clinic end generated code: output=1b7f23b9ffca431b input=678206db25c0652c]*/ /*[clinic input] @@ -5874,7 +5874,7 @@ test_deprecate_positional_pos0_len2(PyObject *module, PyObject *const *args, Py_ # endif #endif if (nargs > 0 && nargs <= 2) { - if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing more than 0 positional arguments to test_deprecate_positional_pos0_len2() is deprecated. Parameters 'a' and 'b' will become keyword-only parameters in Python 3.14.", 1)) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing positional arguments to test_deprecate_positional_pos0_len2() is deprecated. Parameters 'a' and 'b' will become keyword-only parameters in Python 3.14.", 1)) { goto exit; } } @@ -5893,7 +5893,7 @@ test_deprecate_positional_pos0_len2(PyObject *module, PyObject *const *args, Py_ static PyObject * test_deprecate_positional_pos0_len2_impl(PyObject *module, PyObject *a, PyObject *b) -/*[clinic end generated code: output=3d477331ccec8298 input=fae0d0b1d480c939]*/ +/*[clinic end generated code: output=31b494f2dcc016af input=fae0d0b1d480c939]*/ /*[clinic input] @@ -5967,7 +5967,7 @@ test_deprecate_positional_pos0_len3_with_kwdonly(PyObject *module, PyObject *con # endif #endif if (nargs > 0 && nargs <= 3) { - if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing more than 0 positional arguments to test_deprecate_positional_pos0_len3_with_kwdonly() is deprecated. Parameters 'a', 'b' and 'c' will become keyword-only parameters in Python 3.14.", 1)) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing positional arguments to test_deprecate_positional_pos0_len3_with_kwdonly() is deprecated. Parameters 'a', 'b' and 'c' will become keyword-only parameters in Python 3.14.", 1)) { goto exit; } } @@ -5991,7 +5991,7 @@ test_deprecate_positional_pos0_len3_with_kwdonly_impl(PyObject *module, PyObject *b, PyObject *c, PyObject *e) -/*[clinic end generated code: output=256beee68d1e2fb8 input=1b0121770c0c52e0]*/ +/*[clinic end generated code: output=96978e786acfbc7b input=1b0121770c0c52e0]*/ /*[clinic input] diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 348df7e3f0f13e..4669bbf1261633 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -900,17 +900,21 @@ def deprecate_positional_use( f"input of {func.full_name!r} to be keyword-only." ) # Format the deprecation message. + if first_pos == 0: + preamble = "Passing positional arguments to " if len(params) == 1: condition = f"nargs == {first_pos+1}" - depr_message = ( - f"Passing {first_pos+1} positional arguments to " + if first_pos: + preamble = f"Passing {first_pos+1} positional arguments to " + depr_message = preamble + ( f"{func.full_name}() is deprecated. Parameter {pstr} will " f"become a keyword-only parameter in Python {major}.{minor}." ) else: condition = f"nargs > {first_pos} && nargs <= {last_pos+1}" - depr_message = ( - f"Passing more than {first_pos} positional arguments to " + if first_pos: + preamble = f"Passing more than {first_pos} positional arguments to " + depr_message = preamble + ( f"{func.full_name}() is deprecated. Parameters {pstr} will " f"become keyword-only parameters in Python {major}.{minor}." ) From 8324510cbf11b3d1bb0210dfdd4b847392d053b3 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 7 Aug 2023 09:33:59 +0200 Subject: [PATCH 66/71] ValueError will catch if '.' not in thenceforth for us --- Lib/test/test_clinic.py | 12 ++++++------ Tools/clinic/clinic.py | 13 ++++--------- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index 4cf71bd2ee31f2..f4d405350009a0 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -1492,8 +1492,8 @@ def test_depr_star_invalid_format_1(self): Docstring. """ err = ( - "Function 'foo.bar': '* [from ...]' format expected to be " - "'', where 'major' and 'minor' are digits." + "Function 'foo.bar': expected format '* [from major.minor]' " + "where 'major' and 'minor' are integers; got '3'" ) self.expect_failure(block, err, lineno=3) @@ -1506,8 +1506,8 @@ def test_depr_star_invalid_format_2(self): Docstring. """ err = ( - "Function 'foo.bar', '* [from ]': " - "'major' and 'minor' must be digits, not 'a.b'" + "Function 'foo.bar': expected format '* [from major.minor]' " + "where 'major' and 'minor' are integers; got 'a.b'" ) self.expect_failure(block, err, lineno=3) @@ -1520,8 +1520,8 @@ def test_depr_star_invalid_format_3(self): Docstring. """ err = ( - "Function 'foo.bar', '* [from ]': " - "'major' and 'minor' must be digits, not '1.2.3'" + "Function 'foo.bar': expected format '* [from major.minor]' " + "where 'major' and 'minor' are integers; got '1.2.3'" ) self.expect_failure(block, err, lineno=3) diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index dc41a8ce2d0c0d..02266f11309491 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -4532,7 +4532,7 @@ class DSLParser: coexist: bool parameter_continuation: str preserve_output: bool - deprecate_posonly_re = create_regex( + star_from_version_re = create_regex( before="* [from ", after="]", word=False, @@ -5010,7 +5010,7 @@ def state_parameter(self, line: str) -> None: return line = line.lstrip() - match = self.deprecate_posonly_re.match(line) + match = self.star_from_version_re.match(line) if match: self.parse_deprecated_positional(match.group(1)) return @@ -5328,11 +5328,6 @@ def parse_deprecated_positional(self, thenceforth: str) -> None: assert isinstance(self.function, Function) fname = self.function.full_name - if "." not in thenceforth: - fail( - f"Function {fname!r}: '* [from ...]' format expected to be " - f"'', where 'major' and 'minor' are digits." - ) if self.keyword_only: fail(f"Function {fname!r}: '* [from ...]' must come before '*'") if self.deprecated_positional: @@ -5342,8 +5337,8 @@ def parse_deprecated_positional(self, thenceforth: str) -> None: self.deprecated_positional = int(major), int(minor) except ValueError: fail( - f"Function {fname!r}, '* [from ]': " - f"'major' and 'minor' must be digits, not {thenceforth!r}" + f"Function {fname!r}: expected format '* [from major.minor]' " + f"where 'major' and 'minor' are integers; got {thenceforth!r}" ) def parse_star(self, function: Function) -> None: From 2e4d64fd0cd50ae6e220394f3a47b3c31716299f Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 7 Aug 2023 09:42:10 +0200 Subject: [PATCH 67/71] Improve NEWS entry --- Doc/howto/clinic.rst | 2 ++ .../2022-07-23-00-33-28.gh-issue-95065.NfCCpp.rst | 8 ++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Doc/howto/clinic.rst b/Doc/howto/clinic.rst index 3eb538ed03afd9..286623c2410145 100644 --- a/Doc/howto/clinic.rst +++ b/Doc/howto/clinic.rst @@ -1900,6 +1900,8 @@ blocks embedded in Python files look slightly different. They look like this: #/*[python checksum:...]*/ +.. _clinic-howto-deprecate-positional: + How to deprecate passing parameters positionally ------------------------------------------------ diff --git a/Misc/NEWS.d/next/Tools-Demos/2022-07-23-00-33-28.gh-issue-95065.NfCCpp.rst b/Misc/NEWS.d/next/Tools-Demos/2022-07-23-00-33-28.gh-issue-95065.NfCCpp.rst index 63d65fba772632..6a52144bcd5cf1 100644 --- a/Misc/NEWS.d/next/Tools-Demos/2022-07-23-00-33-28.gh-issue-95065.NfCCpp.rst +++ b/Misc/NEWS.d/next/Tools-Demos/2022-07-23-00-33-28.gh-issue-95065.NfCCpp.rst @@ -1,2 +1,6 @@ -Add Argument Clinic support for deprecating positional use of optional -parameters. Patch by Erlend E. Aasland. +It is now possible to deprecate passing parameters positionally with +Argument Clinic, using the new ``* [from X.Y]`` syntax. +(To be read as *"keyword-only from Python version X.Y"*.) +See :ref:`clinic-howto-deprecate-positional` for more information. +Patch by Erlend E. Aasland with help from Alex Waygood, +Nikita Sobolev, and Serhiy Storchaca. From 52e7078d28f62c9c1651a7389bd74d950fa2aa93 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 7 Aug 2023 10:02:45 +0200 Subject: [PATCH 68/71] Update Misc/NEWS.d/next/Tools-Demos/2022-07-23-00-33-28.gh-issue-95065.NfCCpp.rst Co-authored-by: Serhiy Storchaka --- .../Tools-Demos/2022-07-23-00-33-28.gh-issue-95065.NfCCpp.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Tools-Demos/2022-07-23-00-33-28.gh-issue-95065.NfCCpp.rst b/Misc/NEWS.d/next/Tools-Demos/2022-07-23-00-33-28.gh-issue-95065.NfCCpp.rst index 6a52144bcd5cf1..3641716769cd56 100644 --- a/Misc/NEWS.d/next/Tools-Demos/2022-07-23-00-33-28.gh-issue-95065.NfCCpp.rst +++ b/Misc/NEWS.d/next/Tools-Demos/2022-07-23-00-33-28.gh-issue-95065.NfCCpp.rst @@ -3,4 +3,4 @@ Argument Clinic, using the new ``* [from X.Y]`` syntax. (To be read as *"keyword-only from Python version X.Y"*.) See :ref:`clinic-howto-deprecate-positional` for more information. Patch by Erlend E. Aasland with help from Alex Waygood, -Nikita Sobolev, and Serhiy Storchaca. +Nikita Sobolev, and Serhiy Storchaka. From 8a39ffbef81cdda5bc6603c94743247349c04c66 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 7 Aug 2023 11:26:01 +0200 Subject: [PATCH 69/71] Fix grammar in depr sentence --- Lib/test/clinic.test.c | 4 ++-- Tools/clinic/clinic.py | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Lib/test/clinic.test.c b/Lib/test/clinic.test.c index 695141d6e11f9e..321ac69273189f 100644 --- a/Lib/test/clinic.test.c +++ b/Lib/test/clinic.test.c @@ -5708,7 +5708,7 @@ test_deprecate_positional_pos1_len2_with_kwd(PyObject *module, PyObject *const * # endif #endif if (nargs > 1 && nargs <= 3) { - if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing more than 1 positional arguments to test_deprecate_positional_pos1_len2_with_kwd() is deprecated. Parameters 'b' and 'c' will become keyword-only parameters in Python 3.14.", 1)) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing more than 1 positional argument to test_deprecate_positional_pos1_len2_with_kwd() is deprecated. Parameters 'b' and 'c' will become keyword-only parameters in Python 3.14.", 1)) { goto exit; } } @@ -5730,7 +5730,7 @@ static PyObject * test_deprecate_positional_pos1_len2_with_kwd_impl(PyObject *module, PyObject *a, PyObject *b, PyObject *c, PyObject *d) -/*[clinic end generated code: output=60c1c9621739c235 input=28cdb885f6c34eab]*/ +/*[clinic end generated code: output=79c5f04220a1f3aa input=28cdb885f6c34eab]*/ /*[clinic input] diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 02266f11309491..4dfe90b314f543 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -913,7 +913,10 @@ def deprecate_positional_use( else: condition = f"nargs > {first_pos} && nargs <= {last_pos+1}" if first_pos: - preamble = f"Passing more than {first_pos} positional arguments to " + preamble = ( + f"Passing more than {first_pos} positional " + f"argument{'s' if first_pos != 1 else ''} to " + ) depr_message = preamble + ( f"{func.full_name}() is deprecated. Parameters {pstr} will " f"become keyword-only parameters in Python {major}.{minor}." From d2130abe8d2ffb250cf13585ea816cb724690e99 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 7 Aug 2023 12:29:32 +0200 Subject: [PATCH 70/71] Fix fixup code and add test --- Lib/test/test_clinic.py | 12 ++++++++++++ Tools/clinic/clinic.py | 10 ++++------ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index f4d405350009a0..f594e39a90546a 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -1483,6 +1483,18 @@ def test_parameters_required_after_star(self): with self.subTest(block=block): self.expect_failure(block, err) + def test_parameters_required_after_depr_star(self): + dataset = ( + "module foo\nfoo.bar\n * [from 3.14]", + "module foo\nfoo.bar\n * [from 3.14]\nDocstring here.", + "module foo\nfoo.bar\n this: int\n * [from 3.14]", + "module foo\nfoo.bar\n this: int\n * [from 3.14]\nDocstring.", + ) + err = "Function 'foo.bar' specifies '* [from 3.14]' without any parameters afterwards." + for block in dataset: + with self.subTest(block=block): + self.expect_failure(block, err) + def test_depr_star_invalid_format_1(self): block = """ module foo diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 4dfe90b314f543..daca7a34ced367 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -5729,12 +5729,10 @@ def check_remaining( ) -> None: assert isinstance(self.function, Function) - if values := self.function.parameters.values(): - last_param = next(reversed(values)) - no_param_after_symbol = condition(last_param) - else: - no_param_after_symbol = True - if no_param_after_symbol: + values = self.function.parameters.values() + assert values + last_param = next(reversed(values)) + if condition(last_param): fname = self.function.full_name fail(f"Function {fname!r} specifies {symbol!r} " "without any parameters afterwards.", line_number=lineno) From b21bc3230eebf31fdbba3a4635d5fea01b09d18d Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 7 Aug 2023 12:58:34 +0200 Subject: [PATCH 71/71] Revert "Fix fixup code and add test" This reverts commit d2130abe8d2ffb250cf13585ea816cb724690e99, but keeps the test. --- Tools/clinic/clinic.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index daca7a34ced367..4dfe90b314f543 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -5729,10 +5729,12 @@ def check_remaining( ) -> None: assert isinstance(self.function, Function) - values = self.function.parameters.values() - assert values - last_param = next(reversed(values)) - if condition(last_param): + if values := self.function.parameters.values(): + last_param = next(reversed(values)) + no_param_after_symbol = condition(last_param) + else: + no_param_after_symbol = True + if no_param_after_symbol: fname = self.function.full_name fail(f"Function {fname!r} specifies {symbol!r} " "without any parameters afterwards.", line_number=lineno)