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