Model other assignment variants as values

The previous PR only updated simple assignment expressions (where the lvalue is 
an identifier), this PR extends the same idea to all assignment variants. Note 
that there is one case that doesn't work yet, which is complex destructuring 
assignment as a value: 

```javascript 

let x = makeObject(); 

x.foo(([[x]] = makeObject())); 

``` 

What happens here is that we lower the destructuring to a series of steps: 

``` 

tmp1: Destructure Const [ tmp0 ]  = makeObject(); 

tmp2: Destructure Reassign [ x ] = tmp0; 

PropertyCall x, 'foo', [ tmp1 ] 

``` 

Thankfully we can detect this case: if we have a const/let declaration with an 
lvalue, that's invalid. See the new error test case which shows we correctly 
detect & reject this case for now.
This commit is contained in:
Joe Savona
2023-03-21 10:01:09 -07:00
parent bf1db812a8
commit ef34ca6cb0
8 changed files with 139 additions and 18 deletions
+32 -18
View File
@@ -2029,13 +2029,20 @@ function lowerAssignment(
});
return { kind: "UnsupportedNode", node: lvalueNode, loc };
}
return {
kind: "PropertyStore",
object,
property: property.node.name,
value,
const temporary = buildTemporaryPlace(builder, loc);
builder.push({
id: makeInstructionId(0),
lvalue: { ...temporary },
value: {
kind: "PropertyStore",
object,
property: property.node.name,
value,
loc,
},
loc,
};
});
return { kind: "LoadLocal", place: temporary, loc: temporary.loc };
} else {
if (!property.isExpression()) {
builder.errors.push({
@@ -2047,13 +2054,20 @@ function lowerAssignment(
return { kind: "UnsupportedNode", node: lvalueNode, loc };
}
const propertyPlace = lowerExpressionToTemporary(builder, property);
return {
kind: "ComputedStore",
object,
property: propertyPlace,
value,
const temporary = buildTemporaryPlace(builder, loc);
builder.push({
id: makeInstructionId(0),
lvalue: { ...temporary },
value: {
kind: "ComputedStore",
object,
property: propertyPlace,
value,
loc,
},
loc,
};
});
return { kind: "LoadLocal", place: temporary, loc: temporary.loc };
}
}
case "ArrayPattern": {
@@ -2093,10 +2107,10 @@ function lowerAssignment(
followups.push({ place: temp, path: element as NodePath<t.LVal> }); // TODO remove type cast
}
}
const temp = buildTemporaryPlace(builder, loc);
const temporary = buildTemporaryPlace(builder, loc);
builder.push({
id: makeInstructionId(0),
lvalue: { ...temp },
lvalue: { ...temporary },
value: {
kind: "Destructure",
lvalue: {
@@ -2114,7 +2128,7 @@ function lowerAssignment(
for (const { place, path } of followups) {
lowerAssignment(builder, path.node.loc ?? loc, kind, path, place);
}
return { kind: "LoadLocal", place: value, loc: value.loc };
return { kind: "LoadLocal", place: temporary, loc: value.loc };
}
case "ObjectPattern": {
const lvalue = lvaluePath as NodePath<t.ObjectPattern>;
@@ -2187,10 +2201,10 @@ function lowerAssignment(
}
}
}
const temp = buildTemporaryPlace(builder, loc);
const temporary = buildTemporaryPlace(builder, loc);
builder.push({
id: makeInstructionId(0),
lvalue: { ...temp },
lvalue: { ...temporary },
value: {
kind: "Destructure",
lvalue: {
@@ -2208,7 +2222,7 @@ function lowerAssignment(
for (const { place, path } of followups) {
lowerAssignment(builder, path.node.loc ?? loc, kind, path, place);
}
return { kind: "LoadLocal", place: value, loc: value.loc };
return { kind: "LoadLocal", place: temporary, loc: value.loc };
}
default: {
builder.errors.push({
@@ -393,11 +393,23 @@ function codegenInstructionNullable(
}
switch (kind) {
case InstructionKind.Const: {
if (instr.lvalue !== null) {
CompilerError.invariant(
`Const declaration cannot be referenced as an expression`,
instr.value.loc
);
}
return createVariableDeclaration(instr.loc, "const", [
t.variableDeclarator(codegenLValue(lvalue), value),
]);
}
case InstructionKind.Let: {
if (instr.lvalue !== null) {
CompilerError.invariant(
`Const declaration cannot be referenced as an expression`,
instr.value.loc
);
}
return createVariableDeclaration(instr.loc, "let", [
t.variableDeclarator(codegenLValue(lvalue), value),
]);
@@ -0,0 +1,30 @@
## Input
```javascript
function Component(props) {
let x = makeObject();
x.foo((x = makeObject()));
return x;
}
```
## Code
```javascript
function Component(props) {
const $ = React.unstable_useMemoCache(1);
let x;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
x = makeObject();
x.foo((x = makeObject()));
$[0] = x;
} else {
x = $[0];
}
return x;
}
```
@@ -0,0 +1,5 @@
function Component(props) {
let x = makeObject();
x.foo((x = makeObject()));
return x;
}
@@ -0,0 +1,30 @@
## Input
```javascript
function Component(props) {
let x = makeObject();
x.foo(([x] = makeObject()));
return x;
}
```
## Code
```javascript
function Component(props) {
const $ = React.unstable_useMemoCache(1);
let x;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
x = makeObject();
x.foo(([x] = makeObject()));
$[0] = x;
} else {
x = $[0];
}
return x;
}
```
@@ -0,0 +1,5 @@
function Component(props) {
let x = makeObject();
x.foo(([x] = makeObject()));
return x;
}
@@ -0,0 +1,20 @@
## Input
```javascript
function Component(props) {
let x = makeObject();
x.foo(([[x]] = makeObject()));
return x;
}
```
## Error
```
[ReactForget] Invariant: Const declaration cannot be referenced as an expression (3:3)
```
@@ -0,0 +1,5 @@
function Component(props) {
let x = makeObject();
x.foo(([[x]] = makeObject()));
return x;
}