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 5d17f89438a..6611c6a3b3d 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 @@ -244,6 +244,7 @@ public class Parser extends AbstractParser { private static final boolean ES2019_OPTIONAL_CATCH_BINDING = Options.getBooleanProperty("parser.optional.catch.binding", true); private static final boolean ES2020_CLASS_FIELDS = Options.getBooleanProperty("parser.class.fields", true); private static final boolean ES2022_TOP_LEVEL_AWAIT = Options.getBooleanProperty("parser.top.level.await", true); + private static final boolean ESNEXT_EXTRACTORS = Options.getBooleanProperty("parser.extractors", false); private static final int REPARSE_IS_PROPERTY_ACCESSOR = 1 << 0; private static final int REPARSE_IS_METHOD = 1 << 1; @@ -1046,7 +1047,7 @@ private Expression verifyAssignment(final long op, final Expression lhs, final E throw invalidLHSError(lhs); } break; - } else if ((opType == ASSIGN || opType == ASSIGN_INIT) && isDestructuringLhs(lhs) && (inPatternPosition || !lhs.isParenthesized())) { + } else if ((opType == ASSIGN || opType == ASSIGN_INIT) && isDestructuringOrExtractorLhs(lhs) && (inPatternPosition || !lhs.isParenthesized())) { verifyDestructuringAssignmentPattern(lhs, CONTEXT_ASSIGNMENT_TARGET); break; } else { @@ -1060,15 +1061,19 @@ private Expression verifyAssignment(final long op, final Expression lhs, final E return new BinaryNode(op, lhs, rhsExpr); } - private boolean isDestructuringLhs(Expression lhs) { + private boolean isDestructuringOrExtractorLhs(Expression lhs) { if (lhs instanceof ObjectNode || lhs instanceof ArrayLiteralNode) { return ES6_DESTRUCTURING && isES6(); } + if (lhs instanceof CallNode) { + return ESNEXT_EXTRACTORS; + } return false; } + private void verifyDestructuringAssignmentPattern(Expression pattern, String contextString) { - assert pattern instanceof ObjectNode || pattern instanceof ArrayLiteralNode; + assert pattern instanceof ObjectNode || pattern instanceof ArrayLiteralNode || pattern instanceof CallNode; pattern.accept(new VerifyDestructuringPatternNodeVisitor(new LexicalContext()) { @Override protected void verifySpreadElement(Expression lvalue) { @@ -2475,9 +2480,16 @@ private ForVariableDeclarationListResult variableDeclarationList(TokenType varTy final int varLine = line; final long varToken = Token.recast(token, varType); - // Get name of var. - final Expression binding = bindingIdentifierOrPattern(yield, await, CONTEXT_VARIABLE_NAME); - final boolean isDestructuring = !(binding instanceof IdentNode); + // Get left hand side. + final Expression binding = leftHandSideExpression(yield, await, CoverExpressionError.DENY); + if (binding.tokenType() == TokenType.NEW) { + throw error(AbstractParser.message(MSG_INVALID_LVALUE), binding.getToken()); + } + + final boolean isExtracting = binding instanceof CallNode; + assert !isExtracting || ESNEXT_EXTRACTORS; + + final boolean isDestructuring = !(binding instanceof IdentNode) && !isExtracting; if (isDestructuring) { final int finalVarFlags = varFlags | VarNode.IS_DESTRUCTURING; verifyDestructuringBindingPattern(binding, new Consumer() { @@ -2525,7 +2537,7 @@ public void accept(IdentNode identNode) { // else, if we are in a for loop, delay checking until we know the kind of loop } - if (!isDestructuring) { + if (!isDestructuring && !isExtracting) { assert init != null || varType != CONST || !isStatement; final IdentNode ident = (IdentNode) binding; if (varType != VAR && ident.getName().equals(LET.getName())) { @@ -2739,22 +2751,7 @@ public boolean enterLiteralNode(LiteralNode literalNode) { if (((ArrayLiteralNode) literalNode).hasSpread() && ((ArrayLiteralNode) literalNode).hasTrailingComma()) { throw error("Rest element must be last", literalNode.getElementExpressions().get(literalNode.getElementExpressions().size() - 1).getToken()); } - boolean restElement = false; - for (Expression element : literalNode.getElementExpressions()) { - if (element != null) { - if (restElement) { - throw error("Unexpected element after rest element", element.getToken()); - } - if (element.isTokenType(SPREAD_ARRAY)) { - restElement = true; - Expression lvalue = ((UnaryNode) element).getExpression(); - verifySpreadElement(lvalue); - } else { - element.accept(this); - } - } - } - return false; + return handleNodeListWithSpread(literalNode.getElementExpressions(), SPREAD_ARRAY); } else { return enterDefault(literalNode); } @@ -2767,23 +2764,7 @@ public boolean enterObjectNode(ObjectNode objectNode) { if (objectNode.isParenthesized()) { throw error(AbstractParser.message(MSG_INVALID_LVALUE), objectNode.getToken()); } - boolean restElement = false; - for (PropertyNode property : objectNode.getElements()) { - if (property != null) { - if (restElement) { - throw error("Unexpected element after rest element", property.getToken()); - } - Expression key = property.getKey(); - if (key.isTokenType(SPREAD_OBJECT)) { - restElement = true; - Expression lvalue = ((UnaryNode) key).getExpression(); - verifySpreadElement(lvalue); - } else { - property.accept(this); - } - } - } - return false; + return this.handleNodeListWithSpread(objectNode.getElements(), SPREAD_OBJECT); } @Override @@ -2806,6 +2787,39 @@ public boolean enterBinaryNode(BinaryNode binaryNode) { return enterDefault(binaryNode); } } + + @Override + public boolean enterCallNode(CallNode callNode) { + if (callNode.isParenthesized() || !ESNEXT_EXTRACTORS) { + throw error(AbstractParser.message(MSG_INVALID_LVALUE), callNode.getToken()); + } + return handleNodeListWithSpread(callNode.getArgs(), SPREAD_ARGUMENT); + } + + private boolean handleNodeListWithSpread(List nodes, TokenType acceptableSpreadToken) { + boolean encounteredSpread = false; + + for (final var node : nodes) { + if (node == null) { + continue; + } + if (encounteredSpread) { + throw error("Unexpected element after rest element", node.getToken()); + } + + final var key = node instanceof PropertyNode ? ((PropertyNode) node).getKey() : node; + if (key.isTokenType(acceptableSpreadToken)) { + encounteredSpread = true; + + final var lvalue = ((UnaryNode) key).getExpression(); + verifySpreadElement(lvalue); + } else { + node.accept(this); + } + } + + return false; + } } /** @@ -2813,13 +2827,13 @@ public boolean enterBinaryNode(BinaryNode binaryNode) { * declarations. */ private void verifyDestructuringBindingPattern(Expression pattern, Consumer identifierCallback) { - assert pattern instanceof ObjectNode || pattern instanceof ArrayLiteralNode; + assert pattern instanceof ObjectNode || pattern instanceof ArrayLiteralNode || pattern instanceof CallNode; pattern.accept(new VerifyDestructuringPatternNodeVisitor(new LexicalContext()) { @Override protected void verifySpreadElement(Expression lvalue) { if (lvalue instanceof IdentNode) { enterIdentNode((IdentNode) lvalue); - } else if (isDestructuringLhs(lvalue)) { + } else if (isDestructuringOrExtractorLhs(lvalue)) { verifyDestructuringBindingPattern(lvalue, identifierCallback); } else { throw error("Expected a valid binding identifier", lvalue.getToken()); @@ -3072,8 +3086,8 @@ private void forStatement(boolean yield, boolean await) { throw error(AbstractParser.message(MSG_MANY_VARS_IN_FOR_IN_LOOP, isForOf || isForAwaitOf ? CONTEXT_OF : CONTEXT_IN), varDeclList.secondBinding.getToken()); } init = varDeclList.firstBinding; - assert init instanceof IdentNode || isDestructuringLhs(init) : init; - if (varDeclList.declarationWithInitializerToken != 0 && (isStrictMode || type != IN || varType != VAR || isDestructuringLhs(init))) { + assert init instanceof IdentNode || isDestructuringOrExtractorLhs(init) : init; + if (varDeclList.declarationWithInitializerToken != 0 && (isStrictMode || type != IN || varType != VAR || isDestructuringOrExtractorLhs(init))) { /* * ES5 legacy: for (var i = AssignmentExpressionNoIn in Expression). * Invalid in ES6, but allowed in non-strict mode if no ES6 features @@ -3150,7 +3164,7 @@ private boolean checkValidLValue(Expression init, String contextString) { return true; } else if (init instanceof AccessNode || init instanceof IndexNode) { return !((BaseNode) init).isOptional(); - } else if (isDestructuringLhs(init)) { + } else if (isDestructuringOrExtractorLhs(init)) { verifyDestructuringAssignmentPattern(init, contextString); return true; } else { @@ -5081,7 +5095,7 @@ private Expression memberExpression(boolean yield, boolean await, CoverExpressio * This helps report the first error location for cases like: ({x=i}[{y=j}]). */ private void verifyPrimaryExpression(Expression lhs, CoverExpressionError coverExpression) { - if (coverExpression != CoverExpressionError.DENY && coverExpression.hasError() && isDestructuringLhs(lhs)) { + if (coverExpression != CoverExpressionError.DENY && coverExpression.hasError() && isDestructuringOrExtractorLhs(lhs)) { /** * These token types indicate that the preceding PrimaryExpression is part of an * unfinished MemberExpression or other LeftHandSideExpression, which also means that it @@ -5677,7 +5691,7 @@ private static void addDefaultParameter(long paramToken, int paramFinish, int pa } private void addDestructuringParameter(long paramToken, int paramFinish, int paramLine, Expression target, Expression initializer, ParserContextFunctionNode function, boolean isRest) { - assert isDestructuringLhs(target); + assert isDestructuringOrExtractorLhs(target); // desugar to: target := (param === undefined) ? initializer : param; // we use an special positional parameter node not subjected to TDZ rules; // thereby, we forego the need for a synthetic param symbol to refer to the passed value. @@ -6432,7 +6446,7 @@ private Expression assignmentExpression(boolean in, boolean yield, boolean await assert !(exprLhs instanceof ExpressionList); if (type.isAssignment()) { - if (canBeAssignmentPattern && !isDestructuringLhs(exprLhs)) { + if (canBeAssignmentPattern && !isDestructuringOrExtractorLhs(exprLhs)) { // If LHS is not an AssignmentPattern, verify that it is a valid expression. verifyExpression(coverExprLhs); } @@ -6584,7 +6598,7 @@ private void convertArrowFunctionParameterList(Expression paramList, ParserConte return; } final int functionLine = function.getLineNumber(); - if (paramListExpr instanceof IdentNode || paramListExpr.isTokenType(ASSIGN) || isDestructuringLhs(paramListExpr) || paramListExpr.isTokenType(SPREAD_ARGUMENT)) { + if (paramListExpr instanceof IdentNode || paramListExpr.isTokenType(ASSIGN) || isDestructuringOrExtractorLhs(paramListExpr) || paramListExpr.isTokenType(SPREAD_ARGUMENT)) { convertArrowParameter(paramListExpr, 0, functionLine, function); } else if (paramListExpr instanceof BinaryNode && Token.descType(paramListExpr.getToken()) == COMMARIGHT) { List params = new ArrayList<>(); @@ -6637,7 +6651,7 @@ private void convertArrowParameter(Expression param, int index, int paramLine, P addDefaultParameter(paramToken, param.getFinish(), paramLine, ident, initializer, currentFunction); return; - } else if (isDestructuringLhs(lhs)) { + } else if (isDestructuringOrExtractorLhs(lhs)) { // binding pattern with initializer verifyDestructuringParameterBindingPattern(lhs, paramToken, paramLine); @@ -6645,7 +6659,7 @@ private void convertArrowParameter(Expression param, int index, int paramLine, P } else { throw error(AbstractParser.message(MSG_INVALID_ARROW_PARAMETER), paramToken); } - } else if (isDestructuringLhs(param)) { + } else if (isDestructuringOrExtractorLhs(param)) { // binding pattern long paramToken = param.getToken(); @@ -6661,7 +6675,7 @@ private void convertArrowParameter(Expression param, int index, int paramLine, P if (restParam instanceof IdentNode) { IdentNode ident = ((IdentNode) restParam).setIsRestParameter(); convertArrowParameter(ident, index, paramLine, currentFunction); - } else if (isDestructuringLhs(restParam)) { + } else if (isDestructuringOrExtractorLhs(restParam)) { verifyDestructuringParameterBindingPattern(restParam, restParam.getToken(), paramLine); addDestructuringParameter(restParam.getToken(), restParam.getFinish(), paramLine, restParam, null, currentFunction, true); } else { diff --git a/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/ir/Node.java b/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/ir/Node.java index 18c63c0ecb0..b6c2bf44850 100644 --- a/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/ir/Node.java +++ b/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/ir/Node.java @@ -254,7 +254,7 @@ public long getToken() { return token; } - // on change, we have to replace the entire list, that's we can't simple do ListIterator.set + // on change, we have to replace the entire list, that's we can't simply do ListIterator.set static List accept(final NodeVisitor visitor, final List list) { final int size = list.size(); if (size == 0) { 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 d012b0b8eb5..d456243de2f 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 @@ -119,7 +119,9 @@ import com.oracle.truffle.js.nodes.access.DeclareEvalVariableNode; import com.oracle.truffle.js.nodes.access.DeclareGlobalNode; import com.oracle.truffle.js.nodes.access.GetIteratorUnaryNode; +import com.oracle.truffle.js.nodes.access.GetMethodNode; import com.oracle.truffle.js.nodes.access.GlobalPropertyNode; +import com.oracle.truffle.js.nodes.access.IteratorToArrayNode; import com.oracle.truffle.js.nodes.access.JSConstantNode; import com.oracle.truffle.js.nodes.access.JSReadFrameSlotNode; import com.oracle.truffle.js.nodes.access.JSWriteFrameSlotNode; @@ -2846,7 +2848,11 @@ private JavaScriptNode transformAssignmentImpl(Expression assignmentExpression, } // fall through case IDENT: - assignedNode = transformAssignmentIdent((IdentNode) lhsExpression, assignedValue, binaryOp, returnOldValue, convertLHSToNumeric, initializationAssignment); + if (lhsExpression instanceof CallNode) { + assignedNode = transformAssignmentExtractor((CallNode) lhsExpression, assignedValue, initializationAssignment); + } else { + assignedNode = transformAssignmentIdent((IdentNode) lhsExpression, assignedValue, binaryOp, returnOldValue, convertLHSToNumeric, initializationAssignment); + } break; case LBRACKET: // target[element] @@ -2891,40 +2897,38 @@ private JavaScriptNode transformAssignmentIdent(IdentNode identNode, JavaScriptN rhs = checkMutableBinding(rhs, scopeVar.getName()); } return scopeVar.createWriteNode(rhs); + } else if (isLogicalOp(binaryOp)) { + assert !convertLHSToNumeric && !returnOldValue && assignedValue != null; + if (constAssignment) { + rhs = checkMutableBinding(rhs, scopeVar.getName()); + } + JavaScriptNode readNode = tagExpression(scopeVar.createReadNode(), identNode); + JavaScriptNode writeNode = scopeVar.createWriteNode(rhs); + return factory.createBinary(context, binaryOp, readNode, writeNode); } else { - if (isLogicalOp(binaryOp)) { - assert !convertLHSToNumeric && !returnOldValue && assignedValue != null; - if (constAssignment) { - rhs = checkMutableBinding(rhs, scopeVar.getName()); - } - JavaScriptNode readNode = tagExpression(scopeVar.createReadNode(), identNode); - JavaScriptNode writeNode = scopeVar.createWriteNode(rhs); - return factory.createBinary(context, binaryOp, readNode, writeNode); + // e.g.: lhs *= rhs => lhs = lhs * rhs + // If lhs is a side-effecting getter that deletes lhs, we must not throw a + // ReferenceError at the lhs assignment since the lhs reference is already resolved. + // We also need to ensure that HasBinding is idempotent or evaluated at most once. + Pair, UnaryOperator> pair = scopeVar.createCompoundAssignNode(); + JavaScriptNode readNode = tagExpression(pair.getFirst().get(), identNode); + if (convertLHSToNumeric) { + readNode = factory.createToNumericOperand(readNode); + } + VarRef prevValueTemp = null; + if (returnOldValue) { + prevValueTemp = environment.createTempVar(); + readNode = prevValueTemp.createWriteNode(readNode); + } + JavaScriptNode binOpNode = tagExpression(factory.createBinary(context, binaryOp, readNode, rhs), identNode); + if (constAssignment) { + binOpNode = checkMutableBinding(binOpNode, scopeVar.getName()); + } + JavaScriptNode writeNode = pair.getSecond().apply(binOpNode); + if (returnOldValue) { + return factory.createDual(context, writeNode, prevValueTemp.createReadNode()); } else { - // e.g.: lhs *= rhs => lhs = lhs * rhs - // If lhs is a side-effecting getter that deletes lhs, we must not throw a - // ReferenceError at the lhs assignment since the lhs reference is already resolved. - // We also need to ensure that HasBinding is idempotent or evaluated at most once. - Pair, UnaryOperator> pair = scopeVar.createCompoundAssignNode(); - JavaScriptNode readNode = tagExpression(pair.getFirst().get(), identNode); - if (convertLHSToNumeric) { - readNode = factory.createToNumericOperand(readNode); - } - VarRef prevValueTemp = null; - if (returnOldValue) { - prevValueTemp = environment.createTempVar(); - readNode = prevValueTemp.createWriteNode(readNode); - } - JavaScriptNode binOpNode = tagExpression(factory.createBinary(context, binaryOp, readNode, rhs), identNode); - if (constAssignment) { - binOpNode = checkMutableBinding(binOpNode, scopeVar.getName()); - } - JavaScriptNode writeNode = pair.getSecond().apply(binOpNode); - if (returnOldValue) { - return factory.createDual(context, writeNode, prevValueTemp.createReadNode()); - } else { - return writeNode; - } + return writeNode; } } } @@ -3054,14 +3058,19 @@ private JavaScriptNode transformIndexAssignment(IndexNode indexNode, JavaScriptN } private JavaScriptNode transformDestructuringArrayAssignment(Expression lhsExpression, JavaScriptNode assignedValue, boolean initializationAssignment) { + VarRef valueTempVar = environment.createTempVar(); + JavaScriptNode initValue = valueTempVar.createWriteNode(assignedValue); + JavaScriptNode getIterator = factory.createGetIterator(initValue); LiteralNode.ArrayLiteralNode arrayLiteralNode = (LiteralNode.ArrayLiteralNode) lhsExpression; List elementExpressions = arrayLiteralNode.getElementExpressions(); + + return this.transformDestructuringArrayAssignment(elementExpressions, getIterator, valueTempVar.createReadNode(), initializationAssignment); + } + + private JavaScriptNode transformDestructuringArrayAssignment(List elementExpressions, JavaScriptNode getIterator, JavaScriptNode valueTempNode, boolean initializationAssignment) { JavaScriptNode[] initElements = javaScriptNodeArray(elementExpressions.size()); VarRef iteratorTempVar = environment.createTempVar(); - VarRef valueTempVar = environment.createTempVar(); - JavaScriptNode initValue = valueTempVar.createWriteNode(assignedValue); // By default, we use the hint to track the type of iterator. - JavaScriptNode getIterator = factory.createGetIterator(initValue); JavaScriptNode initIteratorTempVar = iteratorTempVar.createWriteNode(getIterator); for (int i = 0; i < elementExpressions.size(); i++) { @@ -3083,7 +3092,7 @@ private JavaScriptNode transformDestructuringArrayAssignment(Expression lhsExpre if (init != null) { rhsNode = factory.createNotUndefinedOr(rhsNode, transform(init)); } - if (lhsExpr != null && lhsExpr.isTokenType(TokenType.SPREAD_ARRAY)) { + if (lhsExpr != null && (lhsExpr.isTokenType(TokenType.SPREAD_ARRAY) || lhsExpr.isTokenType(TokenType.SPREAD_ARGUMENT))) { rhsNode = factory.createIteratorToArray(context, iteratorTempVar.createReadNode()); lhsExpr = ((UnaryNode) lhsExpr).getExpression(); } @@ -3094,7 +3103,25 @@ private JavaScriptNode transformDestructuringArrayAssignment(Expression lhsExpre } } JavaScriptNode closeIfNotDone = factory.createIteratorCloseIfNotDone(context, createBlock(initElements), iteratorTempVar.createReadNode()); - return factory.createExprBlock(initIteratorTempVar, closeIfNotDone, valueTempVar.createReadNode()); + return factory.createExprBlock(initIteratorTempVar, closeIfNotDone, valueTempNode); + } + + private JavaScriptNode transformAssignmentExtractor(CallNode fakeCallNode, JavaScriptNode assignedValue, boolean initializationAssignment) { + final var functionExpr = fakeCallNode.getFunction(); + final var function = transform(functionExpr); + + var receiver = function; + if (functionExpr instanceof AccessNode) { + final AccessNode accessNode = (AccessNode) functionExpr; + receiver = transform(accessNode.getBase()); + } + + final var invokeCustomMatcherOrThrowNode = factory.createInvokeCustomMatcherOrThrow(context, function, assignedValue, receiver); + + final var args = fakeCallNode.getArgs(); + VarRef valueTempVar = environment.createTempVar(); + return this.transformDestructuringArrayAssignment(args, invokeCustomMatcherOrThrowNode, + createBlock(valueTempVar.createWriteNode(assignedValue), valueTempVar.createReadNode()), initializationAssignment); } private JavaScriptNode transformDestructuringObjectAssignment(Expression lhsExpression, JavaScriptNode assignedValue, boolean initializationAssignment) { diff --git a/graal-js/src/com.oracle.truffle.js.test/js-extractors/as-default.js b/graal-js/src/com.oracle.truffle.js.test/js-extractors/as-default.js new file mode 100644 index 00000000000..fab0c98ccca --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js.test/js-extractors/as-default.js @@ -0,0 +1,46 @@ +load('../js/assert.js'); + +const DateExtractor = { + [Symbol.customMatcher](value) { + if (value instanceof Date) { + return [value]; + } else if (typeof value === "number") { + return [new Date(value)]; + } else if (typeof value === "string") { + return [Date.parse(value)]; + } + } +}; + +class Book { + constructor({ + isbn, + title, + // Extract `createdAt` as an Instant + createdAt: DateExtractor(createdAt) = Date.now(), + modifiedAt: DateExtractor(modifiedAt) = createdAt + }) { + this.isbn = isbn; + this.title = title; + this.createdAt = createdAt; + this.modifiedAt = modifiedAt; + } +} + +{ + const date = Date.parse("1970-01-01T00:00:00Z") + const book = new Book({ isbn: "...", title: "...", createdAt: date }); + assertSame(date.valueOf(), book.createdAt.valueOf()); +} + +{ + const msSinceEpoch = 1000; + const book = new Book({ isbn: "...", title: "...", createdAt: msSinceEpoch }); + assertSame(msSinceEpoch, book.createdAt.valueOf()); +} + +{ + const createdAt = "1970-01-01T00Z"; + const book = new Book({ isbn: "...", title: "...", createdAt }); + assertSame(Date.parse(createdAt).valueOf(), book.createdAt.valueOf()); +} diff --git a/graal-js/src/com.oracle.truffle.js.test/js-extractors/assignment-binding.js b/graal-js/src/com.oracle.truffle.js.test/js-extractors/assignment-binding.js new file mode 100644 index 00000000000..eea95fe09a3 --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js.test/js-extractors/assignment-binding.js @@ -0,0 +1,135 @@ +load('../js/assert.js'); + +{ + class C { + #data; + + constructor(data) { + this.#data = data; + } + + static [Symbol.customMatcher](subject) { + return #data in subject && [subject.#data]; + } + } + + const subject = new C("data"); + + { + let x; + C(x) = subject; + assertSame("data", x); + } + + { + let C(x) = subject; + assertSame("data", x); + } + + { + var C(x) = subject; + assertSame("data", x); + } + + { + const C(x) = subject; + assertSame("data", x); + } +} + +{ + class C { + #data; + constructor(data) { + this.#data = data; + } + static [Symbol.customMatcher](subject) { + return #data in subject && [subject.#data]; + } + } + + const subject = new C({ x: 1, y: 2 }); + + { + let x, y; + C({x, y}) = subject; + + assertSame(1, x); + assertSame(2, y); + } + + { + let C({x, y}) = subject; + + assertSame(1, x); + assertSame(2, y); + } +} + +{ + class C { + #first; + #second; + constructor(first, second) { + this.#first = first; + this.#second = second; + } + static [Symbol.customMatcher](subject) { + return #first in subject && [subject.#first, subject.#second]; + } + } + + const subject = new C(undefined, 2); + + { + let x = -1, y = 100; + C(x = -1, y) = subject; + assertSame(-1, x); + assertSame(2, y); + } + + { + const C(x = -1, y) = subject; + assertSame(-1, x); + assertSame(2, y); + } +} + +{ + const [a, ...b] = [1, 2, 3]; + + class C { + #first; + #second; + #third; + constructor(first, second, third) { + this.#first = first; + this.#second = second; + this.#third = third; + } + static [Symbol.customMatcher](subject) { + return #first in subject && [subject.#first, subject.#second, subject.#third]; + } + } + + const subject = new C(1, 2, 3); + + { + let x, y; + C(x, ...y) = subject; + assertSame(1, x); + assertSameContent([2, 3], y); + } + + { + const C(x, ...y) = subject; + assertSame(1, x); + assertSameContent([2, 3], y); + } + + { + assertSame(subject, C() = subject) + assertSame(subject, C(x) = subject) + assertSame(subject, C(x, ...y) = subject) + } +} diff --git a/graal-js/src/com.oracle.truffle.js.test/js-extractors/error.js b/graal-js/src/com.oracle.truffle.js.test/js-extractors/error.js new file mode 100644 index 00000000000..530d727eb37 --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js.test/js-extractors/error.js @@ -0,0 +1,25 @@ +load('../js/assert.js'); + +{ + class C { + #data; + + constructor(data) { + this.#data = data; + } + + static [Symbol.customMatcher](subject) { + return undefined; + } + } + + const subject = new C("data"); + + try { + const C(x) = subject; + assertTrue(false); + } catch (e) { + // ensure a meaningful error message is thrown + assertTrue(e.message.includes("customMatcher")); + } +} diff --git a/graal-js/src/com.oracle.truffle.js.test/js-extractors/nested.js b/graal-js/src/com.oracle.truffle.js.test/js-extractors/nested.js new file mode 100644 index 00000000000..b536d3751aa --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js.test/js-extractors/nested.js @@ -0,0 +1,33 @@ +load('../js/assert.js'); + +class C { + #data1; + constructor(data1) { + this.#data1 = data1; + } + static [Symbol.customMatcher](subject) { + return #data1 in subject && [subject.#data1]; + } +} + +class D { + #data2; + constructor(data2) { + this.#data2 = data2; + } + static [Symbol.customMatcher](subject) { + return #data2 in subject && [subject.#data2]; + } +} + +const subject = new C(new D("data")); + +const C(D(x)) = subject; + +assertSame(x, "data"); + +const { a: C(D(y)) } = { a: subject }; +assertSame(y, "data"); + +const { a: { b: C(D(z)) } } = { a: { b: subject } }; +assertSame(z, "data");`` diff --git a/graal-js/src/com.oracle.truffle.js.test/js-extractors/object.js b/graal-js/src/com.oracle.truffle.js.test/js-extractors/object.js new file mode 100644 index 00000000000..60410ce1802 --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js.test/js-extractors/object.js @@ -0,0 +1,20 @@ +load('../js/assert.js'); + +const MapExtractor = { + [Symbol.customMatcher](map) { + const obj = {}; + for (const [key, value] of map) { + obj[typeof key === "symbol" ? key : `${key}`] = value; + } + return [obj]; + } +}; + +const obj = { + map: new Map([["a", 1], ["b", 2]]) +}; + +const { map: MapExtractor({ a, b }) } = obj; + +assertSame(a, 1); +assertSame(b, 2); diff --git a/graal-js/src/com.oracle.truffle.js.test/js-extractors/receiver.js b/graal-js/src/com.oracle.truffle.js.test/js-extractors/receiver.js new file mode 100644 index 00000000000..feb0876733e --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js.test/js-extractors/receiver.js @@ -0,0 +1,20 @@ +load('../js/assert.js'); + +class C { + #f; + constructor(f) { + this.#f = f; + } + extractor = { + [Symbol.customMatcher](subject, _kind, receiver) { + return [receiver.#f(subject)]; + } + }; +} + +const obj = new C(data => data.toUpperCase() + "1234"); +const subject = "data"; + +const obj.extractor(x) = subject; + +assertSame(x, "DATA1234"); diff --git a/graal-js/src/com.oracle.truffle.js.test/js-extractors/regexp.js b/graal-js/src/com.oracle.truffle.js.test/js-extractors/regexp.js new file mode 100644 index 00000000000..b72cc56f4db --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js.test/js-extractors/regexp.js @@ -0,0 +1,50 @@ +load('../js/assert.js'); + +// potentially built-in as part of Pattern Matching +RegExp.prototype[Symbol.customMatcher] = function (value) { + const match = this.exec(value); + return !!match && [match]; +}; + +const input = '2025-01-02T12:34:56Z'; + +const IsoDate = /^(?\d{4})-(?\d{2})-(?\d{2})$/; +const IsoTime = /^(?\d{2}):(?\d{2}):(?\d{2})$/; +const IsoDateTime = /^(?[^TZ]+)T(?