Skip to content

Commit 34f99e7

Browse files
committed
[GR-10825] fix dict.setdefault
PullRequest: graalpython/116
2 parents a399807 + 67346ff commit 34f99e7

File tree

4 files changed

+134
-27
lines changed

4 files changed

+134
-27
lines changed

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,3 +343,33 @@ def test_dictview_mixed_set_operations():
343343
assert {(1, 1)} == {1: 1}.items()
344344
assert {1: 1}.items() | {2} == {(1, 1), 2}
345345
assert {2} | {1: 1}.items() == {(1, 1), 2}
346+
347+
348+
def test_setdefault():
349+
# dict.setdefault()
350+
d = {}
351+
assert d.setdefault('key0') is None
352+
d.setdefault('key0', [])
353+
assert d.setdefault('key0') is None
354+
d.setdefault('key', []).append(3)
355+
assert d['key'][0] == 3
356+
d.setdefault('key', []).append(4)
357+
assert len(d['key']) == 2
358+
assert_raises(TypeError, d.setdefault)
359+
360+
class Exc(Exception):
361+
pass
362+
363+
class BadHash(object):
364+
fail = False
365+
366+
def __hash__(self):
367+
if self.fail:
368+
raise Exc()
369+
else:
370+
return 42
371+
372+
x = BadHash()
373+
d[x] = 42
374+
x.fail = True
375+
assert_raises(Exc, d.setdefault, x, [])

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/common/HashingStorageNodes.java

