diff --git a/Lib/test/test_capi.py b/Lib/test/test_capi.py index fabc821e5c3..88bda057ed6 100644 --- a/Lib/test/test_capi.py +++ b/Lib/test/test_capi.py @@ -515,9 +515,10 @@ class TestPEP590(unittest.TestCase): def test_vectorcall(self): # Test a bunch of different ways to call objects: - # 1. normal call - # 2. vectorcall using _PyObject_Vectorcall() - # 3. vectorcall using PyVectorcall_Call() + # 1. vectorcall using PyVectorcall_Call() + # (only for objects that support vectorcall directly) + # 2. normal call + # 3. vectorcall using _PyObject_Vectorcall() # 4. call as bound method # 5. call using functools.partial @@ -541,6 +542,27 @@ class TestPEP590(unittest.TestCase): kwnames = tuple(kwargs) return pyobject_vectorcall(func, args, kwnames) + for (func, args, kwargs, expected) in calls: + with self.subTest(str(func)): + if not kwargs: + self.assertEqual(expected, pyvectorcall_call(func, args)) + self.assertEqual(expected, pyvectorcall_call(func, args, kwargs)) + + # Add derived classes (which do not support vectorcall directly, + # but do support all other ways of calling). + + class MethodDescriptorHeap(_testcapi.MethodDescriptorBase): + pass + + class MethodDescriptorOverridden(_testcapi.MethodDescriptorBase): + def __call__(self, n): + return 'new' + + calls += [ + (MethodDescriptorHeap(), (0,), {}, True), + (MethodDescriptorOverridden(), (0,), {}, 'new'), + ] + for (func, args, kwargs, expected) in calls: with self.subTest(str(func)): args1 = args[1:] @@ -549,12 +571,10 @@ class TestPEP590(unittest.TestCase): if not kwargs: self.assertEqual(expected, func(*args)) self.assertEqual(expected, pyobject_vectorcall(func, args, None)) - self.assertEqual(expected, pyvectorcall_call(func, args)) self.assertEqual(expected, meth(*args1)) self.assertEqual(expected, wrapped(*args)) self.assertEqual(expected, func(*args, **kwargs)) self.assertEqual(expected, vectorcall(func, args, kwargs)) - self.assertEqual(expected, pyvectorcall_call(func, args, kwargs)) self.assertEqual(expected, meth(*args1, **kwargs)) self.assertEqual(expected, wrapped(*args, **kwargs)) diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index bf20e81a4ce..eed34c9802c 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -5854,7 +5854,7 @@ MethodDescriptor_vectorcall(PyObject *callable, PyObject *const *args, static PyObject * MethodDescriptor_new(PyTypeObject* type, PyObject* args, PyObject *kw) { - MethodDescriptorObject *op = PyObject_New(MethodDescriptorObject, type); + MethodDescriptorObject *op = type->tp_alloc(type, 0); op->vectorcall = MethodDescriptor_vectorcall; return (PyObject *)op; } diff --git a/Objects/call.c b/Objects/call.c index c0d14567e43..578e1b3ab61 100644 --- a/Objects/call.c +++ b/Objects/call.c @@ -173,12 +173,22 @@ _PyObject_MakeTpCall(PyObject *callable, PyObject *const *args, Py_ssize_t nargs PyObject * PyVectorcall_Call(PyObject *callable, PyObject *tuple, PyObject *kwargs) { - vectorcallfunc func = _PyVectorcall_Function(callable); + /* get vectorcallfunc as in _PyVectorcall_Function, but without + * the _Py_TPFLAGS_HAVE_VECTORCALL check */ + Py_ssize_t offset = Py_TYPE(callable)->tp_vectorcall_offset; + if ((offset <= 0) || (!Py_TYPE(callable)->tp_call)) { + PyErr_Format(PyExc_TypeError, "'%.200s' object does not support vectorcall", + Py_TYPE(callable)->tp_name); + return NULL; + } + vectorcallfunc func = *(vectorcallfunc *)(((char *)callable) + offset); if (func == NULL) { PyErr_Format(PyExc_TypeError, "'%.200s' object does not support vectorcall", Py_TYPE(callable)->tp_name); return NULL; } + + /* Convert arguments & call */ PyObject *const *args; Py_ssize_t nargs = PyTuple_GET_SIZE(tuple); PyObject *kwnames; diff --git a/Objects/typeobject.c b/Objects/typeobject.c index b6d925c1442..76e06aa31d6 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -5145,17 +5145,21 @@ inherit_slots(PyTypeObject *type, PyTypeObject *base) } COPYSLOT(tp_repr); /* tp_hash see tp_richcompare */ - COPYSLOT(tp_call); - /* Inherit tp_vectorcall_offset and _Py_TPFLAGS_HAVE_VECTORCALL if tp_call - * was inherited, but only for extension types */ - if ((base->tp_flags & _Py_TPFLAGS_HAVE_VECTORCALL) && - !(type->tp_flags & _Py_TPFLAGS_HAVE_VECTORCALL) && - !(type->tp_flags & Py_TPFLAGS_HEAPTYPE) && - base->tp_call && - type->tp_call == base->tp_call) { - type->tp_vectorcall_offset = base->tp_vectorcall_offset; - type->tp_flags |= _Py_TPFLAGS_HAVE_VECTORCALL; + /* Inherit tp_vectorcall_offset only if tp_call is not overridden */ + if (!type->tp_call) { + COPYSLOT(tp_vectorcall_offset); + } + /* Inherit_Py_TPFLAGS_HAVE_VECTORCALL for non-heap types + * if tp_call is not overridden */ + if (!type->tp_call && + (base->tp_flags & _Py_TPFLAGS_HAVE_VECTORCALL) && + !(type->tp_flags & _Py_TPFLAGS_HAVE_VECTORCALL) && + !(type->tp_flags & Py_TPFLAGS_HEAPTYPE)) + { + type->tp_flags |= _Py_TPFLAGS_HAVE_VECTORCALL; + } + COPYSLOT(tp_call); } COPYSLOT(tp_str); {