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"