Skip to content

Commit 92f882c

Browse files
committed
Changed ToPrimitive operation for foreign values
1 parent 873287c commit 92f882c

File tree

12 files changed

+373
-39
lines changed

12 files changed

+373
-39
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ The main focus is on user-observable behavior of the engine.
99
* ScriptEngine: Fixed "Multiple applicable overloads found" error in nashorn-compat mode, see [issue #286](https://github.com/graalvm/graaljs/issues/286).
1010
* ScriptEngine: Enabled low precedence lossy number, string-to-boolean, and number-to-boolean conversions in nashorn-compat mode.
1111
* `js.foreign-object-prototype` is a supported option to set JavaScript prototypes for foreign objects mimicing JavaScript types. It was renamed from `js.experimental-foreign-object-prototype`.
12+
* Changed `ToPrimitive` abstract operation to follow the specification for foreign objects. `InteropLibrary.toDisplayString` is not used by `ToPrimitive/ToString` conversions anymore.
1213

1314
## Version 20.2.0
1415
* Implemented the [Intl.NumberFormat Unified API](https://github.com/tc39/proposal-unified-intl-numberformat) proposal.

graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/polyglot/ForeignConsolePrintTest.java

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,9 @@ public void tearDown() {
7575
public void testForeignArray() {
7676
final String script = "(function (a) { return '' + a; })";
7777
final Value fun = ctx.eval(JavaScriptLanguage.ID, script);
78-
Value res = fun.execute(new ArrayTruffleObject(new int[]{0, 1, 2, 3, 4}));
78+
Value res = fun.execute(new ArrayTruffleObject(new Object[]{0, 1, 2, 3, 4}));
7979
String sRes = res.asString();
80-
assertEquals("(5)[0, 1, 2, 3, 4]", sRes);
80+
assertEquals("0,1,2,3,4", sRes);
8181
}
8282

8383
@Test
@@ -112,15 +112,32 @@ public void testForeignObject() {
112112

113113
Value res = fun.execute(map);
114114
String sRes = res.asString();
115-
assertEquals("{x: 42, y: \"foo\"}", sRes);
115+
assertEquals("[object Object]", sRes);
116+
}
117+
118+
@Test
119+
public void testForeignArrayWithObjects() {
120+
final String script = "(function (a) { return '' + a; })";
121+
final Value fun = ctx.eval(JavaScriptLanguage.ID, script);
122+
123+
final ForeignTestMap map = new ForeignTestMap();
124+
map.getContainer().put("x", 42);
125+
map.getContainer().put("y", "foo");
126+
ArrayTruffleObject arr = new ArrayTruffleObject(new Object[]{0, "string", map, 3, new ForeignTestFunction("test", (arg) -> {
127+
return arg[0] + ", " + arg[1];
128+
})});
129+
130+
Value res = fun.execute(arr);
131+
String sRes = res.asString();
132+
assertEquals("0,string,[object Object],3,function test() { [native code] }", sRes);
116133
}
117134

118135
@ExportLibrary(InteropLibrary.class)
119136
static final class ArrayTruffleObject implements TruffleObject {
120137

121-
private final int[] array;
138+
private final Object[] array;
122139

123-
ArrayTruffleObject(int[] array) {
140+
ArrayTruffleObject(Object[] array) {
124141
this.array = array;
125142
}
126143

graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/polyglot/ForeignTestFunction.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,4 +98,14 @@ public Object toDisplayString(boolean allowSideEffects) {
9898
return "f()";
9999
}
100100
}
101+
102+
@ExportMessage
103+
public boolean hasExecutableName() {
104+
return true;
105+
}
106+
107+
@ExportMessage
108+
final Object getExecutableName() {
109+
return name;
110+
}
101111
}

graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/polyglot/JavaScriptLanguageTest.java

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,9 @@ public long getSize() {
113113
Value result = context.eval(JavaScriptLanguage.ID, "[array];");
114114
assertTrue(result.toString(), result.toString().contains("0, 1, 2, 3, 4"));
115115
result = context.eval(JavaScriptLanguage.ID, "String([array]);");
116-
assertTrue(result.toString(), result.asString().contains("0, 1, 2, 3, 4"));
116+
assertTrue(result.toString(), result.asString().contains("0,1,2,3,4"));
117117
result = context.eval(JavaScriptLanguage.ID, "'' + array;");
118-
assertTrue(result.toString(), result.asString().contains("0, 1, 2, 3, 4"));
118+
assertTrue(result.toString(), result.asString().contains("0,1,2,3,4"));
119119
}
120120
}
121121

@@ -126,9 +126,9 @@ public void testToStringForeignObject() {
126126
Value result = context.eval(JavaScriptLanguage.ID, "[obj];");
127127
assertTrue(result.toString(), result.toString().contains("{answer: 42}"));
128128
result = context.eval(JavaScriptLanguage.ID, "String([obj]);");
129-
assertTrue(result.toString(), result.asString().contains("{answer: 42}"));
129+
assertTrue(result.toString(), result.asString().contains("[object Object]"));
130130
result = context.eval(JavaScriptLanguage.ID, "'' + obj;");
131-
assertTrue(result.toString(), result.asString().contains("{answer: 42}"));
131+
assertTrue(result.toString(), result.asString().contains("[object Object]"));
132132
}
133133
}
134134

@@ -139,4 +139,25 @@ public void testToStringNestedArray() {
139139
assertEquals("(2)[1, [2, [3, Array(2)]]]", result.toString());
140140
}
141141
}
142+
143+
@Test
144+
public void testToPrimitiveHostObject() {
145+
try (Context context = Context.newBuilder(JavaScriptLanguage.ID).allowAllAccess(true).build()) {
146+
context.getBindings(JavaScriptLanguage.ID).putMember("obj", new TestHostObject());
147+
Value res = context.eval(JavaScriptLanguage.ID, "obj + obj");
148+
assertEquals(84, res.asInt());
149+
}
150+
}
151+
152+
public static final class TestHostObject {
153+
@SuppressWarnings("static-method")
154+
public int valueOf() {
155+
return 42;
156+
}
157+
158+
@Override
159+
public String toString() {
160+
return "string";
161+
}
162+
}
142163
}

graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/polyglot/PolyglotBuiltinTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -226,8 +226,8 @@ public void testIsInstantiable() {
226226

227227
@Test
228228
public void testCreateForeignObject() {
229-
assertEquals("{}", test("''+Polyglot.createForeignObject();"));
230-
assertEquals("{}", test("''+Polyglot.createForeignDynamicObject();"));
229+
assertEquals("[object Object]", test("''+Polyglot.createForeignObject();"));
230+
assertEquals("[object Object]", test("''+Polyglot.createForeignDynamicObject();"));
231231
}
232232

233233
@Test

graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/ArrayPrototypeBuiltins.java

Lines changed: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
import com.oracle.truffle.api.dsl.ImportStatic;
6161
import com.oracle.truffle.api.dsl.Specialization;
6262
import com.oracle.truffle.api.frame.VirtualFrame;
63+
import com.oracle.truffle.api.interop.InteropException;
6364
import com.oracle.truffle.api.interop.InteropLibrary;
6465
import com.oracle.truffle.api.interop.InvalidArrayIndexException;
6566
import com.oracle.truffle.api.interop.UnsupportedMessageException;
@@ -143,6 +144,7 @@
143144
import com.oracle.truffle.js.nodes.function.JSBuiltin;
144145
import com.oracle.truffle.js.nodes.function.JSBuiltinNode;
145146
import com.oracle.truffle.js.nodes.function.JSFunctionCallNode;
147+
import com.oracle.truffle.js.nodes.interop.ForeignObjectPrototypeNode;
146148
import com.oracle.truffle.js.nodes.interop.ImportValueNode;
147149
import com.oracle.truffle.js.nodes.unary.IsCallableNode;
148150
import com.oracle.truffle.js.nodes.unary.IsConstructorNode;
@@ -1253,25 +1255,79 @@ protected double unshiftHoles(Object thisObjParam, Object[] args,
12531255

12541256
public abstract static class JSArrayToStringNode extends BasicArrayOperation {
12551257
@Child private PropertyNode joinPropertyNode;
1256-
@Child private JSFunctionCallNode callNode;
1258+
@Child private PropertyNode toStringPropertyNode;
1259+
@Child private JSFunctionCallNode callJoinNode;
1260+
@Child private JSFunctionCallNode callToStringNode;
1261+
@Child private ForeignObjectPrototypeNode foreignObjectPrototypeNode;
12571262

12581263
private final ConditionProfile isJSObjectProfile = ConditionProfile.createBinaryProfile();
12591264

1265+
private static final String JOIN = "join";
1266+
12601267
public JSArrayToStringNode(JSContext context, JSBuiltin builtin) {
12611268
super(context, builtin);
1262-
this.joinPropertyNode = PropertyNode.createProperty(getContext(), null, "join");
1269+
this.joinPropertyNode = PropertyNode.createProperty(context, null, JOIN);
12631270
}
12641271

12651272
private Object getJoinProperty(Object target) {
12661273
return joinPropertyNode.executeWithTarget(target);
12671274
}
12681275

1276+
private Object getToStringProperty(Object target) {
1277+
if (toStringPropertyNode == null) {
1278+
CompilerDirectives.transferToInterpreterAndInvalidate();
1279+
toStringPropertyNode = insert(PropertyNode.createProperty(getContext(), null, JSRuntime.TO_STRING));
1280+
}
1281+
return toStringPropertyNode.executeWithTarget(target);
1282+
}
1283+
12691284
private Object callJoin(Object target, Object function) {
1270-
if (callNode == null) {
1285+
if (callJoinNode == null) {
1286+
CompilerDirectives.transferToInterpreterAndInvalidate();
1287+
callJoinNode = insert(JSFunctionCallNode.createCall());
1288+
}
1289+
return callJoinNode.executeCall(JSArguments.createZeroArg(target, function));
1290+
}
1291+
1292+
private Object callToString(Object target, Object function) {
1293+
if (callToStringNode == null) {
1294+
CompilerDirectives.transferToInterpreterAndInvalidate();
1295+
callToStringNode = insert(JSFunctionCallNode.createCall());
1296+
}
1297+
return callToStringNode.executeCall(JSArguments.createZeroArg(target, function));
1298+
}
1299+
1300+
private DynamicObject getForeignObjectPrototype(Object truffleObject) {
1301+
assert JSRuntime.isForeignObject(truffleObject);
1302+
if (foreignObjectPrototypeNode == null) {
12711303
CompilerDirectives.transferToInterpreterAndInvalidate();
1272-
callNode = insert(JSFunctionCallNode.createCall());
1304+
foreignObjectPrototypeNode = insert(ForeignObjectPrototypeNode.create());
1305+
}
1306+
return foreignObjectPrototypeNode.executeDynamicObject(truffleObject);
1307+
}
1308+
1309+
private Object toStringForeign(Object arrayObj) {
1310+
InteropLibrary interop = InteropLibrary.getFactory().getUncached(arrayObj);
1311+
1312+
if (interop.hasMembers(arrayObj) && interop.isMemberInvocable(arrayObj, JOIN)) {
1313+
Object result;
1314+
try {
1315+
result = interop.invokeMember(arrayObj, JOIN);
1316+
} catch (InteropException e) {
1317+
result = null;
1318+
}
1319+
if (result != null) {
1320+
return JSRuntime.importValue(result);
1321+
}
1322+
}
1323+
1324+
Object join = getJoinProperty(getForeignObjectPrototype(arrayObj));
1325+
if (isCallable(join)) {
1326+
return callJoin(arrayObj, join);
1327+
} else {
1328+
Object toString = getToStringProperty(getContext().getRealm().getObjectPrototype());
1329+
return callToString(arrayObj, toString);
12731330
}
1274-
return callNode.executeCall(JSArguments.createZeroArg(target, function));
12751331
}
12761332

12771333
@Specialization
@@ -1285,7 +1341,7 @@ protected Object toString(Object thisObj) {
12851341
return JSObject.defaultToString((DynamicObject) arrayObj);
12861342
}
12871343
} else {
1288-
return "[object Foreign]";
1344+
return toStringForeign(arrayObj);
12891345
}
12901346
}
12911347
}
@@ -1644,7 +1700,8 @@ private String toStringOrEmpty(Object thisObject, Object value) {
16441700

16451701
private static boolean isValidEntry(Object thisObject, Object value) {
16461702
// the last check here is to avoid recursion
1647-
return value != Undefined.instance && value != Null.instance && value != thisObject;
1703+
return value != Undefined.instance && value != Null.instance && (value instanceof JSObject ? value != thisObject
1704+
: !InteropLibrary.getFactory().getUncached(thisObject).isIdentical(thisObject, value, InteropLibrary.getFactory().getUncached(value)));
16481705
}
16491706

16501707
private String joinSparse(Object thisObject, long length, String joinSeparator, final boolean appendSep) {

graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/DatePrototypeBuiltins.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -911,13 +911,13 @@ protected Object toPrimitive(Object obj, Object hint) {
911911
CompilerDirectives.transferToInterpreterAndInvalidate();
912912
ordinaryToPrimitiveHintNumber = insert(OrdinaryToPrimitiveNode.createHintNumber(getContext()));
913913
}
914-
return ordinaryToPrimitiveHintNumber.execute((DynamicObject) obj);
914+
return ordinaryToPrimitiveHintNumber.execute(obj);
915915
} else if (isHintStringOrDefault.profile(JSRuntime.HINT_STRING.equals(hint) || JSRuntime.HINT_DEFAULT.equals(hint))) {
916916
if (ordinaryToPrimitiveHintString == null) {
917917
CompilerDirectives.transferToInterpreterAndInvalidate();
918918
ordinaryToPrimitiveHintString = insert(OrdinaryToPrimitiveNode.createHintString(getContext()));
919919
}
920-
return ordinaryToPrimitiveHintString.execute((DynamicObject) obj);
920+
return ordinaryToPrimitiveHintString.execute(obj);
921921
} else {
922922
throw Errors.createTypeError("invalid hint");
923923
}

graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/FunctionPrototypeBuiltins.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@
4646
import com.oracle.truffle.api.dsl.Cached;
4747
import com.oracle.truffle.api.dsl.Cached.Shared;
4848
import com.oracle.truffle.api.dsl.Specialization;
49+
import com.oracle.truffle.api.interop.InteropLibrary;
50+
import com.oracle.truffle.api.interop.UnsupportedMessageException;
51+
import com.oracle.truffle.api.library.CachedLibrary;
4952
import com.oracle.truffle.api.nodes.RootNode;
5053
import com.oracle.truffle.api.object.DynamicObject;
5154
import com.oracle.truffle.api.profiles.ConditionProfile;
@@ -338,7 +341,21 @@ protected String toString(DynamicObject fnObj) {
338341
@SuppressWarnings("unused")
339342
@Specialization(guards = {"isES2019OrLater()", "!isJSFunction(fnObj)", "isCallable.executeBoolean(fnObj)"}, limit = "1")
340343
protected String toStringCallable(Object fnObj,
341-
@Cached @Shared("isCallable") IsCallableNode isCallable) {
344+
@Cached @Shared("isCallable") IsCallableNode isCallable,
345+
@CachedLibrary("fnObj") InteropLibrary interop) {
346+
if (interop.hasExecutableName(fnObj)) {
347+
try {
348+
Object name = interop.getExecutableName(fnObj);
349+
return getNameIntl(InteropLibrary.getFactory().getUncached().asString(name));
350+
} catch (UnsupportedMessageException e) {
351+
}
352+
} else if (interop.isMetaObject(fnObj)) {
353+
try {
354+
Object name = interop.getMetaSimpleName(fnObj);
355+
return getNameIntl(InteropLibrary.getFactory().getUncached().asString(name));
356+
} catch (UnsupportedMessageException e) {
357+
}
358+
}
342359
return NATIVE_CODE_STR;
343360
}
344361

0 commit comments

Comments
 (0)