summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/howto/clinic.rst88
-rw-r--r--Lib/test/clinic.test.c810
-rw-r--r--Lib/test/test_clinic.py96
-rw-r--r--Misc/NEWS.d/next/Tools-Demos/2022-07-23-00-33-28.gh-issue-95065.NfCCpp.rst6
-rwxr-xr-xTools/clinic/clinic.py167
5 files changed, 1153 insertions, 14 deletions
diff --git a/Doc/howto/clinic.rst b/Doc/howto/clinic.rst
index e8e6aac..286623c 100644
--- a/Doc/howto/clinic.rst
+++ b/Doc/howto/clinic.rst
@@ -1898,3 +1898,91 @@ blocks embedded in Python files look slightly different. They look like this:
#[python start generated code]*/
def foo(): pass
#/*[python checksum:...]*/
+
+
+.. _clinic-howto-deprecate-positional:
+
+How to deprecate passing parameters positionally
+------------------------------------------------
+
+Argument Clinic provides syntax that makes it possible to generate code that
+deprecates passing :term:`arguments <argument>` positionally.
+For example, say we've got a module-level function :py:func:`!foo.myfunc`
+that has three :term:`parameters <parameter>`:
+positional-or-keyword parameters *a* and *b*, and a keyword-only parameter *c*::
+
+ /*[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;
+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]
+ 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 :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 alpha phase of the specified Python version kicks in.
+
+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
+
+ In file included from Modules/foomodule.c:139:
+ 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*
+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.
+
+.. note::
+
+ 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.
diff --git a/Lib/test/clinic.test.c b/Lib/test/clinic.test.c
index d2ad1a0..321ac69 100644
--- a/Lib/test/clinic.test.c
+++ b/Lib/test/clinic.test.c
@@ -5380,6 +5380,7 @@ static PyObject *
fn_with_default_binop_expr_impl(PyObject *module, PyObject *arg)
/*[clinic end generated code: output=018672772e4092ff input=1b55c8ae68d89453]*/
+
/*[python input]
class Custom_converter(CConverter):
type = "str"
@@ -5464,3 +5465,812 @@ exit:
static PyObject *
docstr_fallback_to_converter_default_impl(PyObject *module, str a)
/*[clinic end generated code: output=ae24a9c6f60ee8a6 input=0cbe6a4d24bc2274]*/
+
+
+/*[clinic input]
+test_deprecate_positional_pos1_len1_optional
+ a: object
+ * [from 3.14]
+ b: object = None
+[clinic start generated code]*/
+
+PyDoc_STRVAR(test_deprecate_positional_pos1_len1_optional__doc__,
+"test_deprecate_positional_pos1_len1_optional($module, /, a, b=None)\n"
+"--\n"
+"\n");
+
+#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_pos1_len1_optional_impl(PyObject *module,
+ PyObject *a, PyObject *b);
+
+static PyObject *
+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)
+
+ #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_pos1_len1_optional",
+ .kwtuple = KWTUPLE,
+ };
+ #undef KWTUPLE
+ PyObject *argsbuf[2];
+ Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1;
+ PyObject *a;
+ PyObject *b = Py_None;
+
+ #if PY_VERSION_HEX >= 0x030e00C0
+ # 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) '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) '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_pos1_len1_optional() is deprecated. Parameter 'b' 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;
+ }
+ a = args[0];
+ if (!noptargs) {
+ goto skip_optional_pos;
+ }
+ b = args[1];
+skip_optional_pos:
+ return_value = test_deprecate_positional_pos1_len1_optional_impl(module, a, b);
+
+exit:
+ return return_value;
+}
+
+static PyObject *
+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_pos1_len1
+ a: object
+ * [from 3.14]
+ b: object
+[clinic start generated code]*/
+
+PyDoc_STRVAR(test_deprecate_positional_pos1_len1__doc__,
+"test_deprecate_positional_pos1_len1($module, /, a, b)\n"
+"--\n"
+"\n");
+
+#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_pos1_len1_impl(PyObject *module, PyObject *a,
+ PyObject *b);
+
+static PyObject *
+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)
+
+ #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_pos1_len1",
+ .kwtuple = KWTUPLE,
+ };
+ #undef KWTUPLE
+ PyObject *argsbuf[2];
+ PyObject *a;
+ PyObject *b;
+
+ #if PY_VERSION_HEX >= 0x030e00C0
+ # 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) 'b' in the clinic input of 'test_deprecate_positional_pos1_len1' to be keyword-only.")
+ # else
+ # 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_pos1_len1() is deprecated. Parameter 'b' 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;
+ }
+ 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_pos1_len1_impl(PyObject *module, PyObject *a,
+ PyObject *b)
+/*[clinic end generated code: output=22c70f8b36085758 input=1702bbab1e9b3b99]*/
+
+
+/*[clinic input]
+test_deprecate_positional_pos1_len2_with_kwd
+ a: object
+ * [from 3.14]
+ b: object
+ c: 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 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;
+ }
+ }
+ 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=79c5f04220a1f3aa input=28cdb885f6c34eab]*/
+
+
+/*[clinic input]
+test_deprecate_positional_pos0_len1
+ * [from 3.14]
+ a: object
+[clinic start generated code]*/
+
+PyDoc_STRVAR(test_deprecate_positional_pos0_len1__doc__,
+"test_deprecate_positional_pos0_len1($module, /, a)\n"
+"--\n"
+"\n");
+
+#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_pos0_len1_impl(PyObject *module, PyObject *a);
+
+static PyObject *
+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 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=1b7f23b9ffca431b 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 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=31b494f2dcc016af 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 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=96978e786acfbc7b 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)
+
+ #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(a), &_Py_ID(b), &_Py_ID(c), },
+ };
+ #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", NULL};
+ static _PyArg_Parser _parser = {
+ .keywords = _keywords,
+ .fname = "test_deprecate_positional_pos2_len1",
+ .kwtuple = KWTUPLE,
+ };
+ #undef KWTUPLE
+ PyObject *argsbuf[3];
+ PyObject *a;
+ PyObject *b;
+ PyObject *c;
+
+ #if PY_VERSION_HEX >= 0x030e00C0
+ # 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) 'c' in the clinic input of 'test_deprecate_positional_pos2_len1' to be keyword-only.")
+ # else
+ # 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 == 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, 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;
+ }
+ 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_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]*/
diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py
index f30fad2..f594e39 100644
--- a/Lib/test/test_clinic.py
+++ b/Lib/test/test_clinic.py
@@ -1478,11 +1478,105 @@ class ClinicParserTest(TestCase):
"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)
+ 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
+ foo.bar
+ this: int
+ * [from 3]
+ Docstring.
+ """
+ err = (
+ "Function 'foo.bar': expected format '* [from major.minor]' "
+ "where 'major' and 'minor' are integers; got '3'"
+ )
+ self.expect_failure(block, err, lineno=3)
+
+ def test_depr_star_invalid_format_2(self):
+ block = """
+ module foo
+ foo.bar
+ this: int
+ * [from a.b]
+ Docstring.
+ """
+ err = (
+ "Function 'foo.bar': expected format '* [from major.minor]' "
+ "where 'major' and 'minor' are integers; got '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 'foo.bar': expected format '* [from major.minor]' "
+ "where 'major' and 'minor' are integers; got '1.2.3'"
+ )
+ self.expect_failure(block, err, lineno=3)
+
+ def test_parameters_required_after_depr_star(self):
+ block = """
+ module foo
+ foo.bar
+ this: int
+ * [from 3.14]
+ Docstring.
+ """
+ err = (
+ "Function 'foo.bar' specifies '* [from ...]' without "
+ "any parameters afterwards"
+ )
+ self.expect_failure(block, err, lineno=4)
+
+ def test_depr_star_must_come_before_star(self):
+ block = """
+ module foo
+ foo.bar
+ this: int
+ *
+ * [from 3.14]
+ Docstring.
+ """
+ err = "Function 'foo.bar': '* [from ...]' must come before '*'"
+ self.expect_failure(block, err, lineno=4)
+
+ def test_depr_star_duplicate(self):
+ block = """
+ module foo
+ foo.bar
+ a: int
+ * [from 3.14]
+ b: int
+ * [from 3.14]
+ c: int
+ Docstring.
+ """
+ err = "Function 'foo.bar' uses '[from ...]' more than once"
+ self.expect_failure(block, err, lineno=5)
+
def test_single_slash(self):
block = """
module foo
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 0000000..3641716
--- /dev/null
+++ b/Misc/NEWS.d/next/Tools-Demos/2022-07-23-00-33-28.gh-issue-95065.NfCCpp.rst
@@ -0,0 +1,6 @@
+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 Storchaka.
diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py
index 47b5f5a..4dfe90b 100755
--- a/Tools/clinic/clinic.py
+++ b/Tools/clinic/clinic.py
@@ -347,6 +347,13 @@ def suffix_all_lines(s: str, suffix: str) -> str:
return ''.join(final)
+def pprint_words(items: list[str]) -> str:
+ if len(items) <= 2:
+ return " and ".join(items)
+ else:
+ return ", ".join(items[:-1]) + " and " + items[-1]
+
+
def version_splitter(s: str) -> tuple[int, ...]:
"""Splits a version string into a tuple of integers.
@@ -828,6 +835,22 @@ class CLanguage(Language):
#define {methoddef_name}
#endif /* !defined({methoddef_name}) */
""")
+ DEPRECATED_POSITIONAL_PROTOTYPE: Final[str] = r"""
+ #if PY_VERSION_HEX >= 0x{major:02x}{minor:02x}00C0
+ # error "{cpp_message}"
+ #elif PY_VERSION_HEX >= 0x{major:02x}{minor:02x}00A0
+ # ifdef _MSC_VER
+ # pragma message ("{cpp_message}")
+ # else
+ # warning "{cpp_message}"
+ # endif
+ #endif
+ if ({condition}) {{{{
+ if (PyErr_WarnEx(PyExc_DeprecationWarning, "{depr_message}", 1)) {{{{
+ goto exit;
+ }}}}
+ }}}}
+ """
def __init__(self, filename: str) -> None:
super().__init__(filename)
@@ -850,6 +873,64 @@ class CLanguage(Language):
function = o
return self.render_function(clinic, function)
+ def deprecate_positional_use(
+ self,
+ func: Function,
+ params: dict[int, Parameter],
+ ) -> str:
+ assert len(params) > 0
+ 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()))
+
+ # 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)
+ major, minor = thenceforth
+ 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.
+ if first_pos == 0:
+ preamble = "Passing positional arguments to "
+ if len(params) == 1:
+ condition = f"nargs == {first_pos+1}"
+ 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}"
+ if first_pos:
+ 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}."
+ )
+ # Format and return the code block.
+ code = self.DEPRECATED_POSITIONAL_PROTOTYPE.format(
+ condition=condition,
+ major=major,
+ minor=minor,
+ cpp_message=cpp_message,
+ depr_message=depr_message,
+ )
+ return normalize_snippet(code, indent=4)
+
def docstring_for_c_string(
self,
f: Function
@@ -1199,6 +1280,7 @@ class CLanguage(Language):
flags = 'METH_METHOD|' + flags
parser_prototype = self.PARSER_PROTOTYPE_DEF_CLASS
+ deprecated_positionals: dict[int, Parameter] = {}
add_label: str | None = None
for i, p in enumerate(parameters):
if isinstance(p.converter, defining_class_converter):
@@ -1213,6 +1295,8 @@ class CLanguage(Language):
parser_code.append("%s:" % add_label)
add_label = None
if not p.is_optional():
+ if p.deprecated_positional:
+ deprecated_positionals[i] = p
parser_code.append(normalize_snippet(parsearg, indent=4))
elif i < pos_only:
add_label = 'skip_optional_posonly'
@@ -1242,6 +1326,8 @@ class CLanguage(Language):
goto %s;
}}
""" % add_label, indent=4))
+ if p.deprecated_positional:
+ deprecated_positionals[i] = p
if i + 1 == len(parameters):
parser_code.append(normalize_snippet(parsearg, indent=4))
else:
@@ -1257,6 +1343,12 @@ class CLanguage(Language):
}}
""" % 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)
@@ -2592,6 +2684,9 @@ class Function:
return f
+VersionTuple = tuple[int, int]
+
+
@dc.dataclass(repr=False, slots=True)
class Parameter:
"""
@@ -2606,6 +2701,8 @@ class Parameter:
annotation: object = inspect.Parameter.empty
docstring: str = ''
group: int = 0
+ # (`None` signifies that there is no deprecation)
+ deprecated_positional: VersionTuple | None = None
right_bracket_count: int = dc.field(init=False, default=0)
def __repr__(self) -> str:
@@ -4430,6 +4527,7 @@ class DSLParser:
state: StateKeeper
keyword_only: bool
positional_only: bool
+ deprecated_positional: VersionTuple | None
group: int
parameter_state: ParamState
indent: IndentStack
@@ -4437,6 +4535,11 @@ class DSLParser:
coexist: bool
parameter_continuation: str
preserve_output: bool
+ star_from_version_re = create_regex(
+ before="* [from ",
+ after="]",
+ word=False,
+ )
def __init__(self, clinic: Clinic) -> None:
self.clinic = clinic
@@ -4460,6 +4563,7 @@ class DSLParser:
self.state = self.state_dsl_start
self.keyword_only = False
self.positional_only = False
+ self.deprecated_positional = None
self.group = 0
self.parameter_state: ParamState = ParamState.START
self.indent = IndentStack()
@@ -4622,7 +4726,7 @@ class DSLParser:
exc.lineno = line_number
raise
- self.do_post_block_processing_cleanup()
+ self.do_post_block_processing_cleanup(line_number)
block.output.extend(self.clinic.language.render(self.clinic, block.signatures))
if self.preserve_output:
@@ -4908,8 +5012,14 @@ class DSLParser:
self.parameter_continuation = line[:-1]
return
+ line = line.lstrip()
+ match = self.star_from_version_re.match(line)
+ if match:
+ self.parse_deprecated_positional(match.group(1))
+ return
+
func = self.function
- match line.lstrip():
+ match line:
case '*':
self.parse_star(func)
case '[':
@@ -5182,7 +5292,9 @@ class DSLParser:
"after 'self'.")
- p = Parameter(parameter_name, kind, function=self.function, converter=converter, default=value, group=self.group)
+ 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:]:
@@ -5215,10 +5327,28 @@ class DSLParser:
"Annotations must be either a name, a function call, or a string."
)
+ def parse_deprecated_positional(self, thenceforth: str) -> None:
+ assert isinstance(self.function, Function)
+ fname = self.function.full_name
+
+ if self.keyword_only:
+ fail(f"Function {fname!r}: '* [from ...]' must come before '*'")
+ if self.deprecated_positional:
+ 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 {fname!r}: expected format '* [from major.minor]' "
+ f"where 'major' and 'minor' are integers; got {thenceforth!r}"
+ )
+
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 = None
self.keyword_only = True
def parse_opening_square_bracket(self, function: Function) -> None:
@@ -5586,23 +5716,34 @@ class DSLParser:
return docstring
- def do_post_block_processing_cleanup(self) -> None:
+ def do_post_block_processing_cleanup(self, lineno: int) -> None:
"""
Called when processing the block is done.
"""
if not self.function:
return
- if self.keyword_only:
- values = self.function.parameters.values()
- if not values:
- no_parameter_after_star = True
+ def check_remaining(
+ symbol: str,
+ condition: Callable[[Parameter], bool]
+ ) -> 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:
- last_parameter = next(reversed(list(values)))
- no_parameter_after_star = last_parameter.kind != inspect.Parameter.KEYWORD_ONLY
- if no_parameter_after_star:
- fail(f"Function {self.function.name!r} specifies '*' "
- "without any parameters afterwards.")
+ 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)
+
+ if self.keyword_only:
+ check_remaining("*", lambda p: p.kind != inspect.Parameter.KEYWORD_ONLY)
+
+ if self.deprecated_positional:
+ check_remaining("* [from ...]", lambda p: not p.deprecated_positional)
self.function.docstring = self.format_docstring()