Skip to content

Commit ed92774

Browse files
committed
implement __hash__ builtin for tuples
- add new utility node CastToLong - add simple unittest to test for unhashable keys for dicts (i.e., tuples containing nested unhashable objects) - fix hash builtin to raise error if non numeric value is returned by __hash__ - reuse LenNode for sequences
1 parent 1557a28 commit ed92774

File tree

6 files changed

+220
-28
lines changed

6 files changed

+220
-28
lines changed

graalpython/com.oracle.graal.python.test/src/tests/test_dict.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
1+
# Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
22
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
33
#
44
# The Universal Permissive License (UPL), Version 1.0
@@ -449,3 +449,11 @@ def bar():
449449
bar.a = 'a'
450450
assert 1 in bar.__dict__
451451
assert 'a' in bar.__dict__
452+
453+
454+
def test_unhashable_key():
455+
d = {}
456+
key_list = [10, 11]
457+
assert_raises(TypeError, lambda: d[key_list])
458+
key_tuple_list = (key_list, 2)
459+
assert_raises(TypeError, lambda: d[key_tuple_list])

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/BuiltinFunctions.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2017, 2018, Oracle and/or its affiliates.
2+
* Copyright (c) 2017, 2019, Oracle and/or its affiliates.
33
* Copyright (c) 2013, Regents of the University of California
44
*
55
* All rights reserved.
@@ -432,9 +432,14 @@ protected boolean isPException(Object object) {
432432
public Object hash(Object object,
433433
@Cached("create(__DIR__)") LookupInheritedAttributeNode lookupDirNode,
434434
@Cached("create(__HASH__)") LookupAndCallUnaryNode dispatchHash,
435-
@Cached("createIfTrueNode()") CastToBooleanNode trueNode) {
435+
@Cached("createIfTrueNode()") CastToBooleanNode trueNode,
436+
@Cached("create()") IsInstanceNode isInstanceNode) {
436437
if (trueNode.executeWith(lookupDirNode.execute(object))) {
437-
return dispatchHash.executeObject(object);
438+
Object hashValue = dispatchHash.executeObject(object);
439+
if (isInstanceNode.executeWith(hashValue, getBuiltinPythonClass(PythonBuiltinClassType.PInt))) {
440+
return hashValue;
441+
}
442+
throw raise(PythonErrorType.TypeError, "__hash__ method should return an integer");
438443
}
439444
return object.hashCode();
440445
}

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/tuple/TupleBuiltins.java

Lines changed: 79 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2017, 2018, Oracle and/or its affiliates.
2+
* Copyright (c) 2017, 2019, Oracle and/or its affiliates.
33
* Copyright (c) 2014, Regents of the University of California
44
*
55
* All rights reserved.
@@ -25,7 +25,22 @@
2525
*/
2626
package com.oracle.graal.python.builtins.objects.tuple;
2727

28+
import static com.oracle.graal.python.nodes.SpecialMethodNames.__ADD__;
29+
import static com.oracle.graal.python.nodes.SpecialMethodNames.__BOOL__;
30+
import static com.oracle.graal.python.nodes.SpecialMethodNames.__CONTAINS__;
31+
import static com.oracle.graal.python.nodes.SpecialMethodNames.__EQ__;
32+
import static com.oracle.graal.python.nodes.SpecialMethodNames.__GETITEM__;
33+
import static com.oracle.graal.python.nodes.SpecialMethodNames.__GE__;
34+
import static com.oracle.graal.python.nodes.SpecialMethodNames.__GT__;
35+
import static com.oracle.graal.python.nodes.SpecialMethodNames.__HASH__;
36+
import static com.oracle.graal.python.nodes.SpecialMethodNames.__ITER__;
37+
import static com.oracle.graal.python.nodes.SpecialMethodNames.__LEN__;
38+
import static com.oracle.graal.python.nodes.SpecialMethodNames.__LE__;
39+
import static com.oracle.graal.python.nodes.SpecialMethodNames.__LT__;
40+
import static com.oracle.graal.python.nodes.SpecialMethodNames.__MUL__;
41+
import static com.oracle.graal.python.nodes.SpecialMethodNames.__NE__;
2842
import static com.oracle.graal.python.nodes.SpecialMethodNames.__REPR__;
43+
import static com.oracle.graal.python.nodes.SpecialMethodNames.__RMUL__;
2944
import static com.oracle.graal.python.runtime.exception.PythonErrorType.TypeError;
3045

3146
import java.math.BigInteger;
@@ -47,7 +62,6 @@
4762
import com.oracle.graal.python.builtins.objects.slice.PSlice;
4863
import com.oracle.graal.python.builtins.objects.str.PString;
4964
import com.oracle.graal.python.builtins.objects.tuple.TupleBuiltinsFactory.IndexNodeFactory;
50-
import com.oracle.graal.python.nodes.SpecialMethodNames;
5165
import com.oracle.graal.python.nodes.argument.ReadArgumentNode;
5266
import com.oracle.graal.python.nodes.call.special.LookupAndCallUnaryNode;
5367
import com.oracle.graal.python.nodes.expression.BinaryComparisonNode;
@@ -56,6 +70,7 @@
5670
import com.oracle.graal.python.nodes.function.builtins.PythonBinaryBuiltinNode;
5771
import com.oracle.graal.python.nodes.function.builtins.PythonUnaryBuiltinNode;
5872
import com.oracle.graal.python.nodes.truffle.PythonArithmeticTypes;
73+
import com.oracle.graal.python.nodes.util.CastToJavaLongNode;
5974
import com.oracle.graal.python.runtime.exception.PythonErrorType;
6075
import com.oracle.graal.python.runtime.sequence.storage.SequenceStorage;
6176
import com.oracle.truffle.api.CompilerDirectives;
@@ -263,7 +278,7 @@ long count(PTuple self, Object value,
263278
}
264279
}
265280

