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) pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy