Skip to content

Commit 9bc24c3

Browse files
committed
[GR-13030] [GR-12983] Add support for __slots__
PullRequest: graalpython/328
2 parents fb9ca5a + f892646 commit 9bc24c3

File tree

13 files changed

+499
-52
lines changed

13 files changed

+499
-52
lines changed

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

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,3 +155,54 @@ class B(A):
155155
assert True
156156
else:
157157
assert False
158+
159+
160+
def test_class_slots():
161+
class X():
162+
__slots__ = "_local__impl", "__dict__"
163+
164+
def __init__(self):
165+
self._local__impl = 1
166+
self.foo = 12
167+
self.__dict__ = {"bar": 42}
168+
169+
170+
assert X().bar == 42
171+
assert X()._local__impl == 1
172+
try:
173+
X().foo
174+
except AttributeError:
175+
assert True
176+
else:
177+
assert False
178+
179+
x = X()
180+
x.foo = 1
181+
assert x.foo == 1
182+
assert x.__dict__["foo"] == 1
183+
x.__dict__["_local__impl"] = 22
184+
assert x._local__impl == 1
185+
186+
assert X.__dict__["_local__impl"].__get__(x, type(x)) == 1
187+
188+
189+
def test_class_with_slots_assignment():
190+
class X():
191+
__slots__ = "a", "b"
192+
193+
class Y():
194+
__slots__ = "a", "b"
195+
196+
class Z():
197+
__slots__ = "b", "c"
198+
199+
200+
x = X()
201+
x.__class__ = Y
202+
assert type(x) == Y
203+
try:
204+
x.__class__ = Z
205+
except TypeError as e:
206+
assert True
207+
else:
208+
assert False

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

Lines changed: 111 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,9 @@
4949
import static com.oracle.graal.python.nodes.BuiltinNames.TUPLE;
5050
import static com.oracle.graal.python.nodes.BuiltinNames.TYPE;
5151
import static com.oracle.graal.python.nodes.BuiltinNames.ZIP;
52+
import static com.oracle.graal.python.nodes.SpecialAttributeNames.__DICT__;
5253
import static com.oracle.graal.python.nodes.SpecialAttributeNames.__FILE__;
54+
import static com.oracle.graal.python.nodes.SpecialAttributeNames.__SLOTS__;
5355
import static com.oracle.graal.python.nodes.SpecialMethodNames.DECODE;
5456
import static com.oracle.graal.python.nodes.SpecialMethodNames.__SETITEM__;
5557
import static com.oracle.graal.python.runtime.exception.PythonErrorType.NotImplementedError;
@@ -91,6 +93,7 @@
9193
import com.oracle.graal.python.builtins.objects.function.PFunction;
9294
import com.oracle.graal.python.builtins.objects.function.PKeyword;
9395
import com.oracle.graal.python.builtins.objects.function.PythonCallable;
96+
import com.oracle.graal.python.builtins.objects.getsetdescriptor.HiddenKeyDescriptor;
9497
import com.oracle.graal.python.builtins.objects.ints.PInt;
9598
import com.oracle.graal.python.builtins.objects.iterator.PZip;
9699
import com.oracle.graal.python.builtins.objects.list.PList;
@@ -124,6 +127,7 @@
124127
import com.oracle.graal.python.nodes.control.GetNextNode;
125128
import com.oracle.graal.python.nodes.datamodel.IsIndexNode;
126129
import com.oracle.graal.python.nodes.datamodel.IsSequenceNode;
130+
import com.oracle.graal.python.nodes.expression.CastToListNode;
127131
import com.oracle.graal.python.nodes.function.PythonBuiltinBaseNode;
128132
import com.oracle.graal.python.nodes.function.PythonBuiltinNode;
129133
import com.oracle.graal.python.nodes.function.builtins.PythonBinaryBuiltinNode;
@@ -140,6 +144,7 @@
140144
import com.oracle.graal.python.runtime.exception.PythonErrorType;
141145
import com.oracle.graal.python.runtime.sequence.PSequence;
142146
import com.oracle.graal.python.runtime.sequence.storage.ByteSequenceStorage;
147+
import com.oracle.graal.python.runtime.sequence.storage.SequenceStorage;
143148
import com.oracle.truffle.api.CompilerAsserts;
144149
import com.oracle.truffle.api.CompilerDirectives;
145150
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
@@ -151,6 +156,7 @@
151156
import com.oracle.truffle.api.dsl.TypeSystemReference;
152157
import com.oracle.truffle.api.frame.VirtualFrame;
153158
import com.oracle.truffle.api.nodes.UnexpectedResultException;
159+
import com.oracle.truffle.api.object.HiddenKey;
154160
import com.oracle.truffle.api.profiles.BranchProfile;
155161
import com.oracle.truffle.api.profiles.ConditionProfile;
156162

