Skip to content

Commit 4ae2f77

Browse files
committed
[GR-38895] Allow == comparison between primitive and Java objects using valueOf()/toString().
PullRequest: js/2469
2 parents 64104b8 + 47a8869 commit 4ae2f77

25 files changed

+590
-378
lines changed

graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/JavaBuiltinsTest.java

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2019, 2022, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -112,20 +112,24 @@ public void testJavaTypeName() {
112112
}
113113

114114
@Test
115-
public void testJavaExtend() {
116-
// String result = test("var t = Java.type('java.lang.Object'); var e = Java.extend(t);
117-
// ''+e;");
118-
// assertEquals("class com.oracle.truffle.js.javaadapters.java.lang.Object", result);
119-
120-
// result = test("var t = Java.type('java.lang.Object'); var e = Java.extend(t, {a:'foo'});
121-
// ''+e;");
122-
// assertEquals("class com.oracle.truffle.js.javaadapters.java.lang.Object", result);
123-
115+
public void testJavaExtendArgumentError() {
124116
test("Java.extend();", "needs at least one argument");
125117
test("Java.extend({});", "needs at least one type argument");
126118
test("Java.extend(1);", "needs Java types");
127119
}
128120

121+
@Test
122+
public void testJavaExtend() {
123+
String result;
124+
result = test("var O = Java.type('java.lang.Object');\n" +
125+
"var E = Java.extend(O); new E({a: 'foo', toString() {return 'EXTENDED';}}).toString();");
126+
assertEquals("EXTENDED", result);
127+
128+
result = test("var O = Java.type('java.lang.Object');\n" +
129+
"var E = Java.extend(O, {a: 'foo', toString() {return 'EXTENDED';}}); new E().toString();");
130+
assertEquals("EXTENDED", result);
131+
}
132+
129133
@Test
130134
public void testJavaFrom() {
131135
String result = test("var t = Java.from(arg); ''+t;", null, true, new Object[]{1, 2, 3});
@@ -143,16 +147,19 @@ public void testJavaFrom() {
143147

144148
@Test
145149
public void testJavaTo() {
146-
String result = test("var t = Java.to({a:'foo'}); ''+t;");
150+
String result = test("var t = Java.to({a:'foo'}); '[' + t + ']';");
147151
assertEquals("[]", result);
148152

149-
result = test("var t = Java.to({a:'foo'},arg); ''+t;", null, true, (new Object[0]).getClass());
153+
result = test("var t = Java.to({a:'foo'},arg); '[' + t + ']';", null, true, (new Object[0]).getClass());
150154
assertEquals("[]", result);
151155

152-
test("var t = Java.to({a:'foo'}, 'int[]'); ''+t;");
156+
result = test("var t = Java.to({a:'foo'}, 'int[]'); '[' + t + ']';");
153157
assertEquals("[]", result);
154158

155159
test("var t = Java.to(1, 'int[]'); ''+t;", "is not an Object");
160+
161+
result = test("var t = Java.to([3,1,4,1,5,9], 'int[]'); '[' + t + ']';");
162+
assertEquals("[3,1,4,1,5,9]", result);
156163
}
157164

158165
@Test
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
/*
2+
* Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* The Universal Permissive License (UPL), Version 1.0
6+
*
7+
* Subject to the condition set forth below, permission is hereby granted to any
8+
* person obtaining a copy of this software, associated documentation and/or
9+
* data (collectively the "Software"), free of charge and under any and all
10+
* copyright rights in the Software, and any and all patent rights owned or
11+
* freely licensable by each licensor hereunder covering either (i) the
12+
* unmodified Software as contributed to or provided by such licensor, or (ii)
13+
* the Larger Works (as defined below), to deal in both
14+
*
15+
* (a) the Software, and
16+
*
17+
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
18+
* one is included with the Software each a "Larger Work" to which the Software
19+
* is contributed by such licensors),
20+
*
21+
* without restriction, including without limitation the rights to copy, create
22+
* derivative works of, display, perform, and distribute the Software and make,
23+
* use, sell, offer for sale, import, export, have made, and have sold the
24+
* Software and the Larger Work(s), and to sublicense the foregoing rights on
25+
* either these or other terms.
26+
*
27+
* This license is subject to the following condition:
28+
*
29+
* The above copyright notice and either this complete permission notice or at a
30+
* minimum a reference to the UPL must be included in all copies or substantial
31+
* portions of the Software.
32+
*
33+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
39+
* SOFTWARE.
40+
*/
41+
package com.oracle.truffle.js.test.interop;
42+
43+
import static com.oracle.truffle.js.lang.JavaScriptLanguage.ID;
44+
import static com.oracle.truffle.js.runtime.JSContextOptions.FOREIGN_OBJECT_PROTOTYPE_NAME;
45+
import static org.junit.Assert.assertEquals;
46+
import static org.junit.Assert.assertFalse;
47+
import static org.junit.Assert.assertTrue;
48+
49+
import java.time.Instant;
50+
import java.util.List;
51+
import java.util.Map;
52+
53+
import org.graalvm.polyglot.Context;
54+
import org.graalvm.polyglot.HostAccess;
55+
import org.graalvm.polyglot.Value;
56+
import org.graalvm.polyglot.proxy.ProxyArray;
57+
import org.graalvm.polyglot.proxy.ProxyExecutable;
58+
import org.graalvm.polyglot.proxy.ProxyObject;
59+
import org.junit.Test;
60+
import org.junit.runner.RunWith;
61+
import org.junit.runners.Parameterized;
62+
import org.junit.runners.Parameterized.Parameter;
63+
import org.junit.runners.Parameterized.Parameters;
64+
65+
import com.oracle.truffle.js.test.JSTest;
66+
67+
@RunWith(Parameterized.class)
68+
public class ForeignObjectToPrimitiveTest {
69+
70+
@Parameters(name = "{0}")
71+
public static List<Boolean> data() {
72+
return List.of(Boolean.FALSE, Boolean.TRUE);
73+
}
74+
75+
@Parameter(value = 0) public boolean foreignObjectPrototype;
76+
77+
private Context newContext() {
78+
return JSTest.newContextBuilder(ID).option(FOREIGN_OBJECT_PROTOTYPE_NAME, Boolean.toString(foreignObjectPrototype)).allowHostAccess(HostAccess.ALL).build();
79+
}
80+
81+
@Test
82+
public void testProxyObjectVsString() {
83+
try (Context context = newContext()) {
84+
Value isLooselyEqual = makeIsLooselyEqual(context);
85+
86+
Map<String, Object> members = Map.of(
87+
"toString", (ProxyExecutable) (args) -> "toString()",
88+
"valueOf", (ProxyExecutable) (args) -> "valueOf()");
89+
Object object = ProxyObject.fromMap(members);
90+
assertEquals("toString()", toString(context, object));
91+
assertEquals("valueOf()", valueOf(context, object));
92+
assertTrue("string == object", isLooselyEqual.execute("valueOf()", object).asBoolean());
93+
assertTrue("object == string", isLooselyEqual.execute(object, "valueOf()").asBoolean());
94+
95+
// isIdentical
96+
assertTrue("object == object", isLooselyEqual.execute(object, object).asBoolean());
97+
assertFalse("object == object", isLooselyEqual.execute(object, ProxyObject.fromMap(members)).asBoolean());
98+
assertFalse("object == object", isLooselyEqual.execute(ProxyObject.fromMap(members), object).asBoolean());
99+
}
100+
}
101+
102+
@Test
103+
public void testProxyObjectVsNull() {
104+
try (Context context = newContext()) {
105+
Value isLooselyEqual = makeIsLooselyEqual(context);
106+
107+
Object object = ProxyObject.fromMap(Map.of(
108+
"toString", (ProxyExecutable) (args) -> {
109+
throw new AssertionError("should not be called");
110+
},
111+
"valueOf", (ProxyExecutable) (args) -> {
112+
throw new AssertionError("should not be called");
113+
}));
114+
assertFalse("null == object", isLooselyEqual.execute(null, object).asBoolean());
115+
assertFalse("object == null", isLooselyEqual.execute(object, null).asBoolean());
116+
}
117+
}
118+
119+
@Test
120+
public void testProxyArrayVsString() {
121+
try (Context context = newContext()) {
122+
Value isLooselyEqual = makeIsLooselyEqual(context);
123+
124+
Object[] elements = {"fun", "with", "proxy", "array"};
125+
ProxyArray array = ProxyArray.fromArray(elements);
126+
assertEquals("fun,with,proxy,array", toString(context, array));
127+
assertEquals("fun,with,proxy,array", valueOf(context, array));
128+
assertTrue("string == array", isLooselyEqual.execute("fun,with,proxy,array", array).asBoolean());
129+
assertTrue("array == string", isLooselyEqual.execute(array, "fun,with,proxy,array").asBoolean());
130+
131+
// isIdentical
132+
assertTrue("array == array", isLooselyEqual.execute(array, array).asBoolean());
133+
assertFalse("array == array", isLooselyEqual.execute(array, ProxyArray.fromArray(elements)).asBoolean());
134+
assertFalse("array == array", isLooselyEqual.execute(ProxyArray.fromArray(elements), array).asBoolean());
135+
}
136+
}
137+
138+
@Test
139+
public void testHostArray() {
140+
try (Context context = newContext()) {
141+
Value isLooselyEqual = makeIsLooselyEqual(context);
142+
143+
Object[] array = List.of("fun", "with", "proxy", "array").toArray();
144+
String expectedString = "fun,with,proxy,array";
145+
assertEquals(expectedString, toString(context, array));
146+
assertEquals(expectedString, valueOf(context, array));
147+
assertTrue("string == array", isLooselyEqual.execute(expectedString, array).asBoolean());
148+
assertTrue("array == string", isLooselyEqual.execute(array, expectedString).asBoolean());
149+
150+
// isIdentical
151+
assertTrue("array == array", isLooselyEqual.execute(array, array).asBoolean());
152+
assertFalse("array == array", isLooselyEqual.execute(array, array.clone()).asBoolean());
153+
assertFalse("array == array", isLooselyEqual.execute(array.clone(), array).asBoolean());
154+
}
155+
}
156+
157+
@Test
158+
public void testHostList() {
159+
try (Context context = newContext()) {
160+
Value isLooselyEqual = makeIsLooselyEqual(context);
161+
162+
List<String> list = List.of("fun", "with", "proxy", "array");
163+
String expectedString = "[fun, with, proxy, array]";
164+
assertEquals(expectedString, toString(context, list));
165+
assertEquals(expectedString, valueOf(context, list));
166+
assertTrue("string == array", isLooselyEqual.execute(expectedString, list).asBoolean());
167+
assertTrue("array == string", isLooselyEqual.execute(list, expectedString).asBoolean());
168+
169+
// isIdentical
170+
assertTrue("array == array", isLooselyEqual.execute(list, list).asBoolean());
171+
assertFalse("array == array", isLooselyEqual.execute(list, List.of(list.toArray())).asBoolean());
172+
assertFalse("array == array", isLooselyEqual.execute(List.of(list.toArray()), list).asBoolean());
173+
}
174+
}
175+
176+
@Test
177+
public void testHostObjectVsString() {
178+
try (Context context = newContext()) {
179+
Value isLooselyEqual = makeIsLooselyEqual(context);
180+
181+
Object object = new ValueOfTestObject();
182+
assertEquals("toString()", toString(context, object));
183+
assertEquals("valueOf()", valueOf(context, object));
184+
assertTrue("string == object", isLooselyEqual.execute("valueOf()", object).asBoolean());
185+
assertTrue("object == string", isLooselyEqual.execute(object, "valueOf()").asBoolean());
186+
187+
// isIdentical
188+
assertTrue("object == object", isLooselyEqual.execute(object, object).asBoolean());
189+
assertFalse("object == object", isLooselyEqual.execute(object, new ValueOfTestObject()).asBoolean());
190+
assertFalse("object == object", isLooselyEqual.execute(new ValueOfTestObject(), object).asBoolean());
191+
}
192+
}
193+
194+
public static class ValueOfTestObject {
195+
public String valueOf() {
196+
return "valueOf()";
197+
}
198+
199+
@Override
200+
public String toString() {
201+
return "toString()";
202+
}
203+
}
204+
205+
@Test
206+
public void testHostInstantToPrimitive() {
207+
try (Context context = newContext()) {
208+
Value isLooselyEqual = makeIsLooselyEqual(context);
209+
210+
long epochMilli = 1645568542000L; // 2022-02-22T22:22:22Z
211+
Instant instant = Instant.ofEpochMilli(epochMilli);
212+
assertEquals(instant.toString(), toString(context, instant));
213+
assertEquals(String.valueOf(epochMilli), valueOf(context, instant));
214+
assertTrue("string == instant", isLooselyEqual.execute(String.valueOf(epochMilli), instant).asBoolean());
215+
assertTrue("instant == string", isLooselyEqual.execute(instant, String.valueOf(epochMilli)).asBoolean());
216+
assertTrue("number == instant", isLooselyEqual.execute(epochMilli, instant).asBoolean());
217+
assertTrue("instant == number", isLooselyEqual.execute(instant, epochMilli).asBoolean());
218+
219+
// isIdentical
220+
assertTrue("instant == instant", isLooselyEqual.execute(instant, instant).asBoolean());
221+
assertFalse("instant == instant", isLooselyEqual.execute(instant, Instant.ofEpochMilli(instant.toEpochMilli())).asBoolean());
222+
assertFalse("instant == instant", isLooselyEqual.execute(Instant.ofEpochMilli(instant.toEpochMilli()), instant).asBoolean());
223+
}
224+
}
225+
226+
private static String toString(Context context, Object value) {
227+
return context.eval(ID, "String").execute(value).asString();
228+
}
229+
230+
private static String valueOf(Context context, Object value) {
231+
return context.eval(ID, "x => x + []").execute(value).asString();
232+
}
233+
234+
private static Value makeIsLooselyEqual(Context context) {
235+
return context.eval(ID, "(function(a, b){return a == b;})");
236+
}
237+
}

graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/interop/InteropArrayTest.java

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -391,18 +391,23 @@ public void testPrintJavaArrayInJS() {
391391
ByteArrayOutputStream baos = new ByteArrayOutputStream();
392392
try (Context context = JSTest.newContextBuilder().allowHostAccess(accessWithArrays).out(baos).err(baos).build()) {
393393
context.getBindings(ID).putMember("javaArray", new ToBePassedToJS());
394-
context.eval(ID, "var arrayFromJava = javaArray.methodThatReturnsArrayWithJSObject(" +
395-
"{foo: 'bar', number: 42, f: function() { return 'yes';}, array: [2, 4, 8]});" +
394+
context.eval(ID, "" +
395+
"var jsObj = {foo: 'bar', number: 42, f: function() { return 'yes';}, array: [2, 4, 8]};\n" +
396+
"var arrayFromJava = javaArray.methodThatReturnsArrayWithJSObject(jsObj);\n" +
396397
"console.log(arrayFromJava);");
397-
assertEquals("[41, {foo: \"bar\", number: 42, f: function() { return 'yes';}, array: [2, 4, 8]}, \"string\", {x: 42, y: \"foo\"}]", baos.toString().trim());
398+
assertEquals("41,[object Object],string,[object Object]", baos.toString().trim());
399+
baos.reset();
400+
context.eval(ID, "Object.prototype.toString = function() { return JSON.stringify(this); };\n" +
401+
"console.log(arrayFromJava);");
402+
assertEquals("41,{\"foo\":\"bar\",\"number\":42,\"array\":[2,4,8]},string,{\"x\":42,\"y\":\"foo\"}", baos.toString().trim());
398403
baos.reset();
399404
context.eval(ID, "var arrayFromJava = javaArray.methodThatReturnsArray();" +
400405
"console.log(arrayFromJava);");
401-
assertEquals("[3, 4, 1, 5]", baos.toString().trim());
406+
assertEquals("3,4,1,5", baos.toString().trim());
402407
baos.reset();
403408
context.eval(ID, "var arrayFromJavaAsValue = javaArray.methodThatReturnsArrayAsValue();" +
404409
"console.log(arrayFromJavaAsValue);");
405-
assertEquals("[3, 4, 1, 5]", baos.toString().trim());
410+
assertEquals("3,4,1,5", baos.toString().trim());
406411
}
407412
}
408413

graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/interop/ToNumberTest.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2019, 2022, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -56,22 +56,22 @@ public void testForeignArrayLength() {
5656
try (Context context = JSTest.newContextBuilder().allowAllAccess(true).build()) {
5757
String jscode = "var array = [42,211];\n" +
5858
"array.length = new java.lang.StringBuilder('1');\n" +
59-
"array.length == 1 && array[0] == 42";
59+
"(array.length == 1 && array[0] == 42) || array.toString()";
6060
Value value = context.eval(JavaScriptLanguage.ID, jscode);
61-
assertTrue(value.isBoolean());
62-
assertTrue(value.asBoolean());
61+
assertTrue(value.toString(), value.isBoolean());
62+
assertTrue(value.toString(), value.asBoolean());
6363
}
6464
}
6565

6666
@Test
6767
public void testForeignCompareFnResult() {
6868
try (Context context = JSTest.newContextBuilder().allowAllAccess(true).build()) {
6969
String jscode = "var array = [211,42];\n" +
70-
"array.sort(function(x,y) { return java.math.BigInteger.valueOf(x-y); })\n" +
71-
"array.length == 2 && array[0] == 42 && array[1] == 211";
70+
"array.sort(function(x,y) { return java.math.BigInteger.valueOf(x - y); });\n" +
71+
"(array.length == 2 && array[0] == 42 && array[1] == 211) || array.toString()";
7272
Value value = context.eval(JavaScriptLanguage.ID, jscode);
73-
assertTrue(value.isBoolean());
74-
assertTrue(value.asBoolean());
73+
assertTrue(value.toString(), value.isBoolean());
74+
assertTrue(value.toString(), value.asBoolean());
7575
}
7676
}
7777

0 commit comments

Comments
 (0)