Skip to content

Commit 45d5096

Browse files
committed
[GR-14849] [GH-66] Implement PEP 560 and PEP 487 so that the typing module works again
PullRequest: graalpython/465
2 parents 4772ec8 + 37130d5 commit 45d5096

File tree

8 files changed

+364
-12
lines changed

8 files changed

+364
-12
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ language runtime. The main focus is on user-observable behavior of the engine.
55

66
## Version 1.0.0 RC15
77

8+
* Implement PEP 487 `__init_subclass__`
9+
* Implement PEP 560 `__class_getitem__` and `__mro_entries__`
810
* Migrate to Truffle libraries for interop
911
* Support the buffer protocol for mmap
1012
* Support importing java classes using normal Python import syntax
Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
# Copyright (c) 2018, 2019, Oracle and/or its affiliates.
2+
# Copyright (C) 1996-2017 Python Software Foundation
3+
#
4+
# Licensed under the PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
5+
import unittest, sys
6+
7+
8+
if sys.version_info.minor >= 7:
9+
class TestMROEntry(unittest.TestCase):
10+
def test_mro_entry_signature(self):
11+
tested = []
12+
class B: ...
13+
class C:
14+
def __mro_entries__(self, *args, **kwargs):
15+
tested.extend([args, kwargs])
16+
return (C,)
17+
c = C()
18+
self.assertEqual(tested, [])
19+
class D(B, c): ...
20+
self.assertEqual(tested[0], ((B, c),))
21+
self.assertEqual(tested[1], {})
22+
23+
def test_mro_entry(self):
24+
tested = []
25+
class A: ...
26+
class B: ...
27+
class C:
28+
def __mro_entries__(self, bases):
29+
tested.append(bases)
30+
return (self.__class__,)
31+
c = C()
32+
self.assertEqual(tested, [])
33+
class D(A, c, B): ...
34+
self.assertEqual(tested[-1], (A, c, B))
35+
self.assertEqual(D.__bases__, (A, C, B))
36+
self.assertEqual(D.__orig_bases__, (A, c, B))
37+
self.assertEqual(D.__mro__, (D, A, C, B, object))
38+
d = D()
39+
class E(d): ...
40+
self.assertEqual(tested[-1], (d,))
41+
self.assertEqual(E.__bases__, (D,))
42+
43+
def test_mro_entry_none(self):
44+
tested = []
45+
class A: ...
46+
class B: ...
47+
class C:
48+
def __mro_entries__(self, bases):
49+
tested.append(bases)
50+
return ()
51+
c = C()
52+
self.assertEqual(tested, [])
53+
class D(A, c, B): ...
54+
self.assertEqual(tested[-1], (A, c, B))
55+
self.assertEqual(D.__bases__, (A, B))
56+
self.assertEqual(D.__orig_bases__, (A, c, B))
57+
self.assertEqual(D.__mro__, (D, A, B, object))
58+
class E(c): ...
59+
self.assertEqual(tested[-1], (c,))
60+
self.assertEqual(E.__bases__, (object,))
61+
self.assertEqual(E.__orig_bases__, (c,))
62+
self.assertEqual(E.__mro__, (E, object))
63+
64+
def test_mro_entry_with_builtins(self):
65+
tested = []
66+
class A: ...
67+
class C:
68+
def __mro_entries__(self, bases):
69+
tested.append(bases)
70+
return (dict,)
71+
c = C()
72+
self.assertEqual(tested, [])
73+
class D(A, c): ...
74+
self.assertEqual(tested[-1], (A, c))
75+
self.assertEqual(D.__bases__, (A, dict))
76+
self.assertEqual(D.__orig_bases__, (A, c))
77+
self.assertEqual(D.__mro__, (D, A, dict, object))
78+
79+
def test_mro_entry_with_builtins_2(self):
80+
tested = []
81+
class C:
82+
def __mro_entries__(self, bases):
83+
tested.append(bases)
84+
return (C,)
85+
c = C()
86+
self.assertEqual(tested, [])
87+
class D(c, dict): ...
88+
self.assertEqual(tested[-1], (c, dict))
89+
self.assertEqual(D.__bases__, (C, dict))
90+
self.assertEqual(D.__orig_bases__, (c, dict))
91+
self.assertEqual(D.__mro__, (D, C, dict, object))
92+
93+
def test_mro_entry_errors(self):
94+
class C_too_many:
95+
def __mro_entries__(self, bases, something, other):
96+
return ()
97+
c = C_too_many()
98+
with self.assertRaises(TypeError):
99+
class D(c): ...
100+
class C_too_few:
101+
def __mro_entries__(self):
102+
return ()
103+
d = C_too_few()
104+
with self.assertRaises(TypeError):
105+
class D(d): ...
106+
107+
def test_mro_entry_errors_2(self):
108+
class C_not_callable:
109+
__mro_entries__ = "Surprise!"
110+
c = C_not_callable()
111+
with self.assertRaises(TypeError):
112+
class D(c): ...
113+
class C_not_tuple:
114+
def __mro_entries__(self):
115+
return object
116+
c = C_not_tuple()
117+
with self.assertRaises(TypeError):
118+
class D(c): ...
119+
120+
def test_mro_entry_metaclass(self):
121+
meta_args = []
122+
class Meta(type):
123+
def __new__(mcls, name, bases, ns):
124+
meta_args.extend([mcls, name, bases, ns])
125+
return super().__new__(mcls, name, bases, ns)
126+
class A: ...
127+
class C:
128+
def __mro_entries__(self, bases):
129+
return (A,)
130+
c = C()
131+
class D(c, metaclass=Meta):
132+
x = 1
133+
self.assertEqual(meta_args[0], Meta)
134+
self.assertEqual(meta_args[1], 'D')
135+
self.assertEqual(meta_args[2], (A,))
136+
self.assertEqual(meta_args[3]['x'], 1)
137+
self.assertEqual(D.__bases__, (A,))
138+
self.assertEqual(D.__orig_bases__, (c,))
139+
self.assertEqual(D.__mro__, (D, A, object))
140+
self.assertEqual(D.__class__, Meta)
141+
142+
143+
class TestClassGetitem(unittest.TestCase):
144+
def test_class_getitem(self):
145+
getitem_args = []
146+
class C:
147+
def __class_getitem__(*args, **kwargs):
148+
getitem_args.extend([args, kwargs])
149+
return None
150+
C[int, str]
151+
self.assertEqual(getitem_args[0], (C, (int, str)))
152+
self.assertEqual(getitem_args[1], {})
153+
154+
def test_class_getitem(self):
155+
class C:
156+
def __class_getitem__(cls, item):
157+
return 'C[{0}]'.format(item.__name__)
158+
self.assertEqual(C[int], 'C[int]')
159+
self.assertEqual(C[C], 'C[C]')
160+
161+
def test_class_getitem_inheritance(self):
162+
class C:
163+
def __class_getitem__(cls, item):
164+
return '{0}[{1}]'.format(cls.__name__, item.__name__)
165+
class D(C): ...
166+
self.assertEqual(D[int], 'D[int]')
167+
self.assertEqual(D[D], 'D[D]')
168+
169+
def test_class_getitem_inheritance_2(self):
170+
class C:
171+
def __class_getitem__(cls, item):
172+
return 'Should not see this'
173+
class D(C):
174+
def __class_getitem__(cls, item):
175+
return '{0}[{1}]'.format(cls.__name__, item.__name__)
176+
self.assertEqual(D[int], 'D[int]')
177+
self.assertEqual(D[D], 'D[D]')
178+
179+
def test_class_getitem_classmethod(self):
180+
class C:
181+
@classmethod
182+
def __class_getitem__(cls, item):
183+
return '{0}[{1}]'.format(cls.__name__, item.__name__)
184+
class D(C): ...
185+
self.assertEqual(D[int], 'D[int]')
186+
self.assertEqual(D[D], 'D[D]')
187+
188+
def test_class_getitem_patched(self):
189+
class C:
190+
def __init_subclass__(cls):
191+
def __class_getitem__(cls, item):
192+
return '{0}[{1}]'.format(cls.__name__, item.__name__)
193+
cls.__class_getitem__ = classmethod(__class_getitem__)
194+
class D(C): ...
195+
self.assertEqual(D[int], 'D[int]')
196+
self.assertEqual(D[D], 'D[D]')
197+
198+
def test_class_getitem_with_builtins(self):
199+
class A(dict):
200+
called_with = None
201+
202+
def __class_getitem__(cls, item):
203+
cls.called_with = item
204+
class B(A):
205+
pass
206+
self.assertIs(B.called_with, None)
207+
B[int]
208+
self.assertIs(B.called_with, int)
209+
210+
def test_class_getitem_errors(self):
211+
class C_too_few:
212+
def __class_getitem__(cls):
213+
return None
214+
with self.assertRaises(TypeError):
215+
C_too_few[int]
216+
class C_too_many:
217+
def __class_getitem__(cls, one, two):
218+
return None
219+
with self.assertRaises(TypeError):
220+
C_too_many[int]
221+
222+
def test_class_getitem_errors_2(self):
223+
class C:
224+
def __class_getitem__(cls, item):
225+
return None
226+
with self.assertRaises(TypeError):
227+
C()[int]
228+
class E: ...
229+
e = E()
230+
e.__class_getitem__ = lambda cls, item: 'This will not work'
231+
with self.assertRaises(TypeError):
232+
e[int]
233+
class C_not_callable:
234+
__class_getitem__ = "Surprise!"
235+
with self.assertRaises(TypeError):
236+
C_not_callable[int]
237+
238+
def test_class_getitem_metaclass(self):
239+
class Meta(type):
240+
def __class_getitem__(cls, item):
241+
return '{0}[{1}]'.format(cls.__name__, item.__name__)
242+
self.assertEqual(Meta[int], 'Meta[int]')
243+
244+
def test_class_getitem_metaclass_2(self):
245+
class Meta(type):
246+
def __getitem__(cls, item):
247+
return 'from metaclass'
248+
class C(metaclass=Meta):
249+
def __class_getitem__(cls, item):
250+
return 'from __class_getitem__'
251+
self.assertEqual(C[int], 'from metaclass')

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

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@
119119
import com.oracle.graal.python.builtins.objects.set.PSet;
120120
import com.oracle.graal.python.builtins.objects.set.SetNodes;
121121
import com.oracle.graal.python.builtins.objects.str.PString;
122+
import com.oracle.graal.python.builtins.objects.superobject.SuperObject;
122123
import com.oracle.graal.python.builtins.objects.tuple.PTuple;
123124
import com.oracle.graal.python.builtins.objects.type.LazyPythonClass;
124125
import com.oracle.graal.python.builtins.objects.type.PythonAbstractClass;
@@ -129,6 +130,8 @@
129130
import com.oracle.graal.python.builtins.objects.type.TypeNodes.GetMroNode;
130131
import com.oracle.graal.python.builtins.objects.type.TypeNodes.GetNameNode;
131132
import com.oracle.graal.python.nodes.PGuards;
133+
import com.oracle.graal.python.nodes.SpecialMethodNames;
134+
import com.oracle.graal.python.nodes.attributes.GetAttributeNode;
132135
import com.oracle.graal.python.nodes.attributes.GetAttributeNode.GetAnyAttributeNode;
133136
import com.oracle.graal.python.nodes.attributes.LookupAttributeInMRONode;
134137
import com.oracle.graal.python.nodes.attributes.LookupInheritedAttributeNode;
@@ -1812,6 +1815,8 @@ public Object type(Object cls, Object obj, PNone bases, PNone dict, PKeyword[] k
18121815
public Object type(VirtualFrame frame, PythonAbstractClass cls, String name, PTuple bases, PDict namespace, PKeyword[] kwds,
18131816
@Cached("create()") GetClassNode getMetaclassNode,
18141817
@Cached("create(__NEW__)") LookupInheritedAttributeNode getNewFuncNode,
1818+
@Cached("create(__INIT_SUBCLASS__)") GetAttributeNode getInitSubclassNode,
1819+
@Cached("create()") CallNode callInitSubclassNode,
18151820
@Cached("create()") CallNode callNewFuncNode) {
18161821
// Determine the proper metatype to deal with this
18171822
PythonAbstractClass metaclass = calculate_metaclass(cls, bases, getMetaclassNode);
@@ -1826,7 +1831,14 @@ public Object type(VirtualFrame frame, PythonAbstractClass cls, String name, PTu
18261831
}
18271832

18281833
try {
1829-
Object newType = typeMetaclass(name, bases, namespace, metaclass);
1834+
PythonClass newType = typeMetaclass(name, bases, namespace, metaclass);
1835+
1836+
// TODO: Call __set_name__ on all descriptors in a newly generated type
1837+
1838+
// Call __init_subclass__ on the parent of a newly generated type
1839+
SuperObject superObject = factory().createSuperObject(PythonBuiltinClassType.Super);
1840+
superObject.init(newType, newType, newType);
1841+
callInitSubclassNode.execute(frame, getInitSubclassNode.executeObject(superObject), new Object[0], kwds);
18301842

18311843
// set '__module__' attribute
18321844
Object moduleAttr = ensureReadAttrNode().execute(newType, __MODULE__);
@@ -1862,7 +1874,7 @@ private String getModuleNameFromGlobals(PythonObject globals) {
18621874
}
18631875

18641876
@TruffleBoundary
1865-
private Object typeMetaclass(String name, PTuple bases, PDict namespace, PythonAbstractClass metaclass) {
1877+
private PythonClass typeMetaclass(String name, PTuple bases, PDict namespace, PythonAbstractClass metaclass) {
18661878

18671879
Object[] array = bases.getArray();
18681880

@@ -1896,6 +1908,23 @@ private Object typeMetaclass(String name, PTuple bases, PDict namespace, PythonA
18961908
Object value = entry.getValue();
18971909
if (__SLOTS__.equals(key)) {
18981910
slots = value;
1911+
} else if (SpecialMethodNames.__NEW__.equals(key)) {
1912+
// TODO: see CPython: if it's a plain function, make it a
1913+
// static function
1914+
1915+
// tfel: this requires a little bit of refactoring on our
1916+
// side that I don't want to do now
1917+
pythonClass.setAttribute(key, value);
1918+
} else if (SpecialMethodNames.__INIT_SUBCLASS__.equals(key) ||
1919+
SpecialMethodNames.__CLASS_GETITEM__.equals(key)) {
1920+
// see CPython: Special-case __init_subclass__ and
1921+
// __class_getitem__: if they are plain functions, make them
1922+
// classmethods
1923+
if (value instanceof PFunction) {
1924+
pythonClass.setAttribute(key, factory().createClassmethod(value));
1925+
} else {
1926+
pythonClass.setAttribute(key, value);
1927+
}
18991928
} else {
19001929
pythonClass.setAttribute(key, value);
19011930
}
@@ -1957,10 +1986,6 @@ private Object typeMetaclass(String name, PTuple bases, PDict namespace, PythonA
19571986
}
19581987
}
19591988

1960-
// TODO: tfel special case __new__: if it's a plain function, make it a static function
1961-
// TODO: tfel Special-case __init_subclass__: if it's a plain function, make it a
1962-
// classmethod
1963-
19641989
return pythonClass;
19651990
}
19661991

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import static com.oracle.graal.python.nodes.SpecialMethodNames.__GETATTR__;
4040
import static com.oracle.graal.python.nodes.SpecialMethodNames.__GET__;
4141
import static com.oracle.graal.python.nodes.SpecialMethodNames.__HASH__;
42+
import static com.oracle.graal.python.nodes.SpecialMethodNames.__INIT_SUBCLASS__;
4243
import static com.oracle.graal.python.nodes.SpecialMethodNames.__INIT__;
4344
import static com.oracle.graal.python.nodes.SpecialMethodNames.__LEN__;
4445
import static com.oracle.graal.python.nodes.SpecialMethodNames.__NE__;
@@ -631,4 +632,13 @@ boolean richcmp(Object left, Object right, @SuppressWarnings("unused") String op
631632
return node.executeBool(left, right);
632633
}
633634
}
635+
636+
@Builtin(name = __INIT_SUBCLASS__, minNumOfPositionalArgs = 1)
637+
@GenerateNodeFactory
638+
abstract static class InitSubclass extends PythonUnaryBuiltinNode {
639+
@Specialization
640+
PNone initSubclass(@SuppressWarnings("unused") Object self) {
641+
return PNone.NONE;
642+
}
643+
}
634644
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ public abstract class SpecialMethodNames {
161161
public static final String TOBYTES = "tobytes";
162162
public static final String DECODE = "decode";
163163
public static final String __SIZEOF__ = "__sizeof__";
164+
public static final String __CLASS_GETITEM__ = "__class_getitem__";
164165

165166
public static final String RICHCMP = "__truffle_richcompare__";
166167
public static final String TRUFFLE_SOURCE = "__truffle_source__";

0 commit comments

Comments
 (0)