mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
ValidateFrozenLambdas: visit terminals
The original version of the code wasn't checking return values. I missed this since my examples were passing functions _into_ the return value, as opposed to return the functions directly. This revealed some existing test fixtures that were technically invalid, but easy to fix by changing the return value.
This commit is contained in:
+55
-32
@@ -15,11 +15,12 @@ import {
|
||||
FunctionExpression,
|
||||
HIRFunction,
|
||||
IdentifierId,
|
||||
Place,
|
||||
isMutableEffect,
|
||||
isRefValueType,
|
||||
isUseRefType,
|
||||
} from "./HIR";
|
||||
import { eachInstructionValueOperand } from "./visitors";
|
||||
import { eachInstructionValueOperand, eachTerminalOperand } from "./visitors";
|
||||
|
||||
/**
|
||||
* Various APIs in React take ownership of the values passed to them, such that it is invalid
|
||||
@@ -47,56 +48,78 @@ import { eachInstructionValueOperand } from "./visitors";
|
||||
* the developer fix the mistake earlier.
|
||||
*/
|
||||
export function validateFrozenLambdas(fn: HIRFunction): void {
|
||||
const lambdas = new Map<IdentifierId, FunctionExpression>();
|
||||
const temporaries = new Map<IdentifierId, IdentifierId>();
|
||||
const state = new State();
|
||||
|
||||
const errors = new CompilerError();
|
||||
for (const [, block] of fn.body.blocks) {
|
||||
for (const instr of block.instructions) {
|
||||
if (instr.value.kind === "FunctionExpression") {
|
||||
lambdas.set(instr.lvalue.identifier.id, instr.value);
|
||||
state.lambdas.set(instr.lvalue.identifier.id, instr.value);
|
||||
} else if (instr.value.kind === "LoadLocal") {
|
||||
const resolvedId =
|
||||
temporaries.get(instr.value.place.identifier.id) ??
|
||||
state.temporaries.get(instr.value.place.identifier.id) ??
|
||||
instr.value.place.identifier.id;
|
||||
temporaries.set(instr.lvalue.identifier.id, resolvedId);
|
||||
state.temporaries.set(instr.lvalue.identifier.id, resolvedId);
|
||||
} else if (instr.value.kind === "StoreLocal") {
|
||||
const resolvedId =
|
||||
temporaries.get(instr.value.value.identifier.id) ??
|
||||
state.temporaries.get(instr.value.value.identifier.id) ??
|
||||
instr.value.value.identifier.id;
|
||||
temporaries.set(instr.value.lvalue.place.identifier.id, resolvedId);
|
||||
state.temporaries.set(
|
||||
instr.value.lvalue.place.identifier.id,
|
||||
resolvedId
|
||||
);
|
||||
} else {
|
||||
for (const operand of eachInstructionValueOperand(instr.value)) {
|
||||
if (operand.effect === Effect.Freeze) {
|
||||
const operandId =
|
||||
temporaries.get(operand.identifier.id) ?? operand.identifier.id;
|
||||
const lambda = lambdas.get(operandId);
|
||||
if (
|
||||
lambda !== undefined &&
|
||||
lambda.dependencies.some(
|
||||
(place) =>
|
||||
isMutableEffect(place.effect, place.loc) &&
|
||||
!isRefValueType(place.identifier) &&
|
||||
!isUseRefType(place.identifier)
|
||||
)
|
||||
) {
|
||||
errors.pushErrorDetail(
|
||||
new CompilerErrorDetail({
|
||||
codeframe: null,
|
||||
description: null,
|
||||
loc: typeof operand.loc !== "symbol" ? operand.loc : null,
|
||||
reason:
|
||||
"Cannot use a mutable function where an immutable value is expected",
|
||||
severity: ErrorSeverity.InvalidInput,
|
||||
})
|
||||
);
|
||||
}
|
||||
const operandError = validateOperand(operand, state);
|
||||
if (operandError !== null) {
|
||||
errors.pushErrorDetail(operandError);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const operand of eachTerminalOperand(block.terminal)) {
|
||||
const operandError = validateOperand(operand, state);
|
||||
if (operandError !== null) {
|
||||
errors.pushErrorDetail(operandError);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (errors.hasErrors()) {
|
||||
throw errors;
|
||||
}
|
||||
}
|
||||
|
||||
class State {
|
||||
lambdas: Map<IdentifierId, FunctionExpression> = new Map();
|
||||
temporaries: Map<IdentifierId, IdentifierId> = new Map();
|
||||
}
|
||||
|
||||
function validateOperand(
|
||||
operand: Place,
|
||||
state: State
|
||||
): CompilerErrorDetail | null {
|
||||
if (operand.effect === Effect.Freeze) {
|
||||
const operandId =
|
||||
state.temporaries.get(operand.identifier.id) ?? operand.identifier.id;
|
||||
const lambda = state.lambdas.get(operandId);
|
||||
if (
|
||||
lambda !== undefined &&
|
||||
lambda.dependencies.some(
|
||||
(place) =>
|
||||
isMutableEffect(place.effect, place.loc) &&
|
||||
!isRefValueType(place.identifier) &&
|
||||
!isUseRefType(place.identifier)
|
||||
)
|
||||
) {
|
||||
return new CompilerErrorDetail({
|
||||
codeframe: null,
|
||||
description: null,
|
||||
loc: typeof operand.loc !== "symbol" ? operand.loc : null,
|
||||
reason:
|
||||
"Cannot use a mutable function where an immutable value is expected",
|
||||
severity: ErrorSeverity.InvalidInput,
|
||||
});
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
+7
-7
@@ -10,7 +10,7 @@ function component(a, b) {
|
||||
y.b;
|
||||
};
|
||||
x();
|
||||
return x;
|
||||
return z;
|
||||
}
|
||||
|
||||
```
|
||||
@@ -33,21 +33,21 @@ function component(a, b) {
|
||||
const y = t0;
|
||||
const c_2 = $[2] !== a;
|
||||
const c_3 = $[3] !== y.b;
|
||||
let x;
|
||||
let z;
|
||||
if (c_2 || c_3) {
|
||||
const z = { a };
|
||||
x = function () {
|
||||
z = { a };
|
||||
const x = function () {
|
||||
z.a = 2;
|
||||
y.b;
|
||||
};
|
||||
x();
|
||||
$[2] = a;
|
||||
$[3] = y.b;
|
||||
$[4] = x;
|
||||
$[4] = z;
|
||||
} else {
|
||||
x = $[4];
|
||||
z = $[4];
|
||||
}
|
||||
return x;
|
||||
return z;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
+1
-1
@@ -6,5 +6,5 @@ function component(a, b) {
|
||||
y.b;
|
||||
};
|
||||
x();
|
||||
return x;
|
||||
return z;
|
||||
}
|
||||
|
||||
+7
-32
@@ -9,7 +9,7 @@ function component(a, b) {
|
||||
z.a = 2;
|
||||
y.b;
|
||||
};
|
||||
return x;
|
||||
return z;
|
||||
}
|
||||
|
||||
```
|
||||
@@ -19,43 +19,18 @@ function component(a, b) {
|
||||
```javascript
|
||||
import { unstable_useMemoCache as useMemoCache } from "react";
|
||||
function component(a, b) {
|
||||
const $ = useMemoCache(7);
|
||||
const c_0 = $[0] !== b;
|
||||
const $ = useMemoCache(2);
|
||||
const c_0 = $[0] !== a;
|
||||
let t0;
|
||||
if (c_0) {
|
||||
t0 = { b };
|
||||
$[0] = b;
|
||||
t0 = { a };
|
||||
$[0] = a;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
}
|
||||
const y = t0;
|
||||
const c_2 = $[2] !== a;
|
||||
let t1;
|
||||
if (c_2) {
|
||||
t1 = { a };
|
||||
$[2] = a;
|
||||
$[3] = t1;
|
||||
} else {
|
||||
t1 = $[3];
|
||||
}
|
||||
const z = t1;
|
||||
const c_4 = $[4] !== z.a;
|
||||
const c_5 = $[5] !== y.b;
|
||||
let t2;
|
||||
if (c_4 || c_5) {
|
||||
t2 = function () {
|
||||
z.a = 2;
|
||||
y.b;
|
||||
};
|
||||
$[4] = z.a;
|
||||
$[5] = y.b;
|
||||
$[6] = t2;
|
||||
} else {
|
||||
t2 = $[6];
|
||||
}
|
||||
const x = t2;
|
||||
return x;
|
||||
const z = t0;
|
||||
return z;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
+1
-1
@@ -5,5 +5,5 @@ function component(a, b) {
|
||||
z.a = 2;
|
||||
y.b;
|
||||
};
|
||||
return x;
|
||||
return z;
|
||||
}
|
||||
|
||||
+7
-7
@@ -8,7 +8,7 @@ function component(a) {
|
||||
y.b.a = 2;
|
||||
};
|
||||
x();
|
||||
return x;
|
||||
return y;
|
||||
}
|
||||
|
||||
```
|
||||
@@ -20,19 +20,19 @@ import { unstable_useMemoCache as useMemoCache } from "react";
|
||||
function component(a) {
|
||||
const $ = useMemoCache(2);
|
||||
const c_0 = $[0] !== a;
|
||||
let x;
|
||||
let y;
|
||||
if (c_0) {
|
||||
const y = { b: { a } };
|
||||
x = function () {
|
||||
y = { b: { a } };
|
||||
const x = function () {
|
||||
y.b.a = 2;
|
||||
};
|
||||
x();
|
||||
$[0] = a;
|
||||
$[1] = x;
|
||||
$[1] = y;
|
||||
} else {
|
||||
x = $[1];
|
||||
y = $[1];
|
||||
}
|
||||
return x;
|
||||
return y;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
+1
-1
@@ -4,5 +4,5 @@ function component(a) {
|
||||
y.b.a = 2;
|
||||
};
|
||||
x();
|
||||
return x;
|
||||
return y;
|
||||
}
|
||||
|
||||
+9
-18
@@ -10,7 +10,7 @@ function component(a, b) {
|
||||
y.b;
|
||||
};
|
||||
x();
|
||||
return x;
|
||||
return z;
|
||||
}
|
||||
|
||||
```
|
||||
@@ -20,34 +20,25 @@ function component(a, b) {
|
||||
```javascript
|
||||
import { unstable_useMemoCache as useMemoCache } from "react";
|
||||
function component(a, b) {
|
||||
const $ = useMemoCache(5);
|
||||
const $ = useMemoCache(3);
|
||||
const c_0 = $[0] !== a;
|
||||
const c_1 = $[1] !== b;
|
||||
let x;
|
||||
let z;
|
||||
if (c_0 || c_1) {
|
||||
const z = { a };
|
||||
const c_3 = $[3] !== b;
|
||||
let t0;
|
||||
if (c_3) {
|
||||
t0 = { b };
|
||||
$[3] = b;
|
||||
$[4] = t0;
|
||||
} else {
|
||||
t0 = $[4];
|
||||
}
|
||||
const y = t0;
|
||||
x = function () {
|
||||
z = { a };
|
||||
const y = { b };
|
||||
const x = function () {
|
||||
z.a = 2;
|
||||
y.b;
|
||||
};
|
||||
x();
|
||||
$[0] = a;
|
||||
$[1] = b;
|
||||
$[2] = x;
|
||||
$[2] = z;
|
||||
} else {
|
||||
x = $[2];
|
||||
z = $[2];
|
||||
}
|
||||
return x;
|
||||
return z;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
+1
-1
@@ -6,5 +6,5 @@ function component(a, b) {
|
||||
y.b;
|
||||
};
|
||||
x();
|
||||
return x;
|
||||
return z;
|
||||
}
|
||||
|
||||
-55
@@ -1,55 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
function component(a, b) {
|
||||
let z = { a };
|
||||
let y = b;
|
||||
let x = function () {
|
||||
if (y) {
|
||||
mutate(z);
|
||||
}
|
||||
};
|
||||
return x;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { unstable_useMemoCache as useMemoCache } from "react";
|
||||
function component(a, b) {
|
||||
const $ = useMemoCache(5);
|
||||
const c_0 = $[0] !== a;
|
||||
let t0;
|
||||
if (c_0) {
|
||||
t0 = { a };
|
||||
$[0] = a;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
}
|
||||
const z = t0;
|
||||
const y = b;
|
||||
const c_2 = $[2] !== y;
|
||||
const c_3 = $[3] !== z;
|
||||
let t1;
|
||||
if (c_2 || c_3) {
|
||||
t1 = function () {
|
||||
if (y) {
|
||||
mutate(z);
|
||||
}
|
||||
};
|
||||
$[2] = y;
|
||||
$[3] = z;
|
||||
$[4] = t1;
|
||||
} else {
|
||||
t1 = $[4];
|
||||
}
|
||||
const x = t1;
|
||||
return x;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
function component(a, b) {
|
||||
let z = { a };
|
||||
let y = b;
|
||||
let x = function () {
|
||||
if (y) {
|
||||
mutate(z);
|
||||
}
|
||||
};
|
||||
return x;
|
||||
}
|
||||
+3
-15
@@ -7,7 +7,7 @@ function component(a) {
|
||||
let x = function () {
|
||||
z.a.a();
|
||||
};
|
||||
return x;
|
||||
return z;
|
||||
}
|
||||
|
||||
```
|
||||
@@ -17,7 +17,7 @@ function component(a) {
|
||||
```javascript
|
||||
import { unstable_useMemoCache as useMemoCache } from "react";
|
||||
function component(a) {
|
||||
const $ = useMemoCache(6);
|
||||
const $ = useMemoCache(4);
|
||||
const c_0 = $[0] !== a;
|
||||
let t0;
|
||||
if (c_0) {
|
||||
@@ -37,19 +37,7 @@ function component(a) {
|
||||
t1 = $[3];
|
||||
}
|
||||
const z = t1;
|
||||
const c_4 = $[4] !== z.a;
|
||||
let t2;
|
||||
if (c_4) {
|
||||
t2 = function () {
|
||||
z.a.a();
|
||||
};
|
||||
$[4] = z.a;
|
||||
$[5] = t2;
|
||||
} else {
|
||||
t2 = $[5];
|
||||
}
|
||||
const x = t2;
|
||||
return x;
|
||||
return z;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
+1
-1
@@ -3,5 +3,5 @@ function component(a) {
|
||||
let x = function () {
|
||||
z.a.a();
|
||||
};
|
||||
return x;
|
||||
return z;
|
||||
}
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
function component(a, b) {
|
||||
let z = { a };
|
||||
let y = b;
|
||||
let x = function () {
|
||||
if (y) {
|
||||
// we don't know for sure this mutates, so we should assume
|
||||
// that there is no mutation so long as `x` isn't called
|
||||
// during render
|
||||
maybeMutate(z);
|
||||
}
|
||||
};
|
||||
return x;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
[ReactForget] InvalidInput: Cannot use a mutable function where an immutable value is expected (12:12)
|
||||
```
|
||||
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
function component(a, b) {
|
||||
let z = { a };
|
||||
let y = b;
|
||||
let x = function () {
|
||||
if (y) {
|
||||
// we don't know for sure this mutates, so we should assume
|
||||
// that there is no mutation so long as `x` isn't called
|
||||
// during render
|
||||
maybeMutate(z);
|
||||
}
|
||||
};
|
||||
return x;
|
||||
}
|
||||
Reference in New Issue
Block a user