Skip to content

Commit 7e2b361

Browse files
committed
Fix inheritance of tp_hash in native types
1 parent 215ed99 commit 7e2b361

File tree

4 files changed

+88
-8
lines changed

4 files changed

+88
-8
lines changed

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

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6291,7 +6291,6 @@ type_ready_inherit(PyTypeObject *type)
62916291
}
62926292

62936293

6294-
#if 0 // GraalPy change
62956294
/* Hack for tp_hash and __hash__.
62966295
If after all that, tp_hash is still NULL, and __hash__ is not in
62976296
tp_dict, set tp_hash to PyObject_HashNotImplemented and
@@ -6304,21 +6303,22 @@ type_ready_set_hash(PyTypeObject *type)
63046303
return 0;
63056304
}
63066305

6307-
int r = PyDict_Contains(type->tp_dict, &_Py_ID(__hash__));
6306+
// GraalPy change: cannot use CPython's current _Py_ID mechanism
6307+
int r = _PyDict_ContainsId(type->tp_dict, &PyId___hash__);
63086308
if (r < 0) {
63096309
return -1;
63106310
}
63116311
if (r > 0) {
63126312
return 0;
63136313
}
63146314

6315-
if (PyDict_SetItem(type->tp_dict, &_Py_ID(__hash__), Py_None) < 0) {
6315+
// GraalPy change: cannot use CPython's current _Py_ID mechanism
6316+
if (_PyDict_SetItemId(type->tp_dict, &PyId___hash__, Py_None) < 0) {
63166317
return -1;
63176318
}
63186319
type->tp_hash = PyObject_HashNotImplemented;
63196320
return 0;
63206321
}
6321-
#endif // GraalPy change
63226322

63236323

63246324
/* Link into each base class's list of subclasses */
@@ -6476,11 +6476,10 @@ type_ready(PyTypeObject *type)
64766476
* when the C API is not loaded, we need to do that later.
64776477
*/
64786478
GraalPyTruffle_AddInheritedSlots(type);
6479-
#if 0 // GraalPy change
6479+
64806480
if (type_ready_set_hash(type) < 0) {
64816481
return -1;
64826482
}
6483-
#endif // GraalPy change
64846483
if (type_ready_add_subclasses(type) < 0) {
64856484
return -1;
64866485
}

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

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
3838
# SOFTWARE.
3939
import sys
40-
from . import CPyExtType, CPyExtHeapType, compile_module_from_string
40+
from . import CPyExtType, CPyExtHeapType, compile_module_from_string, assert_raises
4141

