Skip to content

Commit

Permalink
pythongh-107880: Teach Argument Clinic to clone __init__ and __new__ …
Browse files Browse the repository at this point in the history
…methods (python#107885)
  • Loading branch information
erlend-aasland authored Aug 13, 2023
1 parent 7ddc1ea commit 9b75ada
Show file tree
Hide file tree
Showing 5 changed files with 244 additions and 8 deletions.
22 changes: 22 additions & 0 deletions Lib/test/test_clinic.py
Original file line number Diff line number Diff line change
Expand Up @@ -2973,6 +2973,17 @@ def test_depr_star_new(self):
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 "
Expand All @@ -2983,6 +2994,17 @@ def test_depr_star_init(self):
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)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Argument Clinic can now clone :meth:`!__init__` and :meth:`!__new__`
methods.
34 changes: 34 additions & 0 deletions Modules/_testclinic.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};


Expand All @@ -1254,13 +1271,30 @@ 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",
.tp_basicsize = sizeof(PyObject),
.tp_new = PyType_GenericNew,
.tp_init = depr_star_init,
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_methods = depr_star_init_methods,
};


Expand Down
168 changes: 167 additions & 1 deletion Modules/clinic/_testclinic_depr_star.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 19 additions & 7 deletions Tools/clinic/clinic.py
Original file line number Diff line number Diff line change
Expand Up @@ -4888,13 +4888,25 @@ def state_modulename_name(self, line: str) -> None:
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)
Expand Down

0 comments on commit 9b75ada

Please sign in to comment.