gh-107880: Teach Argument Clinic to clone __init__ and __new__ methods (#107885)

This commit is contained in:
Erlend E. Aasland 2023-08-13 12:13:11 +02:00 committed by GitHub
parent 7ddc1eaff1
commit 9b75ada6e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 244 additions and 8 deletions

View File

@ -2973,6 +2973,17 @@ class ClinicFunctionalTest(unittest.TestCase):
ac_tester.DeprStarNew(None)
self.assertEqual(cm.filename, __file__)
def test_depr_star_new_cloned(self):
regex = re.escape(
"Passing positional arguments to _testclinic.DeprStarNew.cloned() "
"is deprecated. Parameter 'a' will become a keyword-only parameter "
"in Python 3.14."
)
obj = ac_tester.DeprStarNew(a=None)
with self.assertWarnsRegex(DeprecationWarning, regex) as cm:
obj.cloned(None)
self.assertEqual(cm.filename, __file__)
def test_depr_star_init(self):
regex = re.escape(
"Passing positional arguments to _testclinic.DeprStarInit() is "
@ -2983,6 +2994,17 @@ class ClinicFunctionalTest(unittest.TestCase):
ac_tester.DeprStarInit(None)
self.assertEqual(cm.filename, __file__)
def test_depr_star_init_cloned(self):
regex = re.escape(
"Passing positional arguments to _testclinic.DeprStarInit.cloned() "
"is deprecated. Parameter 'a' will become a keyword-only parameter "
"in Python 3.14."
)
obj = ac_tester.DeprStarInit(a=None)
with self.assertWarnsRegex(DeprecationWarning, regex) as cm:
obj.cloned(None)
self.assertEqual(cm.filename, __file__)
def test_depr_star_pos0_len1(self):
fn = ac_tester.depr_star_pos0_len1
fn(a=None)

View File

@ -0,0 +1,2 @@
Argument Clinic can now clone :meth:`!__init__` and :meth:`!__new__`
methods.

View File

@ -1230,12 +1230,29 @@ depr_star_new_impl(PyTypeObject *type, PyObject *a)
return type->tp_alloc(type, 0);
}
/*[clinic input]
_testclinic.DeprStarNew.cloned as depr_star_new_clone = _testclinic.DeprStarNew.__new__
[clinic start generated code]*/
static PyObject *
depr_star_new_clone_impl(PyObject *type, PyObject *a)
/*[clinic end generated code: output=3b17bf885fa736bc input=ea659285d5dbec6c]*/
{
Py_RETURN_NONE;
}
static struct PyMethodDef depr_star_new_methods[] = {
DEPR_STAR_NEW_CLONE_METHODDEF
{NULL, NULL}
};
static PyTypeObject DeprStarNew = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "_testclinic.DeprStarNew",
.tp_basicsize = sizeof(PyObject),
.tp_new = depr_star_new,
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_methods = depr_star_new_methods,
};
@ -1254,6 +1271,22 @@ depr_star_init_impl(PyObject *self, PyObject *a)
return 0;
}
/*[clinic input]
_testclinic.DeprStarInit.cloned as depr_star_init_clone = _testclinic.DeprStarInit.__init__
[clinic start generated code]*/
static PyObject *
depr_star_init_clone_impl(PyObject *self, PyObject *a)
/*[clinic end generated code: output=ddfe8a1b5531e7cc input=561e103fe7f8e94f]*/
{
Py_RETURN_NONE;
}
static struct PyMethodDef depr_star_init_methods[] = {
DEPR_STAR_INIT_CLONE_METHODDEF
{NULL, NULL}
};
static PyTypeObject DeprStarInit = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "_testclinic.DeprStarInit",
@ -1261,6 +1294,7 @@ static PyTypeObject DeprStarInit = {
.tp_new = PyType_GenericNew,
.tp_init = depr_star_init,
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_methods = depr_star_init_methods,
};

View File

@ -92,6 +92,89 @@ exit:
return return_value;
}
PyDoc_STRVAR(depr_star_new_clone__doc__,
"cloned($self, /, a)\n"
"--\n"
"\n"
"Note: Passing positional arguments to _testclinic.DeprStarNew.cloned()\n"
"is deprecated. Parameter \'a\' will become a keyword-only parameter in\n"
"Python 3.14.\n"
"");
#define DEPR_STAR_NEW_CLONE_METHODDEF \
{"cloned", _PyCFunction_CAST(depr_star_new_clone), METH_FASTCALL|METH_KEYWORDS, depr_star_new_clone__doc__},
static PyObject *
depr_star_new_clone_impl(PyObject *type, PyObject *a);
static PyObject *
depr_star_new_clone(PyObject *type, 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 = "cloned",
.kwtuple = KWTUPLE,
};
#undef KWTUPLE
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()"
" 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 = depr_star_new_clone_impl(type, a);
exit:
return return_value;
}
PyDoc_STRVAR(depr_star_init__doc__,
"DeprStarInit(a)\n"
"--\n"
@ -176,6 +259,89 @@ exit:
return return_value;
}
PyDoc_STRVAR(depr_star_init_clone__doc__,
"cloned($self, /, a)\n"
"--\n"
"\n"
"Note: Passing positional arguments to\n"
"_testclinic.DeprStarInit.cloned() is deprecated. Parameter \'a\' will\n"
"become a keyword-only parameter in Python 3.14.\n"
"");
#define DEPR_STAR_INIT_CLONE_METHODDEF \
{"cloned", _PyCFunction_CAST(depr_star_init_clone), METH_FASTCALL|METH_KEYWORDS, depr_star_init_clone__doc__},
static PyObject *
depr_star_init_clone_impl(PyObject *self, PyObject *a);
static PyObject *
depr_star_init_clone(PyObject *self, 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 = "cloned",
.kwtuple = KWTUPLE,
};
#undef KWTUPLE
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 "
"_testclinic.DeprStarInit.cloned() 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 = depr_star_init_clone_impl(self, a);
exit:
return return_value;
}
PyDoc_STRVAR(depr_star_pos0_len1__doc__,
"depr_star_pos0_len1($module, /, a)\n"
"--\n"
@ -971,4 +1137,4 @@ depr_star_pos2_len2_with_kwd(PyObject *module, PyObject *const *args, Py_ssize_t
exit:
return return_value;
}
/*[clinic end generated code: output=18ab056f6cc06d7e input=a9049054013a1b77]*/
/*[clinic end generated code: output=7a16fee4d6742d54 input=a9049054013a1b77]*/

View File

@ -4888,13 +4888,25 @@ class DSLParser:
function_name = fields.pop()
module, cls = self.clinic._module_and_class(fields)
if not (existing_function.kind is self.kind and existing_function.coexist == self.coexist):
fail("'kind' of function and cloned function don't match! "
"(@classmethod/@staticmethod/@coexist)")
function = existing_function.copy(
name=function_name, full_name=full_name, module=module,
cls=cls, c_basename=c_basename, docstring=''
)
overrides: dict[str, Any] = {
"name": function_name,
"full_name": full_name,
"module": module,
"cls": cls,
"c_basename": c_basename,
"docstring": "",
}
if not (existing_function.kind is self.kind and
existing_function.coexist == self.coexist):
# Allow __new__ or __init__ methods.
if existing_function.kind.new_or_init:
overrides["kind"] = self.kind
# Future enhancement: allow custom return converters
overrides["return_converter"] = CReturnConverter()
else:
fail("'kind' of function and cloned function don't match! "
"(@classmethod/@staticmethod/@coexist)")
function = existing_function.copy(**overrides)
self.function = function
self.block.signatures.append(function)
(cls or module).functions.append(function)