From 3f4a9bd330a3500824cd223bd9a8984b60153eb9 Mon Sep 17 00:00:00 2001 From: Joe Savona Date: Mon, 11 Dec 2023 11:34:24 -0800 Subject: [PATCH] ReactiveFunctionTransform allows replacing values w transformValue Previously the only way to replace a value was to override transformInstruction and transformTerminal, and to be careful to find nested values. This PR adds `ReactiveFunctionTransform#transformValue()` which allows returning an optional new value, which if present will replace the value in whatever context it appeared. ReactiveFunctionTransform now reimplements all the methods necessary to replace any value anywhere in the AST. See the next PR for an example usage. --- .../src/HIR/visitors.ts | 8 +- .../src/ReactiveScopes/visitors.ts | 201 ++++++++++++++++++ 2 files changed, 208 insertions(+), 1 deletion(-) diff --git a/compiler/packages/babel-plugin-react-forget/src/HIR/visitors.ts b/compiler/packages/babel-plugin-react-forget/src/HIR/visitors.ts index cc6b503c7f..bf76b62b64 100644 --- a/compiler/packages/babel-plugin-react-forget/src/HIR/visitors.ts +++ b/compiler/packages/babel-plugin-react-forget/src/HIR/visitors.ts @@ -339,7 +339,13 @@ export function mapInstructionOperands( instr: Instruction, fn: (place: Place) => Place ): void { - const instrValue = instr.value; + mapInstructionValueOperands(instr.value, fn); +} + +export function mapInstructionValueOperands( + instrValue: InstructionValue, + fn: (place: Place) => Place +): void { switch (instrValue.kind) { case "BinaryExpression": { instrValue.left = fn(instrValue.left); diff --git a/compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/visitors.ts b/compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/visitors.ts index 376e89ae02..01016926f6 100644 --- a/compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/visitors.ts +++ b/compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/visitors.ts @@ -206,6 +206,10 @@ export class ReactiveFunctionVisitor { } } +export type TransformedValue = + | { kind: "keep" } + | { kind: "replace"; value: ReactiveValue }; + export type Transformed = | { kind: "remove" } | { kind: "keep" } @@ -294,6 +298,203 @@ export class ReactiveFunctionTransform< this.visitScope(scope, state); return { kind: "keep" }; } + + transformValue( + id: InstructionId, + value: ReactiveValue, + state: TState + ): TransformedValue { + this.visitValue(id, value, state); + return { kind: "keep" }; + } + + override traverseValue( + id: InstructionId, + value: ReactiveValue, + state: TState + ): void { + switch (value.kind) { + case "OptionalExpression": { + const nextValue = this.transformValue(id, value.value, state); + if (nextValue.kind === "replace") { + value.value = nextValue.value; + } + break; + } + case "LogicalExpression": { + const left = this.transformValue(id, value.left, state); + if (left.kind === "replace") { + value.left = left.value; + } + const right = this.transformValue(id, value.right, state); + if (right.kind === "replace") { + value.right = right.value; + } + break; + } + case "ConditionalExpression": { + const test = this.transformValue(id, value.test, state); + if (test.kind === "replace") { + value.test = test.value; + } + const consequent = this.transformValue(id, value.consequent, state); + if (consequent.kind === "replace") { + value.consequent = consequent.value; + } + const alternate = this.transformValue(id, value.alternate, state); + if (alternate.kind === "replace") { + value.alternate = alternate.value; + } + break; + } + case "SequenceExpression": { + for (const instr of value.instructions) { + this.visitInstruction(instr, state); + } + const nextValue = this.transformValue(value.id, value.value, state); + if (nextValue.kind === "replace") { + value.value = nextValue.value; + } + break; + } + default: { + for (const place of eachReactiveValueOperand(value)) { + this.visitPlace(id, place, state); + } + } + } + } + + override traverseInstruction( + instruction: ReactiveInstruction, + state: TState + ): void { + this.visitID(instruction.id, state); + for (const operand of eachInstructionLValue(instruction)) { + this.visitLValue(instruction.id, operand, state); + } + const nextValue = this.transformValue( + instruction.id, + instruction.value, + state + ); + if (nextValue.kind === "replace") { + instruction.value = nextValue.value; + } + } + + override traverseTerminal( + stmt: ReactiveTerminalStatement, + state: TState + ): void { + const { terminal } = stmt; + if (terminal.id !== null) { + this.visitID(terminal.id, state); + } + switch (terminal.kind) { + case "break": + case "continue": { + break; + } + case "return": { + this.visitPlace(terminal.id, terminal.value, state); + break; + } + case "throw": { + this.visitPlace(terminal.id, terminal.value, state); + break; + } + case "for": { + const init = this.transformValue(terminal.id, terminal.init, state); + if (init.kind === "replace") { + terminal.init = init.value; + } + const test = this.transformValue(terminal.id, terminal.test, state); + if (test.kind === "replace") { + terminal.test = test.value; + } + if (terminal.update !== null) { + const update = this.transformValue( + terminal.id, + terminal.update, + state + ); + if (update.kind === "replace") { + terminal.update = update.value; + } + } + this.visitBlock(terminal.loop, state); + break; + } + case "for-of": { + const init = this.transformValue(terminal.id, terminal.init, state); + if (init.kind === "replace") { + terminal.init = init.value; + } + this.visitBlock(terminal.loop, state); + break; + } + case "for-in": { + const init = this.transformValue(terminal.id, terminal.init, state); + if (init.kind === "replace") { + terminal.init = init.value; + } + this.visitBlock(terminal.loop, state); + break; + } + case "do-while": { + this.visitBlock(terminal.loop, state); + const test = this.transformValue(terminal.id, terminal.test, state); + if (test.kind === "replace") { + terminal.test = test.value; + } + break; + } + case "while": { + const test = this.transformValue(terminal.id, terminal.test, state); + if (test.kind === "replace") { + terminal.test = test.value; + } + this.visitBlock(terminal.loop, state); + break; + } + case "if": { + this.visitPlace(terminal.id, terminal.test, state); + this.visitBlock(terminal.consequent, state); + if (terminal.alternate !== null) { + this.visitBlock(terminal.alternate, state); + } + break; + } + case "switch": { + this.visitPlace(terminal.id, terminal.test, state); + for (const case_ of terminal.cases) { + if (case_.test !== null) { + this.visitPlace(terminal.id, case_.test, state); + } + if (case_.block !== undefined) { + this.visitBlock(case_.block, state); + } + } + break; + } + case "label": { + this.visitBlock(terminal.block, state); + break; + } + case "try": { + this.visitBlock(terminal.block, state); + this.visitBlock(terminal.handler, state); + break; + } + default: { + assertExhaustive( + terminal, + `Unexpected terminal kind '${(terminal as any).kind}'` + ); + } + } + } } export function* eachReactiveValueOperand(