[wip][typer] Infer Array instance properties

--- 

TODO: add inference for TypedFunction to `InferReferenceEffects`
This commit is contained in:
Mofei Zhang
2023-03-21 14:57:11 -04:00
parent ef34ca6cb0
commit ec345ca538
13 changed files with 252 additions and 90 deletions
+134
View File
@@ -0,0 +1,134 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import invariant from "invariant";
import { Effect } from "./HIR";
import { BuiltInType, FunctionType, PrimitiveType } from "./Types";
const PRIMITIVE_TYPE: PrimitiveType = {
kind: "Primitive",
};
let nextAnonId = 0;
// use strings since they are easily debuggable, even though `Symbol()`
// might be more performant
function createAnonId(): string {
return `<generated_${nextAnonId++}>`;
}
function addFunction(
registry: ShapeRegistry,
properties: Iterable<[string, BuiltInType | null]>,
fn: FunctionSignature
): FunctionType {
const shapeId = createAnonId();
addShape(registry, shapeId, properties, fn);
return {
kind: "Function",
shapeId,
};
}
function addShape(
registry: ShapeRegistry,
id: string,
properties: Iterable<[string, BuiltInType | null]>,
functionType?: FunctionSignature
): ObjectShape {
const shape: ObjectShape = {
properties: new Map(properties),
functionType: functionType ?? null,
};
invariant(
!registry.has(id),
`[ObjectShape] Could not add shape to registry: name ${id} already exists.`
);
registry.set(id, shape);
return shape;
}
// Param type not recorded since it currently does not affect inference.
// Specifically, we currently do not:
// - infer types based on their usage in argument position
// - handle inference for overloaded / generic functions
export type FunctionSignature = {
positionalParams: Array<Effect>;
restParam: Effect | null;
returnType: BuiltInType | null;
calleeEffect: Effect;
};
export type ObjectShape = {
properties: Map<string, BuiltInType | null>;
functionType: FunctionSignature | null;
};
export type ShapeRegistry = Map<string, ObjectShape>;
/**
* Shapes of built-in types
*/
// The only "entrypoints" should be Globals and recursive lookups from properties / functions
export const BUILTIN_SHAPES: ShapeRegistry = new Map();
export const ArrayShapeId = "Array";
export const ObjectShapeId = "Object";
/* Built-in array shape */
addShape(BUILTIN_SHAPES, ArrayShapeId, [
[
"at",
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Read],
restParam: null,
returnType: null,
calleeEffect: Effect.Capture,
}),
],
[
"concat",
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: Effect.Capture,
returnType: {
kind: "Object",
shapeId: ArrayShapeId,
},
calleeEffect: Effect.Capture,
}),
],
["length", PRIMITIVE_TYPE],
[
"push",
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: Effect.Capture,
returnType: {
kind: "Primitive",
},
calleeEffect: Effect.Store,
}),
],
// TODO: rest of Array properties
]);
/* Built-in Object shape */
addShape(BUILTIN_SHAPES, ObjectShapeId, [
[
"toString",
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: null,
returnType: {
kind: "Primitive",
},
calleeEffect: Effect.Read,
}),
],
// TODO:
// hasOwnProperty, isPrototypeOf, propertyIsEnumerable, toLocaleString, valueOf
]);
+8 -1
View File
@@ -566,7 +566,14 @@ function printScope(scope: ReactiveScope | null): string {
function printType(type: Type): string {
if (type.kind === "Type") return "";
return `:T${type.kind}`;
// TODO(mofeiZ): add debugName for generated ids
if (type.kind === "Object" && type.shapeId != null) {
return `:T${type.kind}<${type.shapeId}>`;
} else if (type.kind === "Function" && type.shapeId != null) {
return `:T${type.kind}<${type.shapeId}>`;
} else {
return `:T${type.kind}`;
}
}
export function printSourceLocation(loc: SourceLocation): string {
+25 -12
View File
@@ -7,23 +7,36 @@
import invariant from "invariant";
import { Hook } from "./Hooks";
export type Type =
| PrimitiveType
| HookType
| FunctionType
| ObjectType
| PhiType
| PolyType
| TypeVar;
export type BuiltInType = PrimitiveType | FunctionType | ObjectType;
export type Type = BuiltInType | HookType | PhiType | PolyType | TypeVar;
export type PrimitiveType = { kind: "Primitive" };
export type FunctionType = {
kind: "Function";
};
export type HookType = {
kind: "Hook";
definition: Hook;
};
export type ObjectType = { kind: "Object" };
/**
* An {@link FunctionType} or {@link ObjectType} (also a JS object) may be associated with an
* inferred "object shape", i.e. a known property (key -> Type) map. This is
* subtly different from JS language semantics - `shape` represents both
* OwnPropertyDescriptors and properties present in the prototype chain.
*
* In addition, a {@link FunctionType} may be associated with an inferred signature,
*
* If `shapeId` is present, it is a key into the global ShapeRegistry.
*/
export type FunctionType = {
kind: "Function";
shapeId: string | null;
};
export type ObjectType = {
kind: "Object";
shapeId: string | null;
};
export type TypeVar = {
kind: "Type";
id: TypeId;
@@ -9,6 +9,7 @@ import {
TypeId,
TypeVar,
} from "../HIR/HIR";
import { ArrayShapeId, ObjectShapeId } from "../HIR/ObjectShape";
import { eachInstructionLValue, eachInstructionOperand } from "../HIR/visitors";
function isPrimitiveBinaryOp(op: t.BinaryExpression["operator"]): boolean {
@@ -150,7 +151,7 @@ function* generateInstructionTypes(
if (hook !== null) {
type = { kind: "Hook", definition: hook };
} else {
type = { kind: "Function" };
type = { kind: "Function", shapeId: null };
}
yield equation(value.callee.identifier.type, type);
break;
@@ -158,7 +159,14 @@ function* generateInstructionTypes(
case "ObjectExpression": {
invariant(left !== null, "invald object expression");
yield equation(left, { kind: "Object" });
yield equation(left, { kind: "Object", shapeId: ObjectShapeId });
break;
}
case "ArrayExpression": {
if (left) {
yield equation(left, { kind: "Object", shapeId: ArrayShapeId });
}
break;
}
}
@@ -25,7 +25,7 @@ bb0 (block):
[9] store $27 = StoreLocal Const mutate y$26 = capture $25
[10] mutate $28:TPrimitive = LoadLocal read x$21:TPrimitive
[11] mutate $29 = LoadLocal capture y$26
[12] store $30 = Array [read $28:TPrimitive, capture $29]
[13] Return freeze $30
[12] store $30:TObject<Array> = Array [read $28:TPrimitive, capture $29]
[13] Return freeze $30:TObject<Array>
```
@@ -25,7 +25,7 @@ bb0 (block):
[9] store $27 = StoreLocal Const mutate y$26 = capture $25
[10] mutate $28:TPrimitive = LoadLocal read x$21:TPrimitive
[11] mutate $29 = LoadLocal capture y$26
[12] store $30:TObject = Object { x: read $28:TPrimitive, y: capture $29 }
[13] Return freeze $30:TObject
[12] store $30:TObject<Object> = Object { x: read $28:TPrimitive, y: capture $29 }
[13] Return freeze $30:TObject<Object>
```
@@ -19,12 +19,12 @@ function foo(props) {
```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]
[1] store $35[1:33]:TObject<Array> = Array []
[2] store $37[2:33]:TObject<Array> = StoreLocal Let store x$36[2:33]:TObject<Array> = capture $35[1:33]:TObject<Array>
[3] mutate $38[3:33]:TObject<Array> = LoadLocal capture x$36[2:33]:TObject<Array>
[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)
[6] mutate $41 = PropertyCall mutate $38[3:33]:TObject<Array>.push(read $40)
[7] Ternary test:bb2 fallthrough=bb1
bb2 (value):
predecessor blocks: bb0
@@ -33,9 +33,9 @@ bb2 (value):
[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]
[14] store $48[14:33]:TObject<Array> = Array []
[15] store $49[15:33]:TObject<Array> = Array [capture $48[14:33]:TObject<Array>]
[16] store $51[16:33] = Destructure Reassign [ mutate x$36[16:33] ] = capture $49[15:33]:TObject<Array>
[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
@@ -18,12 +18,12 @@ function foo(props) {
```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]
[1] store $32[1:7]:TObject<Array> = Array []
[2] store $34[2:7]:TObject<Array> = StoreLocal Let store x$33[2:7]:TObject<Array> = capture $32[1:7]:TObject<Array>
[3] mutate $35[3:7]:TObject<Array> = LoadLocal capture x$33[2:7]:TObject<Array>
[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)
[6] mutate $38 = PropertyCall mutate $35[3:7]:TObject<Array>.push(read $37)
[7] Ternary test:bb2 fallthrough=bb1
bb2 (value):
predecessor blocks: bb0
@@ -32,9 +32,9 @@ bb2 (value):
[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]
[14] store $45[14:21]:TObject<Array> = Array []
[15] store $46[15:21]:TObject<Array> = Array [capture $45[14:21]:TObject<Array>]
[16] store $48[16:21] = Destructure Reassign [ mutate x$33[16:21] ] = capture $46[15:21]:TObject<Array>
[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
@@ -17,12 +17,12 @@ function foo(props) {
```javascript
bb0 (block):
[1] store $33[1:31] = Array []
[2] store $35[2:31] = StoreLocal Let mutate x$34[2:31] = capture $33[1:31]
[3] mutate $36[3:31] = LoadLocal capture x$34[2:31]
[1] store $33[1:31]:TObject<Array> = Array []
[2] store $35[2:31]:TObject<Array> = StoreLocal Let store x$34[2:31]:TObject<Array> = capture $33[1:31]:TObject<Array>
[3] mutate $36[3:31]:TObject<Array> = LoadLocal capture x$34[2:31]:TObject<Array>
[4] mutate $37 = LoadLocal read props$32
[5] mutate $38 = PropertyLoad read $37.bar
[6] mutate $39 = PropertyCall mutate $36[3:31].push(read $38)
[6] mutate $39 = PropertyCall mutate $36[3:31]:TObject<Array>.push(read $38)
[7] Ternary test:bb2 fallthrough=bb1
bb2 (value):
predecessor blocks: bb0
@@ -31,12 +31,12 @@ bb2 (value):
[10] Branch (read $41) then:bb3 else:bb4
bb3 (value):
predecessor blocks: bb2
[13] store $45[13:31] = Array []
[14] store $47[14:31] = StoreLocal Reassign mutate x$34[14:31] = capture $45[13:31]
[15] mutate $48[15:31] = LoadLocal capture x$34[14:31]
[13] store $45[13:31]:TObject<Array> = Array []
[14] store $47[14:31]:TObject<Array> = StoreLocal Reassign store x$34[14:31]:TObject<Array> = capture $45[13:31]:TObject<Array>
[15] mutate $48[15:31]:TObject<Array> = LoadLocal capture x$34[14:31]:TObject<Array>
[16] mutate $49 = LoadLocal read props$32
[17] mutate $50 = PropertyLoad read $49.foo
[18] mutate $51[18:28] = PropertyCall mutate $48[15:31].push(read $50)
[18] mutate $51[18:28] = PropertyCall mutate $48[15:31]:TObject<Array>.push(read $50)
[19] store $53[19:28] = StoreLocal Const mutate $52[7:28] = capture $51[18:28]
[20] Goto bb1
bb4 (value):
@@ -47,7 +47,7 @@ bb4 (value):
bb1 (block):
predecessor blocks: bb3 bb4
$59[7:28]:TPhi: phi(bb3: $52, bb4: $55)
x$34[2:31]:TPhi: phi(bb3: x$34, bb4: x$34)
x$34[2:31]:TObject<Array>: phi(bb3: x$34, bb4: x$34)
[24] store $58[24:28] = StoreLocal Const mutate _$57[24:28] = capture $52[7:28]
[25] mutate $60 = Global console
[26] mutate $61[26:28] = LoadLocal capture _$57[24:28]
@@ -16,12 +16,12 @@ function foo(props) {
```javascript
bb0 (block):
[1] store $30[1:7] = Array []
[2] store $32[2:7] = StoreLocal Let mutate x$31[2:7] = capture $30[1:7]
[3] mutate $33[3:7] = LoadLocal capture x$31[2:7]
[1] store $30[1:7]:TObject<Array> = Array []
[2] store $32[2:7]:TObject<Array> = StoreLocal Let store x$31[2:7]:TObject<Array> = capture $30[1:7]:TObject<Array>
[3] mutate $33[3:7]:TObject<Array> = LoadLocal capture x$31[2:7]:TObject<Array>
[4] mutate $34 = LoadLocal read props$29
[5] mutate $35 = PropertyLoad read $34.bar
[6] mutate $36 = PropertyCall mutate $33[3:7].push(read $35)
[6] mutate $36 = PropertyCall mutate $33[3:7]:TObject<Array>.push(read $35)
[7] Ternary test:bb2 fallthrough=bb1
bb2 (value):
predecessor blocks: bb0
@@ -30,12 +30,12 @@ bb2 (value):
[10] Branch (read $38) then:bb3 else:bb4
bb3 (value):
predecessor blocks: bb2
[13] store $42[13:19] = Array []
[14] store $44[14:19] = StoreLocal Reassign mutate x$31[14:19] = capture $42[13:19]
[15] mutate $45[15:19] = LoadLocal capture x$31[14:19]
[13] store $42[13:19]:TObject<Array> = Array []
[14] store $44[14:19]:TObject<Array> = StoreLocal Reassign store x$31[14:19]:TObject<Array> = capture $42[13:19]:TObject<Array>
[15] mutate $45[15:19]:TObject<Array> = LoadLocal capture x$31[14:19]:TObject<Array>
[16] mutate $46 = LoadLocal read props$29
[17] mutate $47 = PropertyLoad read $46.foo
[18] mutate $48[18:28] = PropertyCall mutate $45[15:19].push(read $47)
[18] mutate $48[18:28] = PropertyCall mutate $45[15:19]:TObject<Array>.push(read $47)
[19] store $50[19:28] = StoreLocal Const mutate $49[7:28] = capture $48[18:28]
[20] Goto bb1
bb4 (value):
@@ -46,7 +46,7 @@ bb4 (value):
bb1 (block):
predecessor blocks: bb3 bb4
$56[7:28]:TPhi: phi(bb3: $49, bb4: $52)
x$31:TPhi: phi(bb3: x$31, bb4: x$31)
x$31:TObject<Array>: phi(bb3: x$31, bb4: x$31)
[24] store $55[24:28] = StoreLocal Const mutate _$54[24:28] = capture $49[7:28]
[25] mutate $57 = Global console
[26] mutate $58[26:28] = LoadLocal capture _$54[24:28]
@@ -19,12 +19,12 @@ function foo(props) {
```javascript
bb0 (block):
[1] store $40[1:7] = Array []
[2] store $42[2:7] = StoreLocal Let mutate x$41[2:38] = capture $40[1:7]
[3] mutate $43[3:7] = LoadLocal capture x$41[2:38]
[1] store $40[1:7]:TObject<Array> = Array []
[2] store $42[2:7]:TObject<Array> = StoreLocal Let store x$41[2:38]:TObject<Array> = capture $40[1:7]:TObject<Array>
[3] mutate $43[3:7]:TObject<Array> = LoadLocal capture x$41[2:38]:TObject<Array>
[4] mutate $44 = LoadLocal read props$39
[5] mutate $45 = PropertyLoad read $44.bar
[6] mutate $46 = PropertyCall mutate $43[3:7].push(read $45)
[6] mutate $46 = PropertyCall mutate $43[3:7]:TObject<Array>.push(read $45)
[7] Ternary test:bb2 fallthrough=bb1
bb2 (value):
predecessor blocks: bb0
@@ -33,28 +33,28 @@ bb2 (value):
[10] Branch (read $48) then:bb3 else:bb4
bb3 (value):
predecessor blocks: bb2
[13] store $52[13:38] = Array []
[14] store $54[14:38] = StoreLocal Reassign mutate x$41[14:38] = capture $52[13:38]
[15] mutate $55[15:38] = LoadLocal capture x$41[14:38]
[13] store $52[13:38]:TObject<Array> = Array []
[14] store $54[14:38]:TObject<Array> = StoreLocal Reassign store x$41[14:38]:TObject<Array> = capture $52[13:38]:TObject<Array>
[15] mutate $55[15:38]:TObject<Array> = LoadLocal capture x$41[14:38]:TObject<Array>
[16] mutate $56 = LoadLocal read props$39
[17] mutate $57 = PropertyLoad read $56.foo
[18] mutate $58[18:35] = PropertyCall mutate $55[15:38].push(read $57)
[18] mutate $58[18:35] = PropertyCall mutate $55[15:38]:TObject<Array>.push(read $57)
[19] store $60[19:35] = StoreLocal Const mutate $59[7:35] = capture $58[18:35]
[20] Goto bb1
bb4 (value):
predecessor blocks: bb2
[23] store $64[23:38] = Array []
[24] store $66[24:38] = StoreLocal Reassign mutate x$41[24:38] = capture $64[23:38]
[25] mutate $67[25:38] = LoadLocal capture x$41[24:38]
[23] store $64[23:38]:TObject<Array> = Array []
[24] store $66[24:38]:TObject<Array> = StoreLocal Reassign store x$41[24:38]:TObject<Array> = capture $64[23:38]:TObject<Array>
[25] mutate $67[25:38]:TObject<Array> = LoadLocal capture x$41[24:38]:TObject<Array>
[26] mutate $68 = LoadLocal read props$39
[27] mutate $69 = PropertyLoad read $68.bar
[28] mutate $70[28:35] = PropertyCall mutate $67[25:38].push(read $69)
[28] mutate $70[28:35] = PropertyCall mutate $67[25:38]:TObject<Array>.push(read $69)
[29] store $72[29:35] = StoreLocal Const mutate $59[7:35] = capture $70[28:35]
[30] Goto bb1
bb1 (block):
predecessor blocks: bb3 bb4
$75[7:35]:TPhi: phi(bb3: $59, bb4: $71)
x$41[14:38]:TPhi: phi(bb3: x$41, bb4: x$41)
x$41[14:38]:TObject<Array>: phi(bb3: x$41, bb4: x$41)
[31] store $74[31:35] = StoreLocal Const mutate _$73[31:35] = capture $59[7:35]
[32] mutate $76 = Global console
[33] mutate $77[33:35] = LoadLocal capture _$73[31:35]
@@ -18,12 +18,12 @@ function foo(props) {
```javascript
bb0 (block):
[1] store $37[1:7] = Array []
[2] store $39[2:7] = StoreLocal Let mutate x$38[2:7] = capture $37[1:7]
[3] mutate $40[3:7] = LoadLocal capture x$38[2:7]
[1] store $37[1:7]:TObject<Array> = Array []
[2] store $39[2:7]:TObject<Array> = StoreLocal Let store x$38[2:7]:TObject<Array> = capture $37[1:7]:TObject<Array>
[3] mutate $40[3:7]:TObject<Array> = LoadLocal capture x$38[2:7]:TObject<Array>
[4] mutate $41 = LoadLocal read props$36
[5] mutate $42 = PropertyLoad read $41.bar
[6] mutate $43 = PropertyCall mutate $40[3:7].push(read $42)
[6] mutate $43 = PropertyCall mutate $40[3:7]:TObject<Array>.push(read $42)
[7] Ternary test:bb2 fallthrough=bb1
bb2 (value):
predecessor blocks: bb0
@@ -32,28 +32,28 @@ bb2 (value):
[10] Branch (read $45) then:bb3 else:bb4
bb3 (value):
predecessor blocks: bb2
[13] store $49[13:19] = Array []
[14] store $51[14:19] = StoreLocal Reassign mutate x$38[14:19] = capture $49[13:19]
[15] mutate $52[15:19] = LoadLocal capture x$38[14:19]
[13] store $49[13:19]:TObject<Array> = Array []
[14] store $51[14:19]:TObject<Array> = StoreLocal Reassign store x$38[14:19]:TObject<Array> = capture $49[13:19]:TObject<Array>
[15] mutate $52[15:19]:TObject<Array> = LoadLocal capture x$38[14:19]:TObject<Array>
[16] mutate $53 = LoadLocal read props$36
[17] mutate $54 = PropertyLoad read $53.foo
[18] mutate $55[18:35] = PropertyCall mutate $52[15:19].push(read $54)
[18] mutate $55[18:35] = PropertyCall mutate $52[15:19]:TObject<Array>.push(read $54)
[19] store $57[19:35] = StoreLocal Const mutate $56[7:35] = capture $55[18:35]
[20] Goto bb1
bb4 (value):
predecessor blocks: bb2
[23] store $61[23:29] = Array []
[24] store $63[24:29] = StoreLocal Reassign mutate x$38[24:29] = capture $61[23:29]
[25] mutate $64[25:29] = LoadLocal capture x$38[24:29]
[23] store $61[23:29]:TObject<Array> = Array []
[24] store $63[24:29]:TObject<Array> = StoreLocal Reassign store x$38[24:29]:TObject<Array> = capture $61[23:29]:TObject<Array>
[25] mutate $64[25:29]:TObject<Array> = LoadLocal capture x$38[24:29]:TObject<Array>
[26] mutate $65 = LoadLocal read props$36
[27] mutate $66 = PropertyLoad read $65.bar
[28] mutate $67[28:35] = PropertyCall mutate $64[25:29].push(read $66)
[28] mutate $67[28:35] = PropertyCall mutate $64[25:29]:TObject<Array>.push(read $66)
[29] store $69[29:35] = StoreLocal Const mutate $56[7:35] = capture $67[28:35]
[30] Goto bb1
bb1 (block):
predecessor blocks: bb3 bb4
$72[7:35]:TPhi: phi(bb3: $56, bb4: $68)
x$38:TPhi: phi(bb3: x$38, bb4: x$38)
x$38:TObject<Array>: phi(bb3: x$38, bb4: x$38)
[31] store $71[31:35] = StoreLocal Const mutate _$70[31:35] = capture $56[7:35]
[32] mutate $73 = Global console
[33] mutate $74[33:35] = LoadLocal capture _$70[31:35]
@@ -24,36 +24,36 @@ function foo(props) {
```javascript
bb0 (block):
[1] store $32[1:7] = Array []
[2] store $34[2:7] = StoreLocal Let mutate x$33[2:31] = capture $32[1:7]
[3] mutate $35[3:7] = LoadLocal capture x$33[2:31]
[1] store $32[1:7]:TObject<Array> = Array []
[2] store $34[2:7]:TObject<Array> = StoreLocal Let store x$33[2:31]:TObject<Array> = capture $32[1:7]:TObject<Array>
[3] mutate $35[3:7]:TObject<Array> = LoadLocal capture x$33[2:31]:TObject<Array>
[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)
[6] mutate $38 = PropertyCall mutate $35[3:7]:TObject<Array>.push(read $37)
[7] mutate $39 = LoadLocal read props$31
[8] mutate $40 = PropertyLoad read $39.cond
[9] If (read $40) then:bb2 else:bb3 fallthrough=bb1
bb2 (block):
predecessor blocks: bb0
[12] store $44[12:31] = Array []
[13] store $46[13:31] = StoreLocal Reassign mutate x$33[13:31] = capture $44[12:31]
[14] mutate $47[14:31] = LoadLocal capture x$33[13:31]
[12] store $44[12:31]:TObject<Array> = Array []
[13] store $46[13:31]:TObject<Array> = StoreLocal Reassign store x$33[13:31]:TObject<Array> = capture $44[12:31]:TObject<Array>
[14] mutate $47[14:31]:TObject<Array> = LoadLocal capture x$33[13:31]:TObject<Array>
[15] mutate $48 = LoadLocal read props$31
[16] mutate $49 = PropertyLoad read $48.foo
[17] mutate $50 = PropertyCall mutate $47[14:31].push(read $49)
[17] mutate $50 = PropertyCall mutate $47[14:31]:TObject<Array>.push(read $49)
[18] Goto bb1
bb3 (block):
predecessor blocks: bb0
[21] store $54[21:31] = Array []
[22] store $56[22:31] = StoreLocal Reassign mutate x$33[22:31] = capture $54[21:31]
[23] mutate $57[23:31] = LoadLocal capture x$33[22:31]
[21] store $54[21:31]:TObject<Array> = Array []
[22] store $56[22:31]:TObject<Array> = StoreLocal Reassign store x$33[22:31]:TObject<Array> = capture $54[21:31]:TObject<Array>
[23] mutate $57[23:31]:TObject<Array> = LoadLocal capture x$33[22:31]:TObject<Array>
[24] mutate $58 = LoadLocal read props$31
[25] mutate $59 = PropertyLoad read $58.bar
[26] mutate $60 = PropertyCall mutate $57[23:31].push(read $59)
[26] mutate $60 = PropertyCall mutate $57[23:31]:TObject<Array>.push(read $59)
[27] Goto bb1
bb1 (block):
predecessor blocks: bb2 bb3
x$33[13:31]:TPhi: phi(bb2: x$33, bb3: x$33)
x$33[13:31]:TObject<Array>: phi(bb2: x$33, bb3: x$33)
[28] mutate $61:TFunction = Global mut
[29] mutate $62[29:31] = LoadLocal capture x$33[13:31]
[30] mutate $64 = Call read $61:TFunction(mutate $62[29:31])