8000 bpo-36974: Make tp_call=PyVectorcall_Call work for inherited types (G… · python/cpython@fb9423f · GitHub
[go: up one dir, main page]

Skip to content 8000

Commit fb9423f

Browse files
authored
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.
1 parent e1179a5 commit fb9423f

File tree

4 files changed

+51
-17
lines changed

4 files changed

+51
-17
lines changed

Lib/test/test_capi.py

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -515,9 +515,10 @@ def test_vectorcall_override(self):
515515

516516
def test_vectorcall(self):
517517
# Test a bunch of different ways to call objects:
518-
# 1. normal call
519-
# 2. vectorcall using _PyObject_Vectorcall()
520-
# 3. vectorcall using PyVectorcall_Call()
518+
# 1. vectorcall using PyVectorcall_Call()
519+
# (only for objects that support vectorcall directly)
520+
# 2. normal call
521+
# 3. vectorcall using _PyObject_Vectorcall()
521522
# 4. call as bound method
522523
# 5. call using functools.partial
523524

@@ -541,6 +542,27 @@ def vectorcall(func, args, kwargs):
541542
kwnames = tuple(kwargs)
542543
return pyobject_vectorcall(func, args, kwnames)
543544

545+
for (func, args, kwargs, expected) in calls:
546+
with self.subTest(str(func)):
547+
if not kwargs:
548+
self.assertEqual(expected, pyvectorcall_call(func, args))
549+
self.assertEqual(expected, pyvectorcall_call(func, args, kwargs))
550+
551+
# Add derived classes (which do not support vectorcall directly,
552+
# but do support all other ways of calling).
553+
554+
class MethodDescriptorHeap(_testcapi.MethodDescriptorBase):
555+
pass
556+
557+
class MethodDescriptorOverridden(_testcapi.MethodDescriptorBase):
558+
def __call__(self, n):
559+
return 'new'
560+
561+
calls += [
562+
(MethodDescriptorHeap(), (0,), {}, True),
563+
(MethodDescriptorOverridden(), (0,), {}, 'new'),
564+
]
565+
544566
for (func, args, kwargs, expected) in calls:
545567
with self.subTest(str(func)):
546568
args1 = args[1:]
@@ -549,12 +571,10 @@ def vectorcall(func, args, kwargs):
549571
if not kwargs:
550572
self.assertEqual(expected, func(*args))
551573
self.assertEqual(expected, pyobject_vectorcall(func, args, None))
552-
self.assertEqual(expected, pyvectorcall_call(func, args))
553574
self.assertEqual(expected, meth(*args1))
554575
self.assertEqual(expected, wrapped(*args))
555576
self.assertEqual(expected, func(*args, **kwargs))
556577
self.assertEqual(expected, vectorcall(func, args, kwargs))
557-
self.assertEqual(expected, pyvectorcall_call(func, args, kwargs))
558578
self.assertEqual(expected, meth(*args1, **kwargs))
559579
self.assertEqual(expected, wrapped(*args, **kwargs))
560580

Modules/_testcapimodule.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5854,7 +5854,7 @@ MethodDescriptor_vectorcall(PyObject *callable, PyObject *const *args,
58545854
static PyObject *
58555855
MethodDescriptor_new(PyTypeObject* type, PyObject* args, PyObject *kw)
58565856
{
5857-
MethodDescriptorObject *op = PyObject_New(MethodDescriptorObject, type);
5857+
MethodDescriptorObject *op = type->tp_alloc(type, 0);
58585858
op->vectorcall = MethodDescriptor_vectorcall;
58595859
return (PyObject *)op;
58605860
}

Objects/call.c

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,12 +173,22 @@ _PyObject_MakeTpCall(PyObject *callable, PyObject *const *args, Py_ssize_t nargs
173173
PyObject *
174174
PyVectorcall_Call(PyObject *callable, PyObject *tuple, PyObject *kwargs)
175175
{
176-
vectorcallfunc func = _PyVectorcall_Function(callable);
176+
/* get vectorcallfunc as in _PyVectorcall_Function, but without
177+
* the _Py_TPFLAGS_HAVE_VECTORCALL check */
178+
Py_ssize_t offset = Py_TYPE(callable)->tp_vectorcall_offset;
179+
if ((offset <= 0) || (!Py_TYPE(callable)->tp_call)) {
180+
PyErr_Format(PyExc_TypeError, "'%.200s' object does not support vectorcall",
181+
Py_TYPE(callable)->tp_name);
182+
return NULL;
183+
}
184+
vectorcallfunc func = *(vectorcallfunc *)(((char *)callable) + offset);
177185
if (func == NULL) {
178186
PyErr_Format(PyExc_TypeError, "'%.200s' object does not support vectorcall",
179187
Py_TYPE(callable)->tp_name);
180188
return NULL;
181189
}
190+
191+
/* Convert arguments & call */
182192
PyObject *const *args;
183193
Py_ssize_t nargs = PyTuple_GET_SIZE(tuple);
184194
PyObject *kwnames;

Objects/typeobject.c

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5145,17 +5145,21 @@ inherit_slots(PyTypeObject *type, PyTypeObject *base)
51455145
}
51465146
COPYSLOT(tp_repr);
51475147
/* tp_hash see tp_richcompare */
5148-
COPYSLOT(tp_call);
5149-
/* Inherit tp_vectorcall_offset and _Py_TPFLAGS_HAVE_VECTORCALL if tp_call
5150-
* was inherited, but only for extension types */
5151-
if ((base->tp_flags & _Py_TPFLAGS_HAVE_VECTORCALL) &&
5152-
!(type->tp_flags & _Py_TPFLAGS_HAVE_VECTORCALL) &&
5153-
!(type->tp_flags & Py_TPFLAGS_HEAPTYPE) &&
5154-
base->tp_call &&
5155-
type->tp_call == base->tp_call)
51565148
{
5157-
type->tp_vectorcall_offset = base->tp_vectorcall_offset;
5158-
type->tp_flags |= _Py_TPFLAGS_HAVE_VECTORCALL;
5149+
/* Inherit tp_vectorcall_offset only if tp_call is not overridden */
5150+
if (!type->tp_call) {
5151+
COPYSLOT(tp_vectorcall_offset);
5152+
}
5153+
/* Inherit_Py_TPFLAGS_HAVE_VECTORCALL for non-heap types
5154+
* if tp_call is not overridden */
5155+
if (!type->tp_call &&
5156+
(base->tp_flags & _Py_TPFLAGS_HAVE_VECTORCALL) &&
5157+
!(type->tp_flags & _Py_TPFLAGS_HAVE_VECTORCALL) &&
5158+
!(type->tp_flags & Py_TPFLAGS_HEAPTYPE))
5159+
{
5160+
type->tp_flags |= _Py_TPFLAGS_HAVE_VECTORCALL;
5161+
}
5162+
COPYSLOT(tp_call);
51595163
}
51605164
COPYSLOT(tp_str);
51615165
{

0 commit comments

Comments
 (0)
0