4242
SlotsGetter = CPyExtType("SlotsGetter",
4343
"""
@@ -437,4 +437,79 @@ def verify(x):
437437
assert x.__getitem__(-20) == -20
438438

439439
verify(MyMpLenSqItem())
440-
verify(MyMpLenSqItemHeap())
440+
verify(MyMpLenSqItemHeap())
441+
442+
443+
def test_tp_hash():
444+
TypeWithHash = CPyExtType(
445+
"TypeWithHash",
446+
'''
447+
static PyObject* richcompare(PyObject* self, PyObject* other, int cmp) {
448+
Py_RETURN_NOTIMPLEMENTED;
449+
}
450+
static Py_hash_t hash(PyObject* self) {
451+
return 123;
452+
}
453+
''',
454+
tp_richcompare='richcompare',
455+
tp_hash='hash',
456+
)
457+
assert TypeWithHash.__hash__
458+
assert hash(TypeWithHash()) == 123
459+
460+
class InheritsHash(TypeWithHash):
461+
pass
462+
463+
assert InheritsHash.__hash__
464+
assert hash(InheritsHash()) == 123
465+
466+
TypeWithoutHash = CPyExtType(
467+
"TypeWithoutHash",
468+
'''
469+
static PyObject* richcompare(PyObject* self, PyObject* other, int cmp) {
470+
Py_RETURN_NOTIMPLEMENTED;
471+
}
472+
static PyObject* has_hash_not_implemented(PyObject* unused, PyObject* obj) {
473+
return PyBool_FromLong(Py_TYPE(obj)->tp_hash == PyObject_HashNotImplemented);
474+
}
475+
''',
476+
tp_richcompare='richcompare',
477+
tp_methods='{"has_hash_not_implemented", (PyCFunction)has_hash_not_implemented, METH_STATIC | METH_O, ""}',
478+
)
479+
480+
def assert_has_no_hash(obj):
481+
assert_raises(TypeError, hash, obj)
482+
assert type(obj).__hash__ is None
483+
assert TypeWithoutHash.has_hash_not_implemented(obj)
484+
485+
assert_has_no_hash(TypeWithoutHash())
486+
487+
class OverridesEq1:
488+
def __eq__(self, other):
489+
return self is other
490+
491+
assert_has_no_hash(OverridesEq1())
492+
493+
class OverridesEq2(TypeWithHash):
494+
def __eq__(self, other):
495+
return self is other
496+
497+
assert_has_no_hash(OverridesEq2())
498+
499+
class DisablesHash1:
500+
__hash__ = None
501+
502+
assert_has_no_hash(DisablesHash1())
503+
504+
class DisablesHash2(TypeWithHash):
505+
__hash__ = None
506+
507+
assert_has_no_hash(DisablesHash2())
508+
509+
# TODO GR-55196
510+
# TypeWithoutHashExplicit = CPyExtType(
511+
# "TypeWithoutHashExplicit",
512+
# tp_hash='PyObject_HashNotImplemented',
513+
# )
514+
#
515+
# assert_has_no_hash(TypeWithoutHashExplicit())

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/NativeCAPISymbol.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ public enum NativeCAPISymbol implements NativeCExtSymbol {
158158
FUN_MMAP_INIT_BUFFERPROTOCOL("mmap_init_bufferprotocol", ArgDescriptor.Void, PyTypeObject),
159159
FUN_PY_TRUFFLE_CDATA_INIT_BUFFER_PROTOCOL("PyTruffleCData_InitBufferProtocol", ArgDescriptor.Void, PyTypeObject),
160160
FUN_TRUFFLE_CHECK_TYPE_READY("truffle_check_type_ready", ArgDescriptor.Void, PyTypeObject),
161+
FUN_PYOBJECT_HASH_NOT_IMPLEMENTED("PyObject_HashNotImplemented", ArgDescriptor.Py_hash_t, PyObject),
161162

162163
/* PyDateTime_CAPI */
163164

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/attributes/LookupNativeSlotNode.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@
4242

4343
import com.oracle.graal.python.builtins.objects.PNone;
4444
import com.oracle.graal.python.builtins.objects.cext.PythonAbstractNativeObject;
45+
import com.oracle.graal.python.builtins.objects.cext.capi.CApiContext;
46+
import com.oracle.graal.python.builtins.objects.cext.capi.NativeCAPISymbol;
4547
import com.oracle.graal.python.builtins.objects.cext.capi.SlotMethodDef;
4648
import com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess;
4749
import com.oracle.graal.python.builtins.objects.type.PythonAbstractClass;
@@ -138,6 +140,9 @@ private static Object readSlot(SlotMethodDef slot, PythonAbstractClass currentTy
138140
}
139141
Object value = readNode.execute(currentType, slot.methodName);
140142
if (value != PNone.NO_VALUE) {
143+
if (slot == SlotMethodDef.TP_HASH && value == PNone.NONE) {
144+
return CApiContext.getNativeSymbol(null, NativeCAPISymbol.FUN_PYOBJECT_HASH_NOT_IMPLEMENTED);
145+
}
141146
return wrapManagedMethod(slot, (PythonManagedClass) currentType, value);
142147
}
143148
}

0 commit comments

Comments
 (0)