Skip to content

Commit 7784171

Browse files
committed
[GR-45310] Fix slot precedence wrt installed attributes.
PullRequest: graalpython/2710
2 parents ef09203 + 0bd3ba8 commit 7784171

File tree

2 files changed

+58
-14
lines changed

2 files changed

+58
-14
lines changed

graalpython/com.oracle.graal.python.cext/src/typeobject.c

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -538,6 +538,12 @@ int PyType_Ready(PyTypeObject* cls) {
538538
type_ready_set_new(cls, dict, base);
539539

540540
/* fill dict */
541+
542+
/*
543+
* NOTE: ADD_SLOT_CONV won't overwrite existing attributes, so the order is crucial and must
544+
* reflect CPython's 'slotdefs' array.
545+
*/
546+
541547
// add special methods defined directly on the type structs
542548
ADD_SLOT_CONV("__dealloc__", cls->tp_dealloc, -1, JWRAPPER_DIRECT);
543549
// https://docs.python.org/3/c-api/typeobj.html#c.PyTypeObject.tp_getattr
@@ -583,20 +589,7 @@ int PyType_Ready(PyTypeObject* cls) {
583589
ADD_SLOT_CONV("__del__", cls->tp_del, -1, JWRAPPER_DIRECT);
584590
ADD_SLOT_CONV("__finalize__", cls->tp_finalize, -1, JWRAPPER_DIRECT);
585591

586-
PySequenceMethods* sequences = PyTypeObject_tp_as_sequence(cls);
587-
if (sequences) {
588-
// sequence functions first, so that the number functions take precendence
589-
ADD_SLOT_CONV("__len__", sequences->sq_length, -1, JWRAPPER_LENFUNC);
590-
ADD_SLOT_CONV("__add__", sequences->sq_concat, -2, JWRAPPER_BINARYFUNC);
591-
ADD_SLOT_CONV("__mul__", sequences->sq_repeat, -2, JWRAPPER_SSIZE_ARG);
592-
ADD_SLOT_CONV("__getitem__", sequences->sq_item, -2, JWRAPPER_GETITEM);
593-
ADD_SLOT_CONV("__setitem__", sequences->sq_ass_item, -3, JWRAPPER_SETITEM);
594-
ADD_SLOT_CONV("__delitem__", sequences->sq_ass_item, -3, JWRAPPER_DELITEM);
595-
ADD_SLOT_CONV("__contains__", sequences->sq_contains, -2, JWRAPPER_OBJOBJPROC);
596-
ADD_SLOT_CONV("__iadd__", sequences->sq_inplace_concat, -2, JWRAPPER_BINARYFUNC);
597-
ADD_SLOT_CONV("__imul__", sequences->sq_inplace_repeat, -2, JWRAPPER_SSIZE_ARG);
598-
}
599-
592+
// 'tp_as_number' takes precedence over 'tp_as_mapping' and 'tp_as_sequence' !
600593
PyNumberMethods* numbers = PyTypeObject_tp_as_number(cls);
601594
if (numbers) {
602595
ADD_SLOT_CONV("__add__", numbers->nb_add, -2, JWRAPPER_BINARYFUNC_L);
@@ -650,6 +643,7 @@ int PyType_Ready(PyTypeObject* cls) {
650643
ADD_SLOT_CONV("__imatmul__", numbers->nb_inplace_matrix_multiply, -2, JWRAPPER_BINARYFUNC_L);
651644
}
652645

646+
// 'tp_as_mapping' takes precedence over 'tp_as_sequence' !
653647
PyMappingMethods* mappings = PyTypeObject_tp_as_mapping(cls);
654648
if (mappings) {
655649
ADD_SLOT_CONV("__len__", mappings->mp_length, -1, JWRAPPER_LENFUNC);
@@ -658,6 +652,20 @@ int PyType_Ready(PyTypeObject* cls) {
658652
ADD_SLOT_CONV("__delitem__", mappings->mp_ass_subscript, -3, JWRAPPER_MP_DELITEM);
659653
}
660654

655+
PySequenceMethods* sequences = PyTypeObject_tp_as_sequence(cls);
656+
if (sequences) {
657+
// sequence functions first, so that the number functions take precendence
658+
ADD_SLOT_CONV("__len__", sequences->sq_length, -1, JWRAPPER_LENFUNC);
659+
ADD_SLOT_CONV("__add__", sequences->sq_concat, -2, JWRAPPER_BINARYFUNC);
660+
ADD_SLOT_CONV("__mul__", sequences->sq_repeat, -2, JWRAPPER_SSIZE_ARG);
661+
ADD_SLOT_CONV("__getitem__", sequences->sq_item, -2, JWRAPPER_GETITEM);
662+
ADD_SLOT_CONV("__setitem__", sequences->sq_ass_item, -3, JWRAPPER_SETITEM);
663+
ADD_SLOT_CONV("__delitem__", sequences->sq_ass_item, -3, JWRAPPER_DELITEM);
664+
ADD_SLOT_CONV("__contains__", sequences->sq_contains, -2, JWRAPPER_OBJOBJPROC);
665+
ADD_SLOT_CONV("__iadd__", sequences->sq_inplace_concat, -2, JWRAPPER_BINARYFUNC);
666+
ADD_SLOT_CONV("__imul__", sequences->sq_inplace_repeat, -2, JWRAPPER_SSIZE_ARG);
667+
}
668+
661669
PyAsyncMethods* async = PyTypeObject_tp_as_async(cls);
662670
if (async) {
663671
ADD_SLOT_CONV("__await__", async->am_await, -1, JWRAPPER_DIRECT);

graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_object.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -741,6 +741,42 @@ class X(_A, B):
741741
x = X()
742742
assert x.foo == "foo"
743743

744+
def test_slot_precedence(self):
745+
MapAndSeq = CPyExtType("MapAndSeq",
746+
'''
747+
static PyObject * mas_nb_add(PyObject *self, PyObject *other) {
748+
return PyUnicode_FromString("mas_nb_add");
749+
}
750+
static Py_ssize_t mas_sq_length(PyObject *self) {
751+
return 111;
752+
}
753+
static PyObject *mas_sq_item(PyObject *self, Py_ssize_t idx) {
754+
return PyUnicode_FromString("sq_item");
755+
}
756+
static PyObject * mas_sq_concat(PyObject *self, PyObject *other) {
757+
return PyUnicode_FromString("mas_sq_concat");
758+
}
759+
static Py_ssize_t mas_mp_length(PyObject *self) {
760+
return 222;
761+
}
762+
static PyObject * mas_mp_subscript(PyObject *self, PyObject *key) {
763+
return PyUnicode_FromString("mp_subscript");
764+
}
765+
''',
766+
nb_add='mas_nb_add',
767+
sq_length='mas_sq_length',
768+
sq_item='mas_sq_item',
769+
sq_concat='mas_sq_concat',
770+
mp_length='mas_mp_length',
771+
mp_subscript='mas_mp_subscript',
772+
)
773+
obj = MapAndSeq()
774+
# Note: len(obj) uses 'PyObject_Lenght' which does not use the attribute but first tries
775+
# 'sq_length' and falls back to 'mp_length'. Therefore, we just look at '__len__' here.
776+
assert obj.__len__() == 222
777+
assert obj['hello'] == 'mp_subscript'
778+
assert obj + 'hello' == 'mas_nb_add'
779+
744780

745781
class CBytes:
746782
def __bytes__(self):

0 commit comments

Comments
 (0)