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.
This commit is contained in:
Joe Savona
2023-01-03 16:59:38 -08:00
parent 45b3bd4899
commit ab150bd9d1
5 changed files with 240 additions and 52 deletions
+132 -52
View File
@@ -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<t.Identifier>;
return lowerAssignment(
builder,
leftNode.loc ?? GeneratedSource,
InstructionKind.Reassign,
lvalue,
lowerExpression(builder, expr.get("right"))
);
}
case "MemberExpression": {
const leftExpr = left as NodePath<t.MemberExpression>;
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<t.Identifier>,
lvaluePath: NodePath<t.LVal>,
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<t.Identifier>;
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<t.MemberExpression>;
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<t.ArrayPattern>;
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<t.ObjectPattern>;
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");
}
}
}
@@ -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;
}
```
@@ -0,0 +1,17 @@
function foo(a, b, c) {
let d, g, n, o;
[
d,
[
{
e: { f: g },
},
],
] = a;
({
l: {
m: [[n]],
},
o,
} = b);
}
@@ -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;
}
```
@@ -0,0 +1,16 @@
function foo(a, b, c) {
const [
d,
[
{
e: { f },
},
],
] = a;
const {
l: {
m: [[n]],
},
o,
} = b;
}