Skip to content

Commit fecbcbb

Browse files
msimaceksteve-s
authored andcommitted
Replicate CPython's tp_new inheritance better
1 parent f112f6a commit fecbcbb

File tree

1 file changed

+60
-32
lines changed

1 file changed

+60
-32
lines changed

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/SpecialMethodSlot.java

Lines changed: 60 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@
155155
import com.oracle.graal.python.builtins.objects.dict.PDict;
156156
import com.oracle.graal.python.builtins.objects.function.BuiltinMethodDescriptor;
157157
import com.oracle.graal.python.builtins.objects.function.PBuiltinFunction;
158+
import com.oracle.graal.python.builtins.objects.method.PBuiltinMethod;
158159
import com.oracle.graal.python.builtins.objects.type.TypeNodes.GetBaseClassNode;
159160
import com.oracle.graal.python.builtins.objects.type.TypeNodes.GetMroStorageNode;
160161
import com.oracle.graal.python.builtins.objects.type.TypeNodes.GetSubclassesNode;
@@ -299,15 +300,7 @@ static class Flags {
299300
}
300301

301302
public static final SpecialMethodSlot[] VALUES = values();
302-
public static final SpecialMethodSlot[] MRO_INHERITED_SLOTS = new SpecialMethodSlot[VALUES.length - 1];
303-
static {
304-
for (int i = 0, j = 0; i < VALUES.length; i++) {
305-
if (i == New.ordinal()) {
306-
continue;
307-
}
308-
MRO_INHERITED_SLOTS[j++] = VALUES[i];
309-
}
310-
}
303+
311304
private final TruffleString name;
312305
@CompilationFinal private SpecialMethodSlot reverse;
313306
/**
@@ -543,7 +536,7 @@ private static Object[] initializeSpecialMethodsSlots(PythonManagedClass klass,
543536
if (isMroSubtype(mro, managedBase)) {
544537
Object[] result = PythonUtils.arrayCopyOf(managedBase.specialMethodSlots, managedBase.specialMethodSlots.length);
545538
setSlotsFromManaged(result, klass, language);
546-
setNewSlot(result, klass);
539+
fixupNewSlot(result, klass);
547540
setMethodsFlags(result, klass);
548541
return result;
549542
}
@@ -585,7 +578,7 @@ private static Object[] initializeSpecialMethodsSlots(PythonManagedClass klass,
585578
setSlotsFromGeneric(slots, base, language);
586579
}
587580
}
588-
setNewSlot(slots, klass);
581+
fixupNewSlot(slots, klass);
589582
setMethodsFlags(slots, klass);
590583
return slots;
591584
}
@@ -626,29 +619,72 @@ private static void setMethodsFlags(Object[] slots, PythonManagedClass klass) {
626619
}
627620
}
628621

629-
private static void setNewSlot(Object[] slots, Object type) {
630-
Object newMethod = ReadAttributeFromObjectNode.getUncachedForceType().execute(type, New.name);
631-
if (newMethod == PNone.NO_VALUE) {
632-
Object base = GetBaseClassNode.executeUncached(type);
633-
newMethod = LookupNewNode.executeUncached(base);
622+
/**
623+
* CPython has a bug in their {@code tp_new} slot inheritance. Packages, notably pandas, rely on
624+
* the bug being present, so we have to try to replicate the same behavior.
625+
* <p>
626+
* CPython first inherits {@code tp_new} from the dominant base ({@code tp_base}) and creates a
627+
* {@code __new__} special method for it. Later, they update all slots to match what's in the
628+
* object's special methods, which are looked up in MRO. In case of multiple inheritance, the
629+
* MRO-inherited {@code __new__} may be different from the {@code tp_base}-inherited one.
630+
* Normally, one would expect that the MRO-inherited one wins, as is the case for all other
631+
* slots. See the code in {@code update_one_slot}, it does this:
632+
*
633+
* <pre>
634+
...
635+
void **ptr = slotptr(type, offset);
636+
...
637+
descr = find_name_in_mro(type, p->name_strobj, &error);
638+
...
639+
else if (Py_IS_TYPE(descr, &PyCFunction_Type) &&
640+
PyCFunction_GET_FUNCTION(descr) ==
641+
(PyCFunction)(void(*)(void))tp_new_wrapper &&
642+
ptr == (void**)&type->tp_new)
643+
{
644+
specific = (void *)type->tp_new;
645+
}
646+
* </pre>
647+
*
648+
* Apparently, they wanted to make an optimization that avoids using the wrapper if the wrapper
649+
* was for the same {@code tp_new} as was inherited from {@code tp_base}. But they only check
650+
* that it's a wrapper, but they forgot to check that it's for the same type. The wrapper
651+
* function carries the type in its self, so they should have also checked that
652+
* {@code PyCFunction_GET_SELF(descr) == type}. So in summary, {@code tp_new} is inherited
653+
* through {@code tp_base} when the one in MRO is builtin/native, and it's inherited through MRO
654+
* otherwise.
655+
* <p>
656+
* We approximate the check for the wrapper by checking that it's a builtin method, and it's
657+
* named {@code __new__}.
658+
*/
659+
private static void fixupNewSlot(Object[] slots, Object type) {
660+
Object mroInheritedNew = slots[New.ordinal()];
661+
if (mroInheritedNew instanceof PBuiltinMethod builtinMethod) {
662+
mroInheritedNew = builtinMethod.getBuiltinFunction();
663+
}
664+
if (mroInheritedNew instanceof PBuiltinFunction builtinFunction && builtinFunction.getName().equalsUncached(New.name, TS_ENCODING)) {
665+
Object tpBaseInheritedNew = ReadAttributeFromObjectNode.getUncachedForceType().execute(type, New.name);
666+
if (tpBaseInheritedNew == PNone.NO_VALUE) {
667+
Object base = GetBaseClassNode.executeUncached(type);
668+
tpBaseInheritedNew = LookupNewNode.executeUncached(base);
669+
}
670+
slots[New.ordinal()] = tpBaseInheritedNew;
634671
}
635-
slots[New.ordinal()] = newMethod;
636672
}
637673

638674
private static void setSlotsFromManaged(Object[] slots, PythonManagedClass source, PythonLanguage language) {
639675
PDict dict = GetDictIfExistsNode.getUncached().execute(source);
640676
if (dict == null) {
641677
DynamicObject storage = source.getStorage();
642678
DynamicObjectLibrary domLib = DynamicObjectLibrary.getFactory().getUncached(storage);
643-
for (SpecialMethodSlot slot : MRO_INHERITED_SLOTS) {
679+
for (SpecialMethodSlot slot : VALUES) {
644680
final Object value = domLib.getOrDefault(source, slot.getName(), PNone.NO_VALUE);
645681
if (value != PNone.NO_VALUE) {
646682
slots[slot.ordinal()] = asSlotValue(slot, value, language);
647683
}
648684
}
649685
} else {
650686
HashingStorage storage = dict.getDictStorage();
651-
for (SpecialMethodSlot slot : MRO_INHERITED_SLOTS) {
687+
for (SpecialMethodSlot slot : VALUES) {
652688
final Object value = HashingStorageGetItem.executeUncached(storage, slot.getName());
653689
if (value != null) {
654690
slots[slot.ordinal()] = asSlotValue(slot, value, language);
@@ -659,7 +695,7 @@ private static void setSlotsFromManaged(Object[] slots, PythonManagedClass sourc
659695

660696
private static void setSlotsFromGeneric(Object[] slots, PythonAbstractClass base, PythonLanguage language) {
661697
ReadAttributeFromObjectNode readAttNode = ReadAttributeFromObjectNode.getUncachedForceType();
662-
for (SpecialMethodSlot slot : MRO_INHERITED_SLOTS) {
698+
for (SpecialMethodSlot slot : VALUES) {
663699
Object value = readAttNode.execute(base, slot.getName());
664700
if (value != PNone.NO_VALUE) {
665701
slots[slot.ordinal()] = asSlotValue(slot, value, language);
@@ -673,11 +709,7 @@ public static void fixupSpecialMethodSlot(PythonNativeClass klass, SpecialMethod
673709
if (value == PNone.NO_VALUE) {
674710
// We are removing the value: find the new value for the class that is being updated and
675711
// proceed with that
676-
if (slot == New) {
677-
newValue = LookupNewNode.executeUncached(GetBaseClassNode.executeUncached(klass));
678-
} else {
679-
newValue = LookupAttributeInMRONode.lookupSlowPath(klass, slot.getName());
680-
}
712+
newValue = LookupAttributeInMRONode.lookupSlowPath(klass, slot.getName());
681713
}
682714
fixupSpecialMethodInSubClasses(GetSubclassesNode.executeUncached(klass), slot, newValue, PythonContext.get(null));
683715
}
@@ -701,11 +733,7 @@ public static void fixupSpecialMethodSlot(PythonManagedClass klass, SpecialMetho
701733
if (value == PNone.NO_VALUE) {
702734
// We are removing the value: find the new value for the class that is being updated and
703735
// proceed with that
704-
if (slot == New) {
705-
newValue = LookupNewNode.executeUncached(GetBaseClassNode.executeUncached(klass));
706-
} else {
707-
newValue = LookupAttributeInMRONode.lookupSlowPath(klass, slot.getName());
708-
}
736+
newValue = LookupAttributeInMRONode.lookupSlowPath(klass, slot.getName());
709737
}
710738

711739
PythonContext context = PythonContext.get(null);
@@ -1245,7 +1273,7 @@ public static boolean validateSlots(Object klassIn) {
12451273
if (type.getSpecialMethodSlots() == null) {
12461274
return true;
12471275
}
1248-
for (SpecialMethodSlot slot : MRO_INHERITED_SLOTS) {
1276+
for (SpecialMethodSlot slot : VALUES) {
12491277
Object actual = LookupAttributeInMRONode.findAttr(core, type, slot.getName(), uncachedReadAttrNode);
12501278
Object expected = slot.getValue(type);
12511279
if (expected instanceof BuiltinMethodDescriptor) {
@@ -1260,7 +1288,7 @@ public static boolean validateSlots(Object klassIn) {
12601288
}
12611289
if (klass instanceof PythonManagedClass) {
12621290
PythonManagedClass managed = (PythonManagedClass) klass;
1263-
for (SpecialMethodSlot slot : MRO_INHERITED_SLOTS) {
1291+
for (SpecialMethodSlot slot : VALUES) {
12641292
Object actual = LookupAttributeInMRONode.lookupSlowPath(managed, slot.getName());
12651293
Object expected = slot.getValue(managed);
12661294
if (expected instanceof NodeFactory<?>) {

0 commit comments

Comments
 (0)