@@ -1598,6 +1604,9 @@ public abstract static class TypeNode extends PythonBuiltinNode {
15981604
@Child private ReadAttributeFromObjectNode readAttrNode;
15991605
@Child private WriteAttributeToObjectNode writeAttrNode;
16001606
@Child private CastToIndexNode castToInt;
1607+
@Child private CastToListNode castToList;
1608+
@Child private SequenceStorageNodes.LenNode slotLenNode;
1609+
@Child private SequenceStorageNodes.GetItemNode getSlotItemNode;
16011610

16021611
@Specialization(guards = {"isNoValue(bases)", "isNoValue(dict)"})
16031612
@SuppressWarnings("unused")
@@ -1612,12 +1621,15 @@ public Object type(VirtualFrame frame, PythonClass cls, String name, PTuple base
16121621
@Cached("create(__NEW__)") LookupInheritedAttributeNode getNewFuncNode,
16131622
@Cached("create()") CallDispatchNode callNewFuncNode,
16141623
@Cached("create()") CreateArgumentsNode createArgs) {
1624+
// Determine the proper metatype to deal with this
16151625
PythonClass metaclass = calculate_metaclass(cls, bases, getMetaclassNode);
1626+
16161627
if (metaclass != cls) {
16171628
Object newFunc = getNewFuncNode.execute(metaclass);
16181629
if (newFunc instanceof PBuiltinFunction && (((PBuiltinFunction) newFunc).getFunctionRootNode() == getRootNode())) {
1619-
// the new metaclass has the same __new__ function as we are in
1630+
// the new metaclass has the same __new__ function as we are in, continue
16201631
} else {
1632+
// Pass it to the winner
16211633
return callNewFuncNode.executeCall(frame, newFunc, createArgs.execute(metaclass, name, bases, namespace), kwds);
16221634
}
16231635
}
@@ -1626,12 +1638,11 @@ public Object type(VirtualFrame frame, PythonClass cls, String name, PTuple base
16261638

16271639
@TruffleBoundary
16281640
private Object typeMetaclass(String name, PTuple bases, PDict namespace, PythonClass metaclass) {
1629-
if (name.indexOf('\0') != -1) {
1630-
throw raise(ValueError, "type name must not contain null characters");
1631-
}
1641+
16321642
Object[] array = bases.getArray();
16331643
PythonClass[] basesArray;
16341644
if (array.length == 0) {
1645+
// Adjust for empty tuple bases
16351646
basesArray = new PythonClass[]{getCore().lookupType(PythonBuiltinClassType.PythonObject)};
16361647
} else {
16371648
basesArray = new PythonClass[array.length];
@@ -1645,15 +1656,106 @@ private Object typeMetaclass(String name, PTuple bases, PDict namespace, PythonC
16451656
}
16461657
}
16471658
assert metaclass != null;
1659+
1660+
if (name.indexOf('\0') != -1) {
1661+
throw raise(ValueError, "type name must not contain null characters");
1662+
}
16481663
PythonClass pythonClass = factory().createPythonClass(metaclass, name, basesArray);
1664+
1665+
// copy the dictionary slots over, as CPython does through PyDict_Copy
1666+
// Also check for a __slots__ sequence variable in dict
1667+
Object slots = null;
16491668
for (DictEntry entry : namespace.entries()) {
1650-
pythonClass.setAttribute(entry.getKey(), entry.getValue());
1669+
Object key = entry.getKey();
1670+
Object value = entry.getValue();
1671+
if (__SLOTS__.equals(key)) {
1672+
slots = value;
1673+
} else {
1674+
pythonClass.setAttribute(key, value);
1675+
}
16511676
}
1652-
addDictIfNative(pythonClass);
1677+
1678+
if (slots == null) {
1679+
// takes care of checking if we may_add_dict and adds it if needed
1680+
addDictIfNative(pythonClass);
1681+
// TODO: tfel - also deal with weaklistoffset
1682+
} else {
1683+
// have slots
1684+
1685+
// Make it into a list
1686+
SequenceStorage slotList;
1687+
if (slots instanceof String) {
1688+
slotList = factory().createList(new Object[]{slots}).getSequenceStorage();
1689+
} else {
1690+
slotList = getCastToListNode().executeWith(slots).getSequenceStorage();
1691+
}
1692+
int slotlen = getListLenNode().execute(slotList);
1693+
// TODO: tfel - check if slots are allowed. They are not if the base class is var
1694+
// sized
1695+
1696+
for (int i = 0; i < slotlen; i++) {
1697+
String slotName;
1698+
Object element = getSlotItemNode().execute(slotList, i);
1699+
// Check valid slot name
1700+
if (element instanceof String) {
1701+
slotName = (String) element;
1702+
} else {
1703+
throw raise(TypeError, "__slots__ items must be strings, not '%p'", element);
1704+
}
1705+
if (__DICT__.equals(slotName)) {
1706+
// check that the native base does not already have tp_dictoffset
1707+
if (addDictIfNative(pythonClass)) {
1708+
throw raise(TypeError, "__dict__ slot disallowed: we already got one");
1709+
}
1710+
} else {
1711+
// TODO: check for __weakref__
1712+
// TODO: Copy slots into a list, mangle names and sort them
1713+
HiddenKey hiddenSlotKey = new HiddenKey(slotName);
1714+
HiddenKeyDescriptor slotDesc = factory().createHiddenKeyDescriptor(hiddenSlotKey, pythonClass);
1715+
pythonClass.setAttribute(slotName, slotDesc);
1716+
}
1717+
// Make slots into a tuple
1718+
}
1719+
pythonClass.setAttribute(__SLOTS__, factory().createTuple(slotList));
1720+
if (basesArray.length > 1) {
1721+
// TODO: tfel - check if secondary bases provide weakref or dict when we don't
1722+
// already have one
1723+
}
1724+
}
1725+
1726+
// TODO: tfel special case __new__: if it's a plain function, make it a static function
1727+
// TODO: tfel Special-case __init_subclass__: if it's a plain function, make it a
1728+
// classmethod
1729+
16531730
return pythonClass;
16541731
}
16551732

1656-
private void addDictIfNative(PythonClass pythonClass) {
1733+
private SequenceStorageNodes.GetItemNode getSlotItemNode() {
1734+
if (getSlotItemNode == null) {
1735+
CompilerDirectives.transferToInterpreterAndInvalidate();
1736+
getSlotItemNode = insert(SequenceStorageNodes.GetItemNode.create());
1737+
}
1738+
return getSlotItemNode;
1739+
}
1740+
1741+
private SequenceStorageNodes.LenNode getListLenNode() {
1742+
if (slotLenNode == null) {
1743+
CompilerDirectives.transferToInterpreterAndInvalidate();
1744+
slotLenNode = insert(SequenceStorageNodes.LenNode.create());
1745+
}
1746+
return slotLenNode;
1747+
}
1748+
1749+
private CastToListNode getCastToListNode() {
1750+
if (castToList == null) {
1751+
CompilerDirectives.transferToInterpreterAndInvalidate();
1752+
castToList = insert(CastToListNode.create());
1753+
}
1754+
return castToList;
1755+
}
1756+
1757+
private boolean addDictIfNative(PythonClass pythonClass) {
1758+
boolean addedNewDict = false;
16571759
for (Object cls : pythonClass.getMethodResolutionOrder()) {
16581760
if (cls instanceof PythonNativeClass) {
16591761
if (readAttrNode == null) {
@@ -1666,6 +1768,7 @@ private void addDictIfNative(PythonClass pythonClass) {
16661768
long basicsize = castToInt.execute(readAttrNode.execute(cls, SpecialAttributeNames.__BASICSIZE__));
16671769
long itemsize = castToInt.execute(readAttrNode.execute(cls, SpecialAttributeNames.__ITEMSIZE__));
16681770
if (dictoffset == 0) {
1771+
addedNewDict = true;
16691772
// add_dict
16701773
if (itemsize != 0) {
16711774
dictoffset = -SIZEOF_PY_OBJECT_PTR;
@@ -1680,6 +1783,7 @@ private void addDictIfNative(PythonClass pythonClass) {
16801783
break;
16811784
}
16821785
}
1786+
return addedNewDict;
16831787
}
16841788

16851789
private PythonClass calculate_metaclass(PythonClass cls, PTuple bases, GetClassNode getMetaclassNode) {

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1785,11 +1785,17 @@ public Object globals(VirtualFrame frame) {
17851785
abstract static class LocalsNode extends PythonBuiltinNode {
17861786
@Child ReadCallerFrameNode readCallerFrameNode = ReadCallerFrameNode.create();
17871787
@Child GetLocalsNode getLocalsNode = GetLocalsNode.create();
1788+
private final ConditionProfile inGenerator = ConditionProfile.createBinaryProfile();
17881789

17891790
@Specialization
17901791
public Object locals(VirtualFrame frame) {
17911792
Frame callerFrame = readCallerFrameNode.executeWith(frame);
1792-
return getLocalsNode.execute(callerFrame);
1793+
Frame generatorFrame = PArguments.getGeneratorFrame(callerFrame);
1794+
if (inGenerator.profile(generatorFrame == null)) {
1795+
return getLocalsNode.execute(callerFrame);
1796+
} else {
1797+
return getLocalsNode.execute(generatorFrame);
1798+
}
17931799
}
17941800
}
17951801
}

0 commit comments

Comments
 (0)