@samitouri / QOS-React-2 / commits / 501fbd8ed8

[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 +}