@samitouri / QOS-React-2 / commits / b3391295c8

🌲 Separate pass for lowering reactive scopes

For Forest, we previously converted reactive scopes into derived signals during Codegen. I'm moving this to a separate pass primarily to keep codegen simple since there's enough complexity just dealing with core JS semantics. Ideally we'd do a similar setup even for regular Forget, ie lower reactive scopes just prior to codegen. At the same time i also reordered the forget passes to be just before codegen, and cleaned things up a bit. For state lowering, we now just rewrite `useState` -> `createState`, because we actually need to keep around the setter function to trigger scheduling updates in addition to writing the signal value.

Joe Savona committed Dec 11, 2023 at 11:34 UTC b3391295c84dd9ccf38720d31ae9585aa6b735f6
3 files changed +25 -11
compiler/packages/babel-plugin-react-forget/src/Entrypoint/Pipeline.ts
+4 -4
@@ -313,10 +313,6 @@ function* runWithEnvironment(
313 });
314 }
315
316 - if (env.config.enableForest) {
317 - yield* lowerToForest(reactiveFunction);
318 - }
319 -
316 promoteUsedTemporaries(reactiveFunction);
317 yield log({
318 kind: "reactive",
@@ -356,6 +352,10 @@ function* runWithEnvironment(
352 validateMemoizedEffectDependencies(reactiveFunction);
353 }
354
355 + if (env.config.enableForest) {
356 + yield* lowerToForest(reactiveFunction);
357 + }
358 +
359 const ast = codegenFunction(reactiveFunction).unwrap();
360 yield log({ kind: "ast", name: "Codegen", value: ast });
361
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/CodegenReactiveFunction.ts
+16 -6
@@ -104,10 +104,15 @@ export function codegenFunction(
104 return compileResult;
105 }
106
107 -function codegenReactiveFunction(
108 - fn: ReactiveFunction
107 +export function codegenReactiveFunction(
108 + fn: ReactiveFunction,
109 + parentContext: Context | null = null
110 ): Result<CodegenFunction, CompilerError> {
110 - const cx = new Context(fn.env, fn.id ?? "[[ anonymous ]]");
111 + const cx = new Context(
112 + fn.env,
113 + fn.id ?? "[[ anonymous ]]",
114 + parentContext?.temp ?? null
115 + );
116 for (const param of fn.params) {
117 if (param.kind === "Identifier") {
118 cx.temp.set(param.identifier.id, null);
@@ -184,13 +189,18 @@ class Context {
189 fnName: string;
190 #nextCacheIndex: number = 0;
191 #declarations: Set<IdentifierId> = new Set();
187 - temp: Temporaries = new Map();
192 + temp: Temporaries;
193 errors: CompilerError = new CompilerError();
194 objectMethods: Map<IdentifierId, ObjectMethod> = new Map();
195
191 - constructor(env: Environment, fnName: string) {
196 + constructor(
197 + env: Environment,
198 + fnName: string,
199 + temporaries: Temporaries | null = null
200 + ) {
201 this.env = env;
202 this.fnName = fnName;
203 + this.temp = temporaries !== null ? new Map(temporaries) : new Map();
204 }
205 get nextCacheIndex(): number {
206 return this.#nextCacheIndex++;
@@ -1515,7 +1525,7 @@ function codegenInstructionValue(
1525 pruneUnusedLabels(reactiveFunction);
1526 pruneUnusedLValues(reactiveFunction);
1527 renameVariables(reactiveFunction);
1518 - const fn = codegenReactiveFunction(reactiveFunction).unwrap();
1528 + const fn = codegenReactiveFunction(reactiveFunction, cx).unwrap();
1529 if (instrValue.expr.type === "ArrowFunctionExpression") {
1530 let body: t.BlockStatement | t.Expression = fn.body;
1531 if (body.body.length === 1) {
compiler/packages/snap/src/compiler-worker.ts
+5 -1
@@ -52,6 +52,7 @@ export async function compile(
52 version = compilerVersion;
53 const { input, snapshot: expected, snapshotPath: outputPath } = fixture;
54 const basename = getBasename(fixture);
55 + const expectError = isExpectError(fixture);
56
57 // Input will be null if the input file did not exist, in which case the output file
58 // is stale
@@ -89,6 +90,9 @@ export async function compile(
90 parseConfigPragma
91 ).code ?? null;
92 } catch (e) {
93 + if (isOnlyFixture && !expectError) {
94 + console.error(e.stack);
95 + }
96 e.message = e.message.replace(/\u001b[^m]*m/g, "");
97 error = e;
98 }
@@ -104,7 +108,7 @@ export async function compile(
108 }
109
110 let unexpectedError: string | null = null;
107 - if (isExpectError(fixture)) {
111 + if (expectError) {
112 if (error === null) {
113 unexpectedError = `Expected an error to be thrown for fixture: '${basename}', remove the 'error.' prefix if an error is not expected.`;
114 }