mirror of
https://github.com/python/cpython.git
synced 2025-08-02 16:13:13 +00:00
gh-95065: Add Argument Clinic support for deprecating positional use of parameters (#95151)
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") Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com> Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
This commit is contained in:
parent
3c8e8f3cee
commit
33cb0b06ef
5 changed files with 1153 additions and 14 deletions
|
@ -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.
|
||||
|
|
|
@ -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]*/
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
|
@ -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()
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue