mirror of
https://github.com/python/cpython.git
synced 2026-10-28 10:25:05 +08:00
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>
This commit is contained in:
1 parent
e6190acde6
commit
3fc4d1f9b3
15 files changed
+117
-52
No files matched your search
@@ -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) \
|
||||
|
||||
+35
-1
@@ -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 @@ exit:
|
||||
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]
|
||||
|
||||
@@ -3500,8 +3500,6 @@ class ClinicParserTest(TestCase):
|
||||
class Foo "FooObject *" "Foo_Type"
|
||||
@vectorcall
|
||||
Foo.__init__
|
||||
iterable: object = NULL
|
||||
/
|
||||
"""
|
||||
func = self.parse_function(block, signatures_in_block=3,
|
||||
function_index=2)
|
||||
@@ -3532,14 +3530,16 @@ class ClinicParserTest(TestCase):
|
||||
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.
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
Extend Argument Clinic :ref:`vectorcall` to support :ref:`heap-types` with
|
||||
zero argument construction.
|
||||
+17
-12
@@ -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) {
|
||||
|
||||
Generated
+5
-5
@@ -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]*/
|
||||
Generated
+2
-2
@@ -193,7 +193,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));
|
||||
@@ -2037,4 +2037,4 @@ bytearray_sizeof(PyObject *self, PyObject *Py_UNUSED(ignored))
|
||||
{
|
||||
return bytearray_sizeof_impl((PyByteArrayObject *)self);
|
||||
}
|
||||
/*[clinic end generated code: output=fbd9a012eb61b372 input=a9049054013a1b77]*/
|
||||
/*[clinic end generated code: output=b8e659fe4b2f770e input=a9049054013a1b77]*/
|
||||
Generated
+2
-2
@@ -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 @@ skip_optional:
|
||||
exit:
|
||||
return return_value;
|
||||
}
|
||||
/*[clinic end generated code: output=9ea978e8e97e33ae input=a9049054013a1b77]*/
|
||||
/*[clinic end generated code: output=0d49a97b9e2eef5b input=a9049054013a1b77]*/
|
||||
Generated
+3
-3
@@ -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]*/
|
||||
Generated
+2
-2
@@ -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]*/
|
||||
Generated
+2
-2
@@ -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]*/
|
||||
+9
-1
@@ -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);
|
||||
|
||||
@@ -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 -
|
||||
|
||||
|
@@ -647,11 +647,6 @@ class DSLParser:
|
||||
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
|
||||
|
||||
@@ -6,7 +6,7 @@ from libclinic import fail, warn, unspecified, Sentinels
|
||||
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 @@ class ParseArgsCodeGen:
|
||||
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 @@ class ParseArgsCodeGen:
|
||||
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 @@ class ParseArgsCodeGen:
|
||||
"""
|
||||
# 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):
|
||||
|
||||
Reference in new issue
Block a user