From ab150bd9d10f227536edfe1ed146be6bd89f43cd Mon Sep 17 00:00:00 2001 From: Joe Savona Date: Tue, 3 Jan 2023 16:59:38 -0800 Subject: [PATCH] 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. --- compiler/forget/src/HIR/BuildHIR.ts | 184 +++++++++++++----- .../hir/destructuring-assignment.expect.md | 40 ++++ .../fixtures/hir/destructuring-assignment.js | 17 ++ .../fixtures/hir/destructuring.expect.md | 35 ++++ .../__tests__/fixtures/hir/destructuring.js | 16 ++ 5 files changed, 240 insertions(+), 52 deletions(-) create mode 100644 compiler/forget/src/__tests__/fixtures/hir/destructuring-assignment.expect.md create mode 100644 compiler/forget/src/__tests__/fixtures/hir/destructuring-assignment.js create mode 100644 compiler/forget/src/__tests__/fixtures/hir/destructuring.expect.md create mode 100644 compiler/forget/src/__tests__/fixtures/hir/destructuring.js diff --git a/compiler/forget/src/HIR/BuildHIR.ts b/compiler/forget/src/HIR/BuildHIR.ts index 44565f7ae6..b5cec88baf 100644 --- a/compiler/forget/src/HIR/BuildHIR.ts +++ b/compiler/forget/src/HIR/BuildHIR.ts @@ -623,10 +623,6 @@ function lowerStatement( nodeKind === "let" ? InstructionKind.Let : InstructionKind.Const; for (const declaration of stmt.get("declarations")) { const id = declaration.get("id"); - invariant( - id.isIdentifier(), - "Support non-identifier variable declarations" - ); const init = declaration.get("init"); let value: InstructionValue; if (init.hasNode()) { @@ -942,45 +938,13 @@ function lowerExpression( if (operator === "=") { const left = expr.get("left"); - const leftNode = left.node; - switch (leftNode.type) { - case "Identifier": { - const lvalue = left as NodePath; - return lowerAssignment( - builder, - leftNode.loc ?? GeneratedSource, - InstructionKind.Reassign, - lvalue, - lowerExpression(builder, expr.get("right")) - ); - } - case "MemberExpression": { - const leftExpr = left as NodePath; - const property = leftExpr.get("property"); - invariant( - property.isIdentifier(), - "Assignment expression to dynamic properties is not yet supported" - ); - const right = lowerExpressionToPlace(builder, expr.get("right")); - const object = lowerExpressionToPlace( - builder, - leftExpr.get("object") - ); - return { - kind: "PropertyStore", - object, - property: property.node.name, - value: right, - loc: leftNode.loc ?? GeneratedSource, - }; - } - default: { - todoInvariant( - false, - "Support lvalues other than identifier and member expression" - ); - } - } + return lowerAssignment( + builder, + left.node.loc ?? GeneratedSource, + InstructionKind.Reassign, + left, + lowerExpression(builder, expr.get("right")) + ); } const operators: { [key: string]: t.BinaryExpression["operator"] } = { @@ -1399,15 +1363,131 @@ function lowerAssignment( builder: HIRBuilder, loc: SourceLocation, kind: InstructionKind, - lvalue: NodePath, + lvaluePath: NodePath, value: InstructionValue ): InstructionValue { - const id = lowerIdentifier(builder, lvalue); - builder.push({ - id: makeInstructionId(0), - lvalue: { place: id, kind }, - value, - loc, - }); - return id; + const lvalueNode = lvaluePath.node; + switch (lvalueNode.type) { + case "Identifier": { + const lvalue = lvaluePath as NodePath; + const place = lowerIdentifier(builder, lvalue); + builder.push({ + id: makeInstructionId(0), + lvalue: { place: { ...place }, kind }, + value, + loc, + }); + return place; + } + case "MemberExpression": { + const leftExpr = lvaluePath as NodePath; + const property = leftExpr.get("property"); + invariant( + property.isIdentifier(), + "Assignment expression to dynamic properties is not yet supported" + ); + const object = lowerExpressionToPlace(builder, leftExpr.get("object")); + let valuePlace: Place; + if (value.kind === "Identifier") { + valuePlace = value; + } else { + valuePlace = buildTemporaryPlace(builder, loc); + builder.push({ + id: makeInstructionId(0), + lvalue: { place: { ...valuePlace }, kind: InstructionKind.Const }, + value, + loc, + }); + } + return { + kind: "PropertyStore", + object, + property: property.node.name, + value: valuePlace, + loc, + }; + } + case "ArrayPattern": { + const lvalue = lvaluePath as NodePath; + const arrayPlace = buildTemporaryPlace(builder, loc); + builder.push({ + id: makeInstructionId(0), + lvalue: { place: { ...arrayPlace }, kind: InstructionKind.Const }, + value, + loc, + }); + const elements = lvalue.get("elements"); + for (let i = 0; i < elements.length; i++) { + const element = elements[i]; + if (!element.hasNode()) { + continue; + } + todoInvariant( + element.node.type !== "RestElement", + "Rest elements are not supported yet" + ); + const property = buildTemporaryPlace( + builder, + element.node.loc ?? GeneratedSource + ); + builder.push({ + id: makeInstructionId(0), + lvalue: { place: { ...property }, kind: InstructionKind.Const }, + value: { + kind: "Primitive", + value: i, + loc: element.node.loc ?? GeneratedSource, + }, + loc: element.node.loc ?? GeneratedSource, + }); + const value: InstructionValue = { + kind: "IndexLoad", + loc, + object: { ...arrayPlace }, + property, + }; + lowerAssignment(builder, loc, kind, element, value); + } + return arrayPlace; + } + case "ObjectPattern": { + const lvalue = lvaluePath as NodePath; + const objectPlace = buildTemporaryPlace(builder, loc); + builder.push({ + id: makeInstructionId(0), + lvalue: { place: { ...objectPlace }, kind }, + value, + loc, + }); + const properties = lvalue.get("properties"); + for (let i = 0; i < properties.length; i++) { + const property = properties[i]; + invariant( + property.isObjectProperty(), + "Rest elements are not supported yet" + ); + const key = property.get("key"); + invariant( + key.isIdentifier(), + "TODO: support non-identifier object property keys" + ); + const element = property.get("value"); + invariant( + element.isLVal(), + "Expected object property value to be an lvalue" + ); + const value: InstructionValue = { + kind: "PropertyLoad", + loc, + object: { ...objectPlace }, + property: key.node.name, + }; + lowerAssignment(builder, loc, kind, element, value); + } + return objectPlace; + } + default: { + todo("Support other lvalue types beyond identifier"); + } + } } diff --git a/compiler/forget/src/__tests__/fixtures/hir/destructuring-assignment.expect.md b/compiler/forget/src/__tests__/fixtures/hir/destructuring-assignment.expect.md new file mode 100644 index 0000000000..c24172b80b --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/destructuring-assignment.expect.md @@ -0,0 +1,40 @@ + +## Input + +```javascript +function foo(a, b, c) { + let d, g, n, o; + [ + d, + [ + { + e: { f: g }, + }, + ], + ] = a; + ({ + l: { + m: [[n]], + }, + o, + } = b); +} + +``` + +## Code + +```javascript +function foo(a, b, c) { + const d = undefined; + const g = undefined; + const n = undefined; + const o = undefined; + const d$0 = a[0]; + const g$1 = a[1][0].e.f; + const n$2 = b.l.m[0][0]; + const o$3 = b.o; +} + +``` + \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/hir/destructuring-assignment.js b/compiler/forget/src/__tests__/fixtures/hir/destructuring-assignment.js new file mode 100644 index 0000000000..a228348a64 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/destructuring-assignment.js @@ -0,0 +1,17 @@ +function foo(a, b, c) { + let d, g, n, o; + [ + d, + [ + { + e: { f: g }, + }, + ], + ] = a; + ({ + l: { + m: [[n]], + }, + o, + } = b); +} diff --git a/compiler/forget/src/__tests__/fixtures/hir/destructuring.expect.md b/compiler/forget/src/__tests__/fixtures/hir/destructuring.expect.md new file mode 100644 index 0000000000..c8f7a270c2 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/destructuring.expect.md @@ -0,0 +1,35 @@ + +## Input + +```javascript +function foo(a, b, c) { + const [ + d, + [ + { + e: { f }, + }, + ], + ] = a; + const { + l: { + m: [[n]], + }, + o, + } = b; +} + +``` + +## Code + +```javascript +function foo(a, b, c) { + const d = a[0]; + const f = a[1][0].e.f; + const n = b.l.m[0][0]; + const o = b.o; +} + +``` + \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/hir/destructuring.js b/compiler/forget/src/__tests__/fixtures/hir/destructuring.js new file mode 100644 index 0000000000..72ffef9759 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/destructuring.js @@ -0,0 +1,16 @@ +function foo(a, b, c) { + const [ + d, + [ + { + e: { f }, + }, + ], + ] = a; + const { + l: { + m: [[n]], + }, + o, + } = b; +}