Support assignment expressions in value blocks

Enables support for assignment expressions in value blocks (which includes in 
loop init/test/update blocks). This was pretty straightforward, the main changes 
are: 

* During PropagateScopeDependencies, we currently record scope reassignments 
based on `Identifier` object identity. In the case where a variable is 
reassigned in multiple control-flow paths of a value block, however, there can 
be multiple object identities. So we now de-dupe reassignments based on 
identifier id. 

* MergeOverlappingScopes now treats value blocks as regular blocks, allowing it 
to correctly merge scopes from the value with other scopes from the outer block. 

Otherwise this is mostly just lots of tests. Note that there is an outstanding 
todo, which is that we currently error for ternaries and logicals whose value is 
unused (eg `cond ? (x = 1) : null`). I'll address that in a follow-up.
This commit is contained in:
Joe Savona
2023-03-14 20:02:05 -07:00
parent 59cd1ca569
commit 7537df3d4e
22 changed files with 567 additions and 17 deletions
+4
View File
@@ -24,6 +24,10 @@ module.exports = {
// like invariant.
"no-fallthrough": "off",
// Low-value: this fires even for declarations that capture references which wouldn't be as
// obvious if the declaration was lifted to the parent root
"no-inner-declarations": "off",
"@typescript-eslint/no-empty-function": "off",
// Explicitly casting to/through any is sometimes required, often for error messages to
-11
View File
@@ -1198,17 +1198,6 @@ function lowerExpression(
const expr = exprPath as NodePath<t.AssignmentExpression>;
const operator = expr.node.operator;
if (builder.currentBlockKind() === "value") {
// try lowering the RHS in case it also contains errors
lowerExpressionToTemporary(builder, expr.get("right"));
builder.errors.push({
reason: `(BuildHIR::lowerExpression) Handle AssignmentExpression within a LogicalExpression or ConditionalExpression`,
severity: ErrorSeverity.Todo,
nodePath: expr.parentPath,
});
return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
}
if (operator === "=") {
const left = expr.get("left");
return lowerAssignment(
@@ -11,6 +11,7 @@ import {
Place,
ReactiveBlock,
ReactiveFunction,
ReactiveInstruction,
ReactiveScope,
ScopeId,
} from "../HIR";
@@ -98,9 +99,6 @@ import { ReactiveFunctionVisitor, visitReactiveFunction } from "./visitors";
*/
export function mergeOverlappingReactiveScopes(fn: ReactiveFunction): void {
const context = new Context();
// context.enter(() => {
// visitBlock(context, fn.body);
// });
visitReactiveFunction(fn, new Visitor(), context);
context.complete();
}
@@ -120,6 +118,21 @@ class Visitor extends ReactiveFunctionVisitor<Context> {
this.traverseBlock(block, state);
});
}
override visitInstruction(
instruction: ReactiveInstruction,
state: Context
): void {
if (
instruction.value.kind === "ConditionalExpression" ||
instruction.value.kind === "LogicalExpression"
) {
state.enter(() => {
super.visitInstruction(instruction, state);
});
} else {
super.visitInstruction(instruction, state);
}
}
}
class BlockScope {
@@ -317,7 +317,10 @@ class Context {
this.currentScope != null &&
place.identifier.scope != null &&
declaration !== undefined &&
declaration.scope !== place.identifier.scope
declaration.scope !== place.identifier.scope &&
!Array.from(this.currentScope.reassignments).some(
(ident) => ident.id === place.identifier.id
)
) {
this.currentScope.reassignments.add(place.identifier);
}
@@ -0,0 +1,24 @@
## Input
```javascript
function ternary(props) {
let x = 0;
const y = props.a ? (x = 1) : (x = 2);
return x + y;
}
```
## Code
```javascript
function ternary(props) {
let x = undefined;
const y = props.a ? ((x = 1), 1) : ((x = 2), 2);
return x + y;
}
```
@@ -1,4 +1,3 @@
// @skip
function ternary(props) {
let x = 0;
const y = props.a ? (x = 1) : (x = 2);
@@ -2,12 +2,26 @@
## Input
```javascript
function foo(props) {
let x = 0;
while (x > props.min && x < props.max) {
x *= 2;
}
return x;
}
```
## Code
```javascript
function foo(props) {
let x = 0;
while (x > props.min && x < props.max) {
x = x * 2;
}
return x;
}
```
@@ -1,4 +1,3 @@
// @skip
function foo(props) {
let x = 0;
while (x > props.min && x < props.max) {
@@ -0,0 +1,64 @@
## Input
```javascript
function foo(props) {
let x = [];
x.push(props.bar);
const _ = props.cond
? (({ x } = { x: {} }), ([x] = [[]]), x.push(props.foo))
: null;
console.log(_);
mut(x);
return x;
}
```
## HIR
```javascript
bb0 (block):
[1] store $35[1:33] = Array []
[2] store $37[2:33] = StoreLocal Let mutate x$36[2:33] = capture $35[1:33]
[3] mutate $38[3:33] = LoadLocal capture x$36[2:33]
[4] mutate $39 = LoadLocal read props$34
[5] mutate $40 = PropertyLoad read $39.bar
[6] mutate $41 = PropertyCall mutate $38[3:33].push(read $40)
[7] Ternary test:bb2 fallthrough=bb1
bb2 (value):
predecessor blocks: bb0
[8] mutate $42 = LoadLocal read props$34
[9] mutate $43 = PropertyLoad read $42.cond
[10] Branch (read $43) then:bb3 else:bb4
bb3 (value):
predecessor blocks: bb2
[14] store $48[14:33] = Array []
[15] store $49[15:33] = Array [capture $48[14:33]]
[16] store $51[16:33] = Destructure Reassign [ mutate x$36[16:33] ] = capture $49[15:33]
[17] mutate $52[17:33] = LoadLocal capture x$36[16:33]
[18] mutate $53 = LoadLocal read props$34
[19] mutate $54 = PropertyLoad read $53.foo
[20] mutate $55[20:30] = PropertyCall mutate $52[17:33].push(read $54)
[21] store $57[21:30] = StoreLocal Const mutate $56[7:30] = capture $55[20:30]
[22] Goto bb1
bb4 (value):
predecessor blocks: bb2
[23] mutate $58[23:30]:TPrimitive = null
[24] store $60[24:30]:TPrimitive = StoreLocal Const mutate $56[7:30] = read $58[23:30]:TPrimitive
[25] Goto bb1
bb1 (block):
predecessor blocks: bb3 bb4
$63[7:30]:TPhi: phi(bb3: $56, bb4: $59)
x$36[2:33]:TPhi: phi(bb3: x$36, bb4: x$36)
[26] store $62[26:30] = StoreLocal Const mutate _$61[26:30] = capture $56[7:30]
[27] mutate $64 = Global console
[28] mutate $65[28:30] = LoadLocal capture _$61[26:30]
[29] mutate $66 = PropertyCall read $64.log(mutate $65[28:30])
[30] mutate $67:TFunction = Global mut
[31] mutate $68[31:33] = LoadLocal capture x$36[2:33]
[32] mutate $70 = Call read $67:TFunction(mutate $68[31:33])
[33] mutate $71 = LoadLocal capture x$36[2:33]
[34] Return freeze $71
```
@@ -0,0 +1,10 @@
function foo(props) {
let x = [];
x.push(props.bar);
const _ = props.cond
? (({ x } = { x: {} }), ([x] = [[]]), x.push(props.foo))
: null;
console.log(_);
mut(x);
return x;
}
@@ -0,0 +1,60 @@
## Input
```javascript
function foo(props) {
let x = [];
x.push(props.bar);
const _ = props.cond
? (({ x } = { x: {} }), ([x] = [[]]), x.push(props.foo))
: null;
console.log(_);
return x;
}
```
## HIR
```javascript
bb0 (block):
[1] store $32[1:7] = Array []
[2] store $34[2:7] = StoreLocal Let mutate x$33[2:7] = capture $32[1:7]
[3] mutate $35[3:7] = LoadLocal capture x$33[2:7]
[4] mutate $36 = LoadLocal read props$31
[5] mutate $37 = PropertyLoad read $36.bar
[6] mutate $38 = PropertyCall mutate $35[3:7].push(read $37)
[7] Ternary test:bb2 fallthrough=bb1
bb2 (value):
predecessor blocks: bb0
[8] mutate $39 = LoadLocal read props$31
[9] mutate $40 = PropertyLoad read $39.cond
[10] Branch (read $40) then:bb3 else:bb4
bb3 (value):
predecessor blocks: bb2
[14] store $45[14:21] = Array []
[15] store $46[15:21] = Array [capture $45[14:21]]
[16] store $48[16:21] = Destructure Reassign [ mutate x$33[16:21] ] = capture $46[15:21]
[17] mutate $49[17:21] = LoadLocal capture x$33[16:21]
[18] mutate $50 = LoadLocal read props$31
[19] mutate $51 = PropertyLoad read $50.foo
[20] mutate $52[20:30] = PropertyCall mutate $49[17:21].push(read $51)
[21] store $54[21:30] = StoreLocal Const mutate $53[7:30] = capture $52[20:30]
[22] Goto bb1
bb4 (value):
predecessor blocks: bb2
[23] mutate $55[23:30]:TPrimitive = null
[24] store $57[24:30]:TPrimitive = StoreLocal Const mutate $53[7:30] = read $55[23:30]:TPrimitive
[25] Goto bb1
bb1 (block):
predecessor blocks: bb3 bb4
$60[7:30]:TPhi: phi(bb3: $53, bb4: $56)
x$33:TPhi: phi(bb3: x$33, bb4: x$33)
[26] store $59[26:30] = StoreLocal Const mutate _$58[26:30] = capture $53[7:30]
[27] mutate $61 = Global console
[28] mutate $62[28:30] = LoadLocal capture _$58[26:30]
[29] mutate $63 = PropertyCall read $61.log(mutate $62[28:30])
[30] mutate $64 = LoadLocal capture x$33
[31] Return freeze $64
```
@@ -0,0 +1,9 @@
function foo(props) {
let x = [];
x.push(props.bar);
const _ = props.cond
? (({ x } = { x: {} }), ([x] = [[]]), x.push(props.foo))
: null;
console.log(_);
return x;
}
@@ -0,0 +1,61 @@
## Input
```javascript
function foo(props) {
let x = [];
x.push(props.bar);
const _ = props.cond ? ((x = {}), (x = []), x.push(props.foo)) : null;
console.log(_);
mut(x);
return x;
}
```
## HIR
```javascript
bb0 (block):
[1] store $35[1:33] = Array []
[2] store $37[2:33] = StoreLocal Let mutate x$36[2:33] = capture $35[1:33]
[3] mutate $38[3:33] = LoadLocal capture x$36[2:33]
[4] mutate $39 = LoadLocal read props$34
[5] mutate $40 = PropertyLoad read $39.bar
[6] mutate $41 = PropertyCall mutate $38[3:33].push(read $40)
[7] Ternary test:bb2 fallthrough=bb1
bb2 (value):
predecessor blocks: bb0
[8] mutate $42 = LoadLocal read props$34
[9] mutate $43 = PropertyLoad read $42.cond
[10] Branch (read $43) then:bb3 else:bb4
bb3 (value):
predecessor blocks: bb2
[14] store $48[14:33] = Array []
[15] store $50[15:33] = StoreLocal Reassign mutate x$36[15:33] = capture $48[14:33]
[17] mutate $52[17:33] = LoadLocal capture x$36[15:33]
[18] mutate $53 = LoadLocal read props$34
[19] mutate $54 = PropertyLoad read $53.foo
[20] mutate $55[20:30] = PropertyCall mutate $52[17:33].push(read $54)
[21] store $57[21:30] = StoreLocal Const mutate $56[7:30] = capture $55[20:30]
[22] Goto bb1
bb4 (value):
predecessor blocks: bb2
[23] mutate $58[23:30]:TPrimitive = null
[24] store $60[24:30]:TPrimitive = StoreLocal Const mutate $56[7:30] = read $58[23:30]:TPrimitive
[25] Goto bb1
bb1 (block):
predecessor blocks: bb3 bb4
$63[7:30]:TPhi: phi(bb3: $56, bb4: $59)
x$36[2:33]:TPhi: phi(bb3: x$36, bb4: x$36)
[26] store $62[26:30] = StoreLocal Const mutate _$61[26:30] = capture $56[7:30]
[27] mutate $64 = Global console
[28] mutate $65[28:30] = LoadLocal capture _$61[26:30]
[29] mutate $66 = PropertyCall read $64.log(mutate $65[28:30])
[30] mutate $67:TFunction = Global mut
[31] mutate $68[31:33] = LoadLocal capture x$36[2:33]
[32] mutate $70 = Call read $67:TFunction(mutate $68[31:33])
[33] mutate $71 = LoadLocal capture x$36[2:33]
[34] Return freeze $71
```
@@ -0,0 +1,8 @@
function foo(props) {
let x = [];
x.push(props.bar);
const _ = props.cond ? ((x = {}), (x = []), x.push(props.foo)) : null;
console.log(_);
mut(x);
return x;
}
@@ -0,0 +1,57 @@
## Input
```javascript
function foo(props) {
let x = [];
x.push(props.bar);
const _ = props.cond ? ((x = {}), (x = []), x.push(props.foo)) : null;
console.log(_);
return x;
}
```
## HIR
```javascript
bb0 (block):
[1] store $32[1:7] = Array []
[2] store $34[2:7] = StoreLocal Let mutate x$33[2:7] = capture $32[1:7]
[3] mutate $35[3:7] = LoadLocal capture x$33[2:7]
[4] mutate $36 = LoadLocal read props$31
[5] mutate $37 = PropertyLoad read $36.bar
[6] mutate $38 = PropertyCall mutate $35[3:7].push(read $37)
[7] Ternary test:bb2 fallthrough=bb1
bb2 (value):
predecessor blocks: bb0
[8] mutate $39 = LoadLocal read props$31
[9] mutate $40 = PropertyLoad read $39.cond
[10] Branch (read $40) then:bb3 else:bb4
bb3 (value):
predecessor blocks: bb2
[14] store $45[14:21] = Array []
[15] store $47[15:21] = StoreLocal Reassign mutate x$33[15:21] = capture $45[14:21]
[17] mutate $49[17:21] = LoadLocal capture x$33[15:21]
[18] mutate $50 = LoadLocal read props$31
[19] mutate $51 = PropertyLoad read $50.foo
[20] mutate $52[20:30] = PropertyCall mutate $49[17:21].push(read $51)
[21] store $54[21:30] = StoreLocal Const mutate $53[7:30] = capture $52[20:30]
[22] Goto bb1
bb4 (value):
predecessor blocks: bb2
[23] mutate $55[23:30]:TPrimitive = null
[24] store $57[24:30]:TPrimitive = StoreLocal Const mutate $53[7:30] = read $55[23:30]:TPrimitive
[25] Goto bb1
bb1 (block):
predecessor blocks: bb3 bb4
$60[7:30]:TPhi: phi(bb3: $53, bb4: $56)
x$33:TPhi: phi(bb3: x$33, bb4: x$33)
[26] store $59[26:30] = StoreLocal Const mutate _$58[26:30] = capture $53[7:30]
[27] mutate $61 = Global console
[28] mutate $62[28:30] = LoadLocal capture _$58[26:30]
[29] mutate $63 = PropertyCall read $61.log(mutate $62[28:30])
[30] mutate $64 = LoadLocal capture x$33
[31] Return freeze $64
```
@@ -0,0 +1,7 @@
function foo(props) {
let x = [];
x.push(props.bar);
const _ = props.cond ? ((x = {}), (x = []), x.push(props.foo)) : null;
console.log(_);
return x;
}
@@ -0,0 +1,68 @@
## Input
```javascript
function foo(props) {
let x = [];
x.push(props.bar);
const _ = props.cond
? ((x = {}), (x = []), x.push(props.foo))
: ((x = []), (x = []), x.push(props.bar));
console.log(_);
mut(x);
return x;
}
```
## HIR
```javascript
bb0 (block):
[1] store $44[1:7] = Array []
[2] store $46[2:7] = StoreLocal Let mutate x$45[2:42] = capture $44[1:7]
[3] mutate $47[3:7] = LoadLocal capture x$45[2:42]
[4] mutate $48 = LoadLocal read props$43
[5] mutate $49 = PropertyLoad read $48.bar
[6] mutate $50 = PropertyCall mutate $47[3:7].push(read $49)
[7] Ternary test:bb2 fallthrough=bb1
bb2 (value):
predecessor blocks: bb0
[8] mutate $51 = LoadLocal read props$43
[9] mutate $52 = PropertyLoad read $51.cond
[10] Branch (read $52) then:bb3 else:bb4
bb3 (value):
predecessor blocks: bb2
[14] store $57[14:42] = Array []
[15] store $59[15:42] = StoreLocal Reassign mutate x$45[15:42] = capture $57[14:42]
[17] mutate $61[17:42] = LoadLocal capture x$45[15:42]
[18] mutate $62 = LoadLocal read props$43
[19] mutate $63 = PropertyLoad read $62.foo
[20] mutate $64[20:39] = PropertyCall mutate $61[17:42].push(read $63)
[21] store $66[21:39] = StoreLocal Const mutate $65[7:39] = capture $64[20:39]
[22] Goto bb1
bb4 (value):
predecessor blocks: bb2
[26] store $71[26:42] = Array []
[27] store $73[27:42] = StoreLocal Reassign mutate x$45[27:42] = capture $71[26:42]
[29] mutate $75[29:42] = LoadLocal capture x$45[27:42]
[30] mutate $76 = LoadLocal read props$43
[31] mutate $77 = PropertyLoad read $76.bar
[32] mutate $78[32:39] = PropertyCall mutate $75[29:42].push(read $77)
[33] store $80[33:39] = StoreLocal Const mutate $65[7:39] = capture $78[32:39]
[34] Goto bb1
bb1 (block):
predecessor blocks: bb3 bb4
$83[7:39]:TPhi: phi(bb3: $65, bb4: $79)
x$45[15:42]:TPhi: phi(bb3: x$45, bb4: x$45)
[35] store $82[35:39] = StoreLocal Const mutate _$81[35:39] = capture $65[7:39]
[36] mutate $84 = Global console
[37] mutate $85[37:39] = LoadLocal capture _$81[35:39]
[38] mutate $86 = PropertyCall read $84.log(mutate $85[37:39])
[39] mutate $87:TFunction = Global mut
[40] mutate $88[40:42] = LoadLocal capture x$45[15:42]
[41] mutate $90 = Call read $87:TFunction(mutate $88[40:42])
[42] mutate $91 = LoadLocal capture x$45[15:42]
[43] Return freeze $91
```
@@ -0,0 +1,10 @@
function foo(props) {
let x = [];
x.push(props.bar);
const _ = props.cond
? ((x = {}), (x = []), x.push(props.foo))
: ((x = []), (x = []), x.push(props.bar));
console.log(_);
mut(x);
return x;
}
@@ -0,0 +1,64 @@
## Input
```javascript
function foo(props) {
let x = [];
x.push(props.bar);
const _ = props.cond
? ((x = {}), (x = []), x.push(props.foo))
: ((x = []), (x = []), x.push(props.bar));
console.log(_);
return x;
}
```
## HIR
```javascript
bb0 (block):
[1] store $41[1:7] = Array []
[2] store $43[2:7] = StoreLocal Let mutate x$42[2:7] = capture $41[1:7]
[3] mutate $44[3:7] = LoadLocal capture x$42[2:7]
[4] mutate $45 = LoadLocal read props$40
[5] mutate $46 = PropertyLoad read $45.bar
[6] mutate $47 = PropertyCall mutate $44[3:7].push(read $46)
[7] Ternary test:bb2 fallthrough=bb1
bb2 (value):
predecessor blocks: bb0
[8] mutate $48 = LoadLocal read props$40
[9] mutate $49 = PropertyLoad read $48.cond
[10] Branch (read $49) then:bb3 else:bb4
bb3 (value):
predecessor blocks: bb2
[14] store $54[14:21] = Array []
[15] store $56[15:21] = StoreLocal Reassign mutate x$42[15:21] = capture $54[14:21]
[17] mutate $58[17:21] = LoadLocal capture x$42[15:21]
[18] mutate $59 = LoadLocal read props$40
[19] mutate $60 = PropertyLoad read $59.foo
[20] mutate $61[20:39] = PropertyCall mutate $58[17:21].push(read $60)
[21] store $63[21:39] = StoreLocal Const mutate $62[7:39] = capture $61[20:39]
[22] Goto bb1
bb4 (value):
predecessor blocks: bb2
[26] store $68[26:33] = Array []
[27] store $70[27:33] = StoreLocal Reassign mutate x$42[27:33] = capture $68[26:33]
[29] mutate $72[29:33] = LoadLocal capture x$42[27:33]
[30] mutate $73 = LoadLocal read props$40
[31] mutate $74 = PropertyLoad read $73.bar
[32] mutate $75[32:39] = PropertyCall mutate $72[29:33].push(read $74)
[33] store $77[33:39] = StoreLocal Const mutate $62[7:39] = capture $75[32:39]
[34] Goto bb1
bb1 (block):
predecessor blocks: bb3 bb4
$80[7:39]:TPhi: phi(bb3: $62, bb4: $76)
x$42:TPhi: phi(bb3: x$42, bb4: x$42)
[35] store $79[35:39] = StoreLocal Const mutate _$78[35:39] = capture $62[7:39]
[36] mutate $81 = Global console
[37] mutate $82[37:39] = LoadLocal capture _$78[35:39]
[38] mutate $83 = PropertyCall read $81.log(mutate $82[37:39])
[39] mutate $84 = LoadLocal capture x$42
[40] Return freeze $84
```
@@ -0,0 +1,9 @@
function foo(props) {
let x = [];
x.push(props.bar);
const _ = props.cond
? ((x = {}), (x = []), x.push(props.foo))
: ((x = []), (x = []), x.push(props.bar));
console.log(_);
return x;
}
@@ -0,0 +1,63 @@
## Input
```javascript
function foo(props) {
let x = [];
x.push(props.bar);
if (props.cond) {
x = {};
x = [];
x.push(props.foo);
} else {
x = [];
x = [];
x.push(props.bar);
}
mut(x);
return x;
}
```
## HIR
```javascript
bb0 (block):
[1] store $36[1:7] = Array []
[2] store $38[2:7] = StoreLocal Let mutate x$37[2:35] = capture $36[1:7]
[3] mutate $39[3:7] = LoadLocal capture x$37[2:35]
[4] mutate $40 = LoadLocal read props$35
[5] mutate $41 = PropertyLoad read $40.bar
[6] mutate $42 = PropertyCall mutate $39[3:7].push(read $41)
[7] mutate $43 = LoadLocal read props$35
[8] mutate $44 = PropertyLoad read $43.cond
[9] If (read $44) then:bb2 else:bb3 fallthrough=bb1
bb2 (block):
predecessor blocks: bb0
[13] store $49[13:35] = Array []
[14] store $51[14:35] = StoreLocal Reassign mutate x$37[14:35] = capture $49[13:35]
[16] mutate $53[16:35] = LoadLocal capture x$37[14:35]
[17] mutate $54 = LoadLocal read props$35
[18] mutate $55 = PropertyLoad read $54.foo
[19] mutate $56 = PropertyCall mutate $53[16:35].push(read $55)
[20] Goto bb1
bb3 (block):
predecessor blocks: bb0
[24] store $61[24:35] = Array []
[25] store $63[25:35] = StoreLocal Reassign mutate x$37[25:35] = capture $61[24:35]
[27] mutate $65[27:35] = LoadLocal capture x$37[25:35]
[28] mutate $66 = LoadLocal read props$35
[29] mutate $67 = PropertyLoad read $66.bar
[30] mutate $68 = PropertyCall mutate $65[27:35].push(read $67)
[31] Goto bb1
bb1 (block):
predecessor blocks: bb2 bb3
x$37[14:35]:TPhi: phi(bb2: x$37, bb3: x$37)
[32] mutate $69:TFunction = Global mut
[33] mutate $70[33:35] = LoadLocal capture x$37[14:35]
[34] mutate $72 = Call read $69:TFunction(mutate $70[33:35])
[35] mutate $73 = LoadLocal capture x$37[14:35]
[36] Return freeze $73
```
@@ -0,0 +1,15 @@
function foo(props) {
let x = [];
x.push(props.bar);
if (props.cond) {
x = {};
x = [];
x.push(props.foo);
} else {
x = [];
x = [];
x.push(props.bar);
}
mut(x);
return x;
}