Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bpo-36974: Make tp_call=PyVectorcall_Call work for inherited types #13699

Merged
merged 2 commits into from
Jun 2, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 25 additions & 5 deletions Lib/test/test_capi.py
Original file line number Diff line number Diff line change
Expand Up @@ -515,9 +515,10 @@ def test_vectorcall_override(self):

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

Expand All @@ -541,6 +542,27 @@ def vectorcall(func, args, kwargs):
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:]
Expand All @@ -549,12 +571,10 @@ def vectorcall(func, args, kwargs):
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))

Expand Down
2 changes: 1 addition & 1 deletion Modules/_testcapimodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -5853,7 +5853,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;
}
Expand Down
12 changes: 11 additions & 1 deletion Objects/call.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
24 changes: 14 additions & 10 deletions Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
{
Expand Down