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
118 changes: 66 additions & 52 deletions graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/Parser.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand All @@ -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) {
Expand Down Expand Up @@ -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;
Copy link
Member

Choose a reason for hiding this comment

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

isExtracting seems unnecessary, and extractors may be nested inside

Copy link
Author

Choose a reason for hiding this comment

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

do you have a proposed change here? not sure why it seems unnecessary, and don't know why extractors being nested is relevant

Copy link
Member

Choose a reason for hiding this comment

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

Maybe I'm missing something but it seems you could just use isDestructuring and handle extractors in the same code paths, couldn't you?

why extractors being nested is relevant

Unless isExtracting is really supposed to be shallow, it won't be true for extractors nested inside a destructuring pattern. I just don't understand why you'd want such a check?

Copy link
Author

@fs-c fs-c Oct 12, 2025

Choose a reason for hiding this comment

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

can you give an example that you feel won't work? "extractors nested inside a destructuring pattern" sounds like

const subject = new C(new D("data"));
const { a: { b: C(D(z)) } } = { a: { b: subject } }

to me, and that works with the current implementation

assert !isExtracting || ESNEXT_EXTRACTORS;

final boolean isDestructuring = !(binding instanceof IdentNode) && !isExtracting;
if (isDestructuring) {
final int finalVarFlags = varFlags | VarNode.IS_DESTRUCTURING;
verifyDestructuringBindingPattern(binding, new Consumer<IdentNode>() {
Expand Down Expand Up @@ -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())) {
Expand Down Expand Up @@ -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);
}
Expand All @@ -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
Expand All @@ -2806,20 +2787,53 @@ 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<? extends Node> 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;
}
}

/**
* Verify destructuring variable declaration binding pattern and extract bound variable
* declarations.
*/
private void verifyDestructuringBindingPattern(Expression pattern, Consumer<IdentNode> 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());
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -5081,7 +5095,7 @@ private Expression memberExpression(boolean yield, boolean await, CoverExpressio
* This helps report the first error location for cases like: <code>({x=i}[{y=j}])</code>.
*/
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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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<Expression> params = new ArrayList<>();
Expand Down Expand Up @@ -6637,15 +6651,15 @@ 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);

addDestructuringParameter(paramToken, param.getFinish(), paramLine, lhs, initializer, currentFunction, false);
} else {
throw error(AbstractParser.message(MSG_INVALID_ARROW_PARAMETER), paramToken);
}
} else if (isDestructuringLhs(param)) {
} else if (isDestructuringOrExtractorLhs(param)) {
// binding pattern
long paramToken = param.getToken();

Expand All @@ -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 {
Expand Down
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
Loading