Object and array destructuring support in declaration/assignment
Implements support for array and object de-structuring in variable declarations and assignment expressions. Note that the code currently makes the overly optimistic assumption that the RHS is an array or object that can be safely indexed into. The correct representation would instead treat the RHS as possibly iterable, but we need to consider the appropriate representation. I think it's worth landing a first optimistic pass and we can iterate forward, this helps make it more clear what the ideal representation would have to be and should make a bunch of examples work. It also allows us to experiment with representations of, and handling for, scope dependencies that involve computed property access.
Joe Savona committed
Jan 3, 2023 at 16:59 UTC
ab150bd9d10f227536edfe1ed146be6bd89f43cd
5 files changed
+240
-52
compiler/forget/src/HIR/BuildHIR.ts
+132
-52
@@ -623,10 +623,6 @@ function lowerStatement(
623
nodeKind === "let" ? InstructionKind.Let : InstructionKind.Const;
624
for (const declaration of stmt.get("declarations")) {
625
const id = declaration.get("id");
626
- invariant(
627
- id.isIdentifier(),
628
- "Support non-identifier variable declarations"
629
- );
626
const init = declaration.get("init");
627
let value: InstructionValue;
628
if (init.hasNode()) {
@@ -942,45 +938,13 @@ function lowerExpression(
938
939
if (operator === "=") {
940
const left = expr.get("left");
945
- const leftNode = left.node;
946
- switch (leftNode.type) {
947
- case "Identifier": {
948
- const lvalue = left as NodePath<t.Identifier>;
949
- return lowerAssignment(
950
- builder,
951
- leftNode.loc ?? GeneratedSource,
952
- InstructionKind.Reassign,
953
- lvalue,
954
- lowerExpression(builder, expr.get("right"))
955
- );
956
- }
957
- case "MemberExpression": {
958
- const leftExpr = left as NodePath<t.MemberExpression>;
959
- const property = leftExpr.get("property");
960
- invariant(
961
- property.isIdentifier(),
962
- "Assignment expression to dynamic properties is not yet supported"
963
- );
964
- const right = lowerExpressionToPlace(builder, expr.get("right"));
965
- const object = lowerExpressionToPlace(
966
- builder,
967
- leftExpr.get("object")
968
- );
969
- return {
970
- kind: "PropertyStore",
971
- object,
972
- property: property.node.name,
973
- value: right,
974
- loc: leftNode.loc ?? GeneratedSource,
975
- };
976
- }
977
- default: {
978
- todoInvariant(
979
- false,
980
- "Support lvalues other than identifier and member expression"
981
- );
982
- }
983
- }
941
+ return lowerAssignment(
942
+ builder,
943
+ left.node.loc ?? GeneratedSource,
944
+ InstructionKind.Reassign,
945
+ left,
946
+ lowerExpression(builder, expr.get("right"))
947
+ );
948
}
949
950
const operators: { [key: string]: t.BinaryExpression["operator"] } = {
@@ -1399,15 +1363,131 @@ function lowerAssignment(
1363
builder: HIRBuilder,
1364
loc: SourceLocation,
1365
kind: InstructionKind,
1402
- lvalue: NodePath<t.Identifier>,
1366
+ lvaluePath: NodePath<t.LVal>,
1367
value: InstructionValue
1368
): InstructionValue {
1405
- const id = lowerIdentifier(builder, lvalue);
1406
- builder.push({
1407
- id: makeInstructionId(0),
1408
- lvalue: { place: id, kind },
1409
- value,
1410
- loc,
1411
- });
1412
- return id;
1369
+ const lvalueNode = lvaluePath.node;
1370
+ switch (lvalueNode.type) {
1371
+ case "Identifier": {
1372
+ const lvalue = lvaluePath as NodePath<t.Identifier>;
1373
+ const place = lowerIdentifier(builder, lvalue);
1374
+ builder.push({
1375
+ id: makeInstructionId(0),
1376
+ lvalue: { place: { ...place }, kind },
1377
+ value,
1378
+ loc,
1379
+ });
1380
+ return place;
1381
+ }
1382
+ case "MemberExpression": {
1383
+ const leftExpr = lvaluePath as NodePath<t.MemberExpression>;
1384
+ const property = leftExpr.get("property");
1385
+ invariant(
1386
+ property.isIdentifier(),
1387
+ "Assignment expression to dynamic properties is not yet supported"
1388
+ );
1389
+ const object = lowerExpressionToPlace(builder, leftExpr.get("object"));
1390
+ let valuePlace: Place;
1391
+ if (value.kind === "Identifier") {
1392
+ valuePlace = value;
1393
+ } else {
1394
+ valuePlace = buildTemporaryPlace(builder, loc);
1395
+ builder.push({
1396
+ id: makeInstructionId(0),
1397
+ lvalue: { place: { ...valuePlace }, kind: InstructionKind.Const },
1398
+ value,
1399
+ loc,
1400
+ });
1401
+ }
1402
+ return {
1403
+ kind: "PropertyStore",
1404
+ object,
1405
+ property: property.node.name,
1406
+ value: valuePlace,
1407
+ loc,
1408
+ };
1409
+ }
1410
+ case "ArrayPattern": {
1411
+ const lvalue = lvaluePath as NodePath<t.ArrayPattern>;
1412
+ const arrayPlace = buildTemporaryPlace(builder, loc);
1413
+ builder.push({
1414
+ id: makeInstructionId(0),
1415
+ lvalue: { place: { ...arrayPlace }, kind: InstructionKind.Const },
1416
+ value,
1417
+ loc,
1418
+ });
1419
+ const elements = lvalue.get("elements");
1420
+ for (let i = 0; i < elements.length; i++) {
1421
+ const element = elements[i];
1422
+ if (!element.hasNode()) {
1423
+ continue;
1424
+ }
1425
+ todoInvariant(
1426
+ element.node.type !== "RestElement",
1427
+ "Rest elements are not supported yet"
1428
+ );
1429
+ const property = buildTemporaryPlace(
1430
+ builder,
1431
+ element.node.loc ?? GeneratedSource
1432
+ );
1433
+ builder.push({
1434
+ id: makeInstructionId(0),
1435
+ lvalue: { place: { ...property }, kind: InstructionKind.Const },
1436
+ value: {
1437
+ kind: "Primitive",
1438
+ value: i,
1439
+ loc: element.node.loc ?? GeneratedSource,
1440
+ },
1441
+ loc: element.node.loc ?? GeneratedSource,
1442
+ });
1443
+ const value: InstructionValue = {
1444
+ kind: "IndexLoad",
1445
+ loc,
1446
+ object: { ...arrayPlace },
1447
+ property,
1448
+ };
1449
+ lowerAssignment(builder, loc, kind, element, value);
1450
+ }
1451
+ return arrayPlace;
1452
+ }
1453
+ case "ObjectPattern": {
1454
+ const lvalue = lvaluePath as NodePath<t.ObjectPattern>;
1455
+ const objectPlace = buildTemporaryPlace(builder, loc);
1456
+ builder.push({
1457
+ id: makeInstructionId(0),
1458
+ lvalue: { place: { ...objectPlace }, kind },
1459
+ value,
1460
+ loc,
1461
+ });
1462
+ const properties = lvalue.get("properties");
1463
+ for (let i = 0; i < properties.length; i++) {
1464
+ const property = properties[i];
1465
+ invariant(
1466
+ property.isObjectProperty(),
1467
+ "Rest elements are not supported yet"
1468
+ );
1469
+ const key = property.get("key");
1470
+ invariant(
1471
+ key.isIdentifier(),
1472
+ "TODO: support non-identifier object property keys"
1473
+ );
1474
+ const element = property.get("value");
1475
+ invariant(
1476
+ element.isLVal(),
1477
+ "Expected object property value to be an lvalue"
1478
+ );
1479
+ const value: InstructionValue = {
1480
+ kind: "PropertyLoad",
1481
+ loc,
1482
+ object: { ...objectPlace },
1483
+ property: key.node.name,
1484
+ };
1485
+ lowerAssignment(builder, loc, kind, element, value);
1486
+ }
1487
+ return objectPlace;
1488
+ }
1489
+ default: {
1490
+ todo("Support other lvalue types beyond identifier");
1491
+ }
1492
+ }
1493
}
compiler/forget/src/__tests__/fixtures/hir/destructuring-assignment.expect.md
new
+40
@@ -0,0 +1,40 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+function foo(a, b, c) {
6
+ let d, g, n, o;
7
+ [
8
+ d,
9
+ [
10
+ {
11
+ e: { f: g },
12
+ },
13
+ ],
14
+ ] = a;
15
+ ({
16
+ l: {
17
+ m: [[n]],
18
+ },
19
+ o,
20
+ } = b);
21
+}
22
+
23
+```
24
+
25
+## Code
26
+
27
+```javascript
28
+function foo(a, b, c) {
29
+ const d = undefined;
30
+ const g = undefined;
31
+ const n = undefined;
32
+ const o = undefined;
33
+ const d$0 = a[0];
34
+ const g$1 = a[1][0].e.f;
35
+ const n$2 = b.l.m[0][0];
36
+ const o$3 = b.o;
37
+}
38
+
39
+```
40
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/destructuring-assignment.js
new
+17
@@ -0,0 +1,17 @@
1
+function foo(a, b, c) {
2
+ let d, g, n, o;
3
+ [
4
+ d,
5
+ [
6
+ {
7
+ e: { f: g },
8
+ },
9
+ ],
10
+ ] = a;
11
+ ({
12
+ l: {
13
+ m: [[n]],
14
+ },
15
+ o,
16
+ } = b);
17
+}
compiler/forget/src/__tests__/fixtures/hir/destructuring.expect.md
new
+35
@@ -0,0 +1,35 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+function foo(a, b, c) {
6
+ const [
7
+ d,
8
+ [
9
+ {
10
+ e: { f },
11
+ },
12
+ ],
13
+ ] = a;
14
+ const {
15
+ l: {
16
+ m: [[n]],
17
+ },
18
+ o,
19
+ } = b;
20
+}
21
+
22
+```
23
+
24
+## Code
25
+
26
+```javascript
27
+function foo(a, b, c) {
28
+ const d = a[0];
29
+ const f = a[1][0].e.f;
30
+ const n = b.l.m[0][0];
31
+ const o = b.o;
32
+}
33
+
34
+```
35
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/destructuring.js
new
+16
@@ -0,0 +1,16 @@
1
+function foo(a, b, c) {
2
+ const [
3
+ d,
4
+ [
5
+ {
6
+ e: { f },
7
+ },
8
+ ],
9
+ ] = a;
10
+ const {
11
+ l: {
12
+ m: [[n]],
13
+ },
14
+ o,
15
+ } = b;
16
+}