From 4cba9f4d9ed250c0f805468f715550b3928f0e5e Mon Sep 17 00:00:00 2001 From: Cody Maloney Date: Wed, 21 Oct 2026 10:59:53 -0700 Subject: [PATCH 1/3] gh-87613: Allow Argument Clinic @vectorcall on heap types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Heap types have no static type object so switch from `Py_Is` to an assert that the shadowed `tp_new` / `tp_init` function is the expected one to validate the vectorcall is onl„ used with the right type. Heap types can have zero-argument `__new__` and `__init__` so add support for that producing the same error message via a new helper `_PyArg_NoPositionalStack()`. Co-Authored-By: Claude Opus 5.5 --- Include/internal/pycore_modsupport.h | 6 +++ Lib/test/clinic.test.c | 36 ++++++++++++++++- Lib/test/test_clinic.py | 8 ++-- ...6-10-21-11-53-40.gh-issue-87613.tmKBlg.rst | 2 + Modules/_testclinic.c | 29 ++++++++------ Modules/clinic/_testclinic.c.h | 10 ++--- Objects/clinic/bytearrayobject.c.h | 4 +- Objects/clinic/bytesobject.c.h | 4 +- Objects/clinic/enumobject.c.h | 6 +-- Objects/clinic/floatobject.c.h | 4 +- Objects/clinic/tupleobject.c.h | 4 +- Python/getargs.c | 10 ++++- Tools/c-analyzer/cpython/globals-to-fix.tsv | 1 - Tools/clinic/libclinic/dsl_parser.py | 5 --- Tools/clinic/libclinic/parse_args.py | 40 +++++++++++++------ 15 files changed, 117 insertions(+), 52 deletions(-) create mode 100644 Misc/NEWS.d/next/Tools-Demos/2026-10-21-11-53-40.gh-issue-87613.tmKBlg.rst diff --git a/Include/internal/pycore_modsupport.h b/Include/internal/pycore_modsupport.h index a72a557059727a7..37b4a711808b535 100644 --- a/Include/internal/pycore_modsupport.h +++ b/Include/internal/pycore_modsupport.h @@ -20,6 +20,12 @@ PyAPI_FUNC(int) _PyArg_NoPositional(const char *funcname, PyObject *args); #define _PyArg_NoPositional(funcname, args) \ ((args) == NULL || _PyArg_NoPositional((funcname), (args))) +// Export for Argument Clinic generated code in shared extensions +PyAPI_FUNC(int) _PyArg_NoPositionalStack(const char *funcname, + Py_ssize_t nargs); +#define _PyArg_NoPositionalStack(funcname, nargs) \ + ((nargs) == 0 || _PyArg_NoPositionalStack((funcname), (nargs))) + // Export for '_asyncio' shared extension PyAPI_FUNC(int) _PyArg_NoKeywords(const char *funcname, PyObject *kwargs); #define _PyArg_NoKeywords(funcname, kwargs) \ diff --git a/Lib/test/clinic.test.c b/Lib/test/clinic.test.c index b96e5d024c9082b..86b8f81776dfc3e 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,42 @@ 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 (!_PyArg_NoPositionalStack("Test", nargs) || + !_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=ce04b9d7beacd894 input=645fe693f6c6b9b6]*/ /*[clinic input] diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index e3dd4a2be9e410a..dfff2445c2ec037 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -3498,8 +3498,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) @@ -3530,14 +3528,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/Misc/NEWS.d/next/Tools-Demos/2026-10-21-11-53-40.gh-issue-87613.tmKBlg.rst b/Misc/NEWS.d/next/Tools-Demos/2026-10-21-11-53-40.gh-issue-87613.tmKBlg.rst new file mode 100644 index 000000000000000..b52d7ea7fe5e8d1 --- /dev/null +++ b/Misc/NEWS.d/next/Tools-Demos/2026-10-21-11-53-40.gh-issue-87613.tmKBlg.rst @@ -0,0 +1,2 @@ +Extend Argument Clinic :ref:`vectorcall` to support :ref:`heap-types` with +zero argument construction. diff --git a/Modules/_testclinic.c b/Modules/_testclinic.c index 8b1a547ff297d11..b875647598c8c0a 100644 --- a/Modules/_testclinic.c +++ b/Modules/_testclinic.c @@ -24,7 +24,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" @@ -2539,7 +2538,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 +2549,24 @@ _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, +/* Heap type: the generated vectorcall can't name the type object. */ +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, }; @@ -2880,7 +2884,8 @@ PyInit__testclinic(void) if (PyModule_AddType(m, &VcNew_Type) < 0) { goto error; } - if (PyModule_AddType(m, &VcInit_Type) < 0) { + if (PyModule_Add(m, "VcInit", + PyType_FromModuleAndSpec(m, &VcInit_spec, NULL)) < 0) { goto error; } if (PyModule_AddType(m, &VcNewBase_Type) < 0) { diff --git a/Modules/clinic/_testclinic.c.h b/Modules/clinic/_testclinic.c.h index 515d5174bd32e7d..dcc60c7341cbc33 100644 --- a/Modules/clinic/_testclinic.c.h +++ b/Modules/clinic/_testclinic.c.h @@ -4990,7 +4990,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)); @@ -5089,7 +5089,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)); @@ -5208,7 +5208,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)); @@ -5301,7 +5301,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)); @@ -5309,4 +5309,4 @@ vc_kwonly_vectorcall(PyObject *type, PyObject *const *args, kwnames ? PyTuple_GET_SIZE(kwnames) : 0, NULL, kwnames); } -/*[clinic end generated code: output=17b9ff8218a250fe input=a9049054013a1b77]*/ +/*[clinic end generated code: output=b54b37db716469cd input=a9049054013a1b77]*/ diff --git a/Objects/clinic/bytearrayobject.c.h b/Objects/clinic/bytearrayobject.c.h index 83cfa00d59044e9..b4c55b55ca3e378 100644 --- a/Objects/clinic/bytearrayobject.c.h +++ b/Objects/clinic/bytearrayobject.c.h @@ -133,7 +133,7 @@ bytearray_vectorcall(PyObject *type, PyObject *const *args, const char *encoding = NULL; const char *errors = NULL; - assert(Py_Is(_PyType_CAST(type), &PyByteArray_Type)); + assert(_PyType_CAST(type)->tp_init == bytearray___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)); @@ -1962,4 +1962,4 @@ bytearray_sizeof(PyObject *self, PyObject *Py_UNUSED(ignored)) { return bytearray_sizeof_impl((PyByteArrayObject *)self); } -/*[clinic end generated code: output=a16aaa01f1fcf5a6 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=3a969e8e5a854d99 input=a9049054013a1b77]*/ diff --git a/Objects/clinic/bytesobject.c.h b/Objects/clinic/bytesobject.c.h index 2b2850b5933cf9b..c96cd94425c1d18 100644 --- a/Objects/clinic/bytesobject.c.h +++ b/Objects/clinic/bytesobject.c.h @@ -1456,7 +1456,7 @@ bytes_vectorcall(PyObject *type, PyObject *const *args, const char *encoding = NULL; const char *errors = NULL; - assert(Py_Is(_PyType_CAST(type), &PyBytes_Type)); + assert(_PyType_CAST(type)->tp_new == bytes_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)); @@ -1497,4 +1497,4 @@ bytes_vectorcall(PyObject *type, PyObject *const *args, exit: return return_value; } -/*[clinic end generated code: output=9ea978e8e97e33ae input=a9049054013a1b77]*/ +/*[clinic end generated code: output=0d49a97b9e2eef5b 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/floatobject.c.h b/Objects/clinic/floatobject.c.h index 0e4c0614b592d50..9c71663a1503ac4 100644 --- a/Objects/clinic/floatobject.c.h +++ b/Objects/clinic/floatobject.c.h @@ -249,7 +249,7 @@ float_vectorcall(PyObject *type, PyObject *const *args, Py_ssize_t nargs = PyVectorcall_NARGS(nargsf); PyObject *x = NULL; - assert(Py_Is(_PyType_CAST(type), &PyFloat_Type)); + assert(_PyType_CAST(type)->tp_new == float_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)); @@ -378,4 +378,4 @@ float___format__(PyObject *self, PyObject *arg) exit: return return_value; } -/*[clinic end generated code: output=95aaa6d7174f9e40 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=d1198594a76a4638 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/Python/getargs.c b/Python/getargs.c index c3e85b2089bb2a9..70bd54a23e939ce 100644 --- a/Python/getargs.c +++ b/Python/getargs.c @@ -2921,6 +2921,7 @@ _PyArg_UnpackStack(PyObject *const *args, Py_ssize_t nargs, const char *name, #undef _PyArg_NoKeywords #undef _PyArg_NoKwnames #undef _PyArg_NoPositional +#undef _PyArg_NoPositionalStack /* For type constructors that don't take keyword args * @@ -2955,8 +2956,15 @@ _PyArg_NoPositional(const char *funcname, PyObject *args) PyErr_BadInternalCall(); return 0; } - if (PyTuple_GET_SIZE(args) == 0) + return _PyArg_NoPositionalStack(funcname, PyTuple_GET_SIZE(args)); +} + +int +_PyArg_NoPositionalStack(const char *funcname, Py_ssize_t nargs) +{ + if (nargs == 0) { return 1; + } PyErr_Format(PyExc_TypeError, "%.200s() takes no positional arguments", funcname); diff --git a/Tools/c-analyzer/cpython/globals-to-fix.tsv b/Tools/c-analyzer/cpython/globals-to-fix.tsv index 251af27908cfc38..e8bb6b7661e12cf 100644 --- a/Tools/c-analyzer/cpython/globals-to-fix.tsv +++ b/Tools/c-analyzer/cpython/globals-to-fix.tsv @@ -351,7 +351,6 @@ 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 819851dcb78aaad..4586dace37e7cf5 100644 --- a/Tools/clinic/libclinic/dsl_parser.py +++ b/Tools/clinic/libclinic/dsl_parser.py @@ -655,11 +655,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 73414e897e26151..703fa4e94ad5bfa 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, SLOT, + GETTER, SETTER, SETTER_AND_DELETER, METHOD_INIT, METHOD_NEW, SLOT, ACCESSORS, SETTERS) from libclinic.converter import CConverter from libclinic.converters import ( @@ -1590,19 +1590,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)); @@ -1742,6 +1745,19 @@ 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_NoPositionalStack()') + parser_code.append(libclinic.normalize_snippet(""" + if (!_PyArg_NoPositionalStack("{name}", nargs) || + !_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__. @@ -1749,11 +1765,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 1ce0827b57e68cf39d0f397a272e41fe23c34cf8 Mon Sep 17 00:00:00 2001 From: Cody Maloney Date: Thu, 22 Oct 2026 09:53:27 -0700 Subject: [PATCH 2/3] gh-87613: Use Argument Clinic @vectorcall for decimal.Decimal Co-Authored-By: Claude Opus 5.5 --- Modules/_decimal/_decimal.c | 4 ++- Modules/_decimal/clinic/_decimal.c.h | 51 +++++++++++++++++++++++++--- 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index 5eaba2fdbdf2d6a..59c62cc390542af 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -3314,6 +3314,7 @@ PyDec_FromObject(PyObject *v, PyObject *context) /*[clinic input] @classmethod +@vectorcall _decimal.Decimal.__new__ as dec_new value: object(c_default="NULL") = "0" @@ -3329,7 +3330,7 @@ trap is active. static PyObject * dec_new_impl(PyTypeObject *type, PyObject *value, PyObject *context) -/*[clinic end generated code: output=35f48a40c65625ba input=5f8a0892d3fcef80]*/ +/*[clinic end generated code: output=35f48a40c65625ba input=dd1efa0c0ffe9416]*/ { decimal_state *state = get_module_state_by_def(type); CONTEXT_CHECK_VA(state, context); @@ -6190,6 +6191,7 @@ static PyType_Slot dec_slots[] = { {Py_tp_methods, dec_methods}, {Py_tp_getset, dec_getsets}, {Py_tp_new, dec_new}, + {Py_tp_vectorcall, dec_vectorcall}, // Number protocol {Py_nb_add, nm_mpd_qadd}, diff --git a/Modules/_decimal/clinic/_decimal.c.h b/Modules/_decimal/clinic/_decimal.c.h index bbf5613848f13e0..ed16aa420109e86 100644 --- a/Modules/_decimal/clinic/_decimal.c.h +++ b/Modules/_decimal/clinic/_decimal.c.h @@ -867,7 +867,8 @@ static PyObject * dec_new_impl(PyTypeObject *type, PyObject *value, PyObject *context); static PyObject * -dec_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) +dec_new_helper(PyTypeObject *type, PyObject *const *args, + Py_ssize_t nargs, Py_ssize_t nkw, PyObject *kwargs, PyObject *kwnames) { PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) @@ -899,12 +900,11 @@ dec_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) #undef KWTUPLE PyObject *argsbuf[2]; PyObject * const *fastargs; - Py_ssize_t nargs = PyTuple_GET_SIZE(args); - Py_ssize_t noptargs = nargs + (kwargs ? PyDict_GET_SIZE(kwargs) : 0) - 0; + Py_ssize_t noptargs = nargs + nkw - 0; PyObject *value = NULL; PyObject *context = Py_None; - fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser, + fastargs = _PyArg_UnpackKeywords(args, nargs, kwargs, kwnames, &_parser, /*minpos*/ 0, /*maxpos*/ 2, /*minkw*/ 0, /*varpos*/ 0, argsbuf); if (!fastargs) { goto exit; @@ -926,6 +926,47 @@ dec_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) return return_value; } +static PyObject * +dec_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) +{ + return dec_new_helper(type, _PyTuple_CAST(args)->ob_item, + PyTuple_GET_SIZE(args), + kwargs ? PyDict_GET_SIZE(kwargs) : 0, + kwargs, NULL); +} + +static PyObject * +dec_vectorcall(PyObject *type, PyObject *const *args, + size_t nargsf, PyObject *kwnames) +{ + PyObject *return_value = NULL; + Py_ssize_t nargs = PyVectorcall_NARGS(nargsf); + PyObject *value = NULL; + PyObject *context = Py_None; + + assert(_PyType_CAST(type)->tp_new == dec_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)); + if (kwnames != NULL || nargs > 2) { + return dec_new_helper(_PyType_CAST(type), args, nargs, + kwnames ? PyTuple_GET_SIZE(kwnames) : 0, + NULL, kwnames); + } + if (nargs < 1) { + goto skip_optional; + } + value = args[0]; + if (nargs < 2) { + goto skip_optional; + } + context = args[1]; +skip_optional: + return_value = dec_new_impl(_PyType_CAST(type), value, context); + + return return_value; +} + PyDoc_STRVAR(_decimal_Context_create_decimal__doc__, "create_decimal($self, num=\'0\', /)\n" "--\n" @@ -6283,4 +6324,4 @@ _decimal_Context_same_quantum(PyObject *context, PyTypeObject *cls, PyObject *co #ifndef _DECIMAL_CONTEXT_APPLY_METHODDEF #define _DECIMAL_CONTEXT_APPLY_METHODDEF #endif /* !defined(_DECIMAL_CONTEXT_APPLY_METHODDEF) */ -/*[clinic end generated code: output=783d0db339199a30 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=10892d647ee2a899 input=a9049054013a1b77]*/ From d6e1ba52a208bfae0a5bcd7d399125263505792b Mon Sep 17 00:00:00 2001 From: Cody Maloney Date: Thu, 22 Oct 2026 17:14:20 -0700 Subject: [PATCH 3/3] Drop Argument Clinic @vectorcall for decimal.Decimal Per review, change decimal in a separate PR. This reverts commit 1ce0827b57e68cf39d0f397a272e41fe23c34cf8. Co-Authored-By: Claude Opus 5.5 --- Modules/_decimal/_decimal.c | 4 +-- Modules/_decimal/clinic/_decimal.c.h | 51 +++------------------------- 2 files changed, 6 insertions(+), 49 deletions(-) diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index 59c62cc390542af..5eaba2fdbdf2d6a 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -3314,7 +3314,6 @@ PyDec_FromObject(PyObject *v, PyObject *context) /*[clinic input] @classmethod -@vectorcall _decimal.Decimal.__new__ as dec_new value: object(c_default="NULL") = "0" @@ -3330,7 +3329,7 @@ trap is active. static PyObject * dec_new_impl(PyTypeObject *type, PyObject *value, PyObject *context) -/*[clinic end generated code: output=35f48a40c65625ba input=dd1efa0c0ffe9416]*/ +/*[clinic end generated code: output=35f48a40c65625ba input=5f8a0892d3fcef80]*/ { decimal_state *state = get_module_state_by_def(type); CONTEXT_CHECK_VA(state, context); @@ -6191,7 +6190,6 @@ static PyType_Slot dec_slots[] = { {Py_tp_methods, dec_methods}, {Py_tp_getset, dec_getsets}, {Py_tp_new, dec_new}, - {Py_tp_vectorcall, dec_vectorcall}, // Number protocol {Py_nb_add, nm_mpd_qadd}, diff --git a/Modules/_decimal/clinic/_decimal.c.h b/Modules/_decimal/clinic/_decimal.c.h index ed16aa420109e86..bbf5613848f13e0 100644 --- a/Modules/_decimal/clinic/_decimal.c.h +++ b/Modules/_decimal/clinic/_decimal.c.h @@ -867,8 +867,7 @@ static PyObject * dec_new_impl(PyTypeObject *type, PyObject *value, PyObject *context); static PyObject * -dec_new_helper(PyTypeObject *type, PyObject *const *args, - Py_ssize_t nargs, Py_ssize_t nkw, PyObject *kwargs, PyObject *kwnames) +dec_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) { PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) @@ -900,11 +899,12 @@ dec_new_helper(PyTypeObject *type, PyObject *const *args, #undef KWTUPLE PyObject *argsbuf[2]; PyObject * const *fastargs; - Py_ssize_t noptargs = nargs + nkw - 0; + Py_ssize_t nargs = PyTuple_GET_SIZE(args); + Py_ssize_t noptargs = nargs + (kwargs ? PyDict_GET_SIZE(kwargs) : 0) - 0; PyObject *value = NULL; PyObject *context = Py_None; - fastargs = _PyArg_UnpackKeywords(args, nargs, kwargs, kwnames, &_parser, + fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser, /*minpos*/ 0, /*maxpos*/ 2, /*minkw*/ 0, /*varpos*/ 0, argsbuf); if (!fastargs) { goto exit; @@ -926,47 +926,6 @@ dec_new_helper(PyTypeObject *type, PyObject *const *args, return return_value; } -static PyObject * -dec_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) -{ - return dec_new_helper(type, _PyTuple_CAST(args)->ob_item, - PyTuple_GET_SIZE(args), - kwargs ? PyDict_GET_SIZE(kwargs) : 0, - kwargs, NULL); -} - -static PyObject * -dec_vectorcall(PyObject *type, PyObject *const *args, - size_t nargsf, PyObject *kwnames) -{ - PyObject *return_value = NULL; - Py_ssize_t nargs = PyVectorcall_NARGS(nargsf); - PyObject *value = NULL; - PyObject *context = Py_None; - - assert(_PyType_CAST(type)->tp_new == dec_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)); - if (kwnames != NULL || nargs > 2) { - return dec_new_helper(_PyType_CAST(type), args, nargs, - kwnames ? PyTuple_GET_SIZE(kwnames) : 0, - NULL, kwnames); - } - if (nargs < 1) { - goto skip_optional; - } - value = args[0]; - if (nargs < 2) { - goto skip_optional; - } - context = args[1]; -skip_optional: - return_value = dec_new_impl(_PyType_CAST(type), value, context); - - return return_value; -} - PyDoc_STRVAR(_decimal_Context_create_decimal__doc__, "create_decimal($self, num=\'0\', /)\n" "--\n" @@ -6324,4 +6283,4 @@ _decimal_Context_same_quantum(PyObject *context, PyTypeObject *cls, PyObject *co #ifndef _DECIMAL_CONTEXT_APPLY_METHODDEF #define _DECIMAL_CONTEXT_APPLY_METHODDEF #endif /* !defined(_DECIMAL_CONTEXT_APPLY_METHODDEF) */ -/*[clinic end generated code: output=10892d647ee2a899 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=783d0db339199a30 input=a9049054013a1b77]*/