[hir] infer reference effects for property call
--- This PR does not add inference for normal `CallExpression`s, since built-in functions for `Array` and `Object` are usually only valid if called with a correctly-typed `this`. If we want codegen to preserve source code semantics, Forget should only add inferred types it is confident about. This PR also adds `returnEffect` to FunctionSignature. `returnEffect = Store` if this function is known to always return a captured value from `receiver` or `args`.
Mofei Zhang committed
Mar 22, 2023 at 15:58 UTC
501fbd8ed8f036c6e0ec29290a391c5a3b375b5f
29 files changed
+759
-92
compiler/forget/src/CompilerError.ts
+13
@@ -128,6 +128,19 @@ export class CompilerError extends Error {
128
throw errors;
129
}
130
131
+ static invalidInput(reason: string, loc: SourceLocation): never {
132
+ const errors = new CompilerError();
133
+ errors.pushErrorDetail(
134
+ new CompilerErrorDetail({
135
+ codeframe: null,
136
+ loc: typeof loc === "symbol" ? null : loc,
137
+ reason,
138
+ severity: ErrorSeverity.InvalidInput,
139
+ })
140
+ );
141
+ throw errors;
142
+ }
143
+
144
constructor(...args: any[]) {
145
super(...args);
146
}
compiler/forget/src/HIR/HIR.ts
+1
-1
@@ -449,7 +449,7 @@ export type Phi = {
449
* Type inference does not currently guarantee that {@link PropertyCall.property}
450
* is a FunctionType.
451
*/
452
-type PropertyCall = {
452
+export type PropertyCall = {
453
kind: "PropertyCall";
454
receiver: Place;
455
property: Place;
compiler/forget/src/HIR/PrintHIR.ts
+1
-1
@@ -564,7 +564,7 @@ function printScope(scope: ReactiveScope | null): string {
564
return `${scope !== null ? `_@${scope.id}` : ""}`;
565
}
566
567
-function printType(type: Type): string {
567
+export function printType(type: Type): string {
568
if (type.kind === "Type") return "";
569
// TODO(mofeiZ): add debugName for generated ids
570
if (type.kind === "Object" && type.shapeId != null) {
compiler/forget/src/Inference/InferReferenceEffects.ts
+114
-17
@@ -18,12 +18,17 @@ import {
18
isObjectType,
19
Phi,
20
Place,
21
+ PropertyCall,
22
+ Type,
23
ValueKind,
24
} from "../HIR/HIR";
25
+import { FunctionSignature } from "../HIR/ObjectShape";
26
import {
27
+ printIdentifier,
28
printMixedHIR,
29
printPlace,
30
printSourceLocation,
31
+ printType,
32
} from "../HIR/PrintHIR";
33
import {
34
eachInstructionOperand,
@@ -263,6 +268,18 @@ class InferenceState {
268
* value is already frozen or is immutable.
269
*/
270
reference(place: Place, effectKind: Effect): void {
271
+ this.#referenceImpl(place, effectKind, false);
272
+ }
273
+
274
+ /**
275
+ * Throwing version of {@link reference}, which throws with an error
276
+ * if we record a mutate effect on an immutable value.
277
+ */
278
+ referenceAndCheckError(place: Place, effectKind: Effect): void {
279
+ this.#referenceImpl(place, effectKind, true);
280
+ }
281
+
282
+ #referenceImpl(place: Place, effectKind: Effect, shouldError: boolean): void {
283
const values = this.#variables.get(place.identifier.id);
284
if (values === undefined) {
285
place.effect = effectKind === Effect.Mutate ? Effect.Mutate : Effect.Read;
@@ -292,6 +309,14 @@ class InferenceState {
309
) {
310
effect = Effect.Mutate;
311
} else {
312
+ if (shouldError) {
313
+ CompilerError.invalidInput(
314
+ `InferReferenceEffects: inferred mutation of known immutable value ${printIdentifier(
315
+ place.identifier
316
+ )}${printType(place.identifier.type)} (${valueKind})`,
317
+ place.loc
318
+ );
319
+ }
320
effect = Effect.Read;
321
}
322
break;
@@ -554,7 +579,7 @@ function mergeValues(a: ValueKind, b: ValueKind): ValueKind {
579
* recording references on the @param state according to JS semantics.
580
*/
581
function inferBlock(
557
- _env: Environment,
582
+ env: Environment,
583
state: InferenceState,
584
block: BasicBlock
585
): void {
@@ -660,25 +685,39 @@ function inferBlock(
685
continue;
686
}
687
case "PropertyCall": {
663
- if (!state.isDefined(instrValue.receiver)) {
664
- // TODO @josephsavona: improve handling of globals
665
- const value: InstructionValue = {
666
- kind: "Primitive",
667
- loc: instrValue.loc,
668
- value: undefined,
669
- };
670
- state.initialize(value, ValueKind.Frozen);
671
- state.define(instrValue.receiver, value);
672
- }
688
+ invariant(
689
+ state.isDefined(instrValue.receiver),
690
+ "[InferReferenceEffects] Internal error: receiver of PropertyCall should have been defined by corresponding PropertyLoad"
691
+ );
692
674
- state.reference(instrValue.receiver, Effect.Mutate);
693
state.reference(instrValue.property, Effect.Read);
676
- for (const arg of instrValue.args) {
677
- if (arg.kind === "Identifier") {
678
- state.reference(arg, Effect.Mutate);
679
- } else {
680
- state.reference(arg.place, Effect.Mutate);
694
+
695
+ const signature = getFunctionCallSignature(
696
+ env,
697
+ instrValue.property.identifier.type
698
+ );
699
+ if (signature !== null) {
700
+ const effects = getFunctionCallEffects(
701
+ instrValue,
702
+ signature,
703
+ Effect.Mutate
704
+ );
705
+ for (const [place, effect] of effects) {
706
+ state.referenceAndCheckError(place, effect);
707
}
708
+ state.referenceAndCheckError(
709
+ instrValue.receiver,
710
+ signature.calleeEffect
711
+ );
712
+ } else {
713
+ for (const arg of instrValue.args) {
714
+ if (arg.kind === "Identifier") {
715
+ state.reference(arg, Effect.Mutate);
716
+ } else {
717
+ state.reference(arg.place, Effect.Mutate);
718
+ }
719
+ }
720
+ state.reference(instrValue.receiver, Effect.Mutate);
721
}
722
state.initialize(instrValue, ValueKind.Mutable);
723
state.define(instr.lvalue, instrValue);
@@ -904,3 +943,61 @@ function hasContextRefOperand(
943
}
944
return false;
945
}
946
+
947
+function getFunctionCallSignature(
948
+ env: Environment,
949
+ type: Type
950
+): FunctionSignature | null {
951
+ if (type.kind !== "Function") {
952
+ return null;
953
+ }
954
+ return env.getFunctionSignature(type);
955
+}
956
+
957
+/**
958
+ * Make a best attempt at matching arguments of a PropertyCall to its FunctionSignature,
959
+ * calling back to `defaultEffect` when we are unable to.
960
+ *
961
+ * @param fn
962
+ * @param sig
963
+ * @param defaultEffect In the case that inference fails, all arguments will be inferred
964
+ * as defaultEffect
965
+ * @returns Inferred effects of function arguments
966
+ */
967
+function getFunctionCallEffects(
968
+ fn: PropertyCall,
969
+ sig: FunctionSignature,
970
+ defaultEffect: Effect
971
+): Array<[Place, Effect]> {
972
+ const inferredEffects: Array<[Place, Effect | null]> = fn.args.map(
973
+ (arg, idx) => {
974
+ const argPlace = arg.kind === "Identifier" ? arg : arg.place;
975
+ if (idx < sig.positionalParams.length) {
976
+ // Only infer effects when there is a direct mapping positional arg --> positional param
977
+ // Otherwise, return null to indicate inference failed
978
+ if (arg.kind === "Identifier") {
979
+ return [argPlace, sig.positionalParams[idx]];
980
+ } else {
981
+ return [argPlace, null];
982
+ }
983
+ } else if (sig.restParam !== null) {
984
+ return [argPlace, sig.restParam];
985
+ } else {
986
+ // If there are more arguments than positional arguments, we'll also assume
987
+ // that inference failed
988
+ return [argPlace, null];
989
+ }
990
+ }
991
+ );
992
+
993
+ let results: Array<[Place, Effect]>;
994
+ if (inferredEffects.some(([_, effect]) => effect === null)) {
995
+ // If inference failed for any argument, give up on inference for all arguments
996
+ results = inferredEffects.map(([arg, _]) => {
997
+ return [arg, defaultEffect];
998
+ });
999
+ } else {
1000
+ results = inferredEffects as Array<[Place, Effect]>;
1001
+ }
1002
+ return results;
1003
+}
compiler/forget/src/__tests__/fixtures/compiler/array-at-closure.expect.md
new
+52
@@ -0,0 +1,52 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+function Component(props) {
6
+ const x = foo(props.x);
7
+ const fn = function () {
8
+ const arr = [...bar(props)];
9
+ return arr.at(x);
10
+ };
11
+ const fnResult = fn();
12
+ return fnResult;
13
+}
14
+
15
+```
16
+
17
+## Code
18
+
19
+```javascript
20
+function Component(props) {
21
+ const $ = React.unstable_useMemoCache(5);
22
+ const c_0 = $[0] !== props.x;
23
+ let t0;
24
+ if (c_0) {
25
+ t0 = foo(props.x);
26
+ $[0] = props.x;
27
+ $[1] = t0;
28
+ } else {
29
+ t0 = $[1];
30
+ }
31
+ const x = t0;
32
+ const c_2 = $[2] !== props;
33
+ const c_3 = $[3] !== x;
34
+ let t1;
35
+ if (c_2 || c_3) {
36
+ const fn = function () {
37
+ const arr = [...bar(props)];
38
+ return arr.at(x);
39
+ };
40
+ t1 = fn();
41
+ $[2] = props;
42
+ $[3] = x;
43
+ $[4] = t1;
44
+ } else {
45
+ t1 = $[4];
46
+ }
47
+ const fnResult = t1;
48
+ return fnResult;
49
+}
50
+
51
+```
52
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/array-at-closure.js
new
+9
@@ -0,0 +1,9 @@
1
+function Component(props) {
2
+ const x = foo(props.x);
3
+ const fn = function () {
4
+ const arr = [...bar(props)];
5
+ return arr.at(x);
6
+ };
7
+ const fnResult = fn();
8
+ return fnResult;
9
+}
compiler/forget/src/__tests__/fixtures/compiler/array-at-effect.expect.md
new
+70
@@ -0,0 +1,70 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// arrayInstance.at should have the following effects:
6
+// - read on arg0
7
+// - read on receiver
8
+// - mutate on lvalue
9
+function ArrayAtTest(props) {
10
+ const arr = [foo(props.x)];
11
+ const result = arr.at(bar(props.y));
12
+ return result;
13
+}
14
+
15
+```
16
+
17
+## Code
18
+
19
+```javascript
20
+// arrayInstance.at should have the following effects:
21
+// - read on arg0
22
+// - read on receiver
23
+// - mutate on lvalue
24
+function ArrayAtTest(props) {
25
+ const $ = React.unstable_useMemoCache(9);
26
+ const c_0 = $[0] !== props.x;
27
+ let t0;
28
+ if (c_0) {
29
+ t0 = foo(props.x);
30
+ $[0] = props.x;
31
+ $[1] = t0;
32
+ } else {
33
+ t0 = $[1];
34
+ }
35
+ const c_2 = $[2] !== t0;
36
+ let t1;
37
+ if (c_2) {
38
+ t1 = [t0];
39
+ $[2] = t0;
40
+ $[3] = t1;
41
+ } else {
42
+ t1 = $[3];
43
+ }
44
+ const arr = t1;
45
+ const c_4 = $[4] !== props.y;
46
+ let t2;
47
+ if (c_4) {
48
+ t2 = bar(props.y);
49
+ $[4] = props.y;
50
+ $[5] = t2;
51
+ } else {
52
+ t2 = $[5];
53
+ }
54
+ const c_6 = $[6] !== arr;
55
+ const c_7 = $[7] !== t2;
56
+ let t3;
57
+ if (c_6 || c_7) {
58
+ t3 = arr.at(t2);
59
+ $[6] = arr;
60
+ $[7] = t2;
61
+ $[8] = t3;
62
+ } else {
63
+ t3 = $[8];
64
+ }
65
+ const result = t3;
66
+ return result;
67
+}
68
+
69
+```
70
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/array-at-effect.js
new
+9
@@ -0,0 +1,9 @@
1
+// arrayInstance.at should have the following effects:
2
+// - read on arg0
3
+// - read on receiver
4
+// - mutate on lvalue
5
+function ArrayAtTest(props) {
6
+ const arr = [foo(props.x)];
7
+ const result = arr.at(bar(props.y));
8
+ return result;
9
+}
compiler/forget/src/__tests__/fixtures/compiler/array-at-mutate-after-capture.expect.md
new
+47
@@ -0,0 +1,47 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// x's mutable range should extend to `mutate(y)`
6
+
7
+function Component(props) {
8
+ let x = [42, {}];
9
+ const idx = foo(props.b);
10
+ let y = x.at(idx);
11
+ mutate(y);
12
+
13
+ return x;
14
+}
15
+
16
+```
17
+
18
+## Code
19
+
20
+```javascript
21
+// x's mutable range should extend to `mutate(y)`
22
+
23
+function Component(props) {
24
+ const $ = React.unstable_useMemoCache(2);
25
+ let t0;
26
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
27
+ t0 = {};
28
+ $[0] = t0;
29
+ } else {
30
+ t0 = $[0];
31
+ }
32
+ let t1;
33
+ if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
34
+ t1 = [42, t0];
35
+ $[1] = t1;
36
+ } else {
37
+ t1 = $[1];
38
+ }
39
+ const x = t1;
40
+ const idx = foo(props.b);
41
+ const y = x.at(idx);
42
+ mutate(y);
43
+ return x;
44
+}
45
+
46
+```
47
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/array-at-mutate-after-capture.js
new
+10
@@ -0,0 +1,10 @@
1
+// x's mutable range should extend to `mutate(y)`
2
+
3
+function Component(props) {
4
+ let x = [42, {}];
5
+ const idx = foo(props.b);
6
+ let y = x.at(idx);
7
+ mutate(y);
8
+
9
+ return x;
10
+}
compiler/forget/src/__tests__/fixtures/compiler/array-property-call.expect.md
+31
-25
@@ -16,43 +16,49 @@ function Component(props) {
16
17
```javascript
18
function Component(props) {
19
- const $ = React.unstable_useMemoCache(10);
19
+ const $ = React.unstable_useMemoCache(11);
20
const c_0 = $[0] !== props.a;
21
const c_1 = $[1] !== props.b;
22
- const c_2 = $[2] !== props.c;
22
let t0;
23
let a;
25
- let x;
26
- if (c_0 || c_1 || c_2) {
24
+ if (c_0 || c_1) {
25
a = [props.a, props.b, "hello"];
28
- x = a.push(42);
29
- t0 = a.at(props.c);
26
+ t0 = a.push(42);
27
$[0] = props.a;
28
$[1] = props.b;
32
- $[2] = props.c;
33
- $[3] = t0;
34
- $[4] = a;
35
- $[5] = x;
29
+ $[2] = t0;
30
+ $[3] = a;
31
} else {
37
- t0 = $[3];
38
- a = $[4];
39
- x = $[5];
32
+ t0 = $[2];
33
+ a = $[3];
34
}
41
- const y = t0;
42
- const c_6 = $[6] !== a;
43
- const c_7 = $[7] !== x;
44
- const c_8 = $[8] !== y;
35
+ const x = t0;
36
+ const c_4 = $[4] !== a;
37
+ const c_5 = $[5] !== props.c;
38
let t1;
46
- if (c_6 || c_7 || c_8) {
47
- t1 = { a, x, y };
48
- $[6] = a;
49
- $[7] = x;
50
- $[8] = y;
51
- $[9] = t1;
39
+ if (c_4 || c_5) {
40
+ t1 = a.at(props.c);
41
+ $[4] = a;
42
+ $[5] = props.c;
43
+ $[6] = t1;
44
+ } else {
45
+ t1 = $[6];
46
+ }
47
+ const y = t1;
48
+ const c_7 = $[7] !== a;
49
+ const c_8 = $[8] !== x;
50
+ const c_9 = $[9] !== y;
51
+ let t2;
52
+ if (c_7 || c_8 || c_9) {
53
+ t2 = { a, x, y };
54
+ $[7] = a;
55
+ $[8] = x;
56
+ $[9] = y;
57
+ $[10] = t2;
58
} else {
53
- t1 = $[9];
59
+ t2 = $[10];
60
}
55
- return t1;
61
+ return t2;
62
}
63
64
```
compiler/forget/src/__tests__/fixtures/compiler/array-push-effect.expect.md
new
+71
@@ -0,0 +1,71 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// arrayInstance.push should have the following effects:
6
+// - read on all args (rest parameter)
7
+// - mutate on receiver
8
+function Component(props) {
9
+ const x = foo(props.x);
10
+ const y = { y: props.y };
11
+ const arr = [];
12
+ arr.push({});
13
+ arr.push(x, y);
14
+ return arr;
15
+}
16
+
17
+```
18
+
19
+## Code
20
+
21
+```javascript
22
+// arrayInstance.push should have the following effects:
23
+// - read on all args (rest parameter)
24
+// - mutate on receiver
25
+function Component(props) {
26
+ const $ = React.unstable_useMemoCache(8);
27
+ const c_0 = $[0] !== props.x;
28
+ let t0;
29
+ if (c_0) {
30
+ t0 = foo(props.x);
31
+ $[0] = props.x;
32
+ $[1] = t0;
33
+ } else {
34
+ t0 = $[1];
35
+ }
36
+ const x = t0;
37
+ const c_2 = $[2] !== props.y;
38
+ let t1;
39
+ if (c_2) {
40
+ t1 = { y: props.y };
41
+ $[2] = props.y;
42
+ $[3] = t1;
43
+ } else {
44
+ t1 = $[3];
45
+ }
46
+ const y = t1;
47
+ const c_4 = $[4] !== x;
48
+ const c_5 = $[5] !== y;
49
+ let arr;
50
+ if (c_4 || c_5) {
51
+ arr = [];
52
+ let t2;
53
+ if ($[7] === Symbol.for("react.memo_cache_sentinel")) {
54
+ t2 = {};
55
+ $[7] = t2;
56
+ } else {
57
+ t2 = $[7];
58
+ }
59
+ arr.push(t2);
60
+ arr.push(x, y);
61
+ $[4] = x;
62
+ $[5] = y;
63
+ $[6] = arr;
64
+ } else {
65
+ arr = $[6];
66
+ }
67
+ return arr;
68
+}
69
+
70
+```
71
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/array-push-effect.js
new
+11
@@ -0,0 +1,11 @@
1
+// arrayInstance.push should have the following effects:
2
+// - read on all args (rest parameter)
3
+// - mutate on receiver
4
+function Component(props) {
5
+ const x = foo(props.x);
6
+ const y = { y: props.y };
7
+ const arr = [];
8
+ arr.push({});
9
+ arr.push(x, y);
10
+ return arr;
11
+}
compiler/forget/src/__tests__/fixtures/compiler/assignment-variations-complex-lvalue-array.expect.md
+15
-7
@@ -15,16 +15,24 @@ function foo() {
15
16
```javascript
17
function foo() {
18
- const $ = React.unstable_useMemoCache(1);
19
- let a;
18
+ const $ = React.unstable_useMemoCache(2);
19
+ let t0;
20
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
21
- a = [[1]];
22
- const first = a.at(0);
23
- first.set(0, 2);
24
- $[0] = a;
21
+ t0 = [1];
22
+ $[0] = t0;
23
} else {
26
- a = $[0];
24
+ t0 = $[0];
25
}
26
+ let t1;
27
+ if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
28
+ t1 = [t0];
29
+ $[1] = t1;
30
+ } else {
31
+ t1 = $[1];
32
+ }
33
+ const a = t1;
34
+ const first = a.at(0);
35
+ first.set(0, 2);
36
return a;
37
}
38
compiler/forget/src/__tests__/fixtures/compiler/error.mutate-after-aliased-freeze.expect.md
new
+31
@@ -0,0 +1,31 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+function Component(props) {
6
+ let x = [];
7
+ let y = x;
8
+
9
+ if (props.p1) {
10
+ x = [];
11
+ }
12
+
13
+ let _ = <Component x={x} />;
14
+
15
+ // y is MaybeFrozen at this point, since it may alias to x
16
+ // (which is the above line freezes)
17
+ y.push(props.p2);
18
+
19
+ return <Component x={x} y={y} />;
20
+}
21
+
22
+```
23
+
24
+
25
+## Error
26
+
27
+```
28
+[ReactForget] InvalidInput: InferReferenceEffects: inferred mutation of known immutable value $42:TObject<Array> (frozen) (13:13)
29
+```
30
+
31
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/error.mutate-after-aliased-freeze.js
new
+16
@@ -0,0 +1,16 @@
1
+function Component(props) {
2
+ let x = [];
3
+ let y = x;
4
+
5
+ if (props.p1) {
6
+ x = [];
7
+ }
8
+
9
+ let _ = <Component x={x} />;
10
+
11
+ // y is MaybeFrozen at this point, since it may alias to x
12
+ // (which is the above line freezes)
13
+ y.push(props.p2);
14
+
15
+ return <Component x={x} y={y} />;
16
+}
compiler/forget/src/__tests__/fixtures/compiler/error.mutate-after-freeze.expect.md
new
+25
@@ -0,0 +1,25 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+function Component(props) {
6
+ let x = [];
7
+
8
+ let _ = <Component x={x} />;
9
+
10
+ // x is Frozen at this point
11
+ x.push(props.p2);
12
+
13
+ return <div>{_}</div>;
14
+}
15
+
16
+```
17
+
18
+
19
+## Error
20
+
21
+```
22
+[ReactForget] InvalidInput: InferReferenceEffects: inferred mutation of known immutable value $25:TObject<Array> (frozen) (7:7)
23
+```
24
+
25
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/error.mutate-after-freeze.js
new
+10
@@ -0,0 +1,10 @@
1
+function Component(props) {
2
+ let x = [];
3
+
4
+ let _ = <Component x={x} />;
5
+
6
+ // x is Frozen at this point
7
+ x.push(props.p2);
8
+
9
+ return <div>{_}</div>;
10
+}
compiler/forget/src/__tests__/fixtures/compiler/overlapping-scopes-within-block.expect.md
+14
-4
@@ -21,7 +21,7 @@ function foo(a, b, c) {
21
22
```javascript
23
function foo(a, b, c) {
24
- const $ = React.unstable_useMemoCache(4);
24
+ const $ = React.unstable_useMemoCache(7);
25
const c_0 = $[0] !== a;
26
const c_1 = $[1] !== b;
27
const c_2 = $[2] !== c;
@@ -29,9 +29,19 @@ function foo(a, b, c) {
29
if (c_0 || c_1 || c_2) {
30
x = [];
31
if (a) {
32
- const y = [];
33
- if (b) {
34
- y.push(c);
32
+ const c_4 = $[4] !== b;
33
+ const c_5 = $[5] !== c;
34
+ let y;
35
+ if (c_4 || c_5) {
36
+ y = [];
37
+ if (b) {
38
+ y.push(c);
39
+ }
40
+ $[4] = b;
41
+ $[5] = c;
42
+ $[6] = y;
43
+ } else {
44
+ y = $[6];
45
}
46
47
x.push(y);
compiler/forget/src/__tests__/fixtures/compiler/reactive-scope-grouping.expect.md
+9
-2
@@ -18,14 +18,21 @@ function foo() {
18
19
```javascript
20
function foo() {
21
- const $ = React.unstable_useMemoCache(2);
21
+ const $ = React.unstable_useMemoCache(3);
22
let x;
23
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
24
x = {};
25
let y;
26
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
27
y = [];
28
- const z = {};
28
+ let t0;
29
+ if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
30
+ t0 = {};
31
+ $[2] = t0;
32
+ } else {
33
+ t0 = $[2];
34
+ }
35
+ const z = t0;
36
y.push(z);
37
$[1] = y;
38
} else {
compiler/forget/src/__tests__/fixtures/compiler/reassignment-conditional.expect.md
+31
-25
@@ -11,7 +11,6 @@ function Component(props) {
11
x = [];
12
}
13
14
- let _ = <Component x={x} />;
14
y.push(props.p2);
15
16
return <Component x={x} y={y} />;
@@ -23,40 +22,47 @@ function Component(props) {
22
23
```javascript
24
function Component(props) {
26
- const $ = React.unstable_useMemoCache(6);
25
+ const $ = React.unstable_useMemoCache(9);
26
const c_0 = $[0] !== props.p0;
27
+ const c_1 = $[1] !== props.p1;
28
+ const c_2 = $[2] !== props.p2;
29
let x;
29
- if (c_0) {
30
+ let y;
31
+ if (c_0 || c_1 || c_2) {
32
x = [];
33
x.push(props.p0);
34
+ y = x;
35
+ if (props.p1) {
36
+ let t0;
37
+ if ($[5] === Symbol.for("react.memo_cache_sentinel")) {
38
+ t0 = [];
39
+ $[5] = t0;
40
+ } else {
41
+ t0 = $[5];
42
+ }
43
+ x = t0;
44
+ }
45
+
46
+ y.push(props.p2);
47
$[0] = props.p0;
33
- $[1] = x;
48
+ $[1] = props.p1;
49
+ $[2] = props.p2;
50
+ $[3] = x;
51
+ $[4] = y;
52
} else {
35
- x = $[1];
53
+ x = $[3];
54
+ y = $[4];
55
}
37
- const y = x;
38
- if (props.p1) {
39
- let t0;
40
- if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
41
- t0 = [];
42
- $[2] = t0;
43
- } else {
44
- t0 = $[2];
45
- }
46
- x = t0;
47
- }
48
-
49
- y.push(props.p2);
50
- const c_3 = $[3] !== x;
51
- const c_4 = $[4] !== y;
56
+ const c_6 = $[6] !== x;
57
+ const c_7 = $[7] !== y;
58
let t1;
53
- if (c_3 || c_4) {
59
+ if (c_6 || c_7) {
60
t1 = <Component x={x} y={y} />;
55
- $[3] = x;
56
- $[4] = y;
57
- $[5] = t1;
61
+ $[6] = x;
62
+ $[7] = y;
63
+ $[8] = t1;
64
} else {
59
- t1 = $[5];
65
+ t1 = $[8];
66
}
67
return t1;
68
}
compiler/forget/src/__tests__/fixtures/compiler/reassignment-conditional.js
-1
@@ -7,7 +7,6 @@ function Component(props) {
7
x = [];
8
}
9
10
- let _ = <Component x={x} />;
10
y.push(props.p2);
11
12
return <Component x={x} y={y} />;
compiler/forget/src/__tests__/fixtures/hir/array-at-closure.expect.md
new
+47
@@ -0,0 +1,47 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+function Component(props) {
6
+ const x = foo(props.x);
7
+ const fn = function () {
8
+ const arr = [...bar(props)];
9
+ return arr.at(x);
10
+ };
11
+ const fnResult = fn();
12
+ return fnResult;
13
+}
14
+
15
+```
16
+
17
+## HIR
18
+
19
+```javascript
20
+bb0 (block):
21
+ [1] mutate $31:TFunction = Global foo
22
+ [2] mutate $32 = LoadLocal read props$30
23
+ [3] mutate $33 = PropertyLoad read $32.x
24
+ [4] mutate $34 = Call read $31:TFunction(read $33)
25
+ [5] store $36 = StoreLocal Const mutate x$35 = capture $34
26
+ [6] mutate $37 = LoadLocal read props$30
27
+ [7] mutate $38 = LoadLocal capture x$35
28
+ [8] store $39[8:12]:TFunction = Function @deps[read $37,read $38]:
29
+ bb0 (block):
30
+ [1] mutate $49:TFunction = Global bar
31
+ [2] mutate $50[2:4] = LoadLocal capture props$47[0:4]
32
+ [3] mutate $51 = Call read $49:TFunction(mutate $50[2:4])
33
+ [4] store $52:TObject<Array> = Array [...capture $51]
34
+ [5] store $54:TObject<Array> = StoreLocal Const store arr$53:TObject<Array> = capture $52:TObject<Array>
35
+ [6] mutate $55:TObject<Array> = LoadLocal capture arr$53:TObject<Array>
36
+ [7] mutate $56 = LoadLocal capture x$48
37
+ [8] mutate $57:TFunction<<generated_0>> = PropertyLoad read $55:TObject<Array>.at
38
+ [9] mutate $58 = PropertyCall read $55:TObject<Array>.read $57:TFunction<<generated_0>>(read $56)
39
+ [10] Return freeze $58
40
+ [9] store $41[9:12]:TFunction = StoreLocal Const mutate fn$40[9:12]:TFunction = capture $39[8:12]:TFunction
41
+ [10] mutate $42[10:12]:TFunction = LoadLocal capture fn$40[9:12]:TFunction
42
+ [11] mutate $43 = Call mutate $42[10:12]:TFunction()
43
+ [12] store $45 = StoreLocal Const mutate fnResult$44 = capture $43
44
+ [13] mutate $46 = LoadLocal capture fnResult$44
45
+ [14] Return freeze $46
46
+```
47
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/array-at-closure.js
new
+9
@@ -0,0 +1,9 @@
1
+function Component(props) {
2
+ const x = foo(props.x);
3
+ const fn = function () {
4
+ const arr = [...bar(props)];
5
+ return arr.at(x);
6
+ };
7
+ const fnResult = fn();
8
+ return fnResult;
9
+}
compiler/forget/src/__tests__/fixtures/hir/array-at-effect.expect.md
new
+38
@@ -0,0 +1,38 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// arrayInstance.at should have the following effects:
6
+// - read on arg0
7
+// - read on receiver
8
+// - mutate on lvalue
9
+function ArrayAtTest(props) {
10
+ const arr = [foo(props.x)];
11
+ const result = arr.at(bar(props.y));
12
+ return result;
13
+}
14
+
15
+```
16
+
17
+## HIR
18
+
19
+```javascript
20
+bb0 (block):
21
+ [1] mutate $20:TFunction = Global foo
22
+ [2] mutate $21 = LoadLocal read props$19
23
+ [3] mutate $22 = PropertyLoad read $21.x
24
+ [4] mutate $23 = Call read $20:TFunction(read $22)
25
+ [5] store $24:TObject<Array> = Array [capture $23]
26
+ [6] store $26:TObject<Array> = StoreLocal Const store arr$25:TObject<Array> = capture $24:TObject<Array>
27
+ [7] mutate $27:TObject<Array> = LoadLocal capture arr$25:TObject<Array>
28
+ [8] mutate $28:TFunction = Global bar
29
+ [9] mutate $29 = LoadLocal read props$19
30
+ [10] mutate $30 = PropertyLoad read $29.y
31
+ [11] mutate $31 = Call read $28:TFunction(read $30)
32
+ [12] mutate $32:TFunction<<generated_0>> = PropertyLoad read $27:TObject<Array>.at
33
+ [13] mutate $33 = PropertyCall read $27:TObject<Array>.read $32:TFunction<<generated_0>>(read $31)
34
+ [14] store $35 = StoreLocal Const mutate result$34 = capture $33
35
+ [15] mutate $36 = LoadLocal capture result$34
36
+ [16] Return freeze $36
37
+```
38
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/array-at-effect.js
new
+9
@@ -0,0 +1,9 @@
1
+// arrayInstance.at should have the following effects:
2
+// - read on arg0
3
+// - read on receiver
4
+// - mutate on lvalue
5
+function ArrayAtTest(props) {
6
+ const arr = [foo(props.x)];
7
+ const result = arr.at(bar(props.y));
8
+ return result;
9
+}
compiler/forget/src/__tests__/fixtures/hir/array-property-call.expect.md
+9
-9
@@ -21,20 +21,20 @@ bb0 (block):
21
[3] mutate $30 = LoadLocal read props$27
22
[4] mutate $31 = PropertyLoad read $30.b
23
[5] mutate $32:TPrimitive = "hello"
24
- [6] store $33[6:18]:TObject<Array> = Array [read $29, read $31, read $32:TPrimitive]
25
- [7] store $35[7:18]:TObject<Array> = StoreLocal Const store a$34[7:18]:TObject<Array> = capture $33[6:18]:TObject<Array>
26
- [8] mutate $36[8:18]:TObject<Array> = LoadLocal capture a$34[7:18]:TObject<Array>
24
+ [6] store $33[6:12]:TObject<Array> = Array [read $29, read $31, read $32:TPrimitive]
25
+ [7] store $35[7:12]:TObject<Array> = StoreLocal Const store a$34[7:12]:TObject<Array> = capture $33[6:12]:TObject<Array>
26
+ [8] mutate $36[8:12]:TObject<Array> = LoadLocal capture a$34[7:12]:TObject<Array>
27
[9] mutate $37:TPrimitive = 42
28
- [10] mutate $38[10:18]:TFunction<<generated_2>> = PropertyLoad read $36[8:18]:TObject<Array>.push
29
- [11] mutate $39:TPrimitive = PropertyCall mutate $36[8:18]:TObject<Array>.read $38[10:18]:TFunction<<generated_2>>(read $37:TPrimitive)
28
+ [10] mutate $38[10:12]:TFunction<<generated_2>> = PropertyLoad read $36[8:12]:TObject<Array>.push
29
+ [11] mutate $39:TPrimitive = PropertyCall mutate $36[8:12]:TObject<Array>.read $38[10:12]:TFunction<<generated_2>>(read $37:TPrimitive)
30
[12] store $41:TPrimitive = StoreLocal Const mutate x$40:TPrimitive = capture $39:TPrimitive
31
- [13] mutate $42[13:18]:TObject<Array> = LoadLocal capture a$34[7:18]:TObject<Array>
31
+ [13] mutate $42:TObject<Array> = LoadLocal capture a$34[7:12]:TObject<Array>
32
[14] mutate $43 = LoadLocal read props$27
33
[15] mutate $44 = PropertyLoad read $43.c
34
- [16] mutate $45[16:18]:TFunction<<generated_0>> = PropertyLoad read $42[13:18]:TObject<Array>.at
35
- [17] mutate $46 = PropertyCall mutate $42[13:18]:TObject<Array>.read $45[16:18]:TFunction<<generated_0>>(read $44)
34
+ [16] mutate $45:TFunction<<generated_0>> = PropertyLoad read $42:TObject<Array>.at
35
+ [17] mutate $46 = PropertyCall read $42:TObject<Array>.read $45:TFunction<<generated_0>>(read $44)
36
[18] store $48 = StoreLocal Const mutate y$47 = capture $46
37
- [19] mutate $49:TObject<Array> = LoadLocal capture a$34[7:18]:TObject<Array>
37
+ [19] mutate $49:TObject<Array> = LoadLocal capture a$34[7:12]:TObject<Array>
38
[20] mutate $50:TPrimitive = LoadLocal capture x$40:TPrimitive
39
[21] mutate $51 = LoadLocal capture y$47
40
[22] store $52:TObject<Object> = Object { a: capture $49:TObject<Array>, x: capture $50:TPrimitive, y: capture $51 }
compiler/forget/src/__tests__/fixtures/hir/array-push-effect.expect.md
new
+46
@@ -0,0 +1,46 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// arrayInstance.push should have the following effects:
6
+// - read on all args (rest parameter)
7
+// - mutate on receiver
8
+function Component(props) {
9
+ const x = foo(props.x);
10
+ const y = { y: props.y };
11
+ const arr = [];
12
+ arr.push({});
13
+ arr.push(x, y);
14
+ return arr;
15
+}
16
+
17
+```
18
+
19
+## HIR
20
+
21
+```javascript
22
+bb0 (block):
23
+ [1] mutate $27:TFunction = Global foo
24
+ [2] mutate $28 = LoadLocal read props$26
25
+ [3] mutate $29 = PropertyLoad read $28.x
26
+ [4] mutate $30 = Call read $27:TFunction(read $29)
27
+ [5] store $32 = StoreLocal Const mutate x$31 = capture $30
28
+ [6] mutate $33 = LoadLocal read props$26
29
+ [7] mutate $34 = PropertyLoad read $33.y
30
+ [8] store $35:TObject<Object> = Object { y: read $34 }
31
+ [9] store $37:TObject<Object> = StoreLocal Const store y$36:TObject<Object> = capture $35:TObject<Object>
32
+ [10] store $38[10:21]:TObject<Array> = Array []
33
+ [11] store $40[11:21]:TObject<Array> = StoreLocal Const store arr$39[11:21]:TObject<Array> = capture $38[10:21]:TObject<Array>
34
+ [12] mutate $41[12:21]:TObject<Array> = LoadLocal capture arr$39[11:21]:TObject<Array>
35
+ [13] store $42:TObject<Object> = Object { }
36
+ [14] mutate $43[14:21]:TFunction<<generated_2>> = PropertyLoad read $41[12:21]:TObject<Array>.push
37
+ [15] mutate $44:TPrimitive = PropertyCall mutate $41[12:21]:TObject<Array>.read $43[14:21]:TFunction<<generated_2>>(capture $42:TObject<Object>)
38
+ [16] mutate $45[16:21]:TObject<Array> = LoadLocal capture arr$39[11:21]:TObject<Array>
39
+ [17] mutate $46 = LoadLocal capture x$31
40
+ [18] mutate $47:TObject<Object> = LoadLocal capture y$36:TObject<Object>
41
+ [19] mutate $48[19:21]:TFunction<<generated_2>> = PropertyLoad read $45[16:21]:TObject<Array>.push
42
+ [20] mutate $49:TPrimitive = PropertyCall mutate $45[16:21]:TObject<Array>.read $48[19:21]:TFunction<<generated_2>>(capture $46, capture $47:TObject<Object>)
43
+ [21] mutate $50:TObject<Array> = LoadLocal capture arr$39[11:21]:TObject<Array>
44
+ [22] Return freeze $50:TObject<Array>
45
+```
46
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/array-push-effect.js
new
+11
@@ -0,0 +1,11 @@
1
+// arrayInstance.push should have the following effects:
2
+// - read on all args (rest parameter)
3
+// - mutate on receiver
4
+function Component(props) {
5
+ const x = foo(props.x);
6
+ const y = { y: props.y };
7
+ const arr = [];
8
+ arr.push({});
9
+ arr.push(x, y);
10
+ return arr;
11
+}