Lines changed: 5 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@
7878
import com.oracle.graal.python.nodes.call.special.LookupAndCallUnaryNode;
7979
import com.oracle.graal.python.nodes.control.GetIteratorNode;
8080
import com.oracle.graal.python.nodes.control.GetNextNode;
81+
import com.oracle.graal.python.nodes.datamodel.IsHashableNode;
8182
import com.oracle.graal.python.nodes.expression.BinaryComparisonNode;
8283
import com.oracle.graal.python.nodes.object.GetClassNode;
8384
import com.oracle.graal.python.runtime.exception.PException;
@@ -170,7 +171,7 @@ public static PythonEquivalence create() {
170171
@ImportStatic(PGuards.class)
171172
abstract static class DictStorageBaseNode extends PBaseNode {
172173
@Child private GetClassNode getClassNode;
173-
@Child private LookupAndCallUnaryNode lookupHashAttributeNode;
174+
@Child private IsHashableNode isHashableNode;
174175
@Child private Equivalence equivalenceNode;
175176

176177
protected Equivalence getEquivalence() {
@@ -189,8 +190,6 @@ protected PythonClass getClass(Object object) {
189190
return getClassNode.execute(object);
190191
}
191192

192-
private final ValueProfile keyTypeProfile = ValueProfile.createClassProfile();
193-
194193
protected boolean isJavaString(Object o) {
195194
return o instanceof String || o instanceof PString && wrappedString((PString) o);
196195
}
@@ -205,27 +204,11 @@ protected static Shape lookupShape(DynamicObject receiver) {
205204
}
206205

207206
protected boolean isHashable(Object key) {
208-
Object profiledKey = keyTypeProfile.profile(key);
209-
if (PGuards.isString(profiledKey)) {
210-
return true;
211-
} else if (PGuards.isInteger(profiledKey)) {
212-
return true;
213-
} else if (profiledKey instanceof Double || PGuards.isPFloat(profiledKey)) {
214-
return true;
215-
}
216-
217-
if (lookupHashAttributeNode == null) {
207+
if (isHashableNode == null) {
218208
CompilerDirectives.transferToInterpreterAndInvalidate();
219-
lookupHashAttributeNode = insert(LookupAndCallUnaryNode.create(__HASH__));
209+
isHashableNode = insert(IsHashableNode.create());
220210
}
221-
222-
try {
223-
lookupHashAttributeNode.executeObject(key);
224-
return true;
225-
} catch (PException e) {
226-
// ignore
227-
}
228-
return false;
211+
return isHashableNode.execute(key);
229212
}
230213

231214
protected PException unhashable(Object key) {

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/dict/DictBuiltins.java

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
import com.oracle.truffle.api.dsl.Fallback;
6969
import com.oracle.truffle.api.dsl.GenerateNodeFactory;
7070
import com.oracle.truffle.api.dsl.Specialization;
71+
import com.oracle.truffle.api.profiles.ConditionProfile;
7172

7273
@CoreFunctions(extendClasses = PDict.class)
7374
public final class DictBuiltins extends PythonBuiltins {
@@ -110,7 +111,7 @@ public Object doGeneric(@SuppressWarnings("unused") PDict self, Object[] args, @
110111
}
111112

112113
// setdefault(key[, default])
113-
@Builtin(name = "setdefault", fixedNumOfArguments = 3)
114+
@Builtin(name = "setdefault", minNumOfArguments = 2, keywordArguments = {"default"})
114115
@GenerateNodeFactory
115116
public abstract static class SetDefaultNode extends PythonBuiltinNode {
116117
@Child private HashingStorageNodes.ContainsKeyNode containsKeyNode;
@@ -131,10 +132,14 @@ public Object setDefault(PDict dict, Object key, @SuppressWarnings("unused") Obj
131132

132133
@Specialization(guards = "!containsKey(dict.getDictStorage(), key)")
133134
public Object setDefault(PDict dict, Object key, Object defaultValue,
134-
@Cached("create()") HashingStorageNodes.SetItemNode setItemNode) {
135-
136-
setItemNode.execute(dict, dict.getDictStorage(), key, defaultValue);
137-
return defaultValue;
135+
@Cached("create()") HashingStorageNodes.SetItemNode setItemNode,
136+
@Cached("createBinaryProfile()") ConditionProfile defaultValProfile) {
137+
Object value = defaultValue;
138+
if (defaultValProfile.profile(defaultValue == PNone.NO_VALUE)) {
139+
value = PNone.NONE;
140+
}
141+
setItemNode.execute(dict, dict.getDictStorage(), key, value);
142+
return value;
138143
}
139144
}
140145

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
* Copyright (c) 2018, Oracle and/or its affiliates.
3+
*
4+
* The Universal Permissive License (UPL), Version 1.0
5+
*
6+
* Subject to the condition set forth below, permission is hereby granted to any
7+
* person obtaining a copy of this software, associated documentation and/or data
8+
* (collectively the "Software"), free of charge and under any and all copyright
9+
* rights in the Software, and any and all patent rights owned or freely
10+
* licensable by each licensor hereunder covering either (i) the unmodified
11+
* Software as contributed to or provided by such licensor, or (ii) the Larger
12+
* Works (as defined below), to deal in both
13+
*
14+
* (a) the Software, and
15+
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
16+
* one is included with the Software (each a "Larger Work" to which the
17+
* Software is contributed by such licensors),
18+
*
19+
* without restriction, including without limitation the rights to copy, create
20+
* derivative works of, display, perform, and distribute the Software and make,
21+
* use, sell, offer for sale, import, export, have made, and have sold the
22+
* Software and the Larger Work(s), and to sublicense the foregoing rights on
23+
* either these or other terms.
24+
*
25+
* This license is subject to the following condition:
26+
*
27+
* The above copyright notice and either this complete permission notice or at a
28+
* minimum a reference to the UPL must be included in all copies or substantial
29+
* portions of the Software.
30+
*
31+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
32+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
33+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
34+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
35+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
36+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
37+
* SOFTWARE.
38+
*/
39+
package com.oracle.graal.python.nodes.datamodel;
40+
41+
import com.oracle.graal.python.builtins.PythonBuiltinClassType;
42+
import com.oracle.graal.python.builtins.modules.BuiltinFunctions;
43+
import com.oracle.graal.python.builtins.objects.type.PythonClass;
44+
import com.oracle.graal.python.nodes.PGuards;
45+
import com.oracle.graal.python.nodes.call.special.LookupAndCallUnaryNode;
46+
import com.oracle.graal.python.runtime.exception.PythonErrorType;
47+
import com.oracle.truffle.api.dsl.Cached;
48+
import com.oracle.truffle.api.dsl.Specialization;
49+
50+
public abstract class IsHashableNode extends PDataModelEmulationNode {
51+
protected PythonClass getBuiltinIntType() {
52+
return getCore().lookupType(PythonBuiltinClassType.PInt);
53+
}
54+
55+
protected boolean isDouble(Object object) {
56+
return object instanceof Double || PGuards.isPFloat(object);
57+
}
58+
59+
@Specialization(guards = "isString(object)")
60+
protected boolean isHashableString(@SuppressWarnings("unused") Object object) {
61+
return true;
62+
}
63+
64+
@Specialization(guards = "isInteger(object)")
65+
protected boolean isHashableInt(@SuppressWarnings("unused") Object object) {
66+
return true;
67+
}
68+
69+
@Specialization(guards = "isDouble(object)")
70+
protected boolean isHashableGeneric(@SuppressWarnings("unused") Object object) {
71+
return true;
72+
}
73+
74+
@Specialization
75+
protected boolean isHashableGeneric(Object object,
76+
@Cached("create(__HASH__)") LookupAndCallUnaryNode lookupHashAttributeNode,
77+
@Cached("create()") BuiltinFunctions.IsInstanceNode isInstanceNode,
78+
@Cached("getBuiltinIntType()") PythonClass IntType) {
79+
Object hashValue = lookupHashAttributeNode.executeObject(object);
80+
if (isInstanceNode.executeWith(hashValue, IntType)) {
81+
return true;
82+
}
83+
throw raise(PythonErrorType.TypeError, "__hash__ method should return an integer");
84+
}
85+
86+
public static IsHashableNode create() {
87+
return IsHashableNodeGen.create();
88+
}
89+
}

0 commit comments

Comments
 (0)