From 278c9987640f1a24e331456209a057de6c62b366 Mon Sep 17 00:00:00 2001 From: Cody Maloney Date: Wed, 16 Sep 2026 18:34:52 -0700 Subject: [PATCH 1/2] gh-150097: Move _testclinic to multi-phase init Convert the static types to heap types. Move empty init vectorcall to share with existing no-arg __init__ case. Add support for no-argument vectorcall so the no-arg __init__ cases actually work (rather than adding an arg). Produce the same error string as the PyArg helpers do. The vectorcall types no longer have a C address for the generated `Py_Is` so instead compare the slot function instead. This drops the requirement that a class using vectorcall declare a type object. Co-Authored-By: Claude Fable 5.1 --- Lib/test/clinic.test.c | 40 ++- Lib/test/test_clinic.py | 8 +- Modules/_testclinic.c | 313 +++++++++++--------- Modules/clinic/_testclinic.c.h | 10 +- Objects/clinic/enumobject.c.h | 6 +- Objects/clinic/tupleobject.c.h | 4 +- Tools/c-analyzer/cpython/globals-to-fix.tsv | 11 - Tools/clinic/libclinic/dsl_parser.py | 5 - Tools/clinic/libclinic/parse_args.py | 44 ++- 9 files changed, 261 insertions(+), 180 deletions(-) diff --git a/Lib/test/clinic.test.c b/Lib/test/clinic.test.c index fa71df7f4f9d0ed..9aae1ce528a29da 100644 --- a/Lib/test/clinic.test.c +++ b/Lib/test/clinic.test.c @@ -4946,6 +4946,7 @@ Test_cls_with_param_impl(TestObj *self, PyTypeObject *cls, int a) /*[clinic input] +@vectorcall Test.__init__ Empty init method. [clinic start generated code]*/ @@ -4981,9 +4982,46 @@ Test___init__(PyObject *self, PyObject *args, PyObject *kwargs) return return_value; } +static PyObject * +Test_vectorcall(PyObject *type, PyObject *const *args, + size_t nargsf, PyObject *kwnames) +{ + PyObject *return_value = NULL; + Py_ssize_t nargs = PyVectorcall_NARGS(nargsf); + PyObject *self; + int _result; + + assert(_PyType_CAST(type)->tp_init == Test___init__); + /* Make sure the type object is immutable: the generated + * vectorcall doesn't deal e.g. with users reassigning __init__. */ + assert(PyType_HasFeature(_PyType_CAST(type), Py_TPFLAGS_IMMUTABLETYPE)); + if (nargs) { + PyErr_SetString(PyExc_TypeError, + "Test() takes no positional arguments"); + goto exit; + } + if (!_PyArg_NoKwnames("Test", kwnames)) { + goto exit; + } + self = _PyType_CAST(type)->tp_new(_PyType_CAST(type), + (PyObject *)&_Py_SINGLETON(tuple_empty), NULL); + if (self == NULL) { + goto exit; + } + _result = Test___init___impl((TestObj *)self); + if (_result != 0) { + Py_DECREF(self); + goto exit; + } + return_value = self; + +exit: + return return_value; +} + static int Test___init___impl(TestObj *self) -/*[clinic end generated code: output=f6a35c85bc5b408f input=4ea79fee54d0c3ff]*/ +/*[clinic end generated code: output=3798499df7a60323 input=645fe693f6c6b9b6]*/ /*[clinic input] diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index f5334f70768dc45..ebf2e446ef056b6 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -3262,8 +3262,6 @@ def test_vectorcall_on_init(self): class Foo "FooObject *" "Foo_Type" @vectorcall Foo.__init__ - iterable: object = NULL - / """ func = self.parse_function(block, signatures_in_block=3, function_index=2) @@ -3294,14 +3292,16 @@ class Foo "FooObject *" "Foo_Type" self.expect_failure(block, err, lineno=2) def test_vectorcall_without_type_object(self): - err = "@vectorcall requires the type object of 'Foo'" + # Heap types have no C pointer to name, so the type object is optional. block = """ module m class Foo "FooObject *" "" @vectorcall Foo.__init__ """ - self.expect_failure(block, err, lineno=3) + func = self.parse_function(block, signatures_in_block=3, + function_index=2) + self.assertTrue(func.vectorcall) def test_vectorcall_unsupported_converter(self): # str(encoding=...) has no parse_arg() implementation. diff --git a/Modules/_testclinic.c b/Modules/_testclinic.c index 8b1a547ff297d11..2813e261a36b75e 100644 --- a/Modules/_testclinic.c +++ b/Modules/_testclinic.c @@ -21,12 +21,6 @@ custom_converter(PyObject *obj, custom_t *val) } -/* Forward declarations for vectorcall types, needed because - * clinic/_testclinic.c.h is included before the type definitions. */ -static PyTypeObject VcNew_Type; -static PyTypeObject VcInit_Type; -static PyTypeObject VcNewBase_Type; -static PyTypeObject VcKwOnly_Type; #include "clinic/_testclinic.c.h" @@ -1861,13 +1855,17 @@ static struct PyMethodDef test_class_methods[] = { {NULL, NULL} }; -static PyTypeObject TestClass = { - PyVarObject_HEAD_INIT(NULL, 0) - .tp_name = "_testclinic.TestClass", - .tp_basicsize = sizeof(PyObject), - .tp_flags = Py_TPFLAGS_DEFAULT, - .tp_new = PyType_GenericNew, - .tp_methods = test_class_methods, +static PyType_Slot TestClass_slots[] = { + {Py_tp_new, PyType_GenericNew}, + {Py_tp_methods, test_class_methods}, + {0, NULL}, +}; + +static PyType_Spec TestClass_spec = { + .name = "_testclinic.TestClass", + .basicsize = sizeof(PyObject), + .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE, + .slots = TestClass_slots, }; @@ -1924,13 +1922,17 @@ static struct PyMethodDef depr_star_new_methods[] = { {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, +static PyType_Slot DeprStarNew_slots[] = { + {Py_tp_new, depr_star_new}, + {Py_tp_methods, depr_star_new_methods}, + {0, NULL}, +}; + +static PyType_Spec DeprStarNew_spec = { + .name = "_testclinic.DeprStarNew", + .basicsize = sizeof(PyObject), + .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE, + .slots = DeprStarNew_slots, }; @@ -1965,14 +1967,18 @@ static struct PyMethodDef depr_star_init_methods[] = { {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, +static PyType_Slot DeprStarInit_slots[] = { + {Py_tp_new, PyType_GenericNew}, + {Py_tp_init, depr_star_init}, + {Py_tp_methods, depr_star_init_methods}, + {0, NULL}, +}; + +static PyType_Spec DeprStarInit_spec = { + .name = "_testclinic.DeprStarInit", + .basicsize = sizeof(PyObject), + .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE, + .slots = DeprStarInit_slots, }; @@ -1996,13 +2002,17 @@ depr_star_init_noinline_impl(PyObject *self, PyObject *a, PyObject *b, return 0; } -static PyTypeObject DeprStarInitNoInline = { - PyVarObject_HEAD_INIT(NULL, 0) - .tp_name = "_testclinic.DeprStarInitNoInline", - .tp_basicsize = sizeof(PyObject), - .tp_new = PyType_GenericNew, - .tp_init = depr_star_init_noinline, - .tp_flags = Py_TPFLAGS_DEFAULT, +static PyType_Slot DeprStarInitNoInline_slots[] = { + {Py_tp_new, PyType_GenericNew}, + {Py_tp_init, depr_star_init_noinline}, + {0, NULL}, +}; + +static PyType_Spec DeprStarInitNoInline_spec = { + .name = "_testclinic.DeprStarInitNoInline", + .basicsize = sizeof(PyObject), + .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE, + .slots = DeprStarInitNoInline_slots, }; @@ -2022,12 +2032,16 @@ depr_kwd_new_impl(PyTypeObject *type, PyObject *a) return type->tp_alloc(type, 0); } -static PyTypeObject DeprKwdNew = { - PyVarObject_HEAD_INIT(NULL, 0) - .tp_name = "_testclinic.DeprKwdNew", - .tp_basicsize = sizeof(PyObject), - .tp_new = depr_kwd_new, - .tp_flags = Py_TPFLAGS_DEFAULT, +static PyType_Slot DeprKwdNew_slots[] = { + {Py_tp_new, depr_kwd_new}, + {0, NULL}, +}; + +static PyType_Spec DeprKwdNew_spec = { + .name = "_testclinic.DeprKwdNew", + .basicsize = sizeof(PyObject), + .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE, + .slots = DeprKwdNew_slots, }; @@ -2046,13 +2060,17 @@ depr_kwd_init_impl(PyObject *self, PyObject *a) return 0; } -static PyTypeObject DeprKwdInit = { - PyVarObject_HEAD_INIT(NULL, 0) - .tp_name = "_testclinic.DeprKwdInit", - .tp_basicsize = sizeof(PyObject), - .tp_new = PyType_GenericNew, - .tp_init = depr_kwd_init, - .tp_flags = Py_TPFLAGS_DEFAULT, +static PyType_Slot DeprKwdInit_slots[] = { + {Py_tp_new, PyType_GenericNew}, + {Py_tp_init, depr_kwd_init}, + {0, NULL}, +}; + +static PyType_Spec DeprKwdInit_spec = { + .name = "_testclinic.DeprKwdInit", + .basicsize = sizeof(PyObject), + .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE, + .slots = DeprKwdInit_slots, }; @@ -2076,13 +2094,17 @@ depr_kwd_init_noinline_impl(PyObject *self, PyObject *a, PyObject *b, return 0; } -static PyTypeObject DeprKwdInitNoInline = { - PyVarObject_HEAD_INIT(NULL, 0) - .tp_name = "_testclinic.DeprKwdInitNoInline", - .tp_basicsize = sizeof(PyObject), - .tp_new = PyType_GenericNew, - .tp_init = depr_kwd_init_noinline, - .tp_flags = Py_TPFLAGS_DEFAULT, +static PyType_Slot DeprKwdInitNoInline_slots[] = { + {Py_tp_new, PyType_GenericNew}, + {Py_tp_init, depr_kwd_init_noinline}, + {0, NULL}, +}; + +static PyType_Spec DeprKwdInitNoInline_spec = { + .name = "_testclinic.DeprKwdInitNoInline", + .basicsize = sizeof(PyObject), + .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE, + .slots = DeprKwdInitNoInline_slots, }; @@ -2510,7 +2532,7 @@ output pop /* VcNew: __new__ with one optional positional-or-keyword arg */ /*[clinic input] -class _testclinic.VcNew "PyObject *" "&VcNew_Type" +class _testclinic.VcNew "PyObject *" "" @classmethod @vectorcall _testclinic.VcNew.__new__ as vc_plain_new @@ -2519,18 +2541,22 @@ _testclinic.VcNew.__new__ as vc_plain_new static PyObject * vc_plain_new_impl(PyTypeObject *type, PyObject *a) -/*[clinic end generated code: output=55b273e9797a3013 input=e15d88606280badc]*/ +/*[clinic end generated code: output=55b273e9797a3013 input=459ce1ed0393de54]*/ { return type->tp_alloc(type, 0); } -static PyTypeObject VcNew_Type = { - PyVarObject_HEAD_INIT(NULL, 0) - .tp_name = "_testclinic.VcNew", - .tp_basicsize = sizeof(PyObject), - .tp_flags = Py_TPFLAGS_DEFAULT, - .tp_new = vc_plain_new, - .tp_vectorcall = vc_plain_vectorcall, +static PyType_Slot VcNew_slots[] = { + {Py_tp_new, vc_plain_new}, + {Py_tp_vectorcall, vc_plain_vectorcall}, + {0, NULL}, +}; + +static PyType_Spec VcNew_spec = { + .name = "_testclinic.VcNew", + .basicsize = sizeof(PyObject), + .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE, + .slots = VcNew_slots, }; @@ -2539,7 +2565,7 @@ static PyTypeObject VcNew_Type = { * in both the helper body and the vectorcall fast-path inner block. */ /*[clinic input] -class _testclinic.VcInit "PyObject *" "&VcInit_Type" +class _testclinic.VcInit "PyObject *" "" @vectorcall @critical_section _testclinic.VcInit.__init__ as vc_posorkw_init @@ -2550,19 +2576,23 @@ _testclinic.VcInit.__init__ as vc_posorkw_init static int vc_posorkw_init_impl(PyObject *self, PyObject *a, PyObject *b) -/*[clinic end generated code: output=6018424ba9fb0744 input=7a4513f78dd42b57]*/ +/*[clinic end generated code: output=6018424ba9fb0744 input=f0c7a84428d5b33d]*/ { return 0; } -static PyTypeObject VcInit_Type = { - PyVarObject_HEAD_INIT(NULL, 0) - .tp_name = "_testclinic.VcInit", - .tp_basicsize = sizeof(PyObject), - .tp_flags = Py_TPFLAGS_DEFAULT, - .tp_new = PyType_GenericNew, - .tp_init = vc_posorkw_init, - .tp_vectorcall = vc_posorkw_vectorcall, +static PyType_Slot VcInit_slots[] = { + {Py_tp_new, PyType_GenericNew}, + {Py_tp_init, vc_posorkw_init}, + {Py_tp_vectorcall, vc_posorkw_vectorcall}, + {0, NULL}, +}; + +static PyType_Spec VcInit_spec = { + .name = "_testclinic.VcInit", + .basicsize = sizeof(PyObject), + .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE, + .slots = VcInit_slots, }; @@ -2571,7 +2601,7 @@ static PyTypeObject VcInit_Type = { * is constructed through tp_new, never reaching vc_base_vectorcall. */ /*[clinic input] -class _testclinic.VcNewBase "PyObject *" "&VcNewBase_Type" +class _testclinic.VcNewBase "PyObject *" "" @classmethod @vectorcall _testclinic.VcNewBase.__new__ as vc_base_new @@ -2582,18 +2612,23 @@ _testclinic.VcNewBase.__new__ as vc_base_new static PyObject * vc_base_new_impl(PyTypeObject *type, PyObject *a, PyObject *b) -/*[clinic end generated code: output=e4ca5a11e7fb1148 input=c204ca773dc608bf]*/ +/*[clinic end generated code: output=e4ca5a11e7fb1148 input=3dd2277fb9119c40]*/ { return type->tp_alloc(type, 0); } -static PyTypeObject VcNewBase_Type = { - PyVarObject_HEAD_INIT(NULL, 0) - .tp_name = "_testclinic.VcNewBase", - .tp_basicsize = sizeof(PyObject), - .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, - .tp_new = vc_base_new, - .tp_vectorcall = vc_base_vectorcall, +static PyType_Slot VcNewBase_slots[] = { + {Py_tp_new, vc_base_new}, + {Py_tp_vectorcall, vc_base_vectorcall}, + {0, NULL}, +}; + +static PyType_Spec VcNewBase_spec = { + .name = "_testclinic.VcNewBase", + .basicsize = sizeof(PyObject), + .flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE + | Py_TPFLAGS_IMMUTABLETYPE), + .slots = VcNewBase_slots, }; @@ -2603,7 +2638,7 @@ static PyTypeObject VcNewBase_Type = { * keyword-only parameter rules out the positional-only fast path. */ /*[clinic input] -class _testclinic.VcKwOnly "PyObject *" "&VcKwOnly_Type" +class _testclinic.VcKwOnly "PyObject *" "" @classmethod @vectorcall _testclinic.VcKwOnly.__new__ as vc_kwonly_new @@ -2614,18 +2649,22 @@ _testclinic.VcKwOnly.__new__ as vc_kwonly_new static PyObject * vc_kwonly_new_impl(PyTypeObject *type, PyObject *a, PyObject *b) -/*[clinic end generated code: output=00417079caa234dc input=68c863b55575a9e1]*/ +/*[clinic end generated code: output=00417079caa234dc input=682417407ec0e613]*/ { return type->tp_alloc(type, 0); } -static PyTypeObject VcKwOnly_Type = { - PyVarObject_HEAD_INIT(NULL, 0) - .tp_name = "_testclinic.VcKwOnly", - .tp_basicsize = sizeof(PyObject), - .tp_flags = Py_TPFLAGS_DEFAULT, - .tp_new = vc_kwonly_new, - .tp_vectorcall = vc_kwonly_vectorcall, +static PyType_Slot VcKwOnly_slots[] = { + {Py_tp_new, vc_kwonly_new}, + {Py_tp_vectorcall, vc_kwonly_vectorcall}, + {0, NULL}, +}; + +static PyType_Spec VcKwOnly_spec = { + .name = "_testclinic.VcKwOnly", + .basicsize = sizeof(PyObject), + .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE, + .slots = VcKwOnly_slots, }; @@ -2839,61 +2878,61 @@ static PyMethodDef tester_methods[] = { {NULL, NULL} }; +static int +add_type(PyObject *module, PyType_Spec *spec) +{ + PyObject *type = PyType_FromModuleAndSpec(module, spec, NULL); + if (type == NULL) { + return -1; + } + int rc = PyModule_AddType(module, (PyTypeObject *)type); + Py_DECREF(type); + return rc; +} + +static int +_testclinic_exec(PyObject *module) +{ + PyType_Spec *specs[] = { + &TestClass_spec, + &DeprStarNew_spec, + &DeprStarInit_spec, + &DeprStarInitNoInline_spec, + &DeprKwdNew_spec, + &DeprKwdInit_spec, + &DeprKwdInitNoInline_spec, + &VcNew_spec, + &VcInit_spec, + &VcNewBase_spec, + &VcKwOnly_spec, + }; + for (size_t i = 0; i < Py_ARRAY_LENGTH(specs); i++) { + if (add_type(module, specs[i]) < 0) { + return -1; + } + } + return 0; +} + +static PyModuleDef_Slot _testclinic_slots[] = { + {Py_mod_exec, _testclinic_exec}, + {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, + {Py_mod_gil, Py_MOD_GIL_NOT_USED}, + {0, NULL} +}; + static struct PyModuleDef _testclinic_module = { PyModuleDef_HEAD_INIT, .m_name = "_testclinic", .m_size = 0, .m_methods = tester_methods, + .m_slots = _testclinic_slots, }; PyMODINIT_FUNC PyInit__testclinic(void) { - PyObject *m = PyModule_Create(&_testclinic_module); - if (m == NULL) { - return NULL; - } -#ifdef Py_GIL_DISABLED - PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED); -#endif - if (PyModule_AddType(m, &TestClass) < 0) { - goto error; - } - if (PyModule_AddType(m, &DeprStarNew) < 0) { - goto error; - } - if (PyModule_AddType(m, &DeprStarInit) < 0) { - goto error; - } - if (PyModule_AddType(m, &DeprStarInitNoInline) < 0) { - goto error; - } - if (PyModule_AddType(m, &DeprKwdNew) < 0) { - goto error; - } - if (PyModule_AddType(m, &DeprKwdInit) < 0) { - goto error; - } - if (PyModule_AddType(m, &DeprKwdInitNoInline) < 0) { - goto error; - } - if (PyModule_AddType(m, &VcNew_Type) < 0) { - goto error; - } - if (PyModule_AddType(m, &VcInit_Type) < 0) { - goto error; - } - if (PyModule_AddType(m, &VcNewBase_Type) < 0) { - goto error; - } - if (PyModule_AddType(m, &VcKwOnly_Type) < 0) { - goto error; - } - return m; - -error: - Py_DECREF(m); - return NULL; + return PyModuleDef_Init(&_testclinic_module); } #undef RETURN_PACKED_ARGS diff --git a/Modules/clinic/_testclinic.c.h b/Modules/clinic/_testclinic.c.h index c3bf217a9e7b7b7..4b9e5d2791d5361 100644 --- a/Modules/clinic/_testclinic.c.h +++ b/Modules/clinic/_testclinic.c.h @@ -5030,7 +5030,7 @@ vc_plain_vectorcall(PyObject *type, PyObject *const *args, Py_ssize_t nargs = PyVectorcall_NARGS(nargsf); PyObject *a = Py_None; - assert(Py_Is(_PyType_CAST(type), &VcNew_Type)); + assert(_PyType_CAST(type)->tp_new == vc_plain_new); /* Make sure the type object is immutable: the generated * vectorcall doesn't deal e.g. with users reassigning __init__. */ assert(PyType_HasFeature(_PyType_CAST(type), Py_TPFLAGS_IMMUTABLETYPE)); @@ -5129,7 +5129,7 @@ vc_posorkw_vectorcall(PyObject *type, PyObject *const *args, PyObject *a; PyObject *b = Py_None; - assert(Py_Is(_PyType_CAST(type), &VcInit_Type)); + assert(_PyType_CAST(type)->tp_init == vc_posorkw_init); /* Make sure the type object is immutable: the generated * vectorcall doesn't deal e.g. with users reassigning __init__. */ assert(PyType_HasFeature(_PyType_CAST(type), Py_TPFLAGS_IMMUTABLETYPE)); @@ -5248,7 +5248,7 @@ vc_base_vectorcall(PyObject *type, PyObject *const *args, PyObject *a; PyObject *b = Py_None; - assert(Py_Is(_PyType_CAST(type), &VcNewBase_Type)); + assert(_PyType_CAST(type)->tp_new == vc_base_new); /* Make sure the type object is immutable: the generated * vectorcall doesn't deal e.g. with users reassigning __init__. */ assert(PyType_HasFeature(_PyType_CAST(type), Py_TPFLAGS_IMMUTABLETYPE)); @@ -5341,7 +5341,7 @@ vc_kwonly_vectorcall(PyObject *type, PyObject *const *args, { Py_ssize_t nargs = PyVectorcall_NARGS(nargsf); - assert(Py_Is(_PyType_CAST(type), &VcKwOnly_Type)); + assert(_PyType_CAST(type)->tp_new == vc_kwonly_new); /* Make sure the type object is immutable: the generated * vectorcall doesn't deal e.g. with users reassigning __init__. */ assert(PyType_HasFeature(_PyType_CAST(type), Py_TPFLAGS_IMMUTABLETYPE)); @@ -5349,4 +5349,4 @@ vc_kwonly_vectorcall(PyObject *type, PyObject *const *args, kwnames ? PyTuple_GET_SIZE(kwnames) : 0, NULL, kwnames); } -/*[clinic end generated code: output=8a219f606f1296ac input=a9049054013a1b77]*/ +/*[clinic end generated code: output=31a229ecc8e80427 input=a9049054013a1b77]*/ diff --git a/Objects/clinic/enumobject.c.h b/Objects/clinic/enumobject.c.h index 26b1801cb7312ed..9b8a25a60b571ea 100644 --- a/Objects/clinic/enumobject.c.h +++ b/Objects/clinic/enumobject.c.h @@ -99,7 +99,7 @@ enum_vectorcall(PyObject *type, PyObject *const *args, PyObject *iterable; PyObject *start = 0; - assert(Py_Is(_PyType_CAST(type), &PyEnum_Type)); + assert(_PyType_CAST(type)->tp_new == enum_new); /* Make sure the type object is immutable: the generated * vectorcall doesn't deal e.g. with users reassigning __init__. */ assert(PyType_HasFeature(_PyType_CAST(type), Py_TPFLAGS_IMMUTABLETYPE)); @@ -157,7 +157,7 @@ reversed_vectorcall(PyObject *type, PyObject *const *args, Py_ssize_t nargs = PyVectorcall_NARGS(nargsf); PyObject *seq; - assert(Py_Is(_PyType_CAST(type), &PyReversed_Type)); + assert(_PyType_CAST(type)->tp_new == reversed_new); /* Make sure the type object is immutable: the generated * vectorcall doesn't deal e.g. with users reassigning __init__. */ assert(PyType_HasFeature(_PyType_CAST(type), Py_TPFLAGS_IMMUTABLETYPE)); @@ -173,4 +173,4 @@ reversed_vectorcall(PyObject *type, PyObject *const *args, exit: return return_value; } -/*[clinic end generated code: output=d0c066334eeb3b17 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=b97e8b89ca97ba64 input=a9049054013a1b77]*/ diff --git a/Objects/clinic/tupleobject.c.h b/Objects/clinic/tupleobject.c.h index 5e136b2d1cdfdf3..9d6a3edf60bb24b 100644 --- a/Objects/clinic/tupleobject.c.h +++ b/Objects/clinic/tupleobject.c.h @@ -119,7 +119,7 @@ tuple_vectorcall(PyObject *type, PyObject *const *args, Py_ssize_t nargs = PyVectorcall_NARGS(nargsf); PyObject *iterable = NULL; - assert(Py_Is(_PyType_CAST(type), &PyTuple_Type)); + assert(_PyType_CAST(type)->tp_new == tuple_new); /* Make sure the type object is immutable: the generated * vectorcall doesn't deal e.g. with users reassigning __init__. */ assert(PyType_HasFeature(_PyType_CAST(type), Py_TPFLAGS_IMMUTABLETYPE)); @@ -156,4 +156,4 @@ tuple___getnewargs__(PyObject *self, PyObject *Py_UNUSED(ignored)) { return tuple___getnewargs___impl((PyTupleObject *)self); } -/*[clinic end generated code: output=69cab12f1ecb03e9 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=1bc2bfc233992933 input=a9049054013a1b77]*/ diff --git a/Tools/c-analyzer/cpython/globals-to-fix.tsv b/Tools/c-analyzer/cpython/globals-to-fix.tsv index 67ced170243e4a2..5eae9ea9925367a 100644 --- a/Tools/c-analyzer/cpython/globals-to-fix.tsv +++ b/Tools/c-analyzer/cpython/globals-to-fix.tsv @@ -348,17 +348,6 @@ Modules/_testcapi/vectorcall.c - MethodDescriptorBase_Type - Modules/_testcapi/vectorcall.c - MethodDescriptorDerived_Type - Modules/_testcapi/vectorcall.c - MethodDescriptorNopGet_Type - Modules/_testcapi/vectorcall.c - MethodDescriptor2_Type - -Modules/_testclinic.c - DeprStarInit - -Modules/_testclinic.c - DeprStarInitNoInline - -Modules/_testclinic.c - DeprStarNew - -Modules/_testclinic.c - DeprKwdInit - -Modules/_testclinic.c - DeprKwdInitNoInline - -Modules/_testclinic.c - DeprKwdNew - -Modules/_testclinic.c - TestClass - -Modules/_testclinic.c - VcInit_Type - -Modules/_testclinic.c - VcKwOnly_Type - -Modules/_testclinic.c - VcNew_Type - -Modules/_testclinic.c - VcNewBase_Type - ################################## diff --git a/Tools/clinic/libclinic/dsl_parser.py b/Tools/clinic/libclinic/dsl_parser.py index a798fac4f3fd096..ba3345dadecc97f 100644 --- a/Tools/clinic/libclinic/dsl_parser.py +++ b/Tools/clinic/libclinic/dsl_parser.py @@ -639,11 +639,6 @@ def normalize_function_kind(self, fullname: str) -> None: if not self.kind.new_or_init: fail("@vectorcall can only be used with __init__ and __new__ " "methods currently") - # Guaranteed by the __new__ / __init__ checks above. - assert cls is not None - if not cls.type_object: - fail(f"@vectorcall requires the type object of {cls.name!r}, " - f"which was declared without one") def resolve_return_converter( self, full_name: str, forced_converter: str diff --git a/Tools/clinic/libclinic/parse_args.py b/Tools/clinic/libclinic/parse_args.py index 4caab253fe32ccf..a73e8a3d1fe0608 100644 --- a/Tools/clinic/libclinic/parse_args.py +++ b/Tools/clinic/libclinic/parse_args.py @@ -6,7 +6,7 @@ from libclinic.function import ( Function, Parameter, ParamTuple, count_required, group_to_variable_name, permute_optional_groups, - GETTER, SETTER, SETTER_AND_DELETER, METHOD_INIT, + GETTER, SETTER, SETTER_AND_DELETER, METHOD_INIT, METHOD_NEW, ACCESSORS, SETTERS) from libclinic.converter import CConverter from libclinic.converters import ( @@ -1537,19 +1537,22 @@ def create_template_dict(self) -> dict[str, str]: return d2 def _vectorcall_type_check(self) -> list[str]: - """Assert `type` is the one type this vectorcall was generated for. + """Check dispatch function hasn't changed. - The generated code is only correct for that type: __init__ calls - tp_new with no arguments, then the impl. tp_vectorcall is not - inherited, so subclasses never reach it; the assert catches C code - installing the function on a second type. + The generated code is only correct for a type whose tp_new / tp_init is + the parser this vectorcall shadows. """ func = self.func - # The DSL parser rejects @vectorcall without a class and type object. - assert func.cls is not None - assert func.cls.type_object + if func.kind is METHOD_INIT: + check = f"_PyType_CAST(type)->tp_init == {func.c_basename}" + elif func.kind is METHOD_NEW: + check = f"_PyType_CAST(type)->tp_new == {func.c_basename}" + else: + raise AssertionError( + f"Unhandled function kind for vectorcall: {func.kind!r}" + ) return [libclinic.normalize_snippet(f""" - assert(Py_Is(_PyType_CAST(type), {func.cls.type_object})); + assert({check}); /* Make sure the type object is immutable: the generated * vectorcall doesn't deal e.g. with users reassigning __init__. */ assert(PyType_HasFeature(_PyType_CAST(type), Py_TPFLAGS_IMMUTABLETYPE)); @@ -1689,6 +1692,23 @@ def parse_vectorcall_pos_or_kw(self) -> None: parser_code.extend(self._vectorcall_positional(arity_checked=True)) self.vectorcall_body(*parser_code) + def parse_vectorcall_no_args(self) -> None: + """No keyword or positional arguments.""" + parser_code = self._vectorcall_type_check() + self.codegen.add_include('pycore_modsupport.h', + '_PyArg_NoKwnames()') + parser_code.append(libclinic.normalize_snippet(""" + if (nargs) {{ + PyErr_SetString(PyExc_TypeError, + "{name}() takes no positional arguments"); + goto exit; + }} + if (!_PyArg_NoKwnames("{name}", kwnames)) {{ + goto exit; + }} + """, indent=4)) + self.vectorcall_body(*parser_code) + def parse_vectorcall(self) -> None: """Generate the vectorcall entry point for __new__ / __init__. @@ -1696,11 +1716,11 @@ def parse_vectorcall(self) -> None: """ # Branches ordered to mirror parse_args(). The DSL parser rejects # @vectorcall with optional groups, and METH_O never applies to - # __new__/__init__. They always have arguments. + # __new__/__init__. assert not self.has_option_groups() assert not self.use_meth_o() if not self.parameters and not self.varpos and not self.var_keyword: - raise NotImplementedError("No argument vectorcall") + self.parse_vectorcall_no_args() elif self.var_keyword is not None: self.parse_vectorcall_kw_required() elif self.pos_only == len(self.parameters): From b40e98adaeda7befb68cc96704b15a6e335eea10 Mon Sep 17 00:00:00 2001 From: Cody Maloney Date: Sat, 26 Sep 2026 00:23:19 -0700 Subject: [PATCH 2/2] Move one type back to static so both static and heap are tested --- Modules/_testclinic.c | 30 ++++++++++----------- Tools/c-analyzer/cpython/globals-to-fix.tsv | 1 + 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/Modules/_testclinic.c b/Modules/_testclinic.c index 2813e261a36b75e..bdace93cb843ece 100644 --- a/Modules/_testclinic.c +++ b/Modules/_testclinic.c @@ -2529,10 +2529,12 @@ output pop /* @vectorcall test types. Multiple types as tp_vectorcall is a single slot. */ -/* VcNew: __new__ with one optional positional-or-keyword arg */ +/* VcNew: __new__ with one optional positional-or-keyword arg. A static + * type, so the generated vectorcall is exercised on both static and heap + * types. */ /*[clinic input] -class _testclinic.VcNew "PyObject *" "" +class _testclinic.VcNew "PyObject *" "&VcNew_Type" @classmethod @vectorcall _testclinic.VcNew.__new__ as vc_plain_new @@ -2541,22 +2543,18 @@ _testclinic.VcNew.__new__ as vc_plain_new static PyObject * vc_plain_new_impl(PyTypeObject *type, PyObject *a) -/*[clinic end generated code: output=55b273e9797a3013 input=459ce1ed0393de54]*/ +/*[clinic end generated code: output=55b273e9797a3013 input=e15d88606280badc]*/ { return type->tp_alloc(type, 0); } -static PyType_Slot VcNew_slots[] = { - {Py_tp_new, vc_plain_new}, - {Py_tp_vectorcall, vc_plain_vectorcall}, - {0, NULL}, -}; - -static PyType_Spec VcNew_spec = { - .name = "_testclinic.VcNew", - .basicsize = sizeof(PyObject), - .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE, - .slots = VcNew_slots, +static PyTypeObject VcNew_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "_testclinic.VcNew", + .tp_basicsize = sizeof(PyObject), + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE, + .tp_new = vc_plain_new, + .tp_vectorcall = vc_plain_vectorcall, }; @@ -2901,7 +2899,6 @@ _testclinic_exec(PyObject *module) &DeprKwdNew_spec, &DeprKwdInit_spec, &DeprKwdInitNoInline_spec, - &VcNew_spec, &VcInit_spec, &VcNewBase_spec, &VcKwOnly_spec, @@ -2911,6 +2908,9 @@ _testclinic_exec(PyObject *module) return -1; } } + if (PyModule_AddType(module, &VcNew_Type) < 0) { + return -1; + } return 0; } diff --git a/Tools/c-analyzer/cpython/globals-to-fix.tsv b/Tools/c-analyzer/cpython/globals-to-fix.tsv index 5eae9ea9925367a..1aac251538ed1d8 100644 --- a/Tools/c-analyzer/cpython/globals-to-fix.tsv +++ b/Tools/c-analyzer/cpython/globals-to-fix.tsv @@ -348,6 +348,7 @@ Modules/_testcapi/vectorcall.c - MethodDescriptorBase_Type - Modules/_testcapi/vectorcall.c - MethodDescriptorDerived_Type - Modules/_testcapi/vectorcall.c - MethodDescriptorNopGet_Type - Modules/_testcapi/vectorcall.c - MethodDescriptor2_Type - +Modules/_testclinic.c - VcNew_Type - ##################################