@samitouri / QOS-React-1 / commits / 7ca3b004ae

Early branch with new type inference foundation

It's starting to get complex just with a couple of extra passes — we either need to substantially extend the HIR or (as i've done so far) pass information from early passes to later ones. This PR changes things so that very early in the babel plugin we fork into a separate mode. Forest has its own `compileProgram()` equivalent, its own pipeline, its own codegen, etc.

Joe Savona committed Jan 3, 2024 at 10:47 UTC 7ca3b004aee982ee637dc06ccb5fe890c9f6e5ba
9 files changed +65 -23
compiler/packages/babel-plugin-react-forget/package.json
+1 -1
@@ -58,7 +58,7 @@
58 "babel-plugin-syntax-hermes-parser": "^0.15.1",
59 "eslint": "8.27.0",
60 "glob": "^7.1.6",
61 - "hermes-parser": "^0.17.1",
61 + "hermes-parser": "^0.18.2",
62 "jest": "^29.0.3",
63 "jest-environment-jsdom": "^29.0.3",
64 "prettier": "2.8.8",
compiler/packages/babel-plugin-react-forget/src/Entrypoint/Pipeline.ts
-5
@@ -8,7 +8,6 @@
8 import { NodePath } from "@babel/traverse";
9 import * as t from "@babel/types";
10 import prettyFormat from "pretty-format";
11 -import { lowerToForest } from "../Forest";
11 import {
12 HIRFunction,
13 ReactiveFunction,
@@ -364,10 +363,6 @@ function* runWithEnvironment(
363 validatePreservedManualMemoization(reactiveFunction);
364 }
365
367 - if (env.config.enableForest) {
368 - yield* lowerToForest(reactiveFunction);
369 - }
370 -
366 const ast = codegenFunction(reactiveFunction).unwrap();
367 yield log({ kind: "ast", name: "Codegen", value: ast });
368
compiler/packages/babel-plugin-react-forget/src/Entrypoint/Program.ts
+8 -5
@@ -14,8 +14,8 @@ import {
14 } from "../CompilerError";
15 import {
16 ExternalFunction,
17 + parseEnvironmentConfig,
18 tryParseExternalFunction,
18 - validateEnvironmentConfig,
19 } from "../HIR/Environment";
20 import { CodegenFunction } from "../ReactiveScopes";
21 import { isComponentDeclaration } from "../Utils/ComponentDeclaration";
@@ -67,12 +67,12 @@ function isConfigError(err: unknown): boolean {
67 return false;
68 }
69
70 -type BabelFn =
70 +export type BabelFn =
71 | NodePath<t.FunctionDeclaration>
72 | NodePath<t.FunctionExpression>
73 | NodePath<t.ArrowFunctionExpression>;
74
75 -type CompileResult = {
75 +export type CompileResult = {
76 originalFn: BabelFn;
77 compiledFn: CodegenFunction;
78 };
@@ -115,7 +115,7 @@ function handleError(
115 }
116 }
117
118 -function createNewFunctionNode(
118 +export function createNewFunctionNode(
119 originalFn: BabelFn,
120 compiledFn: CodegenFunction
121 ): t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression {
@@ -182,6 +182,8 @@ export function compileProgram(
182 pass: CompilerPass
183 ): void {
184 const options = parsePluginOptions(pass.opts);
185 + const environment = parseEnvironmentConfig(pass.opts.environment ?? {});
186 +
187 /*
188 * Record lint errors and critical errors as depending on Forget's config,
189 * we may still need to run Forget's analysis on every function (even if we
@@ -224,7 +226,8 @@ export function compileProgram(
226 * TODO(lauren): Remove pass.opts.environment nullcheck once PluginOptions
227 * is validated
228 */
227 - const config = validateEnvironmentConfig(pass.opts.environment ?? {});
229 + const config = environment.unwrap();
230 +
231 compiledFn = compileFn(fn, config);
232 pass.opts.logger?.logEvent(pass.filename, {
233 kind: "CompileSuccess",
compiler/packages/babel-plugin-react-forget/src/HIR/Environment.ts
+13 -1
@@ -6,9 +6,10 @@
6 */
7
8 import * as t from "@babel/types";
9 -import { z } from "zod";
9 +import { ZodError, z } from "zod";
10 import { fromZodError } from "zod-validation-error";
11 import { CompilerError } from "../CompilerError";
12 +import { Err, Ok, Result } from "../Utils/Result";
13 import { log } from "../Utils/logger";
14 import {
15 DEFAULT_GLOBALS,
@@ -508,6 +509,17 @@ export function isHookName(name: string): boolean {
509 return /^use[A-Z0-9]/.test(name);
510 }
511
512 +export function parseEnvironmentConfig(
513 + partialConfig: PartialEnvironmentConfig
514 +): Result<EnvironmentConfig, ZodError<PartialEnvironmentConfig>> {
515 + const config = EnvironmentConfigSchema.safeParse(partialConfig);
516 + if (config.success) {
517 + return Ok(config.data);
518 + } else {
519 + return Err(config.error);
520 + }
521 +}
522 +
523 export function validateEnvironmentConfig(
524 partialConfig: PartialEnvironmentConfig
525 ): EnvironmentConfig {
compiler/packages/babel-plugin-react-forget/src/HIR/HIR.ts
+2 -1
@@ -637,6 +637,7 @@ export type CallExpression = {
637 callee: Place;
638 args: Array<Place | SpreadPattern>;
639 loc: SourceLocation;
640 + typeArguments?: Array<t.FlowType>;
641 };
642
643 /*
@@ -712,7 +713,7 @@ export type InstructionValue =
713 | MethodCall
714 | {
715 kind: "UnaryExpression";
715 - operator: string;
716 + operator: t.UnaryExpression["operator"];
717 value: Place;
718 loc: SourceLocation;
719 }
compiler/packages/babel-plugin-react-forget/src/HIR/visitors.ts
+11 -4
@@ -14,6 +14,7 @@ import {
14 Pattern,
15 Place,
16 ReactiveInstruction,
17 + ReactiveValue,
18 SpreadPattern,
19 Terminal,
20 } from "./HIR";
@@ -24,20 +25,26 @@ export function* eachInstructionLValue(
25 if (instr.lvalue !== null) {
26 yield instr.lvalue;
27 }
27 - switch (instr.value.kind) {
28 + yield* eachInstructionValueLValue(instr.value);
29 +}
30 +
31 +export function* eachInstructionValueLValue(
32 + value: ReactiveValue
33 +): Iterable<Place> {
34 + switch (value.kind) {
35 case "DeclareLocal":
36 case "DeclareContext":
37 case "StoreLocal": {
31 - yield instr.value.lvalue.place;
38 + yield value.lvalue.place;
39 break;
40 }
41 case "Destructure": {
35 - yield* eachPatternOperand(instr.value.lvalue.pattern);
42 + yield* eachPatternOperand(value.lvalue.pattern);
43 break;
44 }
45 case "PostfixUpdate":
46 case "PrefixUpdate": {
40 - yield instr.value.lvalue;
47 + yield value.lvalue;
48 break;
49 }
50 }
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/CodegenReactiveFunction.ts
+11
@@ -1164,6 +1164,17 @@ function codegenInstructionValue(
1164 break;
1165 }
1166 case "CallExpression": {
1167 + if (cx.env.config.enableForest) {
1168 + const callee = codegenPlaceToExpression(cx, instrValue.callee);
1169 + const args = instrValue.args.map((arg) => codegenArgument(cx, arg));
1170 + value = t.callExpression(callee, args);
1171 + if (instrValue.typeArguments != null) {
1172 + value.typeArguments = t.typeParameterInstantiation(
1173 + instrValue.typeArguments
1174 + );
1175 + }
1176 + break;
1177 + }
1178 const isHook = getHookKind(cx.env, instrValue.callee.identifier) != null;
1179 const callee = codegenPlaceToExpression(cx, instrValue.callee);
1180 const args = instrValue.args.map((arg) => codegenArgument(cx, arg));
compiler/packages/sprout/src/SproutTodoFilter.ts
+7 -6
@@ -422,11 +422,12 @@ const skipFilter = new Set([
422 "readonly-object-method-calls-mutable-lambda",
423
424 // TODO: 🌲
425 - "forest/forest-basic",
426 - "forest/forest-basic-jsx",
427 - "forest/forest-primitive-operations",
428 - "forest/computed-load-props",
429 - "forest/property-load-props",
425 + "forest/forest-basic.flow",
426 + "forest/forest-basic-jsx.flow",
427 + "forest/forest-typing.flow",
428 + "forest/forest-primitive-operations.flow",
429 + "forest/computed-load-props.flow",
430 + "forest/property-load-props.flow",
431
432 // TODO: we probably want to always skip these
433 "rules-of-hooks/rules-of-hooks-0592bd574811",
@@ -519,7 +520,7 @@ const skipFilter = new Set([
520 "bug-jsx-memberexpr-tag-in-lambda",
521 "bug-invalid-code-when-bailout",
522 "component-syntax-ref-gating.flow",
522 -
523 +
524 // 'react-forget-runtime' not yet supported
525 "flag-enable-emit-hook-guards",
526 ]);
compiler/yarn.lock
+12
@@ -6711,6 +6711,11 @@ hermes-estree@0.17.1:
6711 resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.17.1.tgz#902806a900c185720424ffcf958027821d23c051"
6712 integrity sha512-EdUJms+eRE40OQxysFlPr1mPpvUbbMi7uDAKlScBw8o3tQY22BZ5yx56OYyp1bVaBm+7Cjc3NQz24sJEFXkPxg==
6713
6714 +hermes-estree@0.18.2:
6715 + version "0.18.2"
6716 + resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.18.2.tgz#fd450fa1659cf074ceaa2ddeeb21674f3b2342f3"
6717 + integrity sha512-KoLsoWXJ5o81nit1wSyEZnWUGy9cBna9iYMZBR7skKh7okYAYKqQ9/OczwpMHn/cH0hKDyblulGsJ7FknlfVxQ==
6718 +
6719 hermes-parser@0.14.0:
6720 version "0.14.0"
6721 resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.14.0.tgz#edb2e7172fce996d2c8bbba250d140b70cc1aaaf"
@@ -6732,6 +6737,13 @@ hermes-parser@0.17.1, hermes-parser@^0.17.1:
6737 dependencies:
6738 hermes-estree "0.17.1"
6739
6740 +hermes-parser@^0.18.2:
6741 + version "0.18.2"
6742 + resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.18.2.tgz#50f15e2fcd559a48c68cd7af259d4292298bd14d"
6743 + integrity sha512-1eQfvib+VPpgBZ2zYKQhpuOjw1tH+Emuib6QmjkJWJMhyjM8xnXMvA+76o9LhF0zOAJDZgPfQhg43cyXEyl5Ew==
6744 + dependencies:
6745 + hermes-estree "0.18.2"
6746 +
6747 hmac-drbg@^1.0.1:
6748 version "1.0.1"
6749 resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"