Skip to content

Commit 735e8af

Browse files
jdemeyerencukou
authored andcommitted
bpo-36974: inherit the vectorcall protocol (GH-13498)
1 parent 0f39c2b commit 735e8af

File tree

3 files changed

+104
-2
lines changed

3 files changed

+104
-2
lines changed

Lib/test/test_capi.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
# Were we compiled --with-pydebug or with #define Py_DEBUG?
2828
Py_DEBUG = hasattr(sys, 'gettotalrefcount')
2929

30+
Py_TPFLAGS_HAVE_VECTORCALL = 1 << 11
3031
Py_TPFLAGS_METHOD_DESCRIPTOR = 1 << 17
3132

3233

@@ -484,6 +485,27 @@ class MethodDescriptorHeap(_testcapi.MethodDescriptorBase):
484485
pass
485486
self.assertFalse(MethodDescriptorHeap.__flags__ & Py_TPFLAGS_METHOD_DESCRIPTOR)
486487

488+
def test_vectorcall_flag(self):
489+
self.assertTrue(_testcapi.MethodDescriptorBase.__flags__ & Py_TPFLAGS_HAVE_VECTORCALL)
490+
self.assertTrue(_testcapi.MethodDescriptorDerived.__flags__ & Py_TPFLAGS_HAVE_VECTORCALL)
491+
self.assertFalse(_testcapi.MethodDescriptorNopGet.__flags__ & Py_TPFLAGS_HAVE_VECTORCALL)
492+
self.assertTrue(_testcapi.MethodDescriptor2.__flags__ & Py_TPFLAGS_HAVE_VECTORCALL)
493+
494+
# Heap type should not inherit Py_TPFLAGS_HAVE_VECTORCALL
495+
class MethodDescriptorHeap(_testcapi.MethodDescriptorBase):
496+
pass
497+
self.assertFalse(MethodDescriptorHeap.__flags__ & Py_TPFLAGS_HAVE_VECTORCALL)
498+
499+
def test_vectorcall_override(self):
500+
# Check that tp_call can correctly override vectorcall.
501+
# MethodDescriptorNopGet implements tp_call but it inherits from
502+
# MethodDescriptorBase, which implements vectorcall. Since
503+
# MethodDescriptorNopGet returns the args tuple when called, we check
504+
# additionally that no new tuple is created for this call.
505+
args = tuple(range(5))
506+
f = _testcapi.MethodDescriptorNopGet()
507+
self.assertIs(f(*args), args)
508+
487509
def test_vectorcall(self):
488510
# Test a bunch of different ways to call objects:
489511
# 1. normal call
@@ -498,7 +520,10 @@ def test_vectorcall(self):
498520
([].append, (0,), {}, None),
499521
(sum, ([36],), {"start":6}, 42),
500522
(testfunction, (42,), {}, 42),
501-
(testfunction_kw, (42,), {"kw":None}, 42)]
523+
(testfunction_kw, (42,), {"kw":None}, 42),
524+
(_testcapi.MethodDescriptorBase(), (0,), {}, True),
525+
(_testcapi.MethodDescriptorDerived(), (0,), {}, True),
526+
(_testcapi.MethodDescriptor2(), (0,), {}, False)]
502527

503528
from _testcapi import pyobject_vectorcall, pyvectorcall_call
504529
from types import MethodType