266-
@Builtin(name = SpecialMethodNames.__LEN__, fixedNumOfPositionalArgs = 1)
281+
@Builtin(name = __LEN__, fixedNumOfPositionalArgs = 1)
267282
@GenerateNodeFactory
268283
public abstract static class LenNode extends PythonUnaryBuiltinNode {
269284
@Specialization
@@ -294,10 +309,11 @@ public String toString(Object item, BuiltinFunctions.ReprNode reprNode) {
294309
@Specialization
295310
@TruffleBoundary
296311
public String repr(PTuple self,
312+
@Cached("create()") SequenceStorageNodes.LenNode getLen,
297313
@Cached("createNotNormalized()") SequenceStorageNodes.GetItemNode getItemNode,
298314
@Cached("createRepr()") BuiltinFunctions.ReprNode reprNode) {
299315
SequenceStorage tupleStore = self.getSequenceStorage();
300-
int len = tupleStore.length();
316+
int len = getLen.execute(tupleStore);
301317
StringBuilder buf = new StringBuilder();
302318
append(buf, "(");
303319
for (int i = 0; i < len - 1; i++) {
@@ -332,7 +348,7 @@ protected static BuiltinFunctions.ReprNode createRepr() {
332348
}
333349
}
334350

335-
@Builtin(name = SpecialMethodNames.__GETITEM__, fixedNumOfPositionalArgs = 2)
351+
@Builtin(name = __GETITEM__, fixedNumOfPositionalArgs = 2)
336352
@ImportStatic(MathGuards.class)
337353
@TypeSystemReference(PythonArithmeticTypes.class)
338354
@GenerateNodeFactory
@@ -363,7 +379,7 @@ protected boolean isPSlice(Object object) {
363379
}
364380
}
365381

366-
@Builtin(name = SpecialMethodNames.__EQ__, fixedNumOfPositionalArgs = 2)
382+
@Builtin(name = __EQ__, fixedNumOfPositionalArgs = 2)
367383
@GenerateNodeFactory
368384
abstract static class EqNode extends PythonBinaryBuiltinNode {
369385

@@ -380,7 +396,7 @@ Object doOther(Object left, Object right) {
380396
}
381397
}
382398

383-
@Builtin(name = SpecialMethodNames.__NE__, fixedNumOfPositionalArgs = 2)
399+
@Builtin(name = __NE__, fixedNumOfPositionalArgs = 2)
384400
@GenerateNodeFactory
385401
abstract static class NeNode extends PythonBinaryBuiltinNode {
386402

@@ -397,7 +413,7 @@ boolean doOther(Object left, Object right) {
397413
}
398414
}
399415

400-
@Builtin(name = SpecialMethodNames.__GE__, fixedNumOfPositionalArgs = 2)
416+
@Builtin(name = __GE__, fixedNumOfPositionalArgs = 2)
401417
@GenerateNodeFactory
402418
abstract static class GeNode extends PythonBinaryBuiltinNode {
403419

@@ -415,7 +431,7 @@ PNotImplemented doOther(Object left, Object right) {
415431

416432
}
417433

418-
@Builtin(name = SpecialMethodNames.__LE__, fixedNumOfPositionalArgs = 2)
434+
@Builtin(name = __LE__, fixedNumOfPositionalArgs = 2)
419435
@GenerateNodeFactory
420436
abstract static class LeNode extends PythonBinaryBuiltinNode {
421437

@@ -433,7 +449,7 @@ PNotImplemented doOther(Object left, Object right) {
433449

434450
}
435451

436-
@Builtin(name = SpecialMethodNames.__GT__, fixedNumOfPositionalArgs = 2)
452+
@Builtin(name = __GT__, fixedNumOfPositionalArgs = 2)
437453
@GenerateNodeFactory
438454
abstract static class GtNode extends PythonBinaryBuiltinNode {
439455

@@ -449,7 +465,7 @@ PNotImplemented doOther(@SuppressWarnings("unused") Object left, @SuppressWarnin
449465
}
450466
}
451467

452-
@Builtin(name = SpecialMethodNames.__LT__, fixedNumOfPositionalArgs = 2)
468+
@Builtin(name = __LT__, fixedNumOfPositionalArgs = 2)
453469
@GenerateNodeFactory
454470
abstract static class LtNode extends PythonBinaryBuiltinNode {
455471
@Specialization
@@ -464,7 +480,7 @@ PNotImplemented contains(@SuppressWarnings("unused") Object self, @SuppressWarni
464480
}
465481
}
466482

467-
@Builtin(name = SpecialMethodNames.__ADD__, fixedNumOfPositionalArgs = 2)
483+
@Builtin(name = __ADD__, fixedNumOfPositionalArgs = 2)
468484
@GenerateNodeFactory
469485
abstract static class AddNode extends PythonBuiltinNode {
470486
@Specialization
@@ -480,7 +496,7 @@ Object doGeneric(@SuppressWarnings("unused") Object left, Object right) {
480496
}
481497
}
482498

483-
@Builtin(name = SpecialMethodNames.__MUL__, fixedNumOfPositionalArgs = 2)
499+
@Builtin(name = __MUL__, fixedNumOfPositionalArgs = 2)
484500
@GenerateNodeFactory
485501
abstract static class MulNode extends PythonBuiltinNode {
486502
@Specialization
@@ -490,12 +506,12 @@ PTuple mul(PTuple left, Object right,
490506
}
491507
}
492508

493-
@Builtin(name = SpecialMethodNames.__RMUL__, fixedNumOfPositionalArgs = 2)
509+
@Builtin(name = __RMUL__, fixedNumOfPositionalArgs = 2)
494510
@GenerateNodeFactory
495511
abstract static class RMulNode extends MulNode {
496512
}
497513

498-
@Builtin(name = SpecialMethodNames.__CONTAINS__, fixedNumOfPositionalArgs = 2)
514+
@Builtin(name = __CONTAINS__, fixedNumOfPositionalArgs = 2)
499515
@GenerateNodeFactory
500516
abstract static class ContainsNode extends PythonBinaryBuiltinNode {
501517
@Specialization
@@ -506,7 +522,7 @@ boolean contains(PTuple self, Object other,
506522

507523
}
508524

509-
@Builtin(name = SpecialMethodNames.__BOOL__, fixedNumOfPositionalArgs = 1)
525+
@Builtin(name = __BOOL__, fixedNumOfPositionalArgs = 1)
510526
@GenerateNodeFactory
511527
public abstract static class BoolNode extends PythonUnaryBuiltinNode {
512528
@Specialization
@@ -521,7 +537,7 @@ Object toBoolean(@SuppressWarnings("unused") Object self) {
521537
}
522538
}
523539

524-
@Builtin(name = SpecialMethodNames.__ITER__, fixedNumOfPositionalArgs = 1)
540+
@Builtin(name = __ITER__, fixedNumOfPositionalArgs = 1)
525541
@GenerateNodeFactory
526542
public abstract static class IterNode extends PythonUnaryBuiltinNode {
527543
@Specialization
@@ -534,4 +550,50 @@ Object doGeneric(@SuppressWarnings("unused") Object self) {
534550
return PNotImplemented.NOT_IMPLEMENTED;
535551
}
536552
}
553+
554+
@Builtin(name = __HASH__, fixedNumOfPositionalArgs = 1)
555+
@GenerateNodeFactory
556+
public abstract static class HashNode extends PythonUnaryBuiltinNode {
557+
@Specialization
558+
public long tupleHash(PTuple self,
559+
@Cached("create()") SequenceStorageNodes.LenNode getLen,
560+
@Cached("createNotNormalized()") SequenceStorageNodes.GetItemNode getItemNode,
561+
@Cached("create(__HASH__)") LookupAndCallUnaryNode lookupHashAttributeNode,
562+
@Cached("create()") BuiltinFunctions.IsInstanceNode isInstanceNode,
563+
@Cached("createLossy()") CastToJavaLongNode castToLongNode) {
564+
// adapted from https://github.com/python/cpython/blob/v3.6.5/Objects/tupleobject.c#L345
565+
SequenceStorage tupleStore = self.getSequenceStorage();
566+
int len = getLen.execute(tupleStore);
567+
long multiplier = 0xf4243;
568+
long x = 0x345678;
569+
long y;
570+
for (int i = 0; i < len; i++) {
571+
Object item = getItemNode.execute(tupleStore, i);
572+
Object hashValue = lookupHashAttributeNode.executeObject(item);
573+
if (!isInstanceNode.executeWith(hashValue, getBuiltinPythonClass(PythonBuiltinClassType.PInt))) {
574+
throw raise(PythonErrorType.TypeError, "__hash__ method should return an integer");
575+
}
576+
y = castToLongNode.execute(hashValue);
577+
if (y == -1) {
578+
return -1;
579+
}
580+
581+
x = (x ^ y) * multiplier;
582+
multiplier += 82520 + len + len;
583+
}
584+
585+
x += 97531;
586+
587+
if (x == Long.MAX_VALUE) {
588+
x = -2;
589+
}
590+
591+
return x;
592+
}
593+
594+
@Fallback
595+
Object genericHash(@SuppressWarnings("unused") Object self) {
596+
return PNotImplemented.NOT_IMPLEMENTED;
597+
}
598+
}
537599
}

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/PNodeWithContext.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -127,6 +127,10 @@ public final PythonClass getPythonClass(LazyPythonClass lazyClass, ConditionProf
127127
}
128128
}
129129

130+
public final PythonClass getBuiltinPythonClass(PythonBuiltinClassType type) {
131+
return getCore().lookupType(type);
132+
}
133+
130134
public final PythonContext getContext() {
131135
if (contextRef == null) {
132136
CompilerDirectives.transferToInterpreterAndInvalidate();

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/datamodel/IsHashableNode.java

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -42,17 +42,13 @@
4242

4343
import com.oracle.graal.python.builtins.PythonBuiltinClassType;
4444
import com.oracle.graal.python.builtins.modules.BuiltinFunctions;
45-
import com.oracle.graal.python.builtins.objects.type.PythonClass;
4645
import com.oracle.graal.python.nodes.PGuards;
4746
import com.oracle.graal.python.nodes.call.special.LookupAndCallUnaryNode;
4847
import com.oracle.graal.python.runtime.exception.PythonErrorType;
4948
import com.oracle.truffle.api.dsl.Cached;
5049
import com.oracle.truffle.api.dsl.Specialization;
5150

5251
public abstract class IsHashableNode extends PDataModelEmulationNode {
53-
protected PythonClass getBuiltinIntType() {
54-
return getCore().lookupType(PythonBuiltinClassType.PInt);
55-
}
5652

5753
protected boolean isDouble(Object object) {
5854
return object instanceof Double || PGuards.isPFloat(object);
@@ -78,7 +74,7 @@ protected boolean isHashableGeneric(Object object,
7874
@Cached("create(__HASH__)") LookupAndCallUnaryNode lookupHashAttributeNode,
7975
@Cached("create()") BuiltinFunctions.IsInstanceNode isInstanceNode) {
8076
Object hashValue = lookupHashAttributeNode.executeObject(object);
81-
if (isInstanceNode.executeWith(hashValue, getBuiltinIntType())) {
77+
if (isInstanceNode.executeWith(hashValue, getBuiltinPythonClass(PythonBuiltinClassType.PInt))) {
8278
return true;
8379
}
8480
throw raise(PythonErrorType.TypeError, "__hash__ method should return an integer");

0 commit comments

Comments
 (0)