Skip to content

Commit 3fc4d1f

Browse files
cmaloneyclaude
andauthored
gh-87613: Allow Argument Clinic vectorcall on heap types (#160115)
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 only 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 <noreply@anthropic.com>
1 parent e6190ac commit 3fc4d1f

15 files changed

Lines changed: 117 additions & 52 deletions

File tree

‎Include/internal/pycore_modsupport.h‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ PyAPI_FUNC(int) _PyArg_NoPositional(const char *funcname, PyObject *args);
2020
#define _PyArg_NoPositional(funcname, args) \
2121
((args) == NULL || _PyArg_NoPositional((funcname), (args)))
2222

23+
// Export for Argument Clinic generated code in shared extensions
24+
PyAPI_FUNC(int) _PyArg_NoPositionalStack(const char *funcname,
25+
Py_ssize_t nargs);
26+
#define _PyArg_NoPositionalStack(funcname, nargs) \
27+
((nargs) == 0 || _PyArg_NoPositionalStack((funcname), (nargs)))
28+
2329
// Export for '_asyncio' shared extension
2430
PyAPI_FUNC(int) _PyArg_NoKeywords(const char *funcname, PyObject *kwargs);
2531
#define _PyArg_NoKeywords(funcname, kwargs) \

‎Lib/test/clinic.test.c‎

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4946,6 +4946,7 @@ Test_cls_with_param_impl(TestObj *self, PyTypeObject *cls, int a)
49464946

49474947

49484948
/*[clinic input]
4949+
@vectorcall
49494950
Test.__init__
49504951
Empty init method.
49514952
[clinic start generated code]*/
@@ -4981,9 +4982,42 @@ Test___init__(PyObject *self, PyObject *args, PyObject *kwargs)
49814982
return return_value;
49824983
}
49834984

4985+
static PyObject *
4986+
Test_vectorcall(PyObject *type, PyObject *const *args,
4987+
size_t nargsf, PyObject *kwnames)
4988+
{
4989+
PyObject *return_value = NULL;
4990+
Py_ssize_t nargs = PyVectorcall_NARGS(nargsf);
4991+
PyObject *self;
4992+
int _result;
4993+
4994+
assert(_PyType_CAST(type)->tp_init == Test___init__);
4995+
/* Make sure the type object is immutable: the generated
4996+
* vectorcall doesn't deal e.g. with users reassigning __init__. */
4997+
assert(PyType_HasFeature(_PyType_CAST(type), Py_TPFLAGS_IMMUTABLETYPE));
4998+
if (!_PyArg_NoPositionalStack("Test", nargs) ||
4999+
!_PyArg_NoKwnames("Test", kwnames)) {
5000+
goto exit;
5001+
}
5002+
self = _PyType_CAST(type)->tp_new(_PyType_CAST(type),
5003+
(PyObject *)&_Py_SINGLETON(tuple_empty), NULL);
5004+
if (self == NULL) {
5005+
goto exit;
5006+
}
5007+
_result = Test___init___impl((TestObj *)self);
5008+
if (_result != 0) {
5009+
Py_DECREF(self);
5010+
goto exit;
5011+
}
5012+
return_value = self;
5013+
5014+
exit:
5015+
return return_value;
5016+
}
5017+
49845018
static int
49855019
Test___init___impl(TestObj *self)
4986-
/*[clinic end generated code: output=f6a35c85bc5b408f input=4ea79fee54d0c3ff]*/
5020+
/*[clinic end generated code: output=ce04b9d7beacd894 input=645fe693f6c6b9b6]*/
49875021

49885022

