Skip to content

Commit 550e14d

Browse files
committed
Make ForeignBoolean inherit from ForeignNumber and implement bool methods
1 parent 5779738 commit 550e14d

File tree

8 files changed

+297
-34
lines changed

8 files changed

+297
-34
lines changed

docs/user/Interoperability.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ Types not listed in the table below have no special interpretation in Python.
226226
| Interop Type | Inherits from | Python Interpretation |
227227
|:---------------|:----------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
228228
| `null` | ForeignNone, `NoneType` | `null` is like `None`. Important to know: interop `null` values are all identical to `None`. JavaScript defines two "null-like" values; `undefined` and `null`, which are *not* identical, but when passed to Python, they are treated so. |
229-
| `boolean` | ForeignBoolean | `boolean` behaves like Python booleans, including the fact that in Python, all booleans are also integers (1 and 0 for true and false, respectively). |
229+
| `boolean` | ForeignBoolean, ForeignNumber | `boolean` behaves like Python booleans, including the fact that in Python, all booleans are also integers (1 and 0 for true and false, respectively). |
230230
| `number` | ForeignNumber | `number` behaves like Python numbers. Python only has one integer and one floating point type, but ranges are imported in some places such as typed arrays. |
231231
| `string` | ForeignString, `str` | Behaves in the same way as a Python string. |
232232
| `buffer` | ForeignObject | Buffers are also a concept in Python's native API (albeit slightly different). Interop buffers are treated in the same was as Python buffers in some places (such as `memoryview`) to avoid copies of data. |
@@ -241,8 +241,10 @@ Types not listed in the table below have no special interpretation in Python.
241241
| `instantiable` | ForeignInstantiable | An `instantiable` object can be called just like a Python type, but never with keyword arguments. |
242242

243243
Foreign numbers inherit from `polyglot.ForeignNumber` and not `int` or `float` because `InteropLibrary` has currently no way to differentiate integers and floats.
244-
However, when foreign numbers are represented as Java primitives `byte`, `short`, `int`, `long`, they are considered Python `int` objects,
245-
and when foreign numbers are represented as Java primitives `float`, `double`, they are considered Python `float` objects.
244+
However:
245+
* When foreign numbers are represented as Java primitives `byte`, `short`, `int`, `long`, they are considered Python `int` objects.
246+
* When foreign numbers are represented as Java primitives `float`, `double`, they are considered Python `float` objects.
247+
* When foreign booleans re represented as Java primitives `boolean`, they are considered Python `bool` objects.
246248

247249
### Python to Interop Types
248250

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

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,8 +128,10 @@ def test_single_trait_classes(self):
128128

129129
for c in classes:
130130
self.assertIsInstance(c, type)
131-
if c != polyglot.ForeignObject:
132-
self.assertEqual(c.__base__, polyglot.ForeignObject)
131+
if c is polyglot.ForeignBoolean:
132+
self.assertIs(c.__base__, polyglot.ForeignNumber)
133+
elif c is not polyglot.ForeignObject:
134+
self.assertIs(c.__base__, polyglot.ForeignObject)
133135

134136
def test_get_class(self):
135137
def wrap(obj):
@@ -1248,10 +1250,43 @@ def test_foreign_number_list(self):
12481250
assert l > n
12491251
assert n < l
12501252

1253+
def test_foreign_number(self):
1254+
def wrap(obj):
1255+
return __graalpython__.foreign_wrapper(obj)
1256+
1257+
n = wrap(42)
1258+
self.assertEqual(type(n).mro(), [polyglot.ForeignNumber, polyglot.ForeignObject, object])
1259+
assert repr(n) == '42', repr(n)
1260+
assert str(n) == '42', str(n)
1261+
assert n
1262+
1263+
assert wrap(2) + wrap(3) == 5
1264+
assert wrap(2) - wrap(3) == -1
1265+
assert wrap(2) * wrap(3) == 6
1266+
assert wrap(7) / wrap(2) == 3.5
1267+
assert wrap(7) // wrap(2) == 3
1268+
1269+
assert wrap(0b1110) & wrap(0b0111) == 0b0110
1270+
assert wrap(0b1110) | wrap(0b0111) == 0b1111
1271+
assert wrap(0b1110) ^ wrap(0b0111) == 0b1001
1272+
1273+
# TODO test ~invert and more
1274+
12511275
def test_foreign_boolean(self):
12521276
def wrap(obj):
12531277
return __graalpython__.foreign_wrapper(obj)
12541278

