Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 101 additions & 66 deletions graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/Parser.java

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@ public enum FunctionStatementBehavior {
/** Are source phase imports enabled. */
final boolean sourcePhaseImports;

/** Are extractors enabled. */
final boolean extractors;

public ScriptEnvironment(boolean strict,
int ecmaScriptVersion,
boolean emptyStatements,
Expand All @@ -149,6 +152,7 @@ public ScriptEnvironment(boolean strict,
boolean privateFieldsIn,
boolean topLevelAwait,
boolean v8Intrinsics,
boolean extractors,
FunctionStatementBehavior functionStatementBehavior) {
this.constAsVar = constAsVar;
this.emptyStatements = emptyStatements;
Expand All @@ -167,6 +171,7 @@ public ScriptEnvironment(boolean strict,
this.privateFieldsIn = privateFieldsIn;
this.topLevelAwait = topLevelAwait;
this.v8Intrinsics = v8Intrinsics;
this.extractors = extractors;
}

public boolean isStrict() {
Expand Down Expand Up @@ -195,6 +200,7 @@ public static final class Builder {
private boolean privateFieldsIn = false;
private boolean topLevelAwait = false;
private boolean v8Intrinsics = false;
private boolean extractors = false;
private FunctionStatementBehavior functionStatementBehavior = FunctionStatementBehavior.ERROR;

private Builder() {
Expand Down Expand Up @@ -284,9 +290,14 @@ public Builder functionStatementBehavior(FunctionStatementBehavior functionState
return this;
}

public Builder extractors(boolean extractors) {
this.extractors = extractors;
return this;
}

public ScriptEnvironment build() {
return new ScriptEnvironment(strict, ecmaScriptVersion, emptyStatements, syntaxExtensions, scripting, shebang, constAsVar, allowBigInt, annexB,
classFields, importAttributes, importAssertions, sourcePhaseImports, privateFieldsIn, topLevelAwait, v8Intrinsics, functionStatementBehavior);
classFields, importAttributes, importAssertions, sourcePhaseImports, privateFieldsIn, topLevelAwait, v8Intrinsics, extractors, functionStatementBehavior);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 <T extends Node> List<T> accept(final NodeVisitor<? extends LexicalContext> visitor, final List<T> list) {
final int size = list.size();
if (size == 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ private static ScriptEnvironment makeScriptEnvironment(JSParserOptions parserOpt
builder.privateFieldsIn(parserOptions.privateFieldsIn());
builder.topLevelAwait(parserOptions.topLevelAwait());
builder.v8Intrinsics(parserOptions.v8Intrinsics());
builder.extractors(parserOptions.extractors());
if (parserOptions.functionStatementError()) {
builder.functionStatementBehavior(FunctionStatementBehavior.ERROR);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -151,6 +153,7 @@
import com.oracle.truffle.js.nodes.control.SequenceNode;
import com.oracle.truffle.js.nodes.control.StatementNode;
import com.oracle.truffle.js.nodes.control.SuspendNode;
import com.oracle.truffle.js.nodes.extractor.InvokeCustomMatcherOrThrowNode;
import com.oracle.truffle.js.nodes.function.AbstractFunctionArgumentsNode;
import com.oracle.truffle.js.nodes.function.BlockScopeNode;
import com.oracle.truffle.js.nodes.function.EvalNode;
Expand Down Expand Up @@ -2858,8 +2861,7 @@ private JavaScriptNode transformAssignmentImpl(Expression assignmentExpression,
// fall through
case IDENT:
if (lhsExpression instanceof CallNode callNode) {
assert callNode.isWebCompatAssignmentTargetType();
assignedNode = factory.createDual(context, transform(lhsExpression), factory.createThrowError(JSErrorType.ReferenceError, INVALID_LHS));
assignedNode = transformAssignmentExtractor(callNode, assignedValue, initializationAssignment);
} else {
assignedNode = transformAssignmentIdent((IdentNode) lhsExpression, assignedValue, binaryOp, returnOldValue, convertLHSToNumeric, initializationAssignment);
}
Expand Down Expand Up @@ -2907,40 +2909,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<Supplier<JavaScriptNode>, UnaryOperator<JavaScriptNode>> 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<Supplier<JavaScriptNode>, UnaryOperator<JavaScriptNode>> 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;
}
}
}
Expand Down Expand Up @@ -3070,14 +3070,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<Expression> elementExpressions = arrayLiteralNode.getElementExpressions();

return this.transformDestructuringArrayAssignment(elementExpressions, getIterator, valueTempVar, initializationAssignment);
}

private JavaScriptNode transformDestructuringArrayAssignment(List<Expression> elementExpressions, JavaScriptNode getIterator, VarRef valueTempVar, 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++) {
Expand All @@ -3099,7 +3104,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();
}
Expand All @@ -3117,6 +3122,32 @@ private JavaScriptNode transformDestructuringArrayAssignment(Expression lhsExpre
factory.createExprBlock(resetIterator, resetValue));
}

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());
}

// It's important to only read from assignedValue once, otherwise we might advance an iterator multiple times
VarRef assignedValueTempVar = environment.createTempVar();
JavaScriptNode storeAssignedValue = assignedValueTempVar.createWriteNode(assignedValue);
JavaScriptNode readAssignedValue = assignedValueTempVar.createReadNode();

final var invokeCustomMatcherOrThrowNode = factory.createInvokeCustomMatcherOrThrow(context, function, readAssignedValue, receiver, environment.isStrictMode());

final var args = fakeCallNode.getArgs();
VarRef valueTempVar = environment.createTempVar();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

valueTempVar seems to be not assigned here? I presume this (Extractor() = x) should still return x, right? we should also add a test for this.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my understanding is that this is not the case, no. it's just a leftover from when i factored out the destructuring code, will remove it.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nevermind, i initially misunderstood you, yeah i think that this should still behave like a normal assignment in that regard; will also add tests

return createBlock(
storeAssignedValue,
this.transformDestructuringArrayAssignment(args, invokeCustomMatcherOrThrowNode, valueTempVar, initializationAssignment),
valueTempVar.createWriteNode(assignedValueTempVar.createReadNode())
);
}

private JavaScriptNode transformDestructuringObjectAssignment(Expression lhsExpression, JavaScriptNode assignedValue, boolean initializationAssignment) {
ObjectNode objectLiteralNode = (ObjectNode) lhsExpression;
List<PropertyNode> propertyExpressions = objectLiteralNode.getElements();
Expand Down
18 changes: 10 additions & 8 deletions graal-js/src/com.oracle.truffle.js.test/js/assert.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,12 @@ function assertThrows(fn, errorType, msg) {
throw Error('error expected for method: ' + fn);
}

function assertEqual(expected, actual) {
function assertEqual(expected, actual, message) {
if (expected != actual) {
var error = 'Objects not equal - '
+ 'expected: [' + expected + '] vs. '
+ 'actual: [' + actual +']';
+ 'actual: [' + actual +']'
+ (message ? ' : ' + message : '');
throw new Error(error);
}
}
Expand All @@ -43,11 +44,12 @@ function _isSame(a, b) {
return (a !== a) && (b !== b);
}

function assertSame(expected, actual) {
function assertSame(expected, actual, message) {
if (!_isSame(expected, actual)) {
var error = 'Objects not same - '
+ 'expected: [' + expected + '] vs. '
+ 'actual: [' + actual +']';
+ 'actual: [' + actual +']'
+ (message ? ' : ' + message : '');
throw new Error(error);
}
}
Expand All @@ -60,12 +62,12 @@ function assertSameContent(expected, actual) {
}
}

function assertTrue(condition) {
assertSame(true, condition);
function assertTrue(condition, message) {
assertSame(true, condition, message);
}

function assertFalse(condition) {
assertSame(false, condition);
function assertFalse(condition, message) {
assertSame(false, condition, message);
}

function fail(msg) {
Expand Down
Loading