Skip to content

Commit e855928

Browse files
committed
Initial extractors proposal implementation
1 parent 3623bac commit e855928

File tree

16 files changed

+583
-69
lines changed

16 files changed

+583
-69
lines changed

graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/Parser.java

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1046,7 +1046,7 @@ private Expression verifyAssignment(final long op, final Expression lhs, final E
10461046
throw invalidLHSError(lhs);
10471047
}
10481048
break;
1049-
} else if ((opType == ASSIGN || opType == ASSIGN_INIT) && isDestructuringLhs(lhs) && (inPatternPosition || !lhs.isParenthesized())) {
1049+
} else if ((opType == ASSIGN || opType == ASSIGN_INIT) && (isDestructuringLhs(lhs) || isExtractorLhs(lhs)) && (inPatternPosition || !lhs.isParenthesized())) {
10501050
verifyDestructuringAssignmentPattern(lhs, CONTEXT_ASSIGNMENT_TARGET);
10511051
break;
10521052
} else {
@@ -1067,6 +1067,11 @@ private boolean isDestructuringLhs(Expression lhs) {
10671067
return false;
10681068
}
10691069

1070+
private boolean isExtractorLhs(Expression lhs) {
1071+
// todo-lw: call node :(
1072+
return lhs instanceof CallNode;
1073+
}
1074+
10701075
private void verifyDestructuringAssignmentPattern(Expression pattern, String contextString) {
10711076
assert pattern instanceof ObjectNode || pattern instanceof ArrayLiteralNode;
10721077
pattern.accept(new VerifyDestructuringPatternNodeVisitor(new LexicalContext()) {
@@ -1103,6 +1108,26 @@ public boolean enterIndexNode(IndexNode indexNode) {
11031108
return false;
11041109
}
11051110

1111+
@Override
1112+
public boolean enterCallNode(CallNode callNode) {
1113+
if (callNode.isParenthesized()) {
1114+
throw error(AbstractParser.message(MSG_INVALID_LVALUE), callNode.getToken());
1115+
}
1116+
// todo-lw: surely there is a better way to do this
1117+
for (final var arg : callNode.getArgs()) {
1118+
if (arg instanceof IdentNode) {
1119+
enterIdentNode((IdentNode) arg);
1120+
} else if (arg instanceof LiteralNode<?>) {
1121+
enterLiteralNode((LiteralNode<?>) arg);
1122+
} else if (arg instanceof ObjectNode) {
1123+
enterObjectNode((ObjectNode) arg);
1124+
} else {
1125+
enterDefault(arg);
1126+
}
1127+
}
1128+
return false;
1129+
}
1130+
11061131
@Override
11071132
protected boolean enterDefault(Node node) {
11081133
throw error(String.format("unexpected node in AssignmentPattern: %s", node));
@@ -2475,9 +2500,12 @@ private ForVariableDeclarationListResult variableDeclarationList(TokenType varTy
24752500
final int varLine = line;
24762501
final long varToken = Token.recast(token, varType);
24772502

2478-
// Get name of var.
2479-
final Expression binding = bindingIdentifierOrPattern(yield, await, CONTEXT_VARIABLE_NAME);
2480-
final boolean isDestructuring = !(binding instanceof IdentNode);
2503+
// Get left hand side.
2504+
// todo-lw: conditionalExpression feels way too broad here, but binding also uses it so idk
2505+
final Expression binding = conditionalExpression(true, yield, await, CoverExpressionError.DENY);
2506+
2507+
final boolean isExtracting = binding instanceof CallNode;
2508+
final boolean isDestructuring = !(binding instanceof IdentNode) && !isExtracting;
24812509
if (isDestructuring) {
24822510
final int finalVarFlags = varFlags | VarNode.IS_DESTRUCTURING;
24832511
verifyDestructuringBindingPattern(binding, new Consumer<IdentNode>() {
@@ -2525,7 +2553,7 @@ public void accept(IdentNode identNode) {
25252553
// else, if we are in a for loop, delay checking until we know the kind of loop
25262554
}
25272555

2528-
if (!isDestructuring) {
2556+
if (!isDestructuring && !isExtracting) {
25292557
assert init != null || varType != CONST || !isStatement;
25302558
final IdentNode ident = (IdentNode) binding;
25312559
if (varType != VAR && ident.getName().equals(LET.getName())) {
@@ -2835,6 +2863,26 @@ public boolean enterIdentNode(IdentNode identNode) {
28352863
return false;
28362864
}
28372865

2866+
// todo-lw: this is duplicate code
2867+
@Override
2868+
public boolean enterCallNode(CallNode callNode) {
2869+
if (callNode.isParenthesized()) {
2870+
throw error(AbstractParser.message(MSG_INVALID_LVALUE), callNode.getToken());
2871+
}
2872+
for (final var arg : callNode.getArgs()) {
2873+
if (arg instanceof IdentNode) {
2874+
enterIdentNode((IdentNode) arg);
2875+
} else if (arg instanceof LiteralNode<?>) {
2876+
enterLiteralNode((LiteralNode<?>) arg);
2877+
} else if (arg instanceof ObjectNode) {
2878+
enterObjectNode((ObjectNode) arg);
2879+
} else {
2880+
enterDefault(arg);
2881+
}
2882+
}
2883+
return false;
2884+
}
2885+
28382886
@Override
28392887
protected boolean enterDefault(Node node) {
28402888
throw error(String.format("unexpected node in BindingPattern: %s", node));

graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/ir/Node.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ public long getToken() {
254254
return token;
255255
}
256256

257-
// on change, we have to replace the entire list, that's we can't simple do ListIterator.set
257+
// on change, we have to replace the entire list, that's we can't simply do ListIterator.set
258258
static <T extends Node> List<T> accept(final NodeVisitor<? extends LexicalContext> visitor, final List<T> list) {
259259
final int size = list.size();
260260
if (size == 0) {

graal-js/src/com.oracle.truffle.js.parser/src/com/oracle/truffle/js/parser/GraalJSTranslator.java

Lines changed: 68 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,9 @@
119119
import com.oracle.truffle.js.nodes.access.DeclareEvalVariableNode;
120120
import com.oracle.truffle.js.nodes.access.DeclareGlobalNode;
121121
import com.oracle.truffle.js.nodes.access.GetIteratorUnaryNode;
122+
import com.oracle.truffle.js.nodes.access.GetMethodNode;
122123
import com.oracle.truffle.js.nodes.access.GlobalPropertyNode;
124+
import com.oracle.truffle.js.nodes.access.IteratorToArrayNode;
123125
import com.oracle.truffle.js.nodes.access.JSConstantNode;
124126
import com.oracle.truffle.js.nodes.access.JSReadFrameSlotNode;
125127
import com.oracle.truffle.js.nodes.access.JSWriteFrameSlotNode;
@@ -151,6 +153,7 @@
151153
import com.oracle.truffle.js.nodes.control.SequenceNode;
152154
import com.oracle.truffle.js.nodes.control.StatementNode;
153155
import com.oracle.truffle.js.nodes.control.SuspendNode;
156+
import com.oracle.truffle.js.nodes.extractor.InvokeCustomMatcherOrThrowNode;
154157
import com.oracle.truffle.js.nodes.function.AbstractFunctionArgumentsNode;
155158
import com.oracle.truffle.js.nodes.function.BlockScopeNode;
156159
import com.oracle.truffle.js.nodes.function.EvalNode;
@@ -2846,7 +2849,12 @@ private JavaScriptNode transformAssignmentImpl(Expression assignmentExpression,
28462849
}
28472850
// fall through
28482851
case IDENT:
2849-
assignedNode = transformAssignmentIdent((IdentNode) lhsExpression, assignedValue, binaryOp, returnOldValue, convertLHSToNumeric, initializationAssignment);
2852+
// todo-lw: call node :(
2853+
if (lhsExpression instanceof CallNode) {
2854+
assignedNode = transformAssignmentExtractor((CallNode) lhsExpression, assignedValue, binaryOp, returnOldValue, convertLHSToNumeric, initializationAssignment);
2855+
} else {
2856+
assignedNode = transformAssignmentIdent((IdentNode) lhsExpression, assignedValue, binaryOp, returnOldValue, convertLHSToNumeric, initializationAssignment);
2857+
}
28502858
break;
28512859
case LBRACKET:
28522860
// target[element]
@@ -2891,40 +2899,38 @@ private JavaScriptNode transformAssignmentIdent(IdentNode identNode, JavaScriptN
28912899
rhs = checkMutableBinding(rhs, scopeVar.getName());
28922900
}
28932901
return scopeVar.createWriteNode(rhs);
2902+
} else if (isLogicalOp(binaryOp)) {
2903+
assert !convertLHSToNumeric && !returnOldValue && assignedValue != null;
2904+
if (constAssignment) {
2905+
rhs = checkMutableBinding(rhs, scopeVar.getName());
2906+
}
2907+
JavaScriptNode readNode = tagExpression(scopeVar.createReadNode(), identNode);
2908+
JavaScriptNode writeNode = scopeVar.createWriteNode(rhs);
2909+
return factory.createBinary(context, binaryOp, readNode, writeNode);
28942910
} else {
2895-
if (isLogicalOp(binaryOp)) {
2896-
assert !convertLHSToNumeric && !returnOldValue && assignedValue != null;
2897-
if (constAssignment) {
2898-
rhs = checkMutableBinding(rhs, scopeVar.getName());
2899-
}
2900-
JavaScriptNode readNode = tagExpression(scopeVar.createReadNode(), identNode);
2901-
JavaScriptNode writeNode = scopeVar.createWriteNode(rhs);
2902-
return factory.createBinary(context, binaryOp, readNode, writeNode);
2911+
// e.g.: lhs *= rhs => lhs = lhs * rhs
2912+
// If lhs is a side-effecting getter that deletes lhs, we must not throw a
2913+
// ReferenceError at the lhs assignment since the lhs reference is already resolved.
2914+
// We also need to ensure that HasBinding is idempotent or evaluated at most once.
2915+
Pair<Supplier<JavaScriptNode>, UnaryOperator<JavaScriptNode>> pair = scopeVar.createCompoundAssignNode();
2916+
JavaScriptNode readNode = tagExpression(pair.getFirst().get(), identNode);
2917+
if (convertLHSToNumeric) {
2918+
readNode = factory.createToNumericOperand(readNode);
2919+
}
2920+
VarRef prevValueTemp = null;
2921+
if (returnOldValue) {
2922+
prevValueTemp = environment.createTempVar();
2923+
readNode = prevValueTemp.createWriteNode(readNode);
2924+
}
2925+
JavaScriptNode binOpNode = tagExpression(factory.createBinary(context, binaryOp, readNode, rhs), identNode);
2926+
if (constAssignment) {
2927+
binOpNode = checkMutableBinding(binOpNode, scopeVar.getName());
2928+
}
2929+
JavaScriptNode writeNode = pair.getSecond().apply(binOpNode);
2930+
if (returnOldValue) {
2931+
return factory.createDual(context, writeNode, prevValueTemp.createReadNode());
29032932
} else {
2904-
// e.g.: lhs *= rhs => lhs = lhs * rhs
2905-
// If lhs is a side-effecting getter that deletes lhs, we must not throw a
2906-
// ReferenceError at the lhs assignment since the lhs reference is already resolved.
2907-
// We also need to ensure that HasBinding is idempotent or evaluated at most once.
2908-
Pair<Supplier<JavaScriptNode>, UnaryOperator<JavaScriptNode>> pair = scopeVar.createCompoundAssignNode();
2909-
JavaScriptNode readNode = tagExpression(pair.getFirst().get(), identNode);
2910-
if (convertLHSToNumeric) {
2911-
readNode = factory.createToNumericOperand(readNode);
2912-
}
2913-
VarRef prevValueTemp = null;
2914-
if (returnOldValue) {
2915-
prevValueTemp = environment.createTempVar();
2916-
readNode = prevValueTemp.createWriteNode(readNode);
2917-
}
2918-
JavaScriptNode binOpNode = tagExpression(factory.createBinary(context, binaryOp, readNode, rhs), identNode);
2919-
if (constAssignment) {
2920-
binOpNode = checkMutableBinding(binOpNode, scopeVar.getName());
2921-
}
2922-
JavaScriptNode writeNode = pair.getSecond().apply(binOpNode);
2923-
if (returnOldValue) {
2924-
return factory.createDual(context, writeNode, prevValueTemp.createReadNode());
2925-
} else {
2926-
return writeNode;
2927-
}
2933+
return writeNode;
29282934
}
29292935
}
29302936
}
@@ -3054,14 +3060,19 @@ private JavaScriptNode transformIndexAssignment(IndexNode indexNode, JavaScriptN
30543060
}
30553061

30563062
private JavaScriptNode transformDestructuringArrayAssignment(Expression lhsExpression, JavaScriptNode assignedValue, boolean initializationAssignment) {
3063+
VarRef valueTempVar = environment.createTempVar();
3064+
JavaScriptNode initValue = valueTempVar.createWriteNode(assignedValue);
3065+
JavaScriptNode getIterator = factory.createGetIterator(initValue);
30573066
LiteralNode.ArrayLiteralNode arrayLiteralNode = (LiteralNode.ArrayLiteralNode) lhsExpression;
30583067
List<Expression> elementExpressions = arrayLiteralNode.getElementExpressions();
3068+
3069+
return this.transformDestructuringArrayAssignment(elementExpressions, getIterator, valueTempVar, initializationAssignment);
3070+
}
3071+
3072+
private JavaScriptNode transformDestructuringArrayAssignment(List<Expression> elementExpressions, JavaScriptNode getIterator, VarRef valueTempVar, boolean initializationAssignment) {
30593073
JavaScriptNode[] initElements = javaScriptNodeArray(elementExpressions.size());
30603074
VarRef iteratorTempVar = environment.createTempVar();
3061-
VarRef valueTempVar = environment.createTempVar();
3062-
JavaScriptNode initValue = valueTempVar.createWriteNode(assignedValue);
30633075
// By default, we use the hint to track the type of iterator.
3064-
JavaScriptNode getIterator = factory.createGetIterator(initValue);
30653076
JavaScriptNode initIteratorTempVar = iteratorTempVar.createWriteNode(getIterator);
30663077

30673078
for (int i = 0; i < elementExpressions.size(); i++) {
@@ -3083,7 +3094,8 @@ private JavaScriptNode transformDestructuringArrayAssignment(Expression lhsExpre
30833094
if (init != null) {
30843095
rhsNode = factory.createNotUndefinedOr(rhsNode, transform(init));
30853096
}
3086-
if (lhsExpr != null && lhsExpr.isTokenType(TokenType.SPREAD_ARRAY)) {
3097+
// todo-lw: this change is kind of sus
3098+
if (lhsExpr != null && (lhsExpr.isTokenType(TokenType.SPREAD_ARRAY) || lhsExpr.isTokenType(TokenType.SPREAD_ARGUMENT))) {
30873099
rhsNode = factory.createIteratorToArray(context, iteratorTempVar.createReadNode());
30883100
lhsExpr = ((UnaryNode) lhsExpr).getExpression();
30893101
}
@@ -3097,6 +3109,25 @@ private JavaScriptNode transformDestructuringArrayAssignment(Expression lhsExpre
30973109
return factory.createExprBlock(initIteratorTempVar, closeIfNotDone, valueTempVar.createReadNode());
30983110
}
30993111

3112+
private JavaScriptNode transformAssignmentExtractor(CallNode fakeCallNode, JavaScriptNode assignedValue, BinaryOperation binaryOp, boolean returnOldValue, boolean convertToNumeric, boolean initializationAssignment) {
3113+
// todo-lw: call node :(
3114+
3115+
final var functionExpr = fakeCallNode.getFunction();
3116+
final var function = transform(functionExpr);
3117+
3118+
var receiver = function;
3119+
if (functionExpr instanceof AccessNode) {
3120+
final AccessNode accessNode = (AccessNode) functionExpr;
3121+
receiver = transform(accessNode.getBase());
3122+
}
3123+
3124+
final var invokeCustomMatcherOrThrowNode = InvokeCustomMatcherOrThrowNode.create(context, function, assignedValue, receiver);
3125+
3126+
final var args = fakeCallNode.getArgs();
3127+
VarRef valueTempVar = environment.createTempVar();
3128+
return this.transformDestructuringArrayAssignment(args, invokeCustomMatcherOrThrowNode, valueTempVar, initializationAssignment);
3129+
}
3130+
31003131
private JavaScriptNode transformDestructuringObjectAssignment(Expression lhsExpression, JavaScriptNode assignedValue, boolean initializationAssignment) {
31013132
ObjectNode objectLiteralNode = (ObjectNode) lhsExpression;
31023133
List<PropertyNode> propertyExpressions = objectLiteralNode.getElements();
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
load('../assert.js');
2+
3+
const DateExtractor = {
4+
[Symbol.customMatcher](value) {
5+
if (value instanceof Date) {
6+
return [value];
7+
} else if (typeof value === "number") {
8+
return [new Date(value)];
9+
} else if (typeof value === "string") {
10+
return [Date.parse(value)];
11+
}
12+
}
13+
};
14+
15+
class Book {
16+
constructor({
17+
isbn,
18+
title,
19+
// Extract `createdAt` as an Instant
20+
createdAt: DateExtractor(createdAt) = Date.now(),
21+
modifiedAt: DateExtractor(modifiedAt) = createdAt
22+
}) {
23+
this.isbn = isbn;
24+
this.title = title;
25+
this.createdAt = createdAt;
26+
this.modifiedAt = modifiedAt;
27+
}
28+
}
29+
30+
{
31+
const date = Date.parse("1970-01-01T00:00:00Z")
32+
const book = new Book({ isbn: "...", title: "...", createdAt: date });
33+
assertSame(date.valueOf(), book.createdAt.valueOf());
34+
}
35+
36+
{
37+
const msSinceEpoch = 1000;
38+
const book = new Book({ isbn: "...", title: "...", createdAt: msSinceEpoch });
39+
assertSame(msSinceEpoch, book.createdAt.valueOf());
40+
}
41+
42+
{
43+
const createdAt = "1970-01-01T00Z";
44+
const book = new Book({ isbn: "...", title: "...", createdAt });
45+
assertSame(Date.parse(createdAt).valueOf(), book.createdAt.valueOf());
46+
}

0 commit comments

Comments
 (0)