From d911a9420440c54f7a277923290b20e052a311f4 Mon Sep 17 00:00:00 2001 From: Michael Simacek Date: Mon, 27 Oct 2025 12:45:34 +0100 Subject: [PATCH] Fix missing descriptor for __hash__ in __slots__ Fixes #559 --- .../src/tests/test_descr.py | 21 +++++++++++++++- .../python/builtins/objects/type/TpSlots.java | 24 ++++++++----------- .../builtins/objects/type/TypeNodes.java | 6 ++--- 3 files changed, 33 insertions(+), 18 deletions(-) diff --git a/graalpython/com.oracle.graal.python.test/src/tests/test_descr.py b/graalpython/com.oracle.graal.python.test/src/tests/test_descr.py index 941787d092..0cf0d5a4c3 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/test_descr.py +++ b/graalpython/com.oracle.graal.python.test/src/tests/test_descr.py @@ -1,4 +1,4 @@ -# Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # The Universal Permissive License (UPL), Version 1.0 @@ -58,3 +58,22 @@ def test_overwrite___weakref__(): class C: __weakref__ = 1 assert C.__weakref__ == 1 + + +def test___hash___in___slots__(): + class ObjWithoutHash: + def __eq__(self, other): + return True + + assert ObjWithoutHash.__hash__ is None + + class ObjWithHashSlot: + __slots__ = ("__hash__",) + + def __eq__(self, other): + return True + + assert ObjWithHashSlot.__hash__ is not None + o = ObjWithHashSlot() + o.__hash__ = lambda: 1 + assert hash(o) == 1 diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/TpSlots.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/TpSlots.java index 76694f0f76..6993e7b675 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/TpSlots.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/TpSlots.java @@ -167,7 +167,6 @@ import com.oracle.graal.python.builtins.objects.cext.structs.CFields; import com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess.ReadPointerNode; import com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess.WritePointerNode; -import com.oracle.graal.python.builtins.objects.common.HashingStorageNodes.HashingStorageGetItem; import com.oracle.graal.python.builtins.objects.dict.PDict; import com.oracle.graal.python.builtins.objects.function.PBuiltinFunction; import com.oracle.graal.python.builtins.objects.method.PBuiltinMethod; @@ -1435,12 +1434,12 @@ private static void toNative(Object prtToWrite, TpSlotMeta def, Object slotNativ @TruffleBoundary public static void inherit(PythonClass klass, PDict namespace, MroSequenceStorage mro, boolean allocateAllGroups) { - Builder klassSlots = buildInherited(klass, null, mro, allocateAllGroups); + Builder klassSlots = buildInherited(klass, mro, allocateAllGroups); klass.setTpSlots(klassSlots.build()); } @TruffleBoundary - public static TpSlots.Builder buildInherited(PythonClass klass, PDict namespace, MroSequenceStorage mro, boolean allocateAllGroups) { + public static TpSlots.Builder buildInherited(PythonClass klass, MroSequenceStorage mro, boolean allocateAllGroups) { // partially implements CPython:type_ready_inherit // slots of native classes are initialized in GraalPyPrivate_AddInheritedSlots, they are // just a mirror of the native slots initialized and inherited on the native side @@ -1458,7 +1457,7 @@ public static TpSlots.Builder buildInherited(PythonClass klass, PDict namespace, TpSlots slots = GetTpSlotsNode.executeUncached(type); assert slots != null || type == klass; if (slots != null) { - klassSlots.inherit(klass, namespace, slots); + klassSlots.inherit(klass, slots); } } return klassSlots; @@ -1814,7 +1813,7 @@ public Builder overrideIgnoreGroups(TpSlots other) { return this; } - private Builder inherit(PythonClass klass, PDict namespace, TpSlots base) { + private Builder inherit(PythonClass klass, TpSlots base) { // similar to CPython:inherit_slots // indirect slots (from tp_as_number etc.) are not inherited if the group is not // allocated explicitly. Note: native heap types and managed types have always all @@ -1843,7 +1842,7 @@ private Builder inherit(PythonClass klass, PDict namespace, TpSlots base) { set(TpSlotMeta.TP_SETATTRO, base.tp_setattro()); } if (get(TpSlotMeta.TP_RICHCOMPARE) == null && get(TpSlotMeta.TP_HASH) == null) { - if (!overridesHash(namespace)) { + if (!overridesHash(klass)) { set(TpSlotMeta.TP_RICHCOMPARE, base.tp_richcmp()); set(TpSlotMeta.TP_HASH, base.tp_hash()); } @@ -1851,14 +1850,11 @@ private Builder inherit(PythonClass klass, PDict namespace, TpSlots base) { return this; } - private static boolean overridesHash(PDict namespace) { - if (namespace == null) { - return false; - } - Object eq = HashingStorageGetItem.executeUncached(namespace.getDictStorage(), T___EQ__); - if (eq == null) { - Object hash = HashingStorageGetItem.executeUncached(namespace.getDictStorage(), T___HASH__); - return hash != null; + private static boolean overridesHash(PythonClass klass) { + Object eq = klass.getAttribute(T___EQ__); + if (eq == PNone.NO_VALUE) { + Object hash = klass.getAttribute(T___HASH__); + return hash != PNone.NO_VALUE; } return true; } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/TypeNodes.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/TypeNodes.java index f141d80df1..8617b83851 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/TypeNodes.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/TypeNodes.java @@ -2010,11 +2010,11 @@ protected PythonClass makeType(VirtualFrame frame, PDict namespaceOrig, TruffleS // // - fixup_slot_dispatchers to set the slots according to magic methods. - Builder inheritedSlots = TpSlots.buildInherited(newType, namespace, getMroStorageNode.execute(inliningTarget, newType), true); + Builder inheritedSlots = TpSlots.buildInherited(newType, getMroStorageNode.execute(inliningTarget, newType), true); // type_ready_set_hash if (inheritedSlots.get(TpSlotMeta.TP_HASH) == null) { - Object dunderHash = getItemNamespace.execute(inliningTarget, namespace.getDictStorage(), T___HASH__); - if (dunderHash == null) { + Object dunderHash = newType.getAttribute(T___HASH__); + if (dunderHash == NO_VALUE) { inheritedSlots.set(TpSlotMeta.TP_HASH, TpSlotHashFun.HASH_NOT_IMPLEMENTED); newType.setAttribute(T___HASH__, PNone.NONE); }