49895023
/*[clinic input]

‎Lib/test/test_clinic.py‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3500,8 +3500,6 @@ def test_vectorcall_on_init(self):
35003500
class Foo "FooObject *" "Foo_Type"
35013501
@vectorcall
35023502
Foo.__init__
3503-
iterable: object = NULL
3504-
/
35053503
"""
35063504
func = self.parse_function(block, signatures_in_block=3,
35073505
function_index=2)
@@ -3532,14 +3530,16 @@ class Foo "FooObject *" "Foo_Type"
35323530
self.expect_failure(block, err, lineno=2)
35333531

35343532
def test_vectorcall_without_type_object(self):
3535-
err = "@vectorcall requires the type object of 'Foo'"
3533+
# Heap types have no C pointer to name, so the type object is optional.
35363534
block = """
35373535
module m
35383536
class Foo "FooObject *" ""
35393537
@vectorcall
35403538
Foo.__init__
35413539
"""
3542-
self.expect_failure(block, err, lineno=3)
3540+
func = self.parse_function(block, signatures_in_block=3,
3541+
function_index=2)
3542+
self.assertTrue(func.vectorcall)
35433543

35443544
def test_vectorcall_unsupported_converter(self):
35453545
# str(encoding=...) has no parse_arg() implementation.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Extend Argument Clinic :ref:`vectorcall` to support :ref:`heap-types` with
2+
zero argument construction.

‎Modules/_testclinic.c‎

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ custom_converter(PyObject *obj, custom_t *val)
2424
/* Forward declarations for vectorcall types, needed because
2525
* clinic/_testclinic.c.h is included before the type definitions. */
2626
static PyTypeObject VcNew_Type;
27-
static PyTypeObject VcInit_Type;
2827
static PyTypeObject VcNewBase_Type;
2928
static PyTypeObject VcKwOnly_Type;
3029
#include "clinic/_testclinic.c.h"
@@ -2539,7 +2538,7 @@ static PyTypeObject VcNew_Type = {
25392538
* in both the helper body and the vectorcall fast-path inner block. */
25402539

25412540
/*[clinic input]
2542-
class _testclinic.VcInit "PyObject *" "&VcInit_Type"
2541+
class _testclinic.VcInit "PyObject *" ""
25432542
@vectorcall
25442543
@critical_section
25452544
_testclinic.VcInit.__init__ as vc_posorkw_init
@@ -2550,19 +2549,24 @@ _testclinic.VcInit.__init__ as vc_posorkw_init
25502549

25512550
static int
25522551
vc_posorkw_init_impl(PyObject *self, PyObject *a, PyObject *b)
2553-
/*[clinic end generated code: output=6018424ba9fb0744 input=7a4513f78dd42b57]*/
2552+
/*[clinic end generated code: output=6018424ba9fb0744 input=f0c7a84428d5b33d]*/
25542553
{
25552554
return 0;
25562555
}
25572556

2558-
static PyTypeObject VcInit_Type = {
2559-
PyVarObject_HEAD_INIT(NULL, 0)
2560-
.tp_name = "_testclinic.VcInit",
2561-
.tp_basicsize = sizeof(PyObject),
2562-
.tp_flags = Py_TPFLAGS_DEFAULT,
2563-
.tp_new = PyType_GenericNew,
2564-
.tp_init = vc_posorkw_init,
2565-
.tp_vectorcall = vc_posorkw_vectorcall,
2557+
/* Heap type: the generated vectorcall can't name the type object. */
2558+
static PyType_Slot VcInit_slots[] = {
2559+
{Py_tp_new, PyType_GenericNew},
2560+
{Py_tp_init, vc_posorkw_init},
2561+
{Py_tp_vectorcall, vc_posorkw_vectorcall},
2562+
{0, NULL},
2563+
};
2564+
2565+
static PyType_Spec VcInit_spec = {
2566+
.name = "_testclinic.VcInit",
2567+
.basicsize = sizeof(PyObject),
2568+
.flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE,
2569+
.slots = VcInit_slots,
25662570
};
25672571

25682572

@@ -2880,7 +2884,8 @@ PyInit__testclinic(void)
28802884
if (PyModule_AddType(m, &VcNew_Type) < 0) {
28812885
goto error;
28822886
}
2883-
if (PyModule_AddType(m, &VcInit_Type) < 0) {
2887+
if (PyModule_Add(m, "VcInit",
2888+
PyType_FromModuleAndSpec(m, &VcInit_spec, NULL)) < 0) {
28842889
goto error;
28852890
}
28862891
if (PyModule_AddType(m, &VcNewBase_Type) < 0) {

‎Modules/clinic/_testclinic.c.h‎

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Objects/clinic/bytearrayobject.c.h‎

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Objects/clinic/bytesobject.c.h‎

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Objects/clinic/enumobject.c.h‎

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Objects/clinic/floatobject.c.h‎

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)