Skip to content

Commit 18f4e44

Browse files
committed
[GR-45265] Inherit slots after installing attrs in PyType_Ready.
PullRequest: graalpython/2708
2 parents 2c20e61 + 6a363c7 commit 18f4e44

File tree

3 files changed

+93
-48
lines changed

3 files changed

+93
-48
lines changed

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

Lines changed: 74 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -178,34 +178,19 @@ void PyType_Modified(PyTypeObject* type) {
178178
static void inherit_special(PyTypeObject *type, PyTypeObject *base) {
179179

180180
/* Copying basicsize is connected to the GC flags */
181-
unsigned long flags = PyTypeObject_tp_flags(type);
182-
unsigned long base_flags = PyTypeObject_tp_flags(base);
181+
unsigned long flags = PyTypeObject_tp_flags(type);
182+
unsigned long base_flags = PyTypeObject_tp_flags(base);
183183
if (!(flags & Py_TPFLAGS_HAVE_GC) &&
184184
(base_flags & Py_TPFLAGS_HAVE_GC) &&
185185
(!PyTypeObject_tp_traverse(type) && !PyTypeObject_tp_clear(type))) {
186-
flags |= Py_TPFLAGS_HAVE_GC;
186+
187+
flags |= Py_TPFLAGS_HAVE_GC;
187188
if (PyTypeObject_tp_traverse(type) == NULL)
188189
set_PyTypeObject_tp_traverse(type, PyTypeObject_tp_traverse(base));
189190
if (PyTypeObject_tp_clear(type) == NULL)
190-
set_PyTypeObject_tp_clear(type, PyTypeObject_tp_clear(base));
191-
}
192-
{
193-
/* The condition below could use some explanation.
194-
It appears that tp_new is not inherited for static types
195-
whose base class is 'object'; this seems to be a precaution
196-
so that old extension types don't suddenly become
197-
callable (object.__new__ wouldn't insure the invariants
198-
that the extension type's own factory function ensures).
199-
Heap types, of course, are under our control, so they do
200-
inherit tp_new; static extension types that specify some
201-
other built-in type as the default also
202-
inherit object.__new__. */
203-
if (base != &PyBaseObject_Type ||
204-
(flags & Py_TPFLAGS_HEAPTYPE)) {
205-
if (PyTypeObject_tp_new(type) == NULL)
206-
set_PyTypeObject_tp_new(type, PyTypeObject_tp_new(base)) ;
207-
}
191+
set_PyTypeObject_tp_clear(type, PyTypeObject_tp_clear(base));
208192
}
193+
209194
/* Copy other non-function slots */
210195

211196
#undef COPYVAL
@@ -238,7 +223,7 @@ static void inherit_special(PyTypeObject *type, PyTypeObject *base) {
238223
flags |= _Py_TPFLAGS_MATCH_SELF;
239224

240225
if (flags != PyTypeObject_tp_flags(type)) {
241-
set_PyTypeObject_tp_flags(type, flags);
226+
set_PyTypeObject_tp_flags(type, flags);
242227
}
243228
}
244229

@@ -372,13 +357,55 @@ static void add_slot(PyTypeObject* cls, PyObject* type_dict, char* name, void* m
372357
}
373358
}
374359

375-
#define ADD_MEMBER(__javacls__, __tpdict__, __mname__, __mtype__, __moffset__, __mflags__, __mdoc__) \
360+
#define ADD_MEMBER(__javacls__, __tpdict__, __mname__, __mtype__, __moffset__, __mflags__, __mdoc__) \
376361
add_member((__javacls__), (__tpdict__), (__mname__), (__mtype__), (__moffset__), (__mflags__), (__mdoc__))
377362

378363

379-
#define ADD_GETSET(__javacls__, __tpdict__, __name__, __getter__, __setter__, __doc__, __closure__) \
364+
#define ADD_GETSET(__javacls__, __tpdict__, __name__, __getter__, __setter__, __doc__, __closure__) \
380365
add_getset((__javacls__), (__tpdict__), (__name__), (__getter__), (__setter__), (__doc__), (__closure__))
381366

367+
// Set tp_new and the "__new__" key in the type dictionary.
368+
// Use the Py_TPFLAGS_DISALLOW_INSTANTIATION flag.
369+
static int
370+
type_ready_set_new(PyTypeObject *type, PyObject *dict, PyTypeObject *base)
371+
{
372+
/* The condition below could use some explanation.
373+
374+
It appears that tp_new is not inherited for static types whose base
375+
class is 'object'; this seems to be a precaution so that old extension
376+
types don't suddenly become callable (object.__new__ wouldn't insure the
377+
invariants that the extension type's own factory function ensures).
378+
379+
Heap types, of course, are under our control, so they do inherit tp_new;
380+
static extension types that specify some other built-in type as the
381+
default also inherit object.__new__. */
382+
newfunc tp_new = PyTypeObject_tp_new(type);
383+
unsigned long tp_flags = PyTypeObject_tp_flags(type);
384+
if (tp_new == NULL
385+
&& base == &PyBaseObject_Type
386+
&& !(tp_flags & Py_TPFLAGS_HEAPTYPE))
387+
{
388+
set_PyTypeObject_tp_flags(type, tp_flags |= Py_TPFLAGS_DISALLOW_INSTANTIATION);
389+
}
390+
391+
if (!(type->tp_flags & Py_TPFLAGS_DISALLOW_INSTANTIATION)) {
392+
if (tp_new != NULL) {
393+
// If "__new__" key does not exists in the type dictionary,
394+
// set it to tp_new_wrapper().
395+
add_slot(type, dict, "__new__", tp_new, METH_KEYWORDS | METH_VARARGS, JWRAPPER_NEW, NULL);
396+
}
397+
else {
398+
// tp_new is NULL: inherit tp_new from base
399+
set_PyTypeObject_tp_new(type, PyTypeObject_tp_new(base)) ;
400+
}
401+
}
402+
else {
403+
// Py_TPFLAGS_DISALLOW_INSTANTIATION sets tp_new to NULL
404+
// not supported yet
405+
// set_PyTypeObject_tp_new(type, NULL) ;
406+
}
407+
return 0;
408+
}
382409

383410
int PyType_Ready(PyTypeObject* cls) {
384411
#define RETURN_ERROR(__type__) \
@@ -388,7 +415,7 @@ int PyType_Ready(PyTypeObject* cls) {
388415
return -1; \
389416
} while(0)
390417

391-
#define ADD_IF_MISSING(attr, def) if (!(attr)) { attr = def; }
418+
#define ADD_IF_MISSING(OBJ, SLOT, VAL) if (!(PyTypeObject_##SLOT(OBJ))) { set_PyTypeObject_##SLOT((OBJ), (VAL)); }
392419
#define ADD_SLOT_CONV(__name__, __meth__, __flags__, __signature__) add_slot(cls, dict, (__name__), (__meth__), (__flags__), (__signature__), NULL)
393420

394421
Py_ssize_t n;
@@ -507,24 +534,10 @@ int PyType_Ready(PyTypeObject* cls) {
507534
PyObject* mro = GraalPyTruffle_Compute_Mro(cls, truffleString(cls->tp_name));
508535
set_PyTypeObject_tp_mro(cls, mro);
509536

510-
/* Inherit special flags from dominant base */
511-
if (base != NULL)
512-
inherit_special(cls, base);
513-
514-
/* Initialize tp_dict properly */
515-
bases = mro;
516-
assert(bases != NULL);
517-
assert(PyTuple_Check(bases));
518-
n = PyTuple_GET_SIZE(bases);
519-
for (i = 1; i < n; i++) {
520-
PyObject *b = PyTuple_GET_ITEM(bases, i);
521-
if (PyType_Check(b))
522-
inherit_slots(cls, (PyTypeObject *)b);
523-
}
524-
525-
ADD_IF_MISSING(cls->tp_alloc, PyType_GenericAlloc);
526-
ADD_IF_MISSING(cls->tp_new, PyType_GenericNew);
537+
/* set new */
538+
type_ready_set_new(cls, dict, base);
527539

540+
/* fill dict */
528541
// add special methods defined directly on the type structs
529542
ADD_SLOT_CONV("__dealloc__", cls->tp_dealloc, -1, JWRAPPER_DIRECT);
530543
// https://docs.python.org/3/c-api/typeobj.html#c.PyTypeObject.tp_getattr
@@ -563,7 +576,9 @@ int PyType_Ready(PyTypeObject* cls) {
563576
ADD_SLOT_CONV("__set__", cls->tp_descr_set, -3, JWRAPPER_DESCR_SET);
564577
ADD_SLOT_CONV("__init__", cls->tp_init, METH_KEYWORDS | METH_VARARGS, JWRAPPER_INITPROC);
565578
ADD_SLOT_CONV("__alloc__", cls->tp_alloc, -2, JWRAPPER_ALLOC);
566-
ADD_SLOT_CONV("__new__", cls->tp_new, METH_KEYWORDS | METH_VARARGS, JWRAPPER_NEW);
579+
/* Note: '__new__' was added here previously but we don't do it similar to CPython.
580+
They also skip it because the appropriate 'slotdef' doesn't have a wrapper.
581+
Adding '__new__' is done by function 'type_ready_set_new'. */
567582
ADD_SLOT_CONV("__free__", cls->tp_free, -1, JWRAPPER_DIRECT);
568583
ADD_SLOT_CONV("__del__", cls->tp_del, -1, JWRAPPER_DIRECT);
569584
ADD_SLOT_CONV("__finalize__", cls->tp_finalize, -1, JWRAPPER_DIRECT);
@@ -655,6 +670,22 @@ int PyType_Ready(PyTypeObject* cls) {
655670
// TODO ...
656671
}
657672

673+
/* Inherit slots */
674+
if (base != NULL)
675+
inherit_special(cls, base);
676+
bases = mro;
677+
assert(bases != NULL);
678+
assert(PyTuple_Check(bases));
679+
n = PyTuple_GET_SIZE(bases);
680+
for (i = 1; i < n; i++) {
681+
PyObject *b = PyTuple_GET_ITEM(bases, i);
682+
if (PyType_Check(b))
683+
inherit_slots(cls, (PyTypeObject *)b);
684+
}
685+
686+
ADD_IF_MISSING(cls, tp_alloc, PyType_GenericAlloc);
687+
ADD_IF_MISSING(cls, tp_new, PyType_GenericNew);
688+
658689
// process inherited slots
659690
// CPython doesn't do that in 'PyType_Ready' but we must because a native type can inherit
660691
// dynamic slots from a managed Python class. Since the managed Python class may be created

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -731,6 +731,17 @@ def test_doc(self):
731731
assert len(obj.some_member.__doc__) == len(expected_doc)
732732
assert obj.some_member.__doc__ == expected_doc
733733

734+
def test_multiple_inheritance_with_native(self):
735+
_A = CPyExtType("_A","")
736+
class B:
737+
def __getattr__(self, name):
738+
return name
739+
class X(_A, B):
740+
b = 2
741+
x = X()
742+
assert x.foo == "foo"
743+
744+
734745
class CBytes:
735746
def __bytes__(self):
736747
return b'abc'

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextTypeBuiltins.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
import com.oracle.graal.python.builtins.objects.cext.common.CExtContext;
8181
import com.oracle.graal.python.builtins.objects.cext.common.CExtContext.Store;
8282
import com.oracle.graal.python.builtins.objects.common.DynamicObjectStorage;
83+
import com.oracle.graal.python.builtins.objects.common.HashingStorageNodes.HashingStorageGetItem;
8384
import com.oracle.graal.python.builtins.objects.dict.DictBuiltins;
8485
import com.oracle.graal.python.builtins.objects.dict.PDict;
8586
import com.oracle.graal.python.builtins.objects.function.PBuiltinFunction;
@@ -292,12 +293,14 @@ abstract static class PyTruffleType_AddSlot extends CApi7BuiltinNode {
292293
@Specialization
293294
@TruffleBoundary
294295
static int addSlot(Object clazz, PDict tpDict, TruffleString memberName, Object cfunc, int flags, int wrapper, Object memberDoc) {
295-
// create wrapper descriptor
296-
Object wrapperDescriptor = CreateFunctionNodeGen.getUncached().execute(memberName, cfunc, wrapper, clazz, flags, PythonObjectFactory.getUncached());
297-
WriteAttributeToDynamicObjectNode.getUncached().execute(wrapperDescriptor, SpecialAttributeNames.T___DOC__, memberDoc);
296+
if (!HashingStorageGetItem.hasKeyUncached(tpDict.getDictStorage(), memberName)) {
297+
// create wrapper descriptor
298+
Object wrapperDescriptor = CreateFunctionNodeGen.getUncached().execute(memberName, cfunc, wrapper, clazz, flags, PythonObjectFactory.getUncached());
299+
WriteAttributeToDynamicObjectNode.getUncached().execute(wrapperDescriptor, SpecialAttributeNames.T___DOC__, memberDoc);
298300

299-
// add wrapper descriptor to tp_dict
300-
PyDictSetItem.executeUncached(tpDict, memberName, wrapperDescriptor);
301+
// add wrapper descriptor to tp_dict
302+
PyDictSetItem.executeUncached(tpDict, memberName, wrapperDescriptor);
303+
}
301304
return 0;
302305
}
303306
}

0 commit comments

Comments
 (0)