diff --git a/compiler/forget/src/HIR/BuildHIR.ts b/compiler/forget/src/HIR/BuildHIR.ts index 98c295c291..1386175177 100644 --- a/compiler/forget/src/HIR/BuildHIR.ts +++ b/compiler/forget/src/HIR/BuildHIR.ts @@ -1148,6 +1148,22 @@ function lowerExpression( loc: exprLoc, }; } + case "FunctionExpression": { + const expr = exprPath as NodePath; + const name: string | null = expr.get("id")?.node?.name ?? null; + const body = expr.get("body").node; + const params: Array = expr.get("params").map((p) => { + todoInvariant(p.isIdentifier(), "handle non identifier params"); + return p.node.name; + }); + return { + kind: "FunctionExpression", + name, + body, + params, + loc: exprLoc, + }; + } default: { todo(`lowerExpression(${exprNode.type})`); // assertExhaustive( diff --git a/compiler/forget/src/HIR/Codegen.ts b/compiler/forget/src/HIR/Codegen.ts index 7a1efcbba6..8019b7f924 100644 --- a/compiler/forget/src/HIR/Codegen.ts +++ b/compiler/forget/src/HIR/Codegen.ts @@ -498,6 +498,13 @@ export function codegenInstructionValue( value = codegenPlace(temp, instrValue); break; } + case "FunctionExpression": { + const id = + instrValue.name !== null ? t.identifier(instrValue.name) : null; + const params = instrValue.params.map((p) => t.identifier(p)); + value = t.functionExpression(id, params, instrValue.body); + break; + } default: { assertExhaustive( instrValue, diff --git a/compiler/forget/src/HIR/HIR.ts b/compiler/forget/src/HIR/HIR.ts index 4230ac0c3b..8043f44d99 100644 --- a/compiler/forget/src/HIR/HIR.ts +++ b/compiler/forget/src/HIR/HIR.ts @@ -309,6 +309,12 @@ export type InstructionData = | { kind: "ComputedStore"; object: Place; property: Place; value: Place } // load `object[index]` - like PropertyLoad but with a dynamic property | { kind: "ComputedLoad"; object: Place; property: Place } + | { + kind: "FunctionExpression"; + name: string | null; + params: Array; + body: t.BlockStatement; + } /** * Catch-all for statements such as type imports, nested class declarations, etc diff --git a/compiler/forget/src/HIR/PrintHIR.ts b/compiler/forget/src/HIR/PrintHIR.ts index 14fbbdea5d..67392651a3 100644 --- a/compiler/forget/src/HIR/PrintHIR.ts +++ b/compiler/forget/src/HIR/PrintHIR.ts @@ -290,6 +290,12 @@ export function printInstructionValue(instrValue: InstructionValue): string { )}] = ${printPlace(instrValue.value)}`; break; } + case "FunctionExpression": { + const params = instrValue.params.join(","); + const body = generate(instrValue.body).code; + value = `Function ${instrValue.name}(${params}){${body}}`; + break; + } default: { assertExhaustive( instrValue, diff --git a/compiler/forget/src/HIR/visitors.ts b/compiler/forget/src/HIR/visitors.ts index 97711d4b59..be172e978f 100644 --- a/compiler/forget/src/HIR/visitors.ts +++ b/compiler/forget/src/HIR/visitors.ts @@ -89,6 +89,7 @@ export function* eachInstructionValueOperand( } case "OtherStatement": case "Primitive": + case "FunctionExpression": case "JSXText": { break; } @@ -175,6 +176,7 @@ export function mapInstructionOperands( } case "OtherStatement": case "Primitive": + case "FunctionExpression": case "JSXText": { break; } diff --git a/compiler/forget/src/Inference/InferReferenceEffects.ts b/compiler/forget/src/Inference/InferReferenceEffects.ts index 94dc09087a..1a6da443fd 100644 --- a/compiler/forget/src/Inference/InferReferenceEffects.ts +++ b/compiler/forget/src/Inference/InferReferenceEffects.ts @@ -551,6 +551,12 @@ function inferBlock(env: Environment, block: BasicBlock) { lvalueEffect = Effect.Store; break; } + case "FunctionExpression": { + valueKind = ValueKind.Mutable; + effectKind = Effect.Read; + lvalueEffect = Effect.Store; + break; + } case "UnaryExpression": { // TODO check that value must be a primitive, or make conditional based on the operator valueKind = ValueKind.Immutable; diff --git a/compiler/forget/src/ReactiveScopes/InferReactiveScopeVariables.ts b/compiler/forget/src/ReactiveScopes/InferReactiveScopeVariables.ts index 1b18d224a5..08512c5505 100644 --- a/compiler/forget/src/ReactiveScopes/InferReactiveScopeVariables.ts +++ b/compiler/forget/src/ReactiveScopes/InferReactiveScopeVariables.ts @@ -173,6 +173,7 @@ function mayAllocate(value: InstructionValue): boolean { case "NewExpression": case "ObjectExpression": case "OtherStatement": + case "FunctionExpression": case "UnaryExpression": { return true; } diff --git a/compiler/forget/src/ReactiveScopes/PropagateScopeDependencies.ts b/compiler/forget/src/ReactiveScopes/PropagateScopeDependencies.ts index f7a1b2914d..818e14a642 100644 --- a/compiler/forget/src/ReactiveScopes/PropagateScopeDependencies.ts +++ b/compiler/forget/src/ReactiveScopes/PropagateScopeDependencies.ts @@ -313,6 +313,7 @@ function valueKind(value: InstructionValue): DeclKind { case "JsxFragment": case "NewExpression": case "ObjectExpression": + case "FunctionExpression": case "OtherStatement": case "UnaryExpression": { return DeclKind.Dynamic; diff --git a/compiler/forget/src/__tests__/fixtures/hir/simple-function-1.expect.md b/compiler/forget/src/__tests__/fixtures/hir/simple-function-1.expect.md new file mode 100644 index 0000000000..ab30c7cab7 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/simple-function-1.expect.md @@ -0,0 +1,34 @@ + +## Input + +```javascript +function component() { + let x = function (a) { + a.foo(); + }; + return x; +} + +``` + +## Code + +```javascript +function component() { + const $ = React.useMemoCache(); + let x; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + x = function (a) { + a.foo(); + }; + + $[0] = x; + } else { + x = $[0]; + } + + return x; +} + +``` + \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/hir/simple-function-1.js b/compiler/forget/src/__tests__/fixtures/hir/simple-function-1.js new file mode 100644 index 0000000000..0bc8c500b0 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/simple-function-1.js @@ -0,0 +1,6 @@ +function component() { + let x = function (a) { + a.foo(); + }; + return x; +}