From 57d71735d225dd51a4034552aa2caaf933eb2c31 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 8 Aug 2023 12:18:40 +0300 Subject: [PATCH] WIP: gh-107704: Argument Clinic: add support for deprecating keyword use of parameters --- Modules/_testclinic.c | 18 + Modules/clinic/_testclinic_depr_star.c.h | 496 +++++++++++++---------- Tools/clinic/clinic.py | 240 +++++++---- 3 files changed, 459 insertions(+), 295 deletions(-) diff --git a/Modules/_testclinic.c b/Modules/_testclinic.c index c33536234af0bc3..3d77e480f7e8f45 100644 --- a/Modules/_testclinic.c +++ b/Modules/_testclinic.c @@ -1450,6 +1450,24 @@ depr_star_pos2_len2_with_kwd_impl(PyObject *module, PyObject *a, PyObject *b, } +/*[clinic input] +depr_keyword_1 + a: object + / + b: object + c: object = None + / [from 3.14] + d: object = None +[clinic start generated code]*/ + +static PyObject * +depr_keyword_1_impl(PyObject *module, PyObject *a, PyObject *b, PyObject *c, + PyObject *d) +/*[clinic end generated code: output=966537d4b9c21546 input=964e3a27f846b519]*/ +{ + Py_RETURN_NONE; +} + // Reset PY_VERSION_HEX #undef PY_VERSION_HEX #define PY_VERSION_HEX _SAVED_PY_VERSION diff --git a/Modules/clinic/_testclinic_depr_star.c.h b/Modules/clinic/_testclinic_depr_star.c.h index 1aa42dd40597776..65f2bcf25de314e 100644 --- a/Modules/clinic/_testclinic_depr_star.c.h +++ b/Modules/clinic/_testclinic_depr_star.c.h @@ -22,6 +22,22 @@ PyDoc_STRVAR(depr_star_new__doc__, static PyObject * depr_star_new_impl(PyTypeObject *type, PyObject *a); +// Emit compiler warnings when we get to Python 3.14. +#if PY_VERSION_HEX >= 0x030e00C0 +# error \ + "In _testclinic.c, update the clinic input of " \ + "'_testclinic.DeprStarNew.__new__'." +#elif PY_VERSION_HEX >= 0x030e00A0 +# ifdef _MSC_VER +# pragma message ( \ + "In _testclinic.c, update the clinic input of " \ + "'_testclinic.DeprStarNew.__new__'.") +# else +# warning \ + "In _testclinic.c, update the clinic input of " \ + "'_testclinic.DeprStarNew.__new__'." +# endif +#endif static PyObject * depr_star_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) { @@ -56,22 +72,6 @@ depr_star_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) Py_ssize_t nargs = PyTuple_GET_SIZE(args); PyObject *a; - // Emit compiler warnings when we get to Python 3.14. - #if PY_VERSION_HEX >= 0x030e00C0 - # error \ - "In _testclinic.c, update parameter(s) 'a' in the clinic input of" \ - " '_testclinic.DeprStarNew.__new__' to be keyword-only." - #elif PY_VERSION_HEX >= 0x030e00A0 - # ifdef _MSC_VER - # pragma message ( \ - "In _testclinic.c, update parameter(s) 'a' in the clinic input of" \ - " '_testclinic.DeprStarNew.__new__' to be keyword-only.") - # else - # warning \ - "In _testclinic.c, update parameter(s) 'a' in the clinic input of" \ - " '_testclinic.DeprStarNew.__new__' to be keyword-only." - # endif - #endif if (nargs == 1) { if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing positional arguments to _testclinic.DeprStarNew() is " @@ -107,6 +107,22 @@ PyDoc_STRVAR(depr_star_new_clone__doc__, static PyObject * depr_star_new_clone_impl(PyObject *type, PyObject *a); +// Emit compiler warnings when we get to Python 3.14. +#if PY_VERSION_HEX >= 0x030e00C0 +# error \ + "In _testclinic.c, update the clinic input of " \ + "'_testclinic.DeprStarNew.cloned'." +#elif PY_VERSION_HEX >= 0x030e00A0 +# ifdef _MSC_VER +# pragma message ( \ + "In _testclinic.c, update the clinic input of " \ + "'_testclinic.DeprStarNew.cloned'.") +# else +# warning \ + "In _testclinic.c, update the clinic input of " \ + "'_testclinic.DeprStarNew.cloned'." +# endif +#endif static PyObject * depr_star_new_clone(PyObject *type, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { @@ -139,22 +155,6 @@ depr_star_new_clone(PyObject *type, PyObject *const *args, Py_ssize_t nargs, PyO PyObject *argsbuf[1]; PyObject *a; - // Emit compiler warnings when we get to Python 3.14. - #if PY_VERSION_HEX >= 0x030e00C0 - # error \ - "In _testclinic.c, update parameter(s) 'a' in the clinic input of" \ - " '_testclinic.DeprStarNew.cloned' to be keyword-only." - #elif PY_VERSION_HEX >= 0x030e00A0 - # ifdef _MSC_VER - # pragma message ( \ - "In _testclinic.c, update parameter(s) 'a' in the clinic input of" \ - " '_testclinic.DeprStarNew.cloned' to be keyword-only.") - # else - # warning \ - "In _testclinic.c, update parameter(s) 'a' in the clinic input of" \ - " '_testclinic.DeprStarNew.cloned' to be keyword-only." - # endif - #endif if (nargs == 1) { if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing positional arguments to _testclinic.DeprStarNew.cloned()" @@ -189,6 +189,22 @@ PyDoc_STRVAR(depr_star_init__doc__, static int depr_star_init_impl(PyObject *self, PyObject *a); +// Emit compiler warnings when we get to Python 3.14. +#if PY_VERSION_HEX >= 0x030e00C0 +# error \ + "In _testclinic.c, update the clinic input of " \ + "'_testclinic.DeprStarInit.__init__'." +#elif PY_VERSION_HEX >= 0x030e00A0 +# ifdef _MSC_VER +# pragma message ( \ + "In _testclinic.c, update the clinic input of " \ + "'_testclinic.DeprStarInit.__init__'.") +# else +# warning \ + "In _testclinic.c, update the clinic input of " \ + "'_testclinic.DeprStarInit.__init__'." +# endif +#endif static int depr_star_init(PyObject *self, PyObject *args, PyObject *kwargs) { @@ -223,22 +239,6 @@ depr_star_init(PyObject *self, PyObject *args, PyObject *kwargs) Py_ssize_t nargs = PyTuple_GET_SIZE(args); PyObject *a; - // Emit compiler warnings when we get to Python 3.14. - #if PY_VERSION_HEX >= 0x030e00C0 - # error \ - "In _testclinic.c, update parameter(s) 'a' in the clinic input of" \ - " '_testclinic.DeprStarInit.__init__' to be keyword-only." - #elif PY_VERSION_HEX >= 0x030e00A0 - # ifdef _MSC_VER - # pragma message ( \ - "In _testclinic.c, update parameter(s) 'a' in the clinic input of" \ - " '_testclinic.DeprStarInit.__init__' to be keyword-only.") - # else - # warning \ - "In _testclinic.c, update parameter(s) 'a' in the clinic input of" \ - " '_testclinic.DeprStarInit.__init__' to be keyword-only." - # endif - #endif if (nargs == 1) { if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing positional arguments to _testclinic.DeprStarInit() is " @@ -274,6 +274,22 @@ PyDoc_STRVAR(depr_star_init_clone__doc__, static PyObject * depr_star_init_clone_impl(PyObject *self, PyObject *a); +// Emit compiler warnings when we get to Python 3.14. +#if PY_VERSION_HEX >= 0x030e00C0 +# error \ + "In _testclinic.c, update the clinic input of " \ + "'_testclinic.DeprStarInit.cloned'." +#elif PY_VERSION_HEX >= 0x030e00A0 +# ifdef _MSC_VER +# pragma message ( \ + "In _testclinic.c, update the clinic input of " \ + "'_testclinic.DeprStarInit.cloned'.") +# else +# warning \ + "In _testclinic.c, update the clinic input of " \ + "'_testclinic.DeprStarInit.cloned'." +# endif +#endif static PyObject * depr_star_init_clone(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { @@ -306,22 +322,6 @@ depr_star_init_clone(PyObject *self, PyObject *const *args, Py_ssize_t nargs, Py PyObject *argsbuf[1]; PyObject *a; - // Emit compiler warnings when we get to Python 3.14. - #if PY_VERSION_HEX >= 0x030e00C0 - # error \ - "In _testclinic.c, update parameter(s) 'a' in the clinic input of" \ - " '_testclinic.DeprStarInit.cloned' to be keyword-only." - #elif PY_VERSION_HEX >= 0x030e00A0 - # ifdef _MSC_VER - # pragma message ( \ - "In _testclinic.c, update parameter(s) 'a' in the clinic input of" \ - " '_testclinic.DeprStarInit.cloned' to be keyword-only.") - # else - # warning \ - "In _testclinic.c, update parameter(s) 'a' in the clinic input of" \ - " '_testclinic.DeprStarInit.cloned' to be keyword-only." - # endif - #endif if (nargs == 1) { if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing positional arguments to " @@ -357,6 +357,19 @@ PyDoc_STRVAR(depr_star_pos0_len1__doc__, static PyObject * depr_star_pos0_len1_impl(PyObject *module, PyObject *a); +// Emit compiler warnings when we get to Python 3.14. +#if PY_VERSION_HEX >= 0x030e00C0 +# error \ + "In _testclinic.c, update the clinic input of 'depr_star_pos0_len1'." +#elif PY_VERSION_HEX >= 0x030e00A0 +# ifdef _MSC_VER +# pragma message ( \ + "In _testclinic.c, update the clinic input of 'depr_star_pos0_len1'.") +# else +# warning \ + "In _testclinic.c, update the clinic input of 'depr_star_pos0_len1'." +# endif +#endif static PyObject * depr_star_pos0_len1(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { @@ -389,22 +402,6 @@ depr_star_pos0_len1(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P PyObject *argsbuf[1]; PyObject *a; - // Emit compiler warnings when we get to Python 3.14. - #if PY_VERSION_HEX >= 0x030e00C0 - # error \ - "In _testclinic.c, update parameter(s) 'a' in the clinic input of" \ - " 'depr_star_pos0_len1' to be keyword-only." - #elif PY_VERSION_HEX >= 0x030e00A0 - # ifdef _MSC_VER - # pragma message ( \ - "In _testclinic.c, update parameter(s) 'a' in the clinic input of" \ - " 'depr_star_pos0_len1' to be keyword-only.") - # else - # warning \ - "In _testclinic.c, update parameter(s) 'a' in the clinic input of" \ - " 'depr_star_pos0_len1' to be keyword-only." - # endif - #endif if (nargs == 1) { if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing positional arguments to depr_star_pos0_len1() is " @@ -440,6 +437,19 @@ PyDoc_STRVAR(depr_star_pos0_len2__doc__, static PyObject * depr_star_pos0_len2_impl(PyObject *module, PyObject *a, PyObject *b); +// Emit compiler warnings when we get to Python 3.14. +#if PY_VERSION_HEX >= 0x030e00C0 +# error \ + "In _testclinic.c, update the clinic input of 'depr_star_pos0_len2'." +#elif PY_VERSION_HEX >= 0x030e00A0 +# ifdef _MSC_VER +# pragma message ( \ + "In _testclinic.c, update the clinic input of 'depr_star_pos0_len2'.") +# else +# warning \ + "In _testclinic.c, update the clinic input of 'depr_star_pos0_len2'." +# endif +#endif static PyObject * depr_star_pos0_len2(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { @@ -473,22 +483,6 @@ depr_star_pos0_len2(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P PyObject *a; PyObject *b; - // Emit compiler warnings when we get to Python 3.14. - #if PY_VERSION_HEX >= 0x030e00C0 - # error \ - "In _testclinic.c, update parameter(s) 'a' and 'b' in the clinic " \ - "input of 'depr_star_pos0_len2' to be keyword-only." - #elif PY_VERSION_HEX >= 0x030e00A0 - # ifdef _MSC_VER - # pragma message ( \ - "In _testclinic.c, update parameter(s) 'a' and 'b' in the clinic " \ - "input of 'depr_star_pos0_len2' to be keyword-only.") - # else - # warning \ - "In _testclinic.c, update parameter(s) 'a' and 'b' in the clinic " \ - "input of 'depr_star_pos0_len2' to be keyword-only." - # endif - #endif if (nargs > 0 && nargs <= 2) { if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing positional arguments to depr_star_pos0_len2() is " @@ -526,6 +520,22 @@ static PyObject * depr_star_pos0_len3_with_kwd_impl(PyObject *module, PyObject *a, PyObject *b, PyObject *c, PyObject *d); +// Emit compiler warnings when we get to Python 3.14. +#if PY_VERSION_HEX >= 0x030e00C0 +# error \ + "In _testclinic.c, update the clinic input of " \ + "'depr_star_pos0_len3_with_kwd'." +#elif PY_VERSION_HEX >= 0x030e00A0 +# ifdef _MSC_VER +# pragma message ( \ + "In _testclinic.c, update the clinic input of " \ + "'depr_star_pos0_len3_with_kwd'.") +# else +# warning \ + "In _testclinic.c, update the clinic input of " \ + "'depr_star_pos0_len3_with_kwd'." +# endif +#endif static PyObject * depr_star_pos0_len3_with_kwd(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { @@ -561,25 +571,6 @@ depr_star_pos0_len3_with_kwd(PyObject *module, PyObject *const *args, Py_ssize_t PyObject *c; PyObject *d; - // Emit compiler warnings when we get to Python 3.14. - #if PY_VERSION_HEX >= 0x030e00C0 - # error \ - "In _testclinic.c, update parameter(s) 'a', 'b' and 'c' in the " \ - "clinic input of 'depr_star_pos0_len3_with_kwd' to be " \ - "keyword-only." - #elif PY_VERSION_HEX >= 0x030e00A0 - # ifdef _MSC_VER - # pragma message ( \ - "In _testclinic.c, update parameter(s) 'a', 'b' and 'c' in the " \ - "clinic input of 'depr_star_pos0_len3_with_kwd' to be " \ - "keyword-only.") - # else - # warning \ - "In _testclinic.c, update parameter(s) 'a', 'b' and 'c' in the " \ - "clinic input of 'depr_star_pos0_len3_with_kwd' to be " \ - "keyword-only." - # endif - #endif if (nargs > 0 && nargs <= 3) { if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing positional arguments to depr_star_pos0_len3_with_kwd() " @@ -618,6 +609,19 @@ PyDoc_STRVAR(depr_star_pos1_len1_opt__doc__, static PyObject * depr_star_pos1_len1_opt_impl(PyObject *module, PyObject *a, PyObject *b); +// Emit compiler warnings when we get to Python 3.14. +#if PY_VERSION_HEX >= 0x030e00C0 +# error \ + "In _testclinic.c, update the clinic input of 'depr_star_pos1_len1_opt'." +#elif PY_VERSION_HEX >= 0x030e00A0 +# ifdef _MSC_VER +# pragma message ( \ + "In _testclinic.c, update the clinic input of 'depr_star_pos1_len1_opt'.") +# else +# warning \ + "In _testclinic.c, update the clinic input of 'depr_star_pos1_len1_opt'." +# endif +#endif static PyObject * depr_star_pos1_len1_opt(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { @@ -652,22 +656,6 @@ depr_star_pos1_len1_opt(PyObject *module, PyObject *const *args, Py_ssize_t narg PyObject *a; PyObject *b = Py_None; - // Emit compiler warnings when we get to Python 3.14. - #if PY_VERSION_HEX >= 0x030e00C0 - # error \ - "In _testclinic.c, update parameter(s) 'b' in the clinic input of" \ - " 'depr_star_pos1_len1_opt' to be keyword-only." - #elif PY_VERSION_HEX >= 0x030e00A0 - # ifdef _MSC_VER - # pragma message ( \ - "In _testclinic.c, update parameter(s) 'b' in the clinic input of" \ - " 'depr_star_pos1_len1_opt' to be keyword-only.") - # else - # warning \ - "In _testclinic.c, update parameter(s) 'b' in the clinic input of" \ - " 'depr_star_pos1_len1_opt' to be keyword-only." - # endif - #endif if (nargs == 2) { if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing 2 positional arguments to depr_star_pos1_len1_opt() is " @@ -708,6 +696,19 @@ PyDoc_STRVAR(depr_star_pos1_len1__doc__, static PyObject * depr_star_pos1_len1_impl(PyObject *module, PyObject *a, PyObject *b); +// Emit compiler warnings when we get to Python 3.14. +#if PY_VERSION_HEX >= 0x030e00C0 +# error \ + "In _testclinic.c, update the clinic input of 'depr_star_pos1_len1'." +#elif PY_VERSION_HEX >= 0x030e00A0 +# ifdef _MSC_VER +# pragma message ( \ + "In _testclinic.c, update the clinic input of 'depr_star_pos1_len1'.") +# else +# warning \ + "In _testclinic.c, update the clinic input of 'depr_star_pos1_len1'." +# endif +#endif static PyObject * depr_star_pos1_len1(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { @@ -741,22 +742,6 @@ depr_star_pos1_len1(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P PyObject *a; PyObject *b; - // Emit compiler warnings when we get to Python 3.14. - #if PY_VERSION_HEX >= 0x030e00C0 - # error \ - "In _testclinic.c, update parameter(s) 'b' in the clinic input of" \ - " 'depr_star_pos1_len1' to be keyword-only." - #elif PY_VERSION_HEX >= 0x030e00A0 - # ifdef _MSC_VER - # pragma message ( \ - "In _testclinic.c, update parameter(s) 'b' in the clinic input of" \ - " 'depr_star_pos1_len1' to be keyword-only.") - # else - # warning \ - "In _testclinic.c, update parameter(s) 'b' in the clinic input of" \ - " 'depr_star_pos1_len1' to be keyword-only." - # endif - #endif if (nargs == 2) { if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing 2 positional arguments to depr_star_pos1_len1() is " @@ -794,6 +779,22 @@ static PyObject * depr_star_pos1_len2_with_kwd_impl(PyObject *module, PyObject *a, PyObject *b, PyObject *c, PyObject *d); +// Emit compiler warnings when we get to Python 3.14. +#if PY_VERSION_HEX >= 0x030e00C0 +# error \ + "In _testclinic.c, update the clinic input of " \ + "'depr_star_pos1_len2_with_kwd'." +#elif PY_VERSION_HEX >= 0x030e00A0 +# ifdef _MSC_VER +# pragma message ( \ + "In _testclinic.c, update the clinic input of " \ + "'depr_star_pos1_len2_with_kwd'.") +# else +# warning \ + "In _testclinic.c, update the clinic input of " \ + "'depr_star_pos1_len2_with_kwd'." +# endif +#endif static PyObject * depr_star_pos1_len2_with_kwd(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { @@ -829,22 +830,6 @@ depr_star_pos1_len2_with_kwd(PyObject *module, PyObject *const *args, Py_ssize_t PyObject *c; PyObject *d; - // Emit compiler warnings when we get to Python 3.14. - #if PY_VERSION_HEX >= 0x030e00C0 - # error \ - "In _testclinic.c, update parameter(s) 'b' and 'c' in the clinic " \ - "input of 'depr_star_pos1_len2_with_kwd' to be keyword-only." - #elif PY_VERSION_HEX >= 0x030e00A0 - # ifdef _MSC_VER - # pragma message ( \ - "In _testclinic.c, update parameter(s) 'b' and 'c' in the clinic " \ - "input of 'depr_star_pos1_len2_with_kwd' to be keyword-only.") - # else - # warning \ - "In _testclinic.c, update parameter(s) 'b' and 'c' in the clinic " \ - "input of 'depr_star_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 " @@ -884,6 +869,19 @@ static PyObject * depr_star_pos2_len1_impl(PyObject *module, PyObject *a, PyObject *b, PyObject *c); +// Emit compiler warnings when we get to Python 3.14. +#if PY_VERSION_HEX >= 0x030e00C0 +# error \ + "In _testclinic.c, update the clinic input of 'depr_star_pos2_len1'." +#elif PY_VERSION_HEX >= 0x030e00A0 +# ifdef _MSC_VER +# pragma message ( \ + "In _testclinic.c, update the clinic input of 'depr_star_pos2_len1'.") +# else +# warning \ + "In _testclinic.c, update the clinic input of 'depr_star_pos2_len1'." +# endif +#endif static PyObject * depr_star_pos2_len1(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { @@ -918,22 +916,6 @@ depr_star_pos2_len1(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P PyObject *b; PyObject *c; - // Emit compiler warnings when we get to Python 3.14. - #if PY_VERSION_HEX >= 0x030e00C0 - # error \ - "In _testclinic.c, update parameter(s) 'c' in the clinic input of" \ - " 'depr_star_pos2_len1' to be keyword-only." - #elif PY_VERSION_HEX >= 0x030e00A0 - # ifdef _MSC_VER - # pragma message ( \ - "In _testclinic.c, update parameter(s) 'c' in the clinic input of" \ - " 'depr_star_pos2_len1' to be keyword-only.") - # else - # warning \ - "In _testclinic.c, update parameter(s) 'c' in the clinic input of" \ - " 'depr_star_pos2_len1' to be keyword-only." - # endif - #endif if (nargs == 3) { if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing 3 positional arguments to depr_star_pos2_len1() is " @@ -972,6 +954,19 @@ static PyObject * depr_star_pos2_len2_impl(PyObject *module, PyObject *a, PyObject *b, PyObject *c, PyObject *d); +// Emit compiler warnings when we get to Python 3.14. +#if PY_VERSION_HEX >= 0x030e00C0 +# error \ + "In _testclinic.c, update the clinic input of 'depr_star_pos2_len2'." +#elif PY_VERSION_HEX >= 0x030e00A0 +# ifdef _MSC_VER +# pragma message ( \ + "In _testclinic.c, update the clinic input of 'depr_star_pos2_len2'.") +# else +# warning \ + "In _testclinic.c, update the clinic input of 'depr_star_pos2_len2'." +# endif +#endif static PyObject * depr_star_pos2_len2(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { @@ -1007,22 +1002,6 @@ depr_star_pos2_len2(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P PyObject *c; PyObject *d; - // Emit compiler warnings when we get to Python 3.14. - #if PY_VERSION_HEX >= 0x030e00C0 - # error \ - "In _testclinic.c, update parameter(s) 'c' and 'd' in the clinic " \ - "input of 'depr_star_pos2_len2' to be keyword-only." - #elif PY_VERSION_HEX >= 0x030e00A0 - # ifdef _MSC_VER - # pragma message ( \ - "In _testclinic.c, update parameter(s) 'c' and 'd' in the clinic " \ - "input of 'depr_star_pos2_len2' to be keyword-only.") - # else - # warning \ - "In _testclinic.c, update parameter(s) 'c' and 'd' in the clinic " \ - "input of 'depr_star_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 " @@ -1062,6 +1041,22 @@ static PyObject * depr_star_pos2_len2_with_kwd_impl(PyObject *module, PyObject *a, PyObject *b, PyObject *c, PyObject *d, PyObject *e); +// Emit compiler warnings when we get to Python 3.14. +#if PY_VERSION_HEX >= 0x030e00C0 +# error \ + "In _testclinic.c, update the clinic input of " \ + "'depr_star_pos2_len2_with_kwd'." +#elif PY_VERSION_HEX >= 0x030e00A0 +# ifdef _MSC_VER +# pragma message ( \ + "In _testclinic.c, update the clinic input of " \ + "'depr_star_pos2_len2_with_kwd'.") +# else +# warning \ + "In _testclinic.c, update the clinic input of " \ + "'depr_star_pos2_len2_with_kwd'." +# endif +#endif static PyObject * depr_star_pos2_len2_with_kwd(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { @@ -1098,22 +1093,6 @@ depr_star_pos2_len2_with_kwd(PyObject *module, PyObject *const *args, Py_ssize_t PyObject *d; PyObject *e; - // Emit compiler warnings when we get to Python 3.14. - #if PY_VERSION_HEX >= 0x030e00C0 - # error \ - "In _testclinic.c, update parameter(s) 'c' and 'd' in the clinic " \ - "input of 'depr_star_pos2_len2_with_kwd' to be keyword-only." - #elif PY_VERSION_HEX >= 0x030e00A0 - # ifdef _MSC_VER - # pragma message ( \ - "In _testclinic.c, update parameter(s) 'c' and 'd' in the clinic " \ - "input of 'depr_star_pos2_len2_with_kwd' to be keyword-only.") - # else - # warning \ - "In _testclinic.c, update parameter(s) 'c' and 'd' in the clinic " \ - "input of 'depr_star_pos2_len2_with_kwd' to be keyword-only." - # endif - #endif if (nargs > 2 && nargs <= 4) { if (PyErr_WarnEx(PyExc_DeprecationWarning, "Passing more than 2 positional arguments to " @@ -1137,4 +1116,97 @@ depr_star_pos2_len2_with_kwd(PyObject *module, PyObject *const *args, Py_ssize_t exit: return return_value; } -/*[clinic end generated code: output=7a16fee4d6742d54 input=a9049054013a1b77]*/ + +PyDoc_STRVAR(depr_keyword_1__doc__, +"depr_keyword_1($module, a, /, b, c=None, d=None)\n" +"--\n" +"\n"); + +#define DEPR_KEYWORD_1_METHODDEF \ + {"depr_keyword_1", _PyCFunction_CAST(depr_keyword_1), METH_FASTCALL|METH_KEYWORDS, depr_keyword_1__doc__}, + +static PyObject * +depr_keyword_1_impl(PyObject *module, PyObject *a, PyObject *b, PyObject *c, + PyObject *d); + +// Emit compiler warnings when we get to Python 3.14. +#if PY_VERSION_HEX >= 0x030e00C0 +# error \ + "In _testclinic.c, update the clinic input of 'depr_keyword_1'." +#elif PY_VERSION_HEX >= 0x030e00A0 +# ifdef _MSC_VER +# pragma message ( \ + "In _testclinic.c, update the clinic input of 'depr_keyword_1'.") +# else +# warning \ + "In _testclinic.c, update the clinic input of 'depr_keyword_1'." +# endif +#endif +static PyObject * +depr_keyword_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) + + #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(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[] = {"", "b", "c", "d", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "depr_keyword_1", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[4]; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 2; + PyObject *a; + PyObject *b; + PyObject *c = Py_None; + PyObject *d = Py_None; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 2, 4, 0, argsbuf); + if (!args) { + goto exit; + } + if ((nargs < 2) || (nargs < 3 && args[2])) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "Passing keyword arguments 'b' and 'c' to depr_keyword_1() is " + "deprecated. Corresponding parameters will become positional-only" + " in Python 3.14.", 1)) + { + goto exit; + } + } + a = args[0]; + b = args[1]; + if (!noptargs) { + goto skip_optional_pos; + } + if (args[2]) { + c = args[2]; + if (!--noptargs) { + goto skip_optional_pos; + } + } + d = args[3]; +skip_optional_pos: + return_value = depr_keyword_1_impl(module, a, b, c, d); + +exit: + return return_value; +} +/*[clinic end generated code: output=035107512e35c533 input=a9049054013a1b77]*/ diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 1e0303c77087eb1..4fa759c93ea2f02 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -849,23 +849,25 @@ class CLanguage(Language): #define {methoddef_name} #endif /* !defined({methoddef_name}) */ """) - DEPRECATED_POSITIONAL_PROTOTYPE: Final[str] = r""" + COMPILETIME_DEPRECATION_WARNING_PROTOTYPE: Final[str] = r""" // Emit compiler warnings when we get to Python {major}.{minor}. #if PY_VERSION_HEX >= 0x{major:02x}{minor:02x}00C0 # error \ - {cpp_message} + {message} #elif PY_VERSION_HEX >= 0x{major:02x}{minor:02x}00A0 # ifdef _MSC_VER # pragma message ( \ - {cpp_message}) + {message}) # else # warning \ - {cpp_message} + {message} # endif #endif + """ + DEPRECATION_WARNING_PROTOTYPE: Final[str] = r""" if ({condition}) {{{{ if (PyErr_WarnEx(PyExc_DeprecationWarning, - {depr_message}, 1)) + {message}, 1)) {{{{ goto exit; }}}} @@ -893,6 +895,34 @@ def render( function = o return self.render_function(clinic, function) + def compiletime_deprecated_warning( + self, + func: Function, + parameters: list[Parameter], + ) -> str | None: + minversion: VersionTuple | None = None + for p in parameters: + for version in p.deprecated_positional, p.deprecated_keyword: + if version and (not minversion or minversion > version): + minversion = version + if not minversion: + return None + + # Format the preprocessor warning and error messages. + assert isinstance(self.cpp.filename, str) + source = os.path.basename(self.cpp.filename) + message = ( + f"In {source}, update the clinic input of {func.full_name!r}." + ) + code = self.COMPILETIME_DEPRECATION_WARNING_PROTOTYPE.format( + major=minversion[0], + minor=minversion[1], + message=wrapped_c_string_literal(message, suffix=" \\", + width=72, + subsequent_indent=16), + ) + return normalize_snippet(code) + def deprecate_positional_use( self, func: Function, @@ -910,54 +940,83 @@ def deprecate_positional_use( 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 " + amount = "" + what = "arguments" 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.fulldisplayname}() is deprecated. Parameter {pstr} will " - f"become a keyword-only parameter in Python {major}.{minor}." - ) + amount = f"{first_pos+1} " + message = f"Parameter {pstr} will become a keyword-only parameter" 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.fulldisplayname}() is deprecated. Parameters {pstr} will " - f"become keyword-only parameters in Python {major}.{minor}." - ) - + amount = f"more than {first_pos} " + if first_pos == 1: + what = "argument" + message = f"Parameters {pstr} will become keyword-only parameters" + message = ( + f"Passing {amount}positional {what} to {func.fulldisplayname}() is deprecated. " + f"{message} in Python {major}.{minor}." + ) # Append deprecation warning to docstring. - lines = textwrap.wrap(f"Note: {depr_message}") - docstring = "\n".join(lines) + docstring = textwrap.fill(f"Note: {message}") func.docstring += f"\n\n{docstring}\n" + # Format and return the code block. + code = self.DEPRECATION_WARNING_PROTOTYPE.format( + condition=condition, + message=wrapped_c_string_literal(message, width=64, + subsequent_indent=20), + ) + return normalize_snippet(code, indent=4) + + def deprecate_keyword_use( + self, + func: Function, + params: dict[int, Parameter], + argname_fmt: str, + ) -> 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_keyword == last_param.deprecated_keyword + thenceforth = first_param.deprecated_keyword + assert thenceforth is not None + major, minor = thenceforth + # Format the deprecation message. + conditions = [] + for i, p in params.items(): + if p.is_optional(): + conditions.append(f"nargs < {i+1} && {argname_fmt % i}") + else: + conditions = [f"nargs < {i+1}"] + condition = ") || (".join(conditions) + if len(conditions) > 1: + condition = f"({condition})" + if len(params) == 1: + what1 = "argument" + what2 = "parameter" + else: + what1 = "arguments" + what2 = "parameters" + message = ( + f"Passing keyword {what1} {pstr} to {func.full_name}() is deprecated. " + f"Corresponding {what2} will become positional-only in Python {major}.{minor}." + ) # Format and return the code block. - code = self.DEPRECATED_POSITIONAL_PROTOTYPE.format( + code = self.DEPRECATION_WARNING_PROTOTYPE.format( condition=condition, - major=major, - minor=minor, - cpp_message=wrapped_c_string_literal(cpp_message, suffix=" \\", - width=64, - subsequent_indent=16), - depr_message=wrapped_c_string_literal(depr_message, width=64, - subsequent_indent=20), + message=wrapped_c_string_literal(message, width=64, + subsequent_indent=20), ) return normalize_snippet(code, indent=4) @@ -1311,6 +1370,15 @@ def parser_body( parser_prototype = self.PARSER_PROTOTYPE_DEF_CLASS deprecated_positionals: dict[int, Parameter] = {} + deprecated_keywords: dict[int, Parameter] = {} + for i, p in enumerate(parameters): + if p.deprecated_positional: + deprecated_positionals[i] = p + if p.deprecated_keyword: + deprecated_keywords[i] = p + if deprecated_keywords: + code = self.deprecate_keyword_use(f, deprecated_keywords, argname_fmt) + parser_code.append(code) add_label: str | None = None for i, p in enumerate(parameters): if isinstance(p.converter, defining_class_converter): @@ -1325,8 +1393,6 @@ def parser_body( 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' @@ -1356,8 +1422,6 @@ def parser_body( 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: @@ -1373,12 +1437,6 @@ def parser_body( }} """ % 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) @@ -1398,6 +1456,12 @@ def parser_body( goto exit; }} """, indent=4)] + + if deprecated_positionals: + code = self.deprecate_positional_use(f, deprecated_positionals) + # Insert the deprecation code before parameter parsing. + parser_code.insert(0, code) + parser_definition = parser_body(parser_prototype, *parser_code, declarations=declarations) @@ -1478,6 +1542,10 @@ def parser_body( parser_definition = parser_definition.replace("{return_value_declaration}", return_value_declaration) + compiler_warning = self.compiletime_deprecated_warning(f, parameters) + if compiler_warning: + parser_definition = compiler_warning + "\n" + parser_definition + d = { "docstring_prototype" : docstring_prototype, "docstring_definition" : docstring_definition, @@ -2751,6 +2819,7 @@ class Parameter: group: int = 0 # (`None` signifies that there is no deprecation) deprecated_positional: VersionTuple | None = None + deprecated_keyword: VersionTuple | None = None right_bracket_count: int = dc.field(init=False, default=0) def __repr__(self) -> str: @@ -4583,6 +4652,7 @@ class DSLParser: keyword_only: bool positional_only: bool deprecated_positional: VersionTuple | None + deprecated_keyword: VersionTuple | None group: int parameter_state: ParamState indent: IndentStack @@ -4590,11 +4660,7 @@ class DSLParser: coexist: bool parameter_continuation: str preserve_output: bool - star_from_version_re = create_regex( - before="* [from ", - after="]", - word=False, - ) + from_version_re = re.compile(r'([*/]) +\[from +(\d+)\.(\d+)\]') def __init__(self, clinic: Clinic) -> None: self.clinic = clinic @@ -4619,6 +4685,7 @@ def reset(self) -> None: self.keyword_only = False self.positional_only = False self.deprecated_positional = None + self.deprecated_keyword = None self.group = 0 self.parameter_state: ParamState = ParamState.START self.indent = IndentStack() @@ -5086,21 +5153,22 @@ def state_parameter(self, line: str) -> None: return line = line.lstrip() - match = self.star_from_version_re.match(line) + version: VersionTuple | None = None + match = self.from_version_re.fullmatch(line) if match: - self.parse_deprecated_positional(match.group(1)) - return + line = match[1] + version = int(match[2]), int(match[3]) func = self.function match line: case '*': - self.parse_star(func) + self.parse_star(func, version) case '[': self.parse_opening_square_bracket(func) case ']': self.parse_closing_square_bracket(func) case '/': - self.parse_slash(func) + self.parse_slash(func, version) case param: self.parse_parameter(param) @@ -5401,29 +5469,31 @@ def parse_converter( "Annotations must be either a name, a function call, or a string." ) - def parse_deprecated_positional(self, thenceforth: str) -> None: + def parse_version(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) + return int(major), int(minor) except ValueError: fail( - f"Function {fname!r}: expected format '* [from major.minor]' " + 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: + def parse_star(self, function: Function, version: VersionTuple | None) -> 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 + if version is None: + if self.keyword_only: + fail(f"Function {function.name!r} uses '*' more than once.") + self.keyword_only = True + else: + if self.keyword_only: + fail(f"Function {function.full_name!r}: '* [from ...]' must come before '*'") + if self.deprecated_positional: + fail(f"Function {function.full_name!r} uses '* [from ...]' more than once.") + self.deprecated_positional = version def parse_opening_square_bracket(self, function: Function) -> None: """Parse opening parameter group symbol '['.""" @@ -5457,11 +5527,18 @@ def parse_closing_square_bracket(self, function: Function) -> None: f"has an unsupported group configuration. " f"(Unexpected state {st}.c)") - def parse_slash(self, function: Function) -> None: + def parse_slash(self, function: Function, version: VersionTuple | None) -> None: """Parse positional-only parameter marker '/'.""" - if self.positional_only: - fail(f"Function {function.name!r} uses '/' more than once.") + if version is None: + if self.deprecated_keyword: + fail(f"Function {function.full_name!r}: '/ [from ...]' must come after '/'") + if self.positional_only: + fail(f"Function {function.name!r} uses '/' more than once.") + else: + if self.deprecated_keyword: + fail(f"Function {function.name!r} uses '/ [from ...]' more than once.") self.positional_only = True + self.deprecated_keyword = version # REQUIRED and OPTIONAL are allowed here, that allows positional-only # without option groups to work (and have default values!) allowed = { @@ -5473,19 +5550,16 @@ def parse_slash(self, function: Function) -> None: if (self.parameter_state not in allowed) or self.group: fail(f"Function {function.name!r} has an unsupported group configuration. " f"(Unexpected state {self.parameter_state}.d)") - if self.keyword_only: + if self.keyword_only or self.deprecated_positional: fail(f"Function {function.name!r} mixes keyword-only and " "positional-only parameters, which is unsupported.") # fixup preceding parameters for p in function.parameters.values(): - if p.is_vararg(): - continue - if (p.kind is not inspect.Parameter.POSITIONAL_OR_KEYWORD and - not isinstance(p.converter, self_converter) - ): - fail(f"Function {function.name!r} mixes keyword-only and " - "positional-only parameters, which is unsupported.") - p.kind = inspect.Parameter.POSITIONAL_ONLY + if p.kind is inspect.Parameter.POSITIONAL_OR_KEYWORD: + if version is None: + p.kind = inspect.Parameter.POSITIONAL_ONLY + else: + p.deprecated_keyword = version def state_parameter_docstring_start(self, line: str) -> None: assert self.indent.margin is not None, "self.margin.infer() has not yet been called to set the margin"