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 73cd23c7e8e..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 @@ -88,6 +88,8 @@ 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; import static com.oracle.js.parser.TokenType.SUPER; @@ -156,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.Scope; import com.oracle.js.parser.ir.Statement; @@ -3664,6 +3668,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: @@ -3799,7 +3807,8 @@ private LiteralNode arrayLiteral(boolean yield, boolean await) { *
      * ObjectLiteral :
      *      { }
-     *      { PropertyNameAndValueList } { PropertyNameAndValueList , }
+     *      { PropertyNameAndValueList }
+     *      { PropertyNameAndValueList , }
      *
      * PropertyNameAndValueList :
      *      PropertyAssignment
@@ -4286,6 +4295,194 @@ 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]
+     * 
+ * + * @return Record 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]
+     *      ... AssignmentExpression[+In, ?Yield, ?Await]
+     * 
+ * + * @return Record property node. + */ + 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); + } + + /** + * 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 Literal 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. * @@ -5817,10 +6014,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/TokenType.java b/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/TokenType.java index bb8185bb32c..8bd1425f5f8 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 @@ -230,7 +230,16 @@ public enum TokenType { SPREAD_OBJECT (IR, null), YIELD_STAR (IR, null), ASSIGN_INIT (IR, null), - NAMEDEVALUATION(IR, null); + NAMEDEVALUATION(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..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 @@ -509,4 +509,61 @@ 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. + */ + @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)); + } } 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 20a3a4a051e..70f8897b4c9 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.SwitchNode; import com.oracle.js.parser.ir.TemplateLiteralNode; @@ -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 be19b66eed0..b6fe4f24a63 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.SwitchNode; import com.oracle.js.parser.ir.TemplateLiteralNode; @@ -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 a2000061a67..328b3f62ae1 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.Scope; import com.oracle.js.parser.ir.Statement; import com.oracle.js.parser.ir.Symbol; @@ -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; @@ -1424,6 +1427,8 @@ public JavaScriptNode enterBlockStatement(BlockStatement blockStatement) { public JavaScriptNode enterLiteralNode(LiteralNode literalNode) { if (literalNode instanceof LiteralNode.ArrayLiteralNode) { return tagExpression(createArrayLiteral(((LiteralNode.ArrayLiteralNode) literalNode).getElementExpressions()), literalNode); + } else if (literalNode instanceof LiteralNode.TupleLiteralNode) { + return tagExpression(enterLiteralTupleNode((LiteralNode.TupleLiteralNode) literalNode), literalNode); } else { return tagExpression(enterLiteralDefaultNode(literalNode), literalNode); } @@ -1458,6 +1463,46 @@ private JavaScriptNode createArrayLiteral(List elementExpr 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 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(); @@ -1963,6 +2008,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.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 4a8255225cf..773dc7a52fe 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 @@ -866,6 +866,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.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..ef495c5cfe1 --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js.test/js/record-tuple/record.js @@ -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. + * + * 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__: 1 }"); // 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]] + */ +a = #{ age: 22 }; +a = Object(a); +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")]); + +/* + * 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 + +/* + * Test 11: + * typeof + */ +assertSame("record", typeof #{id: 1}); +assertSame("record", typeof #{}); 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..464958c43f4 --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js.test/js/record-tuple/tuple.js @@ -0,0 +1,400 @@ +/* + * 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'); + +let a, b, c, d, e, f; + +/* + * Test 1: + * valid tuple literals - simple parser syntax test + */ +a = #[]; +b = #[1, 2]; +c = #[1, 2, #[3]]; +d = #[1, 2, #{ a: 3 }]; +e = #[...b, 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 + */ +a = 2; +assertSame("tuple", typeof #[a]); +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(), #[]); +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]); +assertTrue(Object(#[1, 2]) !== Object(#[1, 2])); + +assertTrue(#[-0] === #[+0]); +assertTrue(#[NaN] === #[NaN]); + +assertTrue(#[-0] == #[+0]); +assertTrue(#[NaN] == #[NaN]); +assertTrue(#[1] != #["1"]); + +assertTrue(!Object.is(#[-0], #[+0])); +assertTrue(Object.is(#[NaN], #[NaN])); + +// Map keys are compared with the SameValueZero algorithm +assertTrue(new Map().set(#[1], true).get(#[1])); +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))); + +/* + * 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 + +/* + * 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]); +} + +/* + * 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.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 new file mode 100644 index 00000000000..77acc48c6ee --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/JSONBuiltinsTest.java @@ -0,0 +1,72 @@ +/* + * 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.runtime.JSContextOptions; +import com.oracle.truffle.js.test.JSSimpleTest; +import org.junit.Test; + +import static org.junit.Assert.assertTrue; + +public class JSONBuiltinsTest extends JSSimpleTest { + + public JSONBuiltinsTest() { + super("json-builtins-test"); + addOption(JSContextOptions.ECMASCRIPT_VERSION_NAME, "2022"); + } + + @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.test/src/com/oracle/truffle/js/test/builtins/RecordAndTupleConstructorTest.java b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/RecordAndTupleConstructorTest.java new file mode 100644 index 00000000000..566b9e89c71 --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/RecordAndTupleConstructorTest.java @@ -0,0 +1,72 @@ +/* + * 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.runtime.JSContextOptions; +import com.oracle.truffle.js.test.JSSimpleTest; +import org.junit.Test; + +import static org.junit.Assert.assertTrue; + +public class RecordAndTupleConstructorTest extends JSSimpleTest { + + public RecordAndTupleConstructorTest() { + super("record-and-tuple-constructor-test"); + addOption(JSContextOptions.ECMASCRIPT_VERSION_NAME, "2022"); + } + + @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.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..cb0cfada0ff --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/RecordFunctionBuiltinsTest.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.test.builtins; + +import com.oracle.truffle.js.runtime.JSContextOptions; +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 extends JSSimpleTest { + + public RecordFunctionBuiltinsTest() { + super("record-function-builtins-test"); + addOption(JSContextOptions.ECMASCRIPT_VERSION_NAME, "2022"); + } + + @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]}))", "non-primitive values"); + expectError("Record.fromEntries([0])", "not an object"); + } +} 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..23b9a328c29 --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/TupleFunctionBuiltinsTest.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.runtime.JSContextOptions; +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 extends JSSimpleTest { + + public TupleFunctionBuiltinsTest() { + super("tuple-function-builtins-test"); + addOption(JSContextOptions.ECMASCRIPT_VERSION_NAME, "2022"); + } + + @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_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()); + 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"); + } + + // 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()); + 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/TuplePrototypeBuiltinsTest.java b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/TuplePrototypeBuiltinsTest.java new file mode 100644 index 00000000000..f6b89aa9510 --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/builtins/TuplePrototypeBuiltinsTest.java @@ -0,0 +1,362 @@ +/* + * 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.runtime.JSContextOptions; +import com.oracle.truffle.js.test.JSSimpleTest; +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 TuplePrototypeBuiltinsTest extends JSSimpleTest { + + public TuplePrototypeBuiltinsTest() { + super("tuple-prototype-builtins-test"); + addOption(JSContextOptions.ECMASCRIPT_VERSION_NAME, "2022"); + addOption(JSContextOptions.INTL_402_NAME, "true"); + } + + @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()); + 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()); + 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"); + } + + @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 // TODO: re-evaluate, check proposal for changes + @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 // TODO: re-evaluate, check proposal for changes + @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 // TODO: re-evaluate, check proposal for changes + @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 // TODO: re-evaluate, check proposal for changes + @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 // TODO: re-evaluate, check proposal for changes + @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 // TODO: re-evaluate, check proposal for changes + @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 // TODO: re-evaluate, check proposal for changes + @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 // TODO: re-evaluate, check proposal for changes + @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 // TODO: re-evaluate, check proposal for changes + @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 // TODO: re-evaluate, check proposal for changes + @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 // TODO: re-evaluate, check proposal for changes + @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.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..e8d23f2e3f2 --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/comparison/RecordEqualityTest.java @@ -0,0 +1,116 @@ +/* + * 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.runtime.JSContextOptions; +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 extends JSSimpleTest { + + public RecordEqualityTest() { + super("record-equality-test"); + addOption(JSContextOptions.ECMASCRIPT_VERSION_NAME, "2022"); + } + + @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..d8a9208c824 --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/comparison/TupleEqualityTest.java @@ -0,0 +1,116 @@ +/* + * 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.runtime.JSContextOptions; +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 extends JSSimpleTest { + + public TupleEqualityTest() { + super("tuple-equality-test"); + addOption(JSContextOptions.ECMASCRIPT_VERSION_NAME, "2022"); + } + + @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/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..f62f0296df7 --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/nodes/record/RecordLiteralNodeTest.java @@ -0,0 +1,33 @@ +package com.oracle.truffle.js.test.nodes.record; + +import com.oracle.truffle.js.runtime.JSContextOptions; +import com.oracle.truffle.js.test.JSSimpleTest; +import org.junit.Ignore; +import org.junit.Test; + +import static org.junit.Assert.assertTrue; + +public class RecordLiteralNodeTest extends JSSimpleTest { + + public RecordLiteralNodeTest() { + super("record-literal-node-test"); + addOption(JSContextOptions.ECMASCRIPT_VERSION_NAME, "2022"); + } + + // 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()); + } + @Test + public void testSpread_Polyfill() { + assertTrue(execute("#{...['test']} === #{'0': 'test'}").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.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 new file mode 100644 index 00000000000..7df2f5add47 --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/runtime/builtins/JSTupleTest.java @@ -0,0 +1,228 @@ +/* + * 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.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 com.oracle.truffle.js.test.TestHelper; +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 = 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 = 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()); + assertEquals(desc.getValue(), "Tuple"); + + 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/builtins/ConstructorBuiltins.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/ConstructorBuiltins.java index cc0f197e0c1..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 @@ -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.TreeMap; 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,12 +107,14 @@ 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.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; @@ -126,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; @@ -185,8 +192,10 @@ 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; 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; @@ -239,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. @@ -325,7 +335,11 @@ public enum Constructor implements BuiltinEnum { // non-standard (Nashorn) extensions JSAdapter(1), - JavaImporter(1); + JavaImporter(1), + + // Record & Tuple Proposal + Record(1), + Tuple(1); private final int length; @@ -350,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; @@ -624,6 +640,12 @@ 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: + 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)); } return null; @@ -2741,4 +2763,86 @@ protected DynamicObject getIntrinsicDefaultProto(JSRealm realm) { } + public abstract static class CallRecordNode extends JSBuiltinNode { + + private final BranchProfile errorProfile = BranchProfile.create(); + + @Child JSToObjectNode toObjectNode; + @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); + } + + @Specialization + protected Record call(Object arg) { + Object obj = toObjectNode.execute(arg); + UnmodifiableArrayList props = enumerableOwnPropertyNamesNode.execute((DynamicObject) obj); + 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.createTypeErrorRecordsCannotContainObjects(this); + } + fields.put(name, value); + } + return Record.create(fields); + } + } + + 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 { + + private final BranchProfile errorProfile = BranchProfile.create(); + + public CallTupleNode(JSContext context, JSBuiltin builtin) { + super(context, builtin); + } + + @Specialization(guards = {"items.length == 0"}) + protected Tuple callEmpty(@SuppressWarnings("unused") Object[] items) { + return Tuple.create(); + } + + @Specialization(guards = {"items.length != 0"}) + protected Tuple call(Object[] items, @Cached("create()") IsJSObjectNode isObjectNode) { + for (Object item : items) { + if (isObjectNode.executeBoolean(item)) { + errorProfile.enter(); + throw Errors.createTypeErrorTuplesCannotContainObjects(this); + } + } + return Tuple.create(items); + } + } + + 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/JSONBuiltins.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/JSONBuiltins.java index 1e9611a2473..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 @@ -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,25 @@ protected Number toNumber(Object target) { return toNumberNode.executeNumber(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(Object text, Object reviver) { + 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/ObjectPrototypeBuiltins.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/ObjectPrototypeBuiltins.java index d6158f0c941..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 @@ -80,8 +80,10 @@ 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; 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 +252,16 @@ 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); + } + @Specialization(guards = "!isTruffleObject(thisObj)") protected DynamicObject valueOfOther(Object thisObj) { return toJSObject(thisObj); @@ -365,6 +377,16 @@ 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)); + } + @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/RecordFunctionBuiltins.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/RecordFunctionBuiltins.java new file mode 100644 index 00000000000..de652d69497 --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/RecordFunctionBuiltins.java @@ -0,0 +1,225 @@ +/* + * 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.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.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.Record; +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. + */ +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 { + fromEntries(1), + isRecord(1); + + 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) { + 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 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 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 (isObjectNode.executeBoolean(value)) { + errorBranch.enter(); + throw Errors.createTypeErrorRecordsCannotContainObjects(this); + } + fields.put(toStringNode.executeString(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 (!isObjectNode.executeBoolean(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); + } + } + + 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/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..1b263e2bfbd --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TupleFunctionBuiltins.java @@ -0,0 +1,326 @@ +/* + * 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.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.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.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. + */ +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), + from(1), + 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)); + 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; + } + + public abstract static class TupleIsTupleNode extends JSBuiltinNode { + + @Child private JSIsTupleNode isTupleNode = JSIsTupleNode.create(); + + public TupleIsTupleNode(JSContext context, JSBuiltin builtin) { + super(context, builtin); + } + + @Specialization + 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 IsObjectNode isObjectNode; + @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)) { + // 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. + 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 (isObject(value)) { + isObjectErrorBranch.enter(); + throw Errors.createTypeErrorTuplesCannotContainObjects(this); + } + 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 (isObject(value)) { + isObjectErrorBranch.enter(); + throw Errors.createTypeErrorTuplesCannotContainObjects(this); + } + 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 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(); + 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 { + + private final BranchProfile errorProfile = BranchProfile.create(); + + @Child private IsObjectNode isObjectNode = IsObjectNode.create(); + + public TupleOfNode(JSContext context, JSBuiltin builtin) { + super(context, builtin); + } + + @Specialization + protected Tuple of(Object[] items) { + for (Object item : items) { + if (isObjectNode.executeBoolean(item)) { + errorProfile.enter(); + 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 new file mode 100644 index 00000000000..e6c46b5634e --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/builtins/TuplePrototypeBuiltins.java @@ -0,0 +1,1008 @@ +/* + * 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.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.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.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; +import com.oracle.truffle.js.nodes.function.JSBuiltinNode; +import com.oracle.truffle.js.nodes.function.JSFunctionCallNode; +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; +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.JSTuple; +import com.oracle.truffle.js.runtime.objects.JSObject; +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.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.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; +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.JSTupleUnshiftedNodeGen; +import static com.oracle.truffle.js.builtins.TuplePrototypeBuiltinsFactory.JSTupleValueOfNodeGen; +import static com.oracle.truffle.js.builtins.TuplePrototypeBuiltinsFactory.JSTupleWithNodeGen; + +/** + * 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 { + length(0), + 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), + entries(0), + every(1), + filter(1), + find(1), + findIndex(1), + flat(0), + flatMap(1), + forEach(1), + keys(1), + map(1), + reduce(1), + reduceRight(1), + some(1), + unshifted(1), + toLocaleString(0), + toString(0), + values(0), + with(2); + + private final int len; + + TuplePrototype(int length) { + this.len = length; + } + + @Override + public int getLength() { + 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: + 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 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; + } + + 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 BasicTupleOperation extends JSBuiltinNode { + + protected final BranchProfile errorProfile = BranchProfile.create(); + + @Child private IsObjectNode isObjectNode; + + 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 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; + } + + protected boolean isObject(Object obj) { + if (isObjectNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + isObjectNode = insert(IsObjectNode.create()); + } + return isObjectNode.executeBoolean(obj); + } + } + + public abstract static class JSTupleToStringNode extends BasicTupleOperation { + + private final static String JOIN = "join"; + + @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); + } + + @Specialization + protected Object toString(Object thisObj) { + Tuple tuple = thisTupleValue(thisObj); + 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 JSTupleIteratorNode extends BasicTupleOperation { + + private final int iterationKind; + + @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 DynamicObject doObject(VirtualFrame frame, Object thisObj) { + Tuple tuple = thisTupleValue(thisObj); + Object object = toObject(tuple); + return createArrayIterator(frame, object); + } + + private Object toObject(Object obj) { + if (toObjectNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + toObjectNode = insert(JSToObjectNode.createToObject(getContext())); + } + return toObjectNode.execute(obj); + } + + 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 JSTupleValueOfNode extends BasicTupleOperation { + + public JSTupleValueOfNode(JSContext context, JSBuiltin builtin) { + super(context, builtin); + } + + @Specialization + protected Tuple valueOf(Object thisObj) { + return thisTupleValue(thisObj); + } + } + + public abstract static class JSTuplePoppedNode extends BasicTupleOperation { + + private final ConditionProfile emptyTupleProfile = ConditionProfile.createBinaryProfile(); + + public JSTuplePoppedNode(JSContext context, JSBuiltin builtin) { + super(context, builtin); + } + + @Specialization + protected Tuple popped(Object thisObj) { + Tuple tuple = thisTupleValue(thisObj); + if (emptyTupleProfile.profile(tuple.getArraySize() <= 1)) { + return Tuple.EMPTY_TUPLE; + } + Object[] values = tuple.getElements(); + 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 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 (isObject(value)) { + errorProfile.enter(); + throw Errors.createTypeErrorTuplesCannotContainObjects(this); + } + values[tuple.getArraySizeInt() + i] = value; + } + return Tuple.create(values); + } + } + + public abstract static class JSTupleReversedNode extends BasicTupleOperation { + + public JSTupleReversedNode(JSContext context, JSBuiltin builtin) { + super(context, builtin); + } + + @Specialization + 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); + } + } + + 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 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())); + } + } + + public abstract static class JSTupleSliceNode extends BasicTupleOperation { + + private final ConditionProfile emptyTupleProfile = ConditionProfile.createBinaryProfile(); + + @Child private JSToIntegerAsIntNode toIntegerAsIntNode; + + public JSTupleSliceNode(JSContext context, JSBuiltin builtin) { + super(context, builtin); + } + + @Specialization + protected Tuple slice(Object thisObj, Object begin, Object end) { + Tuple tuple = thisTupleValue(thisObj); + int size = tuple.getArraySizeInt(); + + 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 (emptyTupleProfile.profile(startPos >= endPos)) { + return Tuple.EMPTY_TUPLE; + } + 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); + } + } + + 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 = thisTupleValue(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))) { + errorProfile.enter(); + 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); + } + } + + // taken from ArrayPrototypeBuiltins.JSArraySortNode.SortComparator and slightly adjusted + 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 { + + @Child JSToIntegerAsLongNode toIntegerAsLongNode; + + public JSTupleSplicedNode(JSContext context, JSBuiltin builtin) { + super(context, builtin); + } + + @Specialization + protected Tuple spliced(Object thisObj, Object[] args) { + Object start = JSRuntime.getArgOrUndefined(args, 0); + Object deleteCount = JSRuntime.getArgOrUndefined(args, 1); + + Tuple tuple = thisTupleValue(thisObj); + long size = tuple.getArraySize(); + + long startPos = toInteger(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 = toInteger(deleteCount); + delCount = Math.min(Math.max(delCount, 0), size - startPos); + } + + int valuesSize = checkSize(size + insertCount - delCount); + Object[] values = new Object[valuesSize]; + + int k = 0; + while (k < startPos) { + values[k] = tuple.getElement(k); + k++; + } + for (int i = 2; i < args.length; i++) { + if (isObject(args[i])) { + errorProfile.enter(); + throw Errors.createTypeErrorTuplesCannotContainObjects(this); + } + values[k] = args[i]; + k++; + } + 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 { + + 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; + + public JSTupleConcatNode(JSContext context, JSBuiltin builtin) { + super(context, builtin); + } + + @Specialization + protected Tuple concat(Object thisObj, Object[] args) { + Tuple tuple = thisTupleValue(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 e, SimpleArrayList list) { + if (isConcatSpreadable(e)) { + + // 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(e, k)) { + Object subElement = get(e, k); + if (isObject(subElement)) { + errorProfile.enter(); + throw Errors.createTypeErrorTuplesCannotContainObjects(this); + } + list.add(subElement, growProfile); + } + } + } else { + if (isObject(e)) { + errorProfile.enter(); + throw Errors.createTypeErrorTuplesCannotContainObjects(this); + } + list.add(e, growProfile); + } + } + + private boolean isConcatSpreadable(Object object) { + if (isConcatSpreadableNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + isConcatSpreadableNode = insert(JSIsConcatSpreadableNode.create(getContext())); + } + 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(); + 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())); + } + return readElementNode.executeWithTargetAndIndex(target, index); + } + } + + 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)) { + errorProfile.enter(); + 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 { + + @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 (isObject(element)) { + errorProfile.enter(); + throw Errors.createTypeErrorTuplesCannotContainObjects(this); + } + } + 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)) { + errorProfile.enter(); + 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(); + + @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)) { + 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 (isObject(value)) { + errorProfile.enter(); + throw Errors.createTypeErrorTuplesCannotContainObjects(this); + } + 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(); + + 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 (isObject(arg)) { + errorProfile.enter(); + throw Errors.createTypeErrorTuplesCannotContainObjects(this); + } + list.add(arg, growProfile); + } + for (long k = 0; k < tuple.getArraySize(); k++) { + Object value = tuple.getElement(k); + if (isObject(value)) { + errorProfile.enter(); + throw Errors.createTypeErrorTuplesCannotContainObjects(this); + } + list.add(value, growProfile); + } + return Tuple.create(list.toArray()); + } + } + + public abstract static class JSTupleWithNode extends BasicTupleOperation { + + @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) { + errorProfile.enter(); + throw Errors.createRangeError("Index out of range"); + + } + if (isObject(value)) { + errorProfile.enter(); + 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/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/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(); 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 26ceaab381c..571fed79889 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 @@ -66,11 +66,13 @@ import com.oracle.truffle.js.runtime.builtins.JSOrdinary; import com.oracle.truffle.js.runtime.builtins.JSOverloadedOperatorsObject; 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; 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; @@ -143,6 +145,14 @@ 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); + } + 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 d0499bda653..bf2bffe50a8 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 @@ -207,6 +207,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 +787,30 @@ 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); + } + + 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); } @@ -825,6 +852,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/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; 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/access/ReadElementNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/access/ReadElementNode.java index 3dbe1a15f09..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 @@ -86,6 +86,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; @@ -110,6 +111,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.interop.JSInteropUtil; import com.oracle.truffle.js.runtime.objects.JSDynamicObject; import com.oracle.truffle.js.runtime.objects.JSLazyString; @@ -463,6 +465,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); @@ -1581,6 +1585,35 @@ 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) { + 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); + } + + @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/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/JSEqualNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/binary/JSEqualNode.java index 3274c087944..8176c3d6f90 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,7 +64,9 @@ 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; import com.oracle.truffle.js.runtime.objects.Null; @@ -353,6 +355,18 @@ protected boolean doStringNumber(String a, Object b) { return doStringDouble(a, JSRuntime.doubleValue((Number) b)); } + @Specialization + 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 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..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,7 +68,9 @@ 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; import com.oracle.truffle.js.runtime.objects.Undefined; @@ -82,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; @@ -285,6 +290,61 @@ protected static boolean doSymbol(Symbol a, Symbol b) { return a == b; } + @Specialization + 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)"}) 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/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/cast/JSPrepareThisNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSPrepareThisNode.java index 4c19a3c09b4..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,12 +55,16 @@ 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; import com.oracle.truffle.js.runtime.objects.JSLazyString; /** @@ -140,6 +144,16 @@ 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); + } + @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..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,7 +47,9 @@ 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; public abstract class JSToBigIntNode extends JavaScriptBaseNode { @@ -101,6 +103,16 @@ 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); + } + @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..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,7 +50,9 @@ 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; /** @@ -124,6 +126,16 @@ 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; + } + @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..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,7 +55,9 @@ 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; /** @@ -155,6 +157,16 @@ 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; + } + @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..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,11 +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 { @@ -113,6 +116,16 @@ 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); + } + @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 78bc3aff47f..527d967bdb5 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 @@ -56,8 +56,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; import com.oracle.truffle.js.runtime.builtins.JSOverloadedOperatorsObject; import java.util.Set; @@ -218,6 +220,16 @@ protected String getOverloadedOperatorName() { return "|"; } + @Specialization + 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)", "!isBitwiseOr() || !hasOverloadedOperators(value)"}) protected int doJSObject(DynamicObject value, @Cached("create()") JSToDoubleNode toDoubleNode) { 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..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,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; /** * Basically ECMAScript ToInteger, but correct only for values in the int32 range. Used by built-in @@ -133,6 +135,16 @@ protected final int doBigInt(@SuppressWarnings("unused") BigInt value) { throw Errors.createTypeErrorCannotConvertToNumber("a BigInt value", this); } + @Specialization + 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 protected int doString(String value, @Cached("create()") JSToIntegerAsIntNode nestedToIntegerNode, 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..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,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; /** * Basically ECMAScript ToInteger, but correct only for values in the safe integer range. Used by @@ -114,6 +116,16 @@ protected final long 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 long doString(String value, @Cached("create()") JSToIntegerAsLongNode nestedToIntegerNode, 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 2140721196a..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,13 +52,16 @@ 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; import java.util.Set; /** * This implements ECMA 9.3 ToNumber. * + * @see JSToDoubleNode */ public abstract class JSToNumberNode extends JavaScriptBaseNode { @@ -129,6 +132,16 @@ 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); + } + @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..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,12 +61,16 @@ 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; 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 +177,16 @@ 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); + } + @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/JSToPrimitiveNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToPrimitiveNode.java index c739a4bbf21..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,8 +69,10 @@ 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; 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 +158,16 @@ protected BigInt doBigInt(BigInt value) { return value; } + @Specialization + protected Record doRecord(Record 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..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,12 +52,16 @@ 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; 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; import com.oracle.truffle.js.runtime.objects.Null; import com.oracle.truffle.js.runtime.objects.Undefined; @@ -161,6 +165,16 @@ protected String doSymbol(Symbol value) { } } + @Specialization + protected String doRecord(Record value, @Cached("create()") JSRecordToStringNode recordToStringNode) { + return recordToStringNode.execute(value); + } + + @Specialization + protected String doTuple(Tuple value, @Cached("create()") JSTupleToStringNode tupleToStringNode ) { + return tupleToStringNode.execute(value); + } + @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/cast/JSToStringOrNumberNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToStringOrNumberNode.java index bc6af14bb7d..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,14 +41,17 @@ 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; 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 node is intended to be used only by comparison operators. @@ -120,4 +123,14 @@ protected double doUndefined(@SuppressWarnings("unused") Object value) { protected static BigInt doBigInt(BigInt value) { return value; } + + @Specialization + protected String doRecord(Record value, @Cached("create()") @Shared("toString") JSToStringNode toStringNode) { + return toStringNode.executeString(value); + } + + @Specialization + 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/cast/JSToUInt32Node.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/cast/JSToUInt32Node.java index b87a16393c5..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 @@ -61,8 +61,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; import com.oracle.truffle.js.runtime.builtins.JSOverloadedOperatorsObject; import java.util.Set; @@ -179,6 +181,16 @@ protected int doBigInt(@SuppressWarnings("unused") BigInt value) { throw Errors.createTypeErrorCannotConvertBigIntToNumber(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); + } + protected boolean isUnsignedRightShift() { return unsignedRightShift; } 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 13ddb71764d..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; @@ -74,9 +75,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.JSDynamicObject; import com.oracle.truffle.js.runtime.objects.JSObject; import com.oracle.truffle.js.runtime.objects.JSProperty; @@ -211,6 +216,24 @@ protected final boolean doJSObject(JSDynamicObject 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/instrumentation/JSTags.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/instrumentation/JSTags.java index d0932748b73..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,8 @@ public static final class LiteralTag extends Tag { public enum Type { ObjectLiteral, ArrayLiteral, + RecordLiteral, + TupleLiteral, FunctionLiteral, NumericLiteral, BooleanLiteral, 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/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..51c20762bd5 --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/record/RecordLiteralNode.java @@ -0,0 +1,297 @@ +/* + * 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.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; +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.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; +import java.util.Set; +import java.util.TreeMap; + +public class RecordLiteralNode extends JavaScriptNode { + + protected final JSContext context; + + @Children protected final AbstractRecordLiteralMemberNode[] elements; + + private RecordLiteralNode(JSContext context, AbstractRecordLiteralMemberNode[] elements) { + this.context = context; + this.elements = elements; + } + + @Override + public Record execute(VirtualFrame frame) { + Map entries = new TreeMap<>(); + for (AbstractRecordLiteralMemberNode element : elements) { + element.evaluate(frame, entries, context); + } + assert entries.values().stream().noneMatch(JSRuntime::isObject); + return Record.create(entries); + } + + public static RecordLiteralNode 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 { + + /** + * Runtime Semantics: RecordPropertyDefinitionEvaluation + */ + public abstract void evaluate(VirtualFrame frame, Map entries, JSContext context); + + /** + * Abstract operation AddPropertyIntoRecordEntriesList ( entries, propName, 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"); + } + if (JSRuntime.isObject(value)) { + throw Errors.createTypeError("Record may only contain primitive values"); + } + entries.put((String) key, value); + } + + 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; + } + } + + /** + * RecordPropertyDefinition : + * IdentifierReference + * LiteralPropertyName : AssignmentExpression + */ + 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; + } + + @Override + public void evaluate(VirtualFrame frame, Map entries, JSContext context) { + Object value = valueNode.execute(frame); + addPropertyIntoRecordEntriesList(entries, name, value); + } + + @Override + protected AbstractRecordLiteralMemberNode copyUninitialized(Set> materializedTags) { + return new RecordLiteralMemberNode(name, JavaScriptNode.cloneUninitialized(valueNode, materializedTags)); + } + } + + /** + * RecordPropertyDefinition : ComputedPropertyName : AssignmentExpression + */ + 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; + } + + private static boolean isAnonymousFunctionDefinition(JavaScriptNode expression) { + return expression instanceof FunctionNameHolder && ((FunctionNameHolder) expression).isAnonymous(); + } + + @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 + } + addPropertyIntoRecordEntriesList(entries, key, value); + } + + @Override + protected AbstractRecordLiteralMemberNode copyUninitialized(Set> materializedTags) { + return new ComputedRecordLiteralMemberNode( + JavaScriptNode.cloneUninitialized(keyNode, materializedTags), + JavaScriptNode.cloneUninitialized(valueNode, materializedTags) + ); + } + } + + /** + * RecordPropertyDefinition : ... AssignmentExpression + */ + private static class SpreadRecordLiteralMemberNode extends AbstractRecordLiteralMemberNode { + + @Child private JavaScriptNode valueNode; + @Child private JSToObjectNode toObjectNode; + @Child private ReadElementNode readElementNode; + @Child private JSGetOwnPropertyNode getOwnPropertyNode; + @Child private ListGetNode listGet = ListGetNode.create(); + @Child private ListSizeNode listSize = ListSizeNode.create(); + + SpreadRecordLiteralMemberNode(JavaScriptNode valueNode) { + this.valueNode = valueNode; + } + + @Override + public void evaluate(VirtualFrame frame, Map entries, JSContext context) { + Object source = valueNode.execute(frame); + if (JSRuntime.isNullOrUndefined(source)) { + return; + } + 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); + PropertyDescriptor desc = getOwnProperty(from, key); + if (desc != null && desc.getEnumerable()) { + Object value = get(from, key, context); + addPropertyIntoRecordEntriesList(entries, key, value); + } + } + } + + private Object toObject(Object obj, JSContext context) { + if (toObjectNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + toObjectNode = insert(JSToObjectNode.createToObjectNoCheck(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(); + readElementNode = insert(ReadElementNode.create(context)); + } + return readElementNode.executeWithTargetAndIndex(obj, key); + } + + @Override + protected AbstractRecordLiteralMemberNode copyUninitialized(Set> materializedTags) { + return new SpreadRecordLiteralMemberNode( + JavaScriptNode.cloneUninitialized(valueNode, materializedTags) + ); + } + } + + @Override + public boolean isResultAlwaysOfType(Class 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/JSIsConcatSpreadableNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/JSIsConcatSpreadableNode.java new file mode 100644 index 00000000000..17abcc9590b --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/JSIsConcatSpreadableNode.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.truffle.js.nodes.tuples; + +import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.dsl.Specialization; +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.Undefined; + +/** + * Represents the abstract operation IsConcatSpreadable. + */ +public abstract class JSIsConcatSpreadableNode extends JavaScriptBaseNode { + + @Child private PropertyGetNode getSpreadableNode; + @Child private JSToBooleanNode toBooleanNode; + @Child private JSIsTupleNode isTupleNode; + @Child private JSIsArrayNode isArrayNode; + + protected final JSContext context; + + protected JSIsConcatSpreadableNode(JSContext context) { + super(); + this.context = context; + } + + public abstract boolean execute(Object operand); + + @Specialization + protected boolean doObject(Object o) { + if (!JSRuntime.isObject(o) && !JSRuntime.isTuple(o)) { + return false; + } + if (JSDynamicObject.isJSDynamicObject(o)) { + Object spreadable = getSpreadableProperty(o); + if (spreadable != Undefined.instance) { + return toBoolean(spreadable); + } + } + return isTuple(o) || isArray(o); + } + + private boolean isArray(Object object) { + if (isArrayNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + isArrayNode = insert(JSIsArrayNode.createIsArrayLike()); + } + 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, 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 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/JSIsTupleNode.java b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/JSIsTupleNode.java new file mode 100644 index 00000000000..79db2a13199 --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/JSIsTupleNode.java @@ -0,0 +1,77 @@ +/* + * 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; +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 the abstract operation IsTuple. + */ +public abstract class JSIsTupleNode extends JavaScriptBaseNode { + + protected JSIsTupleNode() { + } + + 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; + } + + @Fallback + protected static boolean doOther(@SuppressWarnings("unused") Object object) { + return false; + } + + public static JSIsTupleNode create() { + return JSIsTupleNodeGen.create(); + } +} 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/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..28c6a3142ff --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/tuples/TupleLiteralNode.java @@ -0,0 +1,242 @@ +/* + * 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.frame.VirtualFrame; +import com.oracle.truffle.api.instrumentation.Tag; +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.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; + + protected TupleLiteralNode(JSContext context) { + this.context = context; + } + + @Override + public abstract Tuple execute(VirtualFrame frame); + + 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 = Tuple.create(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) { + Object value = ((JSConstantNode) node).getValue(); + values[i] = requireNonObject(value); + } else { + return null; + } + } + return 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.createTypeErrorTuplesCannotContainObjects(null); + } + return value; + } + + private static Tuple createTuple(Object[] array) { + assert Arrays.stream(array).noneMatch(JSRuntime::isObject); + return Tuple.create(array); + } + + private static class DefaultTupleLiteralNode extends TupleLiteralNode { + + @Children protected final JavaScriptNode[] elements; + + private DefaultTupleLiteralNode(JSContext context, JavaScriptNode[] elements) { + super(context); + this.elements = elements; + } + + @Override + public Tuple execute(VirtualFrame frame) { + Object[] values = new Object[elements.length]; + for (int i = 0; i < elements.length; i++) { + Object value = elements[i].execute(frame); + values[i] = requireNonObject(value); + } + return 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(); + + private DefaultTupleLiteralWithSpreadNode(JSContext context, JavaScriptNode[] elements) { + super(context, elements); + } + + @Override + 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, sequence, growProfile); + } else { + Object value = node.execute(frame); + addValueToTupleSequenceList(sequence, value, growProfile); + } + } + return createTuple(sequence.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; + } + addValueToTupleSequenceList(toList, 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 == Tuple.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.TupleLiteral.name()); + } +} 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..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,15 +60,19 @@ 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; 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.Null; import com.oracle.truffle.js.runtime.objects.Undefined; @@ -174,6 +178,16 @@ 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; + } + @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/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); + } } 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 7259f23e602..3e5f2709a1a 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 @@ -96,11 +96,13 @@ 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; 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.JSUncheckedProxyHandler; import com.oracle.truffle.js.runtime.builtins.JSWeakMap; import com.oracle.truffle.js.runtime.builtins.JSWeakRef; @@ -417,6 +419,9 @@ public enum BuiltinFunctionKey { private final JSObjectFactory webAssemblyTableFactory; private final JSObjectFactory webAssemblyGlobalFactory; + private final JSObjectFactory recordFactory; + private final JSObjectFactory tupleFactory; + private final int factoryCount; @CompilationFinal private Locale locale; @@ -575,6 +580,14 @@ protected JSContext(Evaluator evaluator, JSContextOptions contextOptions, JavaSc this.webAssemblyTableFactory = builder.create(JSWebAssemblyTable.INSTANCE); this.webAssemblyGlobalFactory = builder.create(JSWebAssemblyGlobal.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(); this.argumentsPropertyProxy = new JSFunction.ArgumentsProxyProperty(this); @@ -1025,6 +1038,14 @@ public JSObjectFactory getWebAssemblyGlobalFactory() { return webAssemblyGlobalFactory; } + public final JSObjectFactory getRecordFactory() { + return recordFactory; + } + + 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"; @@ -1734,4 +1755,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 31bf8bb402f..7c1ef21e6e5 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; @@ -135,6 +136,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; @@ -378,6 +380,12 @@ public class JSRealm { /** Foreign object prototypes. */ private final DynamicObject foreignIterablePrototype; + /** Record and Tuple support. */ + private final DynamicObject recordConstructor; + private final DynamicObject recordPrototype; + private final DynamicObject tupleConstructor; + private final DynamicObject tuplePrototype; + /** * Local time zone ID. Initialized lazily. */ @@ -755,6 +763,20 @@ public JSRealm(JSContext context, TruffleLanguage.Env env) { } this.foreignIterablePrototype = createForeignIterablePrototype(); + + if (context.isRecordAndTupleEnabled()) { + 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; + } } private void initializeTypedArrayConstructors() { @@ -1416,6 +1438,10 @@ public void setupGlobals() { if (context.getContextOptions().isProfileTime()) { System.out.println("SetupGlobals: " + (System.nanoTime() - time) / 1000000); } + if (context.isRecordAndTupleEnabled()) { + putGlobalProperty(JSRecord.CLASS_NAME, getRecordConstructor()); + putGlobalProperty(JSTuple.CLASS_NAME, getTupleConstructor()); + } } private void initGlobalNashornExtensions() { @@ -2453,4 +2479,19 @@ public DynamicObject getForeignIterablePrototype() { return foreignIterablePrototype; } + public DynamicObject getRecordConstructor() { + return recordConstructor; + } + + public DynamicObject getRecordPrototype() { + return recordPrototype; + } + + 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 4380fd65946..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 @@ -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; @@ -76,9 +77,11 @@ import com.oracle.truffle.js.runtime.builtins.JSOrdinary; import com.oracle.truffle.js.runtime.builtins.JSOverloadedOperatorsObject; 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; +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; @@ -228,6 +231,10 @@ 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)) { DynamicObject object = (DynamicObject) value; if (JSProxy.isJSProxy(object)) { @@ -492,6 +499,10 @@ public static Number toNumberFromPrimitive(Object value) { throw Errors.createTypeErrorCannotConvertToNumber("a Symbol 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; @@ -530,6 +541,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; } @@ -920,6 +935,10 @@ public static String toString(Object value) { throw Errors.createTypeErrorCannotConvertToString("a Symbol value"); } 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) { @@ -973,6 +992,10 @@ private static String toDisplayStringImpl(Object value, int depth, Object parent 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())) { @@ -1318,6 +1341,31 @@ 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 + */ + @TruffleBoundary + public static String tupleToString(Tuple value) { + 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) { if (value == Undefined.instance) { return Undefined.NAME; @@ -1511,6 +1559,10 @@ 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 { assert !isJSNative(value) && isJavaPrimitive(value) : value; if (useJavaWrapper) { @@ -1535,6 +1587,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()); @@ -1542,10 +1597,74 @@ 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; } + /** + * Abstract operation RecordEqual ( x, y, elementEqual ) + */ + 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; + } + + /** + * Abstract operation TupleEqual ( x, y, elementEqual ) + */ + 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) { @@ -1578,6 +1697,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 (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) { @@ -1620,8 +1743,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); + 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) { @@ -1695,6 +1819,12 @@ public static boolean identical(Object a, Object b) { if (isBigInt(a) && isBigInt(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) { return ((Integer) a).intValue() == ((Integer) b).intValue(); @@ -2017,8 +2147,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; + 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..8e9efdc2bf7 --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Record.java @@ -0,0 +1,129 @@ +/* + * 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; +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; + +/** + * 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 + */ +@ValueType +public final class Record implements TruffleObject { + + private final TreeMap map; + + private Record(Map map) { + this.map = new TreeMap<>(map); + } + + 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); + } + + @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 new file mode 100644 index 00000000000..2585a64acb0 --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/Tuple.java @@ -0,0 +1,150 @@ +/* + * 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; +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; +import com.oracle.truffle.js.lang.JavaScriptLanguage; +import com.oracle.truffle.js.runtime.interop.JSMetaType; + +import java.util.Arrays; +import java.util.stream.Collectors; + +/** + * 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 + */ +@ValueType +public final class Tuple implements TruffleObject { + + public static final Tuple EMPTY_TUPLE = new Tuple(new Object[]{}); + + private final Object[] value; + + private Tuple(Object[] v) { + assert v != null; + this.value = v; + } + + public static Tuple create() { + return EMPTY_TUPLE; + } + + public static Tuple create(Object[] v) { + if (v == null || v.length == 0) { + return EMPTY_TUPLE; + } + 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() { + 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.length != other.value.length) { + return false; + } + for (int i = 0; i < value.length; i++) { + if (!JSRuntime.isSameValueZero(value[i], other.value[i])) { + return false; + } + } + return true; + } +} 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..f99603606fa --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSRecord.java @@ -0,0 +1,277 @@ +/* + * 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; +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; +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.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; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * Implementation of the Record (exotic) object internal methods and properties. + * See Records & Tuples Proposal: 5.1.1 Record Exotic Objects + * + * @see JSRecordObject + * @see Record + */ +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 STRING_NAME = "[object Record]"; + + 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) { + return Null.instance; + } + + @Override + public DynamicObject getIntrinsicDefaultProto(JSRealm realm) { + return realm.getRecordPrototype(); + } + + 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. + * @see JSNonProxy#isExtensible(com.oracle.truffle.api.object.DynamicObject) + */ + @Override + public Shape makeInitialShape(JSContext context, DynamicObject prototype) { + 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; + } + + /** + * [[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); + } + + /** + * [[DefineOwnProperty]] + */ + @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"); + } + 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; + } + return DefinePropertyUtil.reject(doThrow, "cannot redefine property"); + } + + /** + * [[HasProperty]] + * This methods also represents the abstract operation RecordHasProperty (R, P). + */ + @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); + } + + /** + * [[Get]] + */ + @Override + public Object getOwnHelper(DynamicObject store, Object thisObj, Object key, Node encapsulatingNode) { + assert JSRuntime.isPropertyKey(key); + if (key instanceof Symbol) { + return null; + } + return recordGet(store, key); + } + + /** + * [[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; + } + return !hasOwnProperty(thisObj, key); + } + + @Override + public boolean delete(DynamicObject thisObj, long index, boolean isStrict) { + return delete(thisObj, String.valueOf(index), isStrict); + } + + /** + * [[OwnPropertyKeys]] + */ + @TruffleBoundary + @Override + public List getOwnPropertyKeys(DynamicObject thisObj, boolean strings, boolean symbols) { + if (!strings) { + return Collections.emptyList(); + } + Record value = valueOf(thisObj); + return Arrays.asList(value.getKeys()); + } + + 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.isObject(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..3dd5fb6709c --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSRecordObject.java @@ -0,0 +1,77 @@ +/* + * 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; + +/** + * 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; + + 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 new file mode 100644 index 00000000000..fce268a5d22 --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSTuple.java @@ -0,0 +1,330 @@ +/* + * 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; +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.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.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; + +/** + * 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"; + public static final String CLASS_NAME = "Tuple"; + public static final String PROTOTYPE_NAME = "Tuple.prototype"; + + public static final JSTuple INSTANCE = new JSTuple(); + + public static final String LENGTH = "length"; + + 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); + + // Sets the Tuple.prototype.length accessor property. + 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()); + + 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) { + 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) { + 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(); + } + + /** + * [[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) { + 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); + } + + @TruffleBoundary + @Override + public Object getOwnHelper(DynamicObject store, Object thisObj, long index, Node encapsulatingNode) { + 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; + } + 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 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); + } + + 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; + } +} 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..0280b2b93f0 --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/builtins/JSTupleObject.java @@ -0,0 +1,77 @@ +/* + * 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.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; + + 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); + } +}