1279+
self.assertEqual(type(wrap(True)).mro(), [polyglot.ForeignBoolean, polyglot.ForeignNumber, polyglot.ForeignObject, object])
1280+
assert repr(wrap(True)) == 'True'
1281+
assert repr(wrap(False)) == 'False'
1282+
assert str(wrap(True)) == 'True'
1283+
assert str(wrap(False)) == 'False'
1284+
assert wrap(True)
1285+
assert not wrap(False)
1286+
1287+
assert bool(wrap(True)) is True
1288+
assert bool(wrap(False)) is False
1289+
12551290
assert wrap(True) + wrap(2) == 3
12561291
assert wrap(False) + wrap(2) == 2
12571292
assert wrap(2) + wrap(True) == 3
@@ -1260,6 +1295,17 @@ def wrap(obj):
12601295
assert wrap(True) - wrap(2) == -1
12611296
assert wrap(2) - wrap(True) == 1
12621297

1298+
assert wrap(True) & wrap(True) is True
1299+
assert wrap(True) & wrap(False) is False
1300+
1301+
assert wrap(True) | wrap(False) is True
1302+
assert wrap(False) | wrap(False) is False
1303+
1304+
assert wrap(True) ^ wrap(False) is True
1305+
assert wrap(False) ^ wrap(False) is False
1306+
1307+
# TODO ~invert
1308+
12631309
def test_foreign_repl(self):
12641310
from java.util.logging import LogRecord
12651311
from java.util.logging import Level

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
import java.util.ServiceLoader;
6060
import java.util.logging.Level;
6161

