@samitouri / QOS-React-1 / commits / 34890556ec

[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