diff --git a/compiler/forget/packages/babel-plugin-react-forget/src/HIR/ValidateFrozenLambdas.ts b/compiler/forget/packages/babel-plugin-react-forget/src/HIR/ValidateFrozenLambdas.ts index 7e80a8c1d8..c2d6aed9d0 100644 --- a/compiler/forget/packages/babel-plugin-react-forget/src/HIR/ValidateFrozenLambdas.ts +++ b/compiler/forget/packages/babel-plugin-react-forget/src/HIR/ValidateFrozenLambdas.ts @@ -51,27 +51,54 @@ export function validateFrozenLambdas(fn: HIRFunction): void { const errors = new CompilerError(); for (const [, block] of fn.body.blocks) { + for (const phi of block.phis) { + for (const [, operand] of phi.operands) { + const resolvedId = state.temporaries.get(operand.id) ?? operand.id; + const lambda = state.lambdas.get(resolvedId); + if (lambda !== undefined) { + state.lambdas.set(phi.id.id, lambda); + break; + } + } + } for (const instr of block.instructions) { - if (instr.value.kind === "FunctionExpression") { - state.lambdas.set(instr.lvalue.identifier.id, instr.value); - } else if (instr.value.kind === "LoadLocal") { - const resolvedId = - state.temporaries.get(instr.value.place.identifier.id) ?? - instr.value.place.identifier.id; - state.temporaries.set(instr.lvalue.identifier.id, resolvedId); - } else if (instr.value.kind === "StoreLocal") { - const resolvedId = - state.temporaries.get(instr.value.value.identifier.id) ?? - instr.value.value.identifier.id; - state.temporaries.set( - instr.value.lvalue.place.identifier.id, - resolvedId - ); - } else { - for (const operand of eachInstructionValueOperand(instr.value)) { - const operandError = validateOperand(operand, state); - if (operandError !== null) { - errors.pushErrorDetail(operandError); + switch (instr.value.kind) { + case "FunctionExpression": { + if ( + instr.value.dependencies.some( + (place) => + place.effect === Effect.Mutate && + !isRefValueType(place.identifier) && + !isUseRefType(place.identifier) + ) + ) { + state.lambdas.set(instr.lvalue.identifier.id, instr.value); + } + break; + } + case "LoadLocal": { + const resolvedId = + state.temporaries.get(instr.value.place.identifier.id) ?? + instr.value.place.identifier.id; + state.temporaries.set(instr.lvalue.identifier.id, resolvedId); + break; + } + case "StoreLocal": { + const resolvedId = + state.temporaries.get(instr.value.value.identifier.id) ?? + instr.value.value.identifier.id; + state.temporaries.set( + instr.value.lvalue.place.identifier.id, + resolvedId + ); + break; + } + default: { + for (const operand of eachInstructionValueOperand(instr.value)) { + const operandError = validateOperand(operand, state); + if (operandError !== null) { + errors.pushErrorDetail(operandError); + } } } } @@ -101,15 +128,7 @@ function validateOperand( const operandId = state.temporaries.get(operand.identifier.id) ?? operand.identifier.id; const lambda = state.lambdas.get(operandId); - if ( - lambda !== undefined && - lambda.dependencies.some( - (place) => - place.effect === Effect.Mutate && - !isRefValueType(place.identifier) && - !isUseRefType(place.identifier) - ) - ) { + if (lambda !== undefined) { return new CompilerErrorDetail({ codeframe: null, description: null, diff --git a/compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.invalid-freeze-conditionally-mutable-lambda.expect.md b/compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.invalid-freeze-conditionally-mutable-lambda.expect.md new file mode 100644 index 0000000000..d838f5825b --- /dev/null +++ b/compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.invalid-freeze-conditionally-mutable-lambda.expect.md @@ -0,0 +1,31 @@ + +## Input + +```javascript +function Component(props) { + const x = {}; + let fn; + if (props.cond) { + // mutable + fn = () => { + x.value = props.value; + }; + } else { + // immutable + fn = () => { + x.value; + }; + } + return fn; +} + +``` + + +## Error + +``` +[ReactForget] InvalidInput: Cannot use a mutable function where an immutable value is expected (15:15) +``` + + \ No newline at end of file diff --git a/compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.invalid-freeze-conditionally-mutable-lambda.js b/compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.invalid-freeze-conditionally-mutable-lambda.js new file mode 100644 index 0000000000..0ffb689787 --- /dev/null +++ b/compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.invalid-freeze-conditionally-mutable-lambda.js @@ -0,0 +1,16 @@ +function Component(props) { + const x = {}; + let fn; + if (props.cond) { + // mutable + fn = () => { + x.value = props.value; + }; + } else { + // immutable + fn = () => { + x.value; + }; + } + return fn; +}