62+
import com.oracle.graal.python.builtins.objects.foreign.ForeignBooleanBuiltins;
6263
import com.oracle.graal.python.builtins.objects.foreign.ForeignNumberBuiltins;
6364
import com.oracle.graal.python.builtins.objects.type.PythonManagedClass;
6465
import com.oracle.graal.python.nodes.object.GetForeignObjectClassNode;
@@ -486,6 +487,7 @@ private static PythonBuiltins[] initializeBuiltins(boolean nativeAccessAllowed,
486487
new IntBuiltins(),
487488
new ForeignObjectBuiltins(),
488489
new ForeignNumberBuiltins(),
490+
new ForeignBooleanBuiltins(),
489491
new ListBuiltins(),
490492
new DictBuiltins(),
491493
new DictReprBuiltin(),

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@
134134
import com.oracle.graal.python.builtins.objects.dict.DictValuesBuiltins;
135135
import com.oracle.graal.python.builtins.objects.dict.DictViewBuiltins;
136136
import com.oracle.graal.python.builtins.objects.floats.FloatBuiltins;
137+
import com.oracle.graal.python.builtins.objects.foreign.ForeignBooleanBuiltins;
137138
import com.oracle.graal.python.builtins.objects.foreign.ForeignNumberBuiltins;
138139
import com.oracle.graal.python.builtins.objects.foreign.ForeignObjectBuiltins;
139140
import com.oracle.graal.python.builtins.objects.function.BuiltinMethodDescriptor;
@@ -298,9 +299,8 @@ public enum PythonBuiltinClassType implements TruffleObject {
298299

299300
// Foreign
300301
ForeignObject("ForeignObject", J_POLYGLOT, Flags.PUBLIC_BASE_WDICT, ForeignObjectBuiltins.SLOTS),
301-
// TODO: should ForeignBoolean inherit from ForeignNumber? And/or from bool?
302-
ForeignBoolean("ForeignBoolean", J_POLYGLOT, Flags.PUBLIC_BASE_WDICT, FOREIGNNUMBER_M_FLAGS, ForeignNumberBuiltins.SLOTS),
303302
ForeignNumber("ForeignNumber", J_POLYGLOT, Flags.PUBLIC_BASE_WDICT, FOREIGNNUMBER_M_FLAGS, ForeignNumberBuiltins.SLOTS),
303+
ForeignBoolean("ForeignBoolean", J_POLYGLOT, Flags.PUBLIC_BASE_WDICT, FOREIGNNUMBER_M_FLAGS, ForeignBooleanBuiltins.SLOTS),
304304

305305
// bz2
306306
BZ2Compressor("BZ2Compressor", "_bz2"),
@@ -835,8 +835,8 @@ public final Shape getInstanceShape(PythonLanguage lang) {
835835

836836
Boolean.base = PInt;
837837

838-
ForeignBoolean.base = ForeignObject;
839838
ForeignNumber.base = ForeignObject;
839+
ForeignBoolean.base = ForeignNumber;
840840

841841
PBaseExceptionGroup.base = PBaseException;
842842
SystemExit.base = PBaseException;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
/*
2+
* Copyright (c) 2017, 2024, Oracle and/or its affiliates.
3+
* Copyright (c) 2014, Regents of the University of California
4+
*
5+
* All rights reserved.
6+
*
7+
* Redistribution and use in source and binary forms, with or without modification, are
8+
* permitted provided that the following conditions are met:
9+
*
10+
* 1. Redistributions of source code must retain the above copyright notice, this list of
11+
* conditions and the following disclaimer.
12+
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of
13+
* conditions and the following disclaimer in the documentation and/or other materials provided
14+
* with the distribution.
15+
*
16+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
17+
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18+
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
19+
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20+
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
21+
* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
22+
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
23+
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
24+
* OF THE POSSIBILITY OF SUCH DAMAGE.
25+
*/
26+
27+
package com.oracle.graal.python.builtins.objects.foreign;
28+
29+
import com.oracle.graal.python.annotations.Slot;
30+
import com.oracle.graal.python.annotations.Slot.SlotKind;
31+
import com.oracle.graal.python.builtins.Builtin;
32+
import com.oracle.graal.python.builtins.CoreFunctions;
33+
import com.oracle.graal.python.builtins.PythonBuiltinClassType;
34+
import com.oracle.graal.python.builtins.PythonBuiltins;
35+
import com.oracle.graal.python.builtins.objects.ints.PInt;
36+
import com.oracle.graal.python.builtins.objects.type.TpSlots;
37+
import com.oracle.graal.python.builtins.objects.type.slots.TpSlotInquiry.NbBoolBuiltinNode;
38+
import com.oracle.graal.python.lib.PyObjectStrAsTruffleStringNode;
39+
import com.oracle.graal.python.nodes.expression.BinaryArithmetic.BitAndNode;
40+
import com.oracle.graal.python.nodes.expression.BinaryArithmetic.BitOrNode;
41+
import com.oracle.graal.python.nodes.expression.BinaryArithmetic.BitXorNode;
42+
import com.oracle.graal.python.nodes.function.PythonBuiltinBaseNode;
43+
import com.oracle.graal.python.nodes.function.builtins.PythonBinaryBuiltinNode;
44+
import com.oracle.graal.python.nodes.function.builtins.PythonUnaryBuiltinNode;
45+
import com.oracle.graal.python.runtime.GilNode;
46+
import com.oracle.truffle.api.CompilerDirectives;
47+
import com.oracle.truffle.api.dsl.Bind;
48+
import com.oracle.truffle.api.dsl.Cached;
49+
import com.oracle.truffle.api.dsl.GenerateNodeFactory;
50+
import com.oracle.truffle.api.dsl.GenerateUncached;
51+
import com.oracle.truffle.api.dsl.NodeFactory;
52+
import com.oracle.truffle.api.dsl.Specialization;
53+
import com.oracle.truffle.api.frame.VirtualFrame;
54+
import com.oracle.truffle.api.interop.InteropLibrary;
55+
import com.oracle.truffle.api.interop.UnsupportedMessageException;
56+
import com.oracle.truffle.api.library.CachedLibrary;
57+
import com.oracle.truffle.api.nodes.Node;
58+
59+
import java.util.List;
60+
61+
import static com.oracle.graal.python.nodes.SpecialMethodNames.J___AND__;
62+
import static com.oracle.graal.python.nodes.SpecialMethodNames.J___INDEX__;
63+
import static com.oracle.graal.python.nodes.SpecialMethodNames.J___OR__;
64+
import static com.oracle.graal.python.nodes.SpecialMethodNames.J___RAND__;
65+
import static com.oracle.graal.python.nodes.SpecialMethodNames.J___REPR__;
66+
import static com.oracle.graal.python.nodes.SpecialMethodNames.J___ROR__;
67+
import static com.oracle.graal.python.nodes.SpecialMethodNames.J___RXOR__;
68+
import static com.oracle.graal.python.nodes.SpecialMethodNames.J___STR__;
69+
import static com.oracle.graal.python.nodes.SpecialMethodNames.J___XOR__;
70+
71+
/*
72+
* This class handles foreign booleans and reimplements the methods of Python bool.
73+
* We avoid inheriting from bool as that brings many complications, and we want to reuse the logic from ForeignNumberBuiltins.
74+
*
75+
* NOTE: We are not using IndirectCallContext here in this file
76+
* because it seems unlikely that these interop messages would call back to Python
77+
* and that we would also need precise frame info for that case.
78+
* Adding it shouldn't hurt peak, but might be a non-trivial overhead in interpreter.
79+
*/
80+
@CoreFunctions(extendClasses = PythonBuiltinClassType.ForeignBoolean)
81+
public final class ForeignBooleanBuiltins extends PythonBuiltins {
82+
public static TpSlots SLOTS = ForeignBooleanBuiltinsSlotsGen.SLOTS;
83+
84+
@Override
85+
protected List<? extends NodeFactory<? extends PythonBuiltinBaseNode>> getNodeFactories() {
86+
return ForeignBooleanBuiltinsFactory.getFactories();
87+
}
88+
89+
@Slot(SlotKind.nb_bool)
90+
@GenerateUncached
91+
@GenerateNodeFactory
92+
abstract static class BoolNode extends NbBoolBuiltinNode {
93+
@Specialization(limit = "getCallSiteInlineCacheMaxDepth()")
94+
static boolean bool(Object receiver,
95+
@CachedLibrary("receiver") InteropLibrary lib,
96+
@Cached GilNode gil) {
97+
gil.release(true);
98+
try {
99+
return lib.asBoolean(receiver);
100+
} catch (UnsupportedMessageException e) {
101+
throw CompilerDirectives.shouldNotReachHere(e);
102+
} finally {
103+
gil.acquire();
104+
}
105+
}
106+
}
107+
108+
@Builtin(name = J___INDEX__, minNumOfPositionalArgs = 1)
109+
@GenerateNodeFactory
110+
abstract static class IndexNode extends PythonUnaryBuiltinNode {
111+
@Specialization(limit = "3")
112+
protected static Object doIt(Object object,
113+
@CachedLibrary("object") InteropLibrary lib,
114+
@Cached GilNode gil) {
115+
gil.release(true);
116+
try {
117+
try {
118+
return PInt.intValue(lib.asBoolean(object));
119+
} catch (UnsupportedMessageException e) {
120+
throw CompilerDirectives.shouldNotReachHere("foreign value claims to be a boolean but isn't");
121+
}
122+
} finally {
123+
gil.acquire();
124+
}
125+
}
126+
}
127+
128+
@Builtin(name = J___STR__, minNumOfPositionalArgs = 1)
129+
@GenerateNodeFactory
130+
abstract static class StrNode extends PythonUnaryBuiltinNode {
131+
@Specialization
132+
Object str(VirtualFrame frame, Object object,
133+
@Bind("this") Node inliningTarget,
134+
@CachedLibrary(limit = "3") InteropLibrary lib,
135+
@Cached GilNode gil,
136+
@Cached PyObjectStrAsTruffleStringNode strNode) {
137+
final Object value;
138+
try {
139+
gil.release(true);
140+
try {
141+
value = lib.asBoolean(object);
142+
} finally {
143+
gil.acquire();
144+
}
145+
} catch (UnsupportedMessageException e) {
146+
throw CompilerDirectives.shouldNotReachHere(e);
147+
}
148+
149+
return strNode.execute(frame, inliningTarget, value);
150+
}
151+
}
152+
153+
@Builtin(name = J___REPR__, minNumOfPositionalArgs = 1)
154+
@GenerateNodeFactory
155+
abstract static class ReprNode extends StrNode {
156+
}
157+
158+
@Builtin(name = J___RAND__, minNumOfPositionalArgs = 2)
159+
@Builtin(name = J___AND__, minNumOfPositionalArgs = 2)
160+
@GenerateNodeFactory
161+
abstract static class AndNode extends PythonBinaryBuiltinNode {
162+
@Specialization(limit = "3")
163+
protected static Object op(VirtualFrame frame, Object left, Object right,
164+
@Cached BitAndNode andNode,
165+
@CachedLibrary("left") InteropLibrary lib,
166+
@Cached GilNode gil) {
167+
try {
168+
boolean leftBoolean;
169+
gil.release(true);
170+
try {
171+
leftBoolean = lib.asBoolean(left);
172+
} finally {
173+
gil.acquire();
174+
}
175+
return andNode.executeObject(frame, leftBoolean, right);
176+
} catch (UnsupportedMessageException e) {
177+
throw CompilerDirectives.shouldNotReachHere();
178+
}
179+
}
180+
}
181+
182+
@Builtin(name = J___ROR__, minNumOfPositionalArgs = 2)
183+
@Builtin(name = J___OR__, minNumOfPositionalArgs = 2)
184+
@GenerateNodeFactory
185+
abstract static class OrNode extends PythonBinaryBuiltinNode {
186+
@Specialization(limit = "3")
187+
protected static Object op(VirtualFrame frame, Object left, Object right,
188+
@Cached BitOrNode orNode,
189+
@CachedLibrary("left") InteropLibrary lib,
190+
@Cached GilNode gil) {
191+
try {
192+
boolean leftBoolean;
193+
gil.release(true);
194+
try {
195+
leftBoolean = lib.asBoolean(left);
196+
} finally {
197+
gil.acquire();
198+
}
199+
return orNode.executeObject(frame, leftBoolean, right);
200+
} catch (UnsupportedMessageException e) {
201+
throw CompilerDirectives.shouldNotReachHere();
202+
}
203+
}
204+
}
205+
206+
@Builtin(name = J___RXOR__, minNumOfPositionalArgs = 2)
207+
@Builtin(name = J___XOR__, minNumOfPositionalArgs = 2)
208+
@GenerateNodeFactory
209+
abstract static class XorNode extends PythonBinaryBuiltinNode {
210+
@Specialization(limit = "3")
211+
protected static Object op(VirtualFrame frame, Object left, Object right,
212+
@Cached BitXorNode xorNode,
213+
@CachedLibrary("left") InteropLibrary lib,
214+
@Cached GilNode gil) {
215+
try {
216+
boolean leftBoolean;
217+
gil.release(true);
218+
try {
219+
leftBoolean = lib.asBoolean(left);
220+
} finally {
221+
gil.acquire();
222+
}
223+
return xorNode.executeObject(frame, leftBoolean, right);
224+
} catch (UnsupportedMessageException e) {
225+
throw CompilerDirectives.shouldNotReachHere();
226+
}
227+
}
228+
}
229+
}

0 commit comments

Comments
 (0)