@samitouri / QOS-React-2 / commits / bbb5840e73

Handle phi nodes for ValidateFrozenLambdas

Extends ValidateFrozenLambdas to find cases where a mutable lambda flows into a phi node and the phi is later frozen.

Joe Savona committed Jun 20, 2023 at 15:16 UTC bbb5840e73f384ef735cfe1f2b1dafcf2109477e
3 files changed +95 -29
compiler/forget/packages/babel-plugin-react-forget/src/HIR/ValidateFrozenLambdas.ts
+48 -29
@@ -51,27 +51,54 @@ export function validateFrozenLambdas(fn: HIRFunction): void {
51
52 const errors = new CompilerError();
53 for (const [, block] of fn.body.blocks) {
54 + for (const phi of block.phis) {
55 + for (const [, operand] of phi.operands) {
56 + const resolvedId = state.temporaries.get(operand.id) ?? operand.id;
57 + const lambda = state.lambdas.get(resolvedId);
58 + if (lambda !== undefined) {
59 + state.lambdas.set(phi.id.id, lambda);
60 + break;
61 + }
62 + }
63 + }
64 for (const instr of block.instructions) {
55 - if (instr.value.kind === "FunctionExpression") {
56 - state.lambdas.set(instr.lvalue.identifier.id, instr.value);
57 - } else if (instr.value.kind === "LoadLocal") {
58 - const resolvedId =
59 - state.temporaries.get(instr.value.place.identifier.id) ??
60 - instr.value.place.identifier.id;
61 - state.temporaries.set(instr.lvalue.identifier.id, resolvedId);
62 - } else if (instr.value.kind === "StoreLocal") {
63 - const resolvedId =
64 - state.temporaries.get(instr.value.value.identifier.id) ??
65 - instr.value.value.identifier.id;
66 - state.temporaries.set(
67 - instr.value.lvalue.place.identifier.id,
68 - resolvedId
69 - );
70 - } else {
71 - for (const operand of eachInstructionValueOperand(instr.value)) {
72 - const operandError = validateOperand(operand, state);
73 - if (operandError !== null) {
74 - errors.pushErrorDetail(operandError);
65 + switch (instr.value.kind) {
66 + case "FunctionExpression": {
67 + if (
68 + instr.value.dependencies.some(
69 + (place) =>
70 + place.effect === Effect.Mutate &&
71 + !isRefValueType(place.identifier) &&
72 + !isUseRefType(place.identifier)
73 + )
74 + ) {
75 + state.lambdas.set(instr.lvalue.identifier.id, instr.value);
76 + }
77 + break;
78 + }
79 + case "LoadLocal": {
80 + const resolvedId =
81 + state.temporaries.get(instr.value.place.identifier.id) ??
82 + instr.value.place.identifier.id;
83 + state.temporaries.set(instr.lvalue.identifier.id, resolvedId);
84 + break;
85 + }
86 + case "StoreLocal": {
87 + const resolvedId =
88 + state.temporaries.get(instr.value.value.identifier.id) ??
89 + instr.value.value.identifier.id;
90 + state.temporaries.set(
91 + instr.value.lvalue.place.identifier.id,
92 + resolvedId
93 + );
94 + break;
95 + }
96 + default: {
97 + for (const operand of eachInstructionValueOperand(instr.value)) {
98 + const operandError = validateOperand(operand, state);
99 + if (operandError !== null) {
100 + errors.pushErrorDetail(operandError);
101 + }
102 }
103 }
104 }
@@ -101,15 +128,7 @@ function validateOperand(
128 const operandId =
129 state.temporaries.get(operand.identifier.id) ?? operand.identifier.id;
130 const lambda = state.lambdas.get(operandId);
104 - if (
105 - lambda !== undefined &&
106 - lambda.dependencies.some(
107 - (place) =>
108 - place.effect === Effect.Mutate &&
109 - !isRefValueType(place.identifier) &&
110 - !isUseRefType(place.identifier)
111 - )
112 - ) {
131 + if (lambda !== undefined) {
132 return new CompilerErrorDetail({
133 codeframe: null,
134 description: null,
compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.invalid-freeze-conditionally-mutable-lambda.expect.md new
+31
@@ -0,0 +1,31 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function Component(props) {
6 + const x = {};
7 + let fn;
8 + if (props.cond) {
9 + // mutable
10 + fn = () => {
11 + x.value = props.value;
12 + };
13 + } else {
14 + // immutable
15 + fn = () => {
16 + x.value;
17 + };
18 + }
19 + return fn;
20 +}
21 +
22 +```
23 +
24 +
25 +## Error
26 +
27 +```
28 +[ReactForget] InvalidInput: Cannot use a mutable function where an immutable value is expected (15:15)
29 +```
30 +
31 +
\ No newline at end of file
compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.invalid-freeze-conditionally-mutable-lambda.js new
+16
@@ -0,0 +1,16 @@
1 +function Component(props) {
2 + const x = {};
3 + let fn;
4 + if (props.cond) {
5 + // mutable
6 + fn = () => {
7 + x.value = props.value;
8 + };
9 + } else {
10 + // immutable
11 + fn = () => {
12 + x.value;
13 + };
14 + }
15 + return fn;
16 +}