Skip to content

Commit 15e496f

Browse files
otethalqunaibit
authored andcommitted
intrinsification of _IOBase and allow definition of attributes for some builtins
1 parent dc865f9 commit 15e496f

File tree

10 files changed

+703
-17
lines changed

10 files changed

+703
-17
lines changed
Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
# Copyright (c) 2021, 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+
import unittest
41+
42+
# TODO replace this with simple import _io once _IO_Base is renamed to _IOBase
43+
import _io as _orig_io
44+
45+
class Io:
46+
_IOBase = getattr(_orig_io, "_IO_Base", _orig_io._IOBase)
47+
UnsupportedOperation = _orig_io.UnsupportedOperation
48+
49+
_io = Io()
50+
51+
52+
class IOBaseTests(unittest.TestCase):
53+
54+
def test_iobase_ctor_accepts_anything(self):
55+
_io._IOBase()
56+
_io._IOBase(1, '2', kw=3)
57+
58+
def test_close(self):
59+
x = _io._IOBase()
60+
self.assertFalse(x.closed)
61+
self.assertFalse('__IOBase_closed' in dir(x))
62+
x.close()
63+
self.assertTrue(x.closed)
64+
self.assertEqual(True, getattr(x, '__IOBase_closed'))
65+
66+
def test_flush(self):
67+
x = _io._IOBase()
68+
x.flush()
69+
x.close()
70+
self.assertRaises(ValueError, x.flush)
71+
# the value of __IOBase_closed does not matter, only its presence
72+
setattr(x, '__IOBase_closed', False)
73+
self.assertRaises(ValueError, x.flush)
74+
delattr(x, '__IOBase_closed')
75+
x.flush()
76+
77+
def test_close_calls_flush_once(self):
78+
flush_called = 0
79+
80+
class X(_io._IOBase):
81+
def flush(self):
82+
nonlocal flush_called
83+
flush_called += 1
84+
85+
x = X()
86+
x.close()
87+
self.assertEqual(1, flush_called)
88+
x.close()
89+
self.assertEqual(1, flush_called)
90+
# the value of __IOBase_closed does not matter, only its presence
91+
setattr(x, '__IOBase_closed', False)
92+
x.close()
93+
self.assertEqual(1, flush_called)
94+
delattr(x, '__IOBase_closed')
95+
x.close()
96+
self.assertEqual(2, flush_called)
97+
98+
def test_close_chains_exceptions(self):
99+
class X(_io._IOBase):
100+
def flush(self):
101+
raise ValueError('abc')
102+
103+
def __setattr__(self, key, value):
104+
raise ValueError('xyz')
105+
106+
try:
107+
X().close()
108+
self.fail('close() did not raise an exception')
109+
except ValueError as e:
110+
self.assertEqual('xyz', e.args[0])
111+
self.assertEqual('abc', e.__context__.args[0])
112+
113+
def test_unsupported(self):
114+
self.assertRaises(_io.UnsupportedOperation, _io._IOBase().seek)
115+
self.assertRaises(_io.UnsupportedOperation, _io._IOBase().truncate)
116+
self.assertRaises(_io.UnsupportedOperation, _io._IOBase().fileno)
117+
118+
def test_tell_call_seek(self):
119+
check = self.assertEqual
120+
121+
class X(_io._IOBase):
122+
def seek(self, *args):
123+
check((0, 1), args)
124+
return 42
125+
126+
self.assertEqual(42, X().tell())
127+
128+
def test_check_closed(self):
129+
ret_val = False
130+
131+
class X(_io._IOBase):
132+
@property
133+
def closed(self):
134+
return ret_val
135+
136+
X()._checkClosed()
137+
ret_val = True
138+
self.assertRaises(ValueError, X()._checkClosed)
139+
ret_val = 42 # _checkClosed accepts anything that evaluates as True
140+
self.assertRaises(ValueError, X()._checkClosed)
141+
ret_val = (1, )
142+
self.assertRaises(ValueError, X()._checkClosed)
143+
144+
def test_check_seekable(self):
145+
self.assertFalse(_io._IOBase().seekable())
146+
ret_val = False
147+
148+
class X(_io._IOBase):
149+
def seekable(self):
150+
return ret_val
151+
152+
self.assertRaises(_io.UnsupportedOperation, X()._checkSeekable)
153+
ret_val = True
154+
self.assertTrue(X()._checkSeekable())
155+
ret_val = 42 # _checkSeekable accepts only explicit True
156+
self.assertRaises(_io.UnsupportedOperation, X()._checkSeekable)
157+
ret_val = (1, )
158+
self.assertRaises(_io.UnsupportedOperation, X()._checkSeekable)
159+
160+
def test_check_readable(self):
161+
self.assertFalse(_io._IOBase().readable())
162+
ret_val = False
163+
164+
class X(_io._IOBase):
165+
def readable(self):
166+
return ret_val
167+
168+
self.assertRaises(_io.UnsupportedOperation, X()._checkReadable)
169+
ret_val = True
170+
self.assertTrue(X()._checkReadable())
171+
ret_val = 42 # _checkReadable accepts only explicit True
172+
self.assertRaises(_io.UnsupportedOperation, X()._checkReadable)
173+
ret_val = (1, )
174+
self.assertRaises(_io.UnsupportedOperation, X()._checkReadable)
175+
176+
def test_check_writable(self):
177+
self.assertFalse(_io._IOBase().writable())
178+
ret_val = False
179+
180+
class X(_io._IOBase):
181+
def writable(self):
182+
return ret_val
183+
184+
self.assertRaises(_io.UnsupportedOperation, X()._checkWritable)
185+
ret_val = True
186+
self.assertTrue(X()._checkWritable())
187+
ret_val = 42 # _checkWritable accepts only explicit True
188+
self.assertRaises(_io.UnsupportedOperation, X()._checkWritable)
189+
ret_val = (1, )
190+
self.assertRaises(_io.UnsupportedOperation, X()._checkWritable)
191+
192+
def test_check_properties(self):
193+
class X(_io._IOBase):
194+
@property
195+
def seekable(self):
196+
return True
197+
@property
198+
def readable(self):
199+
return True
200+
@property
201+
def writable(self):
202+
return True
203+
# _checkSeekable calls seekable(), but we define it as a property by mistake
204+
self.assertRaises(TypeError, X()._checkSeekable)
205+
self.assertRaises(TypeError, X()._checkReadable)
206+
self.assertRaises(TypeError, X()._checkWritable)
207+
208+
def test_enter(self):
209+
x = _io._IOBase()
210+
self.assertIs(x, x.__enter__())
211+
x.close()
212+
self.assertRaises(ValueError, x.__enter__)
213+
214+
def test_exit_dispatches_to_close(self):
215+
class X(_io._IOBase):
216+
def close(self):
217+
return 42
218+
219+
self.assertEqual(42, X().__exit__())
220+
221+
def test_exit_accepts_varargs(self):
222+
x = _io._IOBase()
223+
x.__exit__(1, 2, 3)
224+
with self.assertRaises(TypeError):
225+
x.__exit__(kw=1)
226+
227+
def test_isatty(self):
228+
x = _io._IOBase()
229+
self.assertFalse(x.isatty())
230+
x.close()
231+
self.assertRaises(ValueError, x.isatty)
232+
233+
def test_iter(self):
234+
x = _io._IOBase()
235+
self.assertIs(x, x.__iter__())
236+
x.close()
237+
self.assertRaises(ValueError, x.__iter__)
238+
239+
def test_methods_do_not_dispatch_to_checkClosed(self):
240+
class X(_io._IOBase):
241+
def _checkClosed(self):
242+
raise NotImplementedError()
243+
244+
x = X()
245+
self.assertIs(x, x.__enter__())
246+
self.assertIs(x, x.__iter__())
247+
self.assertFalse(x.isatty())
248+
x.writelines([])
249+
250+
def test_next(self):
251+
it = iter(['aaa', 'bbb', ''])
252+
253+
class X(_io._IOBase):
254+
def readline(self, limit=-1):
255+
return next(it)
256+
x = iter(X())
257+
self.assertEqual('aaa', next(x))
258+
self.assertEqual('bbb', next(x))
259+
self.assertRaises(StopIteration, next, x)
260+
261+
def test_writelines(self):
262+
buf = []
263+
264+
class X(_io._IOBase):
265+
def write(self, x):
266+
buf.append(x)
267+
X().writelines(['aaa', 'bbb'])
268+
self.assertEqual(['aaa', 'bbb'], buf)
269+
270+
def test_writelines_err(self):
271+
self.assertRaises(AttributeError, _io._IOBase().writelines, ['aaa', 'bbb'])
272+
273+
274+
if __name__ == '__main__':
275+
unittest.main()

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/PythonLanguage.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -705,7 +705,11 @@ public Shape getBuiltinTypeInstanceShape(PythonBuiltinClassType type) {
705705
Shape shape = builtinTypeInstanceShapes[ordinal];
706706
if (shape == null) {
707707
CompilerDirectives.transferToInterpreterAndInvalidate();
708-
shape = Shape.newBuilder(getEmptyShape()).addConstantProperty(HiddenAttributes.CLASS, type, 0).build();
708+
Shape.DerivedBuilder shapeBuilder = Shape.newBuilder(getEmptyShape()).addConstantProperty(HiddenAttributes.CLASS, type, 0);
709+
if (!type.isBuiltinWithDict()) {
710+
shapeBuilder.shapeFlags(PythonObject.HAS_SLOTS_BUT_NO_DICT_FLAG);
711+
}
712+
shape = shapeBuilder.build();
709713
builtinTypeInstanceShapes[ordinal] = shape;
710714
}
711715
return shape;

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/Python3Core.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@
102102
import com.oracle.graal.python.builtins.modules.WarningsModuleBuiltins;
103103
import com.oracle.graal.python.builtins.modules.WeakRefModuleBuiltins;
104104
import com.oracle.graal.python.builtins.modules.ZipImportModuleBuiltins;
105+
import com.oracle.graal.python.builtins.modules.io.IOBaseBuiltins;
106+
import com.oracle.graal.python.builtins.objects.NotImplementedBuiltins;
105107
import com.oracle.graal.python.builtins.modules.bz2.BZ2CompressorBuiltins;
106108
import com.oracle.graal.python.builtins.modules.bz2.BZ2DecompressorBuiltins;
107109
import com.oracle.graal.python.builtins.modules.bz2.BZ2ModuleBuiltins;
@@ -418,6 +420,7 @@ private static PythonBuiltins[] initializeBuiltins(boolean nativeAccessAllowed)
418420
new ZLibModuleBuiltins(),
419421
new ZlibCompressBuiltins(),
420422
new ZlibDecompressBuiltins(),
423+
new IOBaseBuiltins(),
421424
new BufferedReaderBuiltins(),
422425
new MMapModuleBuiltins(),
423426
new FcntlModuleBuiltins(),

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/PythonBuiltinClassType.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,8 @@ public enum PythonBuiltinClassType implements TruffleObject {
143143
BZ2Decompressor("BZ2Decompressor", "_bz2"),
144144
ZlibCompress("Compress", "zlib"),
145145
ZlibDecompress("Decompress", "zlib"),
146+
// TODO rename to _IOBase and remove its definition from _io.py once readline() and readlines() are implemetned
147+
PIOBase("_IO_Base", true, "_io", true, true),
146148
PBufferedReader("BufferedReader", "_io"),
147149
PStatResult("stat_result", "os", false),
148150
PTerminalSize("terminal_size", "os", false),
@@ -251,11 +253,12 @@ public enum PythonBuiltinClassType implements TruffleObject {
251253
// plain name without module
252254
private final String printName;
253255
private final boolean basetype;
256+
private final boolean isBuiltinWithDict;
254257

255258
// initialized in static constructor
256259
@CompilationFinal private PythonBuiltinClassType base;
257260

258-
PythonBuiltinClassType(String name, boolean isPublic, String module, boolean basetype) {
261+
PythonBuiltinClassType(String name, boolean isPublic, String module, boolean basetype, boolean isBuiltinWithDict) {
259262
this.name = name;
260263
this.publicInModule = isPublic ? module : null;
261264
if (module != null && module != BuiltinNames.BUILTINS) {
@@ -264,6 +267,11 @@ public enum PythonBuiltinClassType implements TruffleObject {
264267
printName = name;
265268
}
266269
this.basetype = basetype;
270+
this.isBuiltinWithDict = isBuiltinWithDict;
271+
}
272+
273+
PythonBuiltinClassType(String name, boolean isPublic, String module, boolean baseType) {
274+
this(name, isPublic, module, baseType, false);
267275
}
268276

269277
PythonBuiltinClassType(String name, String publicInModule, boolean basetype) {
@@ -298,6 +306,10 @@ public PythonBuiltinClassType getBase() {
298306
return base;
299307
}
300308

309+
public boolean isBuiltinWithDict() {
310+
return isBuiltinWithDict;
311+
}
312+
301313
public String getPublicInModule() {
302314
return publicInModule;
303315
}

0 commit comments

Comments
 (0)