Skip to content

Commit 9c74cc6

Browse files
committed
Initial extractors proposal implementation
1 parent 8f53dab commit 9c74cc6

File tree

15 files changed

+567
-42
lines changed

15 files changed

+567
-42
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 if (!(isWebCompatAssignmentTargetType(lhs) && (opType == ASSIGN || opType.isAssignmentOperator()))) {
@@ -1071,6 +1071,11 @@ private boolean isDestructuringLhs(Expression lhs) {
10711071
return false;
10721072
}
10731073

1074+
private boolean isExtractorLhs(Expression lhs) {
1075+
// todo-lw: call node :(
1076+
return lhs instanceof CallNode;
1077+
}
1078+
10741079
private void verifyDestructuringAssignmentPattern(Expression pattern, String contextString) {
10751080
assert pattern instanceof ObjectNode || pattern instanceof ArrayLiteralNode;
10761081
pattern.accept(new VerifyDestructuringPatternNodeVisitor(new LexicalContext()) {
@@ -1107,6 +1112,26 @@ public boolean enterIndexNode(IndexNode indexNode) {
11071112
return false;
11081113
}
11091114

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

2482-
// Get name of var.
2483-
final Expression binding = bindingIdentifierOrPattern(yield, await, CONTEXT_VARIABLE_NAME);
2484-
final boolean isDestructuring = !(binding instanceof IdentNode);
2507+
// Get left hand side.
2508+
// todo-lw: conditionalExpression feels way too broad here, but binding also uses it so idk
2509+
final Expression binding = conditionalExpression(true, yield, await, CoverExpressionError.DENY);
2510+
2511+
final boolean isExtracting = binding instanceof CallNode;
2512+
final boolean isDestructuring = !(binding instanceof IdentNode) && !isExtracting;
24852513
if (isDestructuring) {
24862514
final int finalVarFlags = varFlags | VarNode.IS_DESTRUCTURING;
24872515
verifyDestructuringBindingPattern(binding, new Consumer<IdentNode>() {
@@ -2529,7 +2557,7 @@ public void accept(IdentNode identNode) {
25292557
// else, if we are in a for loop, delay checking until we know the kind of loop
25302558
}
25312559

2532-
if (!isDestructuring) {
2560+
if (!isDestructuring && !isExtracting) {
25332561
assert init != null || varType != CONST || !isStatement;
25342562
final IdentNode ident = (IdentNode) binding;
25352563
if (varType != VAR && ident.getName().equals(LET.getName())) {
@@ -2839,6 +2867,26 @@ public boolean enterIdentNode(IdentNode identNode) {
28392867
return false;
28402868
}
28412869

2870+
// todo-lw: this is duplicate code
2871+
@Override
2872+
public boolean enterCallNode(CallNode callNode) {
2873+
if (callNode.isParenthesized()) {
2874+
throw error(AbstractParser.message(MSG_INVALID_LVALUE), callNode.getToken());
2875+
}
2876+
for (final var arg : callNode.getArgs()) {
2877+
if (arg instanceof IdentNode) {
2878+
enterIdentNode((IdentNode) arg);
2879+
} else if (arg instanceof LiteralNode<?>) {
2880+
enterLiteralNode((LiteralNode<?>) arg);
2881+
} else if (arg instanceof ObjectNode) {
2882+
enterObjectNode((ObjectNode) arg);
2883+
} else {
2884+
enterDefault(arg);
2885+
}
2886+
}
2887+
return false;
2888+
}
2889+
28422890
@Override
28432891
protected boolean enterDefault(Node node) {
28442892
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: 64 additions & 36 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;
@@ -2860,6 +2863,8 @@ private JavaScriptNode transformAssignmentImpl(Expression assignmentExpression,
28602863
if (lhsExpression instanceof CallNode callNode) {
28612864
assert callNode.isWebCompatAssignmentTargetType();
28622865
assignedNode = factory.createDual(context, transform(lhsExpression), factory.createThrowError(JSErrorType.ReferenceError, INVALID_LHS));
2866+
// todo-lw: merge
2867+
// assignedNode = transformAssignmentExtractor((CallNode) lhsExpression, assignedValue, binaryOp, returnOldValue, convertLHSToNumeric, initializationAssignment);
28632868
} else {
28642869
assignedNode = transformAssignmentIdent((IdentNode) lhsExpression, assignedValue, binaryOp, returnOldValue, convertLHSToNumeric, initializationAssignment);
28652870
}
@@ -2907,40 +2912,38 @@ private JavaScriptNode transformAssignmentIdent(IdentNode identNode, JavaScriptN
29072912
rhs = checkMutableBinding(rhs, scopeVar.getName());
29082913
}
29092914
return scopeVar.createWriteNode(rhs);
2915+
} else if (isLogicalOp(binaryOp)) {
2916+
assert !convertLHSToNumeric && !returnOldValue && assignedValue != null;
2917+
if (constAssignment) {
2918+
rhs = checkMutableBinding(rhs, scopeVar.getName());
2919+
}
2920+
JavaScriptNode readNode = tagExpression(scopeVar.createReadNode(), identNode);
2921+
JavaScriptNode writeNode = scopeVar.createWriteNode(rhs);
2922+
return factory.createBinary(context, binaryOp, readNode, writeNode);
29102923
} else {
2911-
if (isLogicalOp(binaryOp)) {
2912-
assert !convertLHSToNumeric && !returnOldValue && assignedValue != null;
2913-
if (constAssignment) {
2914-
rhs = checkMutableBinding(rhs, scopeVar.getName());
2915-
}
2916-
JavaScriptNode readNode = tagExpression(scopeVar.createReadNode(), identNode);
2917-
JavaScriptNode writeNode = scopeVar.createWriteNode(rhs);
2918-
return factory.createBinary(context, binaryOp, readNode, writeNode);
2924+
// e.g.: lhs *= rhs => lhs = lhs * rhs
2925+
// If lhs is a side-effecting getter that deletes lhs, we must not throw a
2926+
// ReferenceError at the lhs assignment since the lhs reference is already resolved.
2927+
// We also need to ensure that HasBinding is idempotent or evaluated at most once.
2928+
Pair<Supplier<JavaScriptNode>, UnaryOperator<JavaScriptNode>> pair = scopeVar.createCompoundAssignNode();
2929+
JavaScriptNode readNode = tagExpression(pair.getFirst().get(), identNode);
2930+
if (convertLHSToNumeric) {
2931+
readNode = factory.createToNumericOperand(readNode);
2932+
}
2933+
VarRef prevValueTemp = null;
2934+
if (returnOldValue) {
2935+
prevValueTemp = environment.createTempVar();
2936+
readNode = prevValueTemp.createWriteNode(readNode);
2937+
}
2938+
JavaScriptNode binOpNode = tagExpression(factory.createBinary(context, binaryOp, readNode, rhs), identNode);
2939+
if (constAssignment) {
2940+
binOpNode = checkMutableBinding(binOpNode, scopeVar.getName());
2941+
}
2942+
JavaScriptNode writeNode = pair.getSecond().apply(binOpNode);
2943+
if (returnOldValue) {
2944+
return factory.createDual(context, writeNode, prevValueTemp.createReadNode());
29192945
} else {
2920-
// e.g.: lhs *= rhs => lhs = lhs * rhs
2921-
// If lhs is a side-effecting getter that deletes lhs, we must not throw a
2922-
// ReferenceError at the lhs assignment since the lhs reference is already resolved.
2923-
// We also need to ensure that HasBinding is idempotent or evaluated at most once.
2924-
Pair<Supplier<JavaScriptNode>, UnaryOperator<JavaScriptNode>> pair = scopeVar.createCompoundAssignNode();
2925-
JavaScriptNode readNode = tagExpression(pair.getFirst().get(), identNode);
2926-
if (convertLHSToNumeric) {
2927-
readNode = factory.createToNumericOperand(readNode);
2928-
}
2929-
VarRef prevValueTemp = null;
2930-
if (returnOldValue) {
2931-
prevValueTemp = environment.createTempVar();
2932-
readNode = prevValueTemp.createWriteNode(readNode);
2933-
}
2934-
JavaScriptNode binOpNode = tagExpression(factory.createBinary(context, binaryOp, readNode, rhs), identNode);
2935-
if (constAssignment) {
2936-
binOpNode = checkMutableBinding(binOpNode, scopeVar.getName());
2937-
}
2938-
JavaScriptNode writeNode = pair.getSecond().apply(binOpNode);
2939-
if (returnOldValue) {
2940-
return factory.createDual(context, writeNode, prevValueTemp.createReadNode());
2941-
} else {
2942-
return writeNode;
2943-
}
2946+
return writeNode;
29442947
}
29452948
}
29462949
}
@@ -3070,14 +3073,19 @@ private JavaScriptNode transformIndexAssignment(IndexNode indexNode, JavaScriptN
30703073
}
30713074

30723075
private JavaScriptNode transformDestructuringArrayAssignment(Expression lhsExpression, JavaScriptNode assignedValue, boolean initializationAssignment) {
3076+
VarRef valueTempVar = environment.createTempVar();
3077+
JavaScriptNode initValue = valueTempVar.createWriteNode(assignedValue);
3078+
JavaScriptNode getIterator = factory.createGetIterator(initValue);
30733079
LiteralNode.ArrayLiteralNode arrayLiteralNode = (LiteralNode.ArrayLiteralNode) lhsExpression;
30743080
List<Expression> elementExpressions = arrayLiteralNode.getElementExpressions();
3081+
3082+
return this.transformDestructuringArrayAssignment(elementExpressions, getIterator, valueTempVar, initializationAssignment);
3083+
}
3084+
3085+
private JavaScriptNode transformDestructuringArrayAssignment(List<Expression> elementExpressions, JavaScriptNode getIterator, VarRef valueTempVar, boolean initializationAssignment) {
30753086
JavaScriptNode[] initElements = javaScriptNodeArray(elementExpressions.size());
30763087
VarRef iteratorTempVar = environment.createTempVar();
3077-
VarRef valueTempVar = environment.createTempVar();
3078-
JavaScriptNode initValue = valueTempVar.createWriteNode(assignedValue);
30793088
// By default, we use the hint to track the type of iterator.
3080-
JavaScriptNode getIterator = factory.createGetIterator(initValue);
30813089
JavaScriptNode initIteratorTempVar = iteratorTempVar.createWriteNode(getIterator);
30823090

30833091
for (int i = 0; i < elementExpressions.size(); i++) {
@@ -3099,7 +3107,8 @@ private JavaScriptNode transformDestructuringArrayAssignment(Expression lhsExpre
30993107
if (init != null) {
31003108
rhsNode = factory.createNotUndefinedOr(rhsNode, transform(init));
31013109
}
3102-
if (lhsExpr != null && lhsExpr.isTokenType(TokenType.SPREAD_ARRAY)) {
3110+
// todo-lw: this change is kind of sus
3111+
if (lhsExpr != null && (lhsExpr.isTokenType(TokenType.SPREAD_ARRAY) || lhsExpr.isTokenType(TokenType.SPREAD_ARGUMENT))) {
31033112
rhsNode = factory.createIteratorToArray(context, iteratorTempVar.createReadNode());
31043113
lhsExpr = ((UnaryNode) lhsExpr).getExpression();
31053114
}
@@ -3117,6 +3126,25 @@ private JavaScriptNode transformDestructuringArrayAssignment(Expression lhsExpre
31173126
factory.createExprBlock(resetIterator, resetValue));
31183127
}
31193128

3129+
private JavaScriptNode transformAssignmentExtractor(CallNode fakeCallNode, JavaScriptNode assignedValue, BinaryOperation binaryOp, boolean returnOldValue, boolean convertToNumeric, boolean initializationAssignment) {
3130+
// todo-lw: call node :(
3131+
3132+
final var functionExpr = fakeCallNode.getFunction();
3133+
final var function = transform(functionExpr);
3134+
3135+
var receiver = function;
3136+
if (functionExpr instanceof AccessNode) {
3137+
final AccessNode accessNode = (AccessNode) functionExpr;
3138+
receiver = transform(accessNode.getBase());
3139+
}
3140+
3141+
final var invokeCustomMatcherOrThrowNode = InvokeCustomMatcherOrThrowNode.create(context, function, assignedValue, receiver);
3142+
3143+
final var args = fakeCallNode.getArgs();
3144+
VarRef valueTempVar = environment.createTempVar();
3145+
return this.transformDestructuringArrayAssignment(args, invokeCustomMatcherOrThrowNode, valueTempVar, initializationAssignment);
3146+
}
3147+
31203148
private JavaScriptNode transformDestructuringObjectAssignment(Expression lhsExpression, JavaScriptNode assignedValue, boolean initializationAssignment) {
31213149
ObjectNode objectLiteralNode = (ObjectNode) lhsExpression;
31223150
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+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
load('../assert.js');
2+
3+
{
4+
class C {
5+
#data;
6+
7+
constructor(data) {
8+
this.#data = data;
9+
}
10+
11+
static [Symbol.customMatcher](subject) {
12+
return #data in subject && [subject.#data];
13+
}
14+
}
15+
16+
const subject = new C("data");
17+
18+
let x;
19+
C(x) = subject;
20+
21+
assertSame(x, "data");
22+
}
23+
24+
{
25+
class C {
26+
#data;
27+
constructor(data) {
28+
this.#data = data;
29+
}
30+
static [Symbol.customMatcher](subject) {
31+
return #data in subject && [subject.#data];
32+
}
33+
}
34+
35+
const subject = new C({ x: 1, y: 2 });
36+
37+
let x, y;
38+
C({ x, y }) = subject;
39+
40+
assertSame(x, 1);
41+
assertSame(y, 2);
42+
}
43+
44+
{
45+
class C {
46+
#first;
47+
#second;
48+
constructor(first, second) {
49+
this.#first = first;
50+
this.#second = second;
51+
}
52+
static [Symbol.customMatcher](subject) {
53+
return #first in subject && [subject.#first, subject.#second];
54+
}
55+
}
56+
57+
const subject = new C(undefined, 2);
58+
59+
const C(x = -1, y) = subject;
60+
assertSame(x, -1);
61+
assertSame(y, 2);
62+
}
63+
64+
{
65+
const [a, ...b] = [1, 2, 3];
66+
67+
class C {
68+
#first;
69+
#second;
70+
#third;
71+
constructor(first, second, third) {
72+
this.#first = first;
73+
this.#second = second;
74+
this.#third = third;
75+
}
76+
static [Symbol.customMatcher](subject) {
77+
return #first in subject && [subject.#first, subject.#second, subject.#third];
78+
}
79+
}
80+
81+
const subject = new C(1, 2, 3);
82+
83+
const C(x, ...y) = subject;
84+
assertSame(x, 1);
85+
assertSameContent(y, [2, 3]);
86+
}

0 commit comments

Comments
 (0)