Skip to content

Commit 9b75ada

Browse files
gh-107880: Teach Argument Clinic to clone __init__ and __new__ methods (#107885)
1 parent 7ddc1ea commit 9b75ada

File tree

5 files changed

+244
-8
lines changed

5 files changed

+244
-8
lines changed

Lib/test/test_clinic.py

+22
Original file line numberDiff line numberDiff line change
@@ -2973,6 +2973,17 @@ def test_depr_star_new(self):
29732973
ac_tester.DeprStarNew(None)
29742974
self.assertEqual(cm.filename, __file__)
29752975

2976+
def test_depr_star_new_cloned(self):
2977+
regex = re.escape(
2978+
"Passing positional arguments to _testclinic.DeprStarNew.cloned() "
2979+
"is deprecated. Parameter 'a' will become a keyword-only parameter "
2980+
"in Python 3.14."
2981+
)
2982+
obj = ac_tester.DeprStarNew(a=None)
2983+
with self.assertWarnsRegex(DeprecationWarning, regex) as cm:
2984+
obj.cloned(None)
2985+
self.assertEqual(cm.filename, __file__)
2986+
29762987
def test_depr_star_init(self):
29772988
regex = re.escape(
29782989
"Passing positional arguments to _testclinic.DeprStarInit() is "
@@ -2983,6 +2994,17 @@ def test_depr_star_init(self):
29832994
ac_tester.DeprStarInit(None)
29842995
self.assertEqual(cm.filename, __file__)
29852996

2997+
def test_depr_star_init_cloned(self):
2998+
regex = re.escape(
2999+
"Passing positional arguments to _testclinic.DeprStarInit.cloned() "
3000+
"is deprecated. Parameter 'a' will become a keyword-only parameter "
3001+
"in Python 3.14."
3002+
)
3003+
obj = ac_tester.DeprStarInit(a=None)
3004+
with self.assertWarnsRegex(DeprecationWarning, regex) as cm:
3005+
obj.cloned(None)
3006+
self.assertEqual(cm.filename, __file__)
3007+
29863008
def test_depr_star_pos0_len1(self):
29873009
fn = ac_tester.depr_star_pos0_len1
29883010
fn(a=None)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Argument Clinic can now clone :meth:`!__init__` and :meth:`!__new__`
2+
methods.

Modules/_testclinic.c

+34
Original file line numberDiff line numberDiff line change
@@ -1230,12 +1230,29 @@ depr_star_new_impl(PyTypeObject *type, PyObject *a)
12301230
return type->tp_alloc(type, 0);
12311231
}
12321232

1233+
/*[clinic input]
1234+
_testclinic.DeprStarNew.cloned as depr_star_new_clone = _testclinic.DeprStarNew.__new__
1235+
[clinic start generated code]*/
1236+
1237+
static PyObject *
1238+
depr_star_new_clone_impl(PyObject *type, PyObject *a)
1239+
/*[clinic end generated code: output=3b17bf885fa736bc input=ea659285d5dbec6c]*/
1240+
{
1241+
Py_RETURN_NONE;
1242+
}
1243+
1244+
static struct PyMethodDef depr_star_new_methods[] = {
1245+
DEPR_STAR_NEW_CLONE_METHODDEF
1246+
{NULL, NULL}
1247+
};
1248+
12331249
static PyTypeObject DeprStarNew = {
12341250
PyVarObject_HEAD_INIT(NULL, 0)
12351251
.tp_name = "_testclinic.DeprStarNew",
12361252
.tp_basicsize = sizeof(PyObject),
12371253
.tp_new = depr_star_new,
12381254
.tp_flags = Py_TPFLAGS_DEFAULT,
1255+
.tp_methods = depr_star_new_methods,
12391256
};
12401257

12411258

@@ -1254,13 +1271,30 @@ depr_star_init_impl(PyObject *self, PyObject *a)
12541271
return 0;
12551272
}
12561273

1274+
/*[clinic input]
1275+
_testclinic.DeprStarInit.cloned as depr_star_init_clone = _testclinic.DeprStarInit.__init__
1276+
[clinic start generated code]*/
1277+
1278+
static PyObject *
1279+
depr_star_init_clone_impl(PyObject *self, PyObject *a)
1280+
/*[clinic end generated code: output=ddfe8a1b5531e7cc input=561e103fe7f8e94f]*/
1281+
{
1282+
Py_RETURN_NONE;
1283+
}
1284+
1285+
static struct PyMethodDef depr_star_init_methods[] = {
1286+
DEPR_STAR_INIT_CLONE_METHODDEF
1287+
{NULL, NULL}
1288+
};
1289+
12571290
static PyTypeObject DeprStarInit = {
12581291
PyVarObject_HEAD_INIT(NULL, 0)
12591292
.tp_name = "_testclinic.DeprStarInit",
12601293
.tp_basicsize = sizeof(PyObject),
12611294
.tp_new = PyType_GenericNew,
12621295
.tp_init = depr_star_init,
12631296
.tp_flags = Py_TPFLAGS_DEFAULT,
1297+
.tp_methods = depr_star_init_methods,
12641298
};
12651299

12661300

Modules/clinic/_testclinic_depr_star.c.h

+167-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Tools/clinic/clinic.py

+19-7
Original file line numberDiff line numberDiff line change
@@ -4888,13 +4888,25 @@ def state_modulename_name(self, line: str) -> None:
48884888
function_name = fields.pop()
48894889
module, cls = self.clinic._module_and_class(fields)
48904890

4891-
if not (existing_function.kind is self.kind and existing_function.coexist == self.coexist):
4892-
fail("'kind' of function and cloned function don't match! "
4893-
"(@classmethod/@staticmethod/@coexist)")
4894-
function = existing_function.copy(
4895-
name=function_name, full_name=full_name, module=module,
4896-
cls=cls, c_basename=c_basename, docstring=''
4897-
)
4891+
overrides: dict[str, Any] = {
4892+
"name": function_name,
4893+
"full_name": full_name,
4894+
"module": module,
4895+
"cls": cls,
4896+
"c_basename": c_basename,
4897+
"docstring": "",
4898+
}
4899+
if not (existing_function.kind is self.kind and
4900+
existing_function.coexist == self.coexist):
4901+
# Allow __new__ or __init__ methods.
4902+
if existing_function.kind.new_or_init:
4903+
overrides["kind"] = self.kind
4904+
# Future enhancement: allow custom return converters
4905+
overrides["return_converter"] = CReturnConverter()
4906+
else:
4907+
fail("'kind' of function and cloned function don't match! "
4908+
"(@classmethod/@staticmethod/@coexist)")
4909+
function = existing_function.copy(**overrides)
48984910
self.function = function
48994911
self.block.signatures.append(function)
49004912
(cls or module).functions.append(function)

0 commit comments

Comments
 (0)