mirror of https://github.com/python/cpython
bpo-36974: Make tp_call=PyVectorcall_Call work for inherited types (GH-13699)
When inheriting a heap subclass from a vectorcall class that sets `.tp_call=PyVectorcall_Call` (as recommended in PEP 590), the subclass does not inherit `_Py_TPFLAGS_HAVE_VECTORCALL`, and thus `PyVectorcall_Call` does not work for it. This attempts to solve the issue by: * always inheriting `tp_vectorcall_offset` unless `tp_call` is overridden in the subclass * inheriting _Py_TPFLAGS_HAVE_VECTORCALL for static types, unless `tp_call` is overridden * making `PyVectorcall_Call` ignore `_Py_TPFLAGS_HAVE_VECTORCALL` This means it'll be ever more important to only call `PyVectorcall_Call` on classes that support vectorcall. In `PyVectorcall_Call`'s intended role as `tp_call` filler, that's not a problem.
This commit is contained in:
parent
e1179a5096
commit
fb9423fd0a
|
@ -515,9 +515,10 @@ class TestPEP590(unittest.TestCase):
|
||||||
|
|
||||||
def test_vectorcall(self):
|
def test_vectorcall(self):
|
||||||
# Test a bunch of different ways to call objects:
|
# Test a bunch of different ways to call objects:
|
||||||
# 1. normal call
|
# 1. vectorcall using PyVectorcall_Call()
|
||||||
# 2. vectorcall using _PyObject_Vectorcall()
|
# (only for objects that support vectorcall directly)
|
||||||
# 3. vectorcall using PyVectorcall_Call()
|
# 2. normal call
|
||||||
|
# 3. vectorcall using _PyObject_Vectorcall()
|
||||||
# 4. call as bound method
|
# 4. call as bound method
|
||||||
# 5. call using functools.partial
|
# 5. call using functools.partial
|
||||||
|
|
||||||
|
@ -541,6 +542,27 @@ class TestPEP590(unittest.TestCase):
|
||||||
kwnames = tuple(kwargs)
|
kwnames = tuple(kwargs)
|
||||||
return pyobject_vectorcall(func, args, kwnames)
|
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:
|
for (func, args, kwargs, expected) in calls:
|
||||||
with self.subTest(str(func)):
|
with self.subTest(str(func)):
|
||||||
args1 = args[1:]
|
args1 = args[1:]
|
||||||
|
@ -549,12 +571,10 @@ class TestPEP590(unittest.TestCase):
|
||||||
if not kwargs:
|
if not kwargs:
|
||||||
self.assertEqual(expected, func(*args))
|
self.assertEqual(expected, func(*args))
|
||||||
self.assertEqual(expected, pyobject_vectorcall(func, args, None))
|
self.assertEqual(expected, pyobject_vectorcall(func, args, None))
|
||||||
self.assertEqual(expected, pyvectorcall_call(func, args))
|
|
||||||
self.assertEqual(expected, meth(*args1))
|
self.assertEqual(expected, meth(*args1))
|
||||||
self.assertEqual(expected, wrapped(*args))
|
self.assertEqual(expected, wrapped(*args))
|
||||||
self.assertEqual(expected, func(*args, **kwargs))
|
self.assertEqual(expected, func(*args, **kwargs))
|
||||||
self.assertEqual(expected, vectorcall(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, meth(*args1, **kwargs))
|
||||||
self.assertEqual(expected, wrapped(*args, **kwargs))
|
self.assertEqual(expected, wrapped(*args, **kwargs))
|
||||||
|
|
||||||
|
|
|
@ -5854,7 +5854,7 @@ MethodDescriptor_vectorcall(PyObject *callable, PyObject *const *args,
|
||||||
static PyObject *
|
static PyObject *
|
||||||
MethodDescriptor_new(PyTypeObject* type, PyObject* args, PyObject *kw)
|
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;
|
op->vectorcall = MethodDescriptor_vectorcall;
|
||||||
return (PyObject *)op;
|
return (PyObject *)op;
|
||||||
}
|
}
|
||||||
|
|
|
@ -173,12 +173,22 @@ _PyObject_MakeTpCall(PyObject *callable, PyObject *const *args, Py_ssize_t nargs
|
||||||
PyObject *
|
PyObject *
|
||||||
PyVectorcall_Call(PyObject *callable, PyObject *tuple, PyObject *kwargs)
|
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) {
|
if (func == NULL) {
|
||||||
PyErr_Format(PyExc_TypeError, "'%.200s' object does not support vectorcall",
|
PyErr_Format(PyExc_TypeError, "'%.200s' object does not support vectorcall",
|
||||||
Py_TYPE(callable)->tp_name);
|
Py_TYPE(callable)->tp_name);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Convert arguments & call */
|
||||||
PyObject *const *args;
|
PyObject *const *args;
|
||||||
Py_ssize_t nargs = PyTuple_GET_SIZE(tuple);
|
Py_ssize_t nargs = PyTuple_GET_SIZE(tuple);
|
||||||
PyObject *kwnames;
|
PyObject *kwnames;
|
||||||
|
|
|
@ -5145,17 +5145,21 @@ inherit_slots(PyTypeObject *type, PyTypeObject *base)
|
||||||
}
|
}
|
||||||
COPYSLOT(tp_repr);
|
COPYSLOT(tp_repr);
|
||||||
/* tp_hash see tp_richcompare */
|
/* 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;
|
/* Inherit tp_vectorcall_offset only if tp_call is not overridden */
|
||||||
type->tp_flags |= _Py_TPFLAGS_HAVE_VECTORCALL;
|
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);
|
COPYSLOT(tp_str);
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue