🌲 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
}