Modules/_testcapimodule.c

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5814,6 +5814,29 @@ static PyTypeObject Generic_Type = {
58145814

58155815
/* Test PEP 590 */
58165816

5817+
typedef struct {
5818+
PyObject_HEAD
5819+
vectorcallfunc vectorcall;
5820+
} MethodDescriptorObject;
5821+
5822+
static PyObject *
5823+
MethodDescriptor_vectorcall(PyObject *callable, PyObject *const *args,
5824+
size_t nargsf, PyObject *kwnames)
5825+
{
5826+
/* True if using the vectorcall function in MethodDescriptorObject
5827+
* but False for MethodDescriptor2Object */
5828+
MethodDescriptorObject *md = (MethodDescriptorObject *)callable;
5829+
return PyBool_FromLong(md->vectorcall != NULL);
5830+
}
5831+
5832+
static PyObject *
5833+
MethodDescriptor_new(PyTypeObject* type, PyObject* args, PyObject *kw)
5834+
{
5835+
MethodDescriptorObject *op = PyObject_New(MethodDescriptorObject, type);
5836+
op->vectorcall = MethodDescriptor_vectorcall;
5837+
return (PyObject *)op;
5838+
}
5839+
58175840
static PyObject *
58185841
func_descr_get(PyObject *func, PyObject *obj, PyObject *type)
58195842
{
@@ -5831,10 +5854,22 @@ nop_descr_get(PyObject *func, PyObject *obj, PyObject *type)
58315854
return func;
58325855
}
58335856

5857+
static PyObject *
5858+
call_return_args(PyObject *self, PyObject *args, PyObject *kwargs)
5859+
{
5860+
Py_INCREF(args);
5861+
return args;
5862+
}
5863+
58345864
static PyTypeObject MethodDescriptorBase_Type = {
58355865
PyVarObject_HEAD_INIT(NULL, 0)
58365866
"MethodDescriptorBase",
5837-
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_METHOD_DESCRIPTOR,
5867+
sizeof(MethodDescriptorObject),
5868+
.tp_new = MethodDescriptor_new,
5869+
.tp_call = PyVectorcall_Call,
5870+
.tp_vectorcall_offset = offsetof(MethodDescriptorObject, vectorcall),
5871+
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE |
5872+
Py_TPFLAGS_METHOD_DESCRIPTOR | _Py_TPFLAGS_HAVE_VECTORCALL,
58385873
.tp_descr_get = func_descr_get,
58395874
};
58405875

@@ -5848,9 +5883,34 @@ static PyTypeObject MethodDescriptorNopGet_Type = {
58485883
PyVarObject_HEAD_INIT(NULL, 0)
58495884
"MethodDescriptorNopGet",
58505885
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
5886+
.tp_call = call_return_args,
58515887
.tp_descr_get = nop_descr_get,
58525888
};
58535889

5890+
typedef struct {
5891+
MethodDescriptorObject base;
5892+
vectorcallfunc vectorcall;
5893+
} MethodDescriptor2Object;
5894+
5895+
static PyObject *
5896+
MethodDescriptor2_new(PyTypeObject* type, PyObject* args, PyObject *kw)
5897+
{
5898+
MethodDescriptor2Object *op = PyObject_New(MethodDescriptor2Object, type);
5899+
op->base.vectorcall = NULL;
5900+
op->vectorcall = MethodDescriptor_vectorcall;
5901+
return (PyObject *)op;
5902+
}
5903+
5904+
static PyTypeObject MethodDescriptor2_Type = {
5905+
PyVarObject_HEAD_INIT(NULL, 0)
5906+
"MethodDescriptor2",
5907+
sizeof(MethodDescriptor2Object),
5908+
.tp_new = MethodDescriptor2_new,
5909+
.tp_call = PyVectorcall_Call,
5910+
.tp_vectorcall_offset = offsetof(MethodDescriptor2Object, vectorcall),
5911+
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | _Py_TPFLAGS_HAVE_VECTORCALL,
5912+
};
5913+
58545914

58555915
static struct PyModuleDef _testcapimodule = {
58565916
PyModuleDef_HEAD_INIT,
@@ -5916,6 +5976,12 @@ PyInit__testcapi(void)
59165976
Py_INCREF(&MethodDescriptorNopGet_Type);
59175977
PyModule_AddObject(m, "MethodDescriptorNopGet", (PyObject *)&MethodDescriptorNopGet_Type);
59185978

5979+
MethodDescriptor2_Type.tp_base = &MethodDescriptorBase_Type;
5980+
if (PyType_Ready(&MethodDescriptor2_Type) < 0)
5981+
return NULL;
5982+
Py_INCREF(&MethodDescriptor2_Type);
5983+
PyModule_AddObject(m, "MethodDescriptor2", (PyObject *)&MethodDescriptor2_Type);
5984+
59195985
if (PyType_Ready(&GenericAlias_Type) < 0)
59205986
return NULL;
59215987
Py_INCREF(&GenericAlias_Type);

Objects/typeobject.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5147,6 +5147,17 @@ inherit_slots(PyTypeObject *type, PyTypeObject *base)
51475147
COPYSLOT(tp_repr);
51485148
/* tp_hash see tp_richcompare */
51495149
COPYSLOT(tp_call);
5150+
/* Inherit tp_vectorcall_offset and _Py_TPFLAGS_HAVE_VECTORCALL if tp_call
5151+
* was inherited, but only for extension types */
5152+
if ((base->tp_flags & _Py_TPFLAGS_HAVE_VECTORCALL) &&
5153+
!(type->tp_flags & _Py_TPFLAGS_HAVE_VECTORCALL) &&
5154+
!(type->tp_flags & Py_TPFLAGS_HEAPTYPE) &&
5155+
base->tp_call &&
5156+
type->tp_call == base->tp_call)
5157+
{
5158+
type->tp_vectorcall_offset = base->tp_vectorcall_offset;
5159+
type->tp_flags |= _Py_TPFLAGS_HAVE_VECTORCALL;
5160+
}
51505161
COPYSLOT(tp_str);
51515162
{
51525163
/* Copy comparison-related slots only when

0 commit comments

Comments
 (0)