From 8cb48a8242de9dafdbf9ec28726f605d42294b4e Mon Sep 17 00:00:00 2001 From: Christian Aistleitner Date: Tue, 9 Mar 2021 20:45:37 +0100 Subject: [PATCH 01/31] Added parsing of tuples. --- .../src/com/oracle/js/parser/Parser.java | 104 +++++++++++++++++- .../src/com/oracle/js/parser/TokenType.java | 11 +- .../com/oracle/js/parser/ir/LiteralNode.java | 59 ++++++++++ 3 files changed, 172 insertions(+), 2 deletions(-) diff --git a/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/Parser.java b/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/Parser.java index c2e81feb796..4eca1d6341b 100644 --- a/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/Parser.java +++ b/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/Parser.java @@ -88,6 +88,7 @@ import static com.oracle.js.parser.TokenType.SPREAD_ARGUMENT; import static com.oracle.js.parser.TokenType.SPREAD_ARRAY; import static com.oracle.js.parser.TokenType.SPREAD_OBJECT; +import static com.oracle.js.parser.TokenType.SPREAD_TUPLE; import static com.oracle.js.parser.TokenType.STATIC; import static com.oracle.js.parser.TokenType.STRING; import static com.oracle.js.parser.TokenType.SUPER; @@ -3662,6 +3663,10 @@ private Expression primaryExpression(boolean yield, boolean await) { return arrayLiteral(yield, await); case LBRACE: return objectLiteral(yield, await); + case HASH_BRACE: + return recordLiteral(yield, await); + case HASH_BRACKET: + return tupleLiteral(yield, await); case LPAREN: return parenthesizedExpressionAndArrowParameterList(yield, await); case TEMPLATE: @@ -3797,7 +3802,8 @@ private LiteralNode arrayLiteral(boolean yield, boolean await) { *
      * ObjectLiteral :
      *      { }
-     *      { PropertyNameAndValueList } { PropertyNameAndValueList , }
+     *      { PropertyNameAndValueList }
+     *      { PropertyNameAndValueList , }
      *
      * PropertyNameAndValueList :
      *      PropertyAssignment
@@ -4284,6 +4290,102 @@ private static final class PropertyFunction {
         }
     }
 
+    /**
+     * Parse a record literal.
+     *
+     * 
+     * RecordLiteral[Yield, Await] :
+     *      #{ }
+     *      #{ RecordPropertyDefinitionList[?Yield, ?Await] }
+     *      #{ RecordPropertyDefinitionList[?Yield, ?Await] , }
+     *
+     * RecordPropertyDefinitionList[Yield, Await] :
+     *      RecordPropertyDefinition[?Yield, ?Await]
+     *      RecordPropertyDefinitionList[?Yield, ?Await],RecordPropertyDefinition[?Yield, ?Await]
+     *
+     * RecordPropertyDefinition[Yield, Await] :
+     *      IdentifierReference[?Yield, ?Await]
+     *      PropertyName[?Yield, ?Await]:AssignmentExpression[+In, ?Yield, ?Await]
+     *      ... AssignmentExpression[+In, ?Yield, ?Await]
+     * 
+ * + * @return Expression node. + */ + private Expression recordLiteral(boolean yield, boolean await) { // TODO: return type + return objectLiteral(yield, await); // TODO: implement + } + + /** + * Parse a tuple literal. + * + *
+     * TupleLiteral[Yield, Await] :
+     *      #[ ]
+     *      #[ TupleElementList[?Yield, ?Await] ]
+     *      #[ TupleElementList[?Yield, ?Await] , ]
+     *
+     * TupleElementList[Yield, Await] :
+     *      AssignmentExpression[+In, ?Yield, ?Await]
+     *      SpreadElement[?Yield, ?Await]
+     *      TupleElementList[?Yield, ?Await] , AssignmentExpression[+In, ?Yield, ?Await]
+     *      TupleElementList[?Yield, ?Await] , SpreadElement[?Yield, ?Await]
+     * 
+ * + * @return Expression node. + */ + private LiteralNode tupleLiteral(boolean yield, boolean await) { + final long tupleToken = token; // Capture HASH_BRACKET token. + next(); // HASH_BRACKET tested in caller. + + // Prepare to accumulate elements. + final ArrayList elements = new ArrayList<>(); + // Track elisions. + boolean elision = true; + loop: while (true) { + long spreadToken = 0; + switch (type) { + case RBRACKET: + next(); + break loop; + + case COMMARIGHT: + if (elision) { // If no prior expression + throw error(AbstractParser.message("unexpected.token", type.getNameOrType())); + } + next(); + elision = true; + break; + + case ELLIPSIS: + spreadToken = token; + next(); + // fall through + + default: + if (!elision) { + throw error(AbstractParser.message("expected.comma", type.getNameOrType())); + } + + // Add expression element. + Expression expression = assignmentExpression(true, yield, await, false); + if (expression != null) { + if (spreadToken != 0) { + expression = new UnaryNode(Token.recast(spreadToken, SPREAD_TUPLE), expression); + } + elements.add(expression); + } else { + // TODO: somehow assignmentExpression(...) can return null, but I'm not sure why we expect a RBRACKET then :/ + expect(RBRACKET); + } + + elision = false; + break; + } + } + + return LiteralNode.newTupleInstance(tupleToken, finish, optimizeList(elements)); + } + /** * Parse left hand side expression. * diff --git a/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/TokenType.java b/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/TokenType.java index 21f42a45f62..ef43295d3c9 100644 --- a/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/TokenType.java +++ b/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/TokenType.java @@ -229,7 +229,16 @@ public enum TokenType { SPREAD_ARRAY (IR, null), SPREAD_OBJECT (IR, null), YIELD_STAR (IR, null), - ASSIGN_INIT (IR, null); + ASSIGN_INIT (IR, null), + + // Records & Tuples Proposal tokens + RECORD (LITERAL, null), + TUPLE (LITERAL, null), + SPREAD_RECORD (IR, null), + SPREAD_TUPLE (IR, null), + // TODO: Associate with the correct ECMAScript Version + HASH_BRACKET (BRACKET, "#[", 0, true, 13), + HASH_BRACE (BRACKET, "#{", 0, true, 13); //@formatter:on /** Next token kind in token lookup table. */ diff --git a/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/ir/LiteralNode.java b/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/ir/LiteralNode.java index f999c7b1139..e43f03bf5e2 100644 --- a/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/ir/LiteralNode.java +++ b/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/ir/LiteralNode.java @@ -509,4 +509,63 @@ public static LiteralNode newInstance(long token, int finish, List public static LiteralNode newInstance(final long token, final int finish, final Expression[] value) { return new ArrayLiteralNode(token, finish, value); } + + /** + * Tuple literal node class. + */ + public static final class TupleLiteralNode extends PrimitiveLiteralNode { + + /** + * Constructor + * + * @param token token + * @param finish finish + * @param value array literal value, a Node array + */ + protected TupleLiteralNode(final long token, final int finish, final Expression[] value) { + super(Token.recast(token, TokenType.TUPLE), finish, value); + } + + /** + * Returns a list of tuple element expressions. + * + * @return a list of tuple element expressions. + */ + @Override + public List getElementExpressions() { + return Collections.unmodifiableList(Arrays.asList(value)); + } + + @Override + public void toString(final StringBuilder sb, final boolean printType) { + sb.append('['); + boolean first = true; + for (final Node node : value) { + if (!first) { + sb.append(','); + sb.append(' '); + } + if (node == null) { + sb.append("undefined"); + } else { + node.toString(sb, printType); + } + first = false; + } + sb.append(']'); + } + } + + /** + * Create a new tuple literal of Nodes from a list of Node values + * + * @param token token + * @param finish finish + * @param value literal value list + * + * @return the new literal node + */ + public static LiteralNode newTupleInstance(long token, int finish, List value) { + return new TupleLiteralNode(token, finish, valueToArray(value)); + } } From 324a8bd729a4344c1be03616d58f93a6abae4f16 Mon Sep 17 00:00:00 2001 From: Christian Aistleitner Date: Sat, 20 Mar 2021 11:47:54 +0100 Subject: [PATCH 02/31] Add basic tuple translation skeleton. --- .../truffle/js/parser/GraalJSTranslator.java | 16 + .../js/record-tuple/tuple.js | 125 ++++++++ .../js/builtins/ConstructorBuiltins.java | 64 +++- .../js/builtins/TupleFunctionBuiltins.java | 61 ++++ .../js/builtins/TuplePrototypeBuiltins.java | 70 +++++ .../com/oracle/truffle/js/nodes/JSGuards.java | 5 + .../oracle/truffle/js/nodes/NodeFactory.java | 13 + .../truffle/js/nodes/binary/JSEqualNode.java | 7 + .../js/nodes/binary/JSIdenticalNode.java | 7 + .../js/nodes/cast/JSToPrimitiveNode.java | 6 + .../truffle/js/nodes/cast/JSToStringNode.java | 6 + .../js/nodes/instrumentation/JSTags.java | 1 + .../truffle/js/nodes/tuples/IsTupleNode.java | 36 +++ .../js/nodes/tuples/TupleLiteralNode.java | 274 ++++++++++++++++++ .../truffle/js/nodes/unary/TypeOfNode.java | 7 + .../oracle/truffle/js/runtime/JSContext.java | 9 + .../oracle/truffle/js/runtime/JSRealm.java | 26 ++ .../oracle/truffle/js/runtime/JSRuntime.java | 18 +- .../com/oracle/truffle/js/runtime/Tuple.java | 126 ++++++++ .../truffle/js/runtime/builtins/JSTuple.java | 72 +++++ .../js/runtime/builtins/JSTupleObject.java | 30 ++ .../js/runtime/interop/JSMetaType.java | 2 + 22 files changed, 978 insertions(+), 3 deletions(-) create mode 100644 graal-js/src/com.oracle.truffle.js.test/js/record-tuple/tuple.js create mode 100644 graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TupleFunctionBuiltins.java create mode 100644 graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TuplePrototypeBuiltins.java create mode 100644 graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/IsTupleNode.java create mode 100644 graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/TupleLiteralNode.java create mode 100644 graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Tuple.java create mode 100644 graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSTuple.java create mode 100644 graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSTupleObject.java diff --git a/graal-js/src/com.oracle.truffle.js.parser/src/com/oracle/truffle/js/parser/GraalJSTranslator.java b/graal-js/src/com.oracle.truffle.js.parser/src/com/oracle/truffle/js/parser/GraalJSTranslator.java index 8466889fce0..d39e8fd231f 100644 --- a/graal-js/src/com.oracle.truffle.js.parser/src/com/oracle/truffle/js/parser/GraalJSTranslator.java +++ b/graal-js/src/com.oracle.truffle.js.parser/src/com/oracle/truffle/js/parser/GraalJSTranslator.java @@ -1424,6 +1424,8 @@ public JavaScriptNode enterBlockStatement(BlockStatement blockStatement) { public JavaScriptNode enterLiteralNode(LiteralNode literalNode) { if (literalNode instanceof LiteralNode.ArrayLiteralNode) { return tagExpression(enterLiteralArrayNode((LiteralNode.ArrayLiteralNode) literalNode), literalNode); + } else if (literalNode instanceof LiteralNode.TupleLiteralNode) { + return tagExpression(enterLiteralTupleNode((LiteralNode.TupleLiteralNode) literalNode), literalNode); } else { return tagExpression(enterLiteralDefaultNode(literalNode), literalNode); } @@ -1459,6 +1461,18 @@ private JavaScriptNode enterLiteralArrayNode(LiteralNode.ArrayLiteralNode arrayL return hasSpread ? factory.createArrayLiteralWithSpread(context, elements) : factory.createArrayLiteral(context, elements); } + private JavaScriptNode enterLiteralTupleNode(LiteralNode.TupleLiteralNode tupleLiteralNode) { + List elementExpressions = tupleLiteralNode.getElementExpressions(); + JavaScriptNode[] elements = javaScriptNodeArray(elementExpressions.size()); + boolean hasSpread = false; + for (int i = 0; i < elementExpressions.size(); i++) { + Expression elementExpression = elementExpressions.get(i); + hasSpread = hasSpread || elementExpression.isTokenType(TokenType.SPREAD_TUPLE); + elements[i] = transform(elementExpression); + } + return hasSpread ? factory.createTupleLiteralWithSpread(context, elements) : factory.createTupleLiteral(context, elements); + } + @Override public JavaScriptNode enterIdentNode(IdentNode identNode) { assert !identNode.isPropertyName(); @@ -1964,6 +1978,8 @@ public JavaScriptNode enterUnaryNode(UnaryNode unaryNode) { return enterDelete(unaryNode); case SPREAD_ARGUMENT: return tagExpression(factory.createSpreadArgument(context, transform(unaryNode.getExpression())), unaryNode); + case SPREAD_TUPLE: + return tagExpression(factory.createSpreadTuple(context, transform(unaryNode.getExpression())), unaryNode); case SPREAD_ARRAY: return tagExpression(factory.createSpreadArray(context, transform(unaryNode.getExpression())), unaryNode); case YIELD: diff --git a/graal-js/src/com.oracle.truffle.js.test/js/record-tuple/tuple.js b/graal-js/src/com.oracle.truffle.js.test/js/record-tuple/tuple.js new file mode 100644 index 00000000000..45a950d993f --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js.test/js/record-tuple/tuple.js @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. + */ + +/** + * Test for checking basic tuple functionality. + * + * @option ecmascript-version=2022 + */ + +load('../assert.js'); + +/* + * Test 1: + * valid tuple literals + */ +const t1a = #[]; +const t1b = #[1, 2]; +const t1c = #[1, 2, #[3]]; +// TODO: const t1d = #[1, 2, #{ a: 3 }]; +// TODO: const t1e = #[...t1b, 3]; + +/* + * Test 2: + * invalid tuple literals + */ +assertThrows(function() { + eval("const x = #[,]"); // SyntaxError, holes are disallowed by syntax +}, SyntaxError); + +/* + * Test 3: + * a confusing case + */ +let x, y = #[x = 0]; +assertSame(0, x); +assertSame(#[0], y); + +/* + * Test 4: + * typeof + */ +const t4 = 2; +assertSame("tuple", typeof #[t4]); +assertSame("tuple", typeof #[]); +assertSame("tuple", typeof #[#[]]); +assertSame("tuple", typeof #[1, 2]); + +/* + * Test 5: + * Tuple.prototype.toString + */ +assertSame([].toString(), #[].toString()); +assertSame([1].toString(), #[1].toString()); +assertSame([1, 2, 3].toString(), #[1, 2, 3].toString()); +assertSame([1, 2, [3]].toString(), #[1, 2, #[3]].toString()); + +/* + * Test 6: + * constructor + */ +assertThrows(function() { + eval("const x = new Tuple()"); // TypeError: Tuple is not a constructor +}, TypeError); +assertThrows(function() { + eval("class Test extends Tuple {} const x = new Test()"); // TypeError: Tuple is not a constructor +}, TypeError); + +/* + * Test 7: + * Tuple function + */ +assertSame(Tuple().toString(), #[].toString()); +assertSame(Tuple(1, 2).toString(), #[1, 2].toString()); +assertSame(Tuple(1, #[2]).toString(), #[1, #[2]].toString()); +// TODO: remove .toString() calls above / remove above +assertSame(Tuple(), #[]); +assertSame(Tuple(1, 2), #[1, 2]); +assertSame(Tuple(1, Tuple(2)), #[1, #[2]]); + +/* + * Test 8: + * non-primitive values + */ +assertThrows(function() { + eval("const x = #[1, {}]"); // TypeError: Tuples cannot contain non-primitive values +}, TypeError); +assertThrows(function() { + eval("const x = Tuple(1, {})"); // TypeError: Tuples cannot contain non-primitive values +}, TypeError); + +/* + * Test 9: + * Equality - test cases taken from https://github.com/tc39/proposal-record-tuple + */ +assertTrue(#[1] === #[1]); +assertTrue(#[1, 2] === #[1, 2]); +// TODO: assertTrue(Object(#[1, 2]) !== Object(#[1, 2])); + +assertTrue(#[-0] === #[+0]); +// TODO: assertTrue(#[NaN] === #[NaN]); + +assertTrue(#[-0] == #[+0]); +// TODO: assertTrue(#[NaN] == #[NaN]); +assertTrue(#[1] != #["1"]); + +// TODO: assertTrue(!Object.is(#[-0], #[+0])); +// TODO: assertTrue(Object.is(#[NaN], #[NaN])); + +// Map keys are compared with the SameValueZero algorithm +// TODO: assertTrue(new Map().set(#[1], true).get(#[1])); +// TODO: assertTrue(new Map().set(#[-0], true).get(#[0])); + +/* + * Test 10: + * Tuple.isTuple + */ +assertTrue(Tuple.isTuple(#[1])); +assertTrue(Tuple.isTuple(Tuple(1))); +assertFalse(Tuple.isTuple(1)); +assertFalse(Tuple.isTuple("1")); +assertFalse(Tuple.isTuple(BigInt(1))); diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/ConstructorBuiltins.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/ConstructorBuiltins.java index 046a24293b9..9e07ded0cf3 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/ConstructorBuiltins.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/ConstructorBuiltins.java @@ -108,6 +108,8 @@ import com.oracle.truffle.js.builtins.ConstructorBuiltinsFactory.ConstructSetNodeGen; import com.oracle.truffle.js.builtins.ConstructorBuiltinsFactory.ConstructStringNodeGen; import com.oracle.truffle.js.builtins.ConstructorBuiltinsFactory.ConstructSymbolNodeGen; +import com.oracle.truffle.js.builtins.ConstructorBuiltinsFactory.CallTupleNodeGen; +import com.oracle.truffle.js.builtins.ConstructorBuiltinsFactory.ConstructTupleNodeGen; import com.oracle.truffle.js.builtins.ConstructorBuiltinsFactory.ConstructWeakMapNodeGen; import com.oracle.truffle.js.builtins.ConstructorBuiltinsFactory.ConstructWeakRefNodeGen; import com.oracle.truffle.js.builtins.ConstructorBuiltinsFactory.ConstructWeakSetNodeGen; @@ -186,6 +188,7 @@ import com.oracle.truffle.js.runtime.PromiseHook; import com.oracle.truffle.js.runtime.SafeInteger; import com.oracle.truffle.js.runtime.Symbol; +import com.oracle.truffle.js.runtime.Tuple; import com.oracle.truffle.js.runtime.array.ArrayAllocationSite; import com.oracle.truffle.js.runtime.array.ScriptArray; import com.oracle.truffle.js.runtime.array.dyn.AbstractWritableArray; @@ -325,7 +328,10 @@ public enum Constructor implements BuiltinEnum { // non-standard (Nashorn) extensions JSAdapter(1), - JavaImporter(1); + JavaImporter(1), + + // Record and Tuple proposal + Tuple(0); private final int length; @@ -624,6 +630,9 @@ protected Object createNode(JSContext context, JSBuiltin builtin, boolean constr ? ConstructWebAssemblyTableNodeGen.create(context, builtin, true, args().newTarget().fixedArgs(1).createArgumentNodes(context)) : ConstructWebAssemblyTableNodeGen.create(context, builtin, false, args().function().fixedArgs(1).createArgumentNodes(context))) : createCallRequiresNew(context, builtin); + case Tuple: + return construct ? ConstructTupleNodeGen.create(context, builtin, args().createArgumentNodes(context)) + : CallTupleNodeGen.create(context, builtin, args().varArgs().createArgumentNodes(context)); } return null; @@ -2748,4 +2757,57 @@ protected DynamicObject getIntrinsicDefaultProto(JSRealm realm) { } + public abstract static class CallTupleNode extends JSBuiltinNode { + + public CallTupleNode(JSContext context, JSBuiltin builtin) { + super(context, builtin); + } + + @Specialization(guards = {"args.length == 0"}) + protected Object constructEmptyTuple(@SuppressWarnings("unused") Object[] args) { + return Tuple.create(); + } + + // TODO: maybe use @Fallback here + @Specialization(guards = {"args.length != 0"}) + protected Object constructTuple(Object[] args, + @Cached("create()") BranchProfile isByteCase, + @Cached("create()") BranchProfile isIntegerCase, + @Cached("create()") BranchProfile isDoubleCase, + @Cached("create()") BranchProfile isObjectCase) { + for (Object element : args) { + if (!JSRuntime.isJSPrimitive(element)) { + throw Errors.createTypeError("Tuples cannot contain non-primitive values"); + } + } + // TODO: maybe use JSToPrimitiveNode as CallBigIntNode does + // TODO: Tuples cannot contain holes, thus ArrayLiteralNode.identifyPrimitiveContentType might not be the best method for classifying the array content + ArrayContentType type = ArrayLiteralNode.identifyPrimitiveContentType(args, true); + if (type == ArrayContentType.Byte) { + isByteCase.enter(); + return Tuple.create(ArrayLiteralNode.createByteArray(args)); + } else if (type == ArrayContentType.Integer) { + isIntegerCase.enter(); + return Tuple.create(ArrayLiteralNode.createIntArray(args)); + } else if (type == ArrayContentType.Double) { + isDoubleCase.enter(); + return Tuple.create(ArrayLiteralNode.createDoubleArray(args)); + } else { + isObjectCase.enter(); + return Tuple.create(args); + } + } + } + + public abstract static class ConstructTupleNode extends JSBuiltinNode { + + public ConstructTupleNode(JSContext context, JSBuiltin builtin) { + super(context, builtin); + } + + @Specialization + protected static final DynamicObject construct() { + throw Errors.createTypeError("Tuple is not a constructor"); + } + } } diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TupleFunctionBuiltins.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TupleFunctionBuiltins.java new file mode 100644 index 00000000000..ec80bb6039b --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TupleFunctionBuiltins.java @@ -0,0 +1,61 @@ +package com.oracle.truffle.js.builtins; + +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.js.nodes.function.JSBuiltin; +import com.oracle.truffle.js.nodes.function.JSBuiltinNode; +import com.oracle.truffle.js.nodes.tuples.IsTupleNode; +import com.oracle.truffle.js.runtime.JSContext; +import com.oracle.truffle.js.runtime.builtins.BuiltinEnum; +import com.oracle.truffle.js.runtime.builtins.JSTuple; + +/** + * Contains builtins for Tuple function. + */ +public final class TupleFunctionBuiltins extends JSBuiltinsContainer.SwitchEnum { + + public static final JSBuiltinsContainer BUILTINS = new TupleFunctionBuiltins(); + + protected TupleFunctionBuiltins() { + super(JSTuple.CLASS_NAME, TupleFunction.class); + } + + public enum TupleFunction implements BuiltinEnum { + isTuple(1); + // TODO: from(1), + // TODO: of(1); + + private final int length; + + TupleFunction(int length) { + this.length = length; + } + + @Override + public int getLength() { + return length; + } + } + + @Override + protected Object createNode(JSContext context, JSBuiltin builtin, boolean construct, boolean newTarget, TupleFunction builtinEnum) { + switch (builtinEnum) { + case isTuple: + return TupleFunctionBuiltinsFactory.TupleIsTupleNodeGen.create(context, builtin, args().fixedArgs(1).createArgumentNodes(context)); + } + return null; + } + + public abstract static class TupleIsTupleNode extends JSBuiltinNode { + + @Child private IsTupleNode isTupleNode = IsTupleNode.create(); + + public TupleIsTupleNode(JSContext context, JSBuiltin builtin) { + super(context, builtin); + } + + @Specialization + protected boolean isTuple(Object object) { + return isTupleNode.execute(object); + } + } +} diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TuplePrototypeBuiltins.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TuplePrototypeBuiltins.java new file mode 100644 index 00000000000..923ad18ca30 --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TuplePrototypeBuiltins.java @@ -0,0 +1,70 @@ +package com.oracle.truffle.js.builtins; + +import com.oracle.truffle.api.dsl.Fallback; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.object.DynamicObject; +import com.oracle.truffle.js.nodes.function.JSBuiltin; +import com.oracle.truffle.js.nodes.function.JSBuiltinNode; +import com.oracle.truffle.js.runtime.Errors; +import com.oracle.truffle.js.runtime.JSContext; +import com.oracle.truffle.js.runtime.Tuple; +import com.oracle.truffle.js.runtime.builtins.BuiltinEnum; +import com.oracle.truffle.js.runtime.builtins.JSTuple; + +/** + * Contains builtins for Tuple.prototype. + */ +public final class TuplePrototypeBuiltins extends JSBuiltinsContainer.SwitchEnum { + + public static final JSBuiltinsContainer BUILTINS = new TuplePrototypeBuiltins(); + + protected TuplePrototypeBuiltins() { + super(JSTuple.PROTOTYPE_NAME, TuplePrototype.class); + } + + public enum TuplePrototype implements BuiltinEnum { + toString(0); + + private final int length; + + TuplePrototype(int length) { + this.length = length; + } + + @Override + public int getLength() { + return length; + } + } + + @Override + protected Object createNode(JSContext context, JSBuiltin builtin, boolean construct, boolean newTarget, TuplePrototype builtinEnum) { + switch (builtinEnum) { + case toString: + return TuplePrototypeBuiltinsFactory.JSTupleToStringNodeGen.create(context, builtin, args().withThis().fixedArgs(1).createArgumentNodes(context)); + } + return null; + } + + public abstract static class JSTupleToStringNode extends JSBuiltinNode { + + public JSTupleToStringNode(JSContext context, JSBuiltin builtin) { + super(context, builtin); + } + + @Specialization + protected String toString(Tuple thisObj) { + return thisObj.toString(); + } + + @Specialization(guards = {"isJSTuple(thisObj)"}) + protected String toString(DynamicObject thisObj) { + return JSTuple.valueOf(thisObj).toString(); + } + + @Fallback + protected void toStringNoTuple(Object thisObj) { + throw Errors.createTypeError("Tuple.prototype.toString requires that 'this' be a Tuple"); + } + } +} diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/JSGuards.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/JSGuards.java index 29c720df3f9..2a624b25ce2 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/JSGuards.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/JSGuards.java @@ -69,6 +69,7 @@ import com.oracle.truffle.js.runtime.builtins.JSSharedArrayBuffer; import com.oracle.truffle.js.runtime.builtins.JSString; import com.oracle.truffle.js.runtime.builtins.JSSymbol; +import com.oracle.truffle.js.runtime.builtins.JSTuple; import com.oracle.truffle.js.runtime.builtins.JSWeakMap; import com.oracle.truffle.js.runtime.builtins.JSWeakRef; import com.oracle.truffle.js.runtime.builtins.JSWeakSet; @@ -141,6 +142,10 @@ public static boolean isJSFunction(Object value) { return JSFunction.isJSFunction(value); } + public static boolean isJSTuple(Object value) { + return JSTuple.isJSTuple(value); + } + public static boolean isJSFunctionShape(Shape shape) { return shape.getDynamicType() == JSFunction.INSTANCE; } diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/NodeFactory.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/NodeFactory.java index 3f479ab4bf7..3ab88986c3d 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/NodeFactory.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/NodeFactory.java @@ -103,6 +103,7 @@ import com.oracle.truffle.js.nodes.access.RestObjectNode; import com.oracle.truffle.js.nodes.access.ScopeFrameNode; import com.oracle.truffle.js.nodes.access.SuperPropertyReferenceNode; +import com.oracle.truffle.js.nodes.tuples.TupleLiteralNode; import com.oracle.truffle.js.nodes.access.WithTargetNode; import com.oracle.truffle.js.nodes.access.WithVarWrapperNode; import com.oracle.truffle.js.nodes.access.WriteElementNode; @@ -783,6 +784,14 @@ public JavaScriptNode createArrayLiteralWithSpread(JSContext context, JavaScript return ArrayLiteralNode.createWithSpread(context, elements); } + public JavaScriptNode createTupleLiteral(JSContext context, JavaScriptNode[] elements) { + return TupleLiteralNode.create(context, elements); + } + + public JavaScriptNode createTupleLiteralWithSpread(JSContext context, JavaScriptNode[] elements) { + return TupleLiteralNode.createWithSpread(context, elements); + } + public ObjectLiteralMemberNode createAccessorMember(String keyName, boolean isStatic, boolean enumerable, JavaScriptNode getter, JavaScriptNode setter) { return ObjectLiteralNode.newAccessorMember(keyName, isStatic, enumerable, getter, setter); } @@ -824,6 +833,10 @@ public JavaScriptNode createSpreadArray(JSContext context, JavaScriptNode argume return ArrayLiteralNode.SpreadArrayNode.create(context, argument); } + public JavaScriptNode createSpreadTuple(JSContext context, JavaScriptNode argument) { + return TupleLiteralNode.SpreadTupleNode.create(context, argument); + } + public ReturnNode createReturn(JavaScriptNode expression) { return ReturnNode.create(expression); } diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/binary/JSEqualNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/binary/JSEqualNode.java index 21d878e6020..3f6585cf986 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/binary/JSEqualNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/binary/JSEqualNode.java @@ -64,6 +64,7 @@ import com.oracle.truffle.js.runtime.JSConfig; import com.oracle.truffle.js.runtime.JSRuntime; import com.oracle.truffle.js.runtime.Symbol; +import com.oracle.truffle.js.runtime.Tuple; import com.oracle.truffle.js.runtime.interop.JSInteropUtil; import com.oracle.truffle.js.runtime.objects.Null; @@ -337,6 +338,12 @@ protected boolean doStringNumber(String a, Object b) { return doStringDouble(a, JSRuntime.doubleValue((Number) b)); } + @Specialization + protected static boolean doTuple(Tuple a, Tuple b) { + // TODO: maybe compare tuples using a node tree + return a.equals(b); + } + @Fallback protected static boolean doFallback(Object a, Object b) { return JSRuntime.equal(a, b); diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/binary/JSIdenticalNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/binary/JSIdenticalNode.java index a21c0f3e28a..e6da2878e08 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/binary/JSIdenticalNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/binary/JSIdenticalNode.java @@ -68,6 +68,7 @@ import com.oracle.truffle.js.runtime.JSConfig; import com.oracle.truffle.js.runtime.JSRuntime; import com.oracle.truffle.js.runtime.Symbol; +import com.oracle.truffle.js.runtime.Tuple; import com.oracle.truffle.js.runtime.objects.JSLazyString; import com.oracle.truffle.js.runtime.objects.Undefined; @@ -285,6 +286,12 @@ protected static boolean doSymbol(Symbol a, Symbol b) { return a == b; } + @Specialization + protected static boolean doTuple(Tuple a, Tuple b) { + // TODO: maybe compare tuples using a node tree + return a.equals(b); + } + @Specialization(guards = {"isBoolean(a) != isBoolean(b)"}) protected static boolean doBooleanNotBoolean(Object a, Object b) { assert (a != null) && (b != null); diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToPrimitiveNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToPrimitiveNode.java index c739a4bbf21..51d444e162d 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToPrimitiveNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToPrimitiveNode.java @@ -71,6 +71,7 @@ import com.oracle.truffle.js.runtime.JSRuntime; import com.oracle.truffle.js.runtime.SafeInteger; import com.oracle.truffle.js.runtime.Symbol; +import com.oracle.truffle.js.runtime.Tuple; import com.oracle.truffle.js.runtime.builtins.JSDate; import com.oracle.truffle.js.runtime.objects.JSObject; import com.oracle.truffle.js.runtime.objects.Null; @@ -156,6 +157,11 @@ protected BigInt doBigInt(BigInt value) { return value; } + @Specialization + protected Tuple doTuple(Tuple value) { + return value; + } + @Specialization(guards = "isJSNull(value)") protected DynamicObject doNull(@SuppressWarnings("unused") Object value) { return Null.instance; diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToStringNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToStringNode.java index 5c896bf0bea..1bcfcf90139 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToStringNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToStringNode.java @@ -58,6 +58,7 @@ import com.oracle.truffle.js.runtime.Errors; import com.oracle.truffle.js.runtime.JSRuntime; import com.oracle.truffle.js.runtime.Symbol; +import com.oracle.truffle.js.runtime.Tuple; import com.oracle.truffle.js.runtime.objects.JSLazyString; import com.oracle.truffle.js.runtime.objects.Null; import com.oracle.truffle.js.runtime.objects.Undefined; @@ -161,6 +162,11 @@ protected String doSymbol(Symbol value) { } } + @Specialization + protected String doTuple(Tuple value) { + return value.toString(); + } + @Specialization(guards = {"isForeignObject(object)"}) protected String doTruffleObject(Object object, @Cached("createHintString()") JSToPrimitiveNode toPrimitiveNode) { diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/instrumentation/JSTags.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/instrumentation/JSTags.java index d0932748b73..159ee3db0fd 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/instrumentation/JSTags.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/instrumentation/JSTags.java @@ -151,6 +151,7 @@ public static final class LiteralTag extends Tag { public enum Type { ObjectLiteral, ArrayLiteral, + TupleLiteral, FunctionLiteral, NumericLiteral, BooleanLiteral, diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/IsTupleNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/IsTupleNode.java new file mode 100644 index 00000000000..050953f1624 --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/IsTupleNode.java @@ -0,0 +1,36 @@ +package com.oracle.truffle.js.nodes.tuples; + +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.object.DynamicObject; +import com.oracle.truffle.js.nodes.JavaScriptBaseNode; +import com.oracle.truffle.js.runtime.Tuple; + +/** + * Represents abstract operation IsTuple. + */ +public abstract class IsTupleNode extends JavaScriptBaseNode { + + protected IsTupleNode() { + } + + public abstract boolean execute(Object operand); + + @Specialization + protected static boolean doTuple(@SuppressWarnings("unused") Tuple tuple) { + return true; + } + + @Specialization(guards = "isJSTuple(object)") + protected static boolean doJSTuple(@SuppressWarnings("unused") DynamicObject object) { + return true; + } + + @Specialization(guards = "!isJSTuple(object)") + protected static boolean doNotJSTuple(@SuppressWarnings("unused") Object object) { + return false; + } + + public static IsTupleNode create() { + return IsTupleNodeGen.create(); + } +} diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/TupleLiteralNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/TupleLiteralNode.java new file mode 100644 index 00000000000..f2eb99c2516 --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/TupleLiteralNode.java @@ -0,0 +1,274 @@ +package com.oracle.truffle.js.nodes.tuples; + +import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.api.instrumentation.Tag; +import com.oracle.truffle.api.nodes.Node; +import com.oracle.truffle.api.object.DynamicObject; +import com.oracle.truffle.api.profiles.BranchProfile; +import com.oracle.truffle.js.nodes.JavaScriptNode; +import com.oracle.truffle.js.nodes.access.GetIteratorNode; +import com.oracle.truffle.js.nodes.access.IteratorGetNextValueNode; +import com.oracle.truffle.js.nodes.access.JSConstantNode; +import com.oracle.truffle.js.nodes.instrumentation.JSTags; +import com.oracle.truffle.js.nodes.instrumentation.JSTags.LiteralTag; +import com.oracle.truffle.js.runtime.Errors; +import com.oracle.truffle.js.runtime.JSConfig; +import com.oracle.truffle.js.runtime.JSContext; +import com.oracle.truffle.js.runtime.JSRuntime; +import com.oracle.truffle.js.runtime.Tuple; +import com.oracle.truffle.js.runtime.array.ScriptArray; +import com.oracle.truffle.js.runtime.objects.IteratorRecord; +import com.oracle.truffle.js.runtime.util.SimpleArrayList; + +import java.util.Set; + +public abstract class TupleLiteralNode extends JavaScriptNode { + + protected final JSContext context; + + public TupleLiteralNode(TupleLiteralNode copy) { + this.context = copy.context; + } + + protected TupleLiteralNode(JSContext context) { + this.context = context; + } + + @Override + public abstract Object execute(VirtualFrame frame); + + @Override + public boolean hasTag(Class tag) { + if (tag == LiteralTag.class) { + return true; + } else { + return super.hasTag(tag); + } + } + + @Override + public Object getNodeObject() { + return JSTags.createNodeObjectDescriptor(LiteralTag.TYPE, LiteralTag.Type.TupleLiteral.name()); + } + + public static JavaScriptNode create(JSContext context, JavaScriptNode[] elements) { + if (elements == null || elements.length == 0) { + return createEmptyTuple(); + } + + Object[] constantValues = resolveConstants(elements); + if (constantValues != null) { + return createConstantTuple(constantValues); + } + + return new DefaultTupleLiteralNode(context, elements); + } + + public static TupleLiteralNode createWithSpread(JSContext context, JavaScriptNode[] elements) { + return new DefaultTupleLiteralWithSpreadNode(context, elements); + } + + private static JSConstantNode createEmptyTuple() { + return JSConstantNode.create(Tuple.create()); + } + + private static JSConstantNode createConstantTuple(Object[] array) { + Tuple tuple = createTuple(array); + return JSConstantNode.create(tuple); + } + + private static Object[] resolveConstants(JavaScriptNode[] nodes) { + Object[] values = new Object[nodes.length]; + for (int i = 0; i < values.length; i++) { + JavaScriptNode node = nodes[i]; + if (node instanceof JSConstantNode) { + values[i] = ((JSConstantNode) node).getValue(); + } else { + return null; + } + } + return values; + } + + private static Tuple createTuple(Object[] array) { + Tuple tuple; + TupleContentType type = identifyPrimitiveContentType(array); + if (type == TupleContentType.Byte) { + tuple = Tuple.create(createByteArray(array)); + } else if (type == TupleContentType.Integer) { + tuple = Tuple.create(createIntArray(array)); + } else if (type == TupleContentType.Double) { + tuple = Tuple.create(createDoubleArray(array)); + } else { + for (Object element : array) { + if (!JSRuntime.isJSPrimitive(element)) { + throw Errors.createTypeError("Tuples cannot contain non-primitive values"); + } + } + tuple = Tuple.create(array); + } + return tuple; + } + + public static TupleContentType identifyPrimitiveContentType(Object[] values) { + boolean bytes = true; + boolean integers = true; + + for (int i = 0; i < values.length; i++) { + Object value = values[i]; + if (integers && value instanceof Integer) { + bytes = bytes && ScriptArray.valueIsByte((int) value); + } else if (value instanceof Double) { + bytes = false; + integers = false; + } else if (!(value instanceof Integer)) { + return TupleContentType.Object; + } + } + + if (bytes) { + return TupleContentType.Byte; + } else if (integers) { + return TupleContentType.Integer; + } else { + return TupleContentType.Double; + } + } + + public static double[] createDoubleArray(Object[] values) { + double[] doubleArray = new double[values.length]; + for (int i = 0; i < values.length; i++) { + Object oValue = values[i]; + if (oValue instanceof Double) { + doubleArray[i] = (double) oValue; + } else if (oValue instanceof Integer) { + doubleArray[i] = (int) oValue; + } + } + return doubleArray; + } + + public static int[] createIntArray(Object[] values) { + int[] intArray = new int[values.length]; + for (int i = 0; i < values.length; i++) { + intArray[i] = (int) values[i]; + } + return intArray; + } + + public static byte[] createByteArray(Object[] values) { + byte[] byteArray = new byte[values.length]; + for (int i = 0; i < values.length; i++) { + byteArray[i] = (byte) ((int) values[i]); + } + return byteArray; + } + + private static class DefaultTupleLiteralNode extends TupleLiteralNode { + + @Children protected final JavaScriptNode[] elements; + + DefaultTupleLiteralNode(JSContext context, JavaScriptNode[] elements) { + super(context); + this.elements = elements; + } + + @Override + public Object execute(VirtualFrame frame) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + Object[] values = new Object[elements.length]; + for (int i = 0; i < elements.length; i++) { + values[i] = elements[i].execute(frame); + } + return TupleLiteralNode.createTuple(values); + } + + @Override + protected JavaScriptNode copyUninitialized(Set> materializedTags) { + return new DefaultTupleLiteralNode(context, cloneUninitialized(elements, materializedTags)); + } + } + + private static final class DefaultTupleLiteralWithSpreadNode extends DefaultTupleLiteralNode { + + private final BranchProfile growProfile = BranchProfile.create(); + + DefaultTupleLiteralWithSpreadNode(JSContext context, JavaScriptNode[] elements) { + super(context, elements); + assert elements.length > 0; + } + + @Override + public Object execute(VirtualFrame frame) { + SimpleArrayList evaluatedElements = new SimpleArrayList<>(elements.length + JSConfig.SpreadArgumentPlaceholderCount); + for (int i = 0; i < elements.length; i++) { + Node node = elements[i]; + if (elements[i] instanceof WrapperNode) { + node = ((WrapperNode) elements[i]).getDelegateNode(); + } + if (node instanceof SpreadTupleNode) { + ((SpreadTupleNode) node).executeToList(frame, evaluatedElements, growProfile); + } else { + evaluatedElements.add(elements[i].execute(frame), growProfile); + } + } + return TupleLiteralNode.createTuple(evaluatedElements.toArray()); + } + + @Override + protected JavaScriptNode copyUninitialized(Set> materializedTags) { + return new DefaultTupleLiteralWithSpreadNode(context, cloneUninitialized(elements, materializedTags)); + } + } + + public static final class SpreadTupleNode extends JavaScriptNode { + @Child private GetIteratorNode getIteratorNode; + @Child private IteratorGetNextValueNode iteratorStepNode; + + private SpreadTupleNode(JSContext context, JavaScriptNode arg) { + this.getIteratorNode = GetIteratorNode.create(context, arg); + this.iteratorStepNode = IteratorGetNextValueNode.create(context, null, JSConstantNode.create(null), false); + } + + public static SpreadTupleNode create(JSContext context, JavaScriptNode arg) { + return new SpreadTupleNode(context, arg); + } + + public void executeToList(VirtualFrame frame, SimpleArrayList toList, BranchProfile growProfile) { + IteratorRecord iteratorRecord = getIteratorNode.execute(frame); + for (;;) { + Object nextArg = iteratorStepNode.execute(frame, iteratorRecord); + if (nextArg == null) { + break; + } + toList.add(nextArg, growProfile); + } + } + + @Override + public Object execute(VirtualFrame frame) { + throw Errors.shouldNotReachHere("Cannot execute SpreadTupleNode"); + } + + @Override + protected JavaScriptNode copyUninitialized(Set> materializedTags) { + SpreadTupleNode copy = (SpreadTupleNode) copy(); + copy.getIteratorNode = cloneUninitialized(getIteratorNode, materializedTags); + copy.iteratorStepNode = cloneUninitialized(iteratorStepNode, materializedTags); + return copy; + } + } + + @Override + public boolean isResultAlwaysOfType(Class clazz) { + return clazz == DynamicObject.class; + } + + public enum TupleContentType { + Byte, + Integer, + Double, + Object + } +} diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/unary/TypeOfNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/unary/TypeOfNode.java index b38309ee60e..7caa7c7258b 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/unary/TypeOfNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/unary/TypeOfNode.java @@ -61,6 +61,7 @@ import com.oracle.truffle.js.runtime.JSConfig; import com.oracle.truffle.js.runtime.JSRuntime; import com.oracle.truffle.js.runtime.Symbol; +import com.oracle.truffle.js.runtime.Tuple; import com.oracle.truffle.js.runtime.builtins.JSBigInt; import com.oracle.truffle.js.runtime.builtins.JSBoolean; import com.oracle.truffle.js.runtime.builtins.JSFunction; @@ -69,6 +70,7 @@ import com.oracle.truffle.js.runtime.builtins.JSProxy; import com.oracle.truffle.js.runtime.builtins.JSString; import com.oracle.truffle.js.runtime.builtins.JSSymbol; +import com.oracle.truffle.js.runtime.builtins.JSTuple; import com.oracle.truffle.js.runtime.objects.Null; import com.oracle.truffle.js.runtime.objects.Undefined; @@ -174,6 +176,11 @@ protected String doSymbol(Symbol operand) { return JSSymbol.TYPE_NAME; } + @Specialization + protected String doTuple(Tuple operand) { + return JSTuple.TYPE_NAME; + } + @TruffleBoundary @Specialization(guards = "isForeignObject(operand)", limit = "InteropLibraryLimit") protected String doTruffleObject(Object operand, diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSContext.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSContext.java index b1953f165a7..986e5389481 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSContext.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSContext.java @@ -100,6 +100,7 @@ import com.oracle.truffle.js.runtime.builtins.JSSharedArrayBuffer; import com.oracle.truffle.js.runtime.builtins.JSString; import com.oracle.truffle.js.runtime.builtins.JSSymbol; +import com.oracle.truffle.js.runtime.builtins.JSTuple; import com.oracle.truffle.js.runtime.builtins.JSWeakMap; import com.oracle.truffle.js.runtime.builtins.JSWeakRef; import com.oracle.truffle.js.runtime.builtins.JSWeakSet; @@ -402,6 +403,8 @@ public enum BuiltinFunctionKey { private final JSObjectFactory webAssemblyTableFactory; private final JSObjectFactory webAssemblyGlobalFactory; + private final JSObjectFactory tupleFactory; + private final int factoryCount; @CompilationFinal private Locale locale; @@ -558,6 +561,8 @@ protected JSContext(Evaluator evaluator, JSContextOptions contextOptions, JavaSc this.webAssemblyTableFactory = builder.create(JSWebAssemblyTable.INSTANCE); this.webAssemblyGlobalFactory = builder.create(JSWebAssemblyGlobal.INSTANCE); + this.tupleFactory = builder.create(JSTuple.INSTANCE); + this.factoryCount = builder.finish(); this.argumentsPropertyProxy = new JSFunction.ArgumentsProxyProperty(this); @@ -984,6 +989,10 @@ public JSObjectFactory getWebAssemblyGlobalFactory() { return webAssemblyGlobalFactory; } + public final JSObjectFactory getTupleFactory() { + return tupleFactory; + } + private static final String REGEX_OPTION_U180E_WHITESPACE = "U180EWhitespace"; private static final String REGEX_OPTION_REGRESSION_TEST_MODE = "RegressionTestMode"; private static final String REGEX_OPTION_DUMP_AUTOMATA = "DumpAutomata"; diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSRealm.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSRealm.java index b32df9b8498..c673077b284 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSRealm.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSRealm.java @@ -134,6 +134,7 @@ import com.oracle.truffle.js.runtime.builtins.JSSymbol; import com.oracle.truffle.js.runtime.builtins.JSTest262; import com.oracle.truffle.js.runtime.builtins.JSTestV8; +import com.oracle.truffle.js.runtime.builtins.JSTuple; import com.oracle.truffle.js.runtime.builtins.JSWeakMap; import com.oracle.truffle.js.runtime.builtins.JSWeakRef; import com.oracle.truffle.js.runtime.builtins.JSWeakSet; @@ -377,6 +378,10 @@ public class JSRealm { /** Foreign object prototypes. */ private final DynamicObject foreignIterablePrototype; + /** Record and Tuple support. */ + private final DynamicObject tupleConstructor; + private final DynamicObject tuplePrototype; + /** * Local time zone ID. Initialized lazily. */ @@ -754,6 +759,17 @@ public JSRealm(JSContext context, TruffleLanguage.Env env) { } this.foreignIterablePrototype = createForeignIterablePrototype(); + + // TODO: Associate with the correct ECMAScript Version + boolean es13 = context.getContextOptions().getEcmaScriptVersion() >= JSConfig.ECMAScript2022; + if (es13) { + ctor = JSTuple.createConstructor(this); + this.tupleConstructor = ctor.getFunctionObject(); + this.tuplePrototype = ctor.getPrototype(); + } else { + this.tupleConstructor = null; + this.tuplePrototype= null; + } } private void initializeTypedArrayConstructors() { @@ -1405,6 +1421,9 @@ public void setupGlobals() { if (context.getContextOptions().isProfileTime()) { System.out.println("SetupGlobals: " + (System.nanoTime() - time) / 1000000); } + if (context.getEcmaScriptVersion() >= 13) { // TODO: Associate with the correct ECMAScript Version + putGlobalProperty(JSTuple.CLASS_NAME, getTupleConstructor()); + } } private void initGlobalNashornExtensions() { @@ -2439,4 +2458,11 @@ public DynamicObject getForeignIterablePrototype() { return foreignIterablePrototype; } + public DynamicObject getTupleConstructor() { + return tupleConstructor; + } + + public DynamicObject getTuplePrototype() { + return tuplePrototype; + } } diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSRuntime.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSRuntime.java index 283040dfe14..1888e6bb11b 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSRuntime.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSRuntime.java @@ -77,6 +77,7 @@ import com.oracle.truffle.js.runtime.builtins.JSSet; import com.oracle.truffle.js.runtime.builtins.JSString; import com.oracle.truffle.js.runtime.builtins.JSSymbol; +import com.oracle.truffle.js.runtime.builtins.JSTuple; import com.oracle.truffle.js.runtime.doubleconv.DoubleConversion; import com.oracle.truffle.js.runtime.external.DToA; import com.oracle.truffle.js.runtime.interop.InteropFunction; @@ -225,6 +226,8 @@ public static String typeof(Object value) { return JSBoolean.TYPE_NAME; } else if (value instanceof Symbol) { return JSSymbol.TYPE_NAME; + } else if (value instanceof Tuple) { + return JSTuple.TYPE_NAME; } else if (JSDynamicObject.isJSDynamicObject(value)) { DynamicObject object = (DynamicObject) value; if (JSProxy.isJSProxy(object)) { @@ -527,6 +530,10 @@ public static boolean isBigInt(Object value) { return value instanceof BigInt; } + public static boolean isTuple(Object value) { + return value instanceof Tuple; + } + public static boolean isJavaNumber(Object value) { return value instanceof Number; } @@ -1512,6 +1519,8 @@ public static TruffleObject toObjectFromPrimitive(JSContext ctx, Object value, b return JSNumber.create(ctx, (Number) value); } else if (value instanceof Symbol) { return JSSymbol.create(ctx, (Symbol) value); + } else if (value instanceof Tuple) { + return JSTuple.create(ctx, (Tuple) value); } else { assert !isJSNative(value) && isJavaPrimitive(value) : value; if (useJavaWrapper) { @@ -1579,6 +1588,8 @@ public static boolean equal(Object a, Object b) { return equalBigIntAndNumber((BigInt) b, (Number) a); } else if (isBigInt(a) && isJavaNumber(b)) { return equalBigIntAndNumber((BigInt) a, (Number) b); + } else if (isTuple(a) && isTuple(b)) { + return a.equals(b); } else if (a instanceof Boolean) { return equal(booleanToNumber((Boolean) a), b); } else if (b instanceof Boolean) { @@ -1602,7 +1613,7 @@ public static boolean isForeignObject(Object value) { public static boolean isForeignObject(TruffleObject value) { return !JSDynamicObject.isJSDynamicObject(value) && !(value instanceof Symbol) && !(value instanceof JSLazyString) && !(value instanceof SafeInteger) && - !(value instanceof BigInt); + !(value instanceof BigInt) && !(value instanceof Tuple); } private static boolean equalInterop(Object a, Object b) { @@ -1667,6 +1678,9 @@ public static boolean identical(Object a, Object b) { if (isBigInt(a) && isBigInt(b)) { return a.equals(b); } + if (isTuple(a) && isTuple(b)) { + return a.equals(b); + } if (isJavaNumber(a) && isJavaNumber(b)) { if (a instanceof Integer && b instanceof Integer) { return ((Integer) a).intValue() == ((Integer) b).intValue(); @@ -1990,7 +2004,7 @@ public static boolean isJSNative(Object value) { } public static boolean isJSPrimitive(Object value) { - return isNumber(value) || value instanceof BigInt || value instanceof Boolean || isString(value) || value == Undefined.instance || value == Null.instance || value instanceof Symbol; + return isNumber(value) || value instanceof BigInt || value instanceof Boolean || isString(value) || value == Undefined.instance || value == Null.instance || value instanceof Symbol || value instanceof Tuple; } /** diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Tuple.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Tuple.java new file mode 100644 index 00000000000..c23039b4803 --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Tuple.java @@ -0,0 +1,126 @@ +package com.oracle.truffle.js.runtime; + +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.api.CompilerDirectives.ValueType; +import com.oracle.truffle.api.TruffleLanguage; +import com.oracle.truffle.api.interop.InteropLibrary; +import com.oracle.truffle.api.interop.TruffleObject; +import com.oracle.truffle.api.library.ExportLibrary; +import com.oracle.truffle.api.library.ExportMessage; +import com.oracle.truffle.js.lang.JavaScriptLanguage; +import com.oracle.truffle.js.runtime.interop.JSMetaType; + +import java.util.Arrays; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +@ExportLibrary(InteropLibrary.class) +@ValueType +public final class Tuple implements TruffleObject { + + public static final Tuple EMPTY_TUPLE = new Tuple(new Object[]{}); // TODO: create a tuple sub-class for empty tuples + + private final Object[] value; + + private Tuple(Object[] v) { + this.value = v; + } + + public static Tuple create() { + return EMPTY_TUPLE; + } + + public static Tuple create(Object[] v) { + return new Tuple(v); + } + + public static Tuple create(byte[] v) { + // TODO: create a tuple sub-class for byte arrays + return new Tuple( + IntStream.range(0, v.length).mapToObj(i -> v[i]).toArray() + ); + } + + public static Tuple create(int[] v) { + // TODO: create a tuple sub-class for int arrays + return new Tuple(Arrays.stream(v).boxed().toArray()); + } + + public static Tuple create(double[] v) { + // TODO: create a tuple sub-class for double arrays + return new Tuple(Arrays.stream(v).boxed().toArray()); + } + + @Override + @TruffleBoundary + public int hashCode() { + return Arrays.hashCode(value); + } + + @Override + @TruffleBoundary + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + return equals((Tuple) obj); + } + + public boolean equals(Tuple other) { + if (value == null || value.length == 0) { + return other.value == null || other.value.length == 0; + } + if (value.length != other.value.length) { + return false; + } + for (int i = 0; i < value.length; i++) { + if (!JSRuntime.identical(value[i], other.value[i])) { + return false; + } + } + return true; + } + + @Override + @TruffleBoundary + public String toString() { + if (value == null) return ""; + return Arrays.stream(value).map(String::valueOf).collect(Collectors.joining(",")); + } + + @SuppressWarnings("static-method") + @ExportMessage + boolean hasLanguage() { + return true; + } + + @SuppressWarnings("static-method") + @ExportMessage + Class> getLanguage() { + return JavaScriptLanguage.class; + } + + @TruffleBoundary + @ExportMessage + Object toDisplayString(@SuppressWarnings("unused") boolean allowSideEffects) { + return "#[" + toString() + "]"; + } + + @SuppressWarnings("static-method") + @ExportMessage + boolean hasMetaObject() { + return true; + } + + @SuppressWarnings("static-method") + @ExportMessage + Object getMetaObject() { + return JSMetaType.JS_TUPLE; + } +} diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSTuple.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSTuple.java new file mode 100644 index 00000000000..2638c79ee8e --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSTuple.java @@ -0,0 +1,72 @@ +package com.oracle.truffle.js.runtime.builtins; + +import com.oracle.truffle.api.object.DynamicObject; +import com.oracle.truffle.api.object.Shape; +import com.oracle.truffle.js.builtins.TupleFunctionBuiltins; +import com.oracle.truffle.js.builtins.TuplePrototypeBuiltins; +import com.oracle.truffle.js.runtime.JSContext; +import com.oracle.truffle.js.runtime.JSRealm; +import com.oracle.truffle.js.runtime.Tuple; +import com.oracle.truffle.js.runtime.objects.JSObjectUtil; + +public final class JSTuple extends JSNonProxy implements JSConstructorFactory.Default.WithFunctions, PrototypeSupplier { + + public static final String TYPE_NAME = "tuple"; + public static final String CLASS_NAME = "Tuple"; + public static final String PROTOTYPE_NAME = "Tuple.prototype"; + + public static final JSTuple INSTANCE = new JSTuple(); + + private JSTuple() { + } + + public static DynamicObject create(JSContext context, Tuple value) { + DynamicObject obj = JSTupleObject.create(context.getRealm(), context.getTupleFactory(), value); + assert isJSTuple(obj); + return context.trackAllocation(obj); + } + + public static Tuple valueOf(DynamicObject obj) { + assert isJSTuple(obj); + return ((JSTupleObject) obj).getTupleValue(); + } + + @Override + public DynamicObject createPrototype(final JSRealm realm, DynamicObject ctor) { + JSContext context = realm.getContext(); + DynamicObject prototype = JSObjectUtil.createOrdinaryPrototypeObject(realm); + JSObjectUtil.putConstructorProperty(context, prototype, ctor); + JSObjectUtil.putFunctionsFromContainer(realm, prototype, TuplePrototypeBuiltins.BUILTINS); + JSObjectUtil.putToStringTag(prototype, CLASS_NAME); + return prototype; + } + + @Override + public Shape makeInitialShape(JSContext context, DynamicObject prototype) { + Shape initialShape = JSObjectUtil.getProtoChildShape(prototype, INSTANCE, context); + return initialShape; + } + + public static JSConstructor createConstructor(JSRealm realm) { + return INSTANCE.createConstructorAndPrototype(realm, TupleFunctionBuiltins.BUILTINS); + } + + public static boolean isJSTuple(Object obj) { + return obj instanceof JSTupleObject; + } + + @Override + public String getClassName() { + return CLASS_NAME; + } + + @Override + public String getClassName(DynamicObject object) { + return getClassName(); + } + + @Override + public DynamicObject getIntrinsicDefaultProto(JSRealm realm) { + return realm.getTuplePrototype(); + } +} diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSTupleObject.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSTupleObject.java new file mode 100644 index 00000000000..0183df511b9 --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSTupleObject.java @@ -0,0 +1,30 @@ +package com.oracle.truffle.js.runtime.builtins; + +import com.oracle.truffle.api.object.DynamicObject; +import com.oracle.truffle.api.object.Shape; +import com.oracle.truffle.js.runtime.JSRealm; +import com.oracle.truffle.js.runtime.Tuple; +import com.oracle.truffle.js.runtime.objects.JSNonProxyObject; + +public final class JSTupleObject extends JSNonProxyObject { + + private final Tuple value; + + protected JSTupleObject(Shape shape, Tuple value) { + super(shape); + this.value = value; + } + + public Tuple getTupleValue() { + return value; + } + + @Override + public String getClassName() { + return JSTuple.CLASS_NAME; + } + + public static DynamicObject create(JSRealm realm, JSObjectFactory factory, Tuple value) { + return factory.initProto(new JSTupleObject(factory.getShape(realm), value), realm); + } +} diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/interop/JSMetaType.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/interop/JSMetaType.java index 723ea6e63de..df807b95dd6 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/interop/JSMetaType.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/interop/JSMetaType.java @@ -55,6 +55,7 @@ import com.oracle.truffle.js.nodes.JSGuards; import com.oracle.truffle.js.runtime.BigInt; import com.oracle.truffle.js.runtime.Symbol; +import com.oracle.truffle.js.runtime.Tuple; /** * General meta objects for JS values and foreign objects through {@link JavaScriptLanguageView}. @@ -87,6 +88,7 @@ public interface TypeCheck { public static final JSMetaType JS_UNDEFINED = new JSMetaType("undefined", (l, v) -> JSGuards.isUndefined(v)); public static final JSMetaType JS_BIGINT = new JSMetaType("bigint", (l, v) -> v instanceof BigInt); public static final JSMetaType JS_SYMBOL = new JSMetaType("symbol", (l, v) -> v instanceof Symbol); + public static final JSMetaType JS_TUPLE = new JSMetaType("tuple", (l, v) -> v instanceof Tuple); public static final JSMetaType JS_PROXY = new JSMetaType("Proxy", (l, v) -> JSGuards.isJSProxy(v)); private final String typeName; From 31d204535fef445858da0df3f837fe0258c2ebab Mon Sep 17 00:00:00 2001 From: Christian Aistleitner Date: Wed, 24 Mar 2021 14:57:42 +0100 Subject: [PATCH 03/31] Update type conversion nodes/methods to support tuples. --- .../js/record-tuple/tuple.js | 45 +++++++++++++++++++ .../js/nodes/cast/JSPrepareThisNode.java | 7 +++ .../truffle/js/nodes/cast/JSToBigIntNode.java | 6 +++ .../js/nodes/cast/JSToBooleanNode.java | 6 +++ .../js/nodes/cast/JSToBooleanUnaryNode.java | 6 +++ .../truffle/js/nodes/cast/JSToDoubleNode.java | 6 +++ .../truffle/js/nodes/cast/JSToInt32Node.java | 6 +++ .../js/nodes/cast/JSToIntegerAsIntNode.java | 6 +++ .../js/nodes/cast/JSToIntegerAsLongNode.java | 6 +++ .../truffle/js/nodes/cast/JSToNumberNode.java | 6 +++ .../truffle/js/nodes/cast/JSToObjectNode.java | 7 +++ .../truffle/js/nodes/cast/JSToStringNode.java | 1 + .../js/nodes/cast/JSToStringOrNumberNode.java | 6 +++ .../truffle/js/nodes/cast/JSToUInt32Node.java | 6 +++ .../oracle/truffle/js/runtime/JSRuntime.java | 6 +++ 15 files changed, 126 insertions(+) diff --git a/graal-js/src/com.oracle.truffle.js.test/js/record-tuple/tuple.js b/graal-js/src/com.oracle.truffle.js.test/js/record-tuple/tuple.js index 45a950d993f..32704c72872 100644 --- a/graal-js/src/com.oracle.truffle.js.test/js/record-tuple/tuple.js +++ b/graal-js/src/com.oracle.truffle.js.test/js/record-tuple/tuple.js @@ -13,6 +13,8 @@ load('../assert.js'); +let a, b, c, d, e, f; + /* * Test 1: * valid tuple literals @@ -123,3 +125,46 @@ assertTrue(Tuple.isTuple(Tuple(1))); assertFalse(Tuple.isTuple(1)); assertFalse(Tuple.isTuple("1")); assertFalse(Tuple.isTuple(BigInt(1))); + +/* + * Test 11: + * Type Conversion + */ +a = #[42]; +// 3.1.1 ToBoolean +assertSame(true, !!a); // JSToBigIntNode +if (a) { // JSToBooleanUnaryNode + // ok +} else { + fail("#[42] should be true") +} +// 3.1.2 ToNumber +assertThrows(function() { + eval("a + 1"); // JSToNumberNode +}, TypeError); +assertThrows(function() { + eval("Math.abs(a)"); // JSToDoubleNode +}, TypeError); +assertThrows(function() { + eval("parseInt(\"1\", a)"); // JSToInt32Node +}, TypeError); +assertThrows(function() { + eval("'ABC'.codePointAt(a)"); // JSToIntegerAsIntNode +}, TypeError); +assertThrows(function() { + eval("[1].at(a)"); // JSToIntegerAsLongNode +}, TypeError); +assertThrows(function() { + eval("'ABC'.split('', a);"); // JSToUInt32Node +}, TypeError); +// 3.1.3 ToBigInt +assertThrows(function() { + eval("BigInt.asIntN(64, a)"); +}, TypeError); +// 3.1.4 ToString +assertSame("42", a + ""); // JSToStringNode +assertSame([10] < [2], #[10] < #[2]); // JSToStringOrNumberNode +assertSame([1] < [1], #[1] < #[1]); +assertSame([1] <= 1, #[1] <= 1); +// 3.1.5 ToObject +// haven't found a test case yet diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSPrepareThisNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSPrepareThisNode.java index 4c19a3c09b4..cb137571e48 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSPrepareThisNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSPrepareThisNode.java @@ -56,11 +56,13 @@ import com.oracle.truffle.js.runtime.JSConfig; import com.oracle.truffle.js.runtime.JSContext; import com.oracle.truffle.js.runtime.Symbol; +import com.oracle.truffle.js.runtime.Tuple; import com.oracle.truffle.js.runtime.builtins.JSBigInt; import com.oracle.truffle.js.runtime.builtins.JSBoolean; import com.oracle.truffle.js.runtime.builtins.JSNumber; import com.oracle.truffle.js.runtime.builtins.JSString; import com.oracle.truffle.js.runtime.builtins.JSSymbol; +import com.oracle.truffle.js.runtime.builtins.JSTuple; import com.oracle.truffle.js.runtime.objects.JSLazyString; /** @@ -140,6 +142,11 @@ protected DynamicObject doSymbol(Symbol value) { return JSSymbol.create(context, value); } + @Specialization + protected DynamicObject doTuple(Tuple value) { + return JSTuple.create(context, value); + } + @Specialization(guards = "isForeignObject(object)", limit = "InteropLibraryLimit") protected Object doForeignObject(Object object, @CachedLibrary("object") InteropLibrary interop) { diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToBigIntNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToBigIntNode.java index 03d7fc80655..30552d7ea3b 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToBigIntNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToBigIntNode.java @@ -48,6 +48,7 @@ import com.oracle.truffle.js.runtime.Errors; import com.oracle.truffle.js.runtime.JSErrorType; import com.oracle.truffle.js.runtime.Symbol; +import com.oracle.truffle.js.runtime.Tuple; public abstract class JSToBigIntNode extends JavaScriptBaseNode { @@ -101,6 +102,11 @@ protected static BigInt doSymbol(Symbol value) { throw Errors.createErrorCanNotConvertToBigInt(JSErrorType.TypeError, value); } + @Specialization + protected static BigInt doTuple(Tuple value) { + throw Errors.createErrorCanNotConvertToBigInt(JSErrorType.TypeError, value); + } + @Specialization(guards = "isNullOrUndefined(value)") protected static BigInt doNullOrUndefined(Object value) { throw Errors.createErrorCanNotConvertToBigInt(JSErrorType.TypeError, value); diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToBooleanNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToBooleanNode.java index 5da796e1ac3..db9385ae9d6 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToBooleanNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToBooleanNode.java @@ -51,6 +51,7 @@ import com.oracle.truffle.js.runtime.Errors; import com.oracle.truffle.js.runtime.JSConfig; import com.oracle.truffle.js.runtime.Symbol; +import com.oracle.truffle.js.runtime.Tuple; import com.oracle.truffle.js.runtime.objects.JSLazyString; /** @@ -124,6 +125,11 @@ protected static boolean doSymbol(@SuppressWarnings("unused") Symbol value) { return true; } + @Specialization + protected static boolean doTuple(@SuppressWarnings("unused") Tuple value) { + return true; + } + @Specialization(guards = "isForeignObject(value)", limit = "InteropLibraryLimit") protected final boolean doForeignObject(Object value, @CachedLibrary("value") InteropLibrary interop) { diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToBooleanUnaryNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToBooleanUnaryNode.java index 5390acdf1d6..e4603d845b8 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToBooleanUnaryNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToBooleanUnaryNode.java @@ -56,6 +56,7 @@ import com.oracle.truffle.js.runtime.JSConfig; import com.oracle.truffle.js.runtime.JSRuntime; import com.oracle.truffle.js.runtime.Symbol; +import com.oracle.truffle.js.runtime.Tuple; import com.oracle.truffle.js.runtime.objects.JSLazyString; /** @@ -155,6 +156,11 @@ protected static boolean doSymbol(@SuppressWarnings("unused") Symbol value) { return true; } + @Specialization + protected static boolean doTuple(@SuppressWarnings("unused") Tuple value) { + return true; + } + @Specialization(guards = "isForeignObject(value)") protected static boolean doForeignObject(Object value, @Cached JSToBooleanNode toBooleanNode) { diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToDoubleNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToDoubleNode.java index 6aeddc88756..1ddca0e3a18 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToDoubleNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToDoubleNode.java @@ -49,6 +49,7 @@ import com.oracle.truffle.js.runtime.Errors; import com.oracle.truffle.js.runtime.JSRuntime; import com.oracle.truffle.js.runtime.Symbol; +import com.oracle.truffle.js.runtime.Tuple; /** * This implements ECMA 9.3 ToNumber, but always converting the result to a double value. @@ -113,6 +114,11 @@ protected final double doSymbol(@SuppressWarnings("unused") Symbol value) { throw Errors.createTypeErrorCannotConvertToNumber("a Symbol value", this); } + @Specialization + protected final double doTuple(@SuppressWarnings("unused") Tuple value) { + throw Errors.createTypeErrorCannotConvertToNumber("a Tuple value", this); + } + @Specialization(guards = "isForeignObject(object)") protected double doForeignObject(Object object, @Cached("createHintNumber()") JSToPrimitiveNode toPrimitiveNode) { diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToInt32Node.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToInt32Node.java index 1919a24e9a9..f3cb741ad6a 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToInt32Node.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToInt32Node.java @@ -57,6 +57,7 @@ import com.oracle.truffle.js.runtime.JSRuntime; import com.oracle.truffle.js.runtime.SafeInteger; import com.oracle.truffle.js.runtime.Symbol; +import com.oracle.truffle.js.runtime.Tuple; import java.util.Set; @@ -186,6 +187,11 @@ protected final int doSymbol(@SuppressWarnings("unused") Symbol value) { throw Errors.createTypeErrorCannotConvertToNumber("a Symbol value", this); } + @Specialization + protected final int doTuple(@SuppressWarnings("unused") Tuple value) { + throw Errors.createTypeErrorCannotConvertToNumber("a Tuple value", this); + } + @Specialization protected int doBigInt(@SuppressWarnings("unused") BigInt value) { throw Errors.createTypeErrorCannotConvertBigIntToNumber(this); diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToIntegerAsIntNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToIntegerAsIntNode.java index 88c37244340..f77b65ee5c7 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToIntegerAsIntNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToIntegerAsIntNode.java @@ -50,6 +50,7 @@ import com.oracle.truffle.js.runtime.JSRuntime; import com.oracle.truffle.js.runtime.SafeInteger; import com.oracle.truffle.js.runtime.Symbol; +import com.oracle.truffle.js.runtime.Tuple; /** * Basically ECMAScript ToInteger, but correct only for values in the int32 range. Used by built-in @@ -128,6 +129,11 @@ protected final int doSymbol(@SuppressWarnings("unused") Symbol value) { throw Errors.createTypeErrorCannotConvertToNumber("a Symbol value", this); } + @Specialization + protected final int doTuple(@SuppressWarnings("unused") Tuple value) { + throw Errors.createTypeErrorCannotConvertToNumber("a Tuple value", this); + } + @Specialization protected final int doBigInt(@SuppressWarnings("unused") BigInt value) { throw Errors.createTypeErrorCannotConvertToNumber("a BigInt value", this); diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToIntegerAsLongNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToIntegerAsLongNode.java index 66cb066fc0a..7350b397c80 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToIntegerAsLongNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToIntegerAsLongNode.java @@ -50,6 +50,7 @@ import com.oracle.truffle.js.runtime.JSRuntime; import com.oracle.truffle.js.runtime.SafeInteger; import com.oracle.truffle.js.runtime.Symbol; +import com.oracle.truffle.js.runtime.Tuple; /** * Basically ECMAScript ToInteger, but correct only for values in the safe integer range. Used by @@ -109,6 +110,11 @@ protected final long doSymbol(@SuppressWarnings("unused") Symbol value) { throw Errors.createTypeErrorCannotConvertToNumber("a Symbol value", this); } + @Specialization + protected final long doTuple(@SuppressWarnings("unused") Tuple value) { + throw Errors.createTypeErrorCannotConvertToNumber("a Tuple value", this); + } + @Specialization protected final long doBigInt(@SuppressWarnings("unused") BigInt value) { throw Errors.createTypeErrorCannotConvertToNumber("a BigInt value", this); diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToNumberNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToNumberNode.java index 2140721196a..8e81a119e4f 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToNumberNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToNumberNode.java @@ -53,6 +53,7 @@ import com.oracle.truffle.js.runtime.Errors; import com.oracle.truffle.js.runtime.JSRuntime; import com.oracle.truffle.js.runtime.Symbol; +import com.oracle.truffle.js.runtime.Tuple; import java.util.Set; @@ -129,6 +130,11 @@ protected final Number doBigInt(@SuppressWarnings("unused") BigInt value) { throw Errors.createTypeErrorCannotConvertToNumber("a BigInt value", this); } + @Specialization + protected final Number doTuple(@SuppressWarnings("unused") Tuple value) { + throw Errors.createTypeErrorCannotConvertToNumber("a Tuple value", this); + } + @Specialization(guards = "isForeignObject(value)") protected Number doForeignObject(Object value, @Cached("createHintNumber()") JSToPrimitiveNode toPrimitiveNode) { diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToObjectNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToObjectNode.java index 25b8938cbb2..c92f5e8b2dd 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToObjectNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToObjectNode.java @@ -62,11 +62,13 @@ import com.oracle.truffle.js.runtime.JSException; import com.oracle.truffle.js.runtime.JSRuntime; import com.oracle.truffle.js.runtime.Symbol; +import com.oracle.truffle.js.runtime.Tuple; import com.oracle.truffle.js.runtime.builtins.JSBigInt; import com.oracle.truffle.js.runtime.builtins.JSBoolean; import com.oracle.truffle.js.runtime.builtins.JSNumber; import com.oracle.truffle.js.runtime.builtins.JSString; import com.oracle.truffle.js.runtime.builtins.JSSymbol; +import com.oracle.truffle.js.runtime.builtins.JSTuple; import com.oracle.truffle.js.runtime.interop.JSInteropUtil; import com.oracle.truffle.js.runtime.objects.JSLazyString; import com.oracle.truffle.js.runtime.objects.Null; @@ -173,6 +175,11 @@ protected DynamicObject doSymbol(Symbol value) { return JSSymbol.create(getContext(), value); } + @Specialization + protected DynamicObject doTuple(Tuple value) { + return JSTuple.create(getContext(), value); + } + @Specialization(guards = {"cachedClass != null", "cachedClass.isInstance(object)"}, limit = "1") protected static Object doJSObjectCached(Object object, @Cached("getClassIfObject(object)") Class cachedClass) { diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToStringNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToStringNode.java index 1bcfcf90139..487107825c1 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToStringNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToStringNode.java @@ -164,6 +164,7 @@ protected String doSymbol(Symbol value) { @Specialization protected String doTuple(Tuple value) { + // TODO: abstract operation "TupleToString" ? return value.toString(); } diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToStringOrNumberNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToStringOrNumberNode.java index bc6af14bb7d..0f285879a5e 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToStringOrNumberNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToStringOrNumberNode.java @@ -49,6 +49,7 @@ import com.oracle.truffle.js.runtime.JSRuntime; import com.oracle.truffle.js.runtime.SafeInteger; import com.oracle.truffle.js.runtime.Symbol; +import com.oracle.truffle.js.runtime.Tuple; /** * This node is intended to be used only by comparison operators. @@ -111,6 +112,11 @@ protected Object doSymbol(@SuppressWarnings("unused") Symbol value) { throw Errors.createTypeErrorCannotConvertToNumber("a Symbol value", this); } + @Specialization + protected String doTuple(Tuple value, @Cached("create()") JSToStringNode toStringNode) { + return toStringNode.executeString(value); + } + @Specialization(guards = "isUndefined(value)") protected double doUndefined(@SuppressWarnings("unused") Object value) { return Double.NaN; diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToUInt32Node.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToUInt32Node.java index 3cb10f621b0..cad32477a51 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToUInt32Node.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToUInt32Node.java @@ -61,6 +61,7 @@ import com.oracle.truffle.js.runtime.JSRuntime; import com.oracle.truffle.js.runtime.SafeInteger; import com.oracle.truffle.js.runtime.Symbol; +import com.oracle.truffle.js.runtime.Tuple; import java.util.Set; @@ -149,6 +150,11 @@ protected final Number doSymbol(@SuppressWarnings("unused") Symbol value) { throw Errors.createTypeErrorCannotConvertToNumber("a Symbol value", this); } + @Specialization + protected final Number doTuple(@SuppressWarnings("unused") Tuple value) { + throw Errors.createTypeErrorCannotConvertToNumber("a Tuple value", this); + } + @Specialization protected int doBigInt(@SuppressWarnings("unused") BigInt value) { throw Errors.createTypeErrorCannotConvertBigIntToNumber(this); diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSRuntime.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSRuntime.java index 1888e6bb11b..b5c25457e24 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSRuntime.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSRuntime.java @@ -490,6 +490,8 @@ public static Number toNumberFromPrimitive(Object value) { return stringToNumber(value.toString()); } else if (value instanceof Symbol) { throw Errors.createTypeErrorCannotConvertToNumber("a Symbol value"); + } else if (value instanceof Tuple) { + throw Errors.createTypeErrorCannotConvertToNumber("a Tuple value"); } else if (value instanceof BigInt) { throw Errors.createTypeErrorCannotConvertToNumber("a BigInt value"); } else if (value instanceof Number) { @@ -926,6 +928,8 @@ public static String toString(Object value) { return numberToString((Number) value); } else if (value instanceof Symbol) { throw Errors.createTypeErrorCannotConvertToString("a Symbol value"); + } else if (value instanceof Tuple) { + return value.toString(); } else if (value instanceof BigInt) { return value.toString(); } else if (JSDynamicObject.isJSDynamicObject(value)) { @@ -979,6 +983,8 @@ private static String toDisplayStringImpl(Object value, int depth, Object parent return JSObject.toDisplayString((DynamicObject) value, depth, allowSideEffects); } else if (value instanceof Symbol) { return value.toString(); + } else if (value instanceof Tuple) { + return value.toString(); } else if (value instanceof BigInt) { return value.toString() + 'n'; } else if (isNumber(value)) { From ff319f0f2326a09aaa2cbda565812035c7e5f339 Mon Sep 17 00:00:00 2001 From: Christian Aistleitner Date: Tue, 30 Mar 2021 14:27:10 +0200 Subject: [PATCH 04/31] Fix tuple index access. --- .../js/record-tuple/tuple.js | 40 ++++++++++- .../js/builtins/TuplePrototypeBuiltins.java | 32 ++++++++- .../TuplePrototypeGetterBuiltins.java | 69 +++++++++++++++++++ .../js/nodes/access/ReadElementNode.java | 28 ++++++++ .../com/oracle/truffle/js/runtime/Tuple.java | 41 ++++++++++- .../truffle/js/runtime/builtins/JSTuple.java | 41 +++++++++++ 6 files changed, 248 insertions(+), 3 deletions(-) create mode 100644 graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TuplePrototypeGetterBuiltins.java diff --git a/graal-js/src/com.oracle.truffle.js.test/js/record-tuple/tuple.js b/graal-js/src/com.oracle.truffle.js.test/js/record-tuple/tuple.js index 32704c72872..c18c6519b08 100644 --- a/graal-js/src/com.oracle.truffle.js.test/js/record-tuple/tuple.js +++ b/graal-js/src/com.oracle.truffle.js.test/js/record-tuple/tuple.js @@ -23,7 +23,7 @@ const t1a = #[]; const t1b = #[1, 2]; const t1c = #[1, 2, #[3]]; // TODO: const t1d = #[1, 2, #{ a: 3 }]; -// TODO: const t1e = #[...t1b, 3]; +const t1e = #[...t1b, 3]; /* * Test 2: @@ -168,3 +168,41 @@ assertSame([1] < [1], #[1] < #[1]); assertSame([1] <= 1, #[1] <= 1); // 3.1.5 ToObject // haven't found a test case yet + +/* + * Test 12: + * Destructuring + */ +[a, ...b] = #[1, 2, 3]; +assertSame(1, a); +assertSame(true, Array.isArray(b)); +assertSame("2,3", b.toString()); +assertSame(#[2, 3], #[...b]); + +/* + * Test 13: + * Spreading + */ +a = #[1, 2]; +b = [1, 2]; +assertSame(#[1, 2, 3], #[...a, 3]); +assertSame(#[1, 2, 3], #[...b, 3]); + +/* + * Test 14: + * Spreading + */ +a = #[1, 2]; +b = [1, 2]; +assertSame(#[1, 2, 3], #[...a, 3]); +assertSame(#[1, 2, 3], #[...b, 3]); + +/* + * Test 15: + * Access (using index) + */ +a = #[1, "2", BigInt(42)]; +b = [1, "2", BigInt(42)]; +for (let i = -1; i < 5; i++) { + assertSame(b[i], a[i]); +} diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TuplePrototypeBuiltins.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TuplePrototypeBuiltins.java index 923ad18ca30..283dc865467 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TuplePrototypeBuiltins.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TuplePrototypeBuiltins.java @@ -1,12 +1,16 @@ package com.oracle.truffle.js.builtins; +import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.Fallback; import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.object.DynamicObject; +import com.oracle.truffle.js.nodes.cast.JSToObjectNode; import com.oracle.truffle.js.nodes.function.JSBuiltin; import com.oracle.truffle.js.nodes.function.JSBuiltinNode; import com.oracle.truffle.js.runtime.Errors; import com.oracle.truffle.js.runtime.JSContext; +import com.oracle.truffle.js.runtime.JSRuntime; import com.oracle.truffle.js.runtime.Tuple; import com.oracle.truffle.js.runtime.builtins.BuiltinEnum; import com.oracle.truffle.js.runtime.builtins.JSTuple; @@ -23,7 +27,8 @@ protected TuplePrototypeBuiltins() { } public enum TuplePrototype implements BuiltinEnum { - toString(0); + toString(0), + values(0); private final int length; @@ -42,6 +47,8 @@ protected Object createNode(JSContext context, JSBuiltin builtin, boolean constr switch (builtinEnum) { case toString: return TuplePrototypeBuiltinsFactory.JSTupleToStringNodeGen.create(context, builtin, args().withThis().fixedArgs(1).createArgumentNodes(context)); + case values: + return TuplePrototypeBuiltinsFactory.TupleIteratorNodeGen.create(context, builtin, JSRuntime.ITERATION_KIND_VALUE, args().withThis().createArgumentNodes(context)); } return null; } @@ -67,4 +74,27 @@ protected void toStringNoTuple(Object thisObj) { throw Errors.createTypeError("Tuple.prototype.toString requires that 'this' be a Tuple"); } } + + public abstract static class TupleIteratorNode extends JSBuiltinNode { + @Child private ArrayPrototypeBuiltins.CreateArrayIteratorNode createArrayIteratorNode; + + public TupleIteratorNode(JSContext context, JSBuiltin builtin, int iterationKind) { + super(context, builtin); + this.createArrayIteratorNode = ArrayPrototypeBuiltins.CreateArrayIteratorNode.create(context, iterationKind); + } + + @Specialization(guards = "isJSObject(thisObj)") + protected DynamicObject doJSObject(VirtualFrame frame, DynamicObject thisObj) { + return createArrayIteratorNode.execute(frame, thisObj); + } + + @Specialization(guards = "!isJSObject(thisObj)") + protected DynamicObject doNotJSObject( + VirtualFrame frame, + Object thisObj, + @Cached("createToObject(getContext())") JSToObjectNode toObjectNode + ) { + return createArrayIteratorNode.execute(frame, toObjectNode.execute(thisObj)); + } + } } diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TuplePrototypeGetterBuiltins.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TuplePrototypeGetterBuiltins.java new file mode 100644 index 00000000000..8d18d0c1688 --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TuplePrototypeGetterBuiltins.java @@ -0,0 +1,69 @@ +package com.oracle.truffle.js.builtins; + +import com.oracle.truffle.api.dsl.Fallback; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.object.DynamicObject; +import com.oracle.truffle.js.nodes.function.JSBuiltin; +import com.oracle.truffle.js.nodes.function.JSBuiltinNode; +import com.oracle.truffle.js.runtime.Errors; +import com.oracle.truffle.js.runtime.JSContext; +import com.oracle.truffle.js.runtime.Tuple; +import com.oracle.truffle.js.runtime.builtins.BuiltinEnum; +import com.oracle.truffle.js.runtime.builtins.JSTuple; + +/** + * Contains accessor builtins for Tuple.prototype. + */ +public final class TuplePrototypeGetterBuiltins extends JSBuiltinsContainer.SwitchEnum { + + public static final JSBuiltinsContainer BUILTINS = new TuplePrototypeGetterBuiltins(); + + protected TuplePrototypeGetterBuiltins() { + super(JSTuple.PROTOTYPE_NAME, TuplePrototype.class); + } + + public enum TuplePrototype implements BuiltinEnum { + length; + + @Override + public int getLength() { + return 0; + } + + @Override + public boolean isGetter() { + return true; + } + } + + @Override + protected Object createNode(JSContext context, JSBuiltin builtin, boolean construct, boolean newTarget, TuplePrototype builtinEnum) { + switch (builtinEnum) { + case length: + return TuplePrototypeGetterBuiltinsFactory.LengthAccessorNodeGen.create(context, builtin, args().withThis().fixedArgs(1).createArgumentNodes(context)); + } + return null; + } + + public abstract static class LengthAccessor extends JSBuiltinNode { + + public LengthAccessor(JSContext context, JSBuiltin builtin) { + super(context, builtin); + } + + @Specialization + protected long doTuple(Tuple thisObj) { + return thisObj.getArraySize(); + } + + @Specialization(guards = {"isJSTuple(thisObj)"}) + protected long doJSTuple(DynamicObject thisObj) { + return JSTuple.valueOf(thisObj).getArraySize(); + } + + @Fallback + protected void doObject(Object thisObj) { + Errors.createTypeErrorIncompatibleReceiver(thisObj); + } + } +} diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/access/ReadElementNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/access/ReadElementNode.java index 4d79ded066a..9a27bd099c7 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/access/ReadElementNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/access/ReadElementNode.java @@ -85,6 +85,7 @@ import com.oracle.truffle.js.runtime.JSContext; import com.oracle.truffle.js.runtime.JSRuntime; import com.oracle.truffle.js.runtime.Symbol; +import com.oracle.truffle.js.runtime.Tuple; import com.oracle.truffle.js.runtime.array.ArrayAllocationSite; import com.oracle.truffle.js.runtime.array.ScriptArray; import com.oracle.truffle.js.runtime.array.TypedArray; @@ -109,6 +110,7 @@ import com.oracle.truffle.js.runtime.builtins.JSSlowArray; import com.oracle.truffle.js.runtime.builtins.JSString; import com.oracle.truffle.js.runtime.builtins.JSSymbol; +import com.oracle.truffle.js.runtime.builtins.JSTuple; import com.oracle.truffle.js.runtime.objects.JSDynamicObject; import com.oracle.truffle.js.runtime.objects.JSLazyString; import com.oracle.truffle.js.runtime.objects.JSObject; @@ -444,6 +446,8 @@ private static ReadElementTypeCacheNode makeTypeCacheNode(Object target, ReadEle return new SymbolReadElementTypeCacheNode(next); } else if (target instanceof BigInt) { return new BigIntReadElementTypeCacheNode(next); + } else if (target instanceof Tuple) { + return new TupleReadElementTypeCacheNode(next); } else if (target instanceof TruffleObject) { assert JSRuntime.isForeignObject(target); return new ForeignObjectReadElementTypeCacheNode(target.getClass(), next); @@ -1514,6 +1518,30 @@ public boolean guard(Object target) { } } + private static class TupleReadElementTypeCacheNode extends ToPropertyKeyCachedReadElementTypeCacheNode { + + TupleReadElementTypeCacheNode(ReadElementTypeCacheNode next) { + super(next); + } + + @Override + protected Object executeWithTargetAndIndexUnchecked(Object target, Object index, Object receiver, Object defaultValue, ReadElementNode root) { + Tuple tuple = (Tuple) target; + return JSObject.getOrDefault(JSTuple.create(root.context, tuple), toPropertyKey(index), receiver, defaultValue, jsclassProfile, root); + } + + @Override + protected Object executeWithTargetAndIndexUnchecked(Object target, int index, Object receiver, Object defaultValue, ReadElementNode root) { + Tuple tuple = (Tuple) target; + return JSObject.getOrDefault(JSTuple.create(root.context, tuple), index, receiver, defaultValue, jsclassProfile, root); + } + + @Override + public boolean guard(Object target) { + return target instanceof Tuple; + } + } + static class ForeignObjectReadElementTypeCacheNode extends ReadElementTypeCacheNode { private final Class targetClass; diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Tuple.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Tuple.java index c23039b4803..a347f2dfdb7 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Tuple.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Tuple.java @@ -4,6 +4,7 @@ import com.oracle.truffle.api.CompilerDirectives.ValueType; import com.oracle.truffle.api.TruffleLanguage; import com.oracle.truffle.api.interop.InteropLibrary; +import com.oracle.truffle.api.interop.InvalidArrayIndexException; import com.oracle.truffle.api.interop.TruffleObject; import com.oracle.truffle.api.library.ExportLibrary; import com.oracle.truffle.api.library.ExportMessage; @@ -37,7 +38,7 @@ public static Tuple create(Object[] v) { public static Tuple create(byte[] v) { // TODO: create a tuple sub-class for byte arrays return new Tuple( - IntStream.range(0, v.length).mapToObj(i -> v[i]).toArray() + IntStream.range(0, v.length).mapToObj(i -> (int) v[i]).toArray() ); } @@ -123,4 +124,42 @@ boolean hasMetaObject() { Object getMetaObject() { return JSMetaType.JS_TUPLE; } + + @ExportMessage + public long getArraySize() { + return value.length; + } + + @SuppressWarnings("static-method") + @ExportMessage + public boolean hasArrayElements() { + return true; + } + + @ExportMessage + public Object readArrayElement(long index) throws InvalidArrayIndexException { + if (index < 0 || index >= value.length) { + throw InvalidArrayIndexException.create(index); + } + return value[(int) index]; + } + + @ExportMessage + public boolean isArrayElementReadable(long index) { + return index >= 0 && index < value.length; + } + + /** + * @return true if the index isn't out of range. + */ + public boolean hasElement(long index) { + return index >= 0 && index < value.length; + } + + /** + * @return value at the given index. + */ + public Object getElement(long index) { + return value[(int) index]; + } } diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSTuple.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSTuple.java index 2638c79ee8e..741c069549e 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSTuple.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSTuple.java @@ -1,14 +1,24 @@ package com.oracle.truffle.js.runtime.builtins; +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.object.DynamicObject; import com.oracle.truffle.api.object.Shape; import com.oracle.truffle.js.builtins.TupleFunctionBuiltins; import com.oracle.truffle.js.builtins.TuplePrototypeBuiltins; +import com.oracle.truffle.js.builtins.TuplePrototypeGetterBuiltins; +import com.oracle.truffle.js.runtime.Boundaries; import com.oracle.truffle.js.runtime.JSContext; import com.oracle.truffle.js.runtime.JSRealm; +import com.oracle.truffle.js.runtime.JSRuntime; +import com.oracle.truffle.js.runtime.Symbol; import com.oracle.truffle.js.runtime.Tuple; +import com.oracle.truffle.js.runtime.objects.JSAttributes; +import com.oracle.truffle.js.runtime.objects.JSDynamicObject; import com.oracle.truffle.js.runtime.objects.JSObjectUtil; +import static com.oracle.truffle.js.runtime.objects.JSObjectUtil.putDataProperty; + public final class JSTuple extends JSNonProxy implements JSConstructorFactory.Default.WithFunctions, PrototypeSupplier { public static final String TYPE_NAME = "tuple"; @@ -17,6 +27,8 @@ public final class JSTuple extends JSNonProxy implements JSConstructorFactory.De public static final JSTuple INSTANCE = new JSTuple(); + public static final String LENGTH = "length"; + private JSTuple() { } @@ -35,9 +47,17 @@ public static Tuple valueOf(DynamicObject obj) { public DynamicObject createPrototype(final JSRealm realm, DynamicObject ctor) { JSContext context = realm.getContext(); DynamicObject prototype = JSObjectUtil.createOrdinaryPrototypeObject(realm); + JSObjectUtil.putConstructorProperty(context, prototype, ctor); JSObjectUtil.putFunctionsFromContainer(realm, prototype, TuplePrototypeBuiltins.BUILTINS); JSObjectUtil.putToStringTag(prototype, CLASS_NAME); + + // Sets the Tuple.prototype.length accessor property. + JSObjectUtil.putBuiltinAccessorProperty(prototype, LENGTH, realm.lookupAccessor(TuplePrototypeGetterBuiltins.BUILTINS, LENGTH), JSAttributes.notConfigurableNotEnumerableNotWritable()); + + // The initial value of the @@iterator property is the same function object as the initial value of the Tuple.prototype.values property. + putDataProperty(context, prototype, Symbol.SYMBOL_ITERATOR, JSDynamicObject.getOrNull(prototype, "values"), JSAttributes.getDefaultNotEnumerable()); + return prototype; } @@ -69,4 +89,25 @@ public String getClassName(DynamicObject object) { public DynamicObject getIntrinsicDefaultProto(JSRealm realm) { return realm.getTuplePrototype(); } + + @TruffleBoundary + @Override + public final Object getOwnHelper(DynamicObject store, Object thisObj, Object key, Node encapsulatingNode) { + long idx = JSRuntime.propertyKeyToArrayIndex(key); + if (JSRuntime.isArrayIndex(idx)) { + return getOwnHelper(store, thisObj, idx, encapsulatingNode); + } + return super.getOwnHelper(store, thisObj, key, encapsulatingNode); + } + + @TruffleBoundary + @Override + public Object getOwnHelper(DynamicObject store, Object thisObj, long index, Node encapsulatingNode) { + assert isJSTuple(store); + Tuple tuple = ((JSTupleObject) store).getTupleValue(); + if (tuple.hasElement(index)) { + return tuple.getElement(index); + } + return super.getOwnHelper(store, thisObj, Boundaries.stringValueOf(index), encapsulatingNode); + } } From dd06e3ddd77e8d01b28a6cacaf501a6f868c8ffc Mon Sep 17 00:00:00 2001 From: Christian Aistleitner Date: Wed, 31 Mar 2021 14:28:14 +0200 Subject: [PATCH 05/31] Add tuple prototype builtins. --- .../js/record-tuple/tuple.js | 196 ++++++ .../js/builtins/ObjectPrototypeBuiltins.java | 11 + .../js/builtins/TuplePrototypeBuiltins.java | 613 +++++++++++++++++- .../nodes/tuples/IsConcatSpreadableNode.java | 73 +++ .../com/oracle/truffle/js/runtime/Tuple.java | 11 + .../truffle/js/runtime/builtins/JSTuple.java | 8 + 6 files changed, 908 insertions(+), 4 deletions(-) create mode 100644 graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/IsConcatSpreadableNode.java diff --git a/graal-js/src/com.oracle.truffle.js.test/js/record-tuple/tuple.js b/graal-js/src/com.oracle.truffle.js.test/js/record-tuple/tuple.js index c18c6519b08..c34ad7af651 100644 --- a/graal-js/src/com.oracle.truffle.js.test/js/record-tuple/tuple.js +++ b/graal-js/src/com.oracle.truffle.js.test/js/record-tuple/tuple.js @@ -206,3 +206,199 @@ b = [1, "2", BigInt(42)]; for (let i = -1; i < 5; i++) { assertSame(b[i], a[i]); } + +/* + * Test 16: + * Tuple.prototype + */ +assertTrue(Tuple.prototype !== undefined); +a = Object.getOwnPropertyDescriptor(Tuple, "prototype"); +assertSame(false, a.writable); +assertSame(false, a.enumerable); +assertSame(false, a.configurable); + +/* + * Test 17: + * Tuple.prototype.valueOf() + */ +a = #[42]; +b = Object(a); +assertSame("object", typeof b); +assertSame("tuple", typeof b.valueOf()); +assertSame(a, b.valueOf()); + +/* + * Test 18: + * Tuple.prototype[@@toStringTag] + */ +assertSame("[object Tuple]", Object.prototype.toString.call(#[])); +a = Object.getOwnPropertyDescriptor(BigInt.prototype, Symbol.toStringTag); +assertSame(false, a.writable); +assertSame(false, a.enumerable); +assertSame(true, a.configurable); + +/* + * Test 19: + * Tuple.prototype.popped() + */ +a = #[1, 2, 3]; +assertSame(#[1, 2], a.popped()); + +/* + * Test 20: + * Tuple.prototype.pushed(...args) + */ +a = #[1]; +assertSame(a, a.pushed()) +assertSame(#[1, 2, 3], a.pushed(2, 3)) +assertThrows(function() { + eval("a.pushed({})"); +}, TypeError); + +/* + * Test 21: + * Tuple.prototype.reversed() + */ +a = #[1, 2, 3]; +assertSame(#[3, 2, 1], a.reversed()) + +/* + * Test 22: + * Tuple.prototype.shifted() + */ +a = #[1, 2, 3]; +assertSame(#[2, 3], a.shifted()) + +/* + * Test 22: + * Tuple.prototype.slice(start, end) + */ +a = #[1, 2, 3]; +assertSame(#[2], a.slice(1, 2)) +assertSame(#[2, 3], a.slice(1, 10)) +assertSame(#[1, 2], a.slice(-3, -1)) +assertSame(#[], a.slice(1, 1)) + +/* + * Test 23: + * Tuple.prototype.sorted(comparefn) + */ +a = #[2, 1, 3]; +assertSame(#[1, 2, 3], a.sorted()); +assertSame(#[3, 2, 1], a.sorted((a, b) => b - a)); + +/* + * Test 24: + * Tuple.prototype.spliced(start, deleteCount, ...items) + */ +a = #[1, 2, 3]; +assertSame(a, a.spliced()); +assertSame(#[1], a.spliced(1)); +assertSame(#[1, 3], a.spliced(1, 1)); +assertSame(#[1, 1.5, 2, 3], a.spliced(1, 0, 1.5)); + +/* + * Test 25: + * Tuple.prototype.concat(...args) + */ +a = #['a', 'b', 'c']; +b = ['d', 'e', 'f']; +assertSame(#[...a, ...b], a.concat(b)); + +/* + * Test 26: + * Tuple.prototype.includes(...args) + */ +a = #['a', 'b', 'c']; +assertSame(true, a.includes('b')); +assertSame(false, a.includes('b', 2)); + +/* + * Test 28: + * Tuple.prototype.indexOf(searchElement [ , fromIndex ]) + */ +a = #['a', 'b', 'c']; +b = ['a', 'b', 'c']; +assertSame(b.indexOf('b'), a.indexOf('b')); +assertSame(b.indexOf('b', 2), a.indexOf('b', 2)); + +/* + * Test 29: + * Tuple.prototype.join(separator) + */ +a = #['a', 'b', 'c']; +assertSame('a,b,c', a.join()); +assertSame('abc', a.join('')); + +/* + * Test 30: + * Tuple.prototype.lastIndexOf(searchElement [ , fromIndex ]) + */ +a = #['a', 'b', 'c', 'b']; +assertSame(3, a.lastIndexOf('b')); +assertSame(1, a.lastIndexOf('b', -2)); + +/* + * Test 31: + * Tuple.prototype.every(callbackfn [ , thisArg ] ) + */ +a = #[1, 2, 3]; +assertSame(true, a.every(it => it > 0)); +assertSame(false, a.every(it => false)); + +/* + * Test 32: + * Tuple.prototype.find(predicate [ , thisArg ]) + */ +a = #[1, 2, 3]; +assertSame(2, a.find(it => it > 1)); +assertSame(undefined, a.find(it => it < 0)); + +/* + * Test 33: + * Tuple.prototype.findIndex(predicate [ , thisArg ]) + */ +a = #[1, 2, 3]; +assertSame(1, a.findIndex(it => it > 1)); +assertSame(-1, a.findIndex(it => it < 0)); + +/* + * Test 34: + * Tuple.prototype.forEach(callbackfn [ , thisArg ]) + */ +a = #[1, 2, 3]; +b = 0 +assertSame(undefined, a.forEach(() => b++)); +if (b !== 3) { + fail("Tuple.prototype.forEach(...) did not visit every entry") +} + +/* + * Test 35: + * Tuple.prototype.reduce(callbackfn [ , thisArg ]) + */ +a = #[1, 2, 3]; +assertSame('123', a.reduce((acc, it) => acc + it, '')); + +/* + * Test 36: + * Tuple.prototype.reduceRight(callbackfn [ , thisArg ]) + */ +a = #[1, 2, 3]; +assertSame('321', a.reduceRight((acc, it) => acc + it, '')); + +/* + * Test 37: + * Tuple.prototype.some(callbackfn [ , thisArg ]) + */ +a = #[1, 2, 3]; +assertSame(true, a.some(it => it > 2)); +assertSame(false, a.some(it => it < 0)); + +/* + * Test 38: + * Tuple.prototype.toLocaleString([ reserved1 [ , reserved2 ] ]) + */ +a = #[1.1]; +assertSame('1.1', a.toLocaleString('en')); +assertSame('1,1', a.toLocaleString('de')); diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/ObjectPrototypeBuiltins.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/ObjectPrototypeBuiltins.java index d6158f0c941..151458c7e42 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/ObjectPrototypeBuiltins.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/ObjectPrototypeBuiltins.java @@ -82,6 +82,7 @@ import com.oracle.truffle.js.runtime.JSRuntime; import com.oracle.truffle.js.runtime.SafeInteger; import com.oracle.truffle.js.runtime.Symbol; +import com.oracle.truffle.js.runtime.Tuple; import com.oracle.truffle.js.runtime.builtins.BuiltinEnum; import com.oracle.truffle.js.runtime.builtins.JSClass; import com.oracle.truffle.js.runtime.builtins.JSOrdinary; @@ -250,6 +251,11 @@ protected DynamicObject valueOfBigInt(BigInt thisObj) { return toJSObject(thisObj); } + @Specialization + protected DynamicObject valueOfTuple(Tuple thisObj) { + return toJSObject(thisObj); + } + @Specialization(guards = "!isTruffleObject(thisObj)") protected DynamicObject valueOfOther(Object thisObj) { return toJSObject(thisObj); @@ -365,6 +371,11 @@ protected String doBigInt(BigInt thisObj) { return JSObject.defaultToString(toJSObject(thisObj)); } + @Specialization + protected String doTuple(Tuple thisObj) { + return JSObject.defaultToString(toJSObject(thisObj)); + } + @Specialization(guards = {"!isTruffleObject(thisObj)"}) protected String doObject(Object thisObj) { assert thisObj != null; diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TuplePrototypeBuiltins.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TuplePrototypeBuiltins.java index 283dc865467..fc254be95e4 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TuplePrototypeBuiltins.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TuplePrototypeBuiltins.java @@ -1,19 +1,64 @@ package com.oracle.truffle.js.builtins; +import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.Fallback; import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.object.DynamicObject; +import com.oracle.truffle.api.profiles.BranchProfile; +import com.oracle.truffle.js.nodes.access.JSHasPropertyNode; +import com.oracle.truffle.js.nodes.access.ReadElementNode; +import com.oracle.truffle.js.nodes.array.JSArrayFirstElementIndexNode; +import com.oracle.truffle.js.nodes.array.JSArrayLastElementIndexNode; +import com.oracle.truffle.js.nodes.array.JSArrayNextElementIndexNode; +import com.oracle.truffle.js.nodes.array.JSGetLengthNode; +import com.oracle.truffle.js.nodes.cast.JSToIntegerAsLongNode; import com.oracle.truffle.js.nodes.cast.JSToObjectNode; import com.oracle.truffle.js.nodes.function.JSBuiltin; import com.oracle.truffle.js.nodes.function.JSBuiltinNode; +import com.oracle.truffle.js.nodes.tuples.IsConcatSpreadableNode; +import com.oracle.truffle.js.nodes.unary.IsCallableNode; import com.oracle.truffle.js.runtime.Errors; +import com.oracle.truffle.js.runtime.JSConfig; import com.oracle.truffle.js.runtime.JSContext; import com.oracle.truffle.js.runtime.JSRuntime; import com.oracle.truffle.js.runtime.Tuple; import com.oracle.truffle.js.runtime.builtins.BuiltinEnum; +import com.oracle.truffle.js.runtime.builtins.JSArray; +import com.oracle.truffle.js.runtime.builtins.JSFunction; +import com.oracle.truffle.js.runtime.builtins.JSProxy; import com.oracle.truffle.js.runtime.builtins.JSTuple; +import com.oracle.truffle.js.runtime.objects.JSDynamicObject; +import com.oracle.truffle.js.runtime.objects.Undefined; +import com.oracle.truffle.js.runtime.util.SimpleArrayList; + +import java.util.Arrays; +import java.util.Comparator; + +import static com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import static com.oracle.truffle.api.CompilerDirectives.transferToInterpreterAndInvalidate; +import static com.oracle.truffle.js.builtins.ArrayPrototypeBuiltinsFactory.JSArrayEveryNodeGen; +import static com.oracle.truffle.js.builtins.ArrayPrototypeBuiltinsFactory.JSArrayFindIndexNodeGen; +import static com.oracle.truffle.js.builtins.ArrayPrototypeBuiltinsFactory.JSArrayFindNodeGen; +import static com.oracle.truffle.js.builtins.ArrayPrototypeBuiltinsFactory.JSArrayForEachNodeGen; +import static com.oracle.truffle.js.builtins.ArrayPrototypeBuiltinsFactory.JSArrayIncludesNodeGen; +import static com.oracle.truffle.js.builtins.ArrayPrototypeBuiltinsFactory.JSArrayIndexOfNodeGen; +import static com.oracle.truffle.js.builtins.ArrayPrototypeBuiltinsFactory.JSArrayJoinNodeGen; +import static com.oracle.truffle.js.builtins.ArrayPrototypeBuiltinsFactory.JSArrayReduceNodeGen; +import static com.oracle.truffle.js.builtins.ArrayPrototypeBuiltinsFactory.JSArraySomeNodeGen; +import static com.oracle.truffle.js.builtins.ArrayPrototypeBuiltinsFactory.JSArrayToLocaleStringNodeGen; +import static com.oracle.truffle.js.builtins.TuplePrototypeBuiltinsFactory.JSTupleConcatNodeGen; +import static com.oracle.truffle.js.builtins.TuplePrototypeBuiltinsFactory.JSTupleIteratorNodeGen; +import static com.oracle.truffle.js.builtins.TuplePrototypeBuiltinsFactory.JSTuplePoppedNodeGen; +import static com.oracle.truffle.js.builtins.TuplePrototypeBuiltinsFactory.JSTuplePushedNodeGen; +import static com.oracle.truffle.js.builtins.TuplePrototypeBuiltinsFactory.JSTupleReversedNodeGen; +import static com.oracle.truffle.js.builtins.TuplePrototypeBuiltinsFactory.JSTupleShiftedNodeGen; +import static com.oracle.truffle.js.builtins.TuplePrototypeBuiltinsFactory.JSTupleSliceNodeGen; +import static com.oracle.truffle.js.builtins.TuplePrototypeBuiltinsFactory.JSTupleSortedNodeGen; +import static com.oracle.truffle.js.builtins.TuplePrototypeBuiltinsFactory.JSTupleSplicedNodeGen; +import static com.oracle.truffle.js.builtins.TuplePrototypeBuiltinsFactory.JSTupleToStringNodeGen; +import static com.oracle.truffle.js.builtins.TuplePrototypeBuiltinsFactory.JSTupleValueOfNodeGen; /** * Contains builtins for Tuple.prototype. @@ -27,8 +72,37 @@ protected TuplePrototypeBuiltins() { } public enum TuplePrototype implements BuiltinEnum { + valueOf(0), + popped(0), + pushed(1), + reversed(0), + shifted(0), + slice(2), + sorted(1), + spliced(3), + concat(1), + includes(1), + indexOf(1), + join(1), + lastIndexOf(1), + // TODO: entries(1), + every(1), + // TODO: filter(1), + find(1), + findIndex(1), + // TODO: flat(1), + // TODO: flatMap(1), + forEach(1), + // TODO: keys(1), + // TODO: map(1), + reduce(1), + reduceRight(1), + some(1), + // TODO: unshifted(1), + toLocaleString(0), toString(0), values(0); + // TODO: with(1); private final int length; @@ -45,10 +119,52 @@ public int getLength() { @Override protected Object createNode(JSContext context, JSBuiltin builtin, boolean construct, boolean newTarget, TuplePrototype builtinEnum) { switch (builtinEnum) { + case valueOf: + return JSTupleValueOfNodeGen.create(context, builtin, args().withThis().createArgumentNodes(context)); + case popped: + return JSTuplePoppedNodeGen.create(context, builtin, args().withThis().createArgumentNodes(context)); + case pushed: + return JSTuplePushedNodeGen.create(context, builtin, args().withThis().varArgs().createArgumentNodes(context)); + case reversed: + return JSTupleReversedNodeGen.create(context, builtin, args().withThis().createArgumentNodes(context)); + case shifted: + return JSTupleShiftedNodeGen.create(context, builtin, args().withThis().createArgumentNodes(context)); + case slice: + return JSTupleSliceNodeGen.create(context, builtin, args().withThis().fixedArgs(2).createArgumentNodes(context)); + case sorted: + return JSTupleSortedNodeGen.create(context, builtin, args().withThis().fixedArgs(1).createArgumentNodes(context)); + case spliced: + return JSTupleSplicedNodeGen.create(context, builtin, args().withThis().varArgs().createArgumentNodes(context)); + case concat: + return JSTupleConcatNodeGen.create(context, builtin, args().withThis().varArgs().createArgumentNodes(context)); + case includes: + return JSArrayIncludesNodeGen.create(context, builtin, false, args().withThis().fixedArgs(2).createArgumentNodes(context)); + case indexOf: + return JSArrayIndexOfNodeGen.create(context, builtin, false, true, args().withThis().varArgs().createArgumentNodes(context)); + case join: + return JSArrayJoinNodeGen.create(context, builtin, false, args().withThis().fixedArgs(1).createArgumentNodes(context)); + case lastIndexOf: + return JSArrayIndexOfNodeGen.create(context, builtin, false, false, args().withThis().varArgs().createArgumentNodes(context)); + case every: + return JSArrayEveryNodeGen.create(context, builtin, false, args().withThis().fixedArgs(2).createArgumentNodes(context)); + case find: + return JSArrayFindNodeGen.create(context, builtin, false, args().withThis().fixedArgs(2).createArgumentNodes(context)); + case findIndex: + return JSArrayFindIndexNodeGen.create(context, builtin, false, args().withThis().fixedArgs(2).createArgumentNodes(context)); + case forEach: + return JSArrayForEachNodeGen.create(context, builtin, args().withThis().fixedArgs(2).createArgumentNodes(context)); + case reduce: + return JSArrayReduceNodeGen.create(context, builtin, false, true, args().withThis().fixedArgs(1).varArgs().createArgumentNodes(context)); + case reduceRight: + return JSArrayReduceNodeGen.create(context, builtin, false, false, args().withThis().fixedArgs(1).varArgs().createArgumentNodes(context)); + case some: + return JSArraySomeNodeGen.create(context, builtin, false, args().withThis().fixedArgs(2).createArgumentNodes(context)); + case toLocaleString: + return JSArrayToLocaleStringNodeGen.create(context, builtin, false, args().withThis().createArgumentNodes(context)); case toString: - return TuplePrototypeBuiltinsFactory.JSTupleToStringNodeGen.create(context, builtin, args().withThis().fixedArgs(1).createArgumentNodes(context)); + return JSTupleToStringNodeGen.create(context, builtin, args().withThis().fixedArgs(1).createArgumentNodes(context)); case values: - return TuplePrototypeBuiltinsFactory.TupleIteratorNodeGen.create(context, builtin, JSRuntime.ITERATION_KIND_VALUE, args().withThis().createArgumentNodes(context)); + return JSTupleIteratorNodeGen.create(context, builtin, JSRuntime.ITERATION_KIND_VALUE, args().withThis().createArgumentNodes(context)); } return null; } @@ -75,10 +191,10 @@ protected void toStringNoTuple(Object thisObj) { } } - public abstract static class TupleIteratorNode extends JSBuiltinNode { + public abstract static class JSTupleIteratorNode extends JSBuiltinNode { @Child private ArrayPrototypeBuiltins.CreateArrayIteratorNode createArrayIteratorNode; - public TupleIteratorNode(JSContext context, JSBuiltin builtin, int iterationKind) { + public JSTupleIteratorNode(JSContext context, JSBuiltin builtin, int iterationKind) { super(context, builtin); this.createArrayIteratorNode = ArrayPrototypeBuiltins.CreateArrayIteratorNode.create(context, iterationKind); } @@ -97,4 +213,493 @@ protected DynamicObject doNotJSObject( return createArrayIteratorNode.execute(frame, toObjectNode.execute(thisObj)); } } + + public abstract static class JSTupleValueOfNode extends JSBuiltinNode { + + public JSTupleValueOfNode(JSContext context, JSBuiltin builtin) { + super(context, builtin); + } + + @Specialization + protected Tuple doTuple(Tuple thisObj) { + return thisObj; + } + + @Specialization(guards = "isJSTuple(thisObj)") + protected Tuple doJSTuple(DynamicObject thisObj) { + return JSTuple.valueOf(thisObj); + } + + @Fallback + protected void fallback(@SuppressWarnings("unused") Object thisObj) { + throw Errors.createTypeError("Tuple.prototype.valueOf requires that 'this' be a Tuple"); + } + } + + public abstract static class JSTuplePoppedNode extends JSBuiltinNode { + + public JSTuplePoppedNode(JSContext context, JSBuiltin builtin) { + super(context, builtin); + } + + @Specialization + protected Tuple doTuple(Tuple thisObj) { + return getPoppedTuple(thisObj); + } + + @Specialization(guards = "isJSTuple(thisObj)") + protected Tuple doJSTuple(DynamicObject thisObj) { + Tuple tuple = JSTuple.valueOf(thisObj); + return getPoppedTuple(tuple); + } + + private Tuple getPoppedTuple(Tuple tuple) { + if (tuple.getArraySize() <= 1) { + return Tuple.EMPTY_TUPLE; + } + Object[] values = tuple.getElements(); + return Tuple.create(Arrays.copyOf(values, values.length-1)); + } + + @Fallback + protected void fallback(@SuppressWarnings("unused") Object thisObj) { + throw Errors.createTypeError("Tuple.prototype.popped requires that 'this' be a Tuple"); + } + } + + public abstract static class JSTuplePushedNode extends BasicTupleOperation { + public JSTuplePushedNode(JSContext context, JSBuiltin builtin) { + super(context, builtin); + } + + @Specialization + protected Tuple doTuple(Tuple thisObj, Object[] args) { + return getPushedTuple(thisObj, args); + } + + @Specialization(guards = "isJSTuple(thisObj)") + protected Tuple doJSTuple(DynamicObject thisObj, Object[] args) { + Tuple tuple = JSTuple.valueOf(thisObj); + return getPushedTuple(tuple, args); + } + + private Tuple getPushedTuple(Tuple tuple, Object[] args) { + long targetSize = tuple.getArraySize() + args.length; + checkSize(targetSize); + + Object[] values = Arrays.copyOf(tuple.getElements(), (int) (targetSize)); + for (int i = 0; i < args.length; i++) { + Object value = args[i]; + if (!JSRuntime.isJSPrimitive(value)) { + throw Errors.createTypeError("Tuples cannot contain non-primitive values"); + } + values[tuple.getArraySizeInt() + i] = value; + } + return Tuple.create(values); + } + } + + public abstract static class JSTupleReversedNode extends JSBuiltinNode { + + public JSTupleReversedNode(JSContext context, JSBuiltin builtin) { + super(context, builtin); + } + + @Specialization + protected Tuple doTuple(Tuple thisObj) { + return getReversedTuple(thisObj); + } + + @Specialization(guards = "isJSTuple(thisObj)") + protected Tuple doJSTuple(DynamicObject thisObj) { + Tuple tuple = JSTuple.valueOf(thisObj); + return getReversedTuple(tuple); + } + + private Tuple getReversedTuple(Tuple tuple) { + Object[] values = new Object[tuple.getArraySizeInt()]; + for (int i = 0; i < values.length; i++) { + values[i] = tuple.getElement(values.length - i - 1); + } + return Tuple.create(values); + } + + @Fallback + protected void fallback(@SuppressWarnings("unused") Object thisObj) { + throw Errors.createTypeError("Tuple.prototype.reversed requires that 'this' be a Tuple"); + } + } + + public abstract static class JSTupleShiftedNode extends JSBuiltinNode { + + public JSTupleShiftedNode(JSContext context, JSBuiltin builtin) { + super(context, builtin); + } + + @Specialization + protected Tuple doTuple(Tuple thisObj) { + return getShiftedTuple(thisObj); + } + + @Specialization(guards = "isJSTuple(thisObj)") + protected Tuple doJSTuple(DynamicObject thisObj) { + Tuple tuple = JSTuple.valueOf(thisObj); + return getShiftedTuple(tuple); + } + + private Tuple getShiftedTuple(Tuple tuple) { + Object[] values = new Object[tuple.getArraySizeInt()]; + if(tuple.getArraySize() == 0) { + return tuple; + } + return Tuple.create(Arrays.copyOfRange(tuple.getElements(), 1, tuple.getArraySizeInt())); + } + + @Fallback + protected void fallback(@SuppressWarnings("unused") Object thisObj) { + throw Errors.createTypeError("Tuple.prototype.shifted requires that 'this' be a Tuple"); + } + } + + public abstract static class BasicTupleOperation extends JSBuiltinNode { + + public BasicTupleOperation(JSContext context, JSBuiltin builtin) { + super(context, builtin); + } + + protected Tuple toTupleValue(Object obj) { + if (obj instanceof Tuple) { + return (Tuple) obj; + } + if (JSTuple.isJSTuple(obj)) { + return JSTuple.valueOf((JSDynamicObject) obj); + } + throw Errors.createTypeError("'this' must be a Tuple"); + } + + protected void checkSize(long size) { + if (size > JSRuntime.MAX_SAFE_INTEGER) { + throw Errors.createTypeError("length too big"); + } + } + } + + public abstract static class JSTupleSliceNode extends BasicTupleOperation { + + public JSTupleSliceNode(JSContext context, JSBuiltin builtin) { + super(context, builtin); + } + + @Specialization + protected Tuple slice(Object thisObj, Object begin, Object end, + @Cached("create()") JSToIntegerAsLongNode toIntegerAsLong) { + Tuple tuple = toTupleValue(thisObj); + long size = tuple.getArraySize(); + + long startPos = toIntegerAsLong.executeLong(begin); + long endPos = end == Undefined.instance ? size : toIntegerAsLong.executeLong(end); + + startPos = startPos < 0 ? Math.max(size + startPos, 0) : Math.min(startPos, size); + endPos = endPos < 0 ? Math.max(size + endPos, 0) : Math.min(endPos, size); + + if (startPos >= endPos) { + return Tuple.EMPTY_TUPLE; + } + return Tuple.create(Arrays.copyOfRange(tuple.getElements(), (int) startPos, (int) endPos)); + } + } + + public abstract static class JSTupleSortedNode extends BasicTupleOperation { + + @Child private IsCallableNode isCallableNode; + + public JSTupleSortedNode(JSContext context, JSBuiltin builtin) { + super(context, builtin); + } + + @Specialization + protected Object sorted(Object thisObj, Object comparefn) { + checkCompareFunction(comparefn); + Tuple tuple = toTupleValue(thisObj); + long size = tuple.getArraySize(); + + if (size < 2) { + // nothing to do + return thisObj; + } + + Object[] values = tuple.getElements(); + sortIntl(getComparator(comparefn), values); + + return Tuple.create(values); + } + + private void checkCompareFunction(Object compare) { + if (!(compare == Undefined.instance || isCallable(compare))) { + throw Errors.createTypeError("The comparison function must be either a function or undefined"); + } + } + + protected final boolean isCallable(Object callback) { + if (isCallableNode == null) { + transferToInterpreterAndInvalidate(); + isCallableNode = insert(IsCallableNode.create()); + } + return isCallableNode.executeBoolean(callback); + } + + private Comparator getComparator(Object comparefn) { + if (comparefn == Undefined.instance) { + return JSArray.DEFAULT_JSARRAY_COMPARATOR; + } else { + assert isCallable(comparefn); + return new SortComparator(comparefn); + } + } + + private class SortComparator implements Comparator { + private final Object compFnObj; + private final boolean isFunction; + + SortComparator(Object compFnObj) { + this.compFnObj = compFnObj; + this.isFunction = JSFunction.isJSFunction(compFnObj); + } + + @Override + public int compare(Object arg0, Object arg1) { + if (arg0 == Undefined.instance) { + if (arg1 == Undefined.instance) { + return 0; + } + return 1; + } else if (arg1 == Undefined.instance) { + return -1; + } + Object retObj; + if (isFunction) { + retObj = JSFunction.call((DynamicObject) compFnObj, Undefined.instance, new Object[]{arg0, arg1}); + } else { + retObj = JSRuntime.call(compFnObj, Undefined.instance, new Object[]{arg0, arg1}); + } + int res = convertResult(retObj); + return res; + } + + private int convertResult(Object retObj) { + if (retObj instanceof Integer) { + return (int) retObj; + } else { + double d = JSRuntime.toDouble(retObj); + if (d < 0) { + return -1; + } else if (d > 0) { + return 1; + } else { + // +/-0 or NaN + return 0; + } + } + } + } + + @TruffleBoundary + private static void sortIntl(Comparator comparator, Object[] array) { + try { + Arrays.sort(array, comparator); + } catch (IllegalArgumentException e) { + // Collections.sort throws IllegalArgumentException when + // Comparison method violates its general contract + + // See ECMA spec 15.4.4.11 Array.prototype.sort (comparefn). + // If "comparefn" is not undefined and is not a consistent + // comparison function for the elements of this array, the + // behaviour of sort is implementation-defined. + } + } + } + + public abstract static class JSTupleSplicedNode extends BasicTupleOperation { + + public JSTupleSplicedNode(JSContext context, JSBuiltin builtin) { + super(context, builtin); + } + + @Specialization + protected Tuple spliced(Object thisObj, Object[] args, + @Cached("create()") JSToIntegerAsLongNode toIntegerAsLong) { + Object start = JSRuntime.getArgOrUndefined(args, 0); + Object deleteCount = JSRuntime.getArgOrUndefined(args, 1); + + Tuple tuple = toTupleValue(thisObj); + long size = tuple.getArraySize(); + + long startPos = toIntegerAsLong.executeLong(start); + startPos = startPos < 0 ? Math.max(size + startPos, 0) : Math.min(startPos, size); + + long insertCount, delCount; + if (args.length == 0) { + insertCount = 0; + delCount = 0; + } else if (args.length == 1) { + insertCount = 0; + delCount = size - startPos; + } else { + insertCount = args.length - 2; + delCount = toIntegerAsLong.executeLong(deleteCount); + delCount = Math.min(Math.max(delCount, 0), size - startPos); + } + + checkSize(size + insertCount - delCount); + Object[] values = new Object[(int) (size + insertCount - delCount)]; + + int k = 0; + while (k < startPos) { + values[k] = tuple.getElement(k); + k++; + } + for (int i = 2; i < args.length; i++) { + if (!JSRuntime.isJSPrimitive(args[i])) { + throw Errors.createTypeError("Tuples cannot contain non-primitive values"); + } + values[k] = args[i]; + k++; + } + for (int i = (int) (startPos + delCount); i < size; i++) { + values[k] = tuple.getElement(i); + k++; + } + + return Tuple.create(values); + } + } + + public abstract static class JSTupleConcatNode extends BasicTupleOperation { + + private final BranchProfile growProfile = BranchProfile.create(); + + @Child private IsConcatSpreadableNode isConcatSpreadableNode; + @Child private JSHasPropertyNode hasPropertyNode; + @Child private ReadElementNode readElementNode; + @Child private JSGetLengthNode getLengthNode; + @Child private JSArrayFirstElementIndexNode firstElementIndexNode; + @Child private JSArrayLastElementIndexNode lastElementIndexNode; + @Child private JSArrayNextElementIndexNode nextElementIndexNode; + + public JSTupleConcatNode(JSContext context, JSBuiltin builtin) { + super(context, builtin); + } + + @Specialization + protected Tuple concat(Object thisObj, Object[] args) { + Tuple tuple = toTupleValue(thisObj); + SimpleArrayList list = new SimpleArrayList<>(1 + JSConfig.SpreadArgumentPlaceholderCount); + concatElement(tuple, list); + for (Object arg : args) { + concatElement(arg, list); + } + return Tuple.create(list.toArray()); + } + + private void concatElement(Object el, SimpleArrayList list) { + if (isConcatSpreadable(el)) { + long len = getLength(el); + if (len > 0) { + concatSpreadable(el, len, list); + } + } else { + if (!JSRuntime.isJSPrimitive(el)) { + throw Errors.createTypeError("Tuples cannot contain non-primitive values"); + } + list.add(el, growProfile); + } + } + + private boolean isConcatSpreadable(Object object) { + if (isConcatSpreadableNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + isConcatSpreadableNode = insert(IsConcatSpreadableNode.create(getContext())); + } + return isConcatSpreadableNode.execute(object); + } + + private void concatSpreadable(Object el, long len, SimpleArrayList list) { + if (JSRuntime.isTuple(el)) { + Tuple tuple = (Tuple) el; + for (long k = 0; k < tuple.getArraySize(); k++) { + list.add(tuple.getElement(k), growProfile); + } + } else if (JSProxy.isJSProxy(el) || !JSDynamicObject.isJSDynamicObject(el)) { + // strictly to the standard implementation; traps could expose optimizations! + for (long k = 0; k < len; k++) { + if (hasProperty(el, k)) { + list.add(get(el, k), growProfile); + } + } + } else if (len == 1) { + // fastpath for 1-element entries + if (hasProperty(el, 0)) { + list.add(get(el, 0), growProfile); + } + } else { + long k = firstElementIndex((DynamicObject) el, len); + long lastI = lastElementIndex((DynamicObject) el, len); + for (; k <= lastI; k = nextElementIndex(el, k, len)) { + list.add(get(el, k), growProfile); + } + } + } + + private boolean hasProperty(Object obj, long idx) { + if (hasPropertyNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + hasPropertyNode = insert(JSHasPropertyNode.create()); + } + return hasPropertyNode.executeBoolean(obj, idx); + } + + private Object get(Object target, long index) { + if (readElementNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + readElementNode = insert(ReadElementNode.create(getContext())); + } + if (JSRuntime.longIsRepresentableAsInt(index)) { + return readElementNode.executeWithTargetAndIndex(target, (int) index); + } else { + return readElementNode.executeWithTargetAndIndex(target, (double) index); + } + } + + private long firstElementIndex(DynamicObject target, long length) { + if (firstElementIndexNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + firstElementIndexNode = insert(JSArrayFirstElementIndexNode.create(getContext())); + } + return firstElementIndexNode.executeLong(target, length); + } + + private long lastElementIndex(DynamicObject target, long length) { + if (lastElementIndexNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + lastElementIndexNode = insert(JSArrayLastElementIndexNode.create(getContext())); + } + return lastElementIndexNode.executeLong(target, length); + } + + private long nextElementIndex(Object target, long currentIndex, long length) { + if (nextElementIndexNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + nextElementIndexNode = insert(JSArrayNextElementIndexNode.create(getContext())); + } + return nextElementIndexNode.executeLong(target, currentIndex, length); + } + + private long getLength(Object obj) { + if (getLengthNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + getLengthNode = insert(JSGetLengthNode.create(getContext())); + } + return getLengthNode.executeLong(obj); + } + } } diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/IsConcatSpreadableNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/IsConcatSpreadableNode.java new file mode 100644 index 00000000000..a68a23fec2b --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/IsConcatSpreadableNode.java @@ -0,0 +1,73 @@ +package com.oracle.truffle.js.nodes.tuples; + +import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.object.DynamicObject; +import com.oracle.truffle.js.nodes.JavaScriptBaseNode; +import com.oracle.truffle.js.nodes.access.PropertyGetNode; +import com.oracle.truffle.js.nodes.cast.JSToBooleanNode; +import com.oracle.truffle.js.nodes.unary.JSIsArrayNode; +import com.oracle.truffle.js.runtime.JSContext; +import com.oracle.truffle.js.runtime.Symbol; +import com.oracle.truffle.js.runtime.objects.JSDynamicObject; +import com.oracle.truffle.js.runtime.objects.Null; +import com.oracle.truffle.js.runtime.objects.Undefined; + +public abstract class IsConcatSpreadableNode extends JavaScriptBaseNode { + + @Child private PropertyGetNode getSpreadableNode; + @Child private JSIsArrayNode isArrayNode; + @Child private JSToBooleanNode toBooleanNode; + + protected final JSContext context; + + protected IsConcatSpreadableNode(JSContext context) { + super(); + this.context = context; + } + + public abstract boolean execute(Object operand); + + @Specialization + protected boolean doObject(Object o) { + if (o == Undefined.instance || o == Null.instance) { + return false; + } + if (JSDynamicObject.isJSDynamicObject(o)) { + DynamicObject obj = (DynamicObject) o; + Object spreadable = getSpreadableProperty(obj); + if (spreadable != Undefined.instance) { + return toBoolean(spreadable); + } + } + return isArray(o); + } + + private boolean isArray(Object object) { + if (isArrayNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + isArrayNode = insert(JSIsArrayNode.createIsArrayLike()); + } + return isArrayNode.execute(object); + } + + private Object getSpreadableProperty(Object obj) { + if (getSpreadableNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + getSpreadableNode = insert(PropertyGetNode.create(Symbol.SYMBOL_IS_CONCAT_SPREADABLE, false, context)); + } + return getSpreadableNode.getValue(obj); + } + + protected boolean toBoolean(Object target) { + if (toBooleanNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + toBooleanNode = insert(JSToBooleanNode.create()); + } + return toBooleanNode.executeBoolean(target); + } + + public static IsConcatSpreadableNode create(JSContext context) { + return IsConcatSpreadableNodeGen.create(context); + } +} diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Tuple.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Tuple.java index a347f2dfdb7..78ee1f956b2 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Tuple.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Tuple.java @@ -162,4 +162,15 @@ public boolean hasElement(long index) { public Object getElement(long index) { return value[(int) index]; } + + /** + * @return all values in a List. + */ + public Object[] getElements() { + return value.clone(); + } + + public int getArraySizeInt() { + return value.length; + } } diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSTuple.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSTuple.java index 741c069549e..b14009456a4 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSTuple.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSTuple.java @@ -110,4 +110,12 @@ public Object getOwnHelper(DynamicObject store, Object thisObj, long index, Node } return super.getOwnHelper(store, thisObj, Boundaries.stringValueOf(index), encapsulatingNode); } + + @TruffleBoundary + @Override + public boolean hasProperty(DynamicObject thisObj, long index) { + assert isJSTuple(thisObj); + Tuple tuple = ((JSTupleObject) thisObj).getTupleValue(); + return tuple.hasElement(index); + } } From 83784a852446325226dccfe7a22d31dcb5574f3e Mon Sep 17 00:00:00 2001 From: Christian Aistleitner Date: Sun, 4 Apr 2021 16:07:48 +0200 Subject: [PATCH 06/31] Add parsing of records. --- .../src/com/oracle/js/parser/Parser.java | 116 ++++++- .../com/oracle/js/parser/ir/RecordNode.java | 123 +++++++ .../js/parser/ir/RecordPropertyNode.java | 159 +++++++++ .../com/oracle/js/parser/ir/UnaryNode.java | 3 +- .../js/parser/ir/visitor/NodeVisitor.java | 42 +++ .../ir/visitor/TranslatorNodeVisitor.java | 22 ++ .../truffle/js/parser/GraalJSTranslator.java | 31 ++ .../parser/internal/ir/debug/JSONWriter.java | 2 + .../oracle/truffle/js/nodes/NodeFactory.java | 20 +- .../js/nodes/instrumentation/JSTags.java | 1 + .../js/nodes/record/RecordLiteralNode.java | 307 ++++++++++++++++++ 11 files changed, 815 insertions(+), 11 deletions(-) create mode 100644 graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/ir/RecordNode.java create mode 100644 graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/ir/RecordPropertyNode.java create mode 100644 graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/record/RecordLiteralNode.java diff --git a/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/Parser.java b/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/Parser.java index 4eca1d6341b..9f7204a8ba1 100644 --- a/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/Parser.java +++ b/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/Parser.java @@ -88,6 +88,7 @@ import static com.oracle.js.parser.TokenType.SPREAD_ARGUMENT; import static com.oracle.js.parser.TokenType.SPREAD_ARRAY; import static com.oracle.js.parser.TokenType.SPREAD_OBJECT; +import static com.oracle.js.parser.TokenType.SPREAD_RECORD; import static com.oracle.js.parser.TokenType.SPREAD_TUPLE; import static com.oracle.js.parser.TokenType.STATIC; import static com.oracle.js.parser.TokenType.STRING; @@ -157,6 +158,8 @@ import com.oracle.js.parser.ir.ParameterNode; import com.oracle.js.parser.ir.PropertyKey; import com.oracle.js.parser.ir.PropertyNode; +import com.oracle.js.parser.ir.RecordNode; +import com.oracle.js.parser.ir.RecordPropertyNode; import com.oracle.js.parser.ir.ReturnNode; import com.oracle.js.parser.ir.RuntimeNode; import com.oracle.js.parser.ir.Scope; @@ -4301,18 +4304,110 @@ private static final class PropertyFunction { * * RecordPropertyDefinitionList[Yield, Await] : * RecordPropertyDefinition[?Yield, ?Await] - * RecordPropertyDefinitionList[?Yield, ?Await],RecordPropertyDefinition[?Yield, ?Await] + * RecordPropertyDefinitionList[?Yield, ?Await] , RecordPropertyDefinition[?Yield, ?Await] + * + * + * @return Expression node. + */ + private RecordNode recordLiteral(boolean yield, boolean await) { + // Capture HASH_LBRACE token. + final long recordToken = token; + // HASH_LBRACE tested in caller. + next(); + + // Accumulate record property elements. + final ArrayList elements = new ArrayList<>(); + boolean commaSeen = true; + loop: while (true) { + switch (type) { + case RBRACE: + next(); + break loop; + + case COMMARIGHT: + if (commaSeen) { + throw error(AbstractParser.message("expected.property.id", type.getNameOrType())); + } + next(); + commaSeen = true; + break; + + default: + if (!commaSeen) { + throw error(AbstractParser.message("expected.comma", type.getNameOrType())); + } + commaSeen = false; + // Get and add the next property. + final RecordPropertyNode property = recordPropertyDefinition(yield, await); + elements.add(property); + } + } + + return new RecordNode(recordToken, finish, optimizeList(elements)); + } + + /** + * Parse an record literal property definition. + * Compared to its object literal counterpart it does not contain + * CoverInitializedName and MethodDefinition productions. * + *
      * RecordPropertyDefinition[Yield, Await] :
      *      IdentifierReference[?Yield, ?Await]
-     *      PropertyName[?Yield, ?Await]:AssignmentExpression[+In, ?Yield, ?Await]
+     *      PropertyName[?Yield, ?Await] : AssignmentExpression[+In, ?Yield, ?Await]
      *      ... AssignmentExpression[+In, ?Yield, ?Await]
      * 
* - * @return Expression node. + * @return Record property node. */ - private Expression recordLiteral(boolean yield, boolean await) { // TODO: return type - return objectLiteral(yield, await); // TODO: implement + private RecordPropertyNode recordPropertyDefinition(boolean yield, boolean await) { + // Capture firstToken. + final long propertyToken = token; + + final Expression propertyKey; + + if (type == ELLIPSIS) { + // ... AssignmentExpression[+In, ?Yield, ?Await] + long spreadToken = Token.recast(propertyToken, SPREAD_RECORD); + next(); + propertyKey = new UnaryNode(spreadToken, assignmentExpression(true, yield, await)); + return new RecordPropertyNode(propertyToken, finish, propertyKey, null, false); + } + + final boolean computed = type == LBRACKET; + final boolean isIdentifier = isIdentifier(); + if (isIdentifier) { + // IdentifierReference[?Yield, ?Await] + propertyKey = getIdent().setIsPropertyName(); + if (type == COMMARIGHT || type == RBRACE) { + verifyIdent((IdentNode) propertyKey, yield, await); + return new RecordPropertyNode(propertyToken, finish, propertyKey, propertyKey, false); + } + } else { + // PropertyName[?Yield, ?Await] + propertyKey = propertyName(yield, await); + } + + // : AssignmentExpression[+In, ?Yield, ?Await] + Expression propertyValue; + expect(COLON); + + if (isIdentifier && PROTO_NAME.equals(((PropertyKey) propertyKey).getPropertyName())) { + throw error("'__proto__' is not allowed in Record expressions", propertyKey.getToken()); + } + + pushDefaultName(propertyKey); + try { + propertyValue = assignmentExpression(true, yield, await); + } finally { + popDefaultName(); + } + + if (!computed && isAnonymousFunctionDefinition(propertyValue) && propertyKey instanceof PropertyKey) { + propertyValue = setAnonymousFunctionName(propertyValue, ((PropertyKey) propertyKey).getPropertyName()); + } + + return new RecordPropertyNode(propertyToken, finish, propertyKey, propertyValue, computed); } /** @@ -5916,10 +6011,13 @@ private Expression assignmentExpression(boolean in, boolean yield, boolean await /** * AssignmentExpression. * - * AssignmentExpression[In, Yield] : ConditionalExpression[?In, ?Yield] [+Yield] - * YieldExpression[?In] ArrowFunction[?In, ?Yield] AsyncArrowFunction - * LeftHandSideExpression[?Yield] = AssignmentExpression[?In, ?Yield] - * LeftHandSideExpression[?Yield] AssignmentOperator AssignmentExpression[?In, ?Yield] + * AssignmentExpression[In, Yield] : + * ConditionalExpression[?In, ?Yield, ?Await] + * [+Yield] YieldExpression[?In, ?Await] + * ArrowFunction[?In, ?Yield, ?Await] + * AsyncArrowFunction[?In, ?Yield, ?Await] + * LeftHandSideExpression[?Yield, ?Await] = AssignmentExpression[?In, ?Yield, ?Await] + * LeftHandSideExpression[?Yield, ?Await] AssignmentOperator AssignmentExpression[?In, ?Yield, ?Await] */ private Expression assignmentExpression(boolean in, boolean yield, boolean await, boolean inPatternPosition) { if (type == YIELD && yield) { diff --git a/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/ir/RecordNode.java b/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/ir/RecordNode.java new file mode 100644 index 00000000000..900513398c5 --- /dev/null +++ b/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/ir/RecordNode.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.oracle.js.parser.ir; + +import com.oracle.js.parser.ir.visitor.NodeVisitor; +import com.oracle.js.parser.ir.visitor.TranslatorNodeVisitor; + +import java.util.Collections; +import java.util.List; + +/** + * IR representation of an record literal. + */ +public final class RecordNode extends Expression { + + /** + * Literal elements. + */ + private final List elements; + + /** + * Constructor + * + * @param token token + * @param finish finish + * @param elements the elements used to initialize this ObjectNode + */ + public RecordNode(final long token, final int finish, final List elements) { + super(token, finish); + this.elements = elements; + } + + private RecordNode(final RecordNode objectNode, final List elements) { + super(objectNode); + this.elements = elements; + } + + @Override + public Node accept(final NodeVisitor visitor) { + if (visitor.enterRecordNode(this)) { + return visitor.leaveRecordNode(setElements(Node.accept(visitor, elements))); + } + return this; + } + + @Override + public R accept(TranslatorNodeVisitor visitor) { + return visitor.enterRecordNode(this); + } + + @Override + public void toString(final StringBuilder sb, final boolean printType) { + sb.append("#{"); + if (!elements.isEmpty()) { + sb.append(' '); + boolean first = true; + for (final Node element : elements) { + if (!first) { + sb.append(", "); + } + first = false; + element.toString(sb, printType); + } + sb.append(' '); + } + sb.append('}'); + } + + /** + * Get the elements of this literal node + * + * @return a list of elements + */ + public List getElements() { + return Collections.unmodifiableList(elements); + } + + private RecordNode setElements(final List elements) { + if (this.elements == elements) { + return this; + } + return new RecordNode(this, elements); + } +} diff --git a/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/ir/RecordPropertyNode.java b/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/ir/RecordPropertyNode.java new file mode 100644 index 00000000000..a847f497d50 --- /dev/null +++ b/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/ir/RecordPropertyNode.java @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.oracle.js.parser.ir; + +import com.oracle.js.parser.TokenType; +import com.oracle.js.parser.ir.visitor.NodeVisitor; +import com.oracle.js.parser.ir.visitor.TranslatorNodeVisitor; + +/** + * IR representation of an record literal property. + */ +public final class RecordPropertyNode extends Node { + + private final Expression key; + private final Expression value; + private final boolean computed; + + /** + * Constructor + * + * @param token token + * @param finish finish + * @param key the key of this property + * @param value the value of this property + * @param computed true if its key is computed + */ + public RecordPropertyNode(long token, int finish, Expression key, Expression value, boolean computed) { + super(token, finish); + this.key = key; + this.value = value; + this.computed = computed; + } + + private RecordPropertyNode(RecordPropertyNode propertyNode, Expression key, Expression value, boolean computed) { + super(propertyNode); + this.key = key; + this.value = value; + this.computed = computed; + } + + /** + * Get the name of the property key + * + * @return key name + */ + public String getKeyName() { + return key instanceof PropertyKey ? ((PropertyKey) key).getPropertyName() : null; + } + + @Override + public Node accept(final NodeVisitor visitor) { + if (visitor.enterPropertyNode(this)) { + return visitor.leavePropertyNode(this + .setKey((Expression) key.accept(visitor)) + .setValue(value == null ? null : (Expression) value.accept(visitor)) + ); + } + return this; + } + + @Override + public R accept(TranslatorNodeVisitor visitor) { + return visitor.enterPropertyNode(this); + } + + @Override + public void toString(final StringBuilder sb, final boolean printType) { + if (computed) { + sb.append('['); + } + key.toString(sb, printType); + if (computed) { + sb.append(']'); + } + if (value != null) { + sb.append(": "); + value.toString(sb, printType); + } + } + + /** + * Return the key for this property node + * + * @return the key + */ + public Expression getKey() { + return key; + } + + private RecordPropertyNode setKey(final Expression key) { + if (this.key == key) { + return this; + } + return new RecordPropertyNode(this, key, value, computed); + } + + /** + * Get the value of this property + * + * @return property value + */ + public Expression getValue() { + return value; + } + + private RecordPropertyNode setValue(final Expression value) { + if (this.value == value) { + return this; + } + return new RecordPropertyNode(this, key, value, computed); + } + + public boolean isComputed() { + return computed; + } + + public boolean isSpread() { + return key != null && key.isTokenType(TokenType.SPREAD_RECORD); + } +} diff --git a/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/ir/UnaryNode.java b/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/ir/UnaryNode.java index acea260108e..09130dfd655 100644 --- a/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/ir/UnaryNode.java +++ b/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/ir/UnaryNode.java @@ -146,7 +146,8 @@ public void toString(final StringBuilder sb, final boolean printType) { if (tokenType == TokenType.AWAIT) { // await expression sb.append("await "); - } else if (tokenType == TokenType.SPREAD_ARRAY || tokenType == TokenType.SPREAD_OBJECT) { + } else if (tokenType == TokenType.SPREAD_ARRAY || tokenType == TokenType.SPREAD_OBJECT + || tokenType == TokenType.SPREAD_RECORD || tokenType == TokenType.SPREAD_TUPLE) { sb.append("..."); } diff --git a/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/ir/visitor/NodeVisitor.java b/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/ir/visitor/NodeVisitor.java index 2a9376abec3..2abeb4523d9 100644 --- a/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/ir/visitor/NodeVisitor.java +++ b/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/ir/visitor/NodeVisitor.java @@ -78,6 +78,8 @@ import com.oracle.js.parser.ir.ObjectNode; import com.oracle.js.parser.ir.ParameterNode; import com.oracle.js.parser.ir.PropertyNode; +import com.oracle.js.parser.ir.RecordNode; +import com.oracle.js.parser.ir.RecordPropertyNode; import com.oracle.js.parser.ir.ReturnNode; import com.oracle.js.parser.ir.RuntimeNode; import com.oracle.js.parser.ir.SwitchNode; @@ -674,6 +676,46 @@ public Node leavePropertyNode(final PropertyNode propertyNode) { return leaveDefault(propertyNode); } + /** + * Callback for entering an RecordNode + * + * @param recordNode the node + * @return true if traversal should continue and node children be traversed, false otherwise + */ + public boolean enterRecordNode(final RecordNode recordNode) { + return enterDefault(recordNode); + } + + /** + * Callback for leaving an RecordNode + * + * @param recordNode the node + * @return processed node, which will replace the original one, or the original node + */ + public Node leaveRecordNode(final RecordNode recordNode) { + return leaveDefault(recordNode); + } + + /** + * Callback for entering a RecordPropertyNode + * + * @param recordPropertyNode the node + * @return true if traversal should continue and node children be traversed, false otherwise + */ + public boolean enterPropertyNode(final RecordPropertyNode recordPropertyNode) { + return enterDefault(recordPropertyNode); + } + + /** + * Callback for leaving a RecordPropertyNode + * + * @param recordPropertyNode the node + * @return processed node, which will replace the original one, or the original node + */ + public Node leavePropertyNode(final RecordPropertyNode recordPropertyNode) { + return leaveDefault(recordPropertyNode); + } + /** * Callback for entering a ReturnNode * diff --git a/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/ir/visitor/TranslatorNodeVisitor.java b/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/ir/visitor/TranslatorNodeVisitor.java index 19704f73403..4feec3c3613 100644 --- a/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/ir/visitor/TranslatorNodeVisitor.java +++ b/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/ir/visitor/TranslatorNodeVisitor.java @@ -78,6 +78,8 @@ import com.oracle.js.parser.ir.ObjectNode; import com.oracle.js.parser.ir.ParameterNode; import com.oracle.js.parser.ir.PropertyNode; +import com.oracle.js.parser.ir.RecordNode; +import com.oracle.js.parser.ir.RecordPropertyNode; import com.oracle.js.parser.ir.ReturnNode; import com.oracle.js.parser.ir.RuntimeNode; import com.oracle.js.parser.ir.SwitchNode; @@ -375,6 +377,26 @@ public R enterNamedImportsNode(final NamedImportsNode namedImportsNode) { return enterDefault(namedImportsNode); } + /** + * Callback for entering an RecordNode + * + * @param recordNode the node + * @return true if traversal should continue and node children be traversed, false otherwise + */ + public R enterRecordNode(final RecordNode recordNode) { + return enterDefault(recordNode); + } + + /** + * Callback for entering a RecordPropertyNode + * + * @param recordPropertyNode the node + * @return true if traversal should continue and node children be traversed, false otherwise + */ + public R enterPropertyNode(final RecordPropertyNode recordPropertyNode) { + return enterDefault(recordPropertyNode); + } + /** * Callback for entering an ObjectNode * diff --git a/graal-js/src/com.oracle.truffle.js.parser/src/com/oracle/truffle/js/parser/GraalJSTranslator.java b/graal-js/src/com.oracle.truffle.js.parser/src/com/oracle/truffle/js/parser/GraalJSTranslator.java index d39e8fd231f..aaaeaf845e0 100644 --- a/graal-js/src/com.oracle.truffle.js.parser/src/com/oracle/truffle/js/parser/GraalJSTranslator.java +++ b/graal-js/src/com.oracle.truffle.js.parser/src/com/oracle/truffle/js/parser/GraalJSTranslator.java @@ -82,6 +82,8 @@ import com.oracle.js.parser.ir.ObjectNode; import com.oracle.js.parser.ir.ParameterNode; import com.oracle.js.parser.ir.PropertyNode; +import com.oracle.js.parser.ir.RecordNode; +import com.oracle.js.parser.ir.RecordPropertyNode; import com.oracle.js.parser.ir.RuntimeNode; import com.oracle.js.parser.ir.Scope; import com.oracle.js.parser.ir.Statement; @@ -151,6 +153,7 @@ import com.oracle.truffle.js.nodes.function.JSFunctionExpressionNode; import com.oracle.truffle.js.nodes.function.JSNewNode; import com.oracle.truffle.js.nodes.function.SpreadArgumentNode; +import com.oracle.truffle.js.nodes.record.RecordLiteralNode.AbstractRecordLiteralMemberNode; import com.oracle.truffle.js.nodes.unary.JSUnaryNode; import com.oracle.truffle.js.nodes.unary.TypeOfNode; import com.oracle.truffle.js.nodes.unary.VoidNode; @@ -1473,6 +1476,34 @@ private JavaScriptNode enterLiteralTupleNode(LiteralNode.TupleLiteralNode tupleL return hasSpread ? factory.createTupleLiteralWithSpread(context, elements) : factory.createTupleLiteral(context, elements); } + @Override + public JavaScriptNode enterRecordNode(RecordNode recordNode) { + AbstractRecordLiteralMemberNode[] members = transformRecordPropertyDefinitionList(recordNode.getElements()); + return tagExpression(factory.createRecordLiteral(context, members), recordNode); + } + + private AbstractRecordLiteralMemberNode[] transformRecordPropertyDefinitionList(List properties) { + AbstractRecordLiteralMemberNode[] elements = new AbstractRecordLiteralMemberNode[properties.size()]; + for (int i = 0; i < properties.size(); i++) { + elements[i] = enterRecordPropertyNode(properties.get(i)); + } + return elements; + } + + private AbstractRecordLiteralMemberNode enterRecordPropertyNode(RecordPropertyNode property) { + if (property.isSpread()) { + JavaScriptNode from = transform(((UnaryNode) property.getKey()).getExpression()); + return factory.createSpreadRecordMember(from); + } + JavaScriptNode value = transform(property.getValue()); + if (property.isComputed()) { + JavaScriptNode computedKey = transform(property.getKey()); + return factory.createComputedRecordMember(computedKey, value); + } else { + return factory.createRecordMember(property.getKeyName(), value); + } + } + @Override public JavaScriptNode enterIdentNode(IdentNode identNode) { assert !identNode.isPropertyName(); diff --git a/graal-js/src/com.oracle.truffle.js.parser/src/com/oracle/truffle/js/parser/internal/ir/debug/JSONWriter.java b/graal-js/src/com.oracle.truffle.js.parser/src/com/oracle/truffle/js/parser/internal/ir/debug/JSONWriter.java index 4e983604aca..b9da51eb2ce 100644 --- a/graal-js/src/com.oracle.truffle.js.parser/src/com/oracle/truffle/js/parser/internal/ir/debug/JSONWriter.java +++ b/graal-js/src/com.oracle.truffle.js.parser/src/com/oracle/truffle/js/parser/internal/ir/debug/JSONWriter.java @@ -885,6 +885,8 @@ public boolean enterUnaryNode(final UnaryNode unaryNode) { case SPREAD_ARGUMENT: case SPREAD_ARRAY: case SPREAD_OBJECT: + case SPREAD_RECORD: + case SPREAD_TUPLE: operator = "..."; prefix = true; break; diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/NodeFactory.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/NodeFactory.java index 3ab88986c3d..a0b322e03e8 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/NodeFactory.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/NodeFactory.java @@ -103,7 +103,6 @@ import com.oracle.truffle.js.nodes.access.RestObjectNode; import com.oracle.truffle.js.nodes.access.ScopeFrameNode; import com.oracle.truffle.js.nodes.access.SuperPropertyReferenceNode; -import com.oracle.truffle.js.nodes.tuples.TupleLiteralNode; import com.oracle.truffle.js.nodes.access.WithTargetNode; import com.oracle.truffle.js.nodes.access.WithVarWrapperNode; import com.oracle.truffle.js.nodes.access.WriteElementNode; @@ -207,6 +206,9 @@ import com.oracle.truffle.js.nodes.module.ResolveNamedImportNode; import com.oracle.truffle.js.nodes.module.ResolveStarImportNode; import com.oracle.truffle.js.nodes.promise.ImportCallNode; +import com.oracle.truffle.js.nodes.record.RecordLiteralNode; +import com.oracle.truffle.js.nodes.record.RecordLiteralNode.AbstractRecordLiteralMemberNode; +import com.oracle.truffle.js.nodes.tuples.TupleLiteralNode; import com.oracle.truffle.js.nodes.unary.JSComplementNode; import com.oracle.truffle.js.nodes.unary.JSNotNode; import com.oracle.truffle.js.nodes.unary.JSUnaryMinusNode; @@ -784,6 +786,22 @@ public JavaScriptNode createArrayLiteralWithSpread(JSContext context, JavaScript return ArrayLiteralNode.createWithSpread(context, elements); } + public JavaScriptNode createRecordLiteral(JSContext context, AbstractRecordLiteralMemberNode[] members) { + return RecordLiteralNode.create(context, members); + } + + public AbstractRecordLiteralMemberNode createRecordMember(String keyName, JavaScriptNode value) { + return RecordLiteralNode.createMember(keyName, value); + } + + public AbstractRecordLiteralMemberNode createComputedRecordMember(JavaScriptNode key, JavaScriptNode value) { + return RecordLiteralNode.createComputedMember(key, value); + } + + public AbstractRecordLiteralMemberNode createSpreadRecordMember(JavaScriptNode node) { + return RecordLiteralNode.createSpreadMember(node); + } + public JavaScriptNode createTupleLiteral(JSContext context, JavaScriptNode[] elements) { return TupleLiteralNode.create(context, elements); } diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/instrumentation/JSTags.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/instrumentation/JSTags.java index 159ee3db0fd..7f3c0c3c8fa 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/instrumentation/JSTags.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/instrumentation/JSTags.java @@ -151,6 +151,7 @@ public static final class LiteralTag extends Tag { public enum Type { ObjectLiteral, ArrayLiteral, + RecordLiteral, TupleLiteral, FunctionLiteral, NumericLiteral, diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/record/RecordLiteralNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/record/RecordLiteralNode.java new file mode 100644 index 00000000000..23e9507c732 --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/record/RecordLiteralNode.java @@ -0,0 +1,307 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.oracle.truffle.js.nodes.record; + +import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.api.instrumentation.Tag; +import com.oracle.truffle.api.object.DynamicObject; +import com.oracle.truffle.js.nodes.JSGuards; +import com.oracle.truffle.js.nodes.JavaScriptBaseNode; +import com.oracle.truffle.js.nodes.JavaScriptNode; +import com.oracle.truffle.js.nodes.access.ReadElementNode; +import com.oracle.truffle.js.nodes.cast.JSToObjectNode; +import com.oracle.truffle.js.nodes.function.FunctionNameHolder; +import com.oracle.truffle.js.nodes.function.SetFunctionNameNode; +import com.oracle.truffle.js.nodes.instrumentation.JSTags; +import com.oracle.truffle.js.nodes.instrumentation.JSTags.LiteralTag; +import com.oracle.truffle.js.runtime.Errors; +import com.oracle.truffle.js.runtime.JSContext; +import com.oracle.truffle.js.runtime.JSRuntime; +import com.oracle.truffle.js.runtime.Symbol; +import com.oracle.truffle.js.runtime.objects.JSObject; +import com.oracle.truffle.js.runtime.objects.Null; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + +public class RecordLiteralNode extends JavaScriptNode { + + protected final JSContext context; + + @Children protected final AbstractRecordLiteralMemberNode[] elements; + + protected RecordLiteralNode(JSContext context, AbstractRecordLiteralMemberNode[] elements) { + this.context = context; + this.elements = elements; + } + + @Override + public Object execute(VirtualFrame frame) { + Map entries = new TreeMap<>(); + for (AbstractRecordLiteralMemberNode element : elements) { + element.evaluate(frame, entries, context); + } + return Null.instance; // TODO: create Record primitive + } + + @Override + public boolean hasTag(Class tag) { + if (tag == LiteralTag.class) { + return true; + } else { + return super.hasTag(tag); + } + } + + @Override + public Object getNodeObject() { + return JSTags.createNodeObjectDescriptor(LiteralTag.TYPE, LiteralTag.Type.RecordLiteral.name()); + } + + public static JavaScriptNode create(JSContext context, AbstractRecordLiteralMemberNode[] elements) { + return new RecordLiteralNode(context, elements); + } + + public static AbstractRecordLiteralMemberNode createMember(String keyName, JavaScriptNode value) { + return new RecordLiteralMemberNode(keyName, value); + } + + public static AbstractRecordLiteralMemberNode createComputedMember(JavaScriptNode key, JavaScriptNode value) { + return new ComputedRecordLiteralMemberNode(key, value); + } + + public static AbstractRecordLiteralMemberNode createSpreadMember(JavaScriptNode node) { + return new SpreadRecordLiteralMemberNode(node); + } + + public abstract static class AbstractRecordLiteralMemberNode extends JavaScriptBaseNode { + + public abstract void evaluate(VirtualFrame frame, Map entries, JSContext context); + + /** + * Records & Tuples Proposal: 3.3.1 AddPropertyIntoRecordEntriesList + * + *
+         * 1. Assert: Type(entries) is List.
+         * 2. Assert: IsProperyKey(propName) is true.
+         * 3. If Type(propName) is Symbol, throw a TypeError exception.
+         * 4. If Type(value) is Object, throw a TypeError exception.
+         * 5. Add { [[Key]]: propType, [[Value]]: value } to entries.
+         * 6. Return entries.
+         * 
+ */ + protected final void addEntry(Map entries, Object key, Object value) { + assert JSRuntime.isPropertyKey(key); + if (key instanceof Symbol) { + throw Errors.createTypeError("Record may only have string as keys"); + } + assert key instanceof String; + if (!JSRuntime.isJSPrimitive(value)) { + throw Errors.createTypeError("Record may only contain primitive values"); + } + entries.put((String) key, value); + } + + protected boolean isAnonymousFunctionDefinition(JavaScriptNode expression) { + return expression instanceof FunctionNameHolder && ((FunctionNameHolder) expression).isAnonymous(); + } + + protected abstract AbstractRecordLiteralMemberNode copyUninitialized(Set> materializedTags); + + public static AbstractRecordLiteralMemberNode[] cloneUninitialized(AbstractRecordLiteralMemberNode[] members, Set> materializedTags) { + AbstractRecordLiteralMemberNode[] copy = members.clone(); + for (int i = 0; i < copy.length; i++) { + copy[i] = copy[i].copyUninitialized(materializedTags); + } + return copy; + } + } + + private static class RecordLiteralMemberNode extends AbstractRecordLiteralMemberNode { + + protected final Object name; + + @Child protected JavaScriptNode valueNode; + + RecordLiteralMemberNode(Object name, JavaScriptNode valueNode) { + this.name = name; + this.valueNode = valueNode; + } + + /** + * Records & Tuples Proposal: 7.1.1.4 Runtime Semantics: RecordPropertyDefinitionEvaluation + */ + @Override + public void evaluate(VirtualFrame frame, Map entries, JSContext context) { + Object value = valueNode.execute(frame); + addEntry(entries, name, value); + } + + @Override + protected AbstractRecordLiteralMemberNode copyUninitialized(Set> materializedTags) { + return new RecordLiteralMemberNode(name, JavaScriptNode.cloneUninitialized(valueNode, materializedTags)); + } + } + + private static class ComputedRecordLiteralMemberNode extends AbstractRecordLiteralMemberNode { + + @Child private JavaScriptNode keyNode; + @Child private JavaScriptNode valueNode; + @Child private SetFunctionNameNode setFunctionName; + + ComputedRecordLiteralMemberNode(JavaScriptNode keyNode, JavaScriptNode valueNode) { + this.keyNode = keyNode; + this.valueNode = valueNode; + this.setFunctionName = isAnonymousFunctionDefinition(valueNode) ? SetFunctionNameNode.create() : null; + } + + /** + * Records & Tuples Proposal: 7.1.1.4 Runtime Semantics: RecordPropertyDefinitionEvaluation + */ + @Override + public void evaluate(VirtualFrame frame, Map entries, JSContext context) { + Object key = keyNode.execute(frame); + Object value = valueNode.execute(frame); + if (setFunctionName != null) { + setFunctionName.execute(value, key); // NamedEvaluation + } + addEntry(entries, key, value); + } + + @Override + protected AbstractRecordLiteralMemberNode copyUninitialized(Set> materializedTags) { + return new ComputedRecordLiteralMemberNode( + JavaScriptNode.cloneUninitialized(keyNode, materializedTags), + JavaScriptNode.cloneUninitialized(valueNode, materializedTags) + ); + } + } + + private static class SpreadRecordLiteralMemberNode extends AbstractRecordLiteralMemberNode { + + @Child private JavaScriptNode valueNode; + @Child private JSToObjectNode toObjectNode; + @Child private ReadElementNode readElementNode; + + SpreadRecordLiteralMemberNode(JavaScriptNode valueNode) { + this.valueNode = valueNode; + } + + /** + * Records & Tuples Proposal: 7.1.1.4 Runtime Semantics: RecordPropertyDefinitionEvaluation + * + *
+         * RecordPropertyDefinition: ...AssignmentExpression
+         *
+         * 1. Let exprValue be the result of evaluating AssignmentExpression.
+         * 2. Let source be ? GetValue(exprValue).
+         * 3. If source is undefined or null, return entries.
+         * 4. Let from be ! ToObject(source).
+         * 5. Let keys be ? from.[[OwnPropertyKeys]]().
+         * 6. For each element nextKey of keys in List order, do
+         * 7. Let value be from.[[Get]](nextKey).
+         * 9. Perform ? AddPropertyIntoRecordEntriesList(entries, nextKey, value).
+         * 10. Return entries.
+         * 
+ */ + @Override + public void evaluate(VirtualFrame frame, Map entries, JSContext context) { + Object source = valueNode.execute(frame); + if (JSGuards.isNullOrUndefined(source)) { + return; + } + Object from = toObject(source, context); + + List keys = JSObject.ownPropertyKeys((DynamicObject) from); + // TODO: Why is ListSizeNode used for getting list.size() ? + // TODO: Also ListGetNode for list.get(...) ? + // TODO: see com.oracle.truffle.js.nodes.access.CopyDataPropertiesNode.copyDataProperties + for (Object key : keys) { + assert JSRuntime.isPropertyKey(key); + Object value = get(from, key, context); + addEntry(entries, key, value); + + // TODO: when spreading members in object literals, [[Enumerable]] gets checked before adding it + // TODO: see com.oracle.truffle.js.nodes.access.CopyDataPropertiesNode.copyDataProperties + // TODO: see sample implementation below + // PropertyDescriptor desc = getOwnProperty.execute(source, key); + // if (desc != null && desc.getEnumerable()) { + // Object value = get(from, key, context); + // addMember(entries, key, value); + // } + // TODO: see test case below + // let a = [42]; + // let b = {...a} + // let c = #{...a} + // console.log(a.length); // "1" + // console.log(b.length); // "undefined" + // console.log(c.length); // "1" according to proposal spec BUT "undefined" according to proposal polyfill + } + } + + private Object toObject(Object obj, JSContext context) { + if (toObjectNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + toObjectNode = insert(JSToObjectNode.createToObjectNoCheck(context)); + } + return toObjectNode.execute(obj); + } + + private Object get(Object obj, Object key, JSContext context) { + if (readElementNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + readElementNode = insert(ReadElementNode.create(context)); + } + return readElementNode.executeWithTargetAndIndex(obj, key); + } + + @Override + protected AbstractRecordLiteralMemberNode copyUninitialized(Set> materializedTags) { + return new SpreadRecordLiteralMemberNode( + JavaScriptNode.cloneUninitialized(valueNode, materializedTags) + ); + } + } +} From 1d593d2023aa2eb88d87c765c17d9341b8adce2d Mon Sep 17 00:00:00 2001 From: Christian Aistleitner Date: Sun, 25 Apr 2021 17:21:16 +0200 Subject: [PATCH 07/31] Add record type and basic functionality. --- .../js/record-tuple/record.js | 109 +++++++ .../js/nodes/access/PropertyCacheNode.java | 5 + .../js/nodes/cast/JSPrepareThisNode.java | 7 + .../truffle/js/nodes/cast/JSToObjectNode.java | 7 + .../js/nodes/control/DeletePropertyNode.java | 22 ++ .../js/nodes/record/RecordLiteralNode.java | 5 +- .../oracle/truffle/js/runtime/JSContext.java | 7 + .../oracle/truffle/js/runtime/JSRuntime.java | 15 +- .../com/oracle/truffle/js/runtime/Record.java | 73 +++++ .../truffle/js/runtime/builtins/JSRecord.java | 269 ++++++++++++++++++ .../js/runtime/builtins/JSRecordObject.java | 70 +++++ .../truffle/js/runtime/builtins/JSTuple.java | 5 +- 12 files changed, 586 insertions(+), 8 deletions(-) create mode 100644 graal-js/src/com.oracle.truffle.js.test/js/record-tuple/record.js create mode 100644 graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Record.java create mode 100644 graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSRecord.java create mode 100644 graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSRecordObject.java diff --git a/graal-js/src/com.oracle.truffle.js.test/js/record-tuple/record.js b/graal-js/src/com.oracle.truffle.js.test/js/record-tuple/record.js new file mode 100644 index 00000000000..e768e7cee4a --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js.test/js/record-tuple/record.js @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. + */ + +/** + * @option ecmascript-version=2022 + */ + +load('../assert.js'); +let a, b, c; + +/* + * Test 1: + * valid records + */ +a = #{}; +b = #{ a: 1, b: 2 }; +c = #{ a: 1, b: #[2, 3, #{ c: 4 }] }; + +/* + * Test 2: + * Non-String key + */ +assertThrows(function() { + eval("const x = #{ [Symbol()]: #{} }"); // TypeError: Record may only have string as keys +}, TypeError); + +/* + * Test 3: + * concise methods + */ +assertThrows(function() { + eval("#{ method() { } }"); // SyntaxError, concise methods are disallowed in Record syntax +}, SyntaxError); + +/* + * Test 4: + * __proto__ + */ +const y = #{ "__proto__": 1 }; // valid, creates a record with a "__proto__" property. +assertThrows(function() { + eval("const x = #{ __proto__: foo }"); // SyntaxError, __proto__ identifier prevented by syntax +}, SyntaxError); + +/* + * Test 5: + * nested records + */ +a = #{ id: 1, child: #{ grandchild: #{ result: 42 } } }; +assertSame(1, a.id); +assertSame(42, a.child.grandchild.result); + +/* + * Test 6: + * [[OwnPropertyKeys]] + */ +a = #{ id: 1, data: "Hello World!" }; +assertSame(["data", "id"].toString(), Object.getOwnPropertyNames(a).toString()); + +/* + * Test 7: + * [[GetOwnProperty]] + */ +a = #{ age: 22 }; +b = Object.getOwnPropertyDescriptors(a); +assertSame(22, b.age.value); +assertSame(false, b.age.writable); +assertSame(true, b.age.enumerable); +assertSame(false, b.age.configurable); + +/* + * Test 8: + * [[DefineOwnProperty]] + */ +// TODO: Not sure if [[DefineOwnProperty]] is reachable according to proposal and ecma262 specs +// TODO: Code below should work according to polyfill, but shouldn't according to specs +// a = #{ age: 22 }; +// Object.defineProperty(a, "age", { value: 22 }); +// Object.defineProperty(a, "age", { value: 22, writable: false, enumerable: true, configurable: false }); +// assertThrows(function() { +// eval(`Object.defineProperty(a, "age", { value: 21 });`); // value must be the same +// }, TypeError); +// assertThrows(function() { +// eval(`Object.defineProperty(a, "age", { writable: false, enumerable: true, configurable: false });`); // value must be provided +// }, TypeError); +// assertThrows(function() { +// eval(`Object.defineProperty(a, "age", { value: 22, writable: true });`); // writeable must always be false +// }, TypeError); +// assertThrows(function() { +// eval(`Object.defineProperty(a, "age", { value: 22, enumerable: false });`); // enumerable must always be true +// }, TypeError); +// assertThrows(function() { +// eval(`Object.defineProperty(a, "age", { value: 22, configurable: true });`); // configurable must always be false +// }, TypeError); +// assertThrows(function() { +// eval(`Object.defineProperty(a, Symbol("42"), { value: 22 });`); // Symbols are not allowed +// }, TypeError); + +/* + * Test 9: + * [[Delete]] + */ +a = #{ age: 22 }; +assertSame(false, delete a.age); +assertSame(true, delete a.unknown); +assertSame(true, delete a[Symbol("test")]); diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/access/PropertyCacheNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/access/PropertyCacheNode.java index fca2c80015d..7478df57184 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/access/PropertyCacheNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/access/PropertyCacheNode.java @@ -73,6 +73,7 @@ import com.oracle.truffle.js.runtime.builtins.JSDictionary; import com.oracle.truffle.js.runtime.builtins.JSFunction; import com.oracle.truffle.js.runtime.builtins.JSProxy; +import com.oracle.truffle.js.runtime.builtins.JSRecord; import com.oracle.truffle.js.runtime.builtins.JSRegExp; import com.oracle.truffle.js.runtime.builtins.JSString; import com.oracle.truffle.js.runtime.builtins.PrototypeSupplier; @@ -1195,6 +1196,10 @@ protected T createSpecialization(Object thisObj, T currentHead, int cachedCount, return rewriteToGeneric(currentHead, cachedCount, "dictionary object"); } + if (JSRecord.isJSRecord(store)) { + return rewriteToGeneric(currentHead, cachedCount, "record object"); + } + if (JSConfig.MergeShapes && cachedCount > 0) { // check if we're creating unnecessary polymorphism due to compatible types if (tryMergeShapes(cacheShape, currentHead)) { diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSPrepareThisNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSPrepareThisNode.java index cb137571e48..8bc631c6452 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSPrepareThisNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSPrepareThisNode.java @@ -55,11 +55,13 @@ import com.oracle.truffle.js.runtime.BigInt; import com.oracle.truffle.js.runtime.JSConfig; import com.oracle.truffle.js.runtime.JSContext; +import com.oracle.truffle.js.runtime.Record; import com.oracle.truffle.js.runtime.Symbol; import com.oracle.truffle.js.runtime.Tuple; import com.oracle.truffle.js.runtime.builtins.JSBigInt; import com.oracle.truffle.js.runtime.builtins.JSBoolean; import com.oracle.truffle.js.runtime.builtins.JSNumber; +import com.oracle.truffle.js.runtime.builtins.JSRecord; import com.oracle.truffle.js.runtime.builtins.JSString; import com.oracle.truffle.js.runtime.builtins.JSSymbol; import com.oracle.truffle.js.runtime.builtins.JSTuple; @@ -142,6 +144,11 @@ protected DynamicObject doSymbol(Symbol value) { return JSSymbol.create(context, value); } + @Specialization + protected DynamicObject doRecord(Record value) { + return JSRecord.create(context, value); + } + @Specialization protected DynamicObject doTuple(Tuple value) { return JSTuple.create(context, value); diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToObjectNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToObjectNode.java index c92f5e8b2dd..814ca063a7c 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToObjectNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToObjectNode.java @@ -61,11 +61,13 @@ import com.oracle.truffle.js.runtime.JSContext; import com.oracle.truffle.js.runtime.JSException; import com.oracle.truffle.js.runtime.JSRuntime; +import com.oracle.truffle.js.runtime.Record; import com.oracle.truffle.js.runtime.Symbol; import com.oracle.truffle.js.runtime.Tuple; import com.oracle.truffle.js.runtime.builtins.JSBigInt; import com.oracle.truffle.js.runtime.builtins.JSBoolean; import com.oracle.truffle.js.runtime.builtins.JSNumber; +import com.oracle.truffle.js.runtime.builtins.JSRecord; import com.oracle.truffle.js.runtime.builtins.JSString; import com.oracle.truffle.js.runtime.builtins.JSSymbol; import com.oracle.truffle.js.runtime.builtins.JSTuple; @@ -175,6 +177,11 @@ protected DynamicObject doSymbol(Symbol value) { return JSSymbol.create(getContext(), value); } + @Specialization + protected DynamicObject doRecord(Record value) { + return JSRecord.create(getContext(), value); + } + @Specialization protected DynamicObject doTuple(Tuple value) { return JSTuple.create(getContext(), value); diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/control/DeletePropertyNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/control/DeletePropertyNode.java index c5922b35d8d..c990cc945e5 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/control/DeletePropertyNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/control/DeletePropertyNode.java @@ -73,9 +73,13 @@ import com.oracle.truffle.js.runtime.JSConfig; import com.oracle.truffle.js.runtime.JSContext; import com.oracle.truffle.js.runtime.JSRuntime; +import com.oracle.truffle.js.runtime.Record; import com.oracle.truffle.js.runtime.SafeInteger; import com.oracle.truffle.js.runtime.Symbol; +import com.oracle.truffle.js.runtime.Tuple; +import com.oracle.truffle.js.runtime.builtins.JSRecord; import com.oracle.truffle.js.runtime.builtins.JSString; +import com.oracle.truffle.js.runtime.builtins.JSTuple; import com.oracle.truffle.js.runtime.objects.JSObject; import com.oracle.truffle.js.runtime.util.JSClassProfile; @@ -185,6 +189,24 @@ protected final boolean doJSObject(DynamicObject targetObject, Object key, return JSObject.delete(targetObject, propertyKey, strict, jsclassProfile); } + @Specialization + protected boolean doRecord(Record target, Object property, + @Cached("create()") JSClassProfile jsclassProfile, + @Shared("toPropertyKey") @Cached("create()") JSToPropertyKeyNode toPropertyKeyNode) { + Object propertyKey = toPropertyKeyNode.execute(property); + DynamicObject object = JSRecord.create(context, target); + return JSObject.delete(object, propertyKey, strict, jsclassProfile); + } + + @Specialization + protected boolean doTuple(Tuple target, Object property, + @Cached("create()") JSClassProfile jsclassProfile, + @Shared("toPropertyKey") @Cached("create()") JSToPropertyKeyNode toPropertyKeyNode) { + Object propertyKey = toPropertyKeyNode.execute(property); + DynamicObject object = JSTuple.create(context, target); + return JSObject.delete(object, propertyKey, strict, jsclassProfile); + } + @SuppressWarnings("unused") @Specialization protected static boolean doSymbol(Symbol target, Object property, diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/record/RecordLiteralNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/record/RecordLiteralNode.java index 23e9507c732..eac86ea701e 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/record/RecordLiteralNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/record/RecordLiteralNode.java @@ -38,7 +38,6 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - package com.oracle.truffle.js.nodes.record; import com.oracle.truffle.api.CompilerDirectives; @@ -57,9 +56,9 @@ import com.oracle.truffle.js.runtime.Errors; import com.oracle.truffle.js.runtime.JSContext; import com.oracle.truffle.js.runtime.JSRuntime; +import com.oracle.truffle.js.runtime.Record; import com.oracle.truffle.js.runtime.Symbol; import com.oracle.truffle.js.runtime.objects.JSObject; -import com.oracle.truffle.js.runtime.objects.Null; import java.util.List; import java.util.Map; @@ -83,7 +82,7 @@ public Object execute(VirtualFrame frame) { for (AbstractRecordLiteralMemberNode element : elements) { element.evaluate(frame, entries, context); } - return Null.instance; // TODO: create Record primitive + return new Record(entries); } @Override diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSContext.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSContext.java index 986e5389481..78f616c5dd9 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSContext.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSContext.java @@ -95,6 +95,7 @@ import com.oracle.truffle.js.runtime.builtins.JSOrdinary; import com.oracle.truffle.js.runtime.builtins.JSPromise; import com.oracle.truffle.js.runtime.builtins.JSProxy; +import com.oracle.truffle.js.runtime.builtins.JSRecord; import com.oracle.truffle.js.runtime.builtins.JSRegExp; import com.oracle.truffle.js.runtime.builtins.JSSet; import com.oracle.truffle.js.runtime.builtins.JSSharedArrayBuffer; @@ -403,6 +404,7 @@ public enum BuiltinFunctionKey { private final JSObjectFactory webAssemblyTableFactory; private final JSObjectFactory webAssemblyGlobalFactory; + private final JSObjectFactory recordFactory; private final JSObjectFactory tupleFactory; private final int factoryCount; @@ -561,6 +563,7 @@ protected JSContext(Evaluator evaluator, JSContextOptions contextOptions, JavaSc this.webAssemblyTableFactory = builder.create(JSWebAssemblyTable.INSTANCE); this.webAssemblyGlobalFactory = builder.create(JSWebAssemblyGlobal.INSTANCE); + this.recordFactory = builder.create(JSRecord.INSTANCE); this.tupleFactory = builder.create(JSTuple.INSTANCE); this.factoryCount = builder.finish(); @@ -989,6 +992,10 @@ public JSObjectFactory getWebAssemblyGlobalFactory() { return webAssemblyGlobalFactory; } + public final JSObjectFactory getRecordFactory() { + return recordFactory; + } + public final JSObjectFactory getTupleFactory() { return tupleFactory; } diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSRuntime.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSRuntime.java index b5c25457e24..e1cbe2ce264 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSRuntime.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSRuntime.java @@ -74,6 +74,7 @@ import com.oracle.truffle.js.runtime.builtins.JSNumber; import com.oracle.truffle.js.runtime.builtins.JSOrdinary; import com.oracle.truffle.js.runtime.builtins.JSProxy; +import com.oracle.truffle.js.runtime.builtins.JSRecord; import com.oracle.truffle.js.runtime.builtins.JSSet; import com.oracle.truffle.js.runtime.builtins.JSString; import com.oracle.truffle.js.runtime.builtins.JSSymbol; @@ -1525,6 +1526,8 @@ public static TruffleObject toObjectFromPrimitive(JSContext ctx, Object value, b return JSNumber.create(ctx, (Number) value); } else if (value instanceof Symbol) { return JSSymbol.create(ctx, (Symbol) value); + } else if (value instanceof Record) { + return JSRecord.create(ctx, (Record) value); } else if (value instanceof Tuple) { return JSTuple.create(ctx, (Tuple) value); } else { @@ -1618,8 +1621,9 @@ public static boolean isForeignObject(Object value) { } public static boolean isForeignObject(TruffleObject value) { - return !JSDynamicObject.isJSDynamicObject(value) && !(value instanceof Symbol) && !(value instanceof JSLazyString) && !(value instanceof SafeInteger) && - !(value instanceof BigInt) && !(value instanceof Tuple); + return !JSDynamicObject.isJSDynamicObject(value) && !(value instanceof Symbol) + && !(value instanceof JSLazyString) && !(value instanceof SafeInteger) + && !(value instanceof BigInt) && !(value instanceof Tuple) && !(value instanceof Record); } private static boolean equalInterop(Object a, Object b) { @@ -2009,8 +2013,13 @@ public static boolean isJSNative(Object value) { return JSDynamicObject.isJSDynamicObject(value) || isJSPrimitive(value); } + /** + * Is value a native JavaScript primitive? + */ public static boolean isJSPrimitive(Object value) { - return isNumber(value) || value instanceof BigInt || value instanceof Boolean || isString(value) || value == Undefined.instance || value == Null.instance || value instanceof Symbol || value instanceof Tuple; + return isNumber(value) || value instanceof BigInt || value instanceof Boolean || isString(value) + || value == Undefined.instance || value == Null.instance || value instanceof Symbol + || value instanceof Tuple || value instanceof Record; } /** diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Record.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Record.java new file mode 100644 index 00000000000..40c91fa214b --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Record.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.truffle.js.runtime; + +import com.oracle.truffle.api.CompilerDirectives.ValueType; +import com.oracle.truffle.api.interop.InteropLibrary; +import com.oracle.truffle.api.interop.TruffleObject; +import com.oracle.truffle.api.library.ExportLibrary; + +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + +@ExportLibrary(InteropLibrary.class) +@ValueType +public final class Record implements TruffleObject { + + private final TreeMap map; + + public Record(Map map) { + this.map = new TreeMap<>(map); + } + + public Object get(String key) { + return map.get(key); + } + + public boolean hasKey(String key) { + return map.containsKey(key); + } + + public Set getKeys() { + return map.keySet(); + } +} diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSRecord.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSRecord.java new file mode 100644 index 00000000000..a835f678a0d --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSRecord.java @@ -0,0 +1,269 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.truffle.js.runtime.builtins; + +import com.oracle.truffle.api.nodes.Node; +import com.oracle.truffle.api.object.DynamicObject; +import com.oracle.truffle.api.object.Shape; +import com.oracle.truffle.js.runtime.JSContext; +import com.oracle.truffle.js.runtime.JSRealm; +import com.oracle.truffle.js.runtime.JSRuntime; +import com.oracle.truffle.js.runtime.Record; +import com.oracle.truffle.js.runtime.Symbol; +import com.oracle.truffle.js.runtime.objects.JSObjectUtil; +import com.oracle.truffle.js.runtime.objects.JSShape; +import com.oracle.truffle.js.runtime.objects.PropertyDescriptor; +import com.oracle.truffle.js.runtime.util.DefinePropertyUtil; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Implementation of Record Exotic Object + * + * @see 5.1.1 Record Exotic Objects + */ +public class JSRecord extends JSNonProxy implements JSConstructorFactory.Default.WithFunctions, PrototypeSupplier { + + public static final String TYPE_NAME = "record"; + public static final String CLASS_NAME = "Record"; + public static final String PROTOTYPE_NAME = "Record.prototype"; + + public static final JSRecord INSTANCE = new JSRecord(); + + private JSRecord() { + } + + public static DynamicObject create(JSContext context, Record value) { + DynamicObject obj = JSRecordObject.create(context.getRealm(), context.getRecordFactory(), value); + assert isJSRecord(obj); + return context.trackAllocation(obj); + } + + public static Record valueOf(DynamicObject obj) { + assert isJSRecord(obj); + return ((JSRecordObject) obj).getValue(); + } + + public static boolean isJSRecord(Object obj) { + return obj instanceof JSRecordObject; + } + + @Override + public String getClassName(DynamicObject object) { + return getClassName(); + } + + @Override + public String getClassName() { + return CLASS_NAME; + } + + @Override + public DynamicObject createPrototype(JSRealm realm, DynamicObject constructor) { + JSContext context = realm.getContext(); + DynamicObject prototype = JSObjectUtil.createOrdinaryPrototypeObject(realm); + + JSObjectUtil.putConstructorProperty(context, prototype, constructor); + // TODO: JSObjectUtil.putFunctionsFromContainer(realm, prototype, RecordPrototypeBuiltins.BUILTINS); + JSObjectUtil.putToStringTag(prototype, CLASS_NAME); + + return prototype; + } + + @Override + public DynamicObject getIntrinsicDefaultProto(JSRealm realm) { + // TODO: return realm.getRecordPrototype(); + return null; + } + + /** + * Records aren't extensible, thus [[IsExtensible]] must always return false. + * + * TODO: see Ref below for ideas + * @see JSModuleNamespace#makeInitialShape(com.oracle.truffle.js.runtime.JSContext) + * + * @see JSNonProxy#isExtensible(com.oracle.truffle.api.object.DynamicObject) + */ + @Override + public Shape makeInitialShape(JSContext context, DynamicObject prototype) { + return JSShape.newBuilder(context, INSTANCE, prototype) + .shapeFlags(JSShape.NOT_EXTENSIBLE_FLAG) + .build(); + } + + /** + * Implementation of [[GetOwnProperty]]. + */ + @Override + public PropertyDescriptor getOwnProperty(DynamicObject thisObj, Object key) { + assert JSRuntime.isPropertyKey(key); + if (key instanceof Symbol) { + return null; + } + assert key instanceof String; + Object value = recordGet(thisObj, key); + if (value == null) { + return null; + } + return PropertyDescriptor.createData(value, true, false, false); + } + + /** + * Implementation of [[DefineOwnProperty]]. + */ + // TODO: Not sure if this code is reachable according to proposal and ecma262 specs + @Override + public boolean defineOwnProperty(DynamicObject thisObj, Object key, PropertyDescriptor desc, boolean doThrow) { + assert JSRuntime.isPropertyKey(key); + if (key instanceof Symbol) { + return DefinePropertyUtil.reject(doThrow, "object is not extensible"); + } + if (desc.getIfHasWritable(false) || !desc.getIfHasEnumerable(true) || desc.getIfHasConfigurable(false)) { + return DefinePropertyUtil.reject(doThrow, "object is not extensible"); + } + Object value = recordGet(thisObj, key); + if (value == null) { + return DefinePropertyUtil.reject(doThrow, "object is not extensible"); + } + assert desc.isDataDescriptor(); + if (desc.hasValue() && JSRuntime.isSameValue(desc.getValue(), value)) { + return true; + } + return DefinePropertyUtil.reject(doThrow, "cannot redefine property"); + } + + /** + * Implementation of [[HasProperty]]. + * This methods also implements the abstract operation 5.1.1.10 RecordHasProperty (R, P). + */ + // TODO: Not sure if this code is reachable according to proposal and ecma262 specs + @Override + public boolean hasOwnProperty(DynamicObject thisObj, Object key) { + assert JSRuntime.isPropertyKey(key); + if (key instanceof Symbol) { + return false; + } + assert key instanceof String; + Record record = valueOf(thisObj); + return record.hasKey((String) key); + } + + /** + * Implementation of [[Get]]. + * + * @return the value associated with the given key + * @see JSClass#get(com.oracle.truffle.api.object.DynamicObject, java.lang.Object) + */ + @Override + public Object getOwnHelper(DynamicObject store, Object thisObj, Object key, Node encapsulatingNode) { + assert JSRuntime.isPropertyKey(key); + if (key instanceof Symbol) { + return null; // will get mapped to Undefined.instance + } + return recordGet(store, key); + } + + /** + * Implementation of [[Set]]. + * + * @return always false + */ + @Override + public boolean set(DynamicObject thisObj, Object key, Object value, Object receiver, boolean isStrict, Node encapsulatingNode) { + assert JSRuntime.isPropertyKey(key); + return false; + } + + @Override + public boolean set(DynamicObject thisObj, long index, Object value, Object receiver, boolean isStrict, Node encapsulatingNode) { + return set(thisObj, String.valueOf(index), value, receiver, isStrict, encapsulatingNode); + } + + /** + * Implementation of [[Delete]]. + */ + @Override + public boolean delete(DynamicObject thisObj, Object key, boolean isStrict) { + assert JSRuntime.isPropertyKey(key); + if (key instanceof Symbol) { + return true; + } + return !hasOwnProperty(thisObj, key); + } + + @Override + public boolean delete(DynamicObject thisObj, long index, boolean isStrict) { + return delete(thisObj, String.valueOf(index), isStrict); + } + + /** + * Implementation of [[OwnPropertyKeys]]. + */ + @Override + public List getOwnPropertyKeys(DynamicObject thisObj, boolean strings, boolean symbols) { + if (!strings) { + return Collections.emptyList(); + } + assert isJSRecord(thisObj); + Record value = valueOf(thisObj); + return new ArrayList<>(value.getKeys()); + } + + /** + * Implementation of the abstract operation RecordGet. + * + * @see 5.1.1.11 RecordGet (R, P) + */ + public Object recordGet(DynamicObject object, Object key) { + assert key instanceof String; + Record record = valueOf(object); + Object value = record.get((String) key); + assert JSRuntime.isJSPrimitive(value); + return value; + } + + @Override + public boolean usesOrdinaryGetOwnProperty() { + return false; + } +} diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSRecordObject.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSRecordObject.java new file mode 100644 index 00000000000..fbb39dd1a67 --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSRecordObject.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.truffle.js.runtime.builtins; + +import com.oracle.truffle.api.object.DynamicObject; +import com.oracle.truffle.api.object.Shape; +import com.oracle.truffle.js.runtime.JSRealm; +import com.oracle.truffle.js.runtime.Record; +import com.oracle.truffle.js.runtime.objects.JSNonProxyObject; + +public final class JSRecordObject extends JSNonProxyObject { + + private final Record value; + + protected JSRecordObject(Shape shape, Record value) { + super(shape); + this.value = value; + } + + public Record getValue() { + return value; + } + + @Override + public String getClassName() { + return JSRecord.CLASS_NAME; + } + + public static DynamicObject create(JSRealm realm, JSObjectFactory factory, Record value) { + return factory.initProto(new JSRecordObject(factory.getShape(realm), value), realm); + } +} diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSTuple.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSTuple.java index b14009456a4..2b2fa36ce93 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSTuple.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSTuple.java @@ -63,8 +63,7 @@ public DynamicObject createPrototype(final JSRealm realm, DynamicObject ctor) { @Override public Shape makeInitialShape(JSContext context, DynamicObject prototype) { - Shape initialShape = JSObjectUtil.getProtoChildShape(prototype, INSTANCE, context); - return initialShape; + return JSObjectUtil.getProtoChildShape(prototype, INSTANCE, context); } public static JSConstructor createConstructor(JSRealm realm) { @@ -118,4 +117,6 @@ public boolean hasProperty(DynamicObject thisObj, long index) { Tuple tuple = ((JSTupleObject) thisObj).getTupleValue(); return tuple.hasElement(index); } + + // TODO: override [[GetOwnProperty]], [[Delete]], etc. } From 052710d7639587e62ff541019a11e5ba20e453db Mon Sep 17 00:00:00 2001 From: Christian Aistleitner Date: Sun, 25 Apr 2021 19:14:55 +0200 Subject: [PATCH 08/31] Fix issues after merge. --- .../js/builtins/ConstructorBuiltins.java | 3 + .../js/builtins/RecordFunctionBuiltins.java | 81 +++++++++++++++++++ .../js/nodes/access/ReadElementNode.java | 5 ++ .../js/nodes/control/DeletePropertyNode.java | 1 + .../oracle/truffle/js/runtime/JSRealm.java | 16 ++++ .../truffle/js/runtime/builtins/JSRecord.java | 23 +++++- 6 files changed, 126 insertions(+), 3 deletions(-) create mode 100644 graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/RecordFunctionBuiltins.java diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/ConstructorBuiltins.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/ConstructorBuiltins.java index 7b8fb2233bc..ac373721278 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/ConstructorBuiltins.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/ConstructorBuiltins.java @@ -331,6 +331,7 @@ public enum Constructor implements BuiltinEnum { JavaImporter(1), // Record and Tuple proposal + Record(0), Tuple(0); private final int length; @@ -630,6 +631,8 @@ protected Object createNode(JSContext context, JSBuiltin builtin, boolean constr ? ConstructWebAssemblyTableNodeGen.create(context, builtin, true, args().newTarget().fixedArgs(1).createArgumentNodes(context)) : ConstructWebAssemblyTableNodeGen.create(context, builtin, false, args().function().fixedArgs(1).createArgumentNodes(context))) : createCallRequiresNew(context, builtin); + case Record: + throw new RuntimeException("TODO"); // TODO case Tuple: return construct ? ConstructTupleNodeGen.create(context, builtin, args().createArgumentNodes(context)) : CallTupleNodeGen.create(context, builtin, args().varArgs().createArgumentNodes(context)); diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/RecordFunctionBuiltins.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/RecordFunctionBuiltins.java new file mode 100644 index 00000000000..0b262d85ca3 --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/RecordFunctionBuiltins.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.truffle.js.builtins; + +import com.oracle.truffle.js.nodes.function.JSBuiltin; +import com.oracle.truffle.js.runtime.JSContext; +import com.oracle.truffle.js.runtime.builtins.BuiltinEnum; +import com.oracle.truffle.js.runtime.builtins.JSRecord; + +/** + * Contains builtins for Record function. + */ +public final class RecordFunctionBuiltins extends JSBuiltinsContainer.SwitchEnum { + + public static final JSBuiltinsContainer BUILTINS = new RecordFunctionBuiltins(); + + protected RecordFunctionBuiltins() { + super(JSRecord.CLASS_NAME, RecordFunction.class); + } + + public enum RecordFunction implements BuiltinEnum { + ; + + private final int length; + + RecordFunction(int length) { + this.length = length; + } + + @Override + public int getLength() { + return length; + } + } + + @Override + protected Object createNode(JSContext context, JSBuiltin builtin, boolean construct, boolean newTarget, RecordFunction builtinEnum) { + switch (builtinEnum) { + // TODO + } + return null; + } +} diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/access/ReadElementNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/access/ReadElementNode.java index 2872e55869a..e4e25321b54 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/access/ReadElementNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/access/ReadElementNode.java @@ -1599,6 +1599,11 @@ protected Object executeWithTargetAndIndexUnchecked(Object target, Object index, @Override protected Object executeWithTargetAndIndexUnchecked(Object target, int index, Object receiver, Object defaultValue, ReadElementNode root) { + return executeWithTargetAndIndexUnchecked(target, (long)index, receiver, defaultValue, root); + } + + @Override + protected Object executeWithTargetAndIndexUnchecked(Object target, long index, Object receiver, Object defaultValue, ReadElementNode root) { Tuple tuple = (Tuple) target; return JSObject.getOrDefault(JSTuple.create(root.context, tuple), index, receiver, defaultValue, jsclassProfile, root); } diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/control/DeletePropertyNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/control/DeletePropertyNode.java index 93125ff0401..54e948f74e4 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/control/DeletePropertyNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/control/DeletePropertyNode.java @@ -57,6 +57,7 @@ import com.oracle.truffle.api.interop.UnsupportedMessageException; import com.oracle.truffle.api.library.CachedLibrary; import com.oracle.truffle.api.nodes.NodeInfo; +import com.oracle.truffle.api.object.DynamicObject; import com.oracle.truffle.api.object.DynamicObjectLibrary; import com.oracle.truffle.api.object.Property; import com.oracle.truffle.api.profiles.ConditionProfile; diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSRealm.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSRealm.java index 5ac7d8c9e59..6a7981eefc3 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSRealm.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSRealm.java @@ -55,6 +55,7 @@ import java.util.SplittableRandom; import java.util.WeakHashMap; +import com.oracle.truffle.js.runtime.builtins.JSRecord; import org.graalvm.collections.Pair; import org.graalvm.home.HomeFinder; import org.graalvm.options.OptionValues; @@ -379,6 +380,8 @@ public class JSRealm { private final DynamicObject foreignIterablePrototype; /** Record and Tuple support. */ + private final DynamicObject recordConstructor; + private final DynamicObject recordPrototype; private final DynamicObject tupleConstructor; private final DynamicObject tuplePrototype; @@ -763,10 +766,15 @@ public JSRealm(JSContext context, TruffleLanguage.Env env) { // TODO: Associate with the correct ECMAScript Version boolean es13 = context.getContextOptions().getEcmaScriptVersion() >= JSConfig.ECMAScript2022; if (es13) { + ctor = JSRecord.createConstructor(this); + this.recordConstructor = ctor.getFunctionObject(); + this.recordPrototype = ctor.getPrototype(); ctor = JSTuple.createConstructor(this); this.tupleConstructor = ctor.getFunctionObject(); this.tuplePrototype = ctor.getPrototype(); } else { + this.recordConstructor = null; + this.recordPrototype = null; this.tupleConstructor = null; this.tuplePrototype= null; } @@ -2468,6 +2476,14 @@ public DynamicObject getForeignIterablePrototype() { return foreignIterablePrototype; } + public DynamicObject getRecordConstructor() { + return recordConstructor; + } + + public DynamicObject getRecordPrototype() { + return recordPrototype; + } + public DynamicObject getTupleConstructor() { return tupleConstructor; } diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSRecord.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSRecord.java index a835f678a0d..71f1cabbcf2 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSRecord.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSRecord.java @@ -43,11 +43,14 @@ import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.object.DynamicObject; import com.oracle.truffle.api.object.Shape; +import com.oracle.truffle.js.builtins.RecordFunctionBuiltins; import com.oracle.truffle.js.runtime.JSContext; import com.oracle.truffle.js.runtime.JSRealm; import com.oracle.truffle.js.runtime.JSRuntime; import com.oracle.truffle.js.runtime.Record; import com.oracle.truffle.js.runtime.Symbol; +import com.oracle.truffle.js.runtime.objects.JSDynamicObject; +import com.oracle.truffle.js.runtime.objects.JSObject; import com.oracle.truffle.js.runtime.objects.JSObjectUtil; import com.oracle.truffle.js.runtime.objects.JSShape; import com.oracle.truffle.js.runtime.objects.PropertyDescriptor; @@ -112,8 +115,11 @@ public DynamicObject createPrototype(JSRealm realm, DynamicObject constructor) { @Override public DynamicObject getIntrinsicDefaultProto(JSRealm realm) { - // TODO: return realm.getRecordPrototype(); - return null; + return realm.getRecordPrototype(); + } + + public static JSConstructor createConstructor(JSRealm realm) { + return INSTANCE.createConstructorAndPrototype(realm, RecordFunctionBuiltins.BUILTINS); } /** @@ -126,9 +132,20 @@ public DynamicObject getIntrinsicDefaultProto(JSRealm realm) { */ @Override public Shape makeInitialShape(JSContext context, DynamicObject prototype) { - return JSShape.newBuilder(context, INSTANCE, prototype) + Shape initialShape = Shape.newBuilder() + .layout(JSShape.getLayout(INSTANCE)) + .dynamicType(INSTANCE) + .addConstantProperty(JSObject.HIDDEN_PROTO, prototype, 0) .shapeFlags(JSShape.NOT_EXTENSIBLE_FLAG) .build(); + + assert !JSShape.isExtensible(initialShape); + return initialShape; + + // TODO: remove commented out code below + // return JSShape.newBuilder(context, INSTANCE, prototype) + // .shapeFlags(JSShape.NOT_EXTENSIBLE_FLAG) + // .build(); } /** From dd9b5d934d977c4408b6e2c9625edefe3ae3529d Mon Sep 17 00:00:00 2001 From: Christian Aistleitner Date: Sun, 25 Apr 2021 21:51:38 +0200 Subject: [PATCH 09/31] Add isRecordAndTupleEnabled-method for hiding functionality. --- .../com/oracle/truffle/js/runtime/JSContext.java | 14 ++++++++++++-- .../src/com/oracle/truffle/js/runtime/JSRealm.java | 7 +++---- .../truffle/js/runtime/builtins/JSRecord.java | 1 - 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSContext.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSContext.java index 0a19ac06266..ed640ec8e17 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSContext.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSContext.java @@ -574,8 +574,13 @@ protected JSContext(Evaluator evaluator, JSContextOptions contextOptions, JavaSc this.webAssemblyTableFactory = builder.create(JSWebAssemblyTable.INSTANCE); this.webAssemblyGlobalFactory = builder.create(JSWebAssemblyGlobal.INSTANCE); - this.recordFactory = builder.create(JSRecord.INSTANCE); - this.tupleFactory = builder.create(JSTuple.INSTANCE); + if (isRecordAndTupleEnabled()) { + this.recordFactory = builder.create(JSRecord.INSTANCE); + this.tupleFactory = builder.create(JSTuple.INSTANCE); + } else { + this.recordFactory = null; + this.tupleFactory = null; + } this.factoryCount = builder.finish(); @@ -1732,4 +1737,9 @@ public boolean isOptionTopLevelAwait() { public boolean isWaitAsyncEnabled() { return getEcmaScriptVersion() >= JSConfig.ECMAScript2022; } + + public boolean isRecordAndTupleEnabled() { + // TODO: Associate with the correct ECMAScript Version + return getEcmaScriptVersion() >= JSConfig.ECMAScript2022; + } } diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSRealm.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSRealm.java index 6a7981eefc3..1f9c33f86b2 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSRealm.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSRealm.java @@ -763,9 +763,7 @@ public JSRealm(JSContext context, TruffleLanguage.Env env) { this.foreignIterablePrototype = createForeignIterablePrototype(); - // TODO: Associate with the correct ECMAScript Version - boolean es13 = context.getContextOptions().getEcmaScriptVersion() >= JSConfig.ECMAScript2022; - if (es13) { + if (context.isRecordAndTupleEnabled()) { ctor = JSRecord.createConstructor(this); this.recordConstructor = ctor.getFunctionObject(); this.recordPrototype = ctor.getPrototype(); @@ -1436,7 +1434,8 @@ public void setupGlobals() { if (context.getContextOptions().isProfileTime()) { System.out.println("SetupGlobals: " + (System.nanoTime() - time) / 1000000); } - if (context.getEcmaScriptVersion() >= 13) { // TODO: Associate with the correct ECMAScript Version + if (context.isRecordAndTupleEnabled()) { + putGlobalProperty(JSRecord.CLASS_NAME, getRecordConstructor()); putGlobalProperty(JSTuple.CLASS_NAME, getTupleConstructor()); } } diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSRecord.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSRecord.java index 71f1cabbcf2..2f6b9da6657 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSRecord.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSRecord.java @@ -49,7 +49,6 @@ import com.oracle.truffle.js.runtime.JSRuntime; import com.oracle.truffle.js.runtime.Record; import com.oracle.truffle.js.runtime.Symbol; -import com.oracle.truffle.js.runtime.objects.JSDynamicObject; import com.oracle.truffle.js.runtime.objects.JSObject; import com.oracle.truffle.js.runtime.objects.JSObjectUtil; import com.oracle.truffle.js.runtime.objects.JSShape; From 05705eb20af220cc04ba56b38a323fb851e32ee5 Mon Sep 17 00:00:00 2001 From: Christian Aistleitner Date: Mon, 26 Apr 2021 15:17:33 +0200 Subject: [PATCH 10/31] Add JavaDoc to type classes. --- .../src/com/oracle/truffle/js/runtime/Record.java | 6 ++++++ .../src/com/oracle/truffle/js/runtime/Tuple.java | 6 ++++++ .../com/oracle/truffle/js/runtime/builtins/JSRecord.java | 8 ++++++-- .../truffle/js/runtime/builtins/JSRecordObject.java | 7 +++++++ .../com/oracle/truffle/js/runtime/builtins/JSTuple.java | 7 +++++++ .../oracle/truffle/js/runtime/builtins/JSTupleObject.java | 7 +++++++ 6 files changed, 39 insertions(+), 2 deletions(-) diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Record.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Record.java index 40c91fa214b..42a70609829 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Record.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Record.java @@ -49,6 +49,12 @@ import java.util.Set; import java.util.TreeMap; +/** + * Implementation of the Record (primitive) type which is a mapping from Strings to ECMAScript primitive values. + * Since a Record value is completely immutable and will not change over time, you will not find a setter-method here. + * + * @see com.oracle.truffle.js.runtime.builtins.JSRecordObject + */ @ExportLibrary(InteropLibrary.class) @ValueType public final class Record implements TruffleObject { diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Tuple.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Tuple.java index 78ee1f956b2..b8997fa0216 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Tuple.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Tuple.java @@ -15,6 +15,12 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; +/** + * Implementation of the Tuple (primitive) type which is an ordered sequence of ECMAScript primitive values. + * Since a Tuple value is completely immutable and will not change over time, you will not find a setter-method here. + * + * @see com.oracle.truffle.js.runtime.builtins.JSTupleObject + */ @ExportLibrary(InteropLibrary.class) @ValueType public final class Tuple implements TruffleObject { diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSRecord.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSRecord.java index 2f6b9da6657..aaf2f720b9f 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSRecord.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSRecord.java @@ -60,9 +60,11 @@ import java.util.List; /** - * Implementation of Record Exotic Object + * Implementation of the Record (exotic) object internal methods and properties. + * See Records & Tuples Proposal: 5.1.1 Record Exotic Objects * - * @see 5.1.1 Record Exotic Objects + * @see JSRecordObject + * @see Record */ public class JSRecord extends JSNonProxy implements JSConstructorFactory.Default.WithFunctions, PrototypeSupplier { @@ -124,6 +126,8 @@ public static JSConstructor createConstructor(JSRealm realm) { /** * Records aren't extensible, thus [[IsExtensible]] must always return false. * + * https://tc39.es/ecma262/#sec-immutable-prototype-exotic-objects + * * TODO: see Ref below for ideas * @see JSModuleNamespace#makeInitialShape(com.oracle.truffle.js.runtime.JSContext) * diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSRecordObject.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSRecordObject.java index fbb39dd1a67..3dd5fb6709c 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSRecordObject.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSRecordObject.java @@ -46,6 +46,13 @@ import com.oracle.truffle.js.runtime.Record; import com.oracle.truffle.js.runtime.objects.JSNonProxyObject; +/** + * A Record object is an exotic object that encapsulates a Record value. + * This class implements this wrapper and thus serves as adapter from a primitive Record value to an object type. + * + * @see JSRecord + * @see Record + */ public final class JSRecordObject extends JSNonProxyObject { private final Record value; diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSTuple.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSTuple.java index 2b2fa36ce93..cd374962486 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSTuple.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSTuple.java @@ -19,6 +19,13 @@ import static com.oracle.truffle.js.runtime.objects.JSObjectUtil.putDataProperty; +/** + * Implementation of the Tuple (exotic) object internal methods and properties. + * See Records & Tuples Proposal: 5.1.2 Tuple Exotic Objects + * + * @see JSTupleObject + * @see Tuple + */ public final class JSTuple extends JSNonProxy implements JSConstructorFactory.Default.WithFunctions, PrototypeSupplier { public static final String TYPE_NAME = "tuple"; diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSTupleObject.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSTupleObject.java index 0183df511b9..f008effe9f8 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSTupleObject.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSTupleObject.java @@ -6,6 +6,13 @@ import com.oracle.truffle.js.runtime.Tuple; import com.oracle.truffle.js.runtime.objects.JSNonProxyObject; +/** + * A Tuple object is an exotic object that encapsulates a Tuple value. + * This class implements this wrapper and thus serves as adapter from a primitive Tuple value to an object type. + * + * @see JSTuple + * @see Tuple + */ public final class JSTupleObject extends JSNonProxyObject { private final Tuple value; From 1f2b8a60f6831595bc08ffcf09183680269b9052 Mon Sep 17 00:00:00 2001 From: Christian Aistleitner Date: Tue, 27 Apr 2021 15:25:41 +0200 Subject: [PATCH 11/31] Add record type conversion. --- .../js/record-tuple/record.js | 87 ++++++++++++++----- .../truffle/js/nodes/cast/JSToBigIntNode.java | 6 ++ .../js/nodes/cast/JSToBooleanNode.java | 6 ++ .../js/nodes/cast/JSToBooleanUnaryNode.java | 6 ++ .../truffle/js/nodes/cast/JSToDoubleNode.java | 7 ++ .../truffle/js/nodes/cast/JSToInt32Node.java | 14 ++- .../js/nodes/cast/JSToIntegerAsIntNode.java | 14 ++- .../js/nodes/cast/JSToIntegerAsLongNode.java | 14 ++- .../nodes/cast/JSToIntegerOrInfinityNode.java | 12 +++ .../truffle/js/nodes/cast/JSToNumberNode.java | 7 ++ .../js/nodes/cast/JSToPrimitiveNode.java | 6 ++ .../truffle/js/nodes/cast/JSToStringNode.java | 9 +- .../js/nodes/cast/JSToStringOrNumberNode.java | 16 ++-- .../truffle/js/nodes/cast/JSToUInt32Node.java | 18 +++- .../oracle/truffle/js/runtime/JSRuntime.java | 33 +++++-- .../truffle/js/runtime/builtins/JSRecord.java | 1 + 16 files changed, 206 insertions(+), 50 deletions(-) diff --git a/graal-js/src/com.oracle.truffle.js.test/js/record-tuple/record.js b/graal-js/src/com.oracle.truffle.js.test/js/record-tuple/record.js index e768e7cee4a..8b0d27505f2 100644 --- a/graal-js/src/com.oracle.truffle.js.test/js/record-tuple/record.js +++ b/graal-js/src/com.oracle.truffle.js.test/js/record-tuple/record.js @@ -77,27 +77,28 @@ assertSame(false, b.age.configurable); */ // TODO: Not sure if [[DefineOwnProperty]] is reachable according to proposal and ecma262 specs // TODO: Code below should work according to polyfill, but shouldn't according to specs -// a = #{ age: 22 }; -// Object.defineProperty(a, "age", { value: 22 }); -// Object.defineProperty(a, "age", { value: 22, writable: false, enumerable: true, configurable: false }); -// assertThrows(function() { -// eval(`Object.defineProperty(a, "age", { value: 21 });`); // value must be the same -// }, TypeError); -// assertThrows(function() { -// eval(`Object.defineProperty(a, "age", { writable: false, enumerable: true, configurable: false });`); // value must be provided -// }, TypeError); -// assertThrows(function() { -// eval(`Object.defineProperty(a, "age", { value: 22, writable: true });`); // writeable must always be false -// }, TypeError); -// assertThrows(function() { -// eval(`Object.defineProperty(a, "age", { value: 22, enumerable: false });`); // enumerable must always be true -// }, TypeError); -// assertThrows(function() { -// eval(`Object.defineProperty(a, "age", { value: 22, configurable: true });`); // configurable must always be false -// }, TypeError); -// assertThrows(function() { -// eval(`Object.defineProperty(a, Symbol("42"), { value: 22 });`); // Symbols are not allowed -// }, TypeError); +a = #{ age: 22 }; +a = Object(a); // TODO: This workaround allows testing [[DefineOwnProperty]] +Object.defineProperty(a, "age", { value: 22 }); +Object.defineProperty(a, "age", { value: 22, writable: false, enumerable: true, configurable: false }); +assertThrows(function() { + eval(`Object.defineProperty(a, "age", { value: 21 });`); // value must be the same +}, TypeError); +assertThrows(function() { + eval(`Object.defineProperty(a, "age", { writable: false, enumerable: true, configurable: false });`); // value must be provided +}, TypeError); +assertThrows(function() { + eval(`Object.defineProperty(a, "age", { value: 22, writable: true });`); // writeable must always be false +}, TypeError); +assertThrows(function() { + eval(`Object.defineProperty(a, "age", { value: 22, enumerable: false });`); // enumerable must always be true +}, TypeError); +assertThrows(function() { + eval(`Object.defineProperty(a, "age", { value: 22, configurable: true });`); // configurable must always be false +}, TypeError); +assertThrows(function() { + eval(`Object.defineProperty(a, Symbol("42"), { value: 22 });`); // Symbols are not allowed +}, TypeError); /* * Test 9: @@ -107,3 +108,47 @@ a = #{ age: 22 }; assertSame(false, delete a.age); assertSame(true, delete a.unknown); assertSame(true, delete a[Symbol("test")]); + +/* + * Test 10: + * Type Conversion + */ +a = #{ age: 22 }; +// 3.1.1 ToBoolean +assertSame(true, !!a); // JSToBigIntNode +if (a) { // JSToBooleanUnaryNode + // ok +} else { + fail("#{ age: 22 } should be true") +} +// 3.1.2 ToNumber +assertThrows(function() { + eval("a + 1"); // JSToNumberNode +}, TypeError); +assertThrows(function() { + eval("Math.abs(a)"); // JSToDoubleNode +}, TypeError); +assertThrows(function() { + eval("parseInt(\"1\", a)"); // JSToInt32Node +}, TypeError); +assertThrows(function() { + eval("'ABC'.codePointAt(a)"); // JSToIntegerAsIntNode +}, TypeError); +assertThrows(function() { + eval("[1].at(a)"); // JSToIntegerAsLongNode +}, TypeError); +assertThrows(function() { + eval("'ABC'.split('', a);"); // JSToUInt32Node +}, TypeError); +// 3.1.3 ToBigInt +assertThrows(function() { + eval("BigInt.asIntN(64, a)"); +}, TypeError); +// 3.1.4 ToString +assertSame("[object Record]", a + ""); // JSToStringNode +assertSame(false, a < #{}); // JSToStringOrNumberNode +assertSame(true, a <= #{}); +assertSame(true, a >= #{}); +assertSame(false, a > #{}); +// 3.1.5 ToObject +assertSame("object", typeof Object(a)); // JSToObjectNode diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToBigIntNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToBigIntNode.java index 30552d7ea3b..30bbd985e84 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToBigIntNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToBigIntNode.java @@ -47,6 +47,7 @@ import com.oracle.truffle.js.runtime.BigInt; import com.oracle.truffle.js.runtime.Errors; import com.oracle.truffle.js.runtime.JSErrorType; +import com.oracle.truffle.js.runtime.Record; import com.oracle.truffle.js.runtime.Symbol; import com.oracle.truffle.js.runtime.Tuple; @@ -102,6 +103,11 @@ protected static BigInt doSymbol(Symbol value) { throw Errors.createErrorCanNotConvertToBigInt(JSErrorType.TypeError, value); } + @Specialization + protected static BigInt doRecord(Record value) { + throw Errors.createErrorCanNotConvertToBigInt(JSErrorType.TypeError, value); + } + @Specialization protected static BigInt doTuple(Tuple value) { throw Errors.createErrorCanNotConvertToBigInt(JSErrorType.TypeError, value); diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToBooleanNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToBooleanNode.java index db9385ae9d6..2795230ccbe 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToBooleanNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToBooleanNode.java @@ -50,6 +50,7 @@ import com.oracle.truffle.js.runtime.BigInt; import com.oracle.truffle.js.runtime.Errors; import com.oracle.truffle.js.runtime.JSConfig; +import com.oracle.truffle.js.runtime.Record; import com.oracle.truffle.js.runtime.Symbol; import com.oracle.truffle.js.runtime.Tuple; import com.oracle.truffle.js.runtime.objects.JSLazyString; @@ -125,6 +126,11 @@ protected static boolean doSymbol(@SuppressWarnings("unused") Symbol value) { return true; } + @Specialization + protected static boolean doRecord(@SuppressWarnings("unused") Record value) { + return true; + } + @Specialization protected static boolean doTuple(@SuppressWarnings("unused") Tuple value) { return true; diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToBooleanUnaryNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToBooleanUnaryNode.java index e4603d845b8..5be8ba46f15 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToBooleanUnaryNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToBooleanUnaryNode.java @@ -55,6 +55,7 @@ import com.oracle.truffle.js.runtime.BigInt; import com.oracle.truffle.js.runtime.JSConfig; import com.oracle.truffle.js.runtime.JSRuntime; +import com.oracle.truffle.js.runtime.Record; import com.oracle.truffle.js.runtime.Symbol; import com.oracle.truffle.js.runtime.Tuple; import com.oracle.truffle.js.runtime.objects.JSLazyString; @@ -156,6 +157,11 @@ protected static boolean doSymbol(@SuppressWarnings("unused") Symbol value) { return true; } + @Specialization + protected static boolean doRecord(@SuppressWarnings("unused") Record value) { + return true; + } + @Specialization protected static boolean doTuple(@SuppressWarnings("unused") Tuple value) { return true; diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToDoubleNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToDoubleNode.java index 1ddca0e3a18..74cf8ef9dde 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToDoubleNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToDoubleNode.java @@ -48,12 +48,14 @@ import com.oracle.truffle.js.runtime.BigInt; import com.oracle.truffle.js.runtime.Errors; import com.oracle.truffle.js.runtime.JSRuntime; +import com.oracle.truffle.js.runtime.Record; import com.oracle.truffle.js.runtime.Symbol; import com.oracle.truffle.js.runtime.Tuple; /** * This implements ECMA 9.3 ToNumber, but always converting the result to a double value. * + * @see JSToNumberNode */ public abstract class JSToDoubleNode extends JavaScriptBaseNode { @@ -114,6 +116,11 @@ protected final double doSymbol(@SuppressWarnings("unused") Symbol value) { throw Errors.createTypeErrorCannotConvertToNumber("a Symbol value", this); } + @Specialization + protected final double doRecord(@SuppressWarnings("unused") Record value) { + throw Errors.createTypeErrorCannotConvertToNumber("a Record value", this); + } + @Specialization protected final double doTuple(@SuppressWarnings("unused") Tuple value) { throw Errors.createTypeErrorCannotConvertToNumber("a Tuple value", this); diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToInt32Node.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToInt32Node.java index f3cb741ad6a..066fef625f4 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToInt32Node.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToInt32Node.java @@ -55,6 +55,7 @@ import com.oracle.truffle.js.runtime.BigInt; import com.oracle.truffle.js.runtime.Errors; import com.oracle.truffle.js.runtime.JSRuntime; +import com.oracle.truffle.js.runtime.Record; import com.oracle.truffle.js.runtime.SafeInteger; import com.oracle.truffle.js.runtime.Symbol; import com.oracle.truffle.js.runtime.Tuple; @@ -188,13 +189,18 @@ protected final int doSymbol(@SuppressWarnings("unused") Symbol value) { } @Specialization - protected final int doTuple(@SuppressWarnings("unused") Tuple value) { - throw Errors.createTypeErrorCannotConvertToNumber("a Tuple value", this); + protected int doBigInt(@SuppressWarnings("unused") BigInt value) { + throw Errors.createTypeErrorCannotConvertBigIntToNumber(this); } @Specialization - protected int doBigInt(@SuppressWarnings("unused") BigInt value) { - throw Errors.createTypeErrorCannotConvertBigIntToNumber(this); + protected int doRecord(@SuppressWarnings("unused") Record value) { + throw Errors.createTypeErrorCannotConvertToNumber("a Record value", this); + } + + @Specialization + protected int doTuple(@SuppressWarnings("unused") Tuple value) { + throw Errors.createTypeErrorCannotConvertToNumber("a Tuple value", this); } @Specialization(guards = "isJSObject(value)") diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToIntegerAsIntNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToIntegerAsIntNode.java index f77b65ee5c7..fa79b5114e7 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToIntegerAsIntNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToIntegerAsIntNode.java @@ -48,6 +48,7 @@ import com.oracle.truffle.js.runtime.BigInt; import com.oracle.truffle.js.runtime.Errors; import com.oracle.truffle.js.runtime.JSRuntime; +import com.oracle.truffle.js.runtime.Record; import com.oracle.truffle.js.runtime.SafeInteger; import com.oracle.truffle.js.runtime.Symbol; import com.oracle.truffle.js.runtime.Tuple; @@ -130,13 +131,18 @@ protected final int doSymbol(@SuppressWarnings("unused") Symbol value) { } @Specialization - protected final int doTuple(@SuppressWarnings("unused") Tuple value) { - throw Errors.createTypeErrorCannotConvertToNumber("a Tuple value", this); + protected final int doBigInt(@SuppressWarnings("unused") BigInt value) { + throw Errors.createTypeErrorCannotConvertToNumber("a BigInt value", this); } @Specialization - protected final int doBigInt(@SuppressWarnings("unused") BigInt value) { - throw Errors.createTypeErrorCannotConvertToNumber("a BigInt value", this); + protected final int doRecord(@SuppressWarnings("unused") Record value) { + throw Errors.createTypeErrorCannotConvertToNumber("a Record value", this); + } + + @Specialization + protected final int doTuple(@SuppressWarnings("unused") Tuple value) { + throw Errors.createTypeErrorCannotConvertToNumber("a Tuple value", this); } @Specialization diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToIntegerAsLongNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToIntegerAsLongNode.java index 7350b397c80..2fac0125998 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToIntegerAsLongNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToIntegerAsLongNode.java @@ -48,6 +48,7 @@ import com.oracle.truffle.js.runtime.BigInt; import com.oracle.truffle.js.runtime.Errors; import com.oracle.truffle.js.runtime.JSRuntime; +import com.oracle.truffle.js.runtime.Record; import com.oracle.truffle.js.runtime.SafeInteger; import com.oracle.truffle.js.runtime.Symbol; import com.oracle.truffle.js.runtime.Tuple; @@ -111,13 +112,18 @@ protected final long doSymbol(@SuppressWarnings("unused") Symbol value) { } @Specialization - protected final long doTuple(@SuppressWarnings("unused") Tuple value) { - throw Errors.createTypeErrorCannotConvertToNumber("a Tuple value", this); + protected final long doBigInt(@SuppressWarnings("unused") BigInt value) { + throw Errors.createTypeErrorCannotConvertToNumber("a BigInt value", this); } @Specialization - protected final long doBigInt(@SuppressWarnings("unused") BigInt value) { - throw Errors.createTypeErrorCannotConvertToNumber("a BigInt value", this); + protected final long doRecord(@SuppressWarnings("unused") Record value) { + throw Errors.createTypeErrorCannotConvertToNumber("a Record value", this); + } + + @Specialization + protected final long doTuple(@SuppressWarnings("unused") Tuple value) { + throw Errors.createTypeErrorCannotConvertToNumber("a Tuple value", this); } @Specialization diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToIntegerOrInfinityNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToIntegerOrInfinityNode.java index cdb55ae67bf..34550966b1c 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToIntegerOrInfinityNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToIntegerOrInfinityNode.java @@ -48,8 +48,10 @@ import com.oracle.truffle.js.runtime.BigInt; import com.oracle.truffle.js.runtime.Errors; import com.oracle.truffle.js.runtime.JSRuntime; +import com.oracle.truffle.js.runtime.Record; import com.oracle.truffle.js.runtime.SafeInteger; import com.oracle.truffle.js.runtime.Symbol; +import com.oracle.truffle.js.runtime.Tuple; /** * This implements ECMA2022 7.1.5 ToIntegerOrInfinity. @@ -117,6 +119,16 @@ protected final Number doBigInt(@SuppressWarnings("unused") BigInt value) { throw Errors.createTypeErrorCannotConvertToNumber("a BigInt value", this); } + @Specialization + protected final long doRecord(@SuppressWarnings("unused") Record value) { + throw Errors.createTypeErrorCannotConvertToNumber("a Record value", this); + } + + @Specialization + protected final long doTuple(@SuppressWarnings("unused") Tuple value) { + throw Errors.createTypeErrorCannotConvertToNumber("a Tuple value", this); + } + @Specialization protected Number doString(String value, @Cached.Shared("recToIntOrInf") @Cached("create()") JSToIntegerOrInfinityNode toIntOrInf, diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToNumberNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToNumberNode.java index 8e81a119e4f..b48342299ea 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToNumberNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToNumberNode.java @@ -52,6 +52,7 @@ import com.oracle.truffle.js.runtime.BigInt; import com.oracle.truffle.js.runtime.Errors; import com.oracle.truffle.js.runtime.JSRuntime; +import com.oracle.truffle.js.runtime.Record; import com.oracle.truffle.js.runtime.Symbol; import com.oracle.truffle.js.runtime.Tuple; @@ -60,6 +61,7 @@ /** * This implements ECMA 9.3 ToNumber. * + * @see JSToDoubleNode */ public abstract class JSToNumberNode extends JavaScriptBaseNode { @@ -130,6 +132,11 @@ protected final Number doBigInt(@SuppressWarnings("unused") BigInt value) { throw Errors.createTypeErrorCannotConvertToNumber("a BigInt value", this); } + @Specialization + protected final Number doRecord(@SuppressWarnings("unused") Record value) { + throw Errors.createTypeErrorCannotConvertToNumber("a Record value", this); + } + @Specialization protected final Number doTuple(@SuppressWarnings("unused") Tuple value) { throw Errors.createTypeErrorCannotConvertToNumber("a Tuple value", this); diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToPrimitiveNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToPrimitiveNode.java index 51d444e162d..0f81f98345a 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToPrimitiveNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToPrimitiveNode.java @@ -69,6 +69,7 @@ import com.oracle.truffle.js.runtime.JSContext; import com.oracle.truffle.js.runtime.JSRealm; import com.oracle.truffle.js.runtime.JSRuntime; +import com.oracle.truffle.js.runtime.Record; import com.oracle.truffle.js.runtime.SafeInteger; import com.oracle.truffle.js.runtime.Symbol; import com.oracle.truffle.js.runtime.Tuple; @@ -157,6 +158,11 @@ protected BigInt doBigInt(BigInt value) { return value; } + @Specialization + protected Record doRecord(Record value) { + return value; + } + @Specialization protected Tuple doTuple(Tuple value) { return value; diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToStringNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToStringNode.java index 487107825c1..4ce8bf501c4 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToStringNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToStringNode.java @@ -57,6 +57,7 @@ import com.oracle.truffle.js.runtime.Boundaries; import com.oracle.truffle.js.runtime.Errors; import com.oracle.truffle.js.runtime.JSRuntime; +import com.oracle.truffle.js.runtime.Record; import com.oracle.truffle.js.runtime.Symbol; import com.oracle.truffle.js.runtime.Tuple; import com.oracle.truffle.js.runtime.objects.JSLazyString; @@ -162,10 +163,14 @@ protected String doSymbol(Symbol value) { } } + @Specialization + protected String doRecord(Record value) { + return JSRuntime.recordToString(value); + } + @Specialization protected String doTuple(Tuple value) { - // TODO: abstract operation "TupleToString" ? - return value.toString(); + return JSRuntime.tupleToString(value); } @Specialization(guards = {"isForeignObject(object)"}) diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToStringOrNumberNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToStringOrNumberNode.java index 0f285879a5e..320f9cbda29 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToStringOrNumberNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToStringOrNumberNode.java @@ -47,6 +47,7 @@ import com.oracle.truffle.js.runtime.BigInt; import com.oracle.truffle.js.runtime.Errors; import com.oracle.truffle.js.runtime.JSRuntime; +import com.oracle.truffle.js.runtime.Record; import com.oracle.truffle.js.runtime.SafeInteger; import com.oracle.truffle.js.runtime.Symbol; import com.oracle.truffle.js.runtime.Tuple; @@ -112,11 +113,6 @@ protected Object doSymbol(@SuppressWarnings("unused") Symbol value) { throw Errors.createTypeErrorCannotConvertToNumber("a Symbol value", this); } - @Specialization - protected String doTuple(Tuple value, @Cached("create()") JSToStringNode toStringNode) { - return toStringNode.executeString(value); - } - @Specialization(guards = "isUndefined(value)") protected double doUndefined(@SuppressWarnings("unused") Object value) { return Double.NaN; @@ -126,4 +122,14 @@ protected double doUndefined(@SuppressWarnings("unused") Object value) { protected static BigInt doBigInt(BigInt value) { return value; } + + @Specialization + protected String doRecord(Record value, @Cached("create()") JSToStringNode toStringNode) { + return toStringNode.executeString(value); + } + + @Specialization + protected String doTuple(Tuple value, @Cached("create()") JSToStringNode toStringNode) { + return toStringNode.executeString(value); + } } diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToUInt32Node.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToUInt32Node.java index cad32477a51..80d622e2312 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToUInt32Node.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToUInt32Node.java @@ -59,6 +59,7 @@ import com.oracle.truffle.js.runtime.BigInt; import com.oracle.truffle.js.runtime.Errors; import com.oracle.truffle.js.runtime.JSRuntime; +import com.oracle.truffle.js.runtime.Record; import com.oracle.truffle.js.runtime.SafeInteger; import com.oracle.truffle.js.runtime.Symbol; import com.oracle.truffle.js.runtime.Tuple; @@ -151,13 +152,22 @@ protected final Number doSymbol(@SuppressWarnings("unused") Symbol value) { } @Specialization - protected final Number doTuple(@SuppressWarnings("unused") Tuple value) { - throw Errors.createTypeErrorCannotConvertToNumber("a Tuple value", this); + protected int doBigInt(@SuppressWarnings("unused") BigInt value) { + throw Errors.createTypeErrorCannotConvertBigIntToNumber(this); } + // TODO: Notes: + // TODO: Why isn't this class implemented like JSToUInt16Node? + // TODO: We could remove the duplicated code by calling JSToNumberNode in a doGeneric Specification. + // TODO: See also https://tc39.es/ecma262/#sec-touint32 @Specialization - protected int doBigInt(@SuppressWarnings("unused") BigInt value) { - throw Errors.createTypeErrorCannotConvertBigIntToNumber(this); + protected final Number doRecord(@SuppressWarnings("unused") Record value) { + throw Errors.createTypeErrorCannotConvertToNumber("a Record value", this); + } + + @Specialization + protected final Number doTuple(@SuppressWarnings("unused") Tuple value) { + throw Errors.createTypeErrorCannotConvertToNumber("a Tuple value", this); } @Specialization(guards = "isJSObject(value)") diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSRuntime.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSRuntime.java index eb593a048b4..62c1cf904ee 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSRuntime.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSRuntime.java @@ -492,10 +492,12 @@ public static Number toNumberFromPrimitive(Object value) { return stringToNumber(value.toString()); } else if (value instanceof Symbol) { throw Errors.createTypeErrorCannotConvertToNumber("a Symbol value"); - } else if (value instanceof Tuple) { - throw Errors.createTypeErrorCannotConvertToNumber("a Tuple value"); } else if (value instanceof BigInt) { throw Errors.createTypeErrorCannotConvertToNumber("a BigInt value"); + } else if (value instanceof Record) { + throw Errors.createTypeErrorCannotConvertToNumber("a Record value"); + } else if (value instanceof Tuple) { + throw Errors.createTypeErrorCannotConvertToNumber("a Tuple value"); } else if (value instanceof Number) { assert isJavaPrimitive(value) : value.getClass().getName(); return (Number) value; @@ -926,10 +928,12 @@ public static String toString(Object value) { return numberToString((Number) value); } else if (value instanceof Symbol) { throw Errors.createTypeErrorCannotConvertToString("a Symbol value"); - } else if (value instanceof Tuple) { - return value.toString(); } else if (value instanceof BigInt) { return value.toString(); + } else if (value instanceof Record) { + return recordToString((Record) value); + } else if (value instanceof Tuple) { + return tupleToString((Tuple) value); } else if (JSDynamicObject.isJSDynamicObject(value)) { return toString(JSObject.toPrimitive((DynamicObject) value, HINT_STRING)); } else if (value instanceof TruffleObject) { @@ -981,10 +985,12 @@ private static String toDisplayStringImpl(Object value, int depth, Object parent return JSObject.toDisplayString((DynamicObject) value, depth, allowSideEffects); } else if (value instanceof Symbol) { return value.toString(); - } else if (value instanceof Tuple) { - return value.toString(); } else if (value instanceof BigInt) { return value.toString() + 'n'; + } else if (value instanceof Record) { + return recordToString((Record) value); + } else if (value instanceof Tuple) { + return tupleToString((Tuple) value); } else if (isNumber(value)) { Number number = (Number) value; if (JSRuntime.isNegativeZero(number.doubleValue())) { @@ -1330,6 +1336,21 @@ public static String booleanToString(boolean value) { return value ? JSBoolean.TRUE_NAME : JSBoolean.FALSE_NAME; } + /** + * Record & Tuple Proposal 2.1.1.1 RecordToString + */ + public static String recordToString(@SuppressWarnings("unused") Record value) { + return JSRecord.STRING_NAME; + } + + /** + * Record & Tuple Proposal 2.1.2.1 TupleToString + */ + public static String tupleToString(Tuple value) { + // TODO: Return ? Call(%Array.prototype.join%, argument, « »). + return value.toString(); + } + public static String toString(DynamicObject value) { if (value == Undefined.instance) { return Undefined.NAME; diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSRecord.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSRecord.java index aaf2f720b9f..c73da8082bb 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSRecord.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSRecord.java @@ -71,6 +71,7 @@ public class JSRecord extends JSNonProxy implements JSConstructorFactory.Default public static final String TYPE_NAME = "record"; public static final String CLASS_NAME = "Record"; public static final String PROTOTYPE_NAME = "Record.prototype"; + public static final String STRING_NAME = "[object Record]"; public static final JSRecord INSTANCE = new JSRecord(); From 8c0c2939ed760c884b9b075c1c915a923c17da15 Mon Sep 17 00:00:00 2001 From: Christian Aistleitner Date: Thu, 29 Apr 2021 11:26:18 +0200 Subject: [PATCH 12/31] Remove tuple sub-class TODOs. --- .../js/builtins/ConstructorBuiltins.java | 24 +----- .../js/nodes/tuples/TupleLiteralNode.java | 78 ++----------------- .../com/oracle/truffle/js/runtime/Tuple.java | 26 +------ 3 files changed, 12 insertions(+), 116 deletions(-) diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/ConstructorBuiltins.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/ConstructorBuiltins.java index ac373721278..83ec9bde0f7 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/ConstructorBuiltins.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/ConstructorBuiltins.java @@ -2766,34 +2766,14 @@ protected Object constructEmptyTuple(@SuppressWarnings("unused") Object[] args) return Tuple.create(); } - // TODO: maybe use @Fallback here @Specialization(guards = {"args.length != 0"}) - protected Object constructTuple(Object[] args, - @Cached("create()") BranchProfile isByteCase, - @Cached("create()") BranchProfile isIntegerCase, - @Cached("create()") BranchProfile isDoubleCase, - @Cached("create()") BranchProfile isObjectCase) { + protected Object constructTuple(Object[] args) { for (Object element : args) { if (!JSRuntime.isJSPrimitive(element)) { throw Errors.createTypeError("Tuples cannot contain non-primitive values"); } } - // TODO: maybe use JSToPrimitiveNode as CallBigIntNode does - // TODO: Tuples cannot contain holes, thus ArrayLiteralNode.identifyPrimitiveContentType might not be the best method for classifying the array content - ArrayContentType type = ArrayLiteralNode.identifyPrimitiveContentType(args, true); - if (type == ArrayContentType.Byte) { - isByteCase.enter(); - return Tuple.create(ArrayLiteralNode.createByteArray(args)); - } else if (type == ArrayContentType.Integer) { - isIntegerCase.enter(); - return Tuple.create(ArrayLiteralNode.createIntArray(args)); - } else if (type == ArrayContentType.Double) { - isDoubleCase.enter(); - return Tuple.create(ArrayLiteralNode.createDoubleArray(args)); - } else { - isObjectCase.enter(); - return Tuple.create(args); - } + return Tuple.create(args); } } diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/TupleLiteralNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/TupleLiteralNode.java index f2eb99c2516..1e054c28e66 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/TupleLiteralNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/TupleLiteralNode.java @@ -17,7 +17,6 @@ import com.oracle.truffle.js.runtime.JSContext; import com.oracle.truffle.js.runtime.JSRuntime; import com.oracle.truffle.js.runtime.Tuple; -import com.oracle.truffle.js.runtime.array.ScriptArray; import com.oracle.truffle.js.runtime.objects.IteratorRecord; import com.oracle.truffle.js.runtime.util.SimpleArrayList; @@ -92,77 +91,12 @@ private static Object[] resolveConstants(JavaScriptNode[] nodes) { } private static Tuple createTuple(Object[] array) { - Tuple tuple; - TupleContentType type = identifyPrimitiveContentType(array); - if (type == TupleContentType.Byte) { - tuple = Tuple.create(createByteArray(array)); - } else if (type == TupleContentType.Integer) { - tuple = Tuple.create(createIntArray(array)); - } else if (type == TupleContentType.Double) { - tuple = Tuple.create(createDoubleArray(array)); - } else { - for (Object element : array) { - if (!JSRuntime.isJSPrimitive(element)) { - throw Errors.createTypeError("Tuples cannot contain non-primitive values"); - } - } - tuple = Tuple.create(array); - } - return tuple; - } - - public static TupleContentType identifyPrimitiveContentType(Object[] values) { - boolean bytes = true; - boolean integers = true; - - for (int i = 0; i < values.length; i++) { - Object value = values[i]; - if (integers && value instanceof Integer) { - bytes = bytes && ScriptArray.valueIsByte((int) value); - } else if (value instanceof Double) { - bytes = false; - integers = false; - } else if (!(value instanceof Integer)) { - return TupleContentType.Object; - } - } - - if (bytes) { - return TupleContentType.Byte; - } else if (integers) { - return TupleContentType.Integer; - } else { - return TupleContentType.Double; - } - } - - public static double[] createDoubleArray(Object[] values) { - double[] doubleArray = new double[values.length]; - for (int i = 0; i < values.length; i++) { - Object oValue = values[i]; - if (oValue instanceof Double) { - doubleArray[i] = (double) oValue; - } else if (oValue instanceof Integer) { - doubleArray[i] = (int) oValue; + for (Object element : array) { + if (!JSRuntime.isJSPrimitive(element)) { + throw Errors.createTypeError("Tuples cannot contain non-primitive values"); } } - return doubleArray; - } - - public static int[] createIntArray(Object[] values) { - int[] intArray = new int[values.length]; - for (int i = 0; i < values.length; i++) { - intArray[i] = (int) values[i]; - } - return intArray; - } - - public static byte[] createByteArray(Object[] values) { - byte[] byteArray = new byte[values.length]; - for (int i = 0; i < values.length; i++) { - byteArray[i] = (byte) ((int) values[i]); - } - return byteArray; + return Tuple.create(array); } private static class DefaultTupleLiteralNode extends TupleLiteralNode { @@ -181,7 +115,7 @@ public Object execute(VirtualFrame frame) { for (int i = 0; i < elements.length; i++) { values[i] = elements[i].execute(frame); } - return TupleLiteralNode.createTuple(values); + return createTuple(values); } @Override @@ -213,7 +147,7 @@ public Object execute(VirtualFrame frame) { evaluatedElements.add(elements[i].execute(frame), growProfile); } } - return TupleLiteralNode.createTuple(evaluatedElements.toArray()); + return createTuple(evaluatedElements.toArray()); } @Override diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Tuple.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Tuple.java index b8997fa0216..ec3e42a8ae9 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Tuple.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Tuple.java @@ -13,7 +13,6 @@ import java.util.Arrays; import java.util.stream.Collectors; -import java.util.stream.IntStream; /** * Implementation of the Tuple (primitive) type which is an ordered sequence of ECMAScript primitive values. @@ -25,7 +24,7 @@ @ValueType public final class Tuple implements TruffleObject { - public static final Tuple EMPTY_TUPLE = new Tuple(new Object[]{}); // TODO: create a tuple sub-class for empty tuples + public static final Tuple EMPTY_TUPLE = new Tuple(new Object[]{}); private final Object[] value; @@ -38,26 +37,12 @@ public static Tuple create() { } public static Tuple create(Object[] v) { + if (v == null || v.length == 0) { + return EMPTY_TUPLE; + } return new Tuple(v); } - public static Tuple create(byte[] v) { - // TODO: create a tuple sub-class for byte arrays - return new Tuple( - IntStream.range(0, v.length).mapToObj(i -> (int) v[i]).toArray() - ); - } - - public static Tuple create(int[] v) { - // TODO: create a tuple sub-class for int arrays - return new Tuple(Arrays.stream(v).boxed().toArray()); - } - - public static Tuple create(double[] v) { - // TODO: create a tuple sub-class for double arrays - return new Tuple(Arrays.stream(v).boxed().toArray()); - } - @Override @TruffleBoundary public int hashCode() { @@ -80,9 +65,6 @@ public boolean equals(Object obj) { } public boolean equals(Tuple other) { - if (value == null || value.length == 0) { - return other.value == null || other.value.length == 0; - } if (value.length != other.value.length) { return false; } From 1471f7788ba8cbbd21d99282fea3f24cec8a09e0 Mon Sep 17 00:00:00 2001 From: Christian Aistleitner Date: Thu, 29 Apr 2021 11:47:53 +0200 Subject: [PATCH 13/31] Update typeof operator. --- .../js/record-tuple/record.js | 7 +++++++ .../access/RequireObjectCoercibleNode.java | 10 ++++++++++ .../nodes/binary/JSTypeofIdenticalNode.java | 20 +++++++++++++++++++ .../truffle/js/nodes/unary/TypeOfNode.java | 7 +++++++ .../oracle/truffle/js/runtime/JSRuntime.java | 2 ++ 5 files changed, 46 insertions(+) diff --git a/graal-js/src/com.oracle.truffle.js.test/js/record-tuple/record.js b/graal-js/src/com.oracle.truffle.js.test/js/record-tuple/record.js index 8b0d27505f2..2d0b43418db 100644 --- a/graal-js/src/com.oracle.truffle.js.test/js/record-tuple/record.js +++ b/graal-js/src/com.oracle.truffle.js.test/js/record-tuple/record.js @@ -152,3 +152,10 @@ assertSame(true, a >= #{}); assertSame(false, a > #{}); // 3.1.5 ToObject assertSame("object", typeof Object(a)); // JSToObjectNode + +/* + * Test 11: + * typeof + */ +assertSame("record", typeof #{id: 1}); +assertSame("record", typeof #{}); diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/access/RequireObjectCoercibleNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/access/RequireObjectCoercibleNode.java index c1a58d7f316..eee28b11004 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/access/RequireObjectCoercibleNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/access/RequireObjectCoercibleNode.java @@ -58,8 +58,10 @@ import com.oracle.truffle.js.runtime.BigInt; import com.oracle.truffle.js.runtime.Errors; import com.oracle.truffle.js.runtime.JSConfig; +import com.oracle.truffle.js.runtime.Record; import com.oracle.truffle.js.runtime.SafeInteger; import com.oracle.truffle.js.runtime.Symbol; +import com.oracle.truffle.js.runtime.Tuple; /** * Implementation of the abstract operation RequireObjectCoercible(argument) (ES6 7.2.1). @@ -113,6 +115,14 @@ protected static void doSymbol(@SuppressWarnings("unused") Symbol value) { protected static void doBigInt(@SuppressWarnings("unused") BigInt value) { } + @Specialization + protected static void doRecord(@SuppressWarnings("unused") Record value) { + } + + @Specialization + protected static void doTuple(@SuppressWarnings("unused") Tuple value) { + } + @Specialization(guards = {"cachedClass != null", "cachedClass.isInstance(object)"}, limit = "1") protected static void doCachedJSClass(@SuppressWarnings("unused") Object object, @Cached("getClassIfJSObject(object)") @SuppressWarnings("unused") Class cachedClass) { diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/binary/JSTypeofIdenticalNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/binary/JSTypeofIdenticalNode.java index 23059db52e4..ef08029b5ed 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/binary/JSTypeofIdenticalNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/binary/JSTypeofIdenticalNode.java @@ -72,16 +72,20 @@ import com.oracle.truffle.js.runtime.JSConfig; import com.oracle.truffle.js.runtime.JSContext; import com.oracle.truffle.js.runtime.JSRuntime; +import com.oracle.truffle.js.runtime.Record; import com.oracle.truffle.js.runtime.SafeInteger; import com.oracle.truffle.js.runtime.Symbol; +import com.oracle.truffle.js.runtime.Tuple; import com.oracle.truffle.js.runtime.builtins.JSBigInt; import com.oracle.truffle.js.runtime.builtins.JSBoolean; import com.oracle.truffle.js.runtime.builtins.JSFunction; import com.oracle.truffle.js.runtime.builtins.JSNumber; import com.oracle.truffle.js.runtime.builtins.JSOrdinary; import com.oracle.truffle.js.runtime.builtins.JSProxy; +import com.oracle.truffle.js.runtime.builtins.JSRecord; import com.oracle.truffle.js.runtime.builtins.JSString; import com.oracle.truffle.js.runtime.builtins.JSSymbol; +import com.oracle.truffle.js.runtime.builtins.JSTuple; import com.oracle.truffle.js.runtime.objects.JSDynamicObject; import com.oracle.truffle.js.runtime.objects.Null; import com.oracle.truffle.js.runtime.objects.Undefined; @@ -106,6 +110,8 @@ public enum Type { Undefined, Function, Symbol, + Record, + Tuple, /** Unknown type string. Always false. */ False } @@ -143,6 +149,10 @@ private static Type typeStringToEnum(String string) { return Type.Function; case JSSymbol.TYPE_NAME: return Type.Symbol; + case JSRecord.TYPE_NAME: + return Type.Record; + case JSTuple.TYPE_NAME: + return Type.Tuple; default: return Type.False; } @@ -275,6 +285,16 @@ protected final boolean doBigInt(@SuppressWarnings("unused") BigInt value) { return (type == Type.BigInt); } + @Specialization + protected final boolean doRecord(@SuppressWarnings("unused") Record value) { + return (type == Type.Record); + } + + @Specialization + protected final boolean doTuple(@SuppressWarnings("unused") Tuple value) { + return (type == Type.Tuple); + } + @Specialization protected final boolean doString(@SuppressWarnings("unused") CharSequence value) { return (type == Type.String); diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/unary/TypeOfNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/unary/TypeOfNode.java index 7caa7c7258b..29743076760 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/unary/TypeOfNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/unary/TypeOfNode.java @@ -60,6 +60,7 @@ import com.oracle.truffle.js.runtime.BigInt; import com.oracle.truffle.js.runtime.JSConfig; import com.oracle.truffle.js.runtime.JSRuntime; +import com.oracle.truffle.js.runtime.Record; import com.oracle.truffle.js.runtime.Symbol; import com.oracle.truffle.js.runtime.Tuple; import com.oracle.truffle.js.runtime.builtins.JSBigInt; @@ -68,6 +69,7 @@ import com.oracle.truffle.js.runtime.builtins.JSNumber; import com.oracle.truffle.js.runtime.builtins.JSOrdinary; import com.oracle.truffle.js.runtime.builtins.JSProxy; +import com.oracle.truffle.js.runtime.builtins.JSRecord; import com.oracle.truffle.js.runtime.builtins.JSString; import com.oracle.truffle.js.runtime.builtins.JSSymbol; import com.oracle.truffle.js.runtime.builtins.JSTuple; @@ -176,6 +178,11 @@ protected String doSymbol(Symbol operand) { return JSSymbol.TYPE_NAME; } + @Specialization + protected String doRecord(Record operand) { + return JSRecord.TYPE_NAME; + } + @Specialization protected String doTuple(Tuple operand) { return JSTuple.TYPE_NAME; diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSRuntime.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSRuntime.java index 62c1cf904ee..29871f047fa 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSRuntime.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSRuntime.java @@ -228,6 +228,8 @@ public static String typeof(Object value) { return JSBoolean.TYPE_NAME; } else if (value instanceof Symbol) { return JSSymbol.TYPE_NAME; + } else if (value instanceof Record) { + return JSRecord.TYPE_NAME; } else if (value instanceof Tuple) { return JSTuple.TYPE_NAME; } else if (JSDynamicObject.isJSDynamicObject(value)) { From 3150e12b9949ff6ae3a554e19fb6e833f80f529f Mon Sep 17 00:00:00 2001 From: Christian Aistleitner Date: Wed, 5 May 2021 09:43:20 +0200 Subject: [PATCH 14/31] Add missing tuple prototype builtins and unit tests. --- .../test/builtins/TuplePrototypeBuiltins.java | 375 ++++++++++++++++++ .../js/builtins/TuplePrototypeBuiltins.java | 364 +++++++++++++++-- .../truffle/js/nodes/tuples/IsTupleNode.java | 3 +- 3 files changed, 706 insertions(+), 36 deletions(-) create mode 100644 graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/TuplePrototypeBuiltins.java diff --git a/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/TuplePrototypeBuiltins.java b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/TuplePrototypeBuiltins.java new file mode 100644 index 00000000000..fafbbef0f6e --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/TuplePrototypeBuiltins.java @@ -0,0 +1,375 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.truffle.js.test.builtins; + +import com.oracle.truffle.js.lang.JavaScriptLanguage; +import com.oracle.truffle.js.runtime.JSContextOptions; +import com.oracle.truffle.js.test.JSTest; +import org.graalvm.polyglot.Context; +import org.graalvm.polyglot.Source; +import org.graalvm.polyglot.Value; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class TuplePrototypeBuiltins { + + private static final String testName = "tuple-prototype-test"; + + private static Value execute(String sourceText) { + try (Context context = JSTest.newContextBuilder().option(JSContextOptions.INTL_402_NAME, "true").build()) { + return context.eval(Source.newBuilder(JavaScriptLanguage.ID, sourceText, testName).buildLiteral()); + } + } + + private static Value execute(String... sourceText) { + return execute(String.join("\n", sourceText)); + } + + private static void expectError(String sourceText, String expectedMessage) { + try (Context context = JSTest.newContextBuilder().build()) { + context.eval(Source.newBuilder(JavaScriptLanguage.ID, sourceText, testName).buildLiteral()); + Assert.fail("should have thrown"); + } catch (Exception ex) { + Assert.assertTrue( + String.format("\"%s\" should contain \"%s\"", ex.getMessage(), expectedMessage), + ex.getMessage().contains(expectedMessage) + ); + } + } + + @Test + public void testValueOf() { + assertTrue(execute("typeof #[].valueOf() === 'tuple'").asBoolean()); + assertTrue(execute("typeof Object(#[]).valueOf() === 'tuple'").asBoolean()); + expectError("Tuple.prototype.valueOf.call('test')", "be a Tuple"); + } + + @Test + public void testPopped() { + assertTrue(execute("#[1, 2].popped() === #[1]").asBoolean()); + assertTrue(execute("Object(#[1, 2]).popped() === #[1]").asBoolean()); + expectError("Tuple.prototype.popped.call('test')", "be a Tuple"); + } + + @Test + public void testPushed() { + assertTrue(execute("#[1].pushed(2, 3) === #[1, 2, 3]").asBoolean()); + assertTrue(execute("Object(#[1]).pushed(2, 3) === #[1, 2, 3]").asBoolean()); + expectError("#[].pushed(Object(1))", "non-primitive values"); + expectError("Tuple.prototype.pushed.call('test')", "be a Tuple"); + } + + @Test + public void testReversed() { + assertTrue(execute("#[1, 2].reversed() === #[2, 1]").asBoolean()); + assertTrue(execute("Object(#[1, 2]).reversed() === #[2, 1]").asBoolean()); + expectError("Tuple.prototype.reversed.call('test')", "be a Tuple"); + } + + @Test + public void testShifted() { + assertTrue(execute("#[1, 2].shifted() === #[2]").asBoolean()); + assertTrue(execute("Object(#[1, 2]).shifted() === #[2]").asBoolean()); + expectError("Tuple.prototype.shifted.call('test')", "be a Tuple"); + } + + @Test + public void testSlice() { + assertTrue(execute("#[1, 2, 3, 4, 5].slice(1, 4) === #[2, 3, 4]").asBoolean()); + assertTrue(execute("#[1, 2, 3, 4, 5].slice(1, 1) === #[]").asBoolean()); + assertTrue(execute("Object(#[1, 2]).slice(1, 2) === #[2]").asBoolean()); + expectError("Tuple.prototype.slice.call('test')", "be a Tuple"); + } + + @Test + public void testSorted() { + assertTrue(execute("#[5, 4, 3, 2, 1, 3].sorted() === #[1, 2, 3, 3, 4, 5]").asBoolean()); + assertTrue(execute("#[5, 4, 3, 2, 1, 3].sorted((a, b) => b - a) === #[5, 4, 3, 3, 2, 1]").asBoolean()); + assertTrue(execute("Object(#[2, 1]).sorted() === #[1, 2]").asBoolean()); + expectError("Tuple.prototype.sorted.call('test')", "be a Tuple"); + } + + @Test + public void testSpliced() { + assertTrue(execute("#[1, 7, 4].spliced(1, 1, 2, 3) === #[1, 2, 3, 4]").asBoolean()); + assertTrue(execute("Object(#[2, 1]).spliced(0, 1) === #[1]").asBoolean()); + expectError("Tuple.prototype.spliced.call('test')", "be a Tuple"); + } + + @Test + public void testConcat() { + assertTrue(execute("#[1].concat(2, [3, 4], #[5, 6], 0) === #[1, 2, 3, 4, 5, 6, 0]").asBoolean()); + assertTrue(execute("Object(#[1, 2]).concat(3) === #[1, 2, 3]").asBoolean()); + expectError("Tuple.prototype.concat.call('test')", "be a Tuple"); + } + + @Test + public void testIncludes() { + assertTrue(execute("#[1, 2, 3].includes(1)").asBoolean()); + assertFalse(execute("#[1, 2, 3].includes(1, 1)").asBoolean()); + assertTrue(execute("Object(#[1, 2]).includes(2)").asBoolean()); + } + + @Ignore + @Test + public void testIncludes_Fallback() { + expectError("Tuple.prototype.includes.call('test')", "be a Tuple"); + } + + @Test + public void testIndexOf() { + assertEquals(0, execute("#[1, 2, 3].indexOf(1)").asInt()); + assertEquals(-1, execute("#[1, 2, 3].indexOf(1, 1)").asInt()); + assertEquals(1, execute("Object(#[1, 2]).indexOf(2)").asInt()); + } + + @Ignore + @Test + public void testIndexOf_Fallback() { + expectError("Tuple.prototype.indexOf.call('test')", "be a Tuple"); + } + + @Test + public void testJoin() { + assertEquals("1,2,3", execute("#[1, 2, 3].join()").asString()); + assertEquals("123", execute("#[1, 2, 3].join('')").asString()); + assertEquals("1", execute("Object(#[1]).join('-')").asString()); + } + + @Ignore + @Test + public void testJoin_Fallback() { + expectError("Tuple.prototype.join.call('test')", "be a Tuple"); + } + + @Test + public void testLastIndexOf() { + assertEquals(2, execute("#[1, 2, 1].lastIndexOf(1)").asInt()); + assertEquals(0, execute("#[1, 2, 1].lastIndexOf(1, 1)").asInt()); + assertEquals(1, execute("Object(#[1, 2]).lastIndexOf(2)").asInt()); + } + + @Ignore + @Test + public void testLastIndexOf_Fallback() { + expectError("Tuple.prototype.lastIndexOf.call('test')", "be a Tuple"); + } + + @Test + public void testEntries() { + assertTrue(execute( + "var iterator = #['a', 'b'].entries();", + "var values = [...iterator];", + "values[0][0] === 0 && values[0][1] === 'a' && values[1][0] === 1 && values[1][1] === 'b';" + ).asBoolean()); + assertTrue(execute("Object(#[]).entries().next().done;").asBoolean()); + expectError("Tuple.prototype.entries.call('test')", "be a Tuple"); + } + + @Test + public void testEvery() { + assertTrue(execute("#[1, 2, 3].every(it => it > 0)").asBoolean()); + assertFalse(execute("#[1, 2, 3].every(it => it < 2)").asBoolean()); + assertTrue(execute("Object(#[1, 1]).every(it => it === 1)").asBoolean()); + } + + @Ignore + @Test + public void testEvery_Fallback() { + expectError("Tuple.prototype.every.call('test')", "be a Tuple"); + } + + @Test + public void testFilter() { + assertTrue(execute("#[1, 2, 3].filter(it => it !== 2) === #[1, 3]").asBoolean()); + assertTrue(execute("Object(#[1, 1]).filter(it => false) === #[]").asBoolean()); + expectError("Tuple.prototype.filter.call('test', it => false)", "be a Tuple"); + } + + @Test + public void testFind() { + assertEquals(2, execute("#[1, 2, 3].find(it => it > 1)").asInt()); + assertTrue(execute("#[1, 2, 3].find(it => it < 0) === undefined").asBoolean()); + assertEquals(1, execute("Object(#[1, 1]).find(it => it === 1)").asInt()); + } + + @Ignore + @Test + public void testFind_Fallback() { + expectError("Tuple.prototype.find.call('test')", "be a Tuple"); + } + + @Test + public void testFindIndex() { + assertEquals(1, execute("#[1, 2, 3].findIndex(it => it > 1)").asInt()); + assertEquals(-1, execute("#[1, 2, 3].findIndex(it => it < 0)").asInt()); + assertEquals(0, execute("Object(#[1, 1]).findIndex(it => it === 1)").asInt()); + } + + @Ignore + @Test + public void testFindIndex_Fallback() { + expectError("Tuple.prototype.findIndex.call('test')", "be a Tuple"); + } + + @Test + public void testFlat() { + assertTrue(execute("#[1, #[2, #[3]]].flat() === #[1, 2, #[3]]").asBoolean()); + assertTrue(execute("#[1, #[2, #[3]]].flat(2) === #[1, 2, 3]").asBoolean()); + expectError("Tuple.prototype.flat.call('test')", "be a Tuple"); + } + + @Test + public void testFlatMap() { + assertTrue(execute( + "var mapper = it => typeof it === 'number' ? it * 10 : it;", + "#[1, #[2, 3]].flatMap(mapper) === #[10, 20, 30];" + ).asBoolean()); + expectError("Tuple.prototype.flatMap.call('test')", "be a Tuple"); + } + + @Test + public void testForEach() { + assertEquals( + "123", + execute("var text = '';", + "#[1, 2, 3].forEach(it => text += it);", + "text;" + ).asString() + ); + } + + @Ignore + @Test + public void testForEach_Fallback() { + expectError("Tuple.prototype.forEach.call('test')", "be a Tuple"); + } + + @Test + public void testKeys() { + assertTrue(execute("#[...#[1, 2, 3].keys()] === #[0, 1, 2];").asBoolean()); + expectError("Tuple.prototype.keys.call('test')", "be a Tuple"); + } + + @Test + public void testMap() { + assertTrue(execute("#[1, 2, 3].map(it => it * 10) === #[10, 20, 30]").asBoolean()); + expectError("#[1, 2, 3].map(it => Object(it))", "non-primitive values"); + expectError("Tuple.prototype.keys.call('test')", "be a Tuple"); + } + + @Test + public void testReduce() { + assertEquals("123", execute("#[1, 2, 3].reduce((acc, it) => acc += it, '')").asString()); + assertEquals(6, execute("Object(#[1, 2, 3]).reduce((acc, it) => acc += it)").asInt()); + } + + @Ignore + @Test + public void testReduce_Fallback() { + expectError("Tuple.prototype.reduce.call('test')", "be a Tuple"); + } + + @Test + public void testReduceRight() { + assertEquals("321", execute("#[1, 2, 3].reduceRight((acc, it) => acc += it, '')").asString()); + assertEquals(6, execute("Object(#[1, 2, 3]).reduceRight((acc, it) => acc += it)").asInt()); + } + + @Ignore + @Test + public void testReduceRight_Fallback() { + expectError("Tuple.prototype.reduceRight.call('test')", "be a Tuple"); + } + + @Test + public void testSome() { + assertTrue(execute("#[1, 2, 3].some(it => it % 2 === 0)").asBoolean()); + assertFalse(execute("Object(#[1, 2, 3]).some(it => it < 0)").asBoolean()); + } + + @Ignore + @Test + public void testSome_Fallback() { + expectError("Tuple.prototype.some.call('test')", "be a Tuple"); + } + + @Test + public void testUnshifted() { + assertTrue(execute("#[1, 2, 3].unshifted(-1, 0) === #[-1, 0, 1, 2, 3]").asBoolean()); + assertTrue(execute("Object(#[1, 2, 3]).unshifted() === #[1, 2, 3]").asBoolean()); + } + + @Test + public void testToLocaleString() { + assertEquals("1.1", execute("#[1.1].toLocaleString('en')").asString()); + assertEquals("1,1", execute("#[1.1].toLocaleString('de')").asString()); + assertEquals("1,1", execute("Object(#[1.1]).toLocaleString('de')").asString()); + } + + @Test + public void testToString() { + assertEquals("1,2,3", execute("#[1, 2, 3].toString()").asString()); + assertEquals("1,2,3", execute("Object(#[1, 2, 3]).toString()").asString()); + } + + @Test + public void testValues() { + assertTrue(execute("#[...#[1, 2, 3].values()] === #[1, 2, 3];").asBoolean()); + assertTrue(execute("Object(#[]).values().next().done;").asBoolean()); + expectError("Tuple.prototype.values.call('test')", "be a Tuple"); + } + + @Test + public void testWith() { + assertTrue(execute("#[1, 2, 3].with(0, 4) === #[4, 2, 3]").asBoolean()); + assertTrue(execute("Object(#[1]).with(0, 0) === #[0]").asBoolean()); + assertTrue(execute("var x = #[1], y = x.with(0, 0); x === #[1] && y === #[0]").asBoolean()); + expectError("#[1, 2, 3].with(3, 4)", "out of range"); + expectError("#[1, 2, 3].with(0, {})", "non-primitive values"); + expectError("Tuple.prototype.with.call('test')", "be a Tuple"); + } +} diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TuplePrototypeBuiltins.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TuplePrototypeBuiltins.java index fc254be95e4..fd3d0ff2bee 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TuplePrototypeBuiltins.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TuplePrototypeBuiltins.java @@ -1,3 +1,43 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ package com.oracle.truffle.js.builtins; import com.oracle.truffle.api.CompilerDirectives; @@ -13,13 +53,17 @@ import com.oracle.truffle.js.nodes.array.JSArrayLastElementIndexNode; import com.oracle.truffle.js.nodes.array.JSArrayNextElementIndexNode; import com.oracle.truffle.js.nodes.array.JSGetLengthNode; +import com.oracle.truffle.js.nodes.cast.JSToBooleanNode; +import com.oracle.truffle.js.nodes.cast.JSToIndexNode; import com.oracle.truffle.js.nodes.cast.JSToIntegerAsLongNode; import com.oracle.truffle.js.nodes.cast.JSToObjectNode; import com.oracle.truffle.js.nodes.function.JSBuiltin; import com.oracle.truffle.js.nodes.function.JSBuiltinNode; +import com.oracle.truffle.js.nodes.function.JSFunctionCallNode; import com.oracle.truffle.js.nodes.tuples.IsConcatSpreadableNode; import com.oracle.truffle.js.nodes.unary.IsCallableNode; import com.oracle.truffle.js.runtime.Errors; +import com.oracle.truffle.js.runtime.JSArguments; import com.oracle.truffle.js.runtime.JSConfig; import com.oracle.truffle.js.runtime.JSContext; import com.oracle.truffle.js.runtime.JSRuntime; @@ -49,7 +93,11 @@ import static com.oracle.truffle.js.builtins.ArrayPrototypeBuiltinsFactory.JSArraySomeNodeGen; import static com.oracle.truffle.js.builtins.ArrayPrototypeBuiltinsFactory.JSArrayToLocaleStringNodeGen; import static com.oracle.truffle.js.builtins.TuplePrototypeBuiltinsFactory.JSTupleConcatNodeGen; +import static com.oracle.truffle.js.builtins.TuplePrototypeBuiltinsFactory.JSTupleFilterNodeGen; +import static com.oracle.truffle.js.builtins.TuplePrototypeBuiltinsFactory.JSTupleFlatMapNodeGen; +import static com.oracle.truffle.js.builtins.TuplePrototypeBuiltinsFactory.JSTupleFlatNodeGen; import static com.oracle.truffle.js.builtins.TuplePrototypeBuiltinsFactory.JSTupleIteratorNodeGen; +import static com.oracle.truffle.js.builtins.TuplePrototypeBuiltinsFactory.JSTupleMapNodeGen; import static com.oracle.truffle.js.builtins.TuplePrototypeBuiltinsFactory.JSTuplePoppedNodeGen; import static com.oracle.truffle.js.builtins.TuplePrototypeBuiltinsFactory.JSTuplePushedNodeGen; import static com.oracle.truffle.js.builtins.TuplePrototypeBuiltinsFactory.JSTupleReversedNodeGen; @@ -58,7 +106,9 @@ import static com.oracle.truffle.js.builtins.TuplePrototypeBuiltinsFactory.JSTupleSortedNodeGen; import static com.oracle.truffle.js.builtins.TuplePrototypeBuiltinsFactory.JSTupleSplicedNodeGen; import static com.oracle.truffle.js.builtins.TuplePrototypeBuiltinsFactory.JSTupleToStringNodeGen; +import static com.oracle.truffle.js.builtins.TuplePrototypeBuiltinsFactory.JSTupleUnshiftedNodeGen; import static com.oracle.truffle.js.builtins.TuplePrototypeBuiltinsFactory.JSTupleValueOfNodeGen; +import static com.oracle.truffle.js.builtins.TuplePrototypeBuiltinsFactory.JSTupleWithNodeGen; /** * Contains builtins for Tuple.prototype. @@ -85,24 +135,24 @@ public enum TuplePrototype implements BuiltinEnum { indexOf(1), join(1), lastIndexOf(1), - // TODO: entries(1), + entries(0), every(1), - // TODO: filter(1), + filter(1), find(1), findIndex(1), - // TODO: flat(1), - // TODO: flatMap(1), + flat(0), + flatMap(1), forEach(1), - // TODO: keys(1), - // TODO: map(1), + keys(1), + map(1), reduce(1), reduceRight(1), some(1), - // TODO: unshifted(1), + unshifted(1), toLocaleString(0), toString(0), - values(0); - // TODO: with(1); + values(0), + with(2); private final int length; @@ -145,26 +195,42 @@ protected Object createNode(JSContext context, JSBuiltin builtin, boolean constr return JSArrayJoinNodeGen.create(context, builtin, false, args().withThis().fixedArgs(1).createArgumentNodes(context)); case lastIndexOf: return JSArrayIndexOfNodeGen.create(context, builtin, false, false, args().withThis().varArgs().createArgumentNodes(context)); + case entries: + return JSTupleIteratorNodeGen.create(context, builtin, JSRuntime.ITERATION_KIND_KEY_PLUS_VALUE, args().withThis().createArgumentNodes(context)); case every: return JSArrayEveryNodeGen.create(context, builtin, false, args().withThis().fixedArgs(2).createArgumentNodes(context)); + case filter: + return JSTupleFilterNodeGen.create(context, builtin, args().withThis().fixedArgs(2).createArgumentNodes(context)); case find: return JSArrayFindNodeGen.create(context, builtin, false, args().withThis().fixedArgs(2).createArgumentNodes(context)); case findIndex: return JSArrayFindIndexNodeGen.create(context, builtin, false, args().withThis().fixedArgs(2).createArgumentNodes(context)); + case flat: + return JSTupleFlatNodeGen.create(context, builtin, args().withThis().fixedArgs(1).createArgumentNodes(context)); + case flatMap: + return JSTupleFlatMapNodeGen.create(context, builtin, args().withThis().fixedArgs(2).createArgumentNodes(context)); case forEach: return JSArrayForEachNodeGen.create(context, builtin, args().withThis().fixedArgs(2).createArgumentNodes(context)); + case keys: + return JSTupleIteratorNodeGen.create(context, builtin, JSRuntime.ITERATION_KIND_KEY, args().withThis().createArgumentNodes(context)); + case map: + return JSTupleMapNodeGen.create(context, builtin, args().withThis().fixedArgs(2).createArgumentNodes(context)); case reduce: return JSArrayReduceNodeGen.create(context, builtin, false, true, args().withThis().fixedArgs(1).varArgs().createArgumentNodes(context)); case reduceRight: return JSArrayReduceNodeGen.create(context, builtin, false, false, args().withThis().fixedArgs(1).varArgs().createArgumentNodes(context)); case some: return JSArraySomeNodeGen.create(context, builtin, false, args().withThis().fixedArgs(2).createArgumentNodes(context)); + case unshifted: + return JSTupleUnshiftedNodeGen.create(context, builtin, args().withThis().varArgs().createArgumentNodes(context)); case toLocaleString: return JSArrayToLocaleStringNodeGen.create(context, builtin, false, args().withThis().createArgumentNodes(context)); case toString: return JSTupleToStringNodeGen.create(context, builtin, args().withThis().fixedArgs(1).createArgumentNodes(context)); case values: return JSTupleIteratorNodeGen.create(context, builtin, JSRuntime.ITERATION_KIND_VALUE, args().withThis().createArgumentNodes(context)); + case with: + return JSTupleWithNodeGen.create(context, builtin, args().withThis().fixedArgs(2).createArgumentNodes(context)); } return null; } @@ -186,12 +252,13 @@ protected String toString(DynamicObject thisObj) { } @Fallback - protected void toStringNoTuple(Object thisObj) { + protected void toStringNoTuple(@SuppressWarnings("unused") Object thisObj) { throw Errors.createTypeError("Tuple.prototype.toString requires that 'this' be a Tuple"); } } - public abstract static class JSTupleIteratorNode extends JSBuiltinNode { + public abstract static class JSTupleIteratorNode extends BasicTupleOperation { + @Child private ArrayPrototypeBuiltins.CreateArrayIteratorNode createArrayIteratorNode; public JSTupleIteratorNode(JSContext context, JSBuiltin builtin, int iterationKind) { @@ -199,18 +266,12 @@ public JSTupleIteratorNode(JSContext context, JSBuiltin builtin, int iterationKi this.createArrayIteratorNode = ArrayPrototypeBuiltins.CreateArrayIteratorNode.create(context, iterationKind); } - @Specialization(guards = "isJSObject(thisObj)") - protected DynamicObject doJSObject(VirtualFrame frame, DynamicObject thisObj) { - return createArrayIteratorNode.execute(frame, thisObj); - } - - @Specialization(guards = "!isJSObject(thisObj)") - protected DynamicObject doNotJSObject( - VirtualFrame frame, - Object thisObj, - @Cached("createToObject(getContext())") JSToObjectNode toObjectNode - ) { - return createArrayIteratorNode.execute(frame, toObjectNode.execute(thisObj)); + @Specialization + protected DynamicObject doObject(VirtualFrame frame, Object thisObj, + @Cached("createToObject(getContext())") JSToObjectNode toObjectNode) { + Tuple tuple = thisTupleValue(thisObj); + Object obj = toObjectNode.execute(tuple); + return createArrayIteratorNode.execute(frame, obj); } } @@ -258,7 +319,7 @@ private Tuple getPoppedTuple(Tuple tuple) { return Tuple.EMPTY_TUPLE; } Object[] values = tuple.getElements(); - return Tuple.create(Arrays.copyOf(values, values.length-1)); + return Tuple.create(Arrays.copyOf(values, values.length - 1)); } @Fallback @@ -297,6 +358,11 @@ private Tuple getPushedTuple(Tuple tuple, Object[] args) { } return Tuple.create(values); } + + @Fallback + protected void fallback(@SuppressWarnings("unused") Object thisObj, @SuppressWarnings("unused") Object args) { + throw Errors.createTypeError("Tuple.prototype.popped requires that 'this' be a Tuple"); + } } public abstract static class JSTupleReversedNode extends JSBuiltinNode { @@ -348,8 +414,7 @@ protected Tuple doJSTuple(DynamicObject thisObj) { } private Tuple getShiftedTuple(Tuple tuple) { - Object[] values = new Object[tuple.getArraySizeInt()]; - if(tuple.getArraySize() == 0) { + if (tuple.getArraySize() == 0) { return tuple; } return Tuple.create(Arrays.copyOfRange(tuple.getElements(), 1, tuple.getArraySizeInt())); @@ -363,10 +428,23 @@ protected void fallback(@SuppressWarnings("unused") Object thisObj) { public abstract static class BasicTupleOperation extends JSBuiltinNode { + private final BranchProfile errorProfile = BranchProfile.create(); + public BasicTupleOperation(JSContext context, JSBuiltin builtin) { super(context, builtin); } + protected Tuple thisTupleValue(Object value) { + if (value instanceof Tuple) { + return (Tuple) value; + } + if (JSTuple.isJSTuple(value)) { + return JSTuple.valueOf((DynamicObject) value); + } + errorProfile.enter(); + throw Errors.createTypeError("'this' must be a Tuple"); + } + protected Tuple toTupleValue(Object obj) { if (obj instanceof Tuple) { return (Tuple) obj; @@ -392,7 +470,7 @@ public JSTupleSliceNode(JSContext context, JSBuiltin builtin) { @Specialization protected Tuple slice(Object thisObj, Object begin, Object end, - @Cached("create()") JSToIntegerAsLongNode toIntegerAsLong) { + @Cached("create()") JSToIntegerAsLongNode toIntegerAsLong) { Tuple tuple = toTupleValue(thisObj); long size = tuple.getArraySize(); @@ -615,14 +693,6 @@ private void concatElement(Object el, SimpleArrayList list) { } } - private boolean isConcatSpreadable(Object object) { - if (isConcatSpreadableNode == null) { - CompilerDirectives.transferToInterpreterAndInvalidate(); - isConcatSpreadableNode = insert(IsConcatSpreadableNode.create(getContext())); - } - return isConcatSpreadableNode.execute(object); - } - private void concatSpreadable(Object el, long len, SimpleArrayList list) { if (JSRuntime.isTuple(el)) { Tuple tuple = (Tuple) el; @@ -650,6 +720,14 @@ private void concatSpreadable(Object el, long len, SimpleArrayList list) } } + private boolean isConcatSpreadable(Object object) { + if (isConcatSpreadableNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + isConcatSpreadableNode = insert(IsConcatSpreadableNode.create(getContext())); + } + return isConcatSpreadableNode.execute(object); + } + private boolean hasProperty(Object obj, long idx) { if (hasPropertyNode == null) { CompilerDirectives.transferToInterpreterAndInvalidate(); @@ -702,4 +780,220 @@ private long getLength(Object obj) { return getLengthNode.executeLong(obj); } } + + public abstract static class JSTupleFilterNode extends BasicTupleOperation { + + private final BranchProfile growProfile = BranchProfile.create(); + + public JSTupleFilterNode(JSContext context, JSBuiltin builtin) { + super(context, builtin); + } + + @Specialization + protected Tuple filter(Object thisObj, Object callbackfn, Object thisArg, + @Cached IsCallableNode isCallableNode, + @Cached JSToBooleanNode toBooleanNode, + @Cached("createCall()") JSFunctionCallNode callNode) { + Tuple tuple = thisTupleValue(thisObj); + if (!isCallableNode.executeBoolean(callbackfn)) { + throw Errors.createTypeErrorCallableExpected(); + } + SimpleArrayList list = SimpleArrayList.create(tuple.getArraySize()); + for (long k = 0; k < tuple.getArraySize(); k++) { + Object value = tuple.getElement(k); + boolean selected = toBooleanNode.executeBoolean( + callNode.executeCall(JSArguments.create(thisArg, callbackfn, value, k, tuple)) + ); + if (selected) { + list.add(value, growProfile); + } + } + return Tuple.create(list.toArray()); + } + } + + public abstract static class TupleFlattenOperation extends BasicTupleOperation { + + private final BranchProfile errorProfile = BranchProfile.create(); + + @Child + private JSFunctionCallNode callNode; + + public TupleFlattenOperation(JSContext context, JSBuiltin builtin) { + super(context, builtin); + } + + protected Object call(Object[] arguments) { + if (callNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + callNode = insert(JSFunctionCallNode.createCall()); + } + return callNode.executeCall(arguments); + } + + protected void flattenIntoTuple(SimpleArrayList target, BranchProfile growProfile, Tuple source, long depth, Object mapperFunction, Object thisArg) { + for (int i = 0; i < source.getArraySize(); i++) { + Object element = source.getElement(i); + if (mapperFunction != null) { + element = call(JSArguments.create(thisArg, mapperFunction, element, i, source)); + if (JSRuntime.isObject(element)) { + errorProfile.enter(); + throw Errors.createTypeError("Tuples cannot contain non-primitive values"); + } + } + if (depth > 0 && element instanceof Tuple) { + flattenIntoTuple(target, growProfile, (Tuple) element, depth - 1, mapperFunction, thisArg); + } else { + target.add(element, growProfile); + } + } + } + } + + public abstract static class JSTupleFlatNode extends TupleFlattenOperation { + + private final BranchProfile growProfile = BranchProfile.create(); + + @Child private JSToIntegerAsLongNode toIntegerNode = JSToIntegerAsLongNode.create(); + + public JSTupleFlatNode(JSContext context, JSBuiltin builtin) { + super(context, builtin); + } + + @Specialization + protected Tuple flat(Object thisObj, Object depth) { + Tuple tuple = thisTupleValue(thisObj); + long depthNum = 1; + if (depth != Undefined.instance) { + depthNum = toIntegerNode.executeLong(depth); + } + SimpleArrayList list = SimpleArrayList.create(tuple.getArraySize()); + flattenIntoTuple(list, growProfile, tuple, depthNum, null, null); + return Tuple.create(list.toArray()); + } + } + + public abstract static class JSTupleFlatMapNode extends TupleFlattenOperation { + + private final BranchProfile growProfile = BranchProfile.create(); + + @Child private IsCallableNode isCallableNode = IsCallableNode.create(); + + public JSTupleFlatMapNode(JSContext context, JSBuiltin builtin) { + super(context, builtin); + } + + @Specialization + protected Tuple flatMap(Object thisObj, Object mapperFunction, Object thisArg) { + Tuple tuple = thisTupleValue(thisObj); + if (!isCallableNode.executeBoolean(mapperFunction)) { + throw Errors.createTypeErrorCallableExpected(); + } + SimpleArrayList list = SimpleArrayList.create(tuple.getArraySize()); + flattenIntoTuple(list, growProfile, tuple, 1, mapperFunction, thisArg); + return Tuple.create(list.toArray()); + } + } + + public abstract static class JSTupleMapNode extends BasicTupleOperation { + + private final BranchProfile growProfile = BranchProfile.create(); + private final BranchProfile errorProfile = BranchProfile.create(); + + @Child private IsCallableNode isCallableNode = IsCallableNode.create(); + @Child private JSFunctionCallNode callNode; + + public JSTupleMapNode(JSContext context, JSBuiltin builtin) { + super(context, builtin); + } + + @Specialization + protected Tuple map(Object thisObj, Object mapperFunction, Object thisArg) { + Tuple tuple = thisTupleValue(thisObj); + if (!isCallableNode.executeBoolean(mapperFunction)) { + throw Errors.createTypeErrorCallableExpected(); + } + SimpleArrayList list = SimpleArrayList.create(tuple.getArraySize()); + for (long k = 0; k < tuple.getArraySize(); k++) { + Object value = tuple.getElement(k); + value = call(JSArguments.create(thisArg, mapperFunction, value, k, tuple)); + if (JSRuntime.isObject(value)) { + errorProfile.enter(); + throw Errors.createTypeError("Tuples cannot contain non-primitive values"); + } + list.add(value, growProfile); + } + return Tuple.create(list.toArray()); + } + + protected Object call(Object[] arguments) { + if (callNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + callNode = insert(JSFunctionCallNode.createCall()); + } + return callNode.executeCall(arguments); + } + } + + public abstract static class JSTupleUnshiftedNode extends BasicTupleOperation { + + private final BranchProfile growProfile = BranchProfile.create(); + private final BranchProfile errorProfile = BranchProfile.create(); + + public JSTupleUnshiftedNode(JSContext context, JSBuiltin builtin) { + super(context, builtin); + } + + @Specialization + protected Tuple unshifted(Object thisObj, Object[] args) { + Tuple tuple = thisTupleValue(thisObj); + SimpleArrayList list = SimpleArrayList.create(tuple.getArraySize()); + for (Object arg : args) { + if (JSRuntime.isObject(arg)) { + errorProfile.enter(); + throw Errors.createTypeError("Tuples cannot contain non-primitive values"); + } + list.add(arg, growProfile); + } + for (long k = 0; k < tuple.getArraySize(); k++) { + Object value = tuple.getElement(k); + if (JSRuntime.isObject(value)) { + errorProfile.enter(); + throw Errors.createTypeError("Tuples cannot contain non-primitive values"); + } + list.add(value, growProfile); + } + return Tuple.create(list.toArray()); + } + } + + public abstract static class JSTupleWithNode extends BasicTupleOperation { + + private final BranchProfile rangeErrorProfile = BranchProfile.create(); + private final BranchProfile primitiveErrorProfile = BranchProfile.create(); + + @Child private JSToIndexNode toIndexNode = JSToIndexNode.create(); + + public JSTupleWithNode(JSContext context, JSBuiltin builtin) { + super(context, builtin); + } + + @Specialization + protected Tuple with(Object thisObj, Object index, Object value) { + Tuple tuple = thisTupleValue(thisObj); + Object[] list = tuple.getElements(); + long i = toIndexNode.executeLong(index); + if (i >= list.length) { + rangeErrorProfile.enter(); + throw Errors.createRangeError("Index out of range"); + + } + if (JSRuntime.isObject(value)) { + primitiveErrorProfile.enter(); + throw Errors.createTypeError("Tuples cannot contain non-primitive values"); + } + list[(int) i] = value; + return Tuple.create(list); + } + } } diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/IsTupleNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/IsTupleNode.java index 050953f1624..61dab7c1a34 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/IsTupleNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/IsTupleNode.java @@ -1,5 +1,6 @@ package com.oracle.truffle.js.nodes.tuples; +import com.oracle.truffle.api.dsl.Fallback; import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.object.DynamicObject; import com.oracle.truffle.js.nodes.JavaScriptBaseNode; @@ -25,7 +26,7 @@ protected static boolean doJSTuple(@SuppressWarnings("unused") DynamicObject obj return true; } - @Specialization(guards = "!isJSTuple(object)") + @Fallback protected static boolean doNotJSTuple(@SuppressWarnings("unused") Object object) { return false; } From bf567e64e545e6e5be0e0cd30cf7410f275fa801 Mon Sep 17 00:00:00 2001 From: Christian Aistleitner Date: Wed, 5 May 2021 11:39:37 +0200 Subject: [PATCH 15/31] Refactor tuple abstract operations. --- .../js/builtins/TupleFunctionBuiltins.java | 4 +-- .../js/builtins/TuplePrototypeBuiltins.java | 6 ++-- ...ode.java => JSIsConcatSpreadableNode.java} | 34 ++++++++++++------- .../{IsTupleNode.java => JSIsTupleNode.java} | 12 +++---- 4 files changed, 33 insertions(+), 23 deletions(-) rename graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/{IsConcatSpreadableNode.java => JSIsConcatSpreadableNode.java} (69%) rename graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/{IsTupleNode.java => JSIsTupleNode.java} (69%) diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TupleFunctionBuiltins.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TupleFunctionBuiltins.java index ec80bb6039b..b02750a3c66 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TupleFunctionBuiltins.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TupleFunctionBuiltins.java @@ -3,7 +3,7 @@ import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.js.nodes.function.JSBuiltin; import com.oracle.truffle.js.nodes.function.JSBuiltinNode; -import com.oracle.truffle.js.nodes.tuples.IsTupleNode; +import com.oracle.truffle.js.nodes.tuples.JSIsTupleNode; import com.oracle.truffle.js.runtime.JSContext; import com.oracle.truffle.js.runtime.builtins.BuiltinEnum; import com.oracle.truffle.js.runtime.builtins.JSTuple; @@ -47,7 +47,7 @@ protected Object createNode(JSContext context, JSBuiltin builtin, boolean constr public abstract static class TupleIsTupleNode extends JSBuiltinNode { - @Child private IsTupleNode isTupleNode = IsTupleNode.create(); + @Child private JSIsTupleNode isTupleNode = JSIsTupleNode.create(); public TupleIsTupleNode(JSContext context, JSBuiltin builtin) { super(context, builtin); diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TuplePrototypeBuiltins.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TuplePrototypeBuiltins.java index fd3d0ff2bee..be1fb0aa9c6 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TuplePrototypeBuiltins.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TuplePrototypeBuiltins.java @@ -60,7 +60,7 @@ import com.oracle.truffle.js.nodes.function.JSBuiltin; import com.oracle.truffle.js.nodes.function.JSBuiltinNode; import com.oracle.truffle.js.nodes.function.JSFunctionCallNode; -import com.oracle.truffle.js.nodes.tuples.IsConcatSpreadableNode; +import com.oracle.truffle.js.nodes.tuples.JSIsConcatSpreadableNode; import com.oracle.truffle.js.nodes.unary.IsCallableNode; import com.oracle.truffle.js.runtime.Errors; import com.oracle.truffle.js.runtime.JSArguments; @@ -656,7 +656,7 @@ public abstract static class JSTupleConcatNode extends BasicTupleOperation { private final BranchProfile growProfile = BranchProfile.create(); - @Child private IsConcatSpreadableNode isConcatSpreadableNode; + @Child private JSIsConcatSpreadableNode isConcatSpreadableNode; @Child private JSHasPropertyNode hasPropertyNode; @Child private ReadElementNode readElementNode; @Child private JSGetLengthNode getLengthNode; @@ -723,7 +723,7 @@ private void concatSpreadable(Object el, long len, SimpleArrayList list) private boolean isConcatSpreadable(Object object) { if (isConcatSpreadableNode == null) { CompilerDirectives.transferToInterpreterAndInvalidate(); - isConcatSpreadableNode = insert(IsConcatSpreadableNode.create(getContext())); + isConcatSpreadableNode = insert(JSIsConcatSpreadableNode.create(getContext())); } return isConcatSpreadableNode.execute(object); } diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/IsConcatSpreadableNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/JSIsConcatSpreadableNode.java similarity index 69% rename from graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/IsConcatSpreadableNode.java rename to graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/JSIsConcatSpreadableNode.java index a68a23fec2b..ebd721dad6d 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/IsConcatSpreadableNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/JSIsConcatSpreadableNode.java @@ -2,26 +2,29 @@ import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.dsl.Specialization; -import com.oracle.truffle.api.object.DynamicObject; import com.oracle.truffle.js.nodes.JavaScriptBaseNode; import com.oracle.truffle.js.nodes.access.PropertyGetNode; import com.oracle.truffle.js.nodes.cast.JSToBooleanNode; import com.oracle.truffle.js.nodes.unary.JSIsArrayNode; import com.oracle.truffle.js.runtime.JSContext; +import com.oracle.truffle.js.runtime.JSRuntime; import com.oracle.truffle.js.runtime.Symbol; import com.oracle.truffle.js.runtime.objects.JSDynamicObject; -import com.oracle.truffle.js.runtime.objects.Null; import com.oracle.truffle.js.runtime.objects.Undefined; -public abstract class IsConcatSpreadableNode extends JavaScriptBaseNode { +/** + * Represents the abstract operation IsConcatSpreadable. + */ +public abstract class JSIsConcatSpreadableNode extends JavaScriptBaseNode { @Child private PropertyGetNode getSpreadableNode; - @Child private JSIsArrayNode isArrayNode; @Child private JSToBooleanNode toBooleanNode; + @Child private JSIsTupleNode isTupleNode; + @Child private JSIsArrayNode isArrayNode; protected final JSContext context; - protected IsConcatSpreadableNode(JSContext context) { + protected JSIsConcatSpreadableNode(JSContext context) { super(); this.context = context; } @@ -30,17 +33,16 @@ protected IsConcatSpreadableNode(JSContext context) { @Specialization protected boolean doObject(Object o) { - if (o == Undefined.instance || o == Null.instance) { + if (!JSRuntime.isObject(o) && !JSRuntime.isTuple(o)) { return false; } if (JSDynamicObject.isJSDynamicObject(o)) { - DynamicObject obj = (DynamicObject) o; - Object spreadable = getSpreadableProperty(obj); + Object spreadable = getSpreadableProperty(o); if (spreadable != Undefined.instance) { return toBoolean(spreadable); } } - return isArray(o); + return isTuple(o) || isArray(o); } private boolean isArray(Object object) { @@ -51,10 +53,18 @@ private boolean isArray(Object object) { return isArrayNode.execute(object); } + private boolean isTuple(Object object) { + if (isTupleNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + isTupleNode = insert(JSIsTupleNode.create()); + } + return isTupleNode.execute(object); + } + private Object getSpreadableProperty(Object obj) { if (getSpreadableNode == null) { CompilerDirectives.transferToInterpreterAndInvalidate(); - getSpreadableNode = insert(PropertyGetNode.create(Symbol.SYMBOL_IS_CONCAT_SPREADABLE, false, context)); + getSpreadableNode = insert(PropertyGetNode.create(Symbol.SYMBOL_IS_CONCAT_SPREADABLE, context)); } return getSpreadableNode.getValue(obj); } @@ -67,7 +77,7 @@ protected boolean toBoolean(Object target) { return toBooleanNode.executeBoolean(target); } - public static IsConcatSpreadableNode create(JSContext context) { - return IsConcatSpreadableNodeGen.create(context); + public static JSIsConcatSpreadableNode create(JSContext context) { + return JSIsConcatSpreadableNodeGen.create(context); } } diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/IsTupleNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/JSIsTupleNode.java similarity index 69% rename from graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/IsTupleNode.java rename to graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/JSIsTupleNode.java index 61dab7c1a34..0e4a7900bea 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/IsTupleNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/JSIsTupleNode.java @@ -7,11 +7,11 @@ import com.oracle.truffle.js.runtime.Tuple; /** - * Represents abstract operation IsTuple. + * Represents the abstract operation IsTuple. */ -public abstract class IsTupleNode extends JavaScriptBaseNode { +public abstract class JSIsTupleNode extends JavaScriptBaseNode { - protected IsTupleNode() { + protected JSIsTupleNode() { } public abstract boolean execute(Object operand); @@ -27,11 +27,11 @@ protected static boolean doJSTuple(@SuppressWarnings("unused") DynamicObject obj } @Fallback - protected static boolean doNotJSTuple(@SuppressWarnings("unused") Object object) { + protected static boolean doOther(@SuppressWarnings("unused") Object object) { return false; } - public static IsTupleNode create() { - return IsTupleNodeGen.create(); + public static JSIsTupleNode create() { + return JSIsTupleNodeGen.create(); } } From 7720e1f16d4558ca2da8eb14aca2573117d82a43 Mon Sep 17 00:00:00 2001 From: Christian Aistleitner Date: Mon, 10 May 2021 08:48:56 +0200 Subject: [PATCH 16/31] Add tuple object internal methods and unit tests. --- .../js/test/runtime/builtins/JSTupleTest.java | 223 +++++++++++++++++ .../truffle/js/runtime/builtins/JSTuple.java | 232 ++++++++++++++++-- 2 files changed, 440 insertions(+), 15 deletions(-) create mode 100644 graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/runtime/builtins/JSTupleTest.java diff --git a/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/runtime/builtins/JSTupleTest.java b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/runtime/builtins/JSTupleTest.java new file mode 100644 index 00000000000..cdf2d09977c --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/runtime/builtins/JSTupleTest.java @@ -0,0 +1,223 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.truffle.js.test.runtime.builtins; + +import com.oracle.truffle.api.object.DynamicObject; +import com.oracle.truffle.js.runtime.JSContext; +import com.oracle.truffle.js.runtime.Symbol; +import com.oracle.truffle.js.runtime.Tuple; +import com.oracle.truffle.js.runtime.builtins.JSFunction; +import com.oracle.truffle.js.runtime.builtins.JSTuple; +import com.oracle.truffle.js.runtime.objects.JSObject; +import com.oracle.truffle.js.runtime.objects.PropertyDescriptor; +import com.oracle.truffle.js.runtime.objects.Undefined; +import com.oracle.truffle.js.test.JSTest; +import org.junit.Test; + +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +public class JSTupleTest extends JSTest { + + private static final Tuple EMPTY_TUPLE = Tuple.EMPTY_TUPLE; + private static final Tuple SIMPLE_TUPLE = Tuple.create(new Object[]{1, 2, 3}); + + @Override + public void setup() { + super.setup(); + testHelper.enterContext(); + } + + @Override + public void close() { + testHelper.leaveContext(); + super.close(); + } + + @Test + public void testIsExtensible() { + JSContext context = testHelper.getJSContext(); + DynamicObject obj = JSTuple.create(context, EMPTY_TUPLE); + assertFalse(JSObject.isExtensible(obj)); + } + + @Test + public void testGetOwnProperty() { + JSContext context = testHelper.getJSContext(); + DynamicObject obj = JSTuple.create(context, SIMPLE_TUPLE); + + PropertyDescriptor desc = JSObject.getOwnProperty(obj, "1"); + assertTrue(desc.hasValue() && desc.hasWritable() && desc.hasEnumerable() && desc.hasConfigurable()); + assertEquals(2, desc.getValue()); + assertFalse(desc.getWritable()); + assertTrue(desc.getEnumerable()); + assertFalse(desc.getConfigurable()); + + assertNull(JSObject.getOwnProperty(obj, "3")); + assertNull(JSObject.getOwnProperty(obj, "-0.0")); + + assertNull(JSObject.getOwnProperty(obj, "foo")); + assertNull(JSObject.getOwnProperty(obj, Symbol.create("foo"))); + } + + @Test + public void testDefineOwnProperty() { + JSContext context = testHelper.getJSContext(); + DynamicObject obj = JSTuple.create(context, SIMPLE_TUPLE); + + assertTrue(JSObject.defineOwnProperty(obj, "1", PropertyDescriptor.createData(2, true, false, false))); + assertFalse(JSObject.defineOwnProperty(obj, "1", PropertyDescriptor.createData(2, true, true, false))); + + assertTrue(JSObject.defineOwnProperty(obj, "1", PropertyDescriptor.createData(2))); + assertFalse(JSObject.defineOwnProperty(obj, "1", PropertyDescriptor.createData(0))); + assertFalse(JSObject.defineOwnProperty(obj, "3", PropertyDescriptor.createData(0))); + assertFalse(JSObject.defineOwnProperty(obj, "foo", PropertyDescriptor.createData(0))); + assertFalse(JSObject.defineOwnProperty(obj, "-0.0", PropertyDescriptor.createData(1))); + + assertFalse(JSObject.defineOwnProperty(obj, Symbol.create("foo"), PropertyDescriptor.createData(0))); + + assertFalse(JSObject.defineOwnProperty(obj, "1", PropertyDescriptor.createAccessor(Undefined.instance, Undefined.instance))); + } + + @Test + public void testHasProperty() { + JSContext context = testHelper.getJSContext(); + DynamicObject obj = JSTuple.create(context, SIMPLE_TUPLE); + + assertTrue(JSObject.hasProperty(obj, 1L)); + assertTrue(JSObject.hasProperty(obj, "1")); + assertFalse(JSObject.hasProperty(obj, 3L)); + assertFalse(JSObject.hasProperty(obj, "3")); + + assertTrue(JSObject.hasProperty(obj, "length")); + assertTrue(JSObject.hasProperty(obj, "with")); + assertFalse(JSObject.hasProperty(obj, "foo")); + assertFalse(JSObject.hasProperty(obj, Symbol.create("foo"))); + } + + @Test + public void testGet() { + JSContext context = testHelper.getJSContext(); + DynamicObject obj = JSTuple.create(context, SIMPLE_TUPLE); + + assertEquals(1, JSObject.get(obj, 0L)); + assertEquals(1, JSObject.get(obj, "0")); + assertEquals(Undefined.instance, JSObject.get(obj, "-0")); + assertEquals(Undefined.instance, JSObject.get(obj, "3")); + + assertEquals(3L, JSObject.get(obj, "length")); + assertTrue(JSFunction.isJSFunction(JSObject.get(obj, "with"))); + assertEquals(Undefined.instance, JSObject.get(obj, "foo")); + } + + @Test + public void testSet() { + JSContext context = testHelper.getJSContext(); + DynamicObject obj = JSTuple.create(context, SIMPLE_TUPLE); + + assertFalse(JSObject.set(obj, 1L, Integer.valueOf(0))); + assertFalse(JSObject.set(obj, "1", 0)); + assertFalse(JSObject.set(obj, "3", 0)); + + assertFalse(JSObject.set(obj, "length", 0)); + assertFalse(JSObject.set(obj, "with", 0)); + assertFalse(JSObject.set(obj, "foo", 0)); + } + + @Test + public void testDelete() { + JSContext context = testHelper.getJSContext(); + DynamicObject obj = JSTuple.create(context, SIMPLE_TUPLE); + + assertFalse(JSObject.delete(obj, 1L)); + assertFalse(JSObject.delete(obj, "1")); + assertTrue(JSObject.delete(obj, 3L)); + assertTrue(JSObject.delete(obj, "3")); + + assertFalse(JSObject.delete(obj, "0")); + assertTrue(JSObject.delete(obj, "-0")); + + assertTrue(JSObject.delete(obj, "foo")); + assertTrue(JSObject.delete(obj, Symbol.create("foo"))); + } + + @Test + public void testOwnPropertyKeys() { + JSContext context = testHelper.getJSContext(); + DynamicObject obj = JSTuple.create(context, SIMPLE_TUPLE); + + List keys = JSObject.ownPropertyKeys(obj); + assertEquals(3, keys.size()); + assertEquals("0", keys.get(0)); + assertEquals("1", keys.get(1)); + assertEquals("2", keys.get(2)); + + obj = JSTuple.create(context, EMPTY_TUPLE); + keys = JSObject.ownPropertyKeys(obj); + assertEquals(0, keys.size()); + } + + @Test + public void testPrototype() { + DynamicObject constructor = testHelper.getRealm().getTupleConstructor(); + DynamicObject prototype = testHelper.getRealm().getTuplePrototype(); + + PropertyDescriptor desc = JSObject.getOwnProperty(constructor, "prototype"); + assertFalse(desc.getWritable()); + assertFalse(desc.getEnumerable()); + assertFalse(desc.getConfigurable()); + + desc = JSObject.getOwnProperty(prototype, Symbol.SYMBOL_TO_STRING_TAG); + assertFalse(desc.getWritable()); + assertFalse(desc.getEnumerable()); + assertTrue(desc.getConfigurable()); + + desc = JSObject.getOwnProperty(prototype, Symbol.SYMBOL_ITERATOR); + assertTrue(desc.getWritable()); + assertFalse(desc.getEnumerable()); + assertTrue(desc.getConfigurable()); + assertEquals(JSObject.get(prototype, "values"), desc.getValue()); + } +} diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSTuple.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSTuple.java index cd374962486..c7de11aeaf8 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSTuple.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSTuple.java @@ -1,3 +1,43 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ package com.oracle.truffle.js.runtime.builtins; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; @@ -7,7 +47,6 @@ import com.oracle.truffle.js.builtins.TupleFunctionBuiltins; import com.oracle.truffle.js.builtins.TuplePrototypeBuiltins; import com.oracle.truffle.js.builtins.TuplePrototypeGetterBuiltins; -import com.oracle.truffle.js.runtime.Boundaries; import com.oracle.truffle.js.runtime.JSContext; import com.oracle.truffle.js.runtime.JSRealm; import com.oracle.truffle.js.runtime.JSRuntime; @@ -15,7 +54,16 @@ import com.oracle.truffle.js.runtime.Tuple; import com.oracle.truffle.js.runtime.objects.JSAttributes; import com.oracle.truffle.js.runtime.objects.JSDynamicObject; +import com.oracle.truffle.js.runtime.objects.JSObject; import com.oracle.truffle.js.runtime.objects.JSObjectUtil; +import com.oracle.truffle.js.runtime.objects.JSShape; +import com.oracle.truffle.js.runtime.objects.PropertyDescriptor; +import com.oracle.truffle.js.runtime.objects.Undefined; +import com.oracle.truffle.js.runtime.util.DefinePropertyUtil; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; import static com.oracle.truffle.js.runtime.objects.JSObjectUtil.putDataProperty; @@ -68,9 +116,19 @@ public DynamicObject createPrototype(final JSRealm realm, DynamicObject ctor) { return prototype; } + /** + * Tuples aren't extensible, thus [[IsExtensible]] must always return false. + * @see JSNonProxy#isExtensible(com.oracle.truffle.api.object.DynamicObject) + */ @Override public Shape makeInitialShape(JSContext context, DynamicObject prototype) { - return JSObjectUtil.getProtoChildShape(prototype, INSTANCE, context); + Shape initialShape = JSShape.newBuilder(context, INSTANCE, null) + .addConstantProperty(JSObject.HIDDEN_PROTO, prototype, 0) + .shapeFlags(JSShape.NOT_EXTENSIBLE_FLAG) + .build(); + + assert !JSShape.isExtensible(initialShape); + return initialShape; } public static JSConstructor createConstructor(JSRealm realm) { @@ -96,12 +154,90 @@ public DynamicObject getIntrinsicDefaultProto(JSRealm realm) { return realm.getTuplePrototype(); } + /** + * [[GetOwnProperty]] + */ + @Override + public PropertyDescriptor getOwnProperty(DynamicObject thisObj, Object key) { + assert JSRuntime.isPropertyKey(key); + if (key instanceof Symbol) { + return null; + } + assert key instanceof String; + Object numericIndex = JSRuntime.canonicalNumericIndexString((String) key); + if (numericIndex == Undefined.instance) { + return null; + } + assert numericIndex instanceof Number; + Object value = tupleGet(thisObj, (Number) numericIndex); + if (value == null) { + return null; + } + return PropertyDescriptor.createData(value, true, false, false); + } + + /** + * [[DefineOwnProperty]] + */ + public boolean defineOwnProperty(DynamicObject thisObj, Object key, PropertyDescriptor desc, boolean doThrow) { + assert JSRuntime.isPropertyKey(key); + if (key instanceof Symbol) { + return DefinePropertyUtil.reject(doThrow, "object is not extensible"); + } + assert key instanceof String; + if (desc.getIfHasWritable(false) || !desc.getIfHasEnumerable(true) || desc.getIfHasConfigurable(false)) { + return DefinePropertyUtil.reject(doThrow, "object is not extensible"); + } + Object numericIndex = JSRuntime.canonicalNumericIndexString((String) key); + if (numericIndex == Undefined.instance) { + return DefinePropertyUtil.reject(doThrow, "key could not be mapped to a numeric index"); + } + assert numericIndex instanceof Number; + Object current = tupleGet(thisObj, (Number) numericIndex); + if (current == null) { + return DefinePropertyUtil.reject(doThrow, "object is not extensible"); + } + if (desc.isAccessorDescriptor()) { + return DefinePropertyUtil.reject(doThrow, "object is not extensible"); + } + if (desc.hasValue() && JSRuntime.isSameValue(desc.getValue(), current)) { + return true; + } + return DefinePropertyUtil.reject(doThrow, "cannot redefine property"); + } + + /** + * [[HasProperty]] + */ + @Override + public boolean hasOwnProperty(DynamicObject thisObj, Object key) { + assert JSRuntime.isPropertyKey(key); + if (key instanceof Symbol) { + return false; + } + assert key instanceof String; + Object numericIndex = JSRuntime.canonicalNumericIndexString((String) key); + if (numericIndex == Undefined.instance) { + return false; + } + assert numericIndex instanceof Number; + return isValidTupleIndex(thisObj, (Number) numericIndex); + } + + /** + * [[Get]] + */ @TruffleBoundary @Override public final Object getOwnHelper(DynamicObject store, Object thisObj, Object key, Node encapsulatingNode) { - long idx = JSRuntime.propertyKeyToArrayIndex(key); - if (JSRuntime.isArrayIndex(idx)) { - return getOwnHelper(store, thisObj, idx, encapsulatingNode); + assert JSRuntime.isPropertyKey(key); + if (!(key instanceof Symbol)) { + assert key instanceof String; + Object numericIndex = JSRuntime.canonicalNumericIndexString((String) key); + if (numericIndex != Undefined.instance) { + assert numericIndex instanceof Number; + return tupleGet(store, (Number) numericIndex); + } } return super.getOwnHelper(store, thisObj, key, encapsulatingNode); } @@ -109,21 +245,87 @@ public final Object getOwnHelper(DynamicObject store, Object thisObj, Object key @TruffleBoundary @Override public Object getOwnHelper(DynamicObject store, Object thisObj, long index, Node encapsulatingNode) { - assert isJSTuple(store); - Tuple tuple = ((JSTupleObject) store).getTupleValue(); - if (tuple.hasElement(index)) { - return tuple.getElement(index); + return tupleGet(store, index); + } + + /** + * [[Set]] + */ + @Override + public boolean set(DynamicObject thisObj, Object key, Object value, Object receiver, boolean isStrict, Node encapsulatingNode) { + assert JSRuntime.isPropertyKey(key); + return false; + } + + @Override + public boolean set(DynamicObject thisObj, long index, Object value, Object receiver, boolean isStrict, Node encapsulatingNode) { + return false; + } + + /** + * [[Delete]] + */ + @Override + public boolean delete(DynamicObject thisObj, Object key, boolean isStrict) { + assert JSRuntime.isPropertyKey(key); + if (key instanceof Symbol) { + return true; + } + assert key instanceof String; + Object numericIndex = JSRuntime.canonicalNumericIndexString((String) key); + if (numericIndex == Undefined.instance) { + return true; } - return super.getOwnHelper(store, thisObj, Boundaries.stringValueOf(index), encapsulatingNode); + assert numericIndex instanceof Number; + return !isValidTupleIndex(thisObj, (Number) numericIndex); } + @Override + public boolean delete(DynamicObject thisObj, long index, boolean isStrict) { + return !isValidTupleIndex(thisObj, index); + } + + /** + * [[OwnPropertyKeys]] + */ @TruffleBoundary @Override - public boolean hasProperty(DynamicObject thisObj, long index) { - assert isJSTuple(thisObj); - Tuple tuple = ((JSTupleObject) thisObj).getTupleValue(); - return tuple.hasElement(index); + public List getOwnPropertyKeys(DynamicObject thisObj, boolean strings, boolean symbols) { + Tuple tuple = valueOf(thisObj); + int len = tuple.getArraySizeInt(); + if (len == 0 || !strings) { + return Collections.emptyList(); + } + String[] keys = new String[len]; + for (int i = 0; i < len; i++) { + keys[i] = Integer.toString(i); + } + return Arrays.asList(keys); } - // TODO: override [[GetOwnProperty]], [[Delete]], etc. + private Object tupleGet(DynamicObject T, Number numericIndex) { + assert isJSTuple(T); + assert JSRuntime.isInteger(numericIndex); + Tuple tuple = valueOf(T); + if (!isValidTupleIndex(T, numericIndex)) { + return null; + } + long longIndex = JSRuntime.toLong(numericIndex); + return tuple.getElement(longIndex); + } + + private boolean isValidTupleIndex(DynamicObject T, Number numericIndex) { + assert isJSTuple(T); + assert JSRuntime.isInteger(numericIndex); + if (numericIndex.equals(-0.0)) { + return false; + } + long longIndex = JSRuntime.toLong(numericIndex); + return longIndex >= 0 && longIndex < valueOf(T).getArraySize(); + } + + @Override + public boolean usesOrdinaryGetOwnProperty() { + return false; + } } From 2fd97378e088dad09eb4c40f976068ac3568e782 Mon Sep 17 00:00:00 2001 From: Christian Aistleitner Date: Mon, 10 May 2021 14:19:27 +0200 Subject: [PATCH 17/31] Add unit tests for record object internal methods. --- .../test/builtins/TuplePrototypeBuiltins.java | 9 +- .../test/runtime/builtins/JSRecordTest.java | 209 ++++++++++++++++++ .../js/test/runtime/builtins/JSTupleTest.java | 4 + .../com/oracle/truffle/js/runtime/Record.java | 4 + .../truffle/js/runtime/builtins/JSRecord.java | 85 +++---- 5 files changed, 260 insertions(+), 51 deletions(-) create mode 100644 graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/runtime/builtins/JSRecordTest.java diff --git a/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/TuplePrototypeBuiltins.java b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/TuplePrototypeBuiltins.java index fafbbef0f6e..44e1b662dae 100644 --- a/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/TuplePrototypeBuiltins.java +++ b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/TuplePrototypeBuiltins.java @@ -59,7 +59,10 @@ public class TuplePrototypeBuiltins { private static final String testName = "tuple-prototype-test"; private static Value execute(String sourceText) { - try (Context context = JSTest.newContextBuilder().option(JSContextOptions.INTL_402_NAME, "true").build()) { + try (Context context = JSTest.newContextBuilder() + .option(JSContextOptions.ECMASCRIPT_VERSION_NAME, "2022") + .option(JSContextOptions.INTL_402_NAME, "true") + .build()) { return context.eval(Source.newBuilder(JavaScriptLanguage.ID, sourceText, testName).buildLiteral()); } } @@ -69,7 +72,9 @@ private static Value execute(String... sourceText) { } private static void expectError(String sourceText, String expectedMessage) { - try (Context context = JSTest.newContextBuilder().build()) { + try (Context context = JSTest.newContextBuilder() + .option(JSContextOptions.ECMASCRIPT_VERSION_NAME, "2022") + .build()) { context.eval(Source.newBuilder(JavaScriptLanguage.ID, sourceText, testName).buildLiteral()); Assert.fail("should have thrown"); } catch (Exception ex) { diff --git a/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/runtime/builtins/JSRecordTest.java b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/runtime/builtins/JSRecordTest.java new file mode 100644 index 00000000000..7e9373bbb38 --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/runtime/builtins/JSRecordTest.java @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.truffle.js.test.runtime.builtins; + +import com.oracle.truffle.api.object.DynamicObject; +import com.oracle.truffle.js.runtime.JSContext; +import com.oracle.truffle.js.runtime.JSContextOptions; +import com.oracle.truffle.js.runtime.Record; +import com.oracle.truffle.js.runtime.Symbol; +import com.oracle.truffle.js.runtime.builtins.JSRecord; +import com.oracle.truffle.js.runtime.objects.JSObject; +import com.oracle.truffle.js.runtime.objects.Null; +import com.oracle.truffle.js.runtime.objects.PropertyDescriptor; +import com.oracle.truffle.js.runtime.objects.Undefined; +import com.oracle.truffle.js.test.JSTest; +import com.oracle.truffle.js.test.TestHelper; +import org.junit.Test; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +public class JSRecordTest extends JSTest { + + private static final Record EMPTY_RECORD = Record.create(Collections.emptyMap()); + private static final Record SIMPLE_RECORD = Record.create(mapOf("id", 1, "name", "John Doe")); + + private static Map mapOf(Object... data) { + Map map = new HashMap<>(); + for (int i = 1; i < data.length; i = i + 2) { + map.put((String) data[i - 1], data[i]); + } + return map; + } + + @Override + public void setup() { + super.setup(); + testHelper = new TestHelper(newContextBuilder() + .option(JSContextOptions.ECMASCRIPT_VERSION_NAME, "2022")); + testHelper.enterContext(); + } + + @Override + public void close() { + testHelper.leaveContext(); + super.close(); + } + + @Test + public void testIsExtensible() { + JSContext context = testHelper.getJSContext(); + DynamicObject obj = JSRecord.create(context, EMPTY_RECORD); + assertFalse(JSObject.isExtensible(obj)); + } + + @Test + public void testGetOwnProperty() { + JSContext context = testHelper.getJSContext(); + DynamicObject obj = JSRecord.create(context, SIMPLE_RECORD); + + PropertyDescriptor desc = JSObject.getOwnProperty(obj, "id"); + assertTrue(desc.hasValue() && desc.hasWritable() && desc.hasEnumerable() && desc.hasConfigurable()); + assertEquals(1, desc.getValue()); + assertFalse(desc.getWritable()); + assertTrue(desc.getEnumerable()); + assertFalse(desc.getConfigurable()); + + assertNull(JSObject.getOwnProperty(obj, "foo")); + assertNull(JSObject.getOwnProperty(obj, Symbol.create("foo"))); + } + + @Test + public void testDefineOwnProperty() { + JSContext context = testHelper.getJSContext(); + DynamicObject obj = JSRecord.create(context, SIMPLE_RECORD); + + assertTrue(JSObject.defineOwnProperty(obj, "id", PropertyDescriptor.createData(1, true, false, false))); + assertFalse(JSObject.defineOwnProperty(obj, "id", PropertyDescriptor.createData(1, true, true, false))); + + assertTrue(JSObject.defineOwnProperty(obj, "id", PropertyDescriptor.createData(1))); + assertFalse(JSObject.defineOwnProperty(obj, "id", PropertyDescriptor.createData(0))); + + assertFalse(JSObject.defineOwnProperty(obj, "foo", PropertyDescriptor.createData(0))); + assertFalse(JSObject.defineOwnProperty(obj, Symbol.create("foo"), PropertyDescriptor.createData(0))); + + assertFalse(JSObject.defineOwnProperty(obj, "id", PropertyDescriptor.createAccessor(Undefined.instance, Undefined.instance))); + } + + @Test + public void testHasProperty() { + JSContext context = testHelper.getJSContext(); + DynamicObject obj = JSRecord.create(context, SIMPLE_RECORD); + + assertTrue(JSObject.hasProperty(obj, "id")); + assertTrue(JSObject.hasProperty(obj, "name")); + assertFalse(JSObject.hasProperty(obj, "foo")); + assertFalse(JSObject.hasProperty(obj, Symbol.create("foo"))); + } + + @Test + public void testGet() { + JSContext context = testHelper.getJSContext(); + DynamicObject obj = JSRecord.create(context, SIMPLE_RECORD); + + assertEquals(1, JSObject.get(obj, "id")); + assertEquals("John Doe", JSObject.get(obj, "name")); + assertEquals(Undefined.instance, JSObject.get(obj, "foo")); + assertEquals(Undefined.instance, JSObject.get(obj, Symbol.create("foo"))); + } + + @Test + public void testSet() { + JSContext context = testHelper.getJSContext(); + DynamicObject obj = JSRecord.create(context, SIMPLE_RECORD); + + assertFalse(JSObject.set(obj, "id", 0)); + assertFalse(JSObject.set(obj, "name", 0)); + assertFalse(JSObject.set(obj, "name", "Larry Ellison")); + assertFalse(JSObject.set(obj, 1, Undefined.instance)); + } + + @Test + public void testDelete() { + JSContext context = testHelper.getJSContext(); + DynamicObject obj = JSRecord.create(context, SIMPLE_RECORD); + + assertFalse(JSObject.delete(obj, "id")); + assertFalse(JSObject.delete(obj, "name")); + assertTrue(JSObject.delete(obj, "foo")); + assertTrue(JSObject.delete(obj, Symbol.create("foo"))); + assertTrue(JSObject.delete(obj, Symbol.create("id"))); + assertTrue(JSObject.delete(obj, 1L)); + } + + @Test + public void testOwnPropertyKeys() { + JSContext context = testHelper.getJSContext(); + DynamicObject obj = JSRecord.create(context, SIMPLE_RECORD); + + List keys = JSObject.ownPropertyKeys(obj); + assertEquals(2, keys.size()); + assertEquals("id", keys.get(0)); + assertEquals("name", keys.get(1)); + + obj = JSRecord.create(context, EMPTY_RECORD); + keys = JSObject.ownPropertyKeys(obj); + assertEquals(0, keys.size()); + } + + @Test + public void testPrototype() { + JSContext context = testHelper.getJSContext(); + DynamicObject obj = JSRecord.create(context, EMPTY_RECORD); + + assertEquals(Null.instance, JSObject.getPrototype(obj)); + + DynamicObject constructor = testHelper.getRealm().getRecordConstructor(); + + PropertyDescriptor desc = JSObject.getOwnProperty(constructor, "prototype"); + assertFalse(desc.getWritable()); + assertFalse(desc.getEnumerable()); + assertFalse(desc.getConfigurable()); + } +} diff --git a/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/runtime/builtins/JSTupleTest.java b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/runtime/builtins/JSTupleTest.java index cdf2d09977c..0a718984dc1 100644 --- a/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/runtime/builtins/JSTupleTest.java +++ b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/runtime/builtins/JSTupleTest.java @@ -42,6 +42,7 @@ import com.oracle.truffle.api.object.DynamicObject; import com.oracle.truffle.js.runtime.JSContext; +import com.oracle.truffle.js.runtime.JSContextOptions; import com.oracle.truffle.js.runtime.Symbol; import com.oracle.truffle.js.runtime.Tuple; import com.oracle.truffle.js.runtime.builtins.JSFunction; @@ -50,6 +51,7 @@ import com.oracle.truffle.js.runtime.objects.PropertyDescriptor; import com.oracle.truffle.js.runtime.objects.Undefined; import com.oracle.truffle.js.test.JSTest; +import com.oracle.truffle.js.test.TestHelper; import org.junit.Test; import java.util.List; @@ -67,6 +69,8 @@ public class JSTupleTest extends JSTest { @Override public void setup() { super.setup(); + testHelper = new TestHelper(newContextBuilder() + .option(JSContextOptions.ECMASCRIPT_VERSION_NAME, "2022")); testHelper.enterContext(); } diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Record.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Record.java index 42a70609829..a4d9c1ede22 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Record.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Record.java @@ -65,6 +65,10 @@ public Record(Map map) { this.map = new TreeMap<>(map); } + public static Record create(Map map) { + return new Record(map); + } + public Object get(String key) { return map.get(key); } diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSRecord.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSRecord.java index c73da8082bb..7225cb54b24 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSRecord.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSRecord.java @@ -40,9 +40,11 @@ */ package com.oracle.truffle.js.runtime.builtins; +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.object.DynamicObject; import com.oracle.truffle.api.object.Shape; +import com.oracle.truffle.js.builtins.JSBuiltinsContainer; import com.oracle.truffle.js.builtins.RecordFunctionBuiltins; import com.oracle.truffle.js.runtime.JSContext; import com.oracle.truffle.js.runtime.JSRealm; @@ -52,6 +54,7 @@ import com.oracle.truffle.js.runtime.objects.JSObject; import com.oracle.truffle.js.runtime.objects.JSObjectUtil; import com.oracle.truffle.js.runtime.objects.JSShape; +import com.oracle.truffle.js.runtime.objects.Null; import com.oracle.truffle.js.runtime.objects.PropertyDescriptor; import com.oracle.truffle.js.runtime.util.DefinePropertyUtil; @@ -70,7 +73,6 @@ public class JSRecord extends JSNonProxy implements JSConstructorFactory.Default public static final String TYPE_NAME = "record"; public static final String CLASS_NAME = "Record"; - public static final String PROTOTYPE_NAME = "Record.prototype"; public static final String STRING_NAME = "[object Record]"; public static final JSRecord INSTANCE = new JSRecord(); @@ -105,14 +107,7 @@ public String getClassName() { @Override public DynamicObject createPrototype(JSRealm realm, DynamicObject constructor) { - JSContext context = realm.getContext(); - DynamicObject prototype = JSObjectUtil.createOrdinaryPrototypeObject(realm); - - JSObjectUtil.putConstructorProperty(context, prototype, constructor); - // TODO: JSObjectUtil.putFunctionsFromContainer(realm, prototype, RecordPrototypeBuiltins.BUILTINS); - JSObjectUtil.putToStringTag(prototype, CLASS_NAME); - - return prototype; + return Null.instance; } @Override @@ -124,36 +119,37 @@ public static JSConstructor createConstructor(JSRealm realm) { return INSTANCE.createConstructorAndPrototype(realm, RecordFunctionBuiltins.BUILTINS); } + /** + * Adjusted to support Null.instance prototypes. + */ + @Override + public JSConstructor createConstructorAndPrototype(JSRealm realm, JSBuiltinsContainer functionBuiltins) { + JSContext ctx = realm.getContext(); + DynamicObject constructor = createConstructorObject(realm); + DynamicObject prototype = createPrototype(realm, constructor); + JSObjectUtil.putConstructorPrototypeProperty(ctx, constructor, prototype); + JSObjectUtil.putFunctionsFromContainer(realm, constructor, functionBuiltins); + fillConstructor(realm, constructor); + return new JSConstructor(constructor, prototype); + } + /** * Records aren't extensible, thus [[IsExtensible]] must always return false. - * - * https://tc39.es/ecma262/#sec-immutable-prototype-exotic-objects - * - * TODO: see Ref below for ideas - * @see JSModuleNamespace#makeInitialShape(com.oracle.truffle.js.runtime.JSContext) - * * @see JSNonProxy#isExtensible(com.oracle.truffle.api.object.DynamicObject) */ @Override public Shape makeInitialShape(JSContext context, DynamicObject prototype) { - Shape initialShape = Shape.newBuilder() - .layout(JSShape.getLayout(INSTANCE)) - .dynamicType(INSTANCE) + Shape initialShape = JSShape.newBuilder(context, INSTANCE, prototype) .addConstantProperty(JSObject.HIDDEN_PROTO, prototype, 0) .shapeFlags(JSShape.NOT_EXTENSIBLE_FLAG) .build(); assert !JSShape.isExtensible(initialShape); return initialShape; - - // TODO: remove commented out code below - // return JSShape.newBuilder(context, INSTANCE, prototype) - // .shapeFlags(JSShape.NOT_EXTENSIBLE_FLAG) - // .build(); } /** - * Implementation of [[GetOwnProperty]]. + * [[GetOwnProperty]] */ @Override public PropertyDescriptor getOwnProperty(DynamicObject thisObj, Object key) { @@ -170,9 +166,8 @@ public PropertyDescriptor getOwnProperty(DynamicObject thisObj, Object key) { } /** - * Implementation of [[DefineOwnProperty]]. + * [[DefineOwnProperty]] */ - // TODO: Not sure if this code is reachable according to proposal and ecma262 specs @Override public boolean defineOwnProperty(DynamicObject thisObj, Object key, PropertyDescriptor desc, boolean doThrow) { assert JSRuntime.isPropertyKey(key); @@ -186,7 +181,9 @@ public boolean defineOwnProperty(DynamicObject thisObj, Object key, PropertyDesc if (value == null) { return DefinePropertyUtil.reject(doThrow, "object is not extensible"); } - assert desc.isDataDescriptor(); + if (!desc.isDataDescriptor()) { // Note: this check should be an assert according to proposal specs + return DefinePropertyUtil.reject(doThrow, "object is not extensible"); + } if (desc.hasValue() && JSRuntime.isSameValue(desc.getValue(), value)) { return true; } @@ -194,10 +191,9 @@ public boolean defineOwnProperty(DynamicObject thisObj, Object key, PropertyDesc } /** - * Implementation of [[HasProperty]]. - * This methods also implements the abstract operation 5.1.1.10 RecordHasProperty (R, P). + * [[HasProperty]] + * This methods also represents the abstract operation RecordHasProperty (R, P). */ - // TODO: Not sure if this code is reachable according to proposal and ecma262 specs @Override public boolean hasOwnProperty(DynamicObject thisObj, Object key) { assert JSRuntime.isPropertyKey(key); @@ -210,24 +206,19 @@ public boolean hasOwnProperty(DynamicObject thisObj, Object key) { } /** - * Implementation of [[Get]]. - * - * @return the value associated with the given key - * @see JSClass#get(com.oracle.truffle.api.object.DynamicObject, java.lang.Object) + * [[Get]] */ @Override public Object getOwnHelper(DynamicObject store, Object thisObj, Object key, Node encapsulatingNode) { assert JSRuntime.isPropertyKey(key); if (key instanceof Symbol) { - return null; // will get mapped to Undefined.instance + return null; } return recordGet(store, key); } /** - * Implementation of [[Set]]. - * - * @return always false + * [[Set]] */ @Override public boolean set(DynamicObject thisObj, Object key, Object value, Object receiver, boolean isStrict, Node encapsulatingNode) { @@ -237,11 +228,11 @@ public boolean set(DynamicObject thisObj, Object key, Object value, Object recei @Override public boolean set(DynamicObject thisObj, long index, Object value, Object receiver, boolean isStrict, Node encapsulatingNode) { - return set(thisObj, String.valueOf(index), value, receiver, isStrict, encapsulatingNode); + return false; } /** - * Implementation of [[Delete]]. + * [[Delete]] */ @Override public boolean delete(DynamicObject thisObj, Object key, boolean isStrict) { @@ -258,28 +249,24 @@ public boolean delete(DynamicObject thisObj, long index, boolean isStrict) { } /** - * Implementation of [[OwnPropertyKeys]]. + * [[OwnPropertyKeys]] */ + @TruffleBoundary @Override public List getOwnPropertyKeys(DynamicObject thisObj, boolean strings, boolean symbols) { if (!strings) { return Collections.emptyList(); } - assert isJSRecord(thisObj); Record value = valueOf(thisObj); return new ArrayList<>(value.getKeys()); } - /** - * Implementation of the abstract operation RecordGet. - * - * @see 5.1.1.11 RecordGet (R, P) - */ - public Object recordGet(DynamicObject object, Object key) { + private Object recordGet(DynamicObject object, Object key) { + assert isJSRecord(object); assert key instanceof String; Record record = valueOf(object); Object value = record.get((String) key); - assert JSRuntime.isJSPrimitive(value); + assert !JSRuntime.isObject(value); return value; } From 921372892f2fe1be33f7771acc0be17321d49fca Mon Sep 17 00:00:00 2001 From: Christian Aistleitner Date: Tue, 11 May 2021 14:02:24 +0200 Subject: [PATCH 18/31] Add missing tuple function builtins and unit tests. --- .../builtins/TupleFunctionBuiltinsTest.java | 110 ++++++++ ...s.java => TuplePrototypeBuiltinsTest.java} | 4 +- .../js/builtins/TupleFunctionBuiltins.java | 256 +++++++++++++++++- 3 files changed, 365 insertions(+), 5 deletions(-) create mode 100644 graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/TupleFunctionBuiltinsTest.java rename graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/{TuplePrototypeBuiltins.java => TuplePrototypeBuiltinsTest.java} (99%) diff --git a/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/TupleFunctionBuiltinsTest.java b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/TupleFunctionBuiltinsTest.java new file mode 100644 index 00000000000..09fec776ecf --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/TupleFunctionBuiltinsTest.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.truffle.js.test.builtins; + +import com.oracle.truffle.js.lang.JavaScriptLanguage; +import com.oracle.truffle.js.runtime.JSContextOptions; +import com.oracle.truffle.js.test.JSTest; +import org.graalvm.polyglot.Context; +import org.graalvm.polyglot.Source; +import org.graalvm.polyglot.Value; +import org.junit.Assert; +import org.junit.Test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class TupleFunctionBuiltinsTest { + + private static final String testName = "tuple-function-builtins-test"; + + private static Value execute(String sourceText) { + try (Context context = JSTest.newContextBuilder() + .option(JSContextOptions.ECMASCRIPT_VERSION_NAME, "2022") + .build()) { + return context.eval(Source.newBuilder(JavaScriptLanguage.ID, sourceText, testName).buildLiteral()); + } + } + + private static void expectError(String sourceText, String expectedMessage) { + try (Context context = JSTest.newContextBuilder() + .option(JSContextOptions.ECMASCRIPT_VERSION_NAME, "2022") + .build()) { + context.eval(Source.newBuilder(JavaScriptLanguage.ID, sourceText, testName).buildLiteral()); + Assert.fail("should have thrown"); + } catch (Exception ex) { + Assert.assertTrue( + String.format("\"%s\" should contain \"%s\"", ex.getMessage(), expectedMessage), + ex.getMessage().contains(expectedMessage) + ); + } + } + + @Test + public void testIsTuple() { + assertTrue(execute("Tuple.isTuple(#[])").asBoolean()); + assertTrue(execute("Tuple.isTuple(Object(#[]))").asBoolean()); + assertFalse(execute("Tuple.isTuple()").asBoolean()); + assertFalse(execute("Tuple.isTuple([])").asBoolean()); + } + + @Test + public void testFrom() { + assertTrue(execute("Tuple.from('foo') === #['f', 'o', 'o']").asBoolean()); + assertTrue(execute("Tuple.from([1, 2, 3], x => x + x) === #[2, 4, 6]").asBoolean()); + assertTrue(execute("Tuple.from([1, 2, 3], function (x) { return x + this}, 10) === #[11, 12, 13]").asBoolean()); + assertTrue(execute("Tuple.from({a: 1}) === #[]").asBoolean()); + assertTrue(execute("Tuple.from({0: 'data', length: 3}) === #['data', undefined, undefined]").asBoolean()); + assertTrue(execute("Tuple.from({0: 'data', length: 1}, it => it.length) === #[4]").asBoolean()); + expectError("Tuple.from([], 10)", "mapping function"); + expectError("Tuple.from([{}])", "non-primitive values"); + expectError("Tuple.from({0: {}, length: 1})", "non-primitive values"); + } + + @Test + public void testOf() { + assertTrue(execute("Tuple.of() === #[]").asBoolean()); + assertTrue(execute("Tuple.of(1, 2, 3) === #[1, 2, 3]").asBoolean()); + assertTrue(execute("Tuple.of(1, #['2', #[3n]]) === #[1, #['2', #[3n]]]").asBoolean()); + assertTrue(execute("Tuple.of(null, undefined) === #[null, undefined]").asBoolean()); + expectError("Tuple.of({})", "non-primitive values"); + } +} diff --git a/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/TuplePrototypeBuiltins.java b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/TuplePrototypeBuiltinsTest.java similarity index 99% rename from graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/TuplePrototypeBuiltins.java rename to graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/TuplePrototypeBuiltinsTest.java index 44e1b662dae..c41b3dba771 100644 --- a/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/TuplePrototypeBuiltins.java +++ b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/TuplePrototypeBuiltinsTest.java @@ -54,9 +54,9 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -public class TuplePrototypeBuiltins { +public class TuplePrototypeBuiltinsTest { - private static final String testName = "tuple-prototype-test"; + private static final String testName = "tuple-prototype-builtins-test"; private static Value execute(String sourceText) { try (Context context = JSTest.newContextBuilder() diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TupleFunctionBuiltins.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TupleFunctionBuiltins.java index b02750a3c66..96151e89219 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TupleFunctionBuiltins.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TupleFunctionBuiltins.java @@ -1,12 +1,74 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ package com.oracle.truffle.js.builtins; +import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.object.DynamicObject; +import com.oracle.truffle.api.profiles.BranchProfile; +import com.oracle.truffle.api.profiles.ConditionProfile; +import com.oracle.truffle.js.nodes.access.GetIteratorNode; +import com.oracle.truffle.js.nodes.access.GetMethodNode; +import com.oracle.truffle.js.nodes.access.IteratorCloseNode; +import com.oracle.truffle.js.nodes.access.IteratorStepNode; +import com.oracle.truffle.js.nodes.access.IteratorValueNode; +import com.oracle.truffle.js.nodes.access.ReadElementNode; +import com.oracle.truffle.js.nodes.array.JSGetLengthNode; +import com.oracle.truffle.js.nodes.cast.JSToObjectNode; import com.oracle.truffle.js.nodes.function.JSBuiltin; import com.oracle.truffle.js.nodes.function.JSBuiltinNode; +import com.oracle.truffle.js.nodes.function.JSFunctionCallNode; import com.oracle.truffle.js.nodes.tuples.JSIsTupleNode; +import com.oracle.truffle.js.nodes.unary.IsCallableNode; +import com.oracle.truffle.js.runtime.Errors; +import com.oracle.truffle.js.runtime.JSArguments; import com.oracle.truffle.js.runtime.JSContext; +import com.oracle.truffle.js.runtime.JSRuntime; +import com.oracle.truffle.js.runtime.Symbol; +import com.oracle.truffle.js.runtime.Tuple; import com.oracle.truffle.js.runtime.builtins.BuiltinEnum; import com.oracle.truffle.js.runtime.builtins.JSTuple; +import com.oracle.truffle.js.runtime.objects.IteratorRecord; +import com.oracle.truffle.js.runtime.objects.Undefined; +import com.oracle.truffle.js.runtime.util.SimpleArrayList; /** * Contains builtins for Tuple function. @@ -20,9 +82,9 @@ protected TupleFunctionBuiltins() { } public enum TupleFunction implements BuiltinEnum { - isTuple(1); - // TODO: from(1), - // TODO: of(1); + isTuple(1), + from(1), + of(1); private final int length; @@ -41,6 +103,10 @@ protected Object createNode(JSContext context, JSBuiltin builtin, boolean constr switch (builtinEnum) { case isTuple: return TupleFunctionBuiltinsFactory.TupleIsTupleNodeGen.create(context, builtin, args().fixedArgs(1).createArgumentNodes(context)); + case from: + return TupleFunctionBuiltinsFactory.TupleFromNodeGen.create(context, builtin, args().fixedArgs(3).createArgumentNodes(context)); + case of: + return TupleFunctionBuiltinsFactory.TupleOfNodeGen.create(context, builtin, args().varArgs().createArgumentNodes(context)); } return null; } @@ -58,4 +124,188 @@ protected boolean isTuple(Object object) { return isTupleNode.execute(object); } } + + public abstract static class TupleFromNode extends JSBuiltinNode { + + private final ConditionProfile usingIterableProfile = ConditionProfile.create(); + private final BranchProfile growProfile = BranchProfile.create(); + private final BranchProfile isCallableErrorBranch = BranchProfile.create(); + private final BranchProfile isObjectErrorBranch = BranchProfile.create(); + private final BranchProfile iteratorErrorBranch = BranchProfile.create(); + + @Child private IsCallableNode isCallableNode; + @Child private GetMethodNode getIteratorMethodNode; + @Child private GetIteratorNode getIteratorNode; + @Child private IteratorStepNode iteratorStepNode; + @Child private IteratorValueNode iteratorValueNode; + @Child private IteratorCloseNode iteratorCloseNode; + @Child private JSToObjectNode toObjectNode; + @Child private JSGetLengthNode getLengthNode; + @Child private ReadElementNode readElementNode; + @Child private JSFunctionCallNode functionCallNode; + + public TupleFromNode(JSContext context, JSBuiltin builtin) { + super(context, builtin); + } + + @Specialization + protected Tuple from(Object items, Object mapFn, Object thisArg) { + boolean mapping; + if (mapFn == Undefined.instance) { + mapping = false; + } else { + if (!isCallable(mapFn)) { + isCallableErrorBranch.enter(); + throw Errors.createTypeError("The mapping function must be either a function or undefined"); + } + mapping = true; + } + SimpleArrayList list = new SimpleArrayList<>(); + long k = 0; + + Object usingIterator = getIteratorMethod(items); + if (usingIterableProfile.profile(usingIterator != Undefined.instance)) { + // NOTE: Proposal spec would not work as intended... + // For this reason I replaced the AddEntriesFromIterable(...) call + // with the corresponding code of https://tc39.es/ecma262/#sec-array.from. + IteratorRecord iteratorRecord = getIterator(items); + try { + while (true) { + Object next = iteratorStep(iteratorRecord); + if (next == Boolean.FALSE) { + return Tuple.create(list.toArray()); + } + Object value = iteratorValue((DynamicObject) next); + if (mapping) { + value = call(mapFn, thisArg, value, k); + } + if (JSRuntime.isObject(value)) { + isObjectErrorBranch.enter(); + throw Errors.createTypeError("Tuples cannot contain non-primitive values"); + } + list.add(value, growProfile); + k++; + } + } catch (Exception ex) { + iteratorErrorBranch.enter(); + iteratorCloseAbrupt(iteratorRecord.getIterator()); + throw ex; + } + } + + // NOTE: items is not an Iterable so assume it is an array-like object. + Object arrayLike = toObject(items); + long len = getLengthOfArrayLike(items); + while (k < len) { + Object value = get(arrayLike, k); + if (mapping) { + value = call(mapFn, thisArg, value, k); + } + if (JSRuntime.isObject(value)) { + isObjectErrorBranch.enter(); + throw Errors.createTypeError("Tuples cannot contain non-primitive values"); + } + list.add(value, growProfile); + k++; + } + return Tuple.create(list.toArray()); + } + + private boolean isCallable(Object obj) { + if (isCallableNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + isCallableNode = insert(IsCallableNode.create()); + } + return isCallableNode.executeBoolean(obj); + } + + private Object getIteratorMethod(Object obj) { + if (getIteratorMethodNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + getIteratorMethodNode = insert(GetMethodNode.create(getContext(), null, Symbol.SYMBOL_ITERATOR)); + } + return getIteratorMethodNode.executeWithTarget(obj); + } + + private IteratorRecord getIterator(Object obj) { + if (getIteratorNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + getIteratorNode = insert(GetIteratorNode.create(getContext())); + } + return getIteratorNode.execute(obj); + } + + private Object iteratorStep(IteratorRecord iterator) { + if (iteratorStepNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + iteratorStepNode = insert(IteratorStepNode.create(getContext())); + } + return iteratorStepNode.execute(iterator); + } + + private Object iteratorValue(DynamicObject obj) { + if (iteratorValueNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + iteratorValueNode = insert(IteratorValueNode.create(getContext())); + } + return iteratorValueNode.execute( obj); + } + + protected void iteratorCloseAbrupt(DynamicObject iterator) { + if (iteratorCloseNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + iteratorCloseNode = insert(IteratorCloseNode.create(getContext())); + } + iteratorCloseNode.executeAbrupt(iterator); + } + + private Object toObject(Object obj) { + if (toObjectNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + toObjectNode = insert(JSToObjectNode.createToObject(getContext())); + } + return toObjectNode.execute(obj); + } + + private long getLengthOfArrayLike(Object obj) { + if (getLengthNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + getLengthNode = insert(JSGetLengthNode.create(getContext())); + } + return getLengthNode.executeLong(obj); + } + + private Object get(Object obj, long idx) { + if (readElementNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + readElementNode = insert(ReadElementNode.create(getContext())); + } + return readElementNode.executeWithTargetAndIndex(obj, idx); + } + + private Object call(Object function, Object target, Object... arguments) { + if (functionCallNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + functionCallNode = insert(JSFunctionCallNode.createCall()); + } + return functionCallNode.executeCall(JSArguments.create(target, function, arguments)); + } + } + + public abstract static class TupleOfNode extends JSBuiltinNode { + + public TupleOfNode(JSContext context, JSBuiltin builtin) { + super(context, builtin); + } + + @Specialization + protected Tuple of(Object[] items) { + for (Object item : items) { + if (JSRuntime.isObject(item)) { + throw Errors.createTypeError("Tuples cannot contain non-primitive values"); + } + } + return Tuple.create(items); + } + } } From 0ae391b0f5178ea286439851b8b6f340d3a29530 Mon Sep 17 00:00:00 2001 From: Christian Aistleitner Date: Tue, 11 May 2021 20:14:58 +0200 Subject: [PATCH 19/31] Add missing constructor builtins and unit tests. --- .../builtins/ConstructorBuiltinsTest.java | 96 +++++++++++++++++++ .../js/builtins/ConstructorBuiltins.java | 80 ++++++++++++++-- 2 files changed, 166 insertions(+), 10 deletions(-) create mode 100644 graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/ConstructorBuiltinsTest.java diff --git a/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/ConstructorBuiltinsTest.java b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/ConstructorBuiltinsTest.java new file mode 100644 index 00000000000..247681e9b38 --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/ConstructorBuiltinsTest.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.truffle.js.test.builtins; + +import com.oracle.truffle.js.lang.JavaScriptLanguage; +import com.oracle.truffle.js.runtime.JSContextOptions; +import com.oracle.truffle.js.test.JSTest; +import org.graalvm.polyglot.Context; +import org.graalvm.polyglot.Source; +import org.graalvm.polyglot.Value; +import org.junit.Assert; +import org.junit.Test; + +import static org.junit.Assert.assertTrue; + +public class ConstructorBuiltinsTest { + + private static final String testName = "constructor-builtins-test"; + + private static Value execute(String sourceText) { + try (Context context = JSTest.newContextBuilder() + .option(JSContextOptions.ECMASCRIPT_VERSION_NAME, "2022") + .build()) { + return context.eval(Source.newBuilder(JavaScriptLanguage.ID, sourceText, testName).buildLiteral()); + } + } + + private static void expectError(String sourceText, String expectedMessage) { + try (Context context = JSTest.newContextBuilder() + .option(JSContextOptions.ECMASCRIPT_VERSION_NAME, "2022") + .build()) { + context.eval(Source.newBuilder(JavaScriptLanguage.ID, sourceText, testName).buildLiteral()); + Assert.fail("should have thrown"); + } catch (Exception ex) { + Assert.assertTrue( + String.format("\"%s\" should contain \"%s\"", ex.getMessage(), expectedMessage), + ex.getMessage().contains(expectedMessage) + ); + } + } + + @Test + public void testRecord() { + assertTrue(execute("Record({a: 1}) === #{a: 1}").asBoolean()); + assertTrue(execute("Record([1, 2, 3]) === #{0: 1, 1: 2, 2: 3}").asBoolean()); + expectError("Record()", "convert undefined or null"); + expectError("Record({data: {}})", "non-primitive values"); + expectError("new Record()", "not a constructor"); + } + + @Test + public void testTuple() { + assertTrue(execute("Tuple() === #[]").asBoolean()); + assertTrue(execute("Tuple(1, '2', 3n) === #[1, '2', 3n]").asBoolean()); + expectError("Tuple({a: 1})", "non-primitive values"); + expectError("new Tuple()", "not a constructor"); + } +} diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/ConstructorBuiltins.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/ConstructorBuiltins.java index 83ec9bde0f7..2ebd248adc5 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/ConstructorBuiltins.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/ConstructorBuiltins.java @@ -42,8 +42,10 @@ import java.util.EnumSet; import java.util.LinkedHashMap; +import java.util.Map; import java.util.Objects; import java.util.StringJoiner; +import java.util.stream.Collectors; import com.oracle.truffle.api.Assumption; import com.oracle.truffle.api.CompilerAsserts; @@ -76,9 +78,11 @@ import com.oracle.truffle.js.builtins.ConstructorBuiltinsFactory.CallDateTimeFormatNodeGen; import com.oracle.truffle.js.builtins.ConstructorBuiltinsFactory.CallNumberFormatNodeGen; import com.oracle.truffle.js.builtins.ConstructorBuiltinsFactory.CallNumberNodeGen; +import com.oracle.truffle.js.builtins.ConstructorBuiltinsFactory.CallRecordNodeGen; import com.oracle.truffle.js.builtins.ConstructorBuiltinsFactory.CallRequiresNewNodeGen; import com.oracle.truffle.js.builtins.ConstructorBuiltinsFactory.CallStringNodeGen; import com.oracle.truffle.js.builtins.ConstructorBuiltinsFactory.CallSymbolNodeGen; +import com.oracle.truffle.js.builtins.ConstructorBuiltinsFactory.CallTupleNodeGen; import com.oracle.truffle.js.builtins.ConstructorBuiltinsFactory.CallTypedArrayNodeGen; import com.oracle.truffle.js.builtins.ConstructorBuiltinsFactory.ConstructAggregateErrorNodeGen; import com.oracle.truffle.js.builtins.ConstructorBuiltinsFactory.ConstructArrayBufferNodeGen; @@ -103,13 +107,13 @@ import com.oracle.truffle.js.builtins.ConstructorBuiltinsFactory.ConstructNumberNodeGen; import com.oracle.truffle.js.builtins.ConstructorBuiltinsFactory.ConstructObjectNodeGen; import com.oracle.truffle.js.builtins.ConstructorBuiltinsFactory.ConstructPluralRulesNodeGen; +import com.oracle.truffle.js.builtins.ConstructorBuiltinsFactory.ConstructRecordNodeGen; import com.oracle.truffle.js.builtins.ConstructorBuiltinsFactory.ConstructRegExpNodeGen; import com.oracle.truffle.js.builtins.ConstructorBuiltinsFactory.ConstructRelativeTimeFormatNodeGen; import com.oracle.truffle.js.builtins.ConstructorBuiltinsFactory.ConstructSegmenterNodeGen; import com.oracle.truffle.js.builtins.ConstructorBuiltinsFactory.ConstructSetNodeGen; import com.oracle.truffle.js.builtins.ConstructorBuiltinsFactory.ConstructStringNodeGen; import com.oracle.truffle.js.builtins.ConstructorBuiltinsFactory.ConstructSymbolNodeGen; -import com.oracle.truffle.js.builtins.ConstructorBuiltinsFactory.CallTupleNodeGen; import com.oracle.truffle.js.builtins.ConstructorBuiltinsFactory.ConstructTupleNodeGen; import com.oracle.truffle.js.builtins.ConstructorBuiltinsFactory.ConstructWeakMapNodeGen; import com.oracle.truffle.js.builtins.ConstructorBuiltinsFactory.ConstructWeakRefNodeGen; @@ -128,6 +132,7 @@ import com.oracle.truffle.js.nodes.ScriptNode; import com.oracle.truffle.js.nodes.access.ArrayLiteralNode; import com.oracle.truffle.js.nodes.access.ArrayLiteralNode.ArrayContentType; +import com.oracle.truffle.js.nodes.access.EnumerableOwnPropertyNamesNode; import com.oracle.truffle.js.nodes.access.ErrorStackTraceLimitNode; import com.oracle.truffle.js.nodes.access.GetIteratorNode; import com.oracle.truffle.js.nodes.access.GetMethodNode; @@ -187,6 +192,7 @@ import com.oracle.truffle.js.runtime.JSRealm; import com.oracle.truffle.js.runtime.JSRuntime; import com.oracle.truffle.js.runtime.PromiseHook; +import com.oracle.truffle.js.runtime.Record; import com.oracle.truffle.js.runtime.SafeInteger; import com.oracle.truffle.js.runtime.Symbol; import com.oracle.truffle.js.runtime.Tuple; @@ -242,6 +248,7 @@ import com.oracle.truffle.js.runtime.objects.Undefined; import com.oracle.truffle.js.runtime.util.SimpleArrayList; import com.oracle.truffle.js.runtime.util.TRegexUtil; +import com.oracle.truffle.js.runtime.util.UnmodifiableArrayList; /** * Contains built-in constructor functions. @@ -357,7 +364,9 @@ public boolean isNewTargetConstructor() { @Override public int getECMAScriptVersion() { - if (AsyncGeneratorFunction == this) { + if (EnumSet.of(Record, Tuple).contains(this)) { + return JSConfig.ECMAScript2022; // TODO: Associate with the correct ECMAScript Version + } else if (AsyncGeneratorFunction == this) { return JSConfig.ECMAScript2018; } else if (EnumSet.of(SharedArrayBuffer, AsyncFunction).contains(this)) { return JSConfig.ECMAScript2017; @@ -632,7 +641,8 @@ protected Object createNode(JSContext context, JSBuiltin builtin, boolean constr : ConstructWebAssemblyTableNodeGen.create(context, builtin, false, args().function().fixedArgs(1).createArgumentNodes(context))) : createCallRequiresNew(context, builtin); case Record: - throw new RuntimeException("TODO"); // TODO + return construct ? ConstructRecordNodeGen.create(context, builtin, args().createArgumentNodes(context)) + : CallRecordNodeGen.create(context, builtin, args().fixedArgs(1).createArgumentNodes(context)); case Tuple: return construct ? ConstructTupleNodeGen.create(context, builtin, args().createArgumentNodes(context)) : CallTupleNodeGen.create(context, builtin, args().varArgs().createArgumentNodes(context)); @@ -2755,25 +2765,75 @@ protected DynamicObject getIntrinsicDefaultProto(JSRealm realm) { } + public abstract static class CallRecordNode extends JSBuiltinNode { + + @Child JSToObjectNode toObjectNode; + @Child EnumerableOwnPropertyNamesNode enumerableOwnPropertyNamesNode; + @Child ReadElementNode readElementNode; + + public CallRecordNode(JSContext context, JSBuiltin builtin) { + super(context, builtin); + toObjectNode = JSToObjectNode.createToObject(context); + enumerableOwnPropertyNamesNode = EnumerableOwnPropertyNamesNode.createKeysValues(context); + } + + @Specialization + protected Record call(Object arg) { + Object obj = toObjectNode.execute(arg); + UnmodifiableArrayList props = enumerableOwnPropertyNamesNode.execute((DynamicObject) obj); + Map fields = props.stream().collect(Collectors.toMap( + it -> (String) getV(it, 0), + it -> { + Object value = getV(it, 1); + if (JSRuntime.isObject(value)) { + throw Errors.createTypeError("Records cannot contain non-primitive values"); + } + return value; + } + )); + return Record.create(fields); + } + + private Object getV(Object object, long index) { + if (readElementNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + readElementNode = insert(ReadElementNode.create(getContext())); + } + return readElementNode.executeWithTargetAndIndex(object, index); + } + } + + public abstract static class ConstructRecordNode extends JSBuiltinNode { + + public ConstructRecordNode(JSContext context, JSBuiltin builtin) { + super(context, builtin); + } + + @Specialization + protected static final DynamicObject construct() { + throw Errors.createTypeError("Record is not a constructor"); + } + } + public abstract static class CallTupleNode extends JSBuiltinNode { public CallTupleNode(JSContext context, JSBuiltin builtin) { super(context, builtin); } - @Specialization(guards = {"args.length == 0"}) - protected Object constructEmptyTuple(@SuppressWarnings("unused") Object[] args) { + @Specialization(guards = {"items.length == 0"}) + protected Tuple callEmpty(@SuppressWarnings("unused") Object[] items) { return Tuple.create(); } - @Specialization(guards = {"args.length != 0"}) - protected Object constructTuple(Object[] args) { - for (Object element : args) { - if (!JSRuntime.isJSPrimitive(element)) { + @Specialization(guards = {"items.length != 0"}) + protected Tuple call(Object[] items) { + for (Object item : items) { + if (JSRuntime.isObject(item)) { throw Errors.createTypeError("Tuples cannot contain non-primitive values"); } } - return Tuple.create(args); + return Tuple.create(items); } } From 866f1b931cd476fc15b45d30b33de8650eb9d39a Mon Sep 17 00:00:00 2001 From: Christian Aistleitner Date: Wed, 12 May 2021 11:35:21 +0200 Subject: [PATCH 20/31] Add record function builtins and unit tests. --- .../builtins/RecordFunctionBuiltinsTest.java | 97 +++++++++++ .../js/builtins/RecordFunctionBuiltins.java | 157 +++++++++++++++++- .../com/oracle/truffle/js/nodes/JSGuards.java | 5 + 3 files changed, 257 insertions(+), 2 deletions(-) create mode 100644 graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/RecordFunctionBuiltinsTest.java diff --git a/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/RecordFunctionBuiltinsTest.java b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/RecordFunctionBuiltinsTest.java new file mode 100644 index 00000000000..9dcf0f5e880 --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/RecordFunctionBuiltinsTest.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.truffle.js.test.builtins; + +import com.oracle.truffle.js.lang.JavaScriptLanguage; +import com.oracle.truffle.js.runtime.JSContextOptions; +import com.oracle.truffle.js.test.JSTest; +import org.graalvm.polyglot.Context; +import org.graalvm.polyglot.Source; +import org.graalvm.polyglot.Value; +import org.junit.Assert; +import org.junit.Test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class RecordFunctionBuiltinsTest { + + private static final String testName = "record-function-builtins-test"; + + private static Value execute(String sourceText) { + try (Context context = JSTest.newContextBuilder() + .option(JSContextOptions.ECMASCRIPT_VERSION_NAME, "2022") + .build()) { + return context.eval(Source.newBuilder(JavaScriptLanguage.ID, sourceText, testName).buildLiteral()); + } + } + + private static void expectError(String sourceText, String expectedMessage) { + try (Context context = JSTest.newContextBuilder() + .option(JSContextOptions.ECMASCRIPT_VERSION_NAME, "2022") + .build()) { + context.eval(Source.newBuilder(JavaScriptLanguage.ID, sourceText, testName).buildLiteral()); + Assert.fail("should have thrown"); + } catch (Exception ex) { + Assert.assertTrue( + String.format("\"%s\" should contain \"%s\"", ex.getMessage(), expectedMessage), + ex.getMessage().contains(expectedMessage) + ); + } + } + + @Test + public void testIsRecord() { + assertTrue(execute("Record.isRecord(#{})").asBoolean()); + assertTrue(execute("Record.isRecord(Object(#{}))").asBoolean()); + assertFalse(execute("Record.isRecord()").asBoolean()); + assertFalse(execute("Record.isRecord({})").asBoolean()); + } + + @Test + public void testFromEntries() { + assertTrue(execute("Record.fromEntries(Object.entries({a: 'foo'})) === #{a: 'foo'}").asBoolean()); + assertTrue(execute("Record.fromEntries([['a', 'foo']]) === #{a: 'foo'}").asBoolean()); + expectError("Record.fromEntries()", "undefined or null"); + expectError("Record.fromEntries(Object.entries({data: [1, 2, 3]}))", "cannot contain objects"); + expectError("Record.fromEntries([0])", "not an object"); + } +} diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/RecordFunctionBuiltins.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/RecordFunctionBuiltins.java index 0b262d85ca3..5c383cd6f07 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/RecordFunctionBuiltins.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/RecordFunctionBuiltins.java @@ -40,10 +40,33 @@ */ package com.oracle.truffle.js.builtins; +import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.dsl.Fallback; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.object.DynamicObject; +import com.oracle.truffle.api.profiles.BranchProfile; +import com.oracle.truffle.js.nodes.access.GetIteratorNode; +import com.oracle.truffle.js.nodes.access.IsObjectNode; +import com.oracle.truffle.js.nodes.access.IteratorCloseNode; +import com.oracle.truffle.js.nodes.access.IteratorStepNode; +import com.oracle.truffle.js.nodes.access.IteratorValueNode; +import com.oracle.truffle.js.nodes.access.ReadElementNode; +import com.oracle.truffle.js.nodes.access.RequireObjectCoercibleNode; +import com.oracle.truffle.js.nodes.cast.JSToPropertyKeyNode; import com.oracle.truffle.js.nodes.function.JSBuiltin; +import com.oracle.truffle.js.nodes.function.JSBuiltinNode; +import com.oracle.truffle.js.runtime.Errors; import com.oracle.truffle.js.runtime.JSContext; +import com.oracle.truffle.js.runtime.JSRuntime; +import com.oracle.truffle.js.runtime.Record; +import com.oracle.truffle.js.runtime.Symbol; import com.oracle.truffle.js.runtime.builtins.BuiltinEnum; import com.oracle.truffle.js.runtime.builtins.JSRecord; +import com.oracle.truffle.js.runtime.objects.IteratorRecord; + +import java.util.Map; +import java.util.TreeMap; +import java.util.function.BiConsumer; /** * Contains builtins for Record function. @@ -57,7 +80,8 @@ protected RecordFunctionBuiltins() { } public enum RecordFunction implements BuiltinEnum { - ; + fromEntries(1), + isRecord (1); private final int length; @@ -74,8 +98,137 @@ public int getLength() { @Override protected Object createNode(JSContext context, JSBuiltin builtin, boolean construct, boolean newTarget, RecordFunction builtinEnum) { switch (builtinEnum) { - // TODO + case fromEntries: + return RecordFunctionBuiltinsFactory.RecordFromEntriesNodeGen.create(context, builtin, args().fixedArgs(1).createArgumentNodes(context)); + case isRecord: + return RecordFunctionBuiltinsFactory.RecordIsRecordNodeGen.create(context, builtin, args().fixedArgs(1).createArgumentNodes(context)); } return null; } + + public abstract static class RecordFromEntriesNode extends JSBuiltinNode { + + private final BranchProfile errorBranch = BranchProfile.create(); + + @Child private RequireObjectCoercibleNode requireObjectCoercibleNode = RequireObjectCoercibleNode.create(); + @Child private GetIteratorNode getIteratorNode; + @Child private IteratorStepNode iteratorStepNode; + @Child private IteratorValueNode iteratorValueNode; + @Child private ReadElementNode readElementNode; + @Child private IsObjectNode isObjectNode; + @Child private IteratorCloseNode iteratorCloseNode; + + public RecordFromEntriesNode(JSContext context, JSBuiltin builtin) { + super(context, builtin); + } + + @Specialization + protected Record doObject(Object iterable) { + requireObjectCoercibleNode.executeVoid(iterable); + Map fields = new TreeMap<>(); + BiConsumer adder = (key, value) -> { + if (JSRuntime.isObject(value)) { + errorBranch.enter(); + throw Errors.createTypeError("Records cannot contain objects", this); + } + fields.put(JSRuntime.toString(key),value); + }; + addEntriesFromIterable(iterable, adder); + return Record.create(fields); + } + + private void addEntriesFromIterable(Object iterable, BiConsumer adder) { + IteratorRecord iterator = getIterator(iterable); + try { + while (true) { + Object next = iteratorStep(iterator); + if (next == Boolean.FALSE) { + break; + } + Object nextItem = iteratorValue((DynamicObject) next); + if (!isObject(nextItem)) { + errorBranch.enter(); + throw Errors.createTypeErrorIteratorResultNotObject(nextItem, this); + } + Object k = get(nextItem, 0); + Object v = get(nextItem, 1); + adder.accept(k, v); + } + } catch (Exception ex) { + errorBranch.enter(); + iteratorCloseAbrupt(iterator.getIterator()); + throw ex; + } + } + + private IteratorRecord getIterator(Object obj) { + if (getIteratorNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + getIteratorNode = insert(GetIteratorNode.create(getContext())); + } + return getIteratorNode.execute(obj); + } + + private Object iteratorStep(IteratorRecord iterator) { + if (iteratorStepNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + iteratorStepNode = insert(IteratorStepNode.create(getContext())); + } + return iteratorStepNode.execute(iterator); + } + + private Object iteratorValue(DynamicObject obj) { + if (iteratorValueNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + iteratorValueNode = insert(IteratorValueNode.create(getContext())); + } + return iteratorValueNode.execute( obj); + } + + private void iteratorCloseAbrupt(DynamicObject iterator) { + if (iteratorCloseNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + iteratorCloseNode = insert(IteratorCloseNode.create(getContext())); + } + iteratorCloseNode.executeAbrupt(iterator); + } + + private Object get(Object obj, long idx) { + if (readElementNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + readElementNode = insert(ReadElementNode.create(getContext())); + } + return readElementNode.executeWithTargetAndIndex(obj, idx); + } + + private boolean isObject(Object obj) { + if (isObjectNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + isObjectNode = insert(IsObjectNode.create()); + } + return isObjectNode.executeBoolean(obj); + } + } + + public abstract static class RecordIsRecordNode extends JSBuiltinNode { + + public RecordIsRecordNode(JSContext context, JSBuiltin builtin) { + super(context, builtin); + } + + @Specialization + protected boolean doRecord(@SuppressWarnings("unused") Record arg) { + return true; + } + + @Specialization(guards = "isJSRecord(arg)") + protected boolean doJSRecord(@SuppressWarnings("unused") Object arg) { + return true; + } + + @Fallback + protected boolean doOther(@SuppressWarnings("unused") Object arg) { + return false; + } + } } diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/JSGuards.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/JSGuards.java index 070e9cba6f1..095277737fb 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/JSGuards.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/JSGuards.java @@ -65,6 +65,7 @@ import com.oracle.truffle.js.runtime.builtins.JSObjectPrototype; import com.oracle.truffle.js.runtime.builtins.JSOrdinary; import com.oracle.truffle.js.runtime.builtins.JSProxy; +import com.oracle.truffle.js.runtime.builtins.JSRecord; import com.oracle.truffle.js.runtime.builtins.JSRegExp; import com.oracle.truffle.js.runtime.builtins.JSSet; import com.oracle.truffle.js.runtime.builtins.JSSharedArrayBuffer; @@ -143,6 +144,10 @@ public static boolean isJSFunction(Object value) { return JSFunction.isJSFunction(value); } + public static boolean isJSRecord(Object value) { + return JSRecord.isJSRecord(value); + } + public static boolean isJSTuple(Object value) { return JSTuple.isJSTuple(value); } From fda0a12711db4bb3cfb0b57f76ff9c6bec1f774c Mon Sep 17 00:00:00 2001 From: Christian Aistleitner Date: Wed, 12 May 2021 14:09:25 +0200 Subject: [PATCH 21/31] Combine tuple prototype and getter builtins. --- .../builtins/TuplePrototypeBuiltinsTest.java | 7 ++ .../js/builtins/TuplePrototypeBuiltins.java | 37 +++++++++- .../TuplePrototypeGetterBuiltins.java | 69 ------------------- .../truffle/js/runtime/builtins/JSTuple.java | 3 +- 4 files changed, 42 insertions(+), 74 deletions(-) delete mode 100644 graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TuplePrototypeGetterBuiltins.java diff --git a/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/TuplePrototypeBuiltinsTest.java b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/TuplePrototypeBuiltinsTest.java index c41b3dba771..5245c09e27d 100644 --- a/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/TuplePrototypeBuiltinsTest.java +++ b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/TuplePrototypeBuiltinsTest.java @@ -85,6 +85,13 @@ private static void expectError(String sourceText, String expectedMessage) { } } + @Test + public void testLength() { + assertEquals(0, execute("#[].length").asInt()); + assertEquals(3, execute("#[1, 2, 3].length").asInt()); + assertEquals(3, execute("Object(#[1, 2, 3]).length").asInt()); + } + @Test public void testValueOf() { assertTrue(execute("typeof #[].valueOf() === 'tuple'").asBoolean()); diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TuplePrototypeBuiltins.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TuplePrototypeBuiltins.java index be1fb0aa9c6..ae00c0b662b 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TuplePrototypeBuiltins.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TuplePrototypeBuiltins.java @@ -97,6 +97,7 @@ import static com.oracle.truffle.js.builtins.TuplePrototypeBuiltinsFactory.JSTupleFlatMapNodeGen; import static com.oracle.truffle.js.builtins.TuplePrototypeBuiltinsFactory.JSTupleFlatNodeGen; import static com.oracle.truffle.js.builtins.TuplePrototypeBuiltinsFactory.JSTupleIteratorNodeGen; +import static com.oracle.truffle.js.builtins.TuplePrototypeBuiltinsFactory.JSTupleLengthGetterNodeGen; import static com.oracle.truffle.js.builtins.TuplePrototypeBuiltinsFactory.JSTupleMapNodeGen; import static com.oracle.truffle.js.builtins.TuplePrototypeBuiltinsFactory.JSTuplePoppedNodeGen; import static com.oracle.truffle.js.builtins.TuplePrototypeBuiltinsFactory.JSTuplePushedNodeGen; @@ -122,6 +123,7 @@ protected TuplePrototypeBuiltins() { } public enum TuplePrototype implements BuiltinEnum { + length(0), valueOf(0), popped(0), pushed(1), @@ -154,21 +156,28 @@ public enum TuplePrototype implements BuiltinEnum { values(0), with(2); - private final int length; + private final int len; TuplePrototype(int length) { - this.length = length; + this.len = length; } @Override public int getLength() { - return length; + return len; + } + + @Override + public boolean isGetter() { + return this == length; } } @Override protected Object createNode(JSContext context, JSBuiltin builtin, boolean construct, boolean newTarget, TuplePrototype builtinEnum) { switch (builtinEnum) { + case length: + return JSTupleLengthGetterNodeGen.create(context, builtin, args().withThis().createArgumentNodes(context)); case valueOf: return JSTupleValueOfNodeGen.create(context, builtin, args().withThis().createArgumentNodes(context)); case popped: @@ -235,6 +244,28 @@ protected Object createNode(JSContext context, JSBuiltin builtin, boolean constr return null; } + public abstract static class JSTupleLengthGetterNode extends JSBuiltinNode { + + public JSTupleLengthGetterNode(JSContext context, JSBuiltin builtin) { + super(context, builtin); + } + + @Specialization + protected long doTuple(Tuple thisObj) { + return thisObj.getArraySize(); + } + + @Specialization(guards = {"isJSTuple(thisObj)"}) + protected long doJSTuple(DynamicObject thisObj) { + return JSTuple.valueOf(thisObj).getArraySize(); + } + + @Fallback + protected long doOther(Object thisObj) { + throw Errors.createTypeErrorIncompatibleReceiver(thisObj); + } + } + public abstract static class JSTupleToStringNode extends JSBuiltinNode { public JSTupleToStringNode(JSContext context, JSBuiltin builtin) { diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TuplePrototypeGetterBuiltins.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TuplePrototypeGetterBuiltins.java deleted file mode 100644 index 8d18d0c1688..00000000000 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TuplePrototypeGetterBuiltins.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.oracle.truffle.js.builtins; - -import com.oracle.truffle.api.dsl.Fallback; -import com.oracle.truffle.api.dsl.Specialization; -import com.oracle.truffle.api.object.DynamicObject; -import com.oracle.truffle.js.nodes.function.JSBuiltin; -import com.oracle.truffle.js.nodes.function.JSBuiltinNode; -import com.oracle.truffle.js.runtime.Errors; -import com.oracle.truffle.js.runtime.JSContext; -import com.oracle.truffle.js.runtime.Tuple; -import com.oracle.truffle.js.runtime.builtins.BuiltinEnum; -import com.oracle.truffle.js.runtime.builtins.JSTuple; - -/** - * Contains accessor builtins for Tuple.prototype. - */ -public final class TuplePrototypeGetterBuiltins extends JSBuiltinsContainer.SwitchEnum { - - public static final JSBuiltinsContainer BUILTINS = new TuplePrototypeGetterBuiltins(); - - protected TuplePrototypeGetterBuiltins() { - super(JSTuple.PROTOTYPE_NAME, TuplePrototype.class); - } - - public enum TuplePrototype implements BuiltinEnum { - length; - - @Override - public int getLength() { - return 0; - } - - @Override - public boolean isGetter() { - return true; - } - } - - @Override - protected Object createNode(JSContext context, JSBuiltin builtin, boolean construct, boolean newTarget, TuplePrototype builtinEnum) { - switch (builtinEnum) { - case length: - return TuplePrototypeGetterBuiltinsFactory.LengthAccessorNodeGen.create(context, builtin, args().withThis().fixedArgs(1).createArgumentNodes(context)); - } - return null; - } - - public abstract static class LengthAccessor extends JSBuiltinNode { - - public LengthAccessor(JSContext context, JSBuiltin builtin) { - super(context, builtin); - } - - @Specialization - protected long doTuple(Tuple thisObj) { - return thisObj.getArraySize(); - } - - @Specialization(guards = {"isJSTuple(thisObj)"}) - protected long doJSTuple(DynamicObject thisObj) { - return JSTuple.valueOf(thisObj).getArraySize(); - } - - @Fallback - protected void doObject(Object thisObj) { - Errors.createTypeErrorIncompatibleReceiver(thisObj); - } - } -} diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSTuple.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSTuple.java index c7de11aeaf8..fce268a5d22 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSTuple.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSTuple.java @@ -46,7 +46,6 @@ import com.oracle.truffle.api.object.Shape; import com.oracle.truffle.js.builtins.TupleFunctionBuiltins; import com.oracle.truffle.js.builtins.TuplePrototypeBuiltins; -import com.oracle.truffle.js.builtins.TuplePrototypeGetterBuiltins; import com.oracle.truffle.js.runtime.JSContext; import com.oracle.truffle.js.runtime.JSRealm; import com.oracle.truffle.js.runtime.JSRuntime; @@ -108,7 +107,7 @@ public DynamicObject createPrototype(final JSRealm realm, DynamicObject ctor) { JSObjectUtil.putToStringTag(prototype, CLASS_NAME); // Sets the Tuple.prototype.length accessor property. - JSObjectUtil.putBuiltinAccessorProperty(prototype, LENGTH, realm.lookupAccessor(TuplePrototypeGetterBuiltins.BUILTINS, LENGTH), JSAttributes.notConfigurableNotEnumerableNotWritable()); + JSObjectUtil.putBuiltinAccessorProperty(prototype, LENGTH, realm.lookupAccessor(TuplePrototypeBuiltins.BUILTINS, LENGTH)); // The initial value of the @@iterator property is the same function object as the initial value of the Tuple.prototype.values property. putDataProperty(context, prototype, Symbol.SYMBOL_ITERATOR, JSDynamicObject.getOrNull(prototype, "values"), JSAttributes.getDefaultNotEnumerable()); From e7b3db833618d7852e85bd6b4499e27af572058a Mon Sep 17 00:00:00 2001 From: Christian Aistleitner Date: Sun, 16 May 2021 20:46:57 +0200 Subject: [PATCH 22/31] Update comparison nodes/utils and add unit tests. --- .../js/record-tuple/tuple.js | 34 +++-- .../test/comparison/RecordEqualityTest.java | 125 ++++++++++++++++++ .../js/test/comparison/TupleEqualityTest.java | 125 ++++++++++++++++++ .../js/test/runtime/JSRuntimeTest.java | 12 ++ .../helper/JSCollectionsNormalizeNode.java | 25 ++++ .../truffle/js/nodes/binary/JSEqualNode.java | 13 +- .../js/nodes/binary/JSIdenticalNode.java | 59 ++++++++- .../oracle/truffle/js/runtime/JSRuntime.java | 75 ++++++++++- .../com/oracle/truffle/js/runtime/Record.java | 51 ++++++- .../com/oracle/truffle/js/runtime/Tuple.java | 2 +- 10 files changed, 489 insertions(+), 32 deletions(-) create mode 100644 graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/comparison/RecordEqualityTest.java create mode 100644 graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/comparison/TupleEqualityTest.java diff --git a/graal-js/src/com.oracle.truffle.js.test/js/record-tuple/tuple.js b/graal-js/src/com.oracle.truffle.js.test/js/record-tuple/tuple.js index c34ad7af651..464958c43f4 100644 --- a/graal-js/src/com.oracle.truffle.js.test/js/record-tuple/tuple.js +++ b/graal-js/src/com.oracle.truffle.js.test/js/record-tuple/tuple.js @@ -17,13 +17,13 @@ let a, b, c, d, e, f; /* * Test 1: - * valid tuple literals + * valid tuple literals - simple parser syntax test */ -const t1a = #[]; -const t1b = #[1, 2]; -const t1c = #[1, 2, #[3]]; -// TODO: const t1d = #[1, 2, #{ a: 3 }]; -const t1e = #[...t1b, 3]; +a = #[]; +b = #[1, 2]; +c = #[1, 2, #[3]]; +d = #[1, 2, #{ a: 3 }]; +e = #[...b, 3]; /* * Test 2: @@ -45,8 +45,8 @@ assertSame(#[0], y); * Test 4: * typeof */ -const t4 = 2; -assertSame("tuple", typeof #[t4]); +a = 2; +assertSame("tuple", typeof #[a]); assertSame("tuple", typeof #[]); assertSame("tuple", typeof #[#[]]); assertSame("tuple", typeof #[1, 2]); @@ -75,10 +75,6 @@ assertThrows(function() { * Test 7: * Tuple function */ -assertSame(Tuple().toString(), #[].toString()); -assertSame(Tuple(1, 2).toString(), #[1, 2].toString()); -assertSame(Tuple(1, #[2]).toString(), #[1, #[2]].toString()); -// TODO: remove .toString() calls above / remove above assertSame(Tuple(), #[]); assertSame(Tuple(1, 2), #[1, 2]); assertSame(Tuple(1, Tuple(2)), #[1, #[2]]); @@ -100,21 +96,21 @@ assertThrows(function() { */ assertTrue(#[1] === #[1]); assertTrue(#[1, 2] === #[1, 2]); -// TODO: assertTrue(Object(#[1, 2]) !== Object(#[1, 2])); +assertTrue(Object(#[1, 2]) !== Object(#[1, 2])); assertTrue(#[-0] === #[+0]); -// TODO: assertTrue(#[NaN] === #[NaN]); +assertTrue(#[NaN] === #[NaN]); assertTrue(#[-0] == #[+0]); -// TODO: assertTrue(#[NaN] == #[NaN]); +assertTrue(#[NaN] == #[NaN]); assertTrue(#[1] != #["1"]); -// TODO: assertTrue(!Object.is(#[-0], #[+0])); -// TODO: assertTrue(Object.is(#[NaN], #[NaN])); +assertTrue(!Object.is(#[-0], #[+0])); +assertTrue(Object.is(#[NaN], #[NaN])); // Map keys are compared with the SameValueZero algorithm -// TODO: assertTrue(new Map().set(#[1], true).get(#[1])); -// TODO: assertTrue(new Map().set(#[-0], true).get(#[0])); +assertTrue(new Map().set(#[1], true).get(#[1])); +assertTrue(new Map().set(#[-0], true).get(#[0])); /* * Test 10: diff --git a/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/comparison/RecordEqualityTest.java b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/comparison/RecordEqualityTest.java new file mode 100644 index 00000000000..051946fbac7 --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/comparison/RecordEqualityTest.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.truffle.js.test.comparison; + +import com.oracle.truffle.js.lang.JavaScriptLanguage; +import com.oracle.truffle.js.runtime.JSContextOptions; +import com.oracle.truffle.js.test.JSTest; +import org.graalvm.polyglot.Context; +import org.graalvm.polyglot.Source; +import org.graalvm.polyglot.Value; +import org.junit.Test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class RecordEqualityTest { + + private static final String testName = "record-equality-test"; + + private static Value execute(String sourceText) { + try (Context context = JSTest.newContextBuilder() + .option(JSContextOptions.ECMASCRIPT_VERSION_NAME, "2022") + .build()) { + return context.eval(Source.newBuilder(JavaScriptLanguage.ID, sourceText, testName).buildLiteral()); + } + } + + @Test + public void testEqual() { + assertTrue(execute("#{ a: 1 } == #{ a: 1 }").asBoolean()); + assertFalse(execute("#{ a: 1 } == #{}").asBoolean()); + + assertTrue(execute("#{ a: -0 } == #{ a: +0 }").asBoolean()); + assertTrue(execute("#{ a: NaN } == #{ a: NaN }").asBoolean()); + + assertFalse(execute("#{ a: 0 } == #{ a: '0' }").asBoolean()); + assertFalse(execute("#{ a: 0 } == #{ a: false }").asBoolean()); + assertFalse(execute("#{ a: '' } == #{ a: false }").asBoolean()); + } + + @Test + public void testIdentical() { + assertTrue(execute("#{ a: 1 } === #{ a: 1 }").asBoolean()); + assertFalse(execute("#{ a: 1 } === #{}").asBoolean()); + + assertTrue(execute("#{ a: -0 } === #{ a: +0 }").asBoolean()); + assertTrue(execute("#{ a: NaN } === #{ a: NaN }").asBoolean()); + + assertFalse(execute("#{ a: 0 } === #{ a: '0' }").asBoolean()); + assertFalse(execute("#{ a: 0 } === #{ a: false }").asBoolean()); + assertFalse(execute("#{ a: '' } === #{ a: false }").asBoolean()); + } + + @Test + public void testSameValue() { + assertTrue(execute("Object.is(#{ a: 1 }, #{ a: 1 })").asBoolean()); + assertFalse(execute("Object.is(#{ a: 1 }, #{})").asBoolean()); + + assertFalse(execute("Object.is(#{ a: -0 }, #{ a: +0 })").asBoolean()); + assertTrue(execute("Object.is(#{ a: NaN }, #{ a: NaN })").asBoolean()); + + assertFalse(execute("Object.is(#{ a: 0 }, #{ a: '0' })").asBoolean()); + assertFalse(execute("Object.is(#{ a: 0 }, #{ a: false })").asBoolean()); + assertFalse(execute("Object.is(#{ a: '' }, #{ a: false })").asBoolean()); + } + + @Test + public void testSameValueZero() { + assertTrue(execute("Array.of(#{ a: 1 }).includes(#{ a: 1 })").asBoolean()); + assertFalse(execute("Array.of(#{ a: 1 }).includes(#{})").asBoolean()); + + assertTrue(execute("Array.of(#{ a: -0 }).includes(#{ a: +0 })").asBoolean()); + assertTrue(execute("Array.of(#{ a: NaN }).includes(#{ a: NaN })").asBoolean()); + + assertFalse(execute("Array.of(#{ a: 0 }).includes(#{ a: '0' })").asBoolean()); + assertFalse(execute("Array.of(#{ a: 0 }).includes(#{ a: false })").asBoolean()); + assertFalse(execute("Array.of(#{ a: '' }).includes(#{ a: false })").asBoolean()); + } + + @Test + public void testJSHashMap() { + // Map/Set keys are compared using the SameValueZero algorithm, but the actual graal-js implementations differs + // as keys are being normalized (see JSCollectionsNormalizeNode) before accessing the internal JSHashMap. + assertTrue(execute("new Map().set(#{ a: -0 }, true).get(#{ a: 0 }) === true").asBoolean()); + assertTrue(execute("new Set().add(#{ a: -0 }).has(#{ a: 0 })").asBoolean()); + } +} diff --git a/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/comparison/TupleEqualityTest.java b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/comparison/TupleEqualityTest.java new file mode 100644 index 00000000000..6c9940e6dcb --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/comparison/TupleEqualityTest.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.truffle.js.test.comparison; + +import com.oracle.truffle.js.lang.JavaScriptLanguage; +import com.oracle.truffle.js.runtime.JSContextOptions; +import com.oracle.truffle.js.test.JSTest; +import org.graalvm.polyglot.Context; +import org.graalvm.polyglot.Source; +import org.graalvm.polyglot.Value; +import org.junit.Test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class TupleEqualityTest { + + private static final String testName = "tuple-equality-test"; + + private static Value execute(String sourceText) { + try (Context context = JSTest.newContextBuilder() + .option(JSContextOptions.ECMASCRIPT_VERSION_NAME, "2022") + .build()) { + return context.eval(Source.newBuilder(JavaScriptLanguage.ID, sourceText, testName).buildLiteral()); + } + } + + @Test + public void testEqual() { + assertTrue(execute("#['test'] == #['test']").asBoolean()); + assertFalse(execute("#[1, 2, 3] == #[4, 5]").asBoolean()); + + assertTrue(execute("#[-0] == #[+0]").asBoolean()); + assertTrue(execute("#[NaN] == #[NaN]").asBoolean()); + + assertFalse(execute("#[0] == #['0']").asBoolean()); + assertFalse(execute("#[0] == #[false]").asBoolean()); + assertFalse(execute("#[''] == #[false]").asBoolean()); + } + + @Test + public void testIdentical() { + assertTrue(execute("#['test'] === #['test']").asBoolean()); + assertFalse(execute("#[1, 2, 3] === #[4, 5]").asBoolean()); + + assertTrue(execute("#[-0] === #[+0]").asBoolean()); + assertTrue(execute("#[NaN] === #[NaN]").asBoolean()); + + assertFalse(execute("#[0] === #['0']").asBoolean()); + assertFalse(execute("#[0] === #[false]").asBoolean()); + assertFalse(execute("#[''] === #[false]").asBoolean()); + } + + @Test + public void testSameValue() { + assertTrue(execute("Object.is(#['test'], #['test'])").asBoolean()); + assertFalse(execute("Object.is(#[1, 2, 3], #[4, 5])").asBoolean()); + + assertFalse(execute("Object.is(#[-0], #[+0])").asBoolean()); + assertTrue(execute("Object.is(#[NaN], #[NaN])").asBoolean()); + + assertFalse(execute("Object.is(#[0], #['0'])").asBoolean()); + assertFalse(execute("Object.is(#[0], #[false])").asBoolean()); + assertFalse(execute("Object.is(#[''], #[false])").asBoolean()); + } + + @Test + public void testSameValueZero() { + assertTrue(execute("Array.of(#['test']).includes(#['test'])").asBoolean()); + assertFalse(execute("Array.of(#[1, 2, 3]).includes(#[4, 5])").asBoolean()); + + assertTrue(execute("Array.of(#[-0]).includes(#[+0])").asBoolean()); + assertTrue(execute("Array.of(#[NaN]).includes(#[NaN])").asBoolean()); + + assertFalse(execute("Array.of(#[0]).includes(#['0'])").asBoolean()); + assertFalse(execute("Array.of(#[0]).includes(#[false])").asBoolean()); + assertFalse(execute("Array.of(#['']).includes(#[false])").asBoolean()); + } + + @Test + public void testJSHashMap() { + // Map/Set keys are compared using the SameValueZero algorithm, but the actual graal-js implementations differs + // as keys are being normalized (see JSCollectionsNormalizeNode) before accessing the internal JSHashMap. + assertTrue(execute("new Map().set(#[-0], true).get(#[0]) === true").asBoolean()); + assertTrue(execute("new Set().add(#[-0]).has(#[0])").asBoolean()); + } +} diff --git a/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/runtime/JSRuntimeTest.java b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/runtime/JSRuntimeTest.java index 4e0c15b3ee5..d326977a9d5 100644 --- a/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/runtime/JSRuntimeTest.java +++ b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/runtime/JSRuntimeTest.java @@ -147,6 +147,18 @@ public void testIdentical() { assertTrue(JSRuntime.identical(env.asGuestValue(BigInteger.ONE), env.asGuestValue(BigInteger.ONE))); } + @Test + public void testIsSameValue() { + assertTrue(JSRuntime.isSameValue(Double.NaN, Double.NaN)); + assertFalse(JSRuntime.isSameValue(-0.0, 0)); + } + + @Test + public void testIsSameValueZero() { + assertTrue(JSRuntime.isSameValueZero(Double.NaN, Double.NaN)); + assertTrue(JSRuntime.isSameValueZero(-0.0, 0)); + } + @Test public void testNumberToStringWorksForSafeInteger() { assertEquals("42", JSRuntime.numberToString(SafeInteger.valueOf(42))); diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/helper/JSCollectionsNormalizeNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/helper/JSCollectionsNormalizeNode.java index 90b8dfc7feb..8e1cdee7727 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/helper/JSCollectionsNormalizeNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/helper/JSCollectionsNormalizeNode.java @@ -40,6 +40,7 @@ */ package com.oracle.truffle.js.builtins.helper; +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.GenerateUncached; import com.oracle.truffle.api.dsl.ImportStatic; @@ -51,11 +52,16 @@ import com.oracle.truffle.js.nodes.JavaScriptBaseNode; import com.oracle.truffle.js.runtime.BigInt; import com.oracle.truffle.js.runtime.JSConfig; +import com.oracle.truffle.js.runtime.Record; import com.oracle.truffle.js.runtime.Symbol; +import com.oracle.truffle.js.runtime.Tuple; import com.oracle.truffle.js.runtime.builtins.JSSet; import com.oracle.truffle.js.runtime.interop.JSInteropUtil; import com.oracle.truffle.js.runtime.objects.JSLazyString; +import java.util.Map; +import java.util.stream.Collectors; + /** * This implements behavior for Collections of ES6. Instead of adhering to the SameValueNull * algorithm, we normalize the key (e.g., transform the double value 1.0 to an integer value of 1). @@ -111,6 +117,25 @@ public BigInt doBigInt(BigInt bigInt) { return bigInt; } + @TruffleBoundary + @Specialization + public Record doRecord(Record record) { + Map fields = record.getEntries().stream().collect(Collectors.toMap( + it -> it.getKey(), + it -> execute(it.getValue()) + )); + return Record.create(fields); + } + + @Specialization + public Tuple doTuple(Tuple tuple) { + Object[] elements = tuple.getElements(); + for (int i = 0; i < elements.length; i++) { + elements[i] = execute(elements[i]); + } + return Tuple.create(elements); + } + @Specialization(guards = "isForeignObject(object)", limit = "InteropLibraryLimit") public Object doForeignObject(Object object, @CachedLibrary("object") InteropLibrary interop, diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/binary/JSEqualNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/binary/JSEqualNode.java index 3f6585cf986..685dcf72a74 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/binary/JSEqualNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/binary/JSEqualNode.java @@ -63,6 +63,7 @@ import com.oracle.truffle.js.runtime.Boundaries; import com.oracle.truffle.js.runtime.JSConfig; import com.oracle.truffle.js.runtime.JSRuntime; +import com.oracle.truffle.js.runtime.Record; import com.oracle.truffle.js.runtime.Symbol; import com.oracle.truffle.js.runtime.Tuple; import com.oracle.truffle.js.runtime.interop.JSInteropUtil; @@ -339,9 +340,15 @@ protected boolean doStringNumber(String a, Object b) { } @Specialization - protected static boolean doTuple(Tuple a, Tuple b) { - // TODO: maybe compare tuples using a node tree - return a.equals(b); + protected static boolean doRecord(Record a, Record b, + @Cached("createStrictEqualityComparison()") @Shared("strictEquality") JSIdenticalNode strictEqualityNode) { + return strictEqualityNode.executeBoolean(a, b); + } + + @Specialization + protected static boolean doTuple(Tuple a, Tuple b, + @Cached("createStrictEqualityComparison()") @Shared("strictEquality") JSIdenticalNode strictEqualityNode) { + return strictEqualityNode.executeBoolean(a, b); } @Fallback diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/binary/JSIdenticalNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/binary/JSIdenticalNode.java index e6da2878e08..68c13e36445 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/binary/JSIdenticalNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/binary/JSIdenticalNode.java @@ -42,6 +42,7 @@ import java.util.Set; +import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.Cached.Shared; import com.oracle.truffle.api.dsl.Fallback; @@ -67,6 +68,7 @@ import com.oracle.truffle.js.runtime.BigInt; import com.oracle.truffle.js.runtime.JSConfig; import com.oracle.truffle.js.runtime.JSRuntime; +import com.oracle.truffle.js.runtime.Record; import com.oracle.truffle.js.runtime.Symbol; import com.oracle.truffle.js.runtime.Tuple; import com.oracle.truffle.js.runtime.objects.JSLazyString; @@ -83,6 +85,8 @@ public abstract class JSIdenticalNode extends JSCompareNode { protected final int type; + @Child private JSIdenticalNode sameValueZeroNode; + protected JSIdenticalNode(JavaScriptNode left, JavaScriptNode right, int type) { super(left, right); this.type = type; @@ -287,9 +291,58 @@ protected static boolean doSymbol(Symbol a, Symbol b) { } @Specialization - protected static boolean doTuple(Tuple a, Tuple b) { - // TODO: maybe compare tuples using a node tree - return a.equals(b); + protected boolean doRecord(Record a, Record b) { + String[] aKeys = a.getKeys(); + String[] bKeys = b.getKeys(); + if (aKeys.length != bKeys.length) { + return false; + } + for (int i = 0; i < aKeys.length; i++) { + if (!aKeys[i].equals(bKeys[i])) { + return false; + } + Object aValue = a.get(aKeys[i]); + Object bValue = b.get(aKeys[i]); + if (type == STRICT_EQUALITY_COMPARISON) { + if (!sameValueZero(aValue, bValue)) { + return false; + } + } else { + if (!executeBoolean(aValue, bValue)) { + return false; + } + } + } + return true; + } + + @Specialization + protected boolean doTuple(Tuple a, Tuple b) { + if (a.getArraySize() != b.getArraySize()) { + return false; + } + for (long k = 0; k < a.getArraySize(); k++) { + Object aValue = a.getElement(k); + Object bValue = b.getElement(k); + if (type == STRICT_EQUALITY_COMPARISON) { + if (!sameValueZero(aValue, bValue)) { + return false; + } + } else { + if (!executeBoolean(aValue, bValue)) { + return false; + } + } + } + return true; + } + + private boolean sameValueZero(Object left, Object right) { + if (sameValueZeroNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + sameValueZeroNode = insert(JSIdenticalNode.createSameValueZero()); + } + return sameValueZeroNode.executeBoolean(left, right); } @Specialization(guards = {"isBoolean(a) != isBoolean(b)"}) diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSRuntime.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSRuntime.java index 29871f047fa..1eada445a74 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSRuntime.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSRuntime.java @@ -43,6 +43,7 @@ import java.lang.reflect.Array; import java.util.ArrayList; import java.util.List; +import java.util.function.BiFunction; import com.oracle.truffle.api.CompilerAsserts; import com.oracle.truffle.api.CompilerDirectives; @@ -1574,6 +1575,9 @@ public static boolean isSameValue(Object x, Object y) { } else if (isNumber(x) && isNumber(y)) { double xd = doubleValue((Number) x); double yd = doubleValue((Number) y); + if (Double.isNaN(xd)) { + return Double.isNaN(yd); + } return Double.compare(xd, yd) == 0; } else if (isString(x) && isString(y)) { return x.toString().equals(y.toString()); @@ -1581,10 +1585,68 @@ public static boolean isSameValue(Object x, Object y) { return (boolean) x == (boolean) y; } else if (isBigInt(x) && isBigInt(y)) { return ((BigInt) x).compareTo((BigInt) y) == 0; + } else if (x instanceof Record && y instanceof Record) { + return recordEqual((Record) x, (Record) y, JSRuntime::isSameValue); + } else if (x instanceof Tuple && y instanceof Tuple) { + return tupleEqual((Tuple) x, (Tuple) y, JSRuntime::isSameValue); } return x == y; } + private static boolean recordEqual(Record x, Record y, BiFunction elementEqual) { + String[] xKeys = x.getKeys(); + String[] yKeys = y.getKeys(); + if (xKeys.length != yKeys.length) { + return false; + } + for (int i = 0; i < xKeys.length; i++) { + Object xValue = x.get(xKeys[i]); + Object yValue = y.get(yKeys[i]); + if (!elementEqual.apply(xValue, yValue)) { + return false; + } + } + return true; + } + + private static boolean tupleEqual(Tuple x, Tuple y, BiFunction elementEqual) { + if (x.getArraySize() != y.getArraySize()) { + return false; + } + for (long k = 0; k < x.getArraySize(); k++) { + Object xValue = x.getElement(k); + Object yValue = y.getElement(k); + if (!elementEqual.apply(xValue, yValue)) { + return false; + } + } + return true; + } + + /** + * SameValueZero differs from SameValue only in its treatment of +0 and -0, + * and the treatment of those values inside a Record or Tuple. + */ + @TruffleBoundary + public static boolean isSameValueZero(Object x, Object y) { + if (isNumber(x) && isNumber(y)) { + double xd = doubleValue((Number) x); + double yd = doubleValue((Number) y); + if (Double.isNaN(xd)) { + return Double.isNaN(yd); + } + if (xd == 0 && yd == 0) { + return true; + } + return Double.compare(xd, yd) == 0; + } else if (x instanceof Record && y instanceof Record) { + return recordEqual((Record) x, (Record) y, JSRuntime::isSameValueZero); + } else if (x instanceof Tuple && y instanceof Tuple) { + return tupleEqual((Tuple) x, (Tuple) y, JSRuntime::isSameValueZero); + } + return isSameValue(x, y); + } + @TruffleBoundary public static boolean equal(Object a, Object b) { if (a == b) { @@ -1617,8 +1679,10 @@ public static boolean equal(Object a, Object b) { return equalBigIntAndNumber((BigInt) b, (Number) a); } else if (isBigInt(a) && isJavaNumber(b)) { return equalBigIntAndNumber((BigInt) a, (Number) b); - } else if (isTuple(a) && isTuple(b)) { - return a.equals(b); + } else if (a instanceof Record && b instanceof Record) { + return recordEqual((Record) a, (Record) b, JSRuntime::isSameValueZero); + } else if (a instanceof Tuple && b instanceof Tuple) { + return tupleEqual((Tuple) a, (Tuple) b, JSRuntime::isSameValueZero); } else if (a instanceof Boolean) { return equal(booleanToNumber((Boolean) a), b); } else if (b instanceof Boolean) { @@ -1708,8 +1772,11 @@ public static boolean identical(Object a, Object b) { if (isBigInt(a) && isBigInt(b)) { return a.equals(b); } - if (isTuple(a) && isTuple(b)) { - return a.equals(b); + if (a instanceof Record && b instanceof Record) { + return recordEqual((Record) a, (Record) b, JSRuntime::isSameValueZero); + } + if (a instanceof Tuple && b instanceof Tuple) { + return tupleEqual((Tuple) a, (Tuple) b, JSRuntime::isSameValueZero); } if (isJavaNumber(a) && isJavaNumber(b)) { if (a instanceof Integer && b instanceof Integer) { diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Record.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Record.java index a4d9c1ede22..b843e1a1b76 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Record.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Record.java @@ -40,6 +40,7 @@ */ package com.oracle.truffle.js.runtime; +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.CompilerDirectives.ValueType; import com.oracle.truffle.api.interop.InteropLibrary; import com.oracle.truffle.api.interop.TruffleObject; @@ -69,15 +70,61 @@ public static Record create(Map map) { return new Record(map); } + @TruffleBoundary public Object get(String key) { return map.get(key); } + @TruffleBoundary public boolean hasKey(String key) { return map.containsKey(key); } - public Set getKeys() { - return map.keySet(); + @TruffleBoundary + public String[] getKeys() { + return map.keySet().toArray(new String[]{}); + } + + @TruffleBoundary + public Set> getEntries() { + return map.entrySet(); + } + + @Override + public int hashCode() { + return map.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + return equals((Record) obj); + } + + @TruffleBoundary + public boolean equals(Record other) { + if (map.size() != other.map.size()) { + return false; + } + for (String key : map.keySet()) { + if (!other.map.containsKey(key)) { + return false; + } + if (!JSRuntime.isSameValueZero( + map.get(key), + other.map.get(key) + )) { + return false; + } + } + return true; } } diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Tuple.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Tuple.java index ec3e42a8ae9..ad0cb15df79 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Tuple.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Tuple.java @@ -69,7 +69,7 @@ public boolean equals(Tuple other) { return false; } for (int i = 0; i < value.length; i++) { - if (!JSRuntime.identical(value[i], other.value[i])) { + if (!JSRuntime.isSameValueZero(value[i], other.value[i])) { return false; } } From 8fc4ba8c7a2ce95e7514667b7d2f76d0f20420be Mon Sep 17 00:00:00 2001 From: Christian Aistleitner Date: Mon, 17 May 2021 13:52:14 +0200 Subject: [PATCH 23/31] Cleanup record and tuple literal nodes. --- .../nodes/record/RecordLiteralNodeTest.java | 43 +++++ .../js/nodes/record/RecordLiteralNode.java | 165 +++++++++--------- .../js/nodes/tuples/TupleLiteralNode.java | 137 +++++++++------ .../com/oracle/truffle/js/runtime/Record.java | 2 +- 4 files changed, 208 insertions(+), 139 deletions(-) create mode 100644 graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/nodes/record/RecordLiteralNodeTest.java diff --git a/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/nodes/record/RecordLiteralNodeTest.java b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/nodes/record/RecordLiteralNodeTest.java new file mode 100644 index 00000000000..91fb5b1c8d3 --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/nodes/record/RecordLiteralNodeTest.java @@ -0,0 +1,43 @@ +package com.oracle.truffle.js.test.nodes.record; + +import com.oracle.truffle.js.lang.JavaScriptLanguage; +import com.oracle.truffle.js.runtime.JSContextOptions; +import com.oracle.truffle.js.test.JSTest; +import org.graalvm.polyglot.Context; +import org.graalvm.polyglot.Source; +import org.graalvm.polyglot.Value; +import org.junit.Ignore; +import org.junit.Test; + +import static org.junit.Assert.assertTrue; + +public class RecordLiteralNodeTest { + + private static final String testName = "record-literal-node-test"; + + private static Value execute(String sourceText) { + try (Context context = JSTest.newContextBuilder() + .option(JSContextOptions.ECMASCRIPT_VERSION_NAME, "2022") + .build()) { + return context.eval(Source.newBuilder(JavaScriptLanguage.ID, sourceText, testName).buildLiteral()); + } + } + + @Test + public void testSpread_Polyfill() { + assertTrue(execute("#{...['test']} === #{'0': 'test'}").asBoolean()); + } + + // TODO: re-evaluate, check proposal for changes + // let a = ['test']; + // let b = {...a} + // let c = #{...a} + // console.log(a.length); // "1" + // console.log(b.length); // "undefined" + // console.log(c.length); // "1" according to proposal spec BUT "undefined" according to proposal polyfill + @Ignore + @Test + public void testSpread_Spec() { + assertTrue(execute("#{...['test']} === #{'0': 'test', length: 1}").asBoolean()); + } +} diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/record/RecordLiteralNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/record/RecordLiteralNode.java index eac86ea701e..51c20762bd5 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/record/RecordLiteralNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/record/RecordLiteralNode.java @@ -44,9 +44,11 @@ import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.instrumentation.Tag; import com.oracle.truffle.api.object.DynamicObject; -import com.oracle.truffle.js.nodes.JSGuards; +import com.oracle.truffle.js.builtins.helper.ListGetNode; +import com.oracle.truffle.js.builtins.helper.ListSizeNode; import com.oracle.truffle.js.nodes.JavaScriptBaseNode; import com.oracle.truffle.js.nodes.JavaScriptNode; +import com.oracle.truffle.js.nodes.access.JSGetOwnPropertyNode; import com.oracle.truffle.js.nodes.access.ReadElementNode; import com.oracle.truffle.js.nodes.cast.JSToObjectNode; import com.oracle.truffle.js.nodes.function.FunctionNameHolder; @@ -59,6 +61,7 @@ import com.oracle.truffle.js.runtime.Record; import com.oracle.truffle.js.runtime.Symbol; import com.oracle.truffle.js.runtime.objects.JSObject; +import com.oracle.truffle.js.runtime.objects.PropertyDescriptor; import java.util.List; import java.util.Map; @@ -71,35 +74,22 @@ public class RecordLiteralNode extends JavaScriptNode { @Children protected final AbstractRecordLiteralMemberNode[] elements; - protected RecordLiteralNode(JSContext context, AbstractRecordLiteralMemberNode[] elements) { + private RecordLiteralNode(JSContext context, AbstractRecordLiteralMemberNode[] elements) { this.context = context; this.elements = elements; } @Override - public Object execute(VirtualFrame frame) { + public Record execute(VirtualFrame frame) { Map entries = new TreeMap<>(); for (AbstractRecordLiteralMemberNode element : elements) { element.evaluate(frame, entries, context); } - return new Record(entries); + assert entries.values().stream().noneMatch(JSRuntime::isObject); + return Record.create(entries); } - @Override - public boolean hasTag(Class tag) { - if (tag == LiteralTag.class) { - return true; - } else { - return super.hasTag(tag); - } - } - - @Override - public Object getNodeObject() { - return JSTags.createNodeObjectDescriptor(LiteralTag.TYPE, LiteralTag.Type.RecordLiteral.name()); - } - - public static JavaScriptNode create(JSContext context, AbstractRecordLiteralMemberNode[] elements) { + public static RecordLiteralNode create(JSContext context, AbstractRecordLiteralMemberNode[] elements) { return new RecordLiteralNode(context, elements); } @@ -117,36 +107,25 @@ public static AbstractRecordLiteralMemberNode createSpreadMember(JavaScriptNode public abstract static class AbstractRecordLiteralMemberNode extends JavaScriptBaseNode { + /** + * Runtime Semantics: RecordPropertyDefinitionEvaluation + */ public abstract void evaluate(VirtualFrame frame, Map entries, JSContext context); /** - * Records & Tuples Proposal: 3.3.1 AddPropertyIntoRecordEntriesList - * - *
-         * 1. Assert: Type(entries) is List.
-         * 2. Assert: IsProperyKey(propName) is true.
-         * 3. If Type(propName) is Symbol, throw a TypeError exception.
-         * 4. If Type(value) is Object, throw a TypeError exception.
-         * 5. Add { [[Key]]: propType, [[Value]]: value } to entries.
-         * 6. Return entries.
-         * 
+ * Abstract operation AddPropertyIntoRecordEntriesList ( entries, propName, value ) */ - protected final void addEntry(Map entries, Object key, Object value) { + protected final void addPropertyIntoRecordEntriesList(Map entries, Object key, Object value) { assert JSRuntime.isPropertyKey(key); if (key instanceof Symbol) { throw Errors.createTypeError("Record may only have string as keys"); } - assert key instanceof String; - if (!JSRuntime.isJSPrimitive(value)) { + if (JSRuntime.isObject(value)) { throw Errors.createTypeError("Record may only contain primitive values"); } entries.put((String) key, value); } - protected boolean isAnonymousFunctionDefinition(JavaScriptNode expression) { - return expression instanceof FunctionNameHolder && ((FunctionNameHolder) expression).isAnonymous(); - } - protected abstract AbstractRecordLiteralMemberNode copyUninitialized(Set> materializedTags); public static AbstractRecordLiteralMemberNode[] cloneUninitialized(AbstractRecordLiteralMemberNode[] members, Set> materializedTags) { @@ -158,6 +137,11 @@ public static AbstractRecordLiteralMemberNode[] cloneUninitialized(AbstractRecor } } + /** + * RecordPropertyDefinition : + * IdentifierReference + * LiteralPropertyName : AssignmentExpression + */ private static class RecordLiteralMemberNode extends AbstractRecordLiteralMemberNode { protected final Object name; @@ -169,13 +153,10 @@ private static class RecordLiteralMemberNode extends AbstractRecordLiteralMember this.valueNode = valueNode; } - /** - * Records & Tuples Proposal: 7.1.1.4 Runtime Semantics: RecordPropertyDefinitionEvaluation - */ @Override public void evaluate(VirtualFrame frame, Map entries, JSContext context) { Object value = valueNode.execute(frame); - addEntry(entries, name, value); + addPropertyIntoRecordEntriesList(entries, name, value); } @Override @@ -184,6 +165,9 @@ protected AbstractRecordLiteralMemberNode copyUninitialized(Set entries, JSContext context) { Object key = keyNode.execute(frame); @@ -206,7 +191,7 @@ public void evaluate(VirtualFrame frame, Map entries, JSContext if (setFunctionName != null) { setFunctionName.execute(value, key); // NamedEvaluation } - addEntry(entries, key, value); + addPropertyIntoRecordEntriesList(entries, key, value); } @Override @@ -218,65 +203,39 @@ protected AbstractRecordLiteralMemberNode copyUninitialized(Set - * RecordPropertyDefinition: ...AssignmentExpression - * - * 1. Let exprValue be the result of evaluating AssignmentExpression. - * 2. Let source be ? GetValue(exprValue). - * 3. If source is undefined or null, return entries. - * 4. Let from be ! ToObject(source). - * 5. Let keys be ? from.[[OwnPropertyKeys]](). - * 6. For each element nextKey of keys in List order, do - * 7. Let value be from.[[Get]](nextKey). - * 9. Perform ? AddPropertyIntoRecordEntriesList(entries, nextKey, value). - * 10. Return entries. - * - */ @Override public void evaluate(VirtualFrame frame, Map entries, JSContext context) { Object source = valueNode.execute(frame); - if (JSGuards.isNullOrUndefined(source)) { + if (JSRuntime.isNullOrUndefined(source)) { return; } - Object from = toObject(source, context); - - List keys = JSObject.ownPropertyKeys((DynamicObject) from); - // TODO: Why is ListSizeNode used for getting list.size() ? - // TODO: Also ListGetNode for list.get(...) ? - // TODO: see com.oracle.truffle.js.nodes.access.CopyDataPropertiesNode.copyDataProperties - for (Object key : keys) { + DynamicObject from = (DynamicObject) toObject(source, context); + List keys = JSObject.ownPropertyKeys(from); + int size = listSize.execute(keys); + for (int i = 0; i < size; i++) { + Object key = listGet.execute(keys, i); assert JSRuntime.isPropertyKey(key); - Object value = get(from, key, context); - addEntry(entries, key, value); - - // TODO: when spreading members in object literals, [[Enumerable]] gets checked before adding it - // TODO: see com.oracle.truffle.js.nodes.access.CopyDataPropertiesNode.copyDataProperties - // TODO: see sample implementation below - // PropertyDescriptor desc = getOwnProperty.execute(source, key); - // if (desc != null && desc.getEnumerable()) { - // Object value = get(from, key, context); - // addMember(entries, key, value); - // } - // TODO: see test case below - // let a = [42]; - // let b = {...a} - // let c = #{...a} - // console.log(a.length); // "1" - // console.log(b.length); // "undefined" - // console.log(c.length); // "1" according to proposal spec BUT "undefined" according to proposal polyfill + PropertyDescriptor desc = getOwnProperty(from, key); + if (desc != null && desc.getEnumerable()) { + Object value = get(from, key, context); + addPropertyIntoRecordEntriesList(entries, key, value); + } } } @@ -288,6 +247,14 @@ private Object toObject(Object obj, JSContext context) { return toObjectNode.execute(obj); } + private PropertyDescriptor getOwnProperty(DynamicObject object, Object key) { + if (getOwnPropertyNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + getOwnPropertyNode = insert(JSGetOwnPropertyNode.create(false, true, false, false, true)); + } + return getOwnPropertyNode.execute(object, key); + } + private Object get(Object obj, Object key, JSContext context) { if (readElementNode == null) { CompilerDirectives.transferToInterpreterAndInvalidate(); @@ -303,4 +270,28 @@ protected AbstractRecordLiteralMemberNode copyUninitialized(Set clazz) { + return clazz == Record.class; + } + + @Override + public boolean hasTag(Class tag) { + if (tag == LiteralTag.class) { + return true; + } else { + return super.hasTag(tag); + } + } + + @Override + public Object getNodeObject() { + return JSTags.createNodeObjectDescriptor(LiteralTag.TYPE, LiteralTag.Type.RecordLiteral.name()); + } + + @Override + protected JavaScriptNode copyUninitialized(Set> materializedTags) { + return new RecordLiteralNode(context, AbstractRecordLiteralMemberNode.cloneUninitialized(elements, materializedTags)); + } } diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/TupleLiteralNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/TupleLiteralNode.java index 1e054c28e66..ecb8b8802f0 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/TupleLiteralNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/TupleLiteralNode.java @@ -1,10 +1,48 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ package com.oracle.truffle.js.nodes.tuples; -import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.instrumentation.Tag; import com.oracle.truffle.api.nodes.Node; -import com.oracle.truffle.api.object.DynamicObject; import com.oracle.truffle.api.profiles.BranchProfile; import com.oracle.truffle.js.nodes.JavaScriptNode; import com.oracle.truffle.js.nodes.access.GetIteratorNode; @@ -20,36 +58,19 @@ import com.oracle.truffle.js.runtime.objects.IteratorRecord; import com.oracle.truffle.js.runtime.util.SimpleArrayList; +import java.util.Arrays; import java.util.Set; public abstract class TupleLiteralNode extends JavaScriptNode { protected final JSContext context; - public TupleLiteralNode(TupleLiteralNode copy) { - this.context = copy.context; - } - protected TupleLiteralNode(JSContext context) { this.context = context; } @Override - public abstract Object execute(VirtualFrame frame); - - @Override - public boolean hasTag(Class tag) { - if (tag == LiteralTag.class) { - return true; - } else { - return super.hasTag(tag); - } - } - - @Override - public Object getNodeObject() { - return JSTags.createNodeObjectDescriptor(LiteralTag.TYPE, LiteralTag.Type.TupleLiteral.name()); - } + public abstract Tuple execute(VirtualFrame frame); public static JavaScriptNode create(JSContext context, JavaScriptNode[] elements) { if (elements == null || elements.length == 0) { @@ -73,7 +94,7 @@ private static JSConstantNode createEmptyTuple() { } private static JSConstantNode createConstantTuple(Object[] array) { - Tuple tuple = createTuple(array); + Tuple tuple = Tuple.create(array); return JSConstantNode.create(tuple); } @@ -82,7 +103,8 @@ private static Object[] resolveConstants(JavaScriptNode[] nodes) { for (int i = 0; i < values.length; i++) { JavaScriptNode node = nodes[i]; if (node instanceof JSConstantNode) { - values[i] = ((JSConstantNode) node).getValue(); + Object value = ((JSConstantNode) node).getValue(); + values[i] = requireNonObject(value); } else { return null; } @@ -90,12 +112,22 @@ private static Object[] resolveConstants(JavaScriptNode[] nodes) { return values; } - private static Tuple createTuple(Object[] array) { - for (Object element : array) { - if (!JSRuntime.isJSPrimitive(element)) { - throw Errors.createTypeError("Tuples cannot contain non-primitive values"); - } + /** + * Abstract operation AddValueToTupleSequenceList ( sequence, value ) + */ + static void addValueToTupleSequenceList(SimpleArrayList sequence, Object value, BranchProfile growProfile) { + sequence.add(requireNonObject(value), growProfile); + } + + private static Object requireNonObject(Object value) { + if (JSRuntime.isObject(value)) { + throw Errors.createTypeError("Tuples cannot contain non-primitive values"); } + return value; + } + + private static Tuple createTuple(Object[] array) { + assert Arrays.stream(array).noneMatch(JSRuntime::isObject); return Tuple.create(array); } @@ -103,17 +135,17 @@ private static class DefaultTupleLiteralNode extends TupleLiteralNode { @Children protected final JavaScriptNode[] elements; - DefaultTupleLiteralNode(JSContext context, JavaScriptNode[] elements) { + private DefaultTupleLiteralNode(JSContext context, JavaScriptNode[] elements) { super(context); this.elements = elements; } @Override - public Object execute(VirtualFrame frame) { - CompilerDirectives.transferToInterpreterAndInvalidate(); + public Tuple execute(VirtualFrame frame) { Object[] values = new Object[elements.length]; for (int i = 0; i < elements.length; i++) { - values[i] = elements[i].execute(frame); + Object value = elements[i].execute(frame); + values[i] = requireNonObject(value); } return createTuple(values); } @@ -128,26 +160,22 @@ private static final class DefaultTupleLiteralWithSpreadNode extends DefaultTupl private final BranchProfile growProfile = BranchProfile.create(); - DefaultTupleLiteralWithSpreadNode(JSContext context, JavaScriptNode[] elements) { + private DefaultTupleLiteralWithSpreadNode(JSContext context, JavaScriptNode[] elements) { super(context, elements); - assert elements.length > 0; } @Override - public Object execute(VirtualFrame frame) { - SimpleArrayList evaluatedElements = new SimpleArrayList<>(elements.length + JSConfig.SpreadArgumentPlaceholderCount); - for (int i = 0; i < elements.length; i++) { - Node node = elements[i]; - if (elements[i] instanceof WrapperNode) { - node = ((WrapperNode) elements[i]).getDelegateNode(); - } + public Tuple execute(VirtualFrame frame) { + SimpleArrayList sequence = new SimpleArrayList<>(elements.length + JSConfig.SpreadArgumentPlaceholderCount); + for (JavaScriptNode node : elements) { if (node instanceof SpreadTupleNode) { - ((SpreadTupleNode) node).executeToList(frame, evaluatedElements, growProfile); + ((SpreadTupleNode) node).executeToList(frame, sequence, growProfile); } else { - evaluatedElements.add(elements[i].execute(frame), growProfile); + Object value = node.execute(frame); + addValueToTupleSequenceList(sequence, value, growProfile); } } - return createTuple(evaluatedElements.toArray()); + return createTuple(sequence.toArray()); } @Override @@ -176,7 +204,7 @@ public void executeToList(VirtualFrame frame, SimpleArrayList toList, Br if (nextArg == null) { break; } - toList.add(nextArg, growProfile); + addValueToTupleSequenceList(toList, nextArg, growProfile); } } @@ -196,13 +224,20 @@ protected JavaScriptNode copyUninitialized(Set> materialize @Override public boolean isResultAlwaysOfType(Class clazz) { - return clazz == DynamicObject.class; + return clazz == Tuple.class; } - public enum TupleContentType { - Byte, - Integer, - Double, - Object + @Override + public boolean hasTag(Class tag) { + if (tag == LiteralTag.class) { + return true; + } else { + return super.hasTag(tag); + } + } + + @Override + public Object getNodeObject() { + return JSTags.createNodeObjectDescriptor(LiteralTag.TYPE, LiteralTag.Type.TupleLiteral.name()); } } diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Record.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Record.java index b843e1a1b76..4680149f96e 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Record.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Record.java @@ -62,7 +62,7 @@ public final class Record implements TruffleObject { private final TreeMap map; - public Record(Map map) { + private Record(Map map) { this.map = new TreeMap<>(map); } From 51166a8282a555a8d48b1bead29ed7bc3a62c2ec Mon Sep 17 00:00:00 2001 From: Christian Aistleitner Date: Mon, 17 May 2021 14:40:25 +0200 Subject: [PATCH 24/31] Fix build error. --- .../truffle/js/test/runtime/builtins/JSTupleTest.java | 1 + .../oracle/truffle/js/builtins/ObjectPrototypeBuiltins.java | 6 ++++++ .../com/oracle/truffle/js/runtime/builtins/JSRecord.java | 4 ++-- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/runtime/builtins/JSTupleTest.java b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/runtime/builtins/JSTupleTest.java index 0a718984dc1..7df2f5add47 100644 --- a/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/runtime/builtins/JSTupleTest.java +++ b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/runtime/builtins/JSTupleTest.java @@ -217,6 +217,7 @@ public void testPrototype() { assertFalse(desc.getWritable()); assertFalse(desc.getEnumerable()); assertTrue(desc.getConfigurable()); + assertEquals(desc.getValue(), "Tuple"); desc = JSObject.getOwnProperty(prototype, Symbol.SYMBOL_ITERATOR); assertTrue(desc.getWritable()); diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/ObjectPrototypeBuiltins.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/ObjectPrototypeBuiltins.java index 151458c7e42..4ccbff74e05 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/ObjectPrototypeBuiltins.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/ObjectPrototypeBuiltins.java @@ -80,6 +80,7 @@ import com.oracle.truffle.js.runtime.JSContext; import com.oracle.truffle.js.runtime.JSException; import com.oracle.truffle.js.runtime.JSRuntime; +import com.oracle.truffle.js.runtime.Record; import com.oracle.truffle.js.runtime.SafeInteger; import com.oracle.truffle.js.runtime.Symbol; import com.oracle.truffle.js.runtime.Tuple; @@ -371,6 +372,11 @@ protected String doBigInt(BigInt thisObj) { return JSObject.defaultToString(toJSObject(thisObj)); } + @Specialization + protected String doRecord(Record thisObj) { + return JSObject.defaultToString(toJSObject(thisObj)); + } + @Specialization protected String doTuple(Tuple thisObj) { return JSObject.defaultToString(toJSObject(thisObj)); diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSRecord.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSRecord.java index 7225cb54b24..f99603606fa 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSRecord.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSRecord.java @@ -58,7 +58,7 @@ import com.oracle.truffle.js.runtime.objects.PropertyDescriptor; import com.oracle.truffle.js.runtime.util.DefinePropertyUtil; -import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -258,7 +258,7 @@ public List getOwnPropertyKeys(DynamicObject thisObj, boolean strings, b return Collections.emptyList(); } Record value = valueOf(thisObj); - return new ArrayList<>(value.getKeys()); + return Arrays.asList(value.getKeys()); } private Object recordGet(DynamicObject object, Object key) { From 48b45477d10ba68d7dff26a69df98a7b12371939 Mon Sep 17 00:00:00 2001 From: Christian Aistleitner Date: Tue, 18 May 2021 10:22:08 +0200 Subject: [PATCH 25/31] Add record and tuple support to JSON builtins. --- .../js/test/builtins/JSONBuiltinsTest.java | 85 ++++++++ .../truffle/js/builtins/JSONBuiltins.java | 37 +++- .../JSONBuildImmutablePropertyNode.java | 186 ++++++++++++++++++ .../helper/JSONStringifyStringNode.java | 12 +- 4 files changed, 317 insertions(+), 3 deletions(-) create mode 100644 graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/JSONBuiltinsTest.java create mode 100644 graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/helper/JSONBuildImmutablePropertyNode.java diff --git a/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/JSONBuiltinsTest.java b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/JSONBuiltinsTest.java new file mode 100644 index 00000000000..718b59c2c38 --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/JSONBuiltinsTest.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.truffle.js.test.builtins; + +import com.oracle.truffle.js.lang.JavaScriptLanguage; +import com.oracle.truffle.js.runtime.JSContextOptions; +import com.oracle.truffle.js.test.JSTest; +import org.graalvm.polyglot.Context; +import org.graalvm.polyglot.Source; +import org.graalvm.polyglot.Value; +import org.junit.Test; + +import static org.junit.Assert.assertTrue; + +public class JSONBuiltinsTest { + + private static final String testName = "json-builtins-test"; + + private static Value execute(String sourceText) { + try (Context context = JSTest.newContextBuilder() + .option(JSContextOptions.ECMASCRIPT_VERSION_NAME, "2022") + .build()) { + return context.eval(Source.newBuilder(JavaScriptLanguage.ID, sourceText, testName).buildLiteral()); + } + } + + private static Value execute(String... sourceText) { + return execute(String.join("\n", sourceText)); + } + + @Test + public void testParseImmutable() { + assertTrue(execute( + "let text = '{ \"status\": 200, \"data\": [1, 2, 3], \"ignore\": 42}';", + "let filter = (name, immutable) => name == 'ignore' ? undefined : immutable;", + "JSON.parseImmutable(text, filter) === #{ status: 200, data: #[1, 2, 3] };" + ).asBoolean()); + } + + @Test + public void testStringify() { + assertTrue(execute( + "let data = #{ status: 200, data: #[1, 2, 3] };", + "JSON.stringify(data) === '{\"data\":[1,2,3],\"status\":200}';" + ).asBoolean()); + } +} diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/JSONBuiltins.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/JSONBuiltins.java index 1e9611a2473..0d530088eda 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/JSONBuiltins.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/JSONBuiltins.java @@ -53,7 +53,9 @@ import com.oracle.truffle.api.profiles.BranchProfile; import com.oracle.truffle.api.profiles.ConditionProfile; import com.oracle.truffle.js.builtins.JSONBuiltinsFactory.JSONParseNodeGen; +import com.oracle.truffle.js.builtins.JSONBuiltinsFactory.JSONParseImmutableNodeGen; import com.oracle.truffle.js.builtins.JSONBuiltinsFactory.JSONStringifyNodeGen; +import com.oracle.truffle.js.builtins.helper.JSONBuildImmutablePropertyNode; import com.oracle.truffle.js.builtins.helper.JSONData; import com.oracle.truffle.js.builtins.helper.JSONStringifyStringNode; import com.oracle.truffle.js.builtins.helper.TruffleJSONParser; @@ -65,6 +67,7 @@ import com.oracle.truffle.js.nodes.function.JSBuiltinNode; import com.oracle.truffle.js.nodes.unary.IsCallableNode; import com.oracle.truffle.js.nodes.unary.JSIsArrayNode; +import com.oracle.truffle.js.runtime.JSConfig; import com.oracle.truffle.js.runtime.JSContext; import com.oracle.truffle.js.runtime.JSRuntime; import com.oracle.truffle.js.runtime.builtins.BuiltinEnum; @@ -92,7 +95,10 @@ protected JSONBuiltins() { public enum JSON implements BuiltinEnum { parse(2), - stringify(3); + stringify(3), + + // Record & Tuple Proposal + parseImmutable(2); private final int length; @@ -104,6 +110,14 @@ public enum JSON implements BuiltinEnum { public int getLength() { return length; } + + @Override + public int getECMAScriptVersion() { + if (this == parseImmutable) { + return JSConfig.ECMAScript2022; // TODO: Associate with the correct ECMAScript Version + } + return BuiltinEnum.super.getECMAScriptVersion(); + } } @Override @@ -113,6 +127,8 @@ protected Object createNode(JSContext context, JSBuiltin builtin, boolean constr return JSONParseNodeGen.create(context, builtin, args().fixedArgs(2).createArgumentNodes(context)); case stringify: return JSONStringifyNodeGen.create(context, builtin, args().fixedArgs(3).createArgumentNodes(context)); + case parseImmutable: + return JSONParseImmutableNodeGen.create(context, builtin, args().fixedArgs(2).createArgumentNodes(context)); } return null; } @@ -348,4 +364,23 @@ protected Number toNumber(Object target) { return toNumberNode.executeNumber(target); } } + + public abstract static class JSONParseImmutableNode extends JSONOperation { + + public JSONParseImmutableNode(JSContext context, JSBuiltin builtin) { + super(context, builtin); + } + + @Specialization(limit = "1") + protected Object parseUnfiltered(String text, Object reviver, + @Cached("create(getContext())") JSONBuildImmutablePropertyNode buildImmutableProperty) { + Object unfiltered = parseIntl(toString(text)); + return buildImmutableProperty.execute(unfiltered, "", reviver); + } + + @TruffleBoundary(transferToInterpreterOnException = false) + private Object parseIntl(String jsonString) { + return new TruffleJSONParser(getContext()).parse(jsonString); + } + } } diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/helper/JSONBuildImmutablePropertyNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/helper/JSONBuildImmutablePropertyNode.java new file mode 100644 index 00000000000..f07b6b12900 --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/helper/JSONBuildImmutablePropertyNode.java @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.truffle.js.builtins.helper; + +import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.object.DynamicObject; +import com.oracle.truffle.api.profiles.ConditionProfile; +import com.oracle.truffle.js.nodes.JavaScriptBaseNode; +import com.oracle.truffle.js.nodes.access.EnumerableOwnPropertyNamesNode; +import com.oracle.truffle.js.nodes.access.ReadElementNode; +import com.oracle.truffle.js.nodes.array.JSGetLengthNode; +import com.oracle.truffle.js.nodes.function.JSFunctionCallNode; +import com.oracle.truffle.js.nodes.unary.IsCallableNode; +import com.oracle.truffle.js.nodes.unary.JSIsArrayNode; +import com.oracle.truffle.js.runtime.Boundaries; +import com.oracle.truffle.js.runtime.Errors; +import com.oracle.truffle.js.runtime.JSArguments; +import com.oracle.truffle.js.runtime.JSContext; +import com.oracle.truffle.js.runtime.JSRuntime; +import com.oracle.truffle.js.runtime.Record; +import com.oracle.truffle.js.runtime.Tuple; +import com.oracle.truffle.js.runtime.objects.Undefined; +import com.oracle.truffle.js.runtime.util.UnmodifiableArrayList; + +import java.util.Map; +import java.util.TreeMap; + +/** + * This class represents the abstract operation BuildImmutableProperty. + */ +public abstract class JSONBuildImmutablePropertyNode extends JavaScriptBaseNode { + + private final JSContext context; + private final ConditionProfile isCallableProfile = ConditionProfile.create(); + + @Child private IsCallableNode isCallableNode; + @Child private JSFunctionCallNode functionCallNode; + @Child private JSIsArrayNode isArrayNode; + @Child private JSGetLengthNode getLengthNode; + @Child private ReadElementNode readElementNode; + @Child private EnumerableOwnPropertyNamesNode enumerableOwnPropertyNamesNode; + + protected JSONBuildImmutablePropertyNode(JSContext context) { + this.context = context; + } + + public abstract Object execute(Object value, String name, Object reviver); + + public static JSONBuildImmutablePropertyNode create(JSContext context) { + return JSONBuildImmutablePropertyNodeGen.create(context); + } + + @Specialization(guards = {"isJSObject(value)", "isArray(value)"}) + public Object doJSArray(Object value, String name, Object reviver) { + int len = (int) lengthOfArrayLike(value); + Object[] items = new Object[len]; + for (int i = 0; i < len; i++) { + String childName = Boundaries.stringValueOf(i); + Object childValue = get(value, i); + Object newElement = execute(childValue, childName, reviver); + assert !JSRuntime.isObject(newElement); + items[i] = newElement; + } + Object immutable = Tuple.create(items); + return mapIfCallable(immutable, reviver, name); + } + + @Specialization(guards = {"isJSObject(value)", "!isArray(value)"}) + public Object doJSObject(DynamicObject value, String name, Object reviver) { + UnmodifiableArrayList props = enumerableOwnPropertyNames(value); + Map fields = new TreeMap<>(); + for (Object prop : props) { + String childName = JSRuntime.toString(get(prop, 0)); + Object childValue = get(prop, 1); + Object newElement = execute(childValue, childName, reviver); + assert !JSRuntime.isObject(newElement); + if (newElement != Undefined.instance) { + fields.put(childName, newElement); + } + } + Object immutable = Record.create(fields); + return mapIfCallable(immutable, reviver, name); + } + + @Specialization(guards = "!isJSObject(value)") + public Object doNonJSObject(Object value, String name, Object reviver) { + return mapIfCallable(value, reviver, name); + } + + protected Object mapIfCallable(Object immutable, Object reviver, String name) { + if (isCallableProfile.profile(isCallable(reviver))) { + immutable = call(JSArguments.create(Undefined.instance, reviver, name, immutable)); + if (JSRuntime.isObject(immutable)) { + throw Errors.createTypeError("objects are not allowed here"); + } + } + return immutable; + } + + protected boolean isArray(Object obj) { + if (isArrayNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + isArrayNode = insert(JSIsArrayNode.createIsArray()); + } + return isArrayNode.execute(obj); + } + + protected long lengthOfArrayLike(Object obj) { + if (getLengthNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + getLengthNode = insert(JSGetLengthNode.create(context)); + } + return getLengthNode.executeLong(obj); + } + + protected Object get(Object obj, int index) { + if (readElementNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + readElementNode = insert(ReadElementNode.create(context)); + } + return readElementNode.executeWithTargetAndIndex(obj, index); + } + + protected UnmodifiableArrayList enumerableOwnPropertyNames(DynamicObject obj) { + if (enumerableOwnPropertyNamesNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + enumerableOwnPropertyNamesNode = insert(EnumerableOwnPropertyNamesNode.createKeysValues(context)); + } + return enumerableOwnPropertyNamesNode.execute(obj); + } + + protected boolean isCallable(Object obj) { + if (isCallableNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + isCallableNode = insert(IsCallableNode.create()); + } + return isCallableNode.executeBoolean(obj); + } + + protected Object call(Object[] arguments) { + if (functionCallNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + functionCallNode = insert(JSFunctionCallNode.createCall()); + } + return functionCallNode.executeCall(arguments); + } +} diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/helper/JSONStringifyStringNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/helper/JSONStringifyStringNode.java index 07a3f450073..56ff8e04929 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/helper/JSONStringifyStringNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/helper/JSONStringifyStringNode.java @@ -59,13 +59,17 @@ import com.oracle.truffle.js.runtime.JSArguments; import com.oracle.truffle.js.runtime.JSContext; import com.oracle.truffle.js.runtime.JSRuntime; +import com.oracle.truffle.js.runtime.Record; import com.oracle.truffle.js.runtime.Symbol; +import com.oracle.truffle.js.runtime.Tuple; import com.oracle.truffle.js.runtime.builtins.JSArray; import com.oracle.truffle.js.runtime.builtins.JSBigInt; import com.oracle.truffle.js.runtime.builtins.JSBoolean; import com.oracle.truffle.js.runtime.builtins.JSClass; import com.oracle.truffle.js.runtime.builtins.JSNumber; +import com.oracle.truffle.js.runtime.builtins.JSRecord; import com.oracle.truffle.js.runtime.builtins.JSString; +import com.oracle.truffle.js.runtime.builtins.JSTuple; import com.oracle.truffle.js.runtime.interop.JSInteropUtil; import com.oracle.truffle.js.runtime.objects.JSDynamicObject; import com.oracle.truffle.js.runtime.objects.JSObject; @@ -129,7 +133,7 @@ private void jsonStrExecute(StringBuilder builder, JSONData data, Object value) throw Errors.createTypeError("Do not know how to serialize a BigInt"); } else if (JSDynamicObject.isJSDynamicObject(value) && !JSRuntime.isCallableIsJSObject((DynamicObject) value)) { DynamicObject valueObj = (DynamicObject) value; - if (JSRuntime.isArray(valueObj)) { + if (JSRuntime.isArray(valueObj) || JSTuple.isJSTuple(value)) { jsonJA(builder, data, valueObj); } else { jsonJO(builder, data, valueObj); @@ -217,6 +221,10 @@ private Object jsonStrPreparePart2(JSONData data, String key, Object holder, Obj return jsonStrPrepareJSObject((DynamicObject) value); } else if (value instanceof Symbol) { return Undefined.instance; + } else if (value instanceof Record) { + return JSRecord.create(context, (Record) value); + } else if (value instanceof Tuple) { + return JSTuple.create(context, (Tuple) value); } else if (JSRuntime.isCallableForeign(value)) { return Undefined.instance; } @@ -358,7 +366,7 @@ private boolean serializeForeignObjectProperties(StringBuilder builder, JSONData @TruffleBoundary private void jsonJA(StringBuilder builder, JSONData data, Object value) { checkCycle(data, value); - assert JSRuntime.isArray(value) || InteropLibrary.getFactory().getUncached().hasArrayElements(value); + assert JSRuntime.isArray(value) || JSTuple.isJSTuple(value) || InteropLibrary.getFactory().getUncached().hasArrayElements(value); data.pushStack(value); checkStackDepth(data); int stepback = data.getIndent(); From 170e056cf9e64a4008ac0e28ab39c8f0dee23ee8 Mon Sep 17 00:00:00 2001 From: Christian Aistleitner Date: Tue, 18 May 2021 14:14:56 +0200 Subject: [PATCH 26/31] Cleanup and remove interop annotations. --- .../src/com/oracle/js/parser/Parser.java | 4 +- .../com/oracle/js/parser/ir/LiteralNode.java | 2 - .../js/record-tuple/record.js | 4 +- .../builtins/TupleFunctionBuiltinsTest.java | 13 +- .../js/builtins/ConstructorBuiltins.java | 6 +- .../js/builtins/ObjectPrototypeBuiltins.java | 5 + .../js/builtins/RecordFunctionBuiltins.java | 2 +- .../js/builtins/TupleFunctionBuiltins.java | 1 + .../js/builtins/TuplePrototypeBuiltins.java | 101 ++++-------- .../truffle/js/nodes/cast/JSToUInt32Node.java | 4 - .../tuples/JSIsConcatSpreadableNode.java | 40 +++++ .../js/nodes/tuples/JSIsTupleNode.java | 40 +++++ .../oracle/truffle/js/runtime/JSRuntime.java | 7 +- .../com/oracle/truffle/js/runtime/Record.java | 1 - .../com/oracle/truffle/js/runtime/Tuple.java | 154 +++++++++--------- .../js/runtime/builtins/JSTupleObject.java | 40 +++++ .../js/runtime/interop/JSMetaType.java | 2 - 17 files changed, 257 insertions(+), 169 deletions(-) diff --git a/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/Parser.java b/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/Parser.java index 44f721ec20a..af3ced1376a 100644 --- a/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/Parser.java +++ b/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/Parser.java @@ -4309,7 +4309,7 @@ private static final class PropertyFunction { * RecordPropertyDefinitionList[?Yield, ?Await] , RecordPropertyDefinition[?Yield, ?Await] * * - * @return Expression node. + * @return Record node. */ private RecordNode recordLiteral(boolean yield, boolean await) { // Capture HASH_LBRACE token. @@ -4428,7 +4428,7 @@ private RecordPropertyNode recordPropertyDefinition(boolean yield, boolean await * TupleElementList[?Yield, ?Await] , SpreadElement[?Yield, ?Await] * * - * @return Expression node. + * @return Literal node. */ private LiteralNode tupleLiteral(boolean yield, boolean await) { final long tupleToken = token; // Capture HASH_BRACKET token. diff --git a/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/ir/LiteralNode.java b/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/ir/LiteralNode.java index e43f03bf5e2..1a72d552dae 100644 --- a/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/ir/LiteralNode.java +++ b/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/ir/LiteralNode.java @@ -528,8 +528,6 @@ protected TupleLiteralNode(final long token, final int finish, final Expression[ /** * Returns a list of tuple element expressions. - * - * @return a list of tuple element expressions. */ @Override public List getElementExpressions() { diff --git a/graal-js/src/com.oracle.truffle.js.test/js/record-tuple/record.js b/graal-js/src/com.oracle.truffle.js.test/js/record-tuple/record.js index 2d0b43418db..f979aef264a 100644 --- a/graal-js/src/com.oracle.truffle.js.test/js/record-tuple/record.js +++ b/graal-js/src/com.oracle.truffle.js.test/js/record-tuple/record.js @@ -75,10 +75,8 @@ assertSame(false, b.age.configurable); * Test 8: * [[DefineOwnProperty]] */ -// TODO: Not sure if [[DefineOwnProperty]] is reachable according to proposal and ecma262 specs -// TODO: Code below should work according to polyfill, but shouldn't according to specs a = #{ age: 22 }; -a = Object(a); // TODO: This workaround allows testing [[DefineOwnProperty]] +a = Object(a); Object.defineProperty(a, "age", { value: 22 }); Object.defineProperty(a, "age", { value: 22, writable: false, enumerable: true, configurable: false }); assertThrows(function() { diff --git a/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/TupleFunctionBuiltinsTest.java b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/TupleFunctionBuiltinsTest.java index 09fec776ecf..74ad3021319 100644 --- a/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/TupleFunctionBuiltinsTest.java +++ b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/TupleFunctionBuiltinsTest.java @@ -47,6 +47,7 @@ import org.graalvm.polyglot.Source; import org.graalvm.polyglot.Value; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; import static org.junit.Assert.assertFalse; @@ -87,7 +88,7 @@ public void testIsTuple() { } @Test - public void testFrom() { + public void testFrom_Polyfill() { assertTrue(execute("Tuple.from('foo') === #['f', 'o', 'o']").asBoolean()); assertTrue(execute("Tuple.from([1, 2, 3], x => x + x) === #[2, 4, 6]").asBoolean()); assertTrue(execute("Tuple.from([1, 2, 3], function (x) { return x + this}, 10) === #[11, 12, 13]").asBoolean()); @@ -99,6 +100,16 @@ public void testFrom() { expectError("Tuple.from({0: {}, length: 1})", "non-primitive values"); } + // TODO: re-evaluate, check proposal for changes + // NOTE: Proposal spec would not work as intended... + // For this reason I replaced the AddEntriesFromIterable(...) call + // with the corresponding code of https://tc39.es/ecma262/#sec-array.from. + @Ignore + @Test + public void testFrom_Spec() { + expectError("Tuple.from([1])", "is not an object"); + } + @Test public void testOf() { assertTrue(execute("Tuple.of() === #[]").asBoolean()); diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/ConstructorBuiltins.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/ConstructorBuiltins.java index eaa00500c67..b5748a1670d 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/ConstructorBuiltins.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/ConstructorBuiltins.java @@ -337,9 +337,9 @@ public enum Constructor implements BuiltinEnum { JSAdapter(1), JavaImporter(1), - // Record and Tuple proposal - Record(0), - Tuple(0); + // Record & Tuple Proposal + Record(1), + Tuple(1); private final int length; diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/ObjectPrototypeBuiltins.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/ObjectPrototypeBuiltins.java index 4ccbff74e05..0f1a3824591 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/ObjectPrototypeBuiltins.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/ObjectPrototypeBuiltins.java @@ -252,6 +252,11 @@ protected DynamicObject valueOfBigInt(BigInt thisObj) { return toJSObject(thisObj); } + @Specialization + protected DynamicObject valueOfRecord(Record thisObj) { + return toJSObject(thisObj); + } + @Specialization protected DynamicObject valueOfTuple(Tuple thisObj) { return toJSObject(thisObj); diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/RecordFunctionBuiltins.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/RecordFunctionBuiltins.java index 5c383cd6f07..64b42bb6904 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/RecordFunctionBuiltins.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/RecordFunctionBuiltins.java @@ -81,7 +81,7 @@ protected RecordFunctionBuiltins() { public enum RecordFunction implements BuiltinEnum { fromEntries(1), - isRecord (1); + isRecord(1); private final int length; diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TupleFunctionBuiltins.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TupleFunctionBuiltins.java index 96151e89219..5926067fb8f 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TupleFunctionBuiltins.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TupleFunctionBuiltins.java @@ -165,6 +165,7 @@ protected Tuple from(Object items, Object mapFn, Object thisArg) { Object usingIterator = getIteratorMethod(items); if (usingIterableProfile.profile(usingIterator != Undefined.instance)) { + // TODO: re-evaluate, check proposal for changes // NOTE: Proposal spec would not work as intended... // For this reason I replaced the AddEntriesFromIterable(...) call // with the corresponding code of https://tc39.es/ecma262/#sec-array.from. diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TuplePrototypeBuiltins.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TuplePrototypeBuiltins.java index ae00c0b662b..1215e8c2469 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TuplePrototypeBuiltins.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TuplePrototypeBuiltins.java @@ -71,7 +71,6 @@ import com.oracle.truffle.js.runtime.builtins.BuiltinEnum; import com.oracle.truffle.js.runtime.builtins.JSArray; import com.oracle.truffle.js.runtime.builtins.JSFunction; -import com.oracle.truffle.js.runtime.builtins.JSProxy; import com.oracle.truffle.js.runtime.builtins.JSTuple; import com.oracle.truffle.js.runtime.objects.JSDynamicObject; import com.oracle.truffle.js.runtime.objects.Undefined; @@ -688,12 +687,10 @@ public abstract static class JSTupleConcatNode extends BasicTupleOperation { private final BranchProfile growProfile = BranchProfile.create(); @Child private JSIsConcatSpreadableNode isConcatSpreadableNode; + @Child private JSToObjectNode toObjectNode; @Child private JSHasPropertyNode hasPropertyNode; @Child private ReadElementNode readElementNode; @Child private JSGetLengthNode getLengthNode; - @Child private JSArrayFirstElementIndexNode firstElementIndexNode; - @Child private JSArrayLastElementIndexNode lastElementIndexNode; - @Child private JSArrayNextElementIndexNode nextElementIndexNode; public JSTupleConcatNode(JSContext context, JSBuiltin builtin) { super(context, builtin); @@ -710,44 +707,28 @@ protected Tuple concat(Object thisObj, Object[] args) { return Tuple.create(list.toArray()); } - private void concatElement(Object el, SimpleArrayList list) { - if (isConcatSpreadable(el)) { - long len = getLength(el); - if (len > 0) { - concatSpreadable(el, len, list); - } - } else { - if (!JSRuntime.isJSPrimitive(el)) { - throw Errors.createTypeError("Tuples cannot contain non-primitive values"); - } - list.add(el, growProfile); - } - } + private void concatElement(Object e, SimpleArrayList list) { + if (isConcatSpreadable(e)) { - private void concatSpreadable(Object el, long len, SimpleArrayList list) { - if (JSRuntime.isTuple(el)) { - Tuple tuple = (Tuple) el; - for (long k = 0; k < tuple.getArraySize(); k++) { - list.add(tuple.getElement(k), growProfile); - } - } else if (JSProxy.isJSProxy(el) || !JSDynamicObject.isJSDynamicObject(el)) { - // strictly to the standard implementation; traps could expose optimizations! + // TODO: re-evaluate, check proposal for changes + // The ToObject(e) call is not according to current proposal spec, but it wouldn't work otherwise. + e = toObject(e); + + long len = getLengthOfArrayLike(e); for (long k = 0; k < len; k++) { - if (hasProperty(el, k)) { - list.add(get(el, k), growProfile); + if (hasProperty(e, k)) { + Object subElement = get(e, k); + if (JSRuntime.isObject(subElement)) { + throw Errors.createTypeError("Tuples cannot contain non-primitive values"); + } + list.add(subElement, growProfile); } } - } else if (len == 1) { - // fastpath for 1-element entries - if (hasProperty(el, 0)) { - list.add(get(el, 0), growProfile); - } } else { - long k = firstElementIndex((DynamicObject) el, len); - long lastI = lastElementIndex((DynamicObject) el, len); - for (; k <= lastI; k = nextElementIndex(el, k, len)) { - list.add(get(el, k), growProfile); + if (JSRuntime.isObject(e)) { + throw Errors.createTypeError("Tuples cannot contain non-primitive values"); } + list.add(e, growProfile); } } @@ -759,6 +740,22 @@ private boolean isConcatSpreadable(Object object) { return isConcatSpreadableNode.execute(object); } + private Object toObject(Object object) { + if (toObjectNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + toObjectNode = insert(JSToObjectNode.createToObjectNoCheck(getContext())); + } + return toObjectNode.execute(object); + } + + private long getLengthOfArrayLike(Object obj) { + if (getLengthNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + getLengthNode = insert(JSGetLengthNode.create(getContext())); + } + return getLengthNode.executeLong(obj); + } + private boolean hasProperty(Object obj, long idx) { if (hasPropertyNode == null) { CompilerDirectives.transferToInterpreterAndInvalidate(); @@ -778,38 +775,6 @@ private Object get(Object target, long index) { return readElementNode.executeWithTargetAndIndex(target, (double) index); } } - - private long firstElementIndex(DynamicObject target, long length) { - if (firstElementIndexNode == null) { - CompilerDirectives.transferToInterpreterAndInvalidate(); - firstElementIndexNode = insert(JSArrayFirstElementIndexNode.create(getContext())); - } - return firstElementIndexNode.executeLong(target, length); - } - - private long lastElementIndex(DynamicObject target, long length) { - if (lastElementIndexNode == null) { - CompilerDirectives.transferToInterpreterAndInvalidate(); - lastElementIndexNode = insert(JSArrayLastElementIndexNode.create(getContext())); - } - return lastElementIndexNode.executeLong(target, length); - } - - private long nextElementIndex(Object target, long currentIndex, long length) { - if (nextElementIndexNode == null) { - CompilerDirectives.transferToInterpreterAndInvalidate(); - nextElementIndexNode = insert(JSArrayNextElementIndexNode.create(getContext())); - } - return nextElementIndexNode.executeLong(target, currentIndex, length); - } - - private long getLength(Object obj) { - if (getLengthNode == null) { - CompilerDirectives.transferToInterpreterAndInvalidate(); - getLengthNode = insert(JSGetLengthNode.create(getContext())); - } - return getLengthNode.executeLong(obj); - } } public abstract static class JSTupleFilterNode extends BasicTupleOperation { diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToUInt32Node.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToUInt32Node.java index 751af98bc55..0009337eaca 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToUInt32Node.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToUInt32Node.java @@ -181,10 +181,6 @@ protected int doBigInt(@SuppressWarnings("unused") BigInt value) { throw Errors.createTypeErrorCannotConvertBigIntToNumber(this); } - // TODO: Notes: - // TODO: Why isn't this class implemented like JSToUInt16Node? - // TODO: We could remove the duplicated code by calling JSToNumberNode in a doGeneric Specification. - // TODO: See also https://tc39.es/ecma262/#sec-touint32 @Specialization protected final Number doRecord(@SuppressWarnings("unused") Record value) { throw Errors.createTypeErrorCannotConvertToNumber("a Record value", this); diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/JSIsConcatSpreadableNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/JSIsConcatSpreadableNode.java index ebd721dad6d..17abcc9590b 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/JSIsConcatSpreadableNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/JSIsConcatSpreadableNode.java @@ -1,3 +1,43 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ package com.oracle.truffle.js.nodes.tuples; import com.oracle.truffle.api.CompilerDirectives; diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/JSIsTupleNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/JSIsTupleNode.java index 0e4a7900bea..79db2a13199 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/JSIsTupleNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/JSIsTupleNode.java @@ -1,3 +1,43 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ package com.oracle.truffle.js.nodes.tuples; import com.oracle.truffle.api.dsl.Fallback; diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSRuntime.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSRuntime.java index 9adbfda31e0..a2ab8275182 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSRuntime.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSRuntime.java @@ -1352,7 +1352,6 @@ public static String recordToString(@SuppressWarnings("unused") Record value) { * Record & Tuple Proposal 2.1.2.1 TupleToString */ public static String tupleToString(Tuple value) { - // TODO: Return ? Call(%Array.prototype.join%, argument, « »). return value.toString(); } @@ -1595,6 +1594,9 @@ public static boolean isSameValue(Object x, Object y) { return x == y; } + /** + * Abstract operation RecordEqual ( x, y, elementEqual ) + */ private static boolean recordEqual(Record x, Record y, BiFunction elementEqual) { String[] xKeys = x.getKeys(); String[] yKeys = y.getKeys(); @@ -1611,6 +1613,9 @@ private static boolean recordEqual(Record x, Record y, BiFunction elementEqual) { if (x.getArraySize() != y.getArraySize()) { return false; diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Record.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Record.java index 4680149f96e..8e9efdc2bf7 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Record.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Record.java @@ -56,7 +56,6 @@ * * @see com.oracle.truffle.js.runtime.builtins.JSRecordObject */ -@ExportLibrary(InteropLibrary.class) @ValueType public final class Record implements TruffleObject { diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Tuple.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Tuple.java index ad0cb15df79..1867eadab67 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Tuple.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Tuple.java @@ -1,3 +1,43 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ package com.oracle.truffle.js.runtime; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; @@ -20,7 +60,6 @@ * * @see com.oracle.truffle.js.runtime.builtins.JSTupleObject */ -@ExportLibrary(InteropLibrary.class) @ValueType public final class Tuple implements TruffleObject { @@ -29,6 +68,7 @@ public final class Tuple implements TruffleObject { private final Object[] value; private Tuple(Object[] v) { + assert v != null; this.value = v; } @@ -43,6 +83,38 @@ public static Tuple create(Object[] v) { return new Tuple(v); } + public long getArraySize() { + return value.length; + } + + public int getArraySizeInt() { + return value.length; + } + + /** + * @return true if the index isn't out of range. + */ + public boolean hasElement(long index) { + return index >= 0 && index < value.length; + } + + /** + * @return value at the given index. + */ + public Object getElement(long index) { + if (hasElement(index)) { + return value[(int) index]; + } + return null; + } + + /** + * @return all values in a List. + */ + public Object[] getElements() { + return value.clone(); + } + @Override @TruffleBoundary public int hashCode() { @@ -79,86 +151,6 @@ public boolean equals(Tuple other) { @Override @TruffleBoundary public String toString() { - if (value == null) return ""; return Arrays.stream(value).map(String::valueOf).collect(Collectors.joining(",")); } - - @SuppressWarnings("static-method") - @ExportMessage - boolean hasLanguage() { - return true; - } - - @SuppressWarnings("static-method") - @ExportMessage - Class> getLanguage() { - return JavaScriptLanguage.class; - } - - @TruffleBoundary - @ExportMessage - Object toDisplayString(@SuppressWarnings("unused") boolean allowSideEffects) { - return "#[" + toString() + "]"; - } - - @SuppressWarnings("static-method") - @ExportMessage - boolean hasMetaObject() { - return true; - } - - @SuppressWarnings("static-method") - @ExportMessage - Object getMetaObject() { - return JSMetaType.JS_TUPLE; - } - - @ExportMessage - public long getArraySize() { - return value.length; - } - - @SuppressWarnings("static-method") - @ExportMessage - public boolean hasArrayElements() { - return true; - } - - @ExportMessage - public Object readArrayElement(long index) throws InvalidArrayIndexException { - if (index < 0 || index >= value.length) { - throw InvalidArrayIndexException.create(index); - } - return value[(int) index]; - } - - @ExportMessage - public boolean isArrayElementReadable(long index) { - return index >= 0 && index < value.length; - } - - /** - * @return true if the index isn't out of range. - */ - public boolean hasElement(long index) { - return index >= 0 && index < value.length; - } - - /** - * @return value at the given index. - */ - public Object getElement(long index) { - return value[(int) index]; - } - - /** - * @return all values in a List. - */ - public Object[] getElements() { - return value.clone(); - } - - public int getArraySizeInt() { - return value.length; - } } diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSTupleObject.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSTupleObject.java index f008effe9f8..0280b2b93f0 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSTupleObject.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSTupleObject.java @@ -1,3 +1,43 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ package com.oracle.truffle.js.runtime.builtins; import com.oracle.truffle.api.object.DynamicObject; diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/interop/JSMetaType.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/interop/JSMetaType.java index df807b95dd6..723ea6e63de 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/interop/JSMetaType.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/interop/JSMetaType.java @@ -55,7 +55,6 @@ import com.oracle.truffle.js.nodes.JSGuards; import com.oracle.truffle.js.runtime.BigInt; import com.oracle.truffle.js.runtime.Symbol; -import com.oracle.truffle.js.runtime.Tuple; /** * General meta objects for JS values and foreign objects through {@link JavaScriptLanguageView}. @@ -88,7 +87,6 @@ public interface TypeCheck { public static final JSMetaType JS_UNDEFINED = new JSMetaType("undefined", (l, v) -> JSGuards.isUndefined(v)); public static final JSMetaType JS_BIGINT = new JSMetaType("bigint", (l, v) -> v instanceof BigInt); public static final JSMetaType JS_SYMBOL = new JSMetaType("symbol", (l, v) -> v instanceof Symbol); - public static final JSMetaType JS_TUPLE = new JSMetaType("tuple", (l, v) -> v instanceof Tuple); public static final JSMetaType JS_PROXY = new JSMetaType("Proxy", (l, v) -> JSGuards.isJSProxy(v)); private final String typeName; From 4bd95f1d1b49baff1cd1f4ad6b794d5e7f24a2a2 Mon Sep 17 00:00:00 2001 From: Christian Aistleitner Date: Sat, 29 May 2021 15:07:28 +0200 Subject: [PATCH 27/31] Cleanup tuple prototype builtins. --- .../builtins/TuplePrototypeBuiltinsTest.java | 25 +- .../js/builtins/TuplePrototypeBuiltins.java | 375 +++++++++--------- .../truffle/js/nodes/access/IsObjectNode.java | 12 + 3 files changed, 220 insertions(+), 192 deletions(-) diff --git a/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/TuplePrototypeBuiltinsTest.java b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/TuplePrototypeBuiltinsTest.java index 5245c09e27d..7ef95ea17d5 100644 --- a/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/TuplePrototypeBuiltinsTest.java +++ b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/TuplePrototypeBuiltinsTest.java @@ -148,6 +148,9 @@ public void testSorted() { public void testSpliced() { assertTrue(execute("#[1, 7, 4].spliced(1, 1, 2, 3) === #[1, 2, 3, 4]").asBoolean()); assertTrue(execute("Object(#[2, 1]).spliced(0, 1) === #[1]").asBoolean()); + assertTrue(execute("Object(#[2, 1]).spliced() === #[2, 1]").asBoolean()); + assertTrue(execute("Object(#[2, 1]).spliced(undefined, undefined) === #[2, 1]").asBoolean()); + assertTrue(execute("Object(#[2, 1]).spliced(undefined) === #[]").asBoolean()); expectError("Tuple.prototype.spliced.call('test')", "be a Tuple"); } @@ -165,7 +168,7 @@ public void testIncludes() { assertTrue(execute("Object(#[1, 2]).includes(2)").asBoolean()); } - @Ignore + @Ignore // TODO: re-evaluate, check proposal for changes @Test public void testIncludes_Fallback() { expectError("Tuple.prototype.includes.call('test')", "be a Tuple"); @@ -178,7 +181,7 @@ public void testIndexOf() { assertEquals(1, execute("Object(#[1, 2]).indexOf(2)").asInt()); } - @Ignore + @Ignore // TODO: re-evaluate, check proposal for changes @Test public void testIndexOf_Fallback() { expectError("Tuple.prototype.indexOf.call('test')", "be a Tuple"); @@ -191,7 +194,7 @@ public void testJoin() { assertEquals("1", execute("Object(#[1]).join('-')").asString()); } - @Ignore + @Ignore // TODO: re-evaluate, check proposal for changes @Test public void testJoin_Fallback() { expectError("Tuple.prototype.join.call('test')", "be a Tuple"); @@ -204,7 +207,7 @@ public void testLastIndexOf() { assertEquals(1, execute("Object(#[1, 2]).lastIndexOf(2)").asInt()); } - @Ignore + @Ignore // TODO: re-evaluate, check proposal for changes @Test public void testLastIndexOf_Fallback() { expectError("Tuple.prototype.lastIndexOf.call('test')", "be a Tuple"); @@ -228,7 +231,7 @@ public void testEvery() { assertTrue(execute("Object(#[1, 1]).every(it => it === 1)").asBoolean()); } - @Ignore + @Ignore // TODO: re-evaluate, check proposal for changes @Test public void testEvery_Fallback() { expectError("Tuple.prototype.every.call('test')", "be a Tuple"); @@ -248,7 +251,7 @@ public void testFind() { assertEquals(1, execute("Object(#[1, 1]).find(it => it === 1)").asInt()); } - @Ignore + @Ignore // TODO: re-evaluate, check proposal for changes @Test public void testFind_Fallback() { expectError("Tuple.prototype.find.call('test')", "be a Tuple"); @@ -261,7 +264,7 @@ public void testFindIndex() { assertEquals(0, execute("Object(#[1, 1]).findIndex(it => it === 1)").asInt()); } - @Ignore + @Ignore // TODO: re-evaluate, check proposal for changes @Test public void testFindIndex_Fallback() { expectError("Tuple.prototype.findIndex.call('test')", "be a Tuple"); @@ -294,7 +297,7 @@ public void testForEach() { ); } - @Ignore + @Ignore // TODO: re-evaluate, check proposal for changes @Test public void testForEach_Fallback() { expectError("Tuple.prototype.forEach.call('test')", "be a Tuple"); @@ -319,7 +322,7 @@ public void testReduce() { assertEquals(6, execute("Object(#[1, 2, 3]).reduce((acc, it) => acc += it)").asInt()); } - @Ignore + @Ignore // TODO: re-evaluate, check proposal for changes @Test public void testReduce_Fallback() { expectError("Tuple.prototype.reduce.call('test')", "be a Tuple"); @@ -331,7 +334,7 @@ public void testReduceRight() { assertEquals(6, execute("Object(#[1, 2, 3]).reduceRight((acc, it) => acc += it)").asInt()); } - @Ignore + @Ignore // TODO: re-evaluate, check proposal for changes @Test public void testReduceRight_Fallback() { expectError("Tuple.prototype.reduceRight.call('test')", "be a Tuple"); @@ -343,7 +346,7 @@ public void testSome() { assertFalse(execute("Object(#[1, 2, 3]).some(it => it < 0)").asBoolean()); } - @Ignore + @Ignore // TODO: re-evaluate, check proposal for changes @Test public void testSome_Fallback() { expectError("Tuple.prototype.some.call('test')", "be a Tuple"); diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TuplePrototypeBuiltins.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TuplePrototypeBuiltins.java index 1215e8c2469..6d5d58570a2 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TuplePrototypeBuiltins.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TuplePrototypeBuiltins.java @@ -47,14 +47,15 @@ import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.object.DynamicObject; import com.oracle.truffle.api.profiles.BranchProfile; +import com.oracle.truffle.api.profiles.ConditionProfile; +import com.oracle.truffle.js.nodes.access.IsObjectNode; import com.oracle.truffle.js.nodes.access.JSHasPropertyNode; +import com.oracle.truffle.js.nodes.access.PropertyNode; import com.oracle.truffle.js.nodes.access.ReadElementNode; -import com.oracle.truffle.js.nodes.array.JSArrayFirstElementIndexNode; -import com.oracle.truffle.js.nodes.array.JSArrayLastElementIndexNode; -import com.oracle.truffle.js.nodes.array.JSArrayNextElementIndexNode; import com.oracle.truffle.js.nodes.array.JSGetLengthNode; import com.oracle.truffle.js.nodes.cast.JSToBooleanNode; import com.oracle.truffle.js.nodes.cast.JSToIndexNode; +import com.oracle.truffle.js.nodes.cast.JSToIntegerAsIntNode; import com.oracle.truffle.js.nodes.cast.JSToIntegerAsLongNode; import com.oracle.truffle.js.nodes.cast.JSToObjectNode; import com.oracle.truffle.js.nodes.function.JSBuiltin; @@ -72,7 +73,7 @@ import com.oracle.truffle.js.runtime.builtins.JSArray; import com.oracle.truffle.js.runtime.builtins.JSFunction; import com.oracle.truffle.js.runtime.builtins.JSTuple; -import com.oracle.truffle.js.runtime.objects.JSDynamicObject; +import com.oracle.truffle.js.runtime.objects.JSObject; import com.oracle.truffle.js.runtime.objects.Undefined; import com.oracle.truffle.js.runtime.util.SimpleArrayList; @@ -265,255 +266,261 @@ protected long doOther(Object thisObj) { } } - public abstract static class JSTupleToStringNode extends JSBuiltinNode { + public abstract static class BasicTupleOperation extends JSBuiltinNode { - public JSTupleToStringNode(JSContext context, JSBuiltin builtin) { + protected final BranchProfile errorProfile = BranchProfile.create(); + + @Child private IsObjectNode isObjectNode; + + public BasicTupleOperation(JSContext context, JSBuiltin builtin) { super(context, builtin); } - @Specialization - protected String toString(Tuple thisObj) { - return thisObj.toString(); + protected Tuple thisTupleValue(Object value) { + if (value instanceof Tuple) { + return (Tuple) value; + } + if (JSTuple.isJSTuple(value)) { + return JSTuple.valueOf((DynamicObject) value); + } + errorProfile.enter(); + throw Errors.createTypeError("'this' must be a Tuple"); } - @Specialization(guards = {"isJSTuple(thisObj)"}) - protected String toString(DynamicObject thisObj) { - return JSTuple.valueOf(thisObj).toString(); + protected int checkSize(long size) { + if (size != (int) size) { + errorProfile.enter(); + throw Errors.createTypeError("Tuple instances cannot hold more than " + Integer.MAX_VALUE + " items currently"); + } + return (int) size; } - @Fallback - protected void toStringNoTuple(@SuppressWarnings("unused") Object thisObj) { - throw Errors.createTypeError("Tuple.prototype.toString requires that 'this' be a Tuple"); + protected boolean isObject(Object obj) { + if (isObjectNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + isObjectNode = insert(IsObjectNode.create()); + } + return isObjectNode.executeBoolean(obj); } } - public abstract static class JSTupleIteratorNode extends BasicTupleOperation { + public abstract static class JSTupleToStringNode extends BasicTupleOperation { - @Child private ArrayPrototypeBuiltins.CreateArrayIteratorNode createArrayIteratorNode; + private final static String JOIN = "join"; - public JSTupleIteratorNode(JSContext context, JSBuiltin builtin, int iterationKind) { + @Child private JSToObjectNode toObjectNode; + @Child private PropertyNode joinPropertyNode; + @Child private IsCallableNode isCallableNode; + @Child private JSFunctionCallNode callNode; + + public JSTupleToStringNode(JSContext context, JSBuiltin builtin) { super(context, builtin); - this.createArrayIteratorNode = ArrayPrototypeBuiltins.CreateArrayIteratorNode.create(context, iterationKind); } @Specialization - protected DynamicObject doObject(VirtualFrame frame, Object thisObj, - @Cached("createToObject(getContext())") JSToObjectNode toObjectNode) { + protected Object toString(Object thisObj) { Tuple tuple = thisTupleValue(thisObj); - Object obj = toObjectNode.execute(tuple); - return createArrayIteratorNode.execute(frame, obj); + Object tupleObj = toObject(tuple); + Object join = getJoinProperty(tupleObj); + if (isCallable(join)) { + return call(JSArguments.createZeroArg(tupleObj, join)); + } else { + return JSObject.defaultToString((DynamicObject) tupleObj); + } + } + + private Object toObject(Object obj) { + if (toObjectNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + toObjectNode = insert(JSToObjectNode.createToObject(getContext())); + } + return toObjectNode.execute(obj); + } + + private Object getJoinProperty(Object obj) { + if (joinPropertyNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + joinPropertyNode = insert(PropertyNode.createProperty(getContext(), null, JOIN)); + } + return joinPropertyNode.executeWithTarget(obj); + } + + private boolean isCallable(Object callback) { + if (isCallableNode == null) { + transferToInterpreterAndInvalidate(); + isCallableNode = insert(IsCallableNode.create()); + } + return isCallableNode.executeBoolean(callback); + } + + protected Object call(Object[] arguments) { + if (callNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + callNode = insert(JSFunctionCallNode.createCall()); + } + return callNode.executeCall(arguments); } } - public abstract static class JSTupleValueOfNode extends JSBuiltinNode { + public abstract static class JSTupleIteratorNode extends BasicTupleOperation { + + private final int iterationKind; - public JSTupleValueOfNode(JSContext context, JSBuiltin builtin) { + @Child private JSToObjectNode toObjectNode; + @Child private ArrayPrototypeBuiltins.CreateArrayIteratorNode createArrayIteratorNode; + + public JSTupleIteratorNode(JSContext context, JSBuiltin builtin, int iterationKind) { super(context, builtin); + this.iterationKind = iterationKind; } @Specialization - protected Tuple doTuple(Tuple thisObj) { - return thisObj; + protected DynamicObject doObject(VirtualFrame frame, Object thisObj) { + Tuple tuple = thisTupleValue(thisObj); + Object object = toObject(tuple); + return createArrayIterator(frame, object); } - @Specialization(guards = "isJSTuple(thisObj)") - protected Tuple doJSTuple(DynamicObject thisObj) { - return JSTuple.valueOf(thisObj); + private Object toObject(Object obj) { + if (toObjectNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + toObjectNode = insert(JSToObjectNode.createToObject(getContext())); + } + return toObjectNode.execute(obj); } - @Fallback - protected void fallback(@SuppressWarnings("unused") Object thisObj) { - throw Errors.createTypeError("Tuple.prototype.valueOf requires that 'this' be a Tuple"); + private DynamicObject createArrayIterator(VirtualFrame frame, Object object) { + if (createArrayIteratorNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + createArrayIteratorNode = insert(ArrayPrototypeBuiltins.CreateArrayIteratorNode.create(getContext(), iterationKind)); + } + return createArrayIteratorNode.execute(frame, object); } } - public abstract static class JSTuplePoppedNode extends JSBuiltinNode { + public abstract static class JSTupleValueOfNode extends BasicTupleOperation { - public JSTuplePoppedNode(JSContext context, JSBuiltin builtin) { + public JSTupleValueOfNode(JSContext context, JSBuiltin builtin) { super(context, builtin); } @Specialization - protected Tuple doTuple(Tuple thisObj) { - return getPoppedTuple(thisObj); + protected Tuple valueOf(Object thisObj) { + return thisTupleValue(thisObj); } + } + + public abstract static class JSTuplePoppedNode extends BasicTupleOperation { + + private final ConditionProfile emptyTupleProfile = ConditionProfile.createBinaryProfile(); - @Specialization(guards = "isJSTuple(thisObj)") - protected Tuple doJSTuple(DynamicObject thisObj) { - Tuple tuple = JSTuple.valueOf(thisObj); - return getPoppedTuple(tuple); + public JSTuplePoppedNode(JSContext context, JSBuiltin builtin) { + super(context, builtin); } - private Tuple getPoppedTuple(Tuple tuple) { - if (tuple.getArraySize() <= 1) { + @Specialization + protected Tuple popped(Object thisObj) { + Tuple tuple = thisTupleValue(thisObj); + if (emptyTupleProfile.profile(tuple.getArraySize() <= 1)) { return Tuple.EMPTY_TUPLE; } Object[] values = tuple.getElements(); - return Tuple.create(Arrays.copyOf(values, values.length - 1)); - } - - @Fallback - protected void fallback(@SuppressWarnings("unused") Object thisObj) { - throw Errors.createTypeError("Tuple.prototype.popped requires that 'this' be a Tuple"); + values = Arrays.copyOf(values, values.length - 1); + return Tuple.create(values); } } public abstract static class JSTuplePushedNode extends BasicTupleOperation { + public JSTuplePushedNode(JSContext context, JSBuiltin builtin) { super(context, builtin); } @Specialization - protected Tuple doTuple(Tuple thisObj, Object[] args) { - return getPushedTuple(thisObj, args); - } - - @Specialization(guards = "isJSTuple(thisObj)") - protected Tuple doJSTuple(DynamicObject thisObj, Object[] args) { - Tuple tuple = JSTuple.valueOf(thisObj); - return getPushedTuple(tuple, args); - } - - private Tuple getPushedTuple(Tuple tuple, Object[] args) { - long targetSize = tuple.getArraySize() + args.length; - checkSize(targetSize); - - Object[] values = Arrays.copyOf(tuple.getElements(), (int) (targetSize)); + protected Tuple pushed(Object thisObj, Object[] args) { + Tuple tuple = thisTupleValue(thisObj); + int targetSize = checkSize(tuple.getArraySize() + args.length); + Object[] values = Arrays.copyOf(tuple.getElements(), targetSize); for (int i = 0; i < args.length; i++) { Object value = args[i]; - if (!JSRuntime.isJSPrimitive(value)) { + if (isObject(value)) { + errorProfile.enter(); throw Errors.createTypeError("Tuples cannot contain non-primitive values"); } values[tuple.getArraySizeInt() + i] = value; } return Tuple.create(values); } - - @Fallback - protected void fallback(@SuppressWarnings("unused") Object thisObj, @SuppressWarnings("unused") Object args) { - throw Errors.createTypeError("Tuple.prototype.popped requires that 'this' be a Tuple"); - } } - public abstract static class JSTupleReversedNode extends JSBuiltinNode { + public abstract static class JSTupleReversedNode extends BasicTupleOperation { public JSTupleReversedNode(JSContext context, JSBuiltin builtin) { super(context, builtin); } @Specialization - protected Tuple doTuple(Tuple thisObj) { - return getReversedTuple(thisObj); - } - - @Specialization(guards = "isJSTuple(thisObj)") - protected Tuple doJSTuple(DynamicObject thisObj) { - Tuple tuple = JSTuple.valueOf(thisObj); - return getReversedTuple(tuple); - } - - private Tuple getReversedTuple(Tuple tuple) { + protected Tuple reversed(Object thisObj) { + Tuple tuple = thisTupleValue(thisObj); Object[] values = new Object[tuple.getArraySizeInt()]; for (int i = 0; i < values.length; i++) { values[i] = tuple.getElement(values.length - i - 1); } return Tuple.create(values); } - - @Fallback - protected void fallback(@SuppressWarnings("unused") Object thisObj) { - throw Errors.createTypeError("Tuple.prototype.reversed requires that 'this' be a Tuple"); - } } - public abstract static class JSTupleShiftedNode extends JSBuiltinNode { + public abstract static class JSTupleShiftedNode extends BasicTupleOperation { + + private final ConditionProfile emptyTupleProfile = ConditionProfile.createBinaryProfile(); public JSTupleShiftedNode(JSContext context, JSBuiltin builtin) { super(context, builtin); } @Specialization - protected Tuple doTuple(Tuple thisObj) { - return getShiftedTuple(thisObj); - } - - @Specialization(guards = "isJSTuple(thisObj)") - protected Tuple doJSTuple(DynamicObject thisObj) { - Tuple tuple = JSTuple.valueOf(thisObj); - return getShiftedTuple(tuple); - } - - private Tuple getShiftedTuple(Tuple tuple) { - if (tuple.getArraySize() == 0) { - return tuple; + protected Tuple shifted(Object thisObj) { + Tuple tuple = thisTupleValue(thisObj); + if (emptyTupleProfile.profile(tuple.getArraySize() <= 1)) { + return Tuple.EMPTY_TUPLE; } return Tuple.create(Arrays.copyOfRange(tuple.getElements(), 1, tuple.getArraySizeInt())); } - - @Fallback - protected void fallback(@SuppressWarnings("unused") Object thisObj) { - throw Errors.createTypeError("Tuple.prototype.shifted requires that 'this' be a Tuple"); - } } - public abstract static class BasicTupleOperation extends JSBuiltinNode { - - private final BranchProfile errorProfile = BranchProfile.create(); - - public BasicTupleOperation(JSContext context, JSBuiltin builtin) { - super(context, builtin); - } - - protected Tuple thisTupleValue(Object value) { - if (value instanceof Tuple) { - return (Tuple) value; - } - if (JSTuple.isJSTuple(value)) { - return JSTuple.valueOf((DynamicObject) value); - } - errorProfile.enter(); - throw Errors.createTypeError("'this' must be a Tuple"); - } - - protected Tuple toTupleValue(Object obj) { - if (obj instanceof Tuple) { - return (Tuple) obj; - } - if (JSTuple.isJSTuple(obj)) { - return JSTuple.valueOf((JSDynamicObject) obj); - } - throw Errors.createTypeError("'this' must be a Tuple"); - } + public abstract static class JSTupleSliceNode extends BasicTupleOperation { - protected void checkSize(long size) { - if (size > JSRuntime.MAX_SAFE_INTEGER) { - throw Errors.createTypeError("length too big"); - } - } - } + private final ConditionProfile emptyTupleProfile = ConditionProfile.createBinaryProfile(); - public abstract static class JSTupleSliceNode extends BasicTupleOperation { + @Child private JSToIntegerAsIntNode toIntegerAsIntNode; public JSTupleSliceNode(JSContext context, JSBuiltin builtin) { super(context, builtin); } @Specialization - protected Tuple slice(Object thisObj, Object begin, Object end, - @Cached("create()") JSToIntegerAsLongNode toIntegerAsLong) { - Tuple tuple = toTupleValue(thisObj); - long size = tuple.getArraySize(); + protected Tuple slice(Object thisObj, Object begin, Object end) { + Tuple tuple = thisTupleValue(thisObj); + int size = tuple.getArraySizeInt(); - long startPos = toIntegerAsLong.executeLong(begin); - long endPos = end == Undefined.instance ? size : toIntegerAsLong.executeLong(end); + int startPos = toInteger(begin); + int endPos = end == Undefined.instance ? size : toInteger(end); startPos = startPos < 0 ? Math.max(size + startPos, 0) : Math.min(startPos, size); endPos = endPos < 0 ? Math.max(size + endPos, 0) : Math.min(endPos, size); - if (startPos >= endPos) { + if (emptyTupleProfile.profile(startPos >= endPos)) { return Tuple.EMPTY_TUPLE; } - return Tuple.create(Arrays.copyOfRange(tuple.getElements(), (int) startPos, (int) endPos)); + return Tuple.create(Arrays.copyOfRange(tuple.getElements(), startPos, endPos)); + } + + private int toInteger(Object obj) { + if (toIntegerAsIntNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + toIntegerAsIntNode = insert(JSToIntegerAsIntNode.create()); + } + return toIntegerAsIntNode.executeInt(obj); } } @@ -528,7 +535,7 @@ public JSTupleSortedNode(JSContext context, JSBuiltin builtin) { @Specialization protected Object sorted(Object thisObj, Object comparefn) { checkCompareFunction(comparefn); - Tuple tuple = toTupleValue(thisObj); + Tuple tuple = thisTupleValue(thisObj); long size = tuple.getArraySize(); if (size < 2) { @@ -544,6 +551,7 @@ protected Object sorted(Object thisObj, Object comparefn) { private void checkCompareFunction(Object compare) { if (!(compare == Undefined.instance || isCallable(compare))) { + errorProfile.enter(); throw Errors.createTypeError("The comparison function must be either a function or undefined"); } } @@ -565,6 +573,7 @@ private Comparator getComparator(Object comparefn) { } } + // taken from ArrayPrototypeBuiltins.JSArraySortNode.SortComparator and slightly adjusted private class SortComparator implements Comparator { private final Object compFnObj; private final boolean isFunction; @@ -629,20 +638,21 @@ private static void sortIntl(Comparator comparator, Object[] array) { public abstract static class JSTupleSplicedNode extends BasicTupleOperation { + @Child JSToIntegerAsLongNode toIntegerAsLongNode; + public JSTupleSplicedNode(JSContext context, JSBuiltin builtin) { super(context, builtin); } @Specialization - protected Tuple spliced(Object thisObj, Object[] args, - @Cached("create()") JSToIntegerAsLongNode toIntegerAsLong) { + protected Tuple spliced(Object thisObj, Object[] args) { Object start = JSRuntime.getArgOrUndefined(args, 0); Object deleteCount = JSRuntime.getArgOrUndefined(args, 1); - Tuple tuple = toTupleValue(thisObj); + Tuple tuple = thisTupleValue(thisObj); long size = tuple.getArraySize(); - long startPos = toIntegerAsLong.executeLong(start); + long startPos = toInteger(start); startPos = startPos < 0 ? Math.max(size + startPos, 0) : Math.min(startPos, size); long insertCount, delCount; @@ -654,12 +664,12 @@ protected Tuple spliced(Object thisObj, Object[] args, delCount = size - startPos; } else { insertCount = args.length - 2; - delCount = toIntegerAsLong.executeLong(deleteCount); + delCount = toInteger(deleteCount); delCount = Math.min(Math.max(delCount, 0), size - startPos); } - checkSize(size + insertCount - delCount); - Object[] values = new Object[(int) (size + insertCount - delCount)]; + int valuesSize = checkSize(size + insertCount - delCount); + Object[] values = new Object[valuesSize]; int k = 0; while (k < startPos) { @@ -667,19 +677,28 @@ protected Tuple spliced(Object thisObj, Object[] args, k++; } for (int i = 2; i < args.length; i++) { - if (!JSRuntime.isJSPrimitive(args[i])) { + if (isObject(args[i])) { + errorProfile.enter(); throw Errors.createTypeError("Tuples cannot contain non-primitive values"); } values[k] = args[i]; k++; } - for (int i = (int) (startPos + delCount); i < size; i++) { + for (long i = startPos + delCount; i < size; i++) { values[k] = tuple.getElement(i); k++; } return Tuple.create(values); } + + private long toInteger(Object obj) { + if (toIntegerAsLongNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + toIntegerAsLongNode = insert(JSToIntegerAsLongNode.create()); + } + return toIntegerAsLongNode.executeLong(obj); + } } public abstract static class JSTupleConcatNode extends BasicTupleOperation { @@ -698,7 +717,7 @@ public JSTupleConcatNode(JSContext context, JSBuiltin builtin) { @Specialization protected Tuple concat(Object thisObj, Object[] args) { - Tuple tuple = toTupleValue(thisObj); + Tuple tuple = thisTupleValue(thisObj); SimpleArrayList list = new SimpleArrayList<>(1 + JSConfig.SpreadArgumentPlaceholderCount); concatElement(tuple, list); for (Object arg : args) { @@ -718,14 +737,16 @@ private void concatElement(Object e, SimpleArrayList list) { for (long k = 0; k < len; k++) { if (hasProperty(e, k)) { Object subElement = get(e, k); - if (JSRuntime.isObject(subElement)) { + if (isObject(subElement)) { + errorProfile.enter(); throw Errors.createTypeError("Tuples cannot contain non-primitive values"); } list.add(subElement, growProfile); } } } else { - if (JSRuntime.isObject(e)) { + if (isObject(e)) { + errorProfile.enter(); throw Errors.createTypeError("Tuples cannot contain non-primitive values"); } list.add(e, growProfile); @@ -769,11 +790,7 @@ private Object get(Object target, long index) { CompilerDirectives.transferToInterpreterAndInvalidate(); readElementNode = insert(ReadElementNode.create(getContext())); } - if (JSRuntime.longIsRepresentableAsInt(index)) { - return readElementNode.executeWithTargetAndIndex(target, (int) index); - } else { - return readElementNode.executeWithTargetAndIndex(target, (double) index); - } + return readElementNode.executeWithTargetAndIndex(target, index); } } @@ -792,6 +809,7 @@ protected Tuple filter(Object thisObj, Object callbackfn, Object thisArg, @Cached("createCall()") JSFunctionCallNode callNode) { Tuple tuple = thisTupleValue(thisObj); if (!isCallableNode.executeBoolean(callbackfn)) { + errorProfile.enter(); throw Errors.createTypeErrorCallableExpected(); } SimpleArrayList list = SimpleArrayList.create(tuple.getArraySize()); @@ -810,8 +828,6 @@ protected Tuple filter(Object thisObj, Object callbackfn, Object thisArg, public abstract static class TupleFlattenOperation extends BasicTupleOperation { - private final BranchProfile errorProfile = BranchProfile.create(); - @Child private JSFunctionCallNode callNode; @@ -832,7 +848,7 @@ protected void flattenIntoTuple(SimpleArrayList target, BranchProfile gr Object element = source.getElement(i); if (mapperFunction != null) { element = call(JSArguments.create(thisArg, mapperFunction, element, i, source)); - if (JSRuntime.isObject(element)) { + if (isObject(element)) { errorProfile.enter(); throw Errors.createTypeError("Tuples cannot contain non-primitive values"); } @@ -883,6 +899,7 @@ public JSTupleFlatMapNode(JSContext context, JSBuiltin builtin) { protected Tuple flatMap(Object thisObj, Object mapperFunction, Object thisArg) { Tuple tuple = thisTupleValue(thisObj); if (!isCallableNode.executeBoolean(mapperFunction)) { + errorProfile.enter(); throw Errors.createTypeErrorCallableExpected(); } SimpleArrayList list = SimpleArrayList.create(tuple.getArraySize()); @@ -894,7 +911,6 @@ protected Tuple flatMap(Object thisObj, Object mapperFunction, Object thisArg) { public abstract static class JSTupleMapNode extends BasicTupleOperation { private final BranchProfile growProfile = BranchProfile.create(); - private final BranchProfile errorProfile = BranchProfile.create(); @Child private IsCallableNode isCallableNode = IsCallableNode.create(); @Child private JSFunctionCallNode callNode; @@ -907,13 +923,14 @@ public JSTupleMapNode(JSContext context, JSBuiltin builtin) { protected Tuple map(Object thisObj, Object mapperFunction, Object thisArg) { Tuple tuple = thisTupleValue(thisObj); if (!isCallableNode.executeBoolean(mapperFunction)) { + errorProfile.enter(); throw Errors.createTypeErrorCallableExpected(); } SimpleArrayList list = SimpleArrayList.create(tuple.getArraySize()); for (long k = 0; k < tuple.getArraySize(); k++) { Object value = tuple.getElement(k); value = call(JSArguments.create(thisArg, mapperFunction, value, k, tuple)); - if (JSRuntime.isObject(value)) { + if (isObject(value)) { errorProfile.enter(); throw Errors.createTypeError("Tuples cannot contain non-primitive values"); } @@ -934,7 +951,6 @@ protected Object call(Object[] arguments) { public abstract static class JSTupleUnshiftedNode extends BasicTupleOperation { private final BranchProfile growProfile = BranchProfile.create(); - private final BranchProfile errorProfile = BranchProfile.create(); public JSTupleUnshiftedNode(JSContext context, JSBuiltin builtin) { super(context, builtin); @@ -945,7 +961,7 @@ protected Tuple unshifted(Object thisObj, Object[] args) { Tuple tuple = thisTupleValue(thisObj); SimpleArrayList list = SimpleArrayList.create(tuple.getArraySize()); for (Object arg : args) { - if (JSRuntime.isObject(arg)) { + if (isObject(arg)) { errorProfile.enter(); throw Errors.createTypeError("Tuples cannot contain non-primitive values"); } @@ -953,7 +969,7 @@ protected Tuple unshifted(Object thisObj, Object[] args) { } for (long k = 0; k < tuple.getArraySize(); k++) { Object value = tuple.getElement(k); - if (JSRuntime.isObject(value)) { + if (isObject(value)) { errorProfile.enter(); throw Errors.createTypeError("Tuples cannot contain non-primitive values"); } @@ -965,9 +981,6 @@ protected Tuple unshifted(Object thisObj, Object[] args) { public abstract static class JSTupleWithNode extends BasicTupleOperation { - private final BranchProfile rangeErrorProfile = BranchProfile.create(); - private final BranchProfile primitiveErrorProfile = BranchProfile.create(); - @Child private JSToIndexNode toIndexNode = JSToIndexNode.create(); public JSTupleWithNode(JSContext context, JSBuiltin builtin) { @@ -980,12 +993,12 @@ protected Tuple with(Object thisObj, Object index, Object value) { Object[] list = tuple.getElements(); long i = toIndexNode.executeLong(index); if (i >= list.length) { - rangeErrorProfile.enter(); + errorProfile.enter(); throw Errors.createRangeError("Index out of range"); } - if (JSRuntime.isObject(value)) { - primitiveErrorProfile.enter(); + if (isObject(value)) { + errorProfile.enter(); throw Errors.createTypeError("Tuples cannot contain non-primitive values"); } list[(int) i] = value; diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/access/IsObjectNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/access/IsObjectNode.java index 361fcd90c77..3ceb5d59af9 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/access/IsObjectNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/access/IsObjectNode.java @@ -48,8 +48,10 @@ import com.oracle.truffle.js.nodes.JavaScriptBaseNode; import com.oracle.truffle.js.runtime.BigInt; import com.oracle.truffle.js.runtime.JSConfig; +import com.oracle.truffle.js.runtime.Record; import com.oracle.truffle.js.runtime.SafeInteger; import com.oracle.truffle.js.runtime.Symbol; +import com.oracle.truffle.js.runtime.Tuple; /** * Checks whether the argument is of type Object (JS or foreign), i.e., not a primitive value. @@ -104,6 +106,16 @@ protected static boolean doBigInt(@SuppressWarnings("unused") BigInt operand) { return false; } + @Specialization + protected static boolean doRecord(@SuppressWarnings("unused") Record operand) { + return false; + } + + @Specialization + protected static boolean doTuple(@SuppressWarnings("unused") Tuple operand) { + return false; + } + @Specialization protected static boolean doString(@SuppressWarnings("unused") CharSequence operand) { return false; From 694e2d0f2ef8147fc2aae65bc47bc3664ce6f24c Mon Sep 17 00:00:00 2001 From: Christian Aistleitner Date: Sat, 29 May 2021 16:48:55 +0200 Subject: [PATCH 28/31] Use nodes for abstract operations RecordToString and TupleToString. --- .../truffle/js/nodes/cast/JSToStringNode.java | 10 +- .../js/nodes/record/JSRecordToStringNode.java | 62 +++++++++++ .../js/nodes/tuples/JSTupleToStringNode.java | 105 ++++++++++++++++++ .../oracle/truffle/js/runtime/JSRuntime.java | 13 ++- .../com/oracle/truffle/js/runtime/Tuple.java | 6 - 5 files changed, 185 insertions(+), 11 deletions(-) create mode 100644 graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/record/JSRecordToStringNode.java create mode 100644 graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/JSTupleToStringNode.java diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToStringNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToStringNode.java index 4ce8bf501c4..222c2f15874 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToStringNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToStringNode.java @@ -52,6 +52,8 @@ import com.oracle.truffle.js.nodes.JavaScriptBaseNode; import com.oracle.truffle.js.nodes.JavaScriptNode; import com.oracle.truffle.js.nodes.cast.JSToStringNodeGen.JSToStringWrapperNodeGen; +import com.oracle.truffle.js.nodes.record.JSRecordToStringNode; +import com.oracle.truffle.js.nodes.tuples.JSTupleToStringNode; import com.oracle.truffle.js.nodes.unary.JSUnaryNode; import com.oracle.truffle.js.runtime.BigInt; import com.oracle.truffle.js.runtime.Boundaries; @@ -164,13 +166,13 @@ protected String doSymbol(Symbol value) { } @Specialization - protected String doRecord(Record value) { - return JSRuntime.recordToString(value); + protected String doRecord(Record value, @Cached("create()") JSRecordToStringNode recordToStringNode) { + return recordToStringNode.execute(value); } @Specialization - protected String doTuple(Tuple value) { - return JSRuntime.tupleToString(value); + protected String doTuple(Tuple value, @Cached("create()") JSTupleToStringNode tupleToStringNode ) { + return tupleToStringNode.execute(value); } @Specialization(guards = {"isForeignObject(object)"}) diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/record/JSRecordToStringNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/record/JSRecordToStringNode.java new file mode 100644 index 00000000000..1e928e28b4a --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/record/JSRecordToStringNode.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.truffle.js.nodes.record; + +import com.oracle.truffle.js.nodes.JavaScriptBaseNode; +import com.oracle.truffle.js.runtime.Record; +import com.oracle.truffle.js.runtime.builtins.JSRecord; + +/** + * Implementation of the abstract operation RecordToString(argument). + */ +public class JSRecordToStringNode extends JavaScriptBaseNode { + + protected JSRecordToStringNode() { + } + + public static JSRecordToStringNode create() { + return new JSRecordToStringNode(); + } + + public String execute(@SuppressWarnings("unused") Record record) { + return JSRecord.STRING_NAME; + } +} diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/JSTupleToStringNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/JSTupleToStringNode.java new file mode 100644 index 00000000000..1589c9474bc --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/JSTupleToStringNode.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.truffle.js.nodes.tuples; + +import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.profiles.ConditionProfile; +import com.oracle.truffle.js.nodes.JavaScriptBaseNode; +import com.oracle.truffle.js.nodes.cast.JSToStringNode; +import com.oracle.truffle.js.runtime.Boundaries; +import com.oracle.truffle.js.runtime.Tuple; +import com.oracle.truffle.js.runtime.objects.Null; +import com.oracle.truffle.js.runtime.objects.Undefined; + +/** + * Implementation of the abstract operation TupleToString(argument). + */ +public class JSTupleToStringNode extends JavaScriptBaseNode { + + private final ConditionProfile isEmptyTuple = ConditionProfile.createBinaryProfile(); + private final ConditionProfile isSingleValue = ConditionProfile.createBinaryProfile(); + + @Child private JSToStringNode toStringNode; + + protected JSTupleToStringNode() { + super(); + } + + public static JSTupleToStringNode create() { + return new JSTupleToStringNode(); + } + + public String execute(Tuple argument) { + // TODO: re-evaluate, check proposal for changes + // TODO: the following code isn't strictly according to spec + // TODO: as I didn't find a way to call %Array.prototype.join% from this node... + return join(argument); + } + + private String join(Tuple tuple) { + long len = tuple.getArraySize(); + if (isEmptyTuple.profile(len == 0)) { + return ""; + } + if (isSingleValue.profile(len == 1)) { + return toString(tuple.getElement(0)); + } + StringBuilder sb = new StringBuilder(); + for (long k = 0; k < len; k++) { + if (k > 0) { + Boundaries.builderAppend(sb, ','); + } + Object element = tuple.getElement(k); + if (element != Undefined.instance && element != Null.instance) { + Boundaries.builderAppend(sb, toString(element)); + } + } + return Boundaries.builderToString(sb); + } + + private String toString(Object obj) { + if (toStringNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + toStringNode = insert(JSToStringNode.create()); + } + return toStringNode.executeString(obj); + } +} diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSRuntime.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSRuntime.java index a2ab8275182..ed68f4c10f0 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSRuntime.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSRuntime.java @@ -1351,8 +1351,19 @@ public static String recordToString(@SuppressWarnings("unused") Record value) { /** * Record & Tuple Proposal 2.1.2.1 TupleToString */ + @TruffleBoundary public static String tupleToString(Tuple value) { - return value.toString(); + StringBuilder sb = new StringBuilder(); + for (long k = 0; k < value.getArraySize(); k++) { + if (k > 0) { + sb.append(','); + } + Object element = value.getElement(k); + if (!isNullOrUndefined(element)) { + sb.append(toString(element)); + } + } + return sb.toString(); } public static String toString(DynamicObject value) { diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Tuple.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Tuple.java index 1867eadab67..2585a64acb0 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Tuple.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Tuple.java @@ -147,10 +147,4 @@ public boolean equals(Tuple other) { } return true; } - - @Override - @TruffleBoundary - public String toString() { - return Arrays.stream(value).map(String::valueOf).collect(Collectors.joining(",")); - } } From e2488b324ba6825d3a4eefa6ebd850d16b31f7cf Mon Sep 17 00:00:00 2001 From: Christian Aistleitner Date: Sun, 30 May 2021 08:47:47 +0200 Subject: [PATCH 29/31] Extract expectError and execute methods to a common base class. --- .../js/record-tuple/record.js | 2 +- .../oracle/truffle/js/test/JSSimpleTest.java | 97 +++++++++++++++++++ .../js/test/builtins/JSONBuiltinsTest.java | 23 +---- ...ava => RecordAndTupleConstructorTest.java} | 34 +------ .../builtins/RecordFunctionBuiltinsTest.java | 34 +------ .../builtins/TupleFunctionBuiltinsTest.java | 34 +------ .../builtins/TuplePrototypeBuiltinsTest.java | 40 ++------ .../test/comparison/RecordEqualityTest.java | 19 +--- .../js/test/comparison/TupleEqualityTest.java | 19 +--- .../nodes/record/RecordLiteralNodeTest.java | 28 ++---- 10 files changed, 143 insertions(+), 187 deletions(-) create mode 100644 graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/JSSimpleTest.java rename graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/{ConstructorBuiltinsTest.java => RecordAndTupleConstructorTest.java} (68%) diff --git a/graal-js/src/com.oracle.truffle.js.test/js/record-tuple/record.js b/graal-js/src/com.oracle.truffle.js.test/js/record-tuple/record.js index f979aef264a..ef495c5cfe1 100644 --- a/graal-js/src/com.oracle.truffle.js.test/js/record-tuple/record.js +++ b/graal-js/src/com.oracle.truffle.js.test/js/record-tuple/record.js @@ -42,7 +42,7 @@ assertThrows(function() { */ const y = #{ "__proto__": 1 }; // valid, creates a record with a "__proto__" property. assertThrows(function() { - eval("const x = #{ __proto__: foo }"); // SyntaxError, __proto__ identifier prevented by syntax + eval("const x = #{ __proto__: 1 }"); // SyntaxError, __proto__ identifier prevented by syntax }, SyntaxError); /* diff --git a/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/JSSimpleTest.java b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/JSSimpleTest.java new file mode 100644 index 00000000000..e290d51a040 --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/JSSimpleTest.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.truffle.js.test; + +import com.oracle.truffle.js.lang.JavaScriptLanguage; +import org.graalvm.polyglot.Context; +import org.graalvm.polyglot.Source; +import org.graalvm.polyglot.Value; +import org.junit.Assert; + +import java.util.HashMap; +import java.util.Map; + +/** + * Base for testing simple JS snippets. + */ +public abstract class JSSimpleTest { + + protected final String testName; + + private final Map options = new HashMap<>(); + + protected JSSimpleTest(String testName) { + this.testName = testName; + } + + protected void addOption(String key, String value) { + options.put(key, value); + } + + protected Value execute(String sourceText) { + try (Context context = newContext()) { + return context.eval(Source.newBuilder(JavaScriptLanguage.ID, sourceText, testName).buildLiteral()); + } + } + + protected Value execute(String... sourceText) { + return execute(String.join("\n", sourceText)); + } + + protected void expectError(String sourceText, String expectedMessage) { + try (Context context = newContext()) { + context.eval(Source.newBuilder(JavaScriptLanguage.ID, sourceText, testName).buildLiteral()); + Assert.fail("should have thrown"); + } catch (Exception ex) { + Assert.assertTrue( + String.format("\"%s\" should contain \"%s\"", ex.getMessage(), expectedMessage), + ex.getMessage().contains(expectedMessage) + ); + } + } + + private Context newContext() { + return Context.newBuilder(JavaScriptLanguage.ID) + .allowExperimentalOptions(true) + .options(options) + .build(); + } +} diff --git a/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/JSONBuiltinsTest.java b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/JSONBuiltinsTest.java index 718b59c2c38..77acc48c6ee 100644 --- a/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/JSONBuiltinsTest.java +++ b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/JSONBuiltinsTest.java @@ -40,30 +40,17 @@ */ package com.oracle.truffle.js.test.builtins; -import com.oracle.truffle.js.lang.JavaScriptLanguage; import com.oracle.truffle.js.runtime.JSContextOptions; -import com.oracle.truffle.js.test.JSTest; -import org.graalvm.polyglot.Context; -import org.graalvm.polyglot.Source; -import org.graalvm.polyglot.Value; +import com.oracle.truffle.js.test.JSSimpleTest; import org.junit.Test; import static org.junit.Assert.assertTrue; -public class JSONBuiltinsTest { +public class JSONBuiltinsTest extends JSSimpleTest { - private static final String testName = "json-builtins-test"; - - private static Value execute(String sourceText) { - try (Context context = JSTest.newContextBuilder() - .option(JSContextOptions.ECMASCRIPT_VERSION_NAME, "2022") - .build()) { - return context.eval(Source.newBuilder(JavaScriptLanguage.ID, sourceText, testName).buildLiteral()); - } - } - - private static Value execute(String... sourceText) { - return execute(String.join("\n", sourceText)); + public JSONBuiltinsTest() { + super("json-builtins-test"); + addOption(JSContextOptions.ECMASCRIPT_VERSION_NAME, "2022"); } @Test diff --git a/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/ConstructorBuiltinsTest.java b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/RecordAndTupleConstructorTest.java similarity index 68% rename from graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/ConstructorBuiltinsTest.java rename to graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/RecordAndTupleConstructorTest.java index 247681e9b38..566b9e89c71 100644 --- a/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/ConstructorBuiltinsTest.java +++ b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/RecordAndTupleConstructorTest.java @@ -40,41 +40,17 @@ */ package com.oracle.truffle.js.test.builtins; -import com.oracle.truffle.js.lang.JavaScriptLanguage; import com.oracle.truffle.js.runtime.JSContextOptions; -import com.oracle.truffle.js.test.JSTest; -import org.graalvm.polyglot.Context; -import org.graalvm.polyglot.Source; -import org.graalvm.polyglot.Value; -import org.junit.Assert; +import com.oracle.truffle.js.test.JSSimpleTest; import org.junit.Test; import static org.junit.Assert.assertTrue; -public class ConstructorBuiltinsTest { +public class RecordAndTupleConstructorTest extends JSSimpleTest { - private static final String testName = "constructor-builtins-test"; - - private static Value execute(String sourceText) { - try (Context context = JSTest.newContextBuilder() - .option(JSContextOptions.ECMASCRIPT_VERSION_NAME, "2022") - .build()) { - return context.eval(Source.newBuilder(JavaScriptLanguage.ID, sourceText, testName).buildLiteral()); - } - } - - private static void expectError(String sourceText, String expectedMessage) { - try (Context context = JSTest.newContextBuilder() - .option(JSContextOptions.ECMASCRIPT_VERSION_NAME, "2022") - .build()) { - context.eval(Source.newBuilder(JavaScriptLanguage.ID, sourceText, testName).buildLiteral()); - Assert.fail("should have thrown"); - } catch (Exception ex) { - Assert.assertTrue( - String.format("\"%s\" should contain \"%s\"", ex.getMessage(), expectedMessage), - ex.getMessage().contains(expectedMessage) - ); - } + public RecordAndTupleConstructorTest() { + super("record-and-tuple-constructor-test"); + addOption(JSContextOptions.ECMASCRIPT_VERSION_NAME, "2022"); } @Test diff --git a/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/RecordFunctionBuiltinsTest.java b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/RecordFunctionBuiltinsTest.java index 9dcf0f5e880..57f79e83d6b 100644 --- a/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/RecordFunctionBuiltinsTest.java +++ b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/RecordFunctionBuiltinsTest.java @@ -40,42 +40,18 @@ */ package com.oracle.truffle.js.test.builtins; -import com.oracle.truffle.js.lang.JavaScriptLanguage; import com.oracle.truffle.js.runtime.JSContextOptions; -import com.oracle.truffle.js.test.JSTest; -import org.graalvm.polyglot.Context; -import org.graalvm.polyglot.Source; -import org.graalvm.polyglot.Value; -import org.junit.Assert; +import com.oracle.truffle.js.test.JSSimpleTest; import org.junit.Test; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -public class RecordFunctionBuiltinsTest { +public class RecordFunctionBuiltinsTest extends JSSimpleTest { - private static final String testName = "record-function-builtins-test"; - - private static Value execute(String sourceText) { - try (Context context = JSTest.newContextBuilder() - .option(JSContextOptions.ECMASCRIPT_VERSION_NAME, "2022") - .build()) { - return context.eval(Source.newBuilder(JavaScriptLanguage.ID, sourceText, testName).buildLiteral()); - } - } - - private static void expectError(String sourceText, String expectedMessage) { - try (Context context = JSTest.newContextBuilder() - .option(JSContextOptions.ECMASCRIPT_VERSION_NAME, "2022") - .build()) { - context.eval(Source.newBuilder(JavaScriptLanguage.ID, sourceText, testName).buildLiteral()); - Assert.fail("should have thrown"); - } catch (Exception ex) { - Assert.assertTrue( - String.format("\"%s\" should contain \"%s\"", ex.getMessage(), expectedMessage), - ex.getMessage().contains(expectedMessage) - ); - } + public RecordFunctionBuiltinsTest() { + super("record-function-builtins-test"); + addOption(JSContextOptions.ECMASCRIPT_VERSION_NAME, "2022"); } @Test diff --git a/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/TupleFunctionBuiltinsTest.java b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/TupleFunctionBuiltinsTest.java index 74ad3021319..23b9a328c29 100644 --- a/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/TupleFunctionBuiltinsTest.java +++ b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/TupleFunctionBuiltinsTest.java @@ -40,43 +40,19 @@ */ package com.oracle.truffle.js.test.builtins; -import com.oracle.truffle.js.lang.JavaScriptLanguage; import com.oracle.truffle.js.runtime.JSContextOptions; -import com.oracle.truffle.js.test.JSTest; -import org.graalvm.polyglot.Context; -import org.graalvm.polyglot.Source; -import org.graalvm.polyglot.Value; -import org.junit.Assert; +import com.oracle.truffle.js.test.JSSimpleTest; import org.junit.Ignore; import org.junit.Test; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -public class TupleFunctionBuiltinsTest { +public class TupleFunctionBuiltinsTest extends JSSimpleTest { - private static final String testName = "tuple-function-builtins-test"; - - private static Value execute(String sourceText) { - try (Context context = JSTest.newContextBuilder() - .option(JSContextOptions.ECMASCRIPT_VERSION_NAME, "2022") - .build()) { - return context.eval(Source.newBuilder(JavaScriptLanguage.ID, sourceText, testName).buildLiteral()); - } - } - - private static void expectError(String sourceText, String expectedMessage) { - try (Context context = JSTest.newContextBuilder() - .option(JSContextOptions.ECMASCRIPT_VERSION_NAME, "2022") - .build()) { - context.eval(Source.newBuilder(JavaScriptLanguage.ID, sourceText, testName).buildLiteral()); - Assert.fail("should have thrown"); - } catch (Exception ex) { - Assert.assertTrue( - String.format("\"%s\" should contain \"%s\"", ex.getMessage(), expectedMessage), - ex.getMessage().contains(expectedMessage) - ); - } + public TupleFunctionBuiltinsTest() { + super("tuple-function-builtins-test"); + addOption(JSContextOptions.ECMASCRIPT_VERSION_NAME, "2022"); } @Test diff --git a/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/TuplePrototypeBuiltinsTest.java b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/TuplePrototypeBuiltinsTest.java index 7ef95ea17d5..f6b89aa9510 100644 --- a/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/TuplePrototypeBuiltinsTest.java +++ b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/TuplePrototypeBuiltinsTest.java @@ -40,13 +40,8 @@ */ package com.oracle.truffle.js.test.builtins; -import com.oracle.truffle.js.lang.JavaScriptLanguage; import com.oracle.truffle.js.runtime.JSContextOptions; -import com.oracle.truffle.js.test.JSTest; -import org.graalvm.polyglot.Context; -import org.graalvm.polyglot.Source; -import org.graalvm.polyglot.Value; -import org.junit.Assert; +import com.oracle.truffle.js.test.JSSimpleTest; import org.junit.Ignore; import org.junit.Test; @@ -54,35 +49,12 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -public class TuplePrototypeBuiltinsTest { +public class TuplePrototypeBuiltinsTest extends JSSimpleTest { - private static final String testName = "tuple-prototype-builtins-test"; - - private static Value execute(String sourceText) { - try (Context context = JSTest.newContextBuilder() - .option(JSContextOptions.ECMASCRIPT_VERSION_NAME, "2022") - .option(JSContextOptions.INTL_402_NAME, "true") - .build()) { - return context.eval(Source.newBuilder(JavaScriptLanguage.ID, sourceText, testName).buildLiteral()); - } - } - - private static Value execute(String... sourceText) { - return execute(String.join("\n", sourceText)); - } - - private static void expectError(String sourceText, String expectedMessage) { - try (Context context = JSTest.newContextBuilder() - .option(JSContextOptions.ECMASCRIPT_VERSION_NAME, "2022") - .build()) { - context.eval(Source.newBuilder(JavaScriptLanguage.ID, sourceText, testName).buildLiteral()); - Assert.fail("should have thrown"); - } catch (Exception ex) { - Assert.assertTrue( - String.format("\"%s\" should contain \"%s\"", ex.getMessage(), expectedMessage), - ex.getMessage().contains(expectedMessage) - ); - } + public TuplePrototypeBuiltinsTest() { + super("tuple-prototype-builtins-test"); + addOption(JSContextOptions.ECMASCRIPT_VERSION_NAME, "2022"); + addOption(JSContextOptions.INTL_402_NAME, "true"); } @Test diff --git a/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/comparison/RecordEqualityTest.java b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/comparison/RecordEqualityTest.java index 051946fbac7..e8d23f2e3f2 100644 --- a/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/comparison/RecordEqualityTest.java +++ b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/comparison/RecordEqualityTest.java @@ -40,27 +40,18 @@ */ package com.oracle.truffle.js.test.comparison; -import com.oracle.truffle.js.lang.JavaScriptLanguage; import com.oracle.truffle.js.runtime.JSContextOptions; -import com.oracle.truffle.js.test.JSTest; -import org.graalvm.polyglot.Context; -import org.graalvm.polyglot.Source; -import org.graalvm.polyglot.Value; +import com.oracle.truffle.js.test.JSSimpleTest; import org.junit.Test; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -public class RecordEqualityTest { +public class RecordEqualityTest extends JSSimpleTest { - private static final String testName = "record-equality-test"; - - private static Value execute(String sourceText) { - try (Context context = JSTest.newContextBuilder() - .option(JSContextOptions.ECMASCRIPT_VERSION_NAME, "2022") - .build()) { - return context.eval(Source.newBuilder(JavaScriptLanguage.ID, sourceText, testName).buildLiteral()); - } + public RecordEqualityTest() { + super("record-equality-test"); + addOption(JSContextOptions.ECMASCRIPT_VERSION_NAME, "2022"); } @Test diff --git a/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/comparison/TupleEqualityTest.java b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/comparison/TupleEqualityTest.java index 6c9940e6dcb..d8a9208c824 100644 --- a/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/comparison/TupleEqualityTest.java +++ b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/comparison/TupleEqualityTest.java @@ -40,27 +40,18 @@ */ package com.oracle.truffle.js.test.comparison; -import com.oracle.truffle.js.lang.JavaScriptLanguage; import com.oracle.truffle.js.runtime.JSContextOptions; -import com.oracle.truffle.js.test.JSTest; -import org.graalvm.polyglot.Context; -import org.graalvm.polyglot.Source; -import org.graalvm.polyglot.Value; +import com.oracle.truffle.js.test.JSSimpleTest; import org.junit.Test; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -public class TupleEqualityTest { +public class TupleEqualityTest extends JSSimpleTest { - private static final String testName = "tuple-equality-test"; - - private static Value execute(String sourceText) { - try (Context context = JSTest.newContextBuilder() - .option(JSContextOptions.ECMASCRIPT_VERSION_NAME, "2022") - .build()) { - return context.eval(Source.newBuilder(JavaScriptLanguage.ID, sourceText, testName).buildLiteral()); - } + public TupleEqualityTest() { + super("tuple-equality-test"); + addOption(JSContextOptions.ECMASCRIPT_VERSION_NAME, "2022"); } @Test diff --git a/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/nodes/record/RecordLiteralNodeTest.java b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/nodes/record/RecordLiteralNodeTest.java index 91fb5b1c8d3..f62f0296df7 100644 --- a/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/nodes/record/RecordLiteralNodeTest.java +++ b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/nodes/record/RecordLiteralNodeTest.java @@ -1,31 +1,17 @@ package com.oracle.truffle.js.test.nodes.record; -import com.oracle.truffle.js.lang.JavaScriptLanguage; import com.oracle.truffle.js.runtime.JSContextOptions; -import com.oracle.truffle.js.test.JSTest; -import org.graalvm.polyglot.Context; -import org.graalvm.polyglot.Source; -import org.graalvm.polyglot.Value; +import com.oracle.truffle.js.test.JSSimpleTest; import org.junit.Ignore; import org.junit.Test; import static org.junit.Assert.assertTrue; -public class RecordLiteralNodeTest { +public class RecordLiteralNodeTest extends JSSimpleTest { - private static final String testName = "record-literal-node-test"; - - private static Value execute(String sourceText) { - try (Context context = JSTest.newContextBuilder() - .option(JSContextOptions.ECMASCRIPT_VERSION_NAME, "2022") - .build()) { - return context.eval(Source.newBuilder(JavaScriptLanguage.ID, sourceText, testName).buildLiteral()); - } - } - - @Test - public void testSpread_Polyfill() { - assertTrue(execute("#{...['test']} === #{'0': 'test'}").asBoolean()); + public RecordLiteralNodeTest() { + super("record-literal-node-test"); + addOption(JSContextOptions.ECMASCRIPT_VERSION_NAME, "2022"); } // TODO: re-evaluate, check proposal for changes @@ -40,4 +26,8 @@ public void testSpread_Polyfill() { public void testSpread_Spec() { assertTrue(execute("#{...['test']} === #{'0': 'test', length: 1}").asBoolean()); } + @Test + public void testSpread_Polyfill() { + assertTrue(execute("#{...['test']} === #{'0': 'test'}").asBoolean()); + } } From 10e73e25ae3a5831a1cdba69196aac043df5cfeb Mon Sep 17 00:00:00 2001 From: Christian Aistleitner Date: Sun, 30 May 2021 11:49:34 +0200 Subject: [PATCH 30/31] Add improvements and small fixes. --- .../js/builtins/ConstructorBuiltins.java | 43 +++++++++---------- .../truffle/js/builtins/JSONBuiltins.java | 6 ++- .../js/builtins/RecordFunctionBuiltins.java | 21 +++------ .../js/builtins/TupleFunctionBuiltins.java | 22 ++++++++-- .../js/nodes/cast/JSToStringOrNumberNode.java | 5 ++- .../js/nodes/tuples/TupleLiteralNode.java | 1 - 6 files changed, 52 insertions(+), 46 deletions(-) diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/ConstructorBuiltins.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/ConstructorBuiltins.java index b5748a1670d..89c8ebb9e51 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/ConstructorBuiltins.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/ConstructorBuiltins.java @@ -45,7 +45,7 @@ import java.util.Map; import java.util.Objects; import java.util.StringJoiner; -import java.util.stream.Collectors; +import java.util.TreeMap; import com.oracle.truffle.api.Assumption; import com.oracle.truffle.api.CompilerAsserts; @@ -2765,13 +2765,17 @@ protected DynamicObject getIntrinsicDefaultProto(JSRealm realm) { public abstract static class CallRecordNode extends JSBuiltinNode { + private final BranchProfile errorProfile = BranchProfile.create(); + @Child JSToObjectNode toObjectNode; - @Child EnumerableOwnPropertyNamesNode enumerableOwnPropertyNamesNode; @Child ReadElementNode readElementNode; + @Child EnumerableOwnPropertyNamesNode enumerableOwnPropertyNamesNode; + @Child IsObjectNode isObjectNode = IsObjectNode.create(); public CallRecordNode(JSContext context, JSBuiltin builtin) { super(context, builtin); toObjectNode = JSToObjectNode.createToObject(context); + readElementNode = ReadElementNode.create(context); enumerableOwnPropertyNamesNode = EnumerableOwnPropertyNamesNode.createKeysValues(context); } @@ -2779,25 +2783,17 @@ public CallRecordNode(JSContext context, JSBuiltin builtin) { protected Record call(Object arg) { Object obj = toObjectNode.execute(arg); UnmodifiableArrayList props = enumerableOwnPropertyNamesNode.execute((DynamicObject) obj); - Map fields = props.stream().collect(Collectors.toMap( - it -> (String) getV(it, 0), - it -> { - Object value = getV(it, 1); - if (JSRuntime.isObject(value)) { - throw Errors.createTypeError("Records cannot contain non-primitive values"); - } - return value; - } - )); - return Record.create(fields); - } - - private Object getV(Object object, long index) { - if (readElementNode == null) { - CompilerDirectives.transferToInterpreterAndInvalidate(); - readElementNode = insert(ReadElementNode.create(getContext())); + Map fields = new TreeMap<>(); + for (Object prop : props) { + String name = (String) readElementNode.executeWithTargetAndIndex(prop, 0); + Object value = readElementNode.executeWithTargetAndIndex(prop, 1); + if (isObjectNode.executeBoolean(value)) { + errorProfile.enter(); + throw Errors.createTypeError("Records cannot contain non-primitive values"); + } + fields.put(name, value); } - return readElementNode.executeWithTargetAndIndex(object, index); + return Record.create(fields); } } @@ -2815,6 +2811,8 @@ protected static final DynamicObject construct() { public abstract static class CallTupleNode extends JSBuiltinNode { + private final BranchProfile errorProfile = BranchProfile.create(); + public CallTupleNode(JSContext context, JSBuiltin builtin) { super(context, builtin); } @@ -2825,9 +2823,10 @@ protected Tuple callEmpty(@SuppressWarnings("unused") Object[] items) { } @Specialization(guards = {"items.length != 0"}) - protected Tuple call(Object[] items) { + protected Tuple call(Object[] items, @Cached("create()") IsJSObjectNode isObjectNode) { for (Object item : items) { - if (JSRuntime.isObject(item)) { + if (isObjectNode.executeBoolean(item)) { + errorProfile.enter(); throw Errors.createTypeError("Tuples cannot contain non-primitive values"); } } diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/JSONBuiltins.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/JSONBuiltins.java index 0d530088eda..8802fd39b7a 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/JSONBuiltins.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/JSONBuiltins.java @@ -367,13 +367,15 @@ protected Number toNumber(Object target) { public abstract static class JSONParseImmutableNode extends JSONOperation { + @Child private JSONBuildImmutablePropertyNode buildImmutableProperty; + public JSONParseImmutableNode(JSContext context, JSBuiltin builtin) { super(context, builtin); + buildImmutableProperty = JSONBuildImmutablePropertyNode.create(context); } @Specialization(limit = "1") - protected Object parseUnfiltered(String text, Object reviver, - @Cached("create(getContext())") JSONBuildImmutablePropertyNode buildImmutableProperty) { + protected Object parseUnfiltered(Object text, Object reviver) { Object unfiltered = parseIntl(toString(text)); return buildImmutableProperty.execute(unfiltered, "", reviver); } diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/RecordFunctionBuiltins.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/RecordFunctionBuiltins.java index 64b42bb6904..2f70459ee6c 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/RecordFunctionBuiltins.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/RecordFunctionBuiltins.java @@ -52,14 +52,12 @@ import com.oracle.truffle.js.nodes.access.IteratorValueNode; import com.oracle.truffle.js.nodes.access.ReadElementNode; import com.oracle.truffle.js.nodes.access.RequireObjectCoercibleNode; -import com.oracle.truffle.js.nodes.cast.JSToPropertyKeyNode; +import com.oracle.truffle.js.nodes.cast.JSToStringNode; import com.oracle.truffle.js.nodes.function.JSBuiltin; import com.oracle.truffle.js.nodes.function.JSBuiltinNode; import com.oracle.truffle.js.runtime.Errors; import com.oracle.truffle.js.runtime.JSContext; -import com.oracle.truffle.js.runtime.JSRuntime; import com.oracle.truffle.js.runtime.Record; -import com.oracle.truffle.js.runtime.Symbol; import com.oracle.truffle.js.runtime.builtins.BuiltinEnum; import com.oracle.truffle.js.runtime.builtins.JSRecord; import com.oracle.truffle.js.runtime.objects.IteratorRecord; @@ -111,11 +109,12 @@ public abstract static class RecordFromEntriesNode extends JSBuiltinNode { private final BranchProfile errorBranch = BranchProfile.create(); @Child private RequireObjectCoercibleNode requireObjectCoercibleNode = RequireObjectCoercibleNode.create(); + @Child private IsObjectNode isObjectNode = IsObjectNode.create(); + @Child private JSToStringNode toStringNode = JSToStringNode.create(); @Child private GetIteratorNode getIteratorNode; @Child private IteratorStepNode iteratorStepNode; @Child private IteratorValueNode iteratorValueNode; @Child private ReadElementNode readElementNode; - @Child private IsObjectNode isObjectNode; @Child private IteratorCloseNode iteratorCloseNode; public RecordFromEntriesNode(JSContext context, JSBuiltin builtin) { @@ -127,11 +126,11 @@ protected Record doObject(Object iterable) { requireObjectCoercibleNode.executeVoid(iterable); Map fields = new TreeMap<>(); BiConsumer adder = (key, value) -> { - if (JSRuntime.isObject(value)) { + if (isObjectNode.executeBoolean(value)) { errorBranch.enter(); throw Errors.createTypeError("Records cannot contain objects", this); } - fields.put(JSRuntime.toString(key),value); + fields.put(toStringNode.executeString(key),value); }; addEntriesFromIterable(iterable, adder); return Record.create(fields); @@ -146,7 +145,7 @@ private void addEntriesFromIterable(Object iterable, BiConsumer break; } Object nextItem = iteratorValue((DynamicObject) next); - if (!isObject(nextItem)) { + if (!isObjectNode.executeBoolean(nextItem)) { errorBranch.enter(); throw Errors.createTypeErrorIteratorResultNotObject(nextItem, this); } @@ -200,14 +199,6 @@ private Object get(Object obj, long idx) { } return readElementNode.executeWithTargetAndIndex(obj, idx); } - - private boolean isObject(Object obj) { - if (isObjectNode == null) { - CompilerDirectives.transferToInterpreterAndInvalidate(); - isObjectNode = insert(IsObjectNode.create()); - } - return isObjectNode.executeBoolean(obj); - } } public abstract static class RecordIsRecordNode extends JSBuiltinNode { diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TupleFunctionBuiltins.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TupleFunctionBuiltins.java index 5926067fb8f..ec86d524213 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TupleFunctionBuiltins.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TupleFunctionBuiltins.java @@ -47,6 +47,7 @@ import com.oracle.truffle.api.profiles.ConditionProfile; import com.oracle.truffle.js.nodes.access.GetIteratorNode; import com.oracle.truffle.js.nodes.access.GetMethodNode; +import com.oracle.truffle.js.nodes.access.IsObjectNode; import com.oracle.truffle.js.nodes.access.IteratorCloseNode; import com.oracle.truffle.js.nodes.access.IteratorStepNode; import com.oracle.truffle.js.nodes.access.IteratorValueNode; @@ -61,7 +62,6 @@ import com.oracle.truffle.js.runtime.Errors; import com.oracle.truffle.js.runtime.JSArguments; import com.oracle.truffle.js.runtime.JSContext; -import com.oracle.truffle.js.runtime.JSRuntime; import com.oracle.truffle.js.runtime.Symbol; import com.oracle.truffle.js.runtime.Tuple; import com.oracle.truffle.js.runtime.builtins.BuiltinEnum; @@ -139,6 +139,7 @@ public abstract static class TupleFromNode extends JSBuiltinNode { @Child private IteratorStepNode iteratorStepNode; @Child private IteratorValueNode iteratorValueNode; @Child private IteratorCloseNode iteratorCloseNode; + @Child private IsObjectNode isObjectNode; @Child private JSToObjectNode toObjectNode; @Child private JSGetLengthNode getLengthNode; @Child private ReadElementNode readElementNode; @@ -180,7 +181,7 @@ protected Tuple from(Object items, Object mapFn, Object thisArg) { if (mapping) { value = call(mapFn, thisArg, value, k); } - if (JSRuntime.isObject(value)) { + if (isObject(value)) { isObjectErrorBranch.enter(); throw Errors.createTypeError("Tuples cannot contain non-primitive values"); } @@ -202,7 +203,7 @@ protected Tuple from(Object items, Object mapFn, Object thisArg) { if (mapping) { value = call(mapFn, thisArg, value, k); } - if (JSRuntime.isObject(value)) { + if (isObject(value)) { isObjectErrorBranch.enter(); throw Errors.createTypeError("Tuples cannot contain non-primitive values"); } @@ -260,6 +261,14 @@ protected void iteratorCloseAbrupt(DynamicObject iterator) { iteratorCloseNode.executeAbrupt(iterator); } + private boolean isObject(Object obj) { + if (isObjectNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + isObjectNode = insert(IsObjectNode.create()); + } + return isObjectNode.executeBoolean(obj); + } + private Object toObject(Object obj) { if (toObjectNode == null) { CompilerDirectives.transferToInterpreterAndInvalidate(); @@ -295,6 +304,10 @@ private Object call(Object function, Object target, Object... arguments) { public abstract static class TupleOfNode extends JSBuiltinNode { + private final BranchProfile errorProfile = BranchProfile.create(); + + @Child private IsObjectNode isObjectNode = IsObjectNode.create(); + public TupleOfNode(JSContext context, JSBuiltin builtin) { super(context, builtin); } @@ -302,7 +315,8 @@ public TupleOfNode(JSContext context, JSBuiltin builtin) { @Specialization protected Tuple of(Object[] items) { for (Object item : items) { - if (JSRuntime.isObject(item)) { + if (isObjectNode.executeBoolean(item)) { + errorProfile.enter(); throw Errors.createTypeError("Tuples cannot contain non-primitive values"); } } diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToStringOrNumberNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToStringOrNumberNode.java index 320f9cbda29..65640ce35fc 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToStringOrNumberNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToStringOrNumberNode.java @@ -41,6 +41,7 @@ package com.oracle.truffle.js.nodes.cast; import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.Cached.Shared; import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.object.DynamicObject; import com.oracle.truffle.js.nodes.JavaScriptBaseNode; @@ -124,12 +125,12 @@ protected static BigInt doBigInt(BigInt value) { } @Specialization - protected String doRecord(Record value, @Cached("create()") JSToStringNode toStringNode) { + protected String doRecord(Record value, @Cached("create()") @Shared("toString") JSToStringNode toStringNode) { return toStringNode.executeString(value); } @Specialization - protected String doTuple(Tuple value, @Cached("create()") JSToStringNode toStringNode) { + protected String doTuple(Tuple value, @Cached("create()") @Shared("toString") JSToStringNode toStringNode) { return toStringNode.executeString(value); } } diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/TupleLiteralNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/TupleLiteralNode.java index ecb8b8802f0..bd67682ce2f 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/TupleLiteralNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/TupleLiteralNode.java @@ -42,7 +42,6 @@ import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.instrumentation.Tag; -import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.profiles.BranchProfile; import com.oracle.truffle.js.nodes.JavaScriptNode; import com.oracle.truffle.js.nodes.access.GetIteratorNode; From f1e85242e94557535cfdccb6582be0b51cc81da2 Mon Sep 17 00:00:00 2001 From: Christian Aistleitner Date: Sun, 30 May 2021 12:22:06 +0200 Subject: [PATCH 31/31] Add predefined record and tuple errors. --- .../builtins/RecordFunctionBuiltinsTest.java | 2 +- .../js/builtins/ConstructorBuiltins.java | 4 ++-- .../js/builtins/RecordFunctionBuiltins.java | 2 +- .../js/builtins/TupleFunctionBuiltins.java | 6 +++--- .../js/builtins/TuplePrototypeBuiltins.java | 18 +++++++++--------- .../js/nodes/tuples/TupleLiteralNode.java | 2 +- .../com/oracle/truffle/js/runtime/Errors.java | 9 +++++++++ 7 files changed, 26 insertions(+), 17 deletions(-) diff --git a/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/RecordFunctionBuiltinsTest.java b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/RecordFunctionBuiltinsTest.java index 57f79e83d6b..cb0cfada0ff 100644 --- a/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/RecordFunctionBuiltinsTest.java +++ b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/RecordFunctionBuiltinsTest.java @@ -67,7 +67,7 @@ public void testFromEntries() { assertTrue(execute("Record.fromEntries(Object.entries({a: 'foo'})) === #{a: 'foo'}").asBoolean()); assertTrue(execute("Record.fromEntries([['a', 'foo']]) === #{a: 'foo'}").asBoolean()); expectError("Record.fromEntries()", "undefined or null"); - expectError("Record.fromEntries(Object.entries({data: [1, 2, 3]}))", "cannot contain objects"); + expectError("Record.fromEntries(Object.entries({data: [1, 2, 3]}))", "non-primitive values"); expectError("Record.fromEntries([0])", "not an object"); } } diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/ConstructorBuiltins.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/ConstructorBuiltins.java index 89c8ebb9e51..6d433ced4ad 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/ConstructorBuiltins.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/ConstructorBuiltins.java @@ -2789,7 +2789,7 @@ protected Record call(Object arg) { Object value = readElementNode.executeWithTargetAndIndex(prop, 1); if (isObjectNode.executeBoolean(value)) { errorProfile.enter(); - throw Errors.createTypeError("Records cannot contain non-primitive values"); + throw Errors.createTypeErrorRecordsCannotContainObjects(this); } fields.put(name, value); } @@ -2827,7 +2827,7 @@ protected Tuple call(Object[] items, @Cached("create()") IsJSObjectNode isObject for (Object item : items) { if (isObjectNode.executeBoolean(item)) { errorProfile.enter(); - throw Errors.createTypeError("Tuples cannot contain non-primitive values"); + throw Errors.createTypeErrorTuplesCannotContainObjects(this); } } return Tuple.create(items); diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/RecordFunctionBuiltins.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/RecordFunctionBuiltins.java index 2f70459ee6c..de652d69497 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/RecordFunctionBuiltins.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/RecordFunctionBuiltins.java @@ -128,7 +128,7 @@ protected Record doObject(Object iterable) { BiConsumer adder = (key, value) -> { if (isObjectNode.executeBoolean(value)) { errorBranch.enter(); - throw Errors.createTypeError("Records cannot contain objects", this); + throw Errors.createTypeErrorRecordsCannotContainObjects(this); } fields.put(toStringNode.executeString(key),value); }; diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TupleFunctionBuiltins.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TupleFunctionBuiltins.java index ec86d524213..1b263e2bfbd 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TupleFunctionBuiltins.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TupleFunctionBuiltins.java @@ -183,7 +183,7 @@ protected Tuple from(Object items, Object mapFn, Object thisArg) { } if (isObject(value)) { isObjectErrorBranch.enter(); - throw Errors.createTypeError("Tuples cannot contain non-primitive values"); + throw Errors.createTypeErrorTuplesCannotContainObjects(this); } list.add(value, growProfile); k++; @@ -205,7 +205,7 @@ protected Tuple from(Object items, Object mapFn, Object thisArg) { } if (isObject(value)) { isObjectErrorBranch.enter(); - throw Errors.createTypeError("Tuples cannot contain non-primitive values"); + throw Errors.createTypeErrorTuplesCannotContainObjects(this); } list.add(value, growProfile); k++; @@ -317,7 +317,7 @@ protected Tuple of(Object[] items) { for (Object item : items) { if (isObjectNode.executeBoolean(item)) { errorProfile.enter(); - throw Errors.createTypeError("Tuples cannot contain non-primitive values"); + throw Errors.createTypeErrorTuplesCannotContainObjects(this); } } return Tuple.create(items); diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TuplePrototypeBuiltins.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TuplePrototypeBuiltins.java index 6d5d58570a2..e6c46b5634e 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TuplePrototypeBuiltins.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TuplePrototypeBuiltins.java @@ -445,7 +445,7 @@ protected Tuple pushed(Object thisObj, Object[] args) { Object value = args[i]; if (isObject(value)) { errorProfile.enter(); - throw Errors.createTypeError("Tuples cannot contain non-primitive values"); + throw Errors.createTypeErrorTuplesCannotContainObjects(this); } values[tuple.getArraySizeInt() + i] = value; } @@ -679,7 +679,7 @@ protected Tuple spliced(Object thisObj, Object[] args) { for (int i = 2; i < args.length; i++) { if (isObject(args[i])) { errorProfile.enter(); - throw Errors.createTypeError("Tuples cannot contain non-primitive values"); + throw Errors.createTypeErrorTuplesCannotContainObjects(this); } values[k] = args[i]; k++; @@ -739,7 +739,7 @@ private void concatElement(Object e, SimpleArrayList list) { Object subElement = get(e, k); if (isObject(subElement)) { errorProfile.enter(); - throw Errors.createTypeError("Tuples cannot contain non-primitive values"); + throw Errors.createTypeErrorTuplesCannotContainObjects(this); } list.add(subElement, growProfile); } @@ -747,7 +747,7 @@ private void concatElement(Object e, SimpleArrayList list) { } else { if (isObject(e)) { errorProfile.enter(); - throw Errors.createTypeError("Tuples cannot contain non-primitive values"); + throw Errors.createTypeErrorTuplesCannotContainObjects(this); } list.add(e, growProfile); } @@ -850,7 +850,7 @@ protected void flattenIntoTuple(SimpleArrayList target, BranchProfile gr element = call(JSArguments.create(thisArg, mapperFunction, element, i, source)); if (isObject(element)) { errorProfile.enter(); - throw Errors.createTypeError("Tuples cannot contain non-primitive values"); + throw Errors.createTypeErrorTuplesCannotContainObjects(this); } } if (depth > 0 && element instanceof Tuple) { @@ -932,7 +932,7 @@ protected Tuple map(Object thisObj, Object mapperFunction, Object thisArg) { value = call(JSArguments.create(thisArg, mapperFunction, value, k, tuple)); if (isObject(value)) { errorProfile.enter(); - throw Errors.createTypeError("Tuples cannot contain non-primitive values"); + throw Errors.createTypeErrorTuplesCannotContainObjects(this); } list.add(value, growProfile); } @@ -963,7 +963,7 @@ protected Tuple unshifted(Object thisObj, Object[] args) { for (Object arg : args) { if (isObject(arg)) { errorProfile.enter(); - throw Errors.createTypeError("Tuples cannot contain non-primitive values"); + throw Errors.createTypeErrorTuplesCannotContainObjects(this); } list.add(arg, growProfile); } @@ -971,7 +971,7 @@ protected Tuple unshifted(Object thisObj, Object[] args) { Object value = tuple.getElement(k); if (isObject(value)) { errorProfile.enter(); - throw Errors.createTypeError("Tuples cannot contain non-primitive values"); + throw Errors.createTypeErrorTuplesCannotContainObjects(this); } list.add(value, growProfile); } @@ -999,7 +999,7 @@ protected Tuple with(Object thisObj, Object index, Object value) { } if (isObject(value)) { errorProfile.enter(); - throw Errors.createTypeError("Tuples cannot contain non-primitive values"); + throw Errors.createTypeErrorTuplesCannotContainObjects(this); } list[(int) i] = value; return Tuple.create(list); diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/TupleLiteralNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/TupleLiteralNode.java index bd67682ce2f..28c6a3142ff 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/TupleLiteralNode.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/TupleLiteralNode.java @@ -120,7 +120,7 @@ static void addValueToTupleSequenceList(SimpleArrayList sequence, Object private static Object requireNonObject(Object value) { if (JSRuntime.isObject(value)) { - throw Errors.createTypeError("Tuples cannot contain non-primitive values"); + throw Errors.createTypeErrorTuplesCannotContainObjects(null); } return value; } diff --git a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Errors.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Errors.java index 7be56a6f980..0d3d0b1a778 100644 --- a/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Errors.java +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Errors.java @@ -870,4 +870,13 @@ public static JSException createRuntimeError(Throwable cause, Node originatingNo return JSException.create(JSErrorType.RuntimeError, cause.getMessage(), cause, originatingNode); } + @TruffleBoundary + public static JSException createTypeErrorRecordsCannotContainObjects(Node originatingNode) { + return JSException.create(JSErrorType.TypeError, "Records cannot contain non-primitive values", originatingNode); + } + + @TruffleBoundary + public static JSException createTypeErrorTuplesCannotContainObjects(Node originatingNode) { + return JSException.create(JSErrorType.TypeError, "Tuples cannot contain non-primitive values", originatingNode); + } }