Skip to content

Commit 7ee2bba

Browse files
committed
[GR-11971] set __dict__
PullRequest: graalpython/214
2 parents 2e85ea3 + 669a02b commit 7ee2bba

16 files changed

+807
-237
lines changed
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
# Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
2+
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
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
8+
# data (collectively the "Software"), free of charge and under any and all
9+
# copyright rights in the Software, and any and all patent rights owned or
10+
# freely licensable by each licensor hereunder covering either (i) the
11+
# unmodified Software as contributed to or provided by such licensor, or (ii)
12+
# the Larger Works (as defined below), to deal in both
13+
#
14+
# (a) the Software, and
15+
#
16+
# (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
17+
# one is included with the Software each a "Larger Work" to which the Software
18+
# is contributed by such licensors),
19+
#
20+
# without restriction, including without limitation the rights to copy, create
21+
# derivative works of, display, perform, and distribute the Software and make,
22+
# use, sell, offer for sale, import, export, have made, and have sold the
23+
# Software and the Larger Work(s), and to sublicense the foregoing rights on
24+
# either these or other terms.
25+
#
26+
# This license is subject to the following condition:
27+
#
28+
# The above copyright notice and either this complete permission notice or at a
29+
# minimum a reference to the UPL must be included in all copies or substantial
30+
# portions of the Software.
31+
#
32+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
33+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
34+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
35+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
36+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
37+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
38+
# SOFTWARE.
39+
40+
41+
def assert_raises(err, fn, *args, **kwargs):
42+
raised = False
43+
try:
44+
fn(*args, **kwargs)
45+
except err:
46+
raised = True
47+
assert raised
48+
49+
50+
def test_set_dict_attr_builtin_extension():
51+
class MyList(list):
52+
pass
53+
54+
lst = MyList()
55+
assert lst.__dict__ == {}
56+
lst.__dict__ = {'a': 9}
57+
assert lst.a == 9
58+
assert lst.__dict__ == {'a': 9}
59+
60+
61+
def test_get_dict_attr():
62+
o = object()
63+
64+
def get_dict_attr():
65+
return o.__dict__
66+
67+
def set_dict_attr():
68+
o.__dict__ = {'a': 10}
69+
70+
assert_raises(AttributeError, get_dict_attr)
71+
assert_raises(AttributeError, set_dict_attr)
72+
73+
74+
def test_set_dict_attr():
75+
class MyClass(object):
76+
def __init__(self):
77+
self.a = 9
78+
79+
m = MyClass()
80+
assert m.a == 9
81+
assert m.__dict__ == {'a': 9}
82+
assert m.a == 9
83+
m.__dict__ = {'a': 10}
84+
assert m.__dict__ == {'a': 10}
85+
assert m.a == 10
86+
m.d = 20
87+
assert m.d == 20
88+
assert "d" in m.__dict__
89+
assert m.__dict__ == {'a': 10, 'd': 20}
90+
91+
92+
def test_set_attr_builtins():
93+
lst = list()
94+
95+
def set_attr():
96+
lst.a = 10
97+
98+
assert_raises(AttributeError, set_attr)
99+
100+
class MyList(list):
101+
pass
102+
103+
mlst = MyList()
104+
mlst.a = 10
105+
assert mlst.a == 10
106+
107+
108+
def test_set_dict_attr_with_getattr_defined():
109+
class MyOtherClass(object):
110+
def __getattribute__(self, item):
111+
return object.__getattribute__(self, item)
112+
113+
def __getattr__(self, item):
114+
if item == "my_attr":
115+
return 10
116+
raise AttributeError
117+
118+
m1 = MyOtherClass()
119+
120+
def get_non_existing_attr():
121+
return m1.my_attr_2
122+
123+
assert_raises(AttributeError, get_non_existing_attr)
124+
assert m1.my_attr == 10
125+
assert "my_attr" not in m1.__dict__
126+
127+
m1.__dict__ = {'d': 10}
128+
assert m1.my_attr == 10
129+
assert "my_attr" not in m1.__dict__
130+
assert m1.d == 10

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import com.oracle.graal.python.builtins.objects.common.DynamicObjectStorage.PythonObjectDictStorage;
4646
import com.oracle.graal.python.builtins.objects.str.PString;
4747
import com.oracle.graal.python.builtins.objects.type.PythonClass;
48+
import com.oracle.truffle.api.Assumption;
4849
import com.oracle.truffle.api.CompilerAsserts;
4950
import com.oracle.truffle.api.interop.ForeignAccess;
5051
import com.oracle.truffle.api.interop.TruffleObject;
@@ -113,8 +114,12 @@ public DynamicObjectNativeWrapper(Object delegate) {
113114
}
114115

115116
public PythonObjectDictStorage createNativeMemberStore() {
117+
return createNativeMemberStore(null);
118+
}
119+
120+
public PythonObjectDictStorage createNativeMemberStore(Assumption dictStableAssumption) {
116121
if (nativeMemberStore == null) {
117-
nativeMemberStore = new PythonObjectDictStorage(SHAPE.newInstance());
122+
nativeMemberStore = new PythonObjectDictStorage(SHAPE.newInstance(), dictStableAssumption);
118123
}
119124
return nativeMemberStore;
120125
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -680,7 +680,7 @@ Object doTpSubclasses(PythonClass object, @SuppressWarnings("unused") String key
680680
Object doMdDef(PythonObject object, @SuppressWarnings("unused") String key, Object value) {
681681
DynamicObjectNativeWrapper nativeWrapper = ((PythonAbstractObject) object).getNativeWrapper();
682682
assert nativeWrapper != null;
683-
getSetItemNode().execute(nativeWrapper.createNativeMemberStore(), NativeMemberNames.MD_DEF, value);
683+
getSetItemNode().execute(nativeWrapper.createNativeMemberStore(object.getDictUnsetOrSameAsStorageAssumption()), NativeMemberNames.MD_DEF, value);
684684
return value;
685685
}
686686

@@ -701,7 +701,7 @@ Object doTpDict(PythonClass object, @SuppressWarnings("unused") String key, Obje
701701
if (existing != null) {
702702
d.setDictStorage(existing.getDictStorage());
703703
} else {
704-
d.setDictStorage(new DynamicObjectStorage.PythonObjectDictStorage(object.getStorage()));
704+
d.setDictStorage(new DynamicObjectStorage.PythonObjectDictStorage(object.getStorage(), object.getDictUnsetOrSameAsStorageAssumption()));
705705
}
706706
object.setDict(d);
707707
} else {

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

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

4343
import java.util.ArrayList;
4444

45+
import com.oracle.truffle.api.Assumption;
4546
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
4647
import com.oracle.truffle.api.object.DynamicObject;
4748
import com.oracle.truffle.api.object.Layout;
@@ -159,8 +160,19 @@ public HashingStorage copy(Equivalence eq) {
159160
}
160161

161162
public static class PythonObjectDictStorage extends DynamicObjectStorage {
163+
private final Assumption dictUnsetOrSameAsStorage;
164+
162165
public PythonObjectDictStorage(DynamicObject store) {
166+
this(store, null);
167+
}
168+
169+
public PythonObjectDictStorage(DynamicObject store, Assumption dictUnsetOrSameAsStorage) {
163170
super(store);
171+
this.dictUnsetOrSameAsStorage = dictUnsetOrSameAsStorage;
172+
}
173+
174+
public Assumption getDictUnsetOrSameAsStorage() {
175+
return dictUnsetOrSameAsStorage;
164176
}
165177

166178
@Override

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@
9090
import com.oracle.graal.python.runtime.exception.PException;
9191
import com.oracle.graal.python.runtime.exception.PythonErrorType;
9292
import com.oracle.graal.python.runtime.sequence.PSequence;
93+
import com.oracle.truffle.api.Assumption;
9394
import com.oracle.truffle.api.CompilerAsserts;
9495
import com.oracle.truffle.api.CompilerDirectives;
9596
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
@@ -240,6 +241,10 @@ protected static DynamicObjectStorage switchToFastDictStorage(HashingStorage sto
240241
}
241242

242243
protected static PythonObjectHybridDictStorage switchToHybridDictStorage(PythonObjectDictStorage dictStorage) {
244+
Assumption dictUnsetOrSameAsStorage = dictStorage.getDictUnsetOrSameAsStorage();
245+
if (dictUnsetOrSameAsStorage != null) {
246+
dictUnsetOrSameAsStorage.invalidate();
247+
}
243248
return new PythonObjectHybridDictStorage(dictStorage);
244249
}
245250

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/getsetdescriptor/GetSetDescriptorTypeBuiltins.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ Object set(GetSetDescriptor descr, Object obj, Object value) {
158158
return callNode.executeObject(descr.getSet(), obj, value);
159159
} else {
160160
branchProfile.enter();
161-
throw raise(AttributeError, "attribute '%s' of '%s' objects is not writable", descr.getName(), descr.getType().getName());
161+
throw raise(AttributeError, "attribute '%s' of '%s' object is not writable", descr.getName(), descr.getType().getName());
162162
}
163163
}
164164
}

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/object/ObjectBuiltins.java

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
import com.oracle.graal.python.builtins.objects.cext.PythonNativeClass;
5959
import com.oracle.graal.python.builtins.objects.cext.PythonNativeObject;
6060
import com.oracle.graal.python.builtins.objects.common.PHashingCollection;
61+
import com.oracle.graal.python.builtins.objects.dict.PDict;
6162
import com.oracle.graal.python.builtins.objects.function.PBuiltinFunction;
6263
import com.oracle.graal.python.builtins.objects.function.PKeyword;
6364
import com.oracle.graal.python.builtins.objects.function.PythonCallable;
@@ -80,6 +81,7 @@
8081
import com.oracle.graal.python.nodes.function.builtins.PythonVarargsBuiltinNode;
8182
import com.oracle.graal.python.nodes.object.GetClassNode;
8283
import com.oracle.graal.python.nodes.object.GetLazyClassNode;
84+
import com.oracle.graal.python.nodes.object.IsBuiltinClassProfile;
8385
import com.oracle.truffle.api.CompilerDirectives;
8486
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
8587
import com.oracle.truffle.api.dsl.Cached;
@@ -402,7 +404,7 @@ private LazyPythonClass getDataClass(Object descr) {
402404
public abstract static class GetattrNode extends PythonBinaryBuiltinNode {
403405
@Specialization
404406
Object getattr(Object object, Object key) {
405-
throw raise(AttributeError, "'%p' object has no attribute %s", object, key);
407+
throw raise(AttributeError, "'%p' object has no attribute '%s'", object, key);
406408
}
407409
}
408410

@@ -469,22 +471,28 @@ protected PNone doIt(Object object, Object key,
469471
if (descr != PNone.NO_VALUE) {
470472
throw raise(AttributeError, "attribute % is read-only", key);
471473
} else {
472-
throw raise(AttributeError, "%s object has no attribute %s", object, key);
474+
throw raise(AttributeError, "%s object has no attribute '%s'", object, key);
473475
}
474476
}
475477
}
476478

477-
@Builtin(name = __DICT__, fixedNumOfPositionalArgs = 1, isGetter = true)
479+
@Builtin(name = __DICT__, minNumOfPositionalArgs = 1, maxNumOfPositionalArgs = 2, isGetter = true, isSetter = true)
478480
@GenerateNodeFactory
479-
static abstract class DictNode extends PythonUnaryBuiltinNode {
480-
@Specialization
481-
Object dict(@SuppressWarnings("unused") PythonClass self) {
482-
CompilerDirectives.transferToInterpreter();
483-
throw new AssertionError();
481+
static abstract class DictNode extends PythonBinaryBuiltinNode {
482+
private final IsBuiltinClassProfile exactObjInstanceProfile = IsBuiltinClassProfile.create();
483+
private final IsBuiltinClassProfile exactBuiltinInstanceProfile = IsBuiltinClassProfile.create();
484+
485+
protected boolean isExactObjectInstance(PythonObject self) {
486+
return exactObjInstanceProfile.profileObject(self, PythonBuiltinClassType.PythonObject);
487+
}
488+
489+
protected boolean isBuiltinObjectExact(PythonObject self) {
490+
// any builtin class except Modules
491+
return exactBuiltinInstanceProfile.profileIsOtherBuiltinObject(self, PythonBuiltinClassType.PythonModule);
484492
}
485493

486-
@Specialization(guards = {"!isBuiltinObject(self)", "!isClass(self)"})
487-
Object dict(PythonObject self) {
494+
@Specialization(guards = {"!isBuiltinObjectExact(self)", "!isClass(self)", "!isExactObjectInstance(self)", "isNoValue(none)"})
495+
Object dict(PythonObject self, @SuppressWarnings("unused") PNone none) {
488496
PHashingCollection dict = self.getDict();
489497
if (dict == null) {
490498
dict = factory().createDictFixedStorage(self);
@@ -493,8 +501,15 @@ Object dict(PythonObject self) {
493501
return dict;
494502
}
495503

504+
@Specialization(guards = {"!isBuiltinObjectExact(self)", "!isClass(self)", "!isExactObjectInstance(self)"})
505+
Object dict(PythonObject self, PDict dict) {
506+
self.getDictUnsetOrSameAsStorageAssumption().invalidate();
507+
self.setDict(dict);
508+
return PNone.NONE;
509+
}
510+
496511
@Fallback
497-
Object dict(Object self) {
512+
Object dict(Object self, @SuppressWarnings("unused") Object dict) {
498513
throw raise(AttributeError, "'%p' object has no attribute '__dict__'", self);
499514
}
500515

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/object/PythonObject.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@
3939
import com.oracle.truffle.api.Assumption;
4040
import com.oracle.truffle.api.CompilerAsserts;
4141
import com.oracle.truffle.api.CompilerDirectives;
42-
import com.oracle.truffle.api.Truffle;
4342
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
4443
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
44+
import com.oracle.truffle.api.Truffle;
4545
import com.oracle.truffle.api.object.DynamicObject;
4646
import com.oracle.truffle.api.object.Location;
4747
import com.oracle.truffle.api.object.Property;
@@ -50,6 +50,7 @@
5050
public class PythonObject extends PythonAbstractObject {
5151
@CompilationFinal private LazyPythonClass pythonClass;
5252
private final Assumption classStable = Truffle.getRuntime().createAssumption("class unchanged");
53+
private final Assumption dictUnsetOrSameAsStorage = Truffle.getRuntime().createAssumption("dict unset or same as instance attributes");
5354
private final DynamicObject storage;
5455
private PHashingCollection dict;
5556

@@ -103,6 +104,10 @@ public final Assumption getClassStableAssumption() {
103104
return classStable;
104105
}
105106

107+
public final Assumption getDictUnsetOrSameAsStorageAssumption() {
108+
return dictUnsetOrSameAsStorage;
109+
}
110+
106111
public final DynamicObject getStorage() {
107112
return storage;
108113
}

0 commit comments

Comments
 (0)