155
155
import com .oracle .graal .python .builtins .objects .dict .PDict ;
156
156
import com .oracle .graal .python .builtins .objects .function .BuiltinMethodDescriptor ;
157
157
import com .oracle .graal .python .builtins .objects .function .PBuiltinFunction ;
158
+ import com .oracle .graal .python .builtins .objects .method .PBuiltinMethod ;
158
159
import com .oracle .graal .python .builtins .objects .type .TypeNodes .GetBaseClassNode ;
159
160
import com .oracle .graal .python .builtins .objects .type .TypeNodes .GetMroStorageNode ;
160
161
import com .oracle .graal .python .builtins .objects .type .TypeNodes .GetSubclassesNode ;
@@ -299,15 +300,7 @@ static class Flags {
299
300
}
300
301
301
302
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
+
311
304
private final TruffleString name ;
312
305
@ CompilationFinal private SpecialMethodSlot reverse ;
313
306
/**
@@ -543,7 +536,7 @@ private static Object[] initializeSpecialMethodsSlots(PythonManagedClass klass,
543
536
if (isMroSubtype (mro , managedBase )) {
544
537
Object [] result = PythonUtils .arrayCopyOf (managedBase .specialMethodSlots , managedBase .specialMethodSlots .length );
545
538
setSlotsFromManaged (result , klass , language );
546
- setNewSlot (result , klass );
539
+ fixupNewSlot (result , klass );
547
540
setMethodsFlags (result , klass );
548
541
return result ;
549
542
}
@@ -585,7 +578,7 @@ private static Object[] initializeSpecialMethodsSlots(PythonManagedClass klass,
585
578
setSlotsFromGeneric (slots , base , language );
586
579
}
587
580
}
588
- setNewSlot (slots , klass );
581
+ fixupNewSlot (slots , klass );
589
582
setMethodsFlags (slots , klass );
590
583
return slots ;
591
584
}
@@ -626,29 +619,72 @@ private static void setMethodsFlags(Object[] slots, PythonManagedClass klass) {
626
619
}
627
620
}
628
621
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 ;
634
671
}
635
- slots [New .ordinal ()] = newMethod ;
636
672
}
637
673
638
674
private static void setSlotsFromManaged (Object [] slots , PythonManagedClass source , PythonLanguage language ) {
639
675
PDict dict = GetDictIfExistsNode .getUncached ().execute (source );
640
676
if (dict == null ) {
641
677
DynamicObject storage = source .getStorage ();
642
678
DynamicObjectLibrary domLib = DynamicObjectLibrary .getFactory ().getUncached (storage );
643
- for (SpecialMethodSlot slot : MRO_INHERITED_SLOTS ) {
679
+ for (SpecialMethodSlot slot : VALUES ) {
644
680
final Object value = domLib .getOrDefault (source , slot .getName (), PNone .NO_VALUE );
645
681
if (value != PNone .NO_VALUE ) {
646
682
slots [slot .ordinal ()] = asSlotValue (slot , value , language );
647
683
}
648
684
}
649
685
} else {
650
686
HashingStorage storage = dict .getDictStorage ();
651
- for (SpecialMethodSlot slot : MRO_INHERITED_SLOTS ) {
687
+ for (SpecialMethodSlot slot : VALUES ) {
652
688
final Object value = HashingStorageGetItem .executeUncached (storage , slot .getName ());
653
689
if (value != null ) {
654
690
slots [slot .ordinal ()] = asSlotValue (slot , value , language );
@@ -659,7 +695,7 @@ private static void setSlotsFromManaged(Object[] slots, PythonManagedClass sourc
659
695
660
696
private static void setSlotsFromGeneric (Object [] slots , PythonAbstractClass base , PythonLanguage language ) {
661
697
ReadAttributeFromObjectNode readAttNode = ReadAttributeFromObjectNode .getUncachedForceType ();
662
- for (SpecialMethodSlot slot : MRO_INHERITED_SLOTS ) {
698
+ for (SpecialMethodSlot slot : VALUES ) {
663
699
Object value = readAttNode .execute (base , slot .getName ());
664
700
if (value != PNone .NO_VALUE ) {
665
701
slots [slot .ordinal ()] = asSlotValue (slot , value , language );
@@ -673,11 +709,7 @@ public static void fixupSpecialMethodSlot(PythonNativeClass klass, SpecialMethod
673
709
if (value == PNone .NO_VALUE ) {
674
710
// We are removing the value: find the new value for the class that is being updated and
675
711
// 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 ());
681
713
}
682
714
fixupSpecialMethodInSubClasses (GetSubclassesNode .executeUncached (klass ), slot , newValue , PythonContext .get (null ));
683
715
}
@@ -701,11 +733,7 @@ public static void fixupSpecialMethodSlot(PythonManagedClass klass, SpecialMetho
701
733
if (value == PNone .NO_VALUE ) {
702
734
// We are removing the value: find the new value for the class that is being updated and
703
735
// 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 ());
709
737
}
710
738
711
739
PythonContext context = PythonContext .get (null );
@@ -1245,7 +1273,7 @@ public static boolean validateSlots(Object klassIn) {
1245
1273
if (type .getSpecialMethodSlots () == null ) {
1246
1274
return true ;
1247
1275
}
1248
- for (SpecialMethodSlot slot : MRO_INHERITED_SLOTS ) {
1276
+ for (SpecialMethodSlot slot : VALUES ) {
1249
1277
Object actual = LookupAttributeInMRONode .findAttr (core , type , slot .getName (), uncachedReadAttrNode );
1250
1278
Object expected = slot .getValue (type );
1251
1279
if (expected instanceof BuiltinMethodDescriptor ) {
@@ -1260,7 +1288,7 @@ public static boolean validateSlots(Object klassIn) {
1260
1288
}
1261
1289
if (klass instanceof PythonManagedClass ) {
1262
1290
PythonManagedClass managed = (PythonManagedClass ) klass ;
1263
- for (SpecialMethodSlot slot : MRO_INHERITED_SLOTS ) {
1291
+ for (SpecialMethodSlot slot : VALUES ) {
1264
1292
Object actual = LookupAttributeInMRONode .lookupSlowPath (managed , slot .getName ());
1265
1293
Object expected = slot .getValue (managed );
1266
1294
if (expected instanceof NodeFactory <?>) {
0 commit comments