[be] Improve feature flags setup
Updates `Environment` to store all feature flags on a single `config` object. We now also define an object with all the default config values, and use this to populate defaults for any missing values in the user-provided config.
Joe Savona committed
Oct 3, 2023 at 13:58 UTC
34890556eca6751735344a8f41b911344668a67d
8 files changed
+67
-72
compiler/packages/babel-plugin-react-forget/src/Entrypoint/Options.ts
+2
-2
@@ -7,7 +7,7 @@
7
8
import * as t from "@babel/types";
9
import { CompilerErrorDetailOptions } from "../CompilerError";
10
-import { EnvironmentConfig } from "../HIR/Environment";
10
+import { PartialEnvironmentConfig } from "../HIR/Environment";
11
12
export type ExternalFunction = {
13
/**
@@ -39,7 +39,7 @@ export type PanicThresholdOptions =
39
| "NONE";
40
41
export type PluginOptions = {
42
- environment: EnvironmentConfig | null;
42
+ environment: PartialEnvironmentConfig | null;
43
44
logger: Logger | null;
45
compiler/packages/babel-plugin-react-forget/src/Entrypoint/Pipeline.ts
+14
-14
@@ -17,7 +17,7 @@ import {
17
lower,
18
mergeConsecutiveBlocks,
19
} from "../HIR";
20
-import { Environment, EnvironmentConfig } from "../HIR/Environment";
20
+import { Environment, PartialEnvironmentConfig } from "../HIR/Environment";
21
import { findContextIdentifiers } from "../HIR/FindContextIdentifiers";
22
import {
23
analyseFunctions,
@@ -82,7 +82,7 @@ export function* run(
82
func: NodePath<
83
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
84
>,
85
- config?: EnvironmentConfig | null
85
+ config?: PartialEnvironmentConfig | null
86
): Generator<CompilerPipelineValue, CodegenFunction> {
87
const contextIdentifiers = findContextIdentifiers(func);
88
const env = new Environment(config ?? null, contextIdentifiers);
@@ -106,7 +106,7 @@ function* runWithEnvironment(
106
pruneMaybeThrows(hir);
107
yield log({ kind: "hir", name: "PruneMaybeThrows", value: hir });
108
109
- if (env.inlineUseMemo) {
109
+ if (env.config.inlineUseMemo) {
110
inlineUseMemo(hir);
111
yield log({ kind: "hir", name: "RewriteUseMemo", value: hir });
112
}
@@ -131,7 +131,7 @@ function* runWithEnvironment(
131
inferTypes(hir);
132
yield log({ kind: "hir", name: "InferTypes", value: hir });
133
134
- if (env.validateHooksUsage) {
134
+ if (env.config.validateHooksUsage) {
135
validateHooksUsage(hir);
136
const conditionalHooksResult = validateUnconditionalHooks(hir).unwrap();
137
yield log({
@@ -150,7 +150,7 @@ function* runWithEnvironment(
150
inferReferenceEffects(hir);
151
yield log({ kind: "hir", name: "InferReferenceEffects", value: hir });
152
153
- if (env.validateFrozenLambdas) {
153
+ if (env.config.validateFrozenLambdas) {
154
validateFrozenLambdas(hir);
155
}
156
@@ -164,15 +164,15 @@ function* runWithEnvironment(
164
inferMutableRanges(hir);
165
yield log({ kind: "hir", name: "InferMutableRanges", value: hir });
166
167
- if (env.assertValidMutableRanges) {
167
+ if (env.config.assertValidMutableRanges) {
168
assertValidMutableRanges(hir);
169
}
170
171
- if (env.validateRefAccessDuringRender) {
171
+ if (env.config.validateRefAccessDuringRender) {
172
validateNoRefAccessInRender(hir);
173
}
174
175
- if (env.validateNoSetStateInRender) {
175
+ if (env.config.validateNoSetStateInRender) {
176
const noSetStateInRenderResult = validateNoSetStateInRender(hir).unwrap();
177
yield log({
178
kind: "debug",
@@ -222,7 +222,7 @@ function* runWithEnvironment(
222
value: reactiveFunction,
223
});
224
225
- if (env.disableAllMemoization) {
225
+ if (env.config.disableAllMemoization) {
226
pruneAllReactiveScopes(reactiveFunction);
227
yield log({
228
kind: "reactive",
@@ -259,8 +259,8 @@ function* runWithEnvironment(
259
value: reactiveFunction,
260
});
261
262
- let memoizeJsxElements = env.memoizeJsxElements;
263
- if (env.enableForest) {
262
+ let memoizeJsxElements = env.config.memoizeJsxElements;
263
+ if (env.config.enableForest) {
264
memoizeJsxElements = false;
265
}
266
pruneNonEscapingScopes(reactiveFunction, { memoizeJsxElements });
@@ -284,7 +284,7 @@ function* runWithEnvironment(
284
value: reactiveFunction,
285
});
286
287
- if (env.enableMergeConsecutiveScopes) {
287
+ if (env.config.enableMergeConsecutiveScopes) {
288
mergeConsecutiveScopes(reactiveFunction);
289
yield log({
290
kind: "reactive",
@@ -293,7 +293,7 @@ function* runWithEnvironment(
293
});
294
}
295
296
- if (env.enableForest) {
296
+ if (env.config.enableForest) {
297
yield* lowerToForest(reactiveFunction);
298
}
299
@@ -349,7 +349,7 @@ export function compileFn(
349
func: NodePath<
350
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
351
>,
352
- options?: Partial<EnvironmentConfig> | null
352
+ options?: Partial<PartialEnvironmentConfig> | null
353
): CodegenFunction {
354
let generator = run(func, options);
355
while (true) {
compiler/packages/babel-plugin-react-forget/src/HIR/BuildHIR.ts
+1
-1
@@ -1436,7 +1436,7 @@ function lowerExpression(
1436
elements.push({
1437
kind: "Hole",
1438
});
1439
- if (builder.environment.bailoutOnHoleyArrays) {
1439
+ if (builder.environment.config.bailoutOnHoleyArrays) {
1440
builder.errors.push({
1441
reason: `(BuildHIR::lower) Fix babel holey array backward compatibility.`,
1442
severity: ErrorSeverity.Todo,
compiler/packages/babel-plugin-react-forget/src/HIR/Environment.ts
+41
-46
@@ -84,8 +84,9 @@ export type Hook = {
84
// i.e.
85
// missing required shapes (BuiltInArray for [] and BuiltInObject for {})
86
// missing some recursive Object / Function shapeIds
87
-export type EnvironmentConfig = Partial<{
88
- customHooks: Map<string, Hook>;
87
+
88
+export type CompleteEnvironmentConfig = {
89
+ customHooks: Map<string, Hook> | null;
90
91
// 🌲
92
enableForest: boolean;
@@ -243,42 +244,59 @@ export type EnvironmentConfig = Partial<{
244
* https://github.com/babel/babel/pull/10917/files#diff-19b555d2f3904c206af406540d9df200b1e16befedb83ff39ebfcbd876f7fa8aL52-R56
245
*/
246
bailoutOnHoleyArrays: boolean;
246
-}>;
247
+};
248
+
249
+const DEFAULT_ENVIRONMENT_CONFIG: CompleteEnvironmentConfig = {
250
+ customHooks: null,
251
+
252
+ enableTreatHooksAsFunctions: true,
253
+ memoizeJsxElements: true,
254
+
255
+ assertValidMutableRanges: false,
256
+ bailoutOnHoleyArrays: false,
257
+ disableAllMemoization: false,
258
+ enableAssumeHooksFollowRulesOfReact: false,
259
+ enableEmitFreeze: null,
260
+ enableForest: false,
261
+ enableFunctionCallSignatureOptimizations: false,
262
+ enableMergeConsecutiveScopes: false,
263
+ enableNoAliasOptimizations: false,
264
+ inlineUseMemo: false,
265
+
266
+ validateFrozenLambdas: false,
267
+ validateHooksUsage: false,
268
+ validateNoSetStateInRender: false,
269
+ validateRefAccessDuringRender: false,
270
+};
271
+
272
+export type PartialEnvironmentConfig = Partial<CompleteEnvironmentConfig>;
273
274
export class Environment {
275
#globals: GlobalRegistry;
276
#shapes: ShapeRegistry;
277
#nextIdentifer: number = 0;
278
#nextBlock: number = 0;
253
- validateHooksUsage: boolean;
254
- validateRefAccessDuringRender: boolean;
255
- validateFrozenLambdas: boolean;
256
- validateNoSetStateInRender: boolean;
257
- enableFunctionCallSignatureOptimizations: boolean;
258
- enableAssumeHooksFollowRulesOfReact: boolean;
259
- enableTreatHooksAsFunctions: boolean;
260
- enableNoAliasOptimizations: boolean;
261
- inlineUseMemo: boolean;
262
- memoizeJsxElements: boolean;
263
- disableAllMemoization: boolean;
264
- enableEmitFreeze: ExternalFunction | null;
265
- enableMergeConsecutiveScopes: boolean;
266
- assertValidMutableRanges: boolean;
267
- bailoutOnHoleyArrays: boolean;
268
- enableForest: boolean;
279
+ config: CompleteEnvironmentConfig;
280
281
#contextIdentifiers: Set<t.Identifier>;
282
#hoistedIdentifiers: Set<t.Identifier>;
283
284
constructor(
274
- config: EnvironmentConfig | null,
285
+ partialConfig: PartialEnvironmentConfig | null,
286
contextIdentifiers: Set<t.Identifier>
287
) {
288
this.#shapes = new Map(DEFAULT_SHAPES);
289
+ const config: CompleteEnvironmentConfig = { ...DEFAULT_ENVIRONMENT_CONFIG };
290
+ for (const rawKey in DEFAULT_ENVIRONMENT_CONFIG) {
291
+ const key = rawKey as keyof CompleteEnvironmentConfig;
292
+ const value = partialConfig?.[key] ?? DEFAULT_ENVIRONMENT_CONFIG[key];
293
+ config[key] = value as any;
294
+ }
295
+ this.config = config;
296
279
- if (config?.customHooks) {
297
+ if (this.config.customHooks != null && this.config.customHooks.size > 0) {
298
this.#globals = new Map(DEFAULT_GLOBALS);
281
- for (const [hookName, hook] of config.customHooks) {
299
+ for (const [hookName, hook] of this.config.customHooks) {
300
CompilerError.invariant(!this.#globals.has(hookName), {
301
reason: `[Globals] Found existing definition in global registry for custom hook ${hookName}`,
302
description: null,
@@ -303,29 +321,6 @@ export class Environment {
321
} else {
322
this.#globals = DEFAULT_GLOBALS;
323
}
306
- this.validateHooksUsage = config?.validateHooksUsage ?? false;
307
- this.validateRefAccessDuringRender =
308
- config?.validateRefAccessDuringRender ?? false;
309
- this.validateFrozenLambdas = config?.validateFrozenLambdas ?? false;
310
- this.enableFunctionCallSignatureOptimizations =
311
- config?.enableFunctionCallSignatureOptimizations ?? false;
312
- this.enableNoAliasOptimizations =
313
- config?.enableNoAliasOptimizations ?? false;
314
- this.enableAssumeHooksFollowRulesOfReact =
315
- config?.enableAssumeHooksFollowRulesOfReact ?? false;
316
- this.enableTreatHooksAsFunctions =
317
- config?.enableTreatHooksAsFunctions ?? true;
318
- this.disableAllMemoization = config?.disableAllMemoization ?? false;
319
- this.enableEmitFreeze = config?.enableEmitFreeze ?? null;
320
- this.enableMergeConsecutiveScopes =
321
- config?.enableMergeConsecutiveScopes ?? false;
322
- this.assertValidMutableRanges = config?.assertValidMutableRanges ?? false;
323
- this.validateNoSetStateInRender =
324
- config?.validateNoSetStateInRender ?? false;
325
- this.inlineUseMemo = config?.inlineUseMemo ?? false;
326
- this.memoizeJsxElements = config?.memoizeJsxElements ?? true;
327
- this.bailoutOnHoleyArrays = config?.bailoutOnHoleyArrays ?? false;
328
- this.enableForest = config?.enableForest ?? false;
324
325
this.#contextIdentifiers = contextIdentifiers;
326
this.#hoistedIdentifiers = new Set();
@@ -412,7 +407,7 @@ export class Environment {
407
}
408
409
#getCustomHookType(): Global {
415
- if (this.enableAssumeHooksFollowRulesOfReact) {
410
+ if (this.config.enableAssumeHooksFollowRulesOfReact) {
411
return DefaultNonmutatingHook;
412
} else {
413
return DefaultMutatingHook;
compiler/packages/babel-plugin-react-forget/src/Inference/InferReferenceEffects.ts
+2
-2
@@ -747,7 +747,7 @@ function inferBlock(
747
instrValue.callee.identifier.type
748
);
749
signature =
750
- env.enableFunctionCallSignatureOptimizations ||
750
+ env.config.enableFunctionCallSignatureOptimizations ||
751
signature?.hookKind != null
752
? signature
753
: null;
@@ -755,7 +755,7 @@ function inferBlock(
755
if (
756
signature &&
757
signature.hookKind != null &&
758
- !env.enableTreatHooksAsFunctions
758
+ !env.config.enableTreatHooksAsFunctions
759
) {
760
effectKind = signature.restParam;
761
valueKind = signature.returnValueKind;
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/CodegenReactiveFunction.ts
+3
-3
@@ -184,12 +184,12 @@ function codegenBlock(cx: Context, block: ReactiveBlock): t.BlockStatement {
184
}
185
186
function wrapCacheDep(cx: Context, value: t.Expression): t.Expression {
187
- if (cx.env.enableEmitFreeze != null) {
187
+ if (cx.env.config.enableEmitFreeze != null) {
188
// The import declaration for emitFreeze is inserted in the Babel plugin
189
return t.conditionalExpression(
190
t.identifier("__DEV__"),
191
t.callExpression(
192
- t.identifier(cx.env.enableEmitFreeze.importSpecifierName),
192
+ t.identifier(cx.env.config.enableEmitFreeze.importSpecifierName),
193
[value, t.stringLiteral(cx.fnName)]
194
),
195
value
@@ -383,7 +383,7 @@ function codegenReactiveScope(
383
scope: ReactiveScope,
384
block: ReactiveBlock
385
): void {
386
- if (cx.env.enableForest) {
386
+ if (cx.env.config.enableForest) {
387
codegenSignalBlockForReactiveScope(cx, statements, scope, block);
388
} else {
389
codegenMemoBlockForReactiveScope(cx, statements, scope, block);
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/InferReactiveScopeVariables.ts
+1
-1
@@ -250,7 +250,7 @@ function mayAllocate(env: Environment, instruction: Instruction): boolean {
250
}
251
case "CallExpression":
252
case "MethodCall": {
253
- if (env.enableFunctionCallSignatureOptimizations) {
253
+ if (env.config.enableFunctionCallSignatureOptimizations) {
254
return instruction.lvalue.identifier.type.kind !== "Primitive";
255
}
256
return true;
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/PruneNonEscapingScopes.ts
+3
-3
@@ -611,7 +611,7 @@ function computeMemoizationInputs(
611
};
612
}
613
case "CallExpression": {
614
- const signature = env.enableNoAliasOptimizations
614
+ const signature = env.config.enableNoAliasOptimizations
615
? getFunctionCallSignature(env, value.callee.identifier.type)
616
: null;
617
const operands = [...eachReactiveValueOperand(value)];
@@ -636,7 +636,7 @@ function computeMemoizationInputs(
636
};
637
}
638
case "MethodCall": {
639
- const signature = env.enableNoAliasOptimizations
639
+ const signature = env.config.enableNoAliasOptimizations
640
? getFunctionCallSignature(env, value.property.identifier.type)
641
: null;
642
const operands = [...eachReactiveValueOperand(value)];
@@ -808,7 +808,7 @@ class CollectDependenciesVisitor extends ReactiveFunctionVisitor<State> {
808
} else if (instruction.value.kind === "CallExpression") {
809
const callee = instruction.value.callee;
810
if (getHookKind(state.env, callee.identifier) != null) {
811
- const signature = this.env.enableNoAliasOptimizations
811
+ const signature = this.env.config.enableNoAliasOptimizations
812
? getFunctionCallSignature(
813
this.env,
814
instruction.value.callee.identifier.type