Skip to content

Commit 422a51b

Browse files
committed
[GR-12232] Make object.__class__ assignable for and with user types
PullRequest: graalpython/249
2 parents acaaac0 + c411dbf commit 422a51b

File tree

18 files changed

+169
-41
lines changed

18 files changed

+169
-41
lines changed

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

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,3 +116,42 @@ class C(B):
116116
C.foo = 10
117117
assert C.foo == 10
118118

119+
120+
def test_class_assignment():
121+
class A:
122+
foo = 1
123+
124+
class B(A):
125+
foo = 2
126+
127+
a = A()
128+
assert a.foo == 1
129+
a.__class__ = B
130+
assert a.foo == 2
131+
b = B()
132+
assert b.foo == 2
133+
b.__class__ = A
134+
assert b.foo == 1
135+
assert type(a) == B
136+
assert type(b) == A
137+
138+
try:
139+
a.__class__ = 1
140+
except TypeError:
141+
assert True
142+
else:
143+
assert False
144+
145+
try:
146+
a.__class__ = object
147+
except TypeError:
148+
assert True
149+
else:
150+
assert False
151+
152+
try:
153+
object().__class__ = object
154+
except TypeError:
155+
assert True
156+
else:
157+
assert False

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
import com.oracle.graal.python.builtins.objects.str.PString;
5959
import com.oracle.graal.python.nodes.function.PythonBuiltinBaseNode;
6060
import com.oracle.graal.python.nodes.function.PythonBuiltinNode;
61+
import com.oracle.graal.python.nodes.object.GetClassNode;
6162
import com.oracle.graal.python.runtime.PythonContext;
6263
import com.oracle.graal.python.runtime.PythonCore;
6364
import com.oracle.graal.python.runtime.PythonOptions;
@@ -75,7 +76,6 @@
7576
import com.oracle.truffle.api.frame.VirtualFrame;
7677
import com.oracle.truffle.api.nodes.DirectCallNode;
7778
import com.oracle.truffle.api.nodes.RootNode;
78-
import com.oracle.truffle.api.profiles.ConditionProfile;
7979

8080
@CoreFunctions(defineModule = "sys")
8181
public class SysModuleBuiltins extends PythonBuiltins {
@@ -192,15 +192,15 @@ public void initialize(PythonCore core) {
192192
public static abstract class ExcInfoNode extends PythonBuiltinNode {
193193
@Specialization
194194
public Object run(
195-
@Cached("createBinaryProfile()") ConditionProfile getClassProfile) {
195+
@Cached("create()") GetClassNode getClassNode) {
196196
PythonContext context = getContext();
197197
PException currentException = context.getCurrentException();
198198
if (currentException == null) {
199199
return factory().createTuple(new PNone[]{PNone.NONE, PNone.NONE, PNone.NONE});
200200
} else {
201201
PBaseException exception = currentException.getExceptionObject();
202202
exception.reifyException();
203-
return factory().createTuple(new Object[]{getPythonClass(exception.getLazyPythonClass(), getClassProfile), exception, exception.getTraceback(factory())});
203+
return factory().createTuple(new Object[]{getClassNode.execute(exception), exception, exception.getTraceback(factory())});
204204
}
205205
}
206206
}

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -362,11 +362,12 @@ Object run(@SuppressWarnings("unused") PythonClass typ, PBaseException val, @Sup
362362
abstract static class PyErrOccurred extends PythonUnaryBuiltinNode {
363363
@Specialization
364364
Object run(Object errorMarker,
365-
@Cached("createBinaryProfile()") ConditionProfile getClassProfile) {
365+
@Cached("create()") GetClassNode getClass) {
366366
PException currentException = getContext().getCurrentException();
367367
if (currentException != null) {
368-
currentException.getExceptionObject().reifyException();
369-
return getPythonClass(currentException.getExceptionObject().getLazyPythonClass(), getClassProfile);
368+
PBaseException exceptionObject = currentException.getExceptionObject();
369+
exceptionObject.reifyException();
370+
return getClass.execute(exceptionObject);
370371
}
371372
return errorMarker;
372373
}

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/bytes/ByteArrayBuiltins.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
import com.oracle.graal.python.nodes.function.builtins.PythonBinaryBuiltinNode;
7373
import com.oracle.graal.python.nodes.function.builtins.PythonTernaryBuiltinNode;
7474
import com.oracle.graal.python.nodes.function.builtins.PythonUnaryBuiltinNode;
75+
import com.oracle.graal.python.nodes.object.GetLazyClassNode;
7576
import com.oracle.graal.python.nodes.truffle.PythonArithmeticTypes;
7677
import com.oracle.graal.python.runtime.sequence.PSequence;
7778
import com.oracle.graal.python.runtime.sequence.storage.ByteSequenceStorage;
@@ -336,10 +337,11 @@ protected boolean isPSequenceWithStorage(Object source) {
336337
@Builtin(name = "copy", fixedNumOfPositionalArgs = 1)
337338
@GenerateNodeFactory
338339
public abstract static class ByteArrayCopyNode extends PythonBuiltinNode {
339-
340340
@Specialization
341-
public PByteArray copy(PByteArray byteArray) {
342-
return byteArray.copy();
341+
public PByteArray copy(PByteArray byteArray,
342+
@Cached("create()") GetLazyClassNode getClass,
343+
@Cached("create()") SequenceStorageNodes.ToByteArrayNode toByteArray) {
344+
return factory().createByteArray(getClass.execute(byteArray), toByteArray.execute(byteArray.getSequenceStorage()));
343345
}
344346
}
345347

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/bytes/PByteArray.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,6 @@ public final void reverse() {
8181
store.reverse();
8282
}
8383

84-
public PByteArray copy() {
85-
return new PByteArray(getLazyPythonClass(), store.copy());
86-
}
87-
8884
@Override
8985
public PIBytesLike createFromBytes(PythonObjectFactory factory, byte[] bytes) {
9086
return factory.createByteArray(bytes);

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -540,11 +540,11 @@ Object doMemoryview(PMemoryView object, String key,
540540
throw new IllegalStateException("delegate of memoryview object is not native");
541541
}
542542

543-
protected static boolean isPyDateTimeCAPI(PythonObject object) {
544-
return object.getLazyPythonClass().getName().equals("PyDateTime_CAPI");
543+
protected static boolean isPyDateTimeCAPI(PythonObject object, GetLazyClassNode getClass) {
544+
return getClass.execute(object).getName().equals("PyDateTime_CAPI");
545545
}
546546

547-
@Specialization(guards = "isPyDateTimeCAPI(object)")
547+
@Specialization(guards = "isPyDateTimeCAPI(object, getClass)", limit = "1")
548548
Object doDatetimeCAPI(PythonObject object, String key,
549549
@Cached("create()") GetLazyClassNode getClass,
550550
@Cached("create()") LookupAttributeInMRONode.Dynamic getAttrNode) {

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/list/ListBuiltins.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
import com.oracle.graal.python.nodes.function.builtins.PythonBinaryBuiltinNode;
8585
import com.oracle.graal.python.nodes.function.builtins.PythonTernaryBuiltinNode;
8686
import com.oracle.graal.python.nodes.function.builtins.PythonUnaryBuiltinNode;
87+
import com.oracle.graal.python.nodes.object.GetLazyClassNode;
8788
import com.oracle.graal.python.nodes.truffle.PythonArithmeticTypes;
8889
import com.oracle.graal.python.runtime.exception.PythonErrorType;
8990
import com.oracle.graal.python.runtime.sequence.PSequence;
@@ -744,9 +745,10 @@ int doGeneric(PList list,
744745
abstract static class AddNode extends PythonBinaryBuiltinNode {
745746
@Specialization
746747
PList doPList(PList left, PList other,
748+
@Cached("create()") GetLazyClassNode getLazyClassNode,
747749
@Cached("createConcat()") SequenceStorageNodes.ConcatNode concatNode) {
748750
SequenceStorage newStore = concatNode.execute(left.getSequenceStorage(), other.getSequenceStorage());
749-
return factory().createList(left.getLazyPythonClass(), newStore);
751+
return factory().createList(getLazyClassNode.execute(left), newStore);
750752
}
751753

752754
@Specialization(guards = "!isList(right)")

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/mappingproxy/MappingproxyBuiltins.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
import com.oracle.graal.python.nodes.function.builtins.PythonBinaryBuiltinNode;
5151
import com.oracle.graal.python.nodes.function.builtins.PythonTernaryBuiltinNode;
5252
import com.oracle.graal.python.nodes.function.builtins.PythonUnaryBuiltinNode;
53+
import com.oracle.graal.python.nodes.object.GetLazyClassNode;
5354
import com.oracle.truffle.api.CompilerDirectives;
5455
import com.oracle.truffle.api.dsl.Cached;
5556
import com.oracle.truffle.api.dsl.GenerateNodeFactory;
@@ -193,8 +194,9 @@ public int len(PMappingproxy self) {
193194
@GenerateNodeFactory
194195
public abstract static class CopyNode extends PythonUnaryBuiltinNode {
195196
@Specialization
196-
public PMappingproxy copy(PMappingproxy proxy) {
197-
return factory().createMappingproxy(proxy.getLazyPythonClass(), proxy.getDictStorage());
197+
public PMappingproxy copy(PMappingproxy proxy,
198+
@Cached("create()") GetLazyClassNode getClass) {
199+
return factory().createMappingproxy(getClass.execute(proxy), proxy.getDictStorage());
198200
}
199201
}
200202
}

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

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,14 @@
5555
import com.oracle.graal.python.builtins.PythonBuiltinClassType;
5656
import com.oracle.graal.python.builtins.PythonBuiltins;
5757
import com.oracle.graal.python.builtins.objects.PNone;
58+
import com.oracle.graal.python.builtins.objects.cext.PythonNativeClass;
5859
import com.oracle.graal.python.builtins.objects.cext.PythonNativeObject;
5960
import com.oracle.graal.python.builtins.objects.common.PHashingCollection;
6061
import com.oracle.graal.python.builtins.objects.function.PBuiltinFunction;
6162
import com.oracle.graal.python.builtins.objects.function.PKeyword;
6263
import com.oracle.graal.python.builtins.objects.function.PythonCallable;
6364
import com.oracle.graal.python.builtins.objects.type.LazyPythonClass;
65+
import com.oracle.graal.python.builtins.objects.type.PythonBuiltinClass;
6466
import com.oracle.graal.python.builtins.objects.type.PythonClass;
6567
import com.oracle.graal.python.nodes.SpecialMethodNames;
6668
import com.oracle.graal.python.nodes.attributes.LookupAttributeInMRONode;
@@ -72,7 +74,6 @@
7274
import com.oracle.graal.python.nodes.call.special.LookupAndCallUnaryNode;
7375
import com.oracle.graal.python.nodes.expression.BinaryComparisonNode;
7476
import com.oracle.graal.python.nodes.function.PythonBuiltinBaseNode;
75-
import com.oracle.graal.python.nodes.function.PythonBuiltinNode;
7677
import com.oracle.graal.python.nodes.function.builtins.PythonBinaryBuiltinNode;
7778
import com.oracle.graal.python.nodes.function.builtins.PythonTernaryBuiltinNode;
7879
import com.oracle.graal.python.nodes.function.builtins.PythonUnaryBuiltinNode;
@@ -99,14 +100,54 @@ protected List<? extends NodeFactory<? extends PythonBuiltinBaseNode>> getNodeFa
99100
return ObjectBuiltinsFactory.getFactories();
100101
}
101102

102-
@Builtin(name = __CLASS__, fixedNumOfPositionalArgs = 1, isGetter = true)
103+
@Builtin(name = __CLASS__, minNumOfPositionalArgs = 1, maxNumOfPositionalArgs = 2, isGetter = true, isSetter = true)
103104
@GenerateNodeFactory
104-
abstract static class ClassNode extends PythonBuiltinNode {
105-
@Specialization
106-
PythonClass getClass(Object self,
105+
abstract static class ClassNode extends PythonBinaryBuiltinNode {
106+
private static final String ERROR_MESSAGE = "__class__ assignment only supported for heap types or ModuleType subclasses";
107+
108+
@Specialization(guards = "isNoValue(value)")
109+
PythonClass getClass(Object self, @SuppressWarnings("unused") PNone value,
107110
@Cached("create()") GetClassNode getClass) {
108111
return getClass.execute(self);
109112
}
113+
114+
@Specialization
115+
PythonClass setClass(@SuppressWarnings("unused") Object self, @SuppressWarnings("unused") PythonBuiltinClass klass) {
116+
throw raise(TypeError, ERROR_MESSAGE);
117+
}
118+
119+
@Specialization
120+
PythonClass setClass(@SuppressWarnings("unused") Object self, @SuppressWarnings("unused") PythonNativeClass klass) {
121+
throw raise(TypeError, ERROR_MESSAGE);
122+
}
123+
124+
@Specialization
125+
PNone setClass(PythonObject self, PythonClass value,
126+
@Cached("create()") BranchProfile errorValueBranch,
127+
@Cached("create()") BranchProfile errorSelfBranch,
128+
@Cached("create()") GetLazyClassNode getLazyClass) {
129+
if (value instanceof PythonBuiltinClass || value instanceof PythonNativeClass) {
130+
errorValueBranch.enter();
131+
throw raise(TypeError, ERROR_MESSAGE);
132+
}
133+
LazyPythonClass lazyClass = getLazyClass.execute(self);
134+
if (lazyClass instanceof PythonBuiltinClassType || lazyClass instanceof PythonBuiltinClass || lazyClass instanceof PythonNativeClass) {
135+
errorSelfBranch.enter();
136+
throw raise(TypeError, ERROR_MESSAGE);
137+
}
138+
self.setLazyPythonClass(value);
139+
return PNone.NONE;
140+
}
141+
142+
@Specialization(guards = "!isPythonObject(self)")
143+
PythonClass getClass(@SuppressWarnings("unused") Object self, @SuppressWarnings("unused") PythonClass value) {
144+
throw raise(TypeError, ERROR_MESSAGE);
145+
}
146+
147+
@Fallback
148+
PythonClass getClass(@SuppressWarnings("unused") Object self, Object value) {
149+
throw raise(TypeError, "__class__ must be set to a class, not '%p' object", value);
150+
}
110151
}
111152

112153
@Builtin(name = __INIT__, takesVarArgs = true, minNumOfPositionalArgs = 1, takesVarKeywordArgs = true)

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

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,20 @@
3636
import com.oracle.graal.python.builtins.objects.type.LazyPythonClass;
3737
import com.oracle.graal.python.builtins.objects.type.PythonBuiltinClass;
3838
import com.oracle.graal.python.builtins.objects.type.PythonClass;
39+
import com.oracle.truffle.api.Assumption;
3940
import com.oracle.truffle.api.CompilerAsserts;
4041
import com.oracle.truffle.api.CompilerDirectives;
42+
import com.oracle.truffle.api.Truffle;
43+
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
4144
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
4245
import com.oracle.truffle.api.object.DynamicObject;
4346
import com.oracle.truffle.api.object.Location;
4447
import com.oracle.truffle.api.object.Property;
4548
import com.oracle.truffle.api.object.Shape;
4649

4750
public class PythonObject extends PythonAbstractObject {
48-
private final LazyPythonClass pythonClass;
51+
@CompilationFinal private LazyPythonClass pythonClass;
52+
private final Assumption classStable = Truffle.getRuntime().createAssumption("class unchanged");
4953
private final DynamicObject storage;
5054
private PHashingCollection dict;
5155

@@ -77,10 +81,28 @@ public final PythonClass getPythonClass() {
7781
}
7882
}
7983

84+
public final void setLazyPythonClass(PythonClass cls) {
85+
pythonClass = cls;
86+
classStable.invalidate();
87+
}
88+
89+
/**
90+
* Generally reading this directly might not be safe, because the value is
91+
* {@code @CompilationFinal}. It's fine, however, if the class is of a builtin type (because for
92+
* objects of these types, we cannot write to the {@code __class__} field anyway.
93+
*/
8094
public final LazyPythonClass getLazyPythonClass() {
95+
assert (!CompilerDirectives.isCompilationConstant(this) ||
96+
CompilerDirectives.inInterpreter() ||
97+
pythonClass instanceof PythonBuiltinClassType ||
98+
pythonClass instanceof PythonBuiltinClass) : "user type object must not be compilation constant when reading the compilation final lazy python class in compiled code";
8199
return pythonClass;
82100
}
83101

102+
public final Assumption getClassStableAssumption() {
103+
return classStable;
104+
}
105+
84106
public final DynamicObject getStorage() {
85107
return storage;
86108
}

0 commit comments

Comments
 (0)