@samitouri / QOS-React-2 / commits / 6405c980eb

Use starred-block for multi line comments

Sathya Gunasekaran committed Nov 8, 2023 at 08:27 UTC 6405c980ebbd3716b74651e61c73fdc241d77c92
94 files changed +1942 -1538
compiler/packages/babel-plugin-react-forget/.eslintrc.js
+2
@@ -28,6 +28,8 @@ module.exports = {
28 // obvious if the declaration was lifted to the parent root
29 "no-inner-declarations": "off",
30
31 + "multiline-comment-style": ["error", "starred-block"],
32 +
33 "@typescript-eslint/no-empty-function": "off",
34
35 // Explicitly casting to/through any is sometimes required, often for error messages to
compiler/packages/babel-plugin-react-forget/src/Babel/BabelPlugin.ts
+7 -5
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -10,7 +10,7 @@
10 import type * as BabelCore from "@babel/core";
11 import { compileProgram, parsePluginOptions } from "../Entrypoint";
12
13 -/**
13 +/*
14 * The React Forget Babel Plugin
15 * @param {*} _babel
16 * @returns
@@ -21,9 +21,11 @@ export default function ReactForgetBabelPlugin(
21 return {
22 name: "react-forget",
23 visitor: {
24 - // Note: Babel does some "smart" merging of visitors across plugins, so even if A is inserted
25 - // prior to B, if A does not have a Program visitor and B does, B will run first. We always
26 - // want Forget to run true to source as possible.
24 + /*
25 + * Note: Babel does some "smart" merging of visitors across plugins, so even if A is inserted
26 + * prior to B, if A does not have a Program visitor and B does, B will run first. We always
27 + * want Forget to run true to source as possible.
28 + */
29 Program(prog, pass): void {
30 compileProgram(prog, {
31 opts: parsePluginOptions(pass.opts),
compiler/packages/babel-plugin-react-forget/src/Babel/RunReactForgetBabelPlugin.ts
+1 -1
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
compiler/packages/babel-plugin-react-forget/src/CompilerError.ts
+8 -16
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -9,23 +9,15 @@ import type { SourceLocation } from "./HIR";
9 import { assertExhaustive } from "./Utils/utils";
10
11 export enum ErrorSeverity {
12 - /**
13 - * Invalid JS syntax, or valid syntax that is semantically invalid which may indicate some misunderstanding on the user’s part.
14 - */
12 + // Invalid JS syntax, or valid syntax that is semantically invalid which may indicate some misunderstanding on the user’s part.
13 InvalidJS = "InvalidJS",
16 - /**
17 - * Code that breaks the rules of React.
18 - */
14 + // Code that breaks the rules of React.
15 InvalidReact = "InvalidReact",
20 - /**
21 - * Incorrect configuration of the compiler.
22 - */
16 + // Incorrect configuration of the compiler.
17 InvalidConfig = "InvalidConfig",
24 - /**
25 - * Unhandled syntax that we don't support yet.
26 - */
18 + // Unhandled syntax that we don't support yet.
19 Todo = "Todo",
28 - /**
20 + /*
21 * An unexpected internal error in the compiler that indicates critical issues that can panic
22 * the compiler.
23 */
@@ -62,7 +54,7 @@ export type CompilerErrorDetailOptions = {
54 suggestions: Array<CompilerSuggestion> | null;
55 };
56
65 -/**
57 +/*
58 * Each bailout or invariant in HIR lowering creates an {@link CompilerErrorDetail}, which is then
59 * aggregated into a single {@link CompilerError} later.
60 */
@@ -206,7 +198,7 @@ export class CompilerError extends Error {
198 return this.details.length > 0;
199 }
200
209 - /**
201 + /*
202 * An error is critical if it means the compiler has entered into a broken state and cannot
203 * continue safely. Other expected errors such as Todos mean that we can skip over that component
204 * but otherwise continue compiling the rest of the app.
compiler/packages/babel-plugin-react-forget/src/Entrypoint/Gating.ts
+7 -5
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -26,10 +26,12 @@ export function insertGatedFunctionDeclaration(
26 );
27
28 let compiledFn;
29 - // Convert function declarations to named variables *unless* this is an
30 - // `export default function ...` since `export default const ...` is
31 - // not supported. For that case we fall through to replacing w the raw
32 - // conditional expression
29 + /*
30 + * Convert function declarations to named variables *unless* this is an
31 + * `export default function ...` since `export default const ...` is
32 + * not supported. For that case we fall through to replacing w the raw
33 + * conditional expression
34 + */
35 if (
36 fnPath.parentPath.node.type !== "ExportDefaultDeclaration" &&
37 fnPath.node.type === "FunctionDeclaration" &&
compiler/packages/babel-plugin-react-forget/src/Entrypoint/Imports.ts
+16 -10
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -19,8 +19,10 @@ export function addImportsToProgram(
19 const identifiers: Set<string> = new Set();
20 const sortedImports: Map<string, Array<string>> = new Map();
21 for (const { importSpecifierName, source } of importList) {
22 - // Codegen currently does not rename import specifiers, so we do additional
23 - // validation here
22 + /*
23 + * Codegen currently does not rename import specifiers, so we do additional
24 + * validation here
25 + */
26 if (identifiers.has(importSpecifierName)) {
27 CompilerError.invalidConfig({
28 reason: `Encountered conflicting import specifier for ${importSpecifierName} in Forget config.`,
@@ -59,7 +61,7 @@ export function addImportsToProgram(
61 path.unshiftContainer("body", stmts);
62 }
63
62 -/**
64 +/*
65 * Matches `import { ... } from 'react';`
66 * but not `import * as React from 'react';`
67 */
@@ -108,7 +110,7 @@ export function findExistingImports(program: NodePath<t.Program>): {
110 };
111 }
112
111 -/**
113 +/*
114 * If an existing import of React exists (ie `import {useMemo} from 'React'`), inject useMemoCache
115 * into the list of destructured variables.
116 */
@@ -140,8 +142,10 @@ export function updateUseMemoCacheImport(
142 program: NodePath<t.Program>,
143 options: PluginOptions
144 ): void {
143 - // If there isn't already an import of * as React, insert it so useMemoCache doesn't
144 - // throw
145 + /*
146 + * If there isn't already an import of * as React, insert it so useMemoCache doesn't
147 + * throw
148 + */
149 const { didInsertUseMemoCache, hasExistingReactImport } =
150 findExistingImports(program);
151
@@ -151,9 +155,11 @@ export function updateUseMemoCacheImport(
155 }
156
157 if (options.enableUseMemoCachePolyfill === false) {
154 - // If Forget did successfully compile inject/update an import of
155 - // `import {unstable_useMemoCache as useMemoCache} from 'react'` and rename
156 - // `React.unstable_useMemoCache(n)` to `useMemoCache(n)`;
158 + /*
159 + * If Forget did successfully compile inject/update an import of
160 + * `import {unstable_useMemoCache as useMemoCache} from 'react'` and rename
161 + * `React.unstable_useMemoCache(n)` to `useMemoCache(n)`;
162 + */
163 if (hasExistingReactImport) {
164 const didUpdateImport = updateExistingReactImportDeclaration(program);
165 if (didUpdateImport === false) {
compiler/packages/babel-plugin-react-forget/src/Entrypoint/Instrumentation.ts
+5 -3
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -33,8 +33,10 @@ export function addInstrumentForget(
33 body = t.blockStatement([t.expressionStatement(body)]);
34 }
35
36 - // Technically, this is a conditional hook call. However, we expect
37 - // __DEV__ and gatingIdentifier to be runtime constants
36 + /*
37 + * Technically, this is a conditional hook call. However, we expect
38 + * __DEV__ and gatingIdentifier to be runtime constants
39 + */
40 const test: t.IfStatement = t.ifStatement(
41 t.identifier("__DEV__"),
42 t.expressionStatement(
compiler/packages/babel-plugin-react-forget/src/Entrypoint/Options.ts
+48 -42
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -10,14 +10,18 @@ import { CompilerErrorDetailOptions } from "../CompilerError";
10 import { ExternalFunction, PartialEnvironmentConfig } from "../HIR/Environment";
11
12 export type PanicThresholdOptions =
13 - // Any errors will panic the compiler by throwing an exception, which will
14 - // bubble up to the nearest exception handler above the Forget transform.
15 - // If Forget is invoked through `ReactForgetBabelPlugin`, this will at the least
16 - // skip Forget compilation for the rest of current file.
13 + /*
14 + * Any errors will panic the compiler by throwing an exception, which will
15 + * bubble up to the nearest exception handler above the Forget transform.
16 + * If Forget is invoked through `ReactForgetBabelPlugin`, this will at the least
17 + * skip Forget compilation for the rest of current file.
18 + */
19 | "ALL_ERRORS"
18 - // Panic by throwing an exception only on critical or unrecognized errors.
19 - // For all other errors, skip the erroring function without inserting
20 - // a Forget-compiled version (i.e. same behavior as noEmit).
20 + /*
21 + * Panic by throwing an exception only on critical or unrecognized errors.
22 + * For all other errors, skip the erroring function without inserting
23 + * a Forget-compiled version (i.e. same behavior as noEmit).
24 + */
25 | "CRITICAL_ERRORS"
26 // Never panic by throwing an exception.
27 | "NONE";
@@ -27,52 +31,52 @@ export type PluginOptions = {
31
32 logger: Logger | null;
33
30 - /**
34 + /*
35 * Specifying a `gating` config, makes Forget compile and emit a separate
36 * version of the function gated by importing the `gating.importSpecifierName` from the
37 * specified `gating.source`.
38 *
39 * For example:
36 - * gating: {
37 - * source: 'ReactForgetFeatureFlag',
38 - * importSpecifierName: 'isForgetEnabled_Pokes',
39 - * }
40 + * gating: {
41 + * source: 'ReactForgetFeatureFlag',
42 + * importSpecifierName: 'isForgetEnabled_Pokes',
43 + * }
44 *
45 * produces:
42 - * import {isForgetEnabled_Pokes} from 'ReactForgetFeatureFlag';
46 + * import {isForgetEnabled_Pokes} from 'ReactForgetFeatureFlag';
47 *
44 - * Foo_forget() {}
48 + * Foo_forget() {}
49 *
46 - * Foo_uncompiled() {}
50 + * Foo_uncompiled() {}
51 *
48 - * var Foo = isForgetEnabled_Pokes() ? Foo_forget : Foo_uncompiled;
52 + * var Foo = isForgetEnabled_Pokes() ? Foo_forget : Foo_uncompiled;
53 */
54 gating: ExternalFunction | null;
51 - /**
55 + /*
56 * Enables instrumentation codegen. This emits a dev-mode only call to an
57 * instrumentation function, for components and hooks that Forget compiles.
58 * For example:
55 - * instrumentForget: {
56 - * source: 'react-forget-runtime',
57 - * importSpecifierName: 'useRenderCounter',
58 - * }
59 + * instrumentForget: {
60 + * source: 'react-forget-runtime',
61 + * importSpecifierName: 'useRenderCounter',
62 + * }
63 *
64 * produces:
61 - * import {useRenderCounter} from 'react-forget-runtime-pokes';
65 + * import {useRenderCounter} from 'react-forget-runtime-pokes';
66 *
63 - * function Component(props) {
64 - * if (__DEV__) {
65 - * useRenderCounter();
66 - * }
67 - * // ...
68 - * }
67 + * function Component(props) {
68 + * if (__DEV__) {
69 + * useRenderCounter();
70 + * }
71 + * // ...
72 + * }
73 *
74 */
75 instrumentForget: ExternalFunction | null;
76
77 panicThreshold: PanicThresholdOptions;
78
75 - /**
79 + /*
80 * When enabled, Forget will continue statically analyzing and linting code, but skip over codegen
81 * passes.
82 *
@@ -80,21 +84,21 @@ export type PluginOptions = {
84 */
85 noEmit: boolean;
86
83 - /**
87 + /*
88 * Determines the strategy for determining which functions to compile. Note that regardless of
89 * which mode is enabled, a component can be opted out by adding the string literal
90 * `"use no forget"` at the top of the function body, eg.:
91 *
92 * ```
93 * function ComponentYouWantToSkipCompilation(props) {
90 - * "use no forget";
91 - * ...
94 + * "use no forget";
95 + * ...
96 * }
97 * ```
98 */
99 compilationMode: CompilationMode;
100
97 - /**
101 + /*
102 * If enabled, Forget will import `useMemoCache` from a polyfill instead of React. Use this if
103 * you are for whatever reason unable to use an experimental version of React.
104 *
@@ -107,14 +111,16 @@ export type PluginOptions = {
111 };
112
113 export type CompilationMode =
110 - // Compiles functions annotated with "use forget" or component/hook-like functions.
111 - // This latter includes:
112 - // * Components declared with component syntax.
113 - // * Functions which can be inferred to be a component or hook:
114 - // - Be named like a hook or component. This logic matches the ESLint rule.
115 - // - *and* create JSX and/or call a hook. This is an additional check to help prevent
116 - // false positives, since compilation has a greater impact than linting.
117 - // This is the default mode
114 + /*
115 + * Compiles functions annotated with "use forget" or component/hook-like functions.
116 + * This latter includes:
117 + * * Components declared with component syntax.
118 + * * Functions which can be inferred to be a component or hook:
119 + * - Be named like a hook or component. This logic matches the ESLint rule.
120 + * - *and* create JSX and/or call a hook. This is an additional check to help prevent
121 + * false positives, since compilation has a greater impact than linting.
122 + * This is the default mode
123 + */
124 | "infer"
125 // Compile only functions which are explicitly annotated with "use forget"
126 | "annotation"
compiler/packages/babel-plugin-react-forget/src/Entrypoint/Pipeline.ts
+2 -2
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -100,7 +100,7 @@ export function* run(
100 return ast;
101 }
102
103 -/**
103 +/*
104 * Note: this is split from run() to make `config` out of scope, so that all
105 * access to feature flags has to be through the Environment for consistency.
106 */
compiler/packages/babel-plugin-react-forget/src/Entrypoint/Program.ts
+69 -41
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -94,10 +94,12 @@ function handleError(
94 });
95 }
96 }
97 - /** Always throw if the flag is enabled, otherwise we only throw if the error is critical
97 + /*
98 + * Always throw if the flag is enabled, otherwise we only throw if the error is critical
99 * (eg an invariant is broken, meaning the compiler may be buggy). See
100 * {@link CompilerError.isCritical} for mappings.
100 - * */
101 + *
102 + */
103 if (
104 pass.opts.panicThreshold === "ALL_ERRORS" ||
105 (pass.opts.panicThreshold === "CRITICAL_ERRORS" && isCriticalError(err))
@@ -205,9 +207,11 @@ function findEslintSuppressions(
207 }
208 }
209
208 -// This is a hack to work around what seems to be a Babel bug. Babel doesn't
209 -// consistently respect the `skip()` function to avoid revisiting a node within
210 -// a pass, so we use this set to track nodes that we have compiled.
210 +/*
211 + * This is a hack to work around what seems to be a Babel bug. Babel doesn't
212 + * consistently respect the `skip()` function to avoid revisiting a node within
213 + * a pass, so we use this set to track nodes that we have compiled.
214 + */
215 const ALREADY_COMPILED: WeakSet<object> | Set<object> = new (WeakSet ?? Set)();
216
217 export function compileProgram(
@@ -215,9 +219,11 @@ export function compileProgram(
219 pass: CompilerPass
220 ): void {
221 const options = parsePluginOptions(pass.opts);
218 - // Record lint errors and critical errors as depending on Forget's config,
219 - // we may still need to run Forget's analysis on every function (even if we
220 - // have already encountered errors) for reporting.
222 + /*
223 + * Record lint errors and critical errors as depending on Forget's config,
224 + * we may still need to run Forget's analysis on every function (even if we
225 + * have already encountered errors) for reporting.
226 + */
227 const lintError = findEslintSuppressions(pass.comments);
228 let hasCriticalError = lintError != null;
229 const compiledFns: CompileResult[] = [];
@@ -233,15 +239,19 @@ export function compileProgram(
239 return;
240 }
241
236 - // We may be generating a new FunctionDeclaration node, so we must skip over it or this
237 - // traversal will loop infinitely.
238 - // Ensure we avoid visiting the original function again.
242 + /*
243 + * We may be generating a new FunctionDeclaration node, so we must skip over it or this
244 + * traversal will loop infinitely.
245 + * Ensure we avoid visiting the original function again.
246 + */
247 ALREADY_COMPILED.add(fn.node);
248 fn.skip();
249
250 if (lintError != null) {
243 - // Report lint suppressions as InvalidReact if we find forget-able
244 - // functions within the file
251 + /*
252 + * Report lint suppressions as InvalidReact if we find forget-able
253 + * functions within the file
254 + */
255 handleError(pass, fn.node.loc ?? null, lintError);
256 }
257
@@ -270,15 +280,19 @@ export function compileProgram(
280 program.traverse(
281 {
282 ClassDeclaration(node: NodePath<t.ClassDeclaration>) {
273 - // Don't visit functions defined inside classes, because they
274 - // can reference `this` which is unsafe for compilation
283 + /*
284 + * Don't visit functions defined inside classes, because they
285 + * can reference `this` which is unsafe for compilation
286 + */
287 node.skip();
288 return;
289 },
290
291 ClassExpression(node: NodePath<t.ClassExpression>) {
280 - // Don't visit functions defined inside classes, because they
281 - // can reference `this` which is unsafe for compilation
292 + /*
293 + * Don't visit functions defined inside classes, because they
294 + * can reference `this` which is unsafe for compilation
295 + */
296 node.skip();
297 return;
298 },
@@ -333,8 +347,10 @@ export function compileProgram(
347 return;
348 }
349
336 - // Only insert Forget-ified functions if we have not encountered a critical
337 - // error elsewhere in the file, regardless of bailout mode.
350 + /*
351 + * Only insert Forget-ified functions if we have not encountered a critical
352 + * error elsewhere in the file, regardless of bailout mode.
353 + */
354 for (const { originalFn, compiledFn } of compiledFns) {
355 const transformedFn = createNewFunctionNode(originalFn, compiledFn);
356 if (instrumentForget != null) {
@@ -427,7 +443,7 @@ function isHookName(s: string): boolean {
443 return /^use[A-Z0-9]/.test(s);
444 }
445
430 -/**
446 +/*
447 * We consider hooks to be a hook name identifier or a member expression
448 * containing a hook name.
449 */
@@ -448,7 +464,7 @@ function isHook(path: NodePath<t.Expression | t.PrivateName>): boolean {
464 }
465 }
466
451 -/**
467 +/*
468 * Checks if the node is a React component name. React component names must
469 * always start with an uppercase letter.
470 */
@@ -472,7 +488,7 @@ function isReactFunction(
488 );
489 }
490
475 -/**
491 +/*
492 * Checks if the node is a callback argument of forwardRef. This render function
493 * should follow the rules of hooks.
494 */
@@ -485,7 +501,7 @@ function isForwardRefCallback(path: NodePath<t.Expression>): boolean {
501 );
502 }
503
488 -/**
504 +/*
505 * Checks if the node is a callback argument of React.memo. This anonymous
506 * functional component should follow the rules of hooks.
507 */
@@ -498,8 +514,10 @@ function isMemoCallback(path: NodePath<t.Expression>): boolean {
514 );
515 }
516
501 -// Adapted from the ESLint rule at
502 -// https://github.com/facebook/react/blob/main/packages/eslint-plugin-react-hooks/src/RulesOfHooks.js#L90-L103
517 +/*
518 + * Adapted from the ESLint rule at
519 + * https://github.com/facebook/react/blob/main/packages/eslint-plugin-react-hooks/src/RulesOfHooks.js#L90-L103
520 + */
521 function isReactFunctionLike(
522 node: NodePath<
523 t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
@@ -514,14 +532,18 @@ function isReactFunctionLike(
532 return (
533 // As an added check we also look for hook invocations or JSX
534 callsHooksOrCreatesJsx(node) &&
517 - // and avoid helper functions that take more than one argument
518 - // helpers are _usually_ named with lowercase, but some code may
519 - // violate this rule
535 + /*
536 + * and avoid helper functions that take more than one argument
537 + * helpers are _usually_ named with lowercase, but some code may
538 + * violate this rule
539 + */
540 node.get("params").length <= 1
541 );
542 }
523 - // Otherwise for function or arrow function expressions, check if they
524 - // appear as the argument to React.forwardRef() or React.memo():
543 + /*
544 + * Otherwise for function or arrow function expressions, check if they
545 + * appear as the argument to React.forwardRef() or React.memo():
546 + */
547 if (node.isFunctionExpression() || node.isArrowFunctionExpression()) {
548 if (isForwardRefCallback(node) || isMemoCallback(node)) {
549 // As an added check we also look for hook invocations or JSX
@@ -551,7 +573,7 @@ function callsHooksOrCreatesJsx(node: NodePath<t.Node>): boolean {
573 return invokesHooks || createsJsx;
574 }
575
554 -/**
576 +/*
577 * Gets the static name of a function AST node. For function declarations it is
578 * easy. For anonymous function expressions it is much harder. If you search for
579 * `IsAnonymousFunctionDefinition()` in the ECMAScript spec you'll find places
@@ -589,19 +611,23 @@ function getFunctionName(
611 !parent.get("computed") &&
612 parent.get("key").isLVal()
613 ) {
592 - // {useHook: () => {}}
593 - // {useHook() {}}
614 + /*
615 + * {useHook: () => {}}
616 + * {useHook() {}}
617 + */
618 id = parent.get("key");
619 } else if (
620 parent.isAssignmentPattern() &&
621 parent.get("right").node === path.node &&
622 !parent.get("computed")
623 ) {
600 - // const {useHook = () => {}} = {};
601 - // ({useHook = () => {}} = {});
602 - //
603 - // Kinda clowny, but we'd said we'd follow spec convention for
604 - // `IsAnonymousFunctionDefinition()` usage.
624 + /*
625 + * const {useHook = () => {}} = {};
626 + * ({useHook = () => {}} = {});
627 + *
628 + * Kinda clowny, but we'd said we'd follow spec convention for
629 + * `IsAnonymousFunctionDefinition()` usage.
630 + */
631 id = parent.get("left");
632 }
633 if (id !== null && (id.isIdentifier() || id.isMemberExpression())) {
@@ -634,8 +660,10 @@ function checkFunctionReferencedBeforeDeclarationAtTopLevel(
660 }
661
662 const scope = id.scope.getFunctionParent();
637 - // A null scope means there's no function scope, which means we're at the
638 - // top level scope.
663 + /*
664 + * A null scope means there's no function scope, which means we're at the
665 + * top level scope.
666 + */
667 if (
668 scope === null &&
669 id.node.loc &&
compiler/packages/babel-plugin-react-forget/src/Entrypoint/index.ts
+1 -1
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
compiler/packages/babel-plugin-react-forget/src/HIR/AssertConsistentIdentifiers.ts
+2 -2
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -20,7 +20,7 @@ import {
20 eachTerminalOperand,
21 } from "./visitors";
22
23 -/**
23 +/*
24 * Validation pass to check that there is a 1:1 mapping between Identifier objects and IdentifierIds,
25 * ie there can only be one Identifier instance per IdentifierId.
26 */
compiler/packages/babel-plugin-react-forget/src/HIR/AssertTerminalSuccessorsExist.ts
+1 -1
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
compiler/packages/babel-plugin-react-forget/src/HIR/AssertValidMutableRanges.ts
+2 -2
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -13,7 +13,7 @@ import {
13 eachTerminalOperand,
14 } from "./visitors";
15
16 -/**
16 +/*
17 * Checks that all mutable ranges in the function are well-formed, with
18 * start === end === 0 OR end > start.
19 */
compiler/packages/babel-plugin-react-forget/src/HIR/BuildHIR.ts
+144 -102
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -45,13 +45,15 @@ import {
45 } from "./HIR";
46 import HIRBuilder, { Bindings } from "./HIRBuilder";
47
48 -// *******************************************************************************************
49 -// *******************************************************************************************
50 -// ************************************* Lowering to HIR *************************************
51 -// *******************************************************************************************
52 -// *******************************************************************************************
48 +/*
49 + * *******************************************************************************************
50 + * *******************************************************************************************
51 + * ************************************* Lowering to HIR *************************************
52 + * *******************************************************************************************
53 + * *******************************************************************************************
54 + */
55
54 -/**
56 +/*
57 * Converts a function into a high-level intermediate form (HIR) which represents
58 * the code as a control-flow graph. All normal control-flow is modeled as accurately
59 * as possible to allow precise, expression-level memoization. The main exceptions are
@@ -211,9 +213,7 @@ export function lower(
213 });
214 }
215
214 -/**
215 - * Helper to lower a statement
216 - */
216 +// Helper to lower a statement
217 function lowerStatement(
218 builder: HIRBuilder,
219 stmtPath: NodePath<t.Statement>,
@@ -226,9 +226,11 @@ function lowerStatement(
226 const value = lowerExpressionToTemporary(builder, stmt.get("argument"));
227 const handler = builder.resolveThrowHandler();
228 if (handler != null) {
229 - // NOTE: we could support this, but a `throw` inside try/catch is using exceptions
230 - // for control-flow and is generally considered an anti-pattern. we can likely
231 - // just not support this pattern, unless it really becomes necessary for some reason.
229 + /*
230 + * NOTE: we could support this, but a `throw` inside try/catch is using exceptions
231 + * for control-flow and is generally considered an anti-pattern. we can likely
232 + * just not support this pattern, unless it really becomes necessary for some reason.
233 + */
234 builder.errors.push({
235 reason:
236 "(BuildHIR::lowerStatement) Support ThrowStatement inside of try/catch",
@@ -351,16 +353,20 @@ function lowerStatement(
353
354 for (const s of statements) {
355 const hoistableIdentifiers = new Set<NodePath<t.Identifier>>();
354 - // After visiting the declaration, hoisting is no longer required
355 - // TODO: support other kinds of declarations
356 + /*
357 + * After visiting the declaration, hoisting is no longer required
358 + * TODO: support other kinds of declarations
359 + */
360 if (s.isVariableDeclaration()) {
361 for (const decl of s.get("declarations")) {
362 recordDeclaration(decl.get("id"));
363 }
364 }
365
362 - // If we see a hoistable identifier before its declaration, it should be hoisted just
363 - // before the statement that references it
366 + /*
367 + * If we see a hoistable identifier before its declaration, it should be hoisted just
368 + * before the statement that references it
369 + */
370 s.traverse({
371 Identifier(id: NodePath<t.Identifier>) {
372 const binding = stmt.scope.getBinding(id.node.name);
@@ -587,7 +593,7 @@ function lowerStatement(
593 }
594 );
595 });
590 - /**
596 + /*
597 * The code leading up to the loop must jump to the conditional block,
598 * to evaluate whether to enter the loop or bypass to the continuation.
599 */
@@ -603,7 +609,7 @@ function lowerStatement(
609 },
610 conditionalBlock
611 );
606 - /**
612 + /*
613 * The conditional block is empty and exists solely as conditional for
614 * (re)entering or exiting the loop
615 */
@@ -630,14 +636,18 @@ function lowerStatement(
636 case "ForStatement":
637 case "WhileStatement":
638 case "DoWhileStatement": {
633 - // labeled loops are special because of continue, so push the label
634 - // down
639 + /*
640 + * labeled loops are special because of continue, so push the label
641 + * down
642 + */
643 lowerStatement(builder, stmt.get("body"), label);
644 break;
645 }
646 default: {
639 - // All other statements create a continuation block to allow `break`,
640 - // explicitly *don't* pass the label down
647 + /*
648 + * All other statements create a continuation block to allow `break`,
649 + * explicitly *don't* pass the label down
650 + */
651 const continuationBlock = builder.reserve("block");
652 const block = builder.enter("block", () => {
653 const body = stmt.get("body");
@@ -670,13 +680,13 @@ function lowerStatement(
680 const stmt = stmtPath as NodePath<t.SwitchStatement>;
681 // Block following the switch
682 const continuationBlock = builder.reserve("block");
673 - /**
683 + /*
684 * The goto target for any cases that fallthrough, which initially starts
685 * as the continuation block and is then updated as we iterate through cases
686 * in reverse order.
687 */
688 let fallthrough = continuationBlock.id;
679 - /**
689 + /*
690 * Iterate through cases in reverse order, so that previous blocks can fallthrough
691 * to successors
692 */
@@ -702,7 +712,7 @@ function lowerStatement(
712 case_
713 .get("consequent")
714 .forEach((consequent) => lowerStatement(builder, consequent));
705 - /**
715 + /*
716 * always generate a fallthrough to the next block, this may be dead code
717 * if there was an explicit break, but if so it will be pruned later.
718 */
@@ -725,12 +735,12 @@ function lowerStatement(
735 });
736 fallthrough = block;
737 }
728 - /**
738 + /*
739 * it doesn't matter for our analysis purposes, but reverse the order of the cases
740 * back to the original to make it match the original code/intent.
741 */
742 cases.reverse();
733 - /**
743 + /*
744 * If there wasn't an explicit default case, generate one to model the fact that execution
745 * could bypass any of the other cases and jump directly to the continuation.
746 */
@@ -876,8 +886,10 @@ function lowerStatement(
886 }
887 );
888 });
879 - // Jump to the conditional block to evaluate whether to (re)enter the loop or exit to the
880 - // continuation block.
889 + /*
890 + * Jump to the conditional block to evaluate whether to (re)enter the loop or exit to the
891 + * continuation block.
892 + */
893 const loc = stmt.node.loc ?? GeneratedSource;
894 builder.terminateWithContinuation(
895 {
@@ -890,7 +902,7 @@ function lowerStatement(
902 },
903 conditionalBlock
904 );
893 - /**
905 + /*
906 * The conditional block is empty and exists solely as conditional for
907 * (re)entering or exiting the loop
908 */
@@ -918,12 +930,14 @@ function lowerStatement(
930 });
931 const id = stmt.get("id") as NodePath<t.Identifier>;
932
921 - // Desugar FunctionDeclaration to FunctionExpression.
922 - //
923 - // For example:
924 - // function foo() {};
925 - // becomes
926 - // let foo = function foo() {};
933 + /*
934 + * Desugar FunctionDeclaration to FunctionExpression.
935 + *
936 + * For example:
937 + * function foo() {};
938 + * becomes
939 + * let foo = function foo() {};
940 + */
941 const desugared = stmt.replaceWith(
942 t.variableDeclaration("let", [
943 t.variableDeclarator(
@@ -981,9 +995,11 @@ function lowerStatement(
995 initBlock
996 );
997
984 - // The init of a ForOf statement is compound over a left (VariableDeclaration | LVal) and
985 - // right (Expression), so we synthesize a new InstrValue and assignment (potentially multiple
986 - // instructions when we handle other syntax like Patterns)
998 + /*
999 + * The init of a ForOf statement is compound over a left (VariableDeclaration | LVal) and
1000 + * right (Expression), so we synthesize a new InstrValue and assignment (potentially multiple
1001 + * instructions when we handle other syntax like Patterns)
1002 + */
1003 const left = stmt.get("left");
1004 const leftLoc = left.node.loc ?? GeneratedSource;
1005 let test: Place;
@@ -1064,9 +1080,11 @@ function lowerStatement(
1080 initBlock
1081 );
1082
1067 - // The init of a ForIn statement is compound over a left (VariableDeclaration | LVal) and
1068 - // right (Expression), so we synthesize a new InstrValue and assignment (potentially multiple
1069 - // instructions when we handle other syntax like Patterns)
1083 + /*
1084 + * The init of a ForIn statement is compound over a left (VariableDeclaration | LVal) and
1085 + * right (Expression), so we synthesize a new InstrValue and assignment (potentially multiple
1086 + * instructions when we handle other syntax like Patterns)
1087 + */
1088 const left = stmt.get("left");
1089 const leftLoc = left.node.loc ?? GeneratedSource;
1090 let test: Place;
@@ -2146,8 +2164,10 @@ function lowerExpression(
2164 argument
2165 );
2166 if (lvalue === null) {
2149 - // lowerIdentifierForAssignment should have already reported an error if it returned null,
2150 - // we check here just in case
2167 + /*
2168 + * lowerIdentifierForAssignment should have already reported an error if it returned null,
2169 + * we check here just in case
2170 + */
2171 if (!builder.errors.hasErrors()) {
2172 builder.errors.push({
2173 reason: `(BuildHIR::lowerExpression) Found an invalid UpdateExpression without a previously reported error`,
@@ -2209,9 +2229,11 @@ function lowerOptionalMemberExpression(
2229 const continuationBlock = builder.reserve(builder.currentBlockKind());
2230 const consequent = builder.reserve("value");
2231
2212 - // block to evaluate if the callee is null/undefined, this sets the result of the call to undefined.
2213 - // note that we only create an alternate when first entering an optional subtree of the ast: if this
2214 - // is a child of an optional node, we use the alterate created by the parent.
2232 + /*
2233 + * block to evaluate if the callee is null/undefined, this sets the result of the call to undefined.
2234 + * note that we only create an alternate when first entering an optional subtree of the ast: if this
2235 + * is a child of an optional node, we use the alterate created by the parent.
2236 + */
2237 const alternate =
2238 parentAlternate !== null
2239 ? parentAlternate
@@ -2268,8 +2290,10 @@ function lowerOptionalMemberExpression(
2290 suggestions: null,
2291 });
2292
2271 - // block to evaluate if the callee is non-null/undefined. arguments are lowered in this block to preserve
2272 - // the semantic of conditional evaluation depending on the callee
2293 + /*
2294 + * block to evaluate if the callee is non-null/undefined. arguments are lowered in this block to preserve
2295 + * the semantic of conditional evaluation depending on the callee
2296 + */
2297 builder.enterReserved(consequent, () => {
2298 const { value } = lowerMemberExpression(builder, expr, object);
2299 const temp = lowerValueToTemporary(builder, value);
@@ -2315,9 +2339,11 @@ function lowerOptionalCallExpression(
2339 const continuationBlock = builder.reserve(builder.currentBlockKind());
2340 const consequent = builder.reserve("value");
2341
2318 - // block to evaluate if the callee is null/undefined, this sets the result of the call to undefined.
2319 - // note that we only create an alternate when first entering an optional subtree of the ast: if this
2320 - // is a child of an optional node, we use the alterate created by the parent.
2342 + /*
2343 + * block to evaluate if the callee is null/undefined, this sets the result of the call to undefined.
2344 + * note that we only create an alternate when first entering an optional subtree of the ast: if this
2345 + * is a child of an optional node, we use the alterate created by the parent.
2346 + */
2347 const alternate =
2348 parentAlternate !== null
2349 ? parentAlternate
@@ -2342,8 +2368,10 @@ function lowerOptionalCallExpression(
2368 };
2369 });
2370
2345 - // Lower the callee within the test block to represent the fact that the code for the callee is
2346 - // scoped within the optional
2371 + /*
2372 + * Lower the callee within the test block to represent the fact that the code for the callee is
2373 + * scoped within the optional
2374 + */
2375 let callee:
2376 | { kind: "CallExpression"; callee: Place }
2377 | { kind: "MethodCall"; receiver: Place; property: Place };
@@ -2393,8 +2421,10 @@ function lowerOptionalCallExpression(
2421 };
2422 });
2423
2396 - // block to evaluate if the callee is non-null/undefined. arguments are lowered in this block to preserve
2397 - // the semantic of conditional evaluation depending on the callee
2424 + /*
2425 + * block to evaluate if the callee is non-null/undefined. arguments are lowered in this block to preserve
2426 + * the semantic of conditional evaluation depending on the callee
2427 + */
2428 builder.enterReserved(consequent, () => {
2429 const args = lowerArguments(builder, expr.get("arguments"));
2430 const temp = buildTemporaryPlace(builder, loc);
@@ -2454,7 +2484,7 @@ function lowerOptionalCallExpression(
2484 return { kind: "LoadLocal", place, loc: place.loc };
2485 }
2486
2457 -/**
2487 +/*
2488 * There are a few places where we do not preserve original evaluation ordering and/or control flow, such as
2489 * switch case test values and default values in destructuring (assignment patterns). In these cases we allow
2490 * simple expressions whose evaluation cannot be observed:
@@ -2569,10 +2599,12 @@ function isReorderableExpression(
2599 });
2600 }
2601 case "MemberExpression": {
2572 - // A common pattern is switch statements where the case test values are properties of a global,
2573 - // eg `case ProductOptions.Option: { ... }`
2574 - // We therefore allow expressions where the innermost object is a global identifier, and reject
2575 - // all other member expressions (for now).
2602 + /*
2603 + * A common pattern is switch statements where the case test values are properties of a global,
2604 + * eg `case ProductOptions.Option: { ... }`
2605 + * We therefore allow expressions where the innermost object is a global identifier, and reject
2606 + * all other member expressions (for now).
2607 + */
2608 const test = expr as NodePath<t.MemberExpression>;
2609 let innerObject: NodePath<t.Expression> = test;
2610 while (innerObject.isMemberExpression()) {
@@ -2862,7 +2894,7 @@ function lowerJsxElement(
2894 }
2895 }
2896
2865 -/**
2897 +/*
2898 * Trims whitespace according to the JSX spec:
2899 * > JSX removes whitespace at the beginning and ending of a line.
2900 * > It also removes blank lines. New lines adjacent to tags are removed;
@@ -2955,12 +2987,14 @@ function lowerFunction(
2987 const componentScope: Scope = builder.parentFunction.scope;
2988 const captured = gatherCapturedDeps(builder, expr, componentScope);
2989
2958 - // TODO(gsn): In the future, we could only pass in the context identifiers
2959 - // that are actually used by this function and it's nested functions, rather
2960 - // than all context identifiers.
2961 - //
2962 - // This isn't a problem in practice because use Babel's scope analysis to
2963 - // identify the correct references.
2990 + /*
2991 + * TODO(gsn): In the future, we could only pass in the context identifiers
2992 + * that are actually used by this function and it's nested functions, rather
2993 + * than all context identifiers.
2994 + *
2995 + * This isn't a problem in practice because use Babel's scope analysis to
2996 + * identify the correct references.
2997 + */
2998 const lowering = lower(
2999 expr,
3000 builder.environment,
@@ -3034,9 +3068,7 @@ function lowerIdentifier(
3068 return place;
3069 }
3070
3037 -/**
3038 - * Creates a temporary Identifier and Place referencing that identifier.
3039 - */
3071 +// Creates a temporary Identifier and Place referencing that identifier.
3072 function buildTemporaryPlace(builder: HIRBuilder, loc: SourceLocation): Place {
3073 const place: Place = {
3074 kind: "Identifier",
@@ -3218,12 +3250,14 @@ function lowerAssignment(
3250 const elements = lvalue.get("elements");
3251 const items: ArrayPattern["items"] = [];
3252 const followups: Array<{ place: Place; path: NodePath<t.LVal> }> = [];
3221 - // A given destructuring statement must contain all declarations or all
3222 - // reassignments. This is enforced by the parser, but we rewrite nested
3223 - // destructuring into assignment to a temporary. Therefore, if we see
3224 - // any reassignments that are nested destructuring we fall back to
3225 - // using temporaries for all variables, and emitting the actual reassignments
3226 - // in follow-up statements
3253 + /*
3254 + * A given destructuring statement must contain all declarations or all
3255 + * reassignments. This is enforced by the parser, but we rewrite nested
3256 + * destructuring into assignment to a temporary. Therefore, if we see
3257 + * any reassignments that are nested destructuring we fall back to
3258 + * using temporaries for all variables, and emitting the actual reassignments
3259 + * in follow-up statements
3260 + */
3261 const forceTemporaries =
3262 kind === InstructionKind.Reassign &&
3263 elements.some((element) => !element.isIdentifier());
@@ -3313,12 +3347,14 @@ function lowerAssignment(
3347 const propertiesPaths = lvalue.get("properties");
3348 const properties: ObjectPattern["properties"] = [];
3349 const followups: Array<{ place: Place; path: NodePath<t.LVal> }> = [];
3316 - // A given destructuring statement must contain all declarations or all
3317 - // reassignments. This is enforced by the parser, but we rewrite nested
3318 - // destructuring into assignment to a temporary. Therefore, if we see
3319 - // any reassignments that are nested destructuring we fall back to
3320 - // using temporaries for all variables, and emitting the actual reassignments
3321 - // in follow-up statements
3350 + /*
3351 + * A given destructuring statement must contain all declarations or all
3352 + * reassignments. This is enforced by the parser, but we rewrite nested
3353 + * destructuring into assignment to a temporary. Therefore, if we see
3354 + * any reassignments that are nested destructuring we fall back to
3355 + * using temporaries for all variables, and emitting the actual reassignments
3356 + * in follow-up statements
3357 + */
3358 const forceTemporaries =
3359 kind === InstructionKind.Reassign &&
3360 propertiesPaths.some(
@@ -3456,8 +3492,10 @@ function lowerAssignment(
3492 const continuationBlock = builder.reserve(builder.currentBlockKind());
3493
3494 const consequent = builder.enter("value", () => {
3459 - // Because we reorder evaluation, we restrict the allowed default values to those where
3460 - // evaluation order is unobservable
3495 + /*
3496 + * Because we reorder evaluation, we restrict the allowed default values to those where
3497 + * evaluation order is unobservable
3498 + */
3499 const defaultValue = lowerReorderableExpression(
3500 builder,
3501 lvalue.get("right")
@@ -3573,8 +3611,10 @@ function gatherCapturedDeps(
3611 const capturedRefs: Set<Place> = new Set();
3612 const seenPaths: Set<string> = new Set();
3613
3576 - // Capture all the scopes from the parent of this function up to and including
3577 - // the component scope.
3614 + /*
3615 + * Capture all the scopes from the parent of this function up to and including
3616 + * the component scope.
3617 + */
3618 const pureScopes: Set<Scope> = captureScopes({
3619 from: fn.scope.parent,
3620 to: componentScope,
@@ -3598,8 +3638,10 @@ function gatherCapturedDeps(
3638 ): void {
3639 // Base context variable to depend on
3640 let baseIdentifier: NodePath<t.Identifier> | NodePath<t.JSXIdentifier>;
3601 - // Base expression to depend on, which (for now) may contain non side-effectful
3602 - // member expressions
3641 + /*
3642 + * Base expression to depend on, which (for now) may contain non side-effectful
3643 + * member expressions
3644 + */
3645 let dependency:
3646 | NodePath<t.MemberExpression>
3647 | NodePath<t.Identifier>
@@ -3631,8 +3673,10 @@ function gatherCapturedDeps(
3673 }
3674 baseIdentifier = currentId;
3675
3634 - // Get the expression to depend on, which may involve PropertyLoads
3635 - // for member expressions
3676 + /*
3677 + * Get the expression to depend on, which may involve PropertyLoads
3678 + * for member expressions
3679 + */
3680 let currentDep:
3681 | NodePath<t.MemberExpression>
3682 | NodePath<t.Identifier>
@@ -3657,24 +3701,20 @@ function gatherCapturedDeps(
3701 dependency = path;
3702 }
3703
3660 - /**
3704 + /*
3705 * Skip dependency path, as we already tried to recursively add it (+ all subexpressions)
3706 * as a dependency.
3707 */
3708 dependency.skip();
3709
3666 - /**
3667 - * Add the base identifier binding as a dependency.
3668 - */
3710 + // Add the base identifier binding as a dependency.
3711 const binding = baseIdentifier.scope.getBinding(baseIdentifier.node.name);
3712 if (binding === undefined || !pureScopes.has(binding.scope)) {
3713 return;
3714 }
3715 const idKey = String(addCapturedId(binding.identifier));
3716
3675 - /**
3676 - * Add the expression (potentially a memberexpr path) as a dependency.
3677 - */
3717 + // Add the expression (potentially a memberexpr path) as a dependency.
3718 let exprKey = idKey;
3719 if (dependency.isMemberExpression()) {
3720 let pathTokens = [];
@@ -3707,9 +3747,11 @@ function gatherCapturedDeps(
3747 fn.traverse({
3748 Expression(path) {
3749 if (path.isAssignmentExpression()) {
3710 - // Babel has a bug where it doesn't visit the LHS of an
3711 - // AssignmentExpression if it's an Identifier. Work around it by explicitly
3712 - // visiting it.
3750 + /*
3751 + * Babel has a bug where it doesn't visit the LHS of an
3752 + * AssignmentExpression if it's an Identifier. Work around it by explicitly
3753 + * visiting it.
3754 + */
3755 const left = path.get("left");
3756 if (left.isIdentifier()) {
3757 handleMaybeDependency(left);
compiler/packages/babel-plugin-react-forget/src/HIR/Dominator.ts
+16 -22
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -10,7 +10,7 @@ import { CompilerError } from "../CompilerError";
10 import { BlockId, HIRFunction } from "./HIR";
11 import { eachTerminalSuccessor } from "./visitors";
12
13 -/**
13 +/*
14 * Computes the dominator tree of the given function. The returned `Dominator` stores the immediate
15 * dominator of each node in the function, which can be retrieved with `Dominator.prototype.get()`.
16 *
@@ -24,7 +24,7 @@ export function computeDominatorTree(fn: HIRFunction): Dominator<BlockId> {
24 return new Dominator(graph.entry, nodes);
25 }
26
27 -/**
27 +/*
28 * Similar to `computeDominatorTree()` but computes the post dominators of the function. The returned
29 * `PostDominator` stores the immediate post-dominators of each node in the function.
30 *
@@ -39,9 +39,11 @@ export function computePostDominatorTree(
39 const graph = buildReverseGraph(fn, options.includeThrowsAsExitNode);
40 const nodes = computeImmediateDominators(graph);
41
42 - // When options.includeThrowsAsExitNode is false, nodes that flow into a throws
43 - // terminal and don't reach the exit node won't be in the node map. Add them
44 - // with themselves as dominator to reflect that they don't flow into the exit.
42 + /*
43 + * When options.includeThrowsAsExitNode is false, nodes that flow into a throws
44 + * terminal and don't reach the exit node won't be in the node map. Add them
45 + * with themselves as dominator to reflect that they don't flow into the exit.
46 + */
47 if (!options.includeThrowsAsExitNode) {
48 for (const [id] of fn.body.blocks) {
49 if (!nodes.has(id)) {
@@ -63,9 +65,7 @@ type Graph<T> = {
65 nodes: Map<T, Node<T>>;
66 };
67
66 -/**
67 - * A dominator tree that stores the immediate dominator for each block in function.
68 - */
68 +// A dominator tree that stores the immediate dominator for each block in function.
69 export class Dominator<T> {
70 #entry: T;
71 #nodes: Map<T, T>;
@@ -75,14 +75,12 @@ export class Dominator<T> {
75 this.#nodes = nodes;
76 }
77
78 - /**
79 - * Returns the entry node
80 - */
78 + // Returns the entry node
79 get entry(): T {
80 return this.#entry;
81 }
82
85 - /**
83 + /*
84 * Returns the immediate dominator of the block with @param id if present. Returns null
85 * if there is no immediate dominator (ie if the dominator is @param id itself).
86 */
@@ -118,14 +116,12 @@ export class PostDominator<T> {
116 this.#nodes = nodes;
117 }
118
121 - /**
122 - * Returns the node representing normal exit from the function, ie return terminals.
123 - */
119 + // Returns the node representing normal exit from the function, ie return terminals.
120 get exit(): T {
121 return this.#exit;
122 }
123
128 - /**
124 + /*
125 * Returns the immediate dominator of the block with @param id if present. Returns null
126 * if there is no immediate dominator (ie if the dominator is @param id itself).
127 */
@@ -152,7 +148,7 @@ export class PostDominator<T> {
148 }
149 }
150
155 -/**
151 +/*
152 * The implementation is a straightforward adaptation of https://www.cs.rice.edu/~keith/Embed/dom.pdf
153 * except that CFG nodes ordering is inverted (so the comparison functions are swapped)
154 */
@@ -219,9 +215,7 @@ function intersect<T>(a: T, b: T, graph: Graph<T>, nodes: Map<T, T>): T {
215 return block1.id;
216 }
217
222 -/**
223 - * Turns the HIRFunction into a simplified internal form that is shared for dominator/post-dominator computation
224 - */
218 +// Turns the HIRFunction into a simplified internal form that is shared for dominator/post-dominator computation
219 function buildGraph(fn: HIRFunction): Graph<BlockId> {
220 const graph: Graph<BlockId> = { entry: fn.body.entry, nodes: new Map() };
221 let index = 0;
@@ -236,7 +230,7 @@ function buildGraph(fn: HIRFunction): Graph<BlockId> {
230 return graph;
231 }
232
239 -/**
233 +/*
234 * Turns the HIRFunction into a simplified internal form that is shared for dominator/post-dominator computation,
235 * notably this version flips the graph and puts the reversed form back into RPO (such that successors are before predecessors).
236 * Note that RPO of the reversed graph isn't the same as reversed RPO of the forward graph because of loops.
compiler/packages/babel-plugin-react-forget/src/HIR/Environment.ts
+58 -56
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -47,27 +47,27 @@ export const ExternalFunctionSchema = z.object({
47 export type ExternalFunction = z.infer<typeof ExternalFunctionSchema>;
48
49 const HookSchema = z.object({
50 - /**
50 + /*
51 * The effect of arguments to this hook. Describes whether the hook may or may
52 * not mutate arguments, etc.
53 */
54 effectKind: z.nativeEnum(Effect),
55
56 - /**
56 + /*
57 * The kind of value returned by the hook. Allows indicating that a hook returns
58 * a primitive or already-frozen value, which can allow more precise memoization
59 * of callers.
60 */
61 valueKind: z.nativeEnum(ValueKind),
62
63 - /**
63 + /*
64 * Specifies whether hook arguments may be aliased by other arguments or by the
65 * return value of the function. Defaults to false. When enabled, this allows the
66 * compiler to avoid memoizing arguments.
67 */
68 noAlias: z.boolean().default(false),
69
70 - /**
70 + /*
71 * Specifies whether the hook returns data that is composed of:
72 * - undefined
73 * - null
@@ -89,13 +89,15 @@ const HookSchema = z.object({
89
90 export type Hook = z.infer<typeof HookSchema>;
91
92 -// TODO(mofeiZ): User defined global types (with corresponding shapes).
93 -// User defined global types should have inline ObjectShapes instead of directly
94 -// using ObjectShapes.ShapeRegistry, as a user-provided ShapeRegistry may be
95 -// accidentally be not well formed.
96 -// i.e.
97 -// missing required shapes (BuiltInArray for [] and BuiltInObject for {})
98 -// missing some recursive Object / Function shapeIds
92 +/*
93 + * TODO(mofeiZ): User defined global types (with corresponding shapes).
94 + * User defined global types should have inline ObjectShapes instead of directly
95 + * using ObjectShapes.ShapeRegistry, as a user-provided ShapeRegistry may be
96 + * accidentally be not well formed.
97 + * i.e.
98 + * missing required shapes (BuiltInArray for [] and BuiltInObject for {})
99 + * missing some recursive Object / Function shapeIds
100 + */
101
102 const EnvironmentConfigSchema = z.object({
103 customHooks: z.map(z.string(), HookSchema).nullish(),
@@ -103,38 +105,36 @@ const EnvironmentConfigSchema = z.object({
105 // 🌲
106 enableForest: z.boolean().default(false),
107
106 - /**
108 + /*
109 * Enable memoization of JSX elements in addition to other types of values. When disabled,
110 * other types (objects, arrays, call expressions, etc) are memoized, but not known JSX
111 * values.
112 */
113 memoizeJsxElements: z.boolean().default(true),
114
113 - /**
115 + /*
116 * Enable validation of hooks to partially check that the component honors the rules of hooks.
117 * When disabled, the component is assumed to follow the rules (though the Babel plugin looks
118 * for suppressions of the lint rule).
119 */
120 validateHooksUsage: z.boolean().default(true),
121
120 - /**
121 - * Validate that ref values (`ref.current`) are not accessed during render.
122 - */
122 + // Validate that ref values (`ref.current`) are not accessed during render.
123 validateRefAccessDuringRender: z.boolean().default(false),
124
125 - /**
125 + /*
126 * Validate that mutable lambdas are not passed where a frozen value is expected, since mutable
127 * lambdas cannot be frozen. The only mutation allowed inside a frozen lambda is of ref values.
128 */
129 validateFrozenLambdas: z.boolean().default(false),
130
131 - /**
131 + /*
132 * Validates that setState is not unconditionally called during render, as it can lead to
133 * infinite loops.
134 */
135 validateNoSetStateInRender: z.boolean().default(false),
136
137 - /**
137 + /*
138 * When enabled, the compiler assumes that hooks follow the Rules of React:
139 * - Hooks may memoize computation based on any of their parameters, thus
140 * any arguments to a hook are assumed frozen after calling the hook.
@@ -143,37 +143,37 @@ const EnvironmentConfigSchema = z.object({
143 */
144 enableAssumeHooksFollowRulesOfReact: z.boolean().default(false),
145
146 - /**
146 + /*
147 * When enabled, removes *all* memoization from the function: this includes
148 * removing manually added useMemo/useCallback as well as not adding Forget's
149 * usual useMemoCache-based memoization.
150 */
151 disableAllMemoization: z.boolean().default(false),
152
153 - /**
153 + /*
154 * Enables codegen mutability debugging. This emits a dev-mode only to log mutations
155 * to values that Forget assumes are immutable (for Forget compiled code).
156 * For example:
157 - * emitFreeze: {
158 - * source: 'ReactForgetRuntime',
159 - * importSpecifierName: 'makeReadOnly',
160 - * }
157 + * emitFreeze: {
158 + * source: 'ReactForgetRuntime',
159 + * importSpecifierName: 'makeReadOnly',
160 + * }
161 *
162 * produces:
163 - * import {makeReadOnly} from 'ReactForgetRuntime';
163 + * import {makeReadOnly} from 'ReactForgetRuntime';
164 *
165 - * function Component(props) {
166 - * if (c_0) {
167 - * // ...
168 - * $[0] = __DEV__ ? makeReadOnly(x) : x;
169 - * } else {
170 - * x = $[0];
171 - * }
172 - * }
165 + * function Component(props) {
166 + * if (c_0) {
167 + * // ...
168 + * $[0] = __DEV__ ? makeReadOnly(x) : x;
169 + * } else {
170 + * x = $[0];
171 + * }
172 + * }
173 */
174 enableEmitFreeze: ExternalFunctionSchema.nullish(),
175
176 - /**
176 + /*
177 * Forget infers certain operations as "freezing" a value, such that those
178 * values should not be subsequently mutated. By default this freeze operation
179 * applies to the value itself and its direct aliases, but not values captured
@@ -185,9 +185,9 @@ const EnvironmentConfigSchema = z.object({
185 * ```
186 * let x;
187 * if (cond) {
188 - * x = y
188 + * x = y
189 * } else {
190 - * x = z;
190 + * x = z;
191 * }
192 * <div>{x}</div>
193 * ```
@@ -208,39 +208,37 @@ const EnvironmentConfigSchema = z.object({
208 */
209 enableTransitivelyFreezeFunctionExpressions: z.boolean().default(false),
210
211 - /**
212 - * Enable merging consecutive scopes that invalidate together.
213 - */
211 + // Enable merging consecutive scopes that invalidate together.
212 enableMergeConsecutiveScopes: z.boolean().default(true),
213
216 - /**
217 - * Enable validation of mutable ranges
218 - */
214 + // Enable validation of mutable ranges
215 assertValidMutableRanges: z.boolean().default(false),
216
221 - /**
217 + /*
218 + *
219 * Instead of handling holey arrays, bail out with a TODO error.
223 - *
220 + *
221 * Older versions of babel seem to have inconsistent handling of holey arrays,
222 * at least when paired with HermesParser. When using these versions, we should
223 * bail out instead of throwing a Babel validation error.
227 -
224 + *
225 * The babel ast definition for array elements changed from Array<PatternLike>
226 * to Array<PatternLike | null>. Older versions does not expect null in the
227 * ArrayPattern ast and will throw a validation error.
231 - *
228 + *
229 * - HermesParser will parse [, b] into [NodePath<null>, NodePath<Identifier>]
230 * - Forget will try to preserve this holey array when we codegen back to js
231 * (e.g. we call a babel builder function arrayPattern([null, identifier]))
235 - * - Babel will fail with `TypeError: Property elements[0] of ArrayPattern
232 + * - Babel will fail with `TypeError: Property elements[0] of ArrayPattern
233 * expected node to be of a type ["PatternLike"] but instead got null`
237 - *
234 + *
235 * PR that changed the AST definition
236 * https://github.com/babel/babel/pull/10917/files#diff-19b555d2f3904c206af406540d9df200b1e16befedb83ff39ebfcbd876f7fa8aL52-R56
237 + *
238 */
239 bailoutOnHoleyArrays: z.boolean().default(false),
240
243 - /**
241 + /*
242 * Enable emitting "change variables" which store the result of whether a particular
243 * reactive scope dependency has changed since the scope was last executed.
244 *
@@ -387,8 +385,10 @@ export class Environment {
385 shapeId = receiver.shapeId;
386 }
387 if (shapeId !== null) {
390 - // If an object or function has a shapeId, it must have been assigned
391 - // by Forget (and be present in a builtin or user-defined registry)
388 + /*
389 + * If an object or function has a shapeId, it must have been assigned
390 + * by Forget (and be present in a builtin or user-defined registry)
391 + */
392 const shape = this.#shapes.get(shapeId);
393 CompilerError.invariant(shape !== undefined, {
394 reason: `[HIR] Forget internal error: cannot resolve shape ${shapeId}`,
@@ -440,9 +440,11 @@ export class Environment {
440
441 // From https://github.com/facebook/react/blob/main/packages/eslint-plugin-react-hooks/src/RulesOfHooks.js#LL18C1-L23C2
442 function isHookName(name: string): boolean {
443 - // if (__EXPERIMENTAL__) {
444 - // return name === 'use' || /^use[A-Z0-9]/.test(name);
445 - // }
443 + /*
444 + * if (__EXPERIMENTAL__) {
445 + * return name === 'use' || /^use[A-Z0-9]/.test(name);
446 + * }
447 + */
448 return /^use[A-Z0-9]/.test(name);
449 }
450
compiler/packages/babel-plugin-react-forget/src/HIR/FindContextIdentifiers.ts
+5 -3
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -125,8 +125,10 @@ function handleAssignment(
125 reassigned: Set<t.Identifier>,
126 lvalPath: NodePath<t.LVal>
127 ): void {
128 - // Find all reassignments to identifiers declared outside of currentLambda
129 - // This closely follows destructuring assignment assumptions and logic in BuildHIR
128 + /*
129 + * Find all reassignments to identifiers declared outside of currentLambda
130 + * This closely follows destructuring assignment assumptions and logic in BuildHIR
131 + */
132 const lvalNode = lvalPath.node;
133 switch (lvalNode.type) {
134 case "Identifier": {
compiler/packages/babel-plugin-react-forget/src/HIR/Globals.ts
+19 -17
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -18,7 +18,7 @@ import {
18 } from "./ObjectShape";
19 import { BuiltInType, PolyType } from "./Types";
20
21 -/**
21 +/*
22 * This file exports types and defaults for JavaScript global objects.
23 * A Forget `Environment` stores the GlobalRegistry and ShapeRegistry
24 * used for the current project. These ultimately help Forget refine
@@ -26,9 +26,7 @@ import { BuiltInType, PolyType } from "./Types";
26 * (i.e. read vs mutate) in source programs.
27 */
28
29 -/**
30 - * ShapeRegistry with default definitions for builtins and global objects.
31 - */
29 +// ShapeRegistry with default definitions for builtins and global objects.
30 export const DEFAULT_SHAPES: ShapeRegistry = new Map(BUILTIN_SHAPES);
31
32 // Hack until we add ObjectShapes for all globals
@@ -90,15 +88,17 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
88 returnValueKind: ValueKind.Immutable,
89 }),
90 ],
93 - // https://tc39.es/ecma262/multipage/indexed-collections.html#sec-array.from
94 - // Array.from(arrayLike, optionalFn, optionalThis) not added because
95 - // the Effect of `arrayLike` is polymorphic i.e.
96 - // - Effect.read if
97 - // - it does not have an @iterator property and is array-like
98 - // (i.e. has a length property)
99 - /// - it is an iterable object whose iterator does not mutate itself
100 - // - Effect.mutate if it is a self-mutative iterator (e.g. a generator
101 - // function)
91 + /*
92 + * https://tc39.es/ecma262/multipage/indexed-collections.html#sec-array.from
93 + * Array.from(arrayLike, optionalFn, optionalThis) not added because
94 + * the Effect of `arrayLike` is polymorphic i.e.
95 + * - Effect.read if
96 + * - it does not have an @iterator property and is array-like
97 + * (i.e. has a length property)
98 + * - it is an iterable object whose iterator does not mutate itself
99 + * - Effect.mutate if it is a self-mutative iterator (e.g. a generator
100 + * function)
101 + */
102 [
103 "of",
104 // Array.of(element0, ..., elementN)
@@ -231,9 +231,11 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
231 // TODO: rest of Global objects
232 ];
233
234 -// TODO(mofeiZ): We currently only store rest param effects for hooks.
235 -// now that FeatureFlag `enableTreatHooksAsFunctions` is removed we can
236 -// use positional params too (?)
234 +/*
235 + * TODO(mofeiZ): We currently only store rest param effects for hooks.
236 + * now that FeatureFlag `enableTreatHooksAsFunctions` is removed we can
237 + * use positional params too (?)
238 + */
239 const BUILTIN_HOOKS: Array<[string, BuiltInType]> = [
240 [
241 "useContext",
compiler/packages/babel-plugin-react-forget/src/HIR/HIR.ts
+79 -81
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -12,15 +12,17 @@ import { Environment } from "./Environment";
12 import { HookKind } from "./ObjectShape";
13 import { Type } from "./Types";
14
15 -// *******************************************************************************************
16 -// *******************************************************************************************
17 -// ************************************* Core Data Model *************************************
18 -// *******************************************************************************************
19 -// *******************************************************************************************
15 +/*
16 + * *******************************************************************************************
17 + * *******************************************************************************************
18 + * ************************************* Core Data Model *************************************
19 + * *******************************************************************************************
20 + * *******************************************************************************************
21 + */
22
23 // AST -> (lowering) -> HIR -> (analysis) -> Reactive Scopes -> (codegen) -> AST
24
23 -/**
25 +/*
26 * A location in a source file, intended to be used for providing diagnostic information and
27 * transforming code while preserving source information (ie to emit source maps).
28 *
@@ -29,16 +31,16 @@ import { Type } from "./Types";
31 export const GeneratedSource = Symbol();
32 export type SourceLocation = t.SourceLocation | typeof GeneratedSource;
33
32 -/**
34 +/*
35 * A React function defines a computation that takes some set of reactive inputs
36 * (props, hook arguments) and return a result (JSX, hook return value). Unlike
37 * HIR, the data model is tree-shaped:
38 *
39 * ReactFunction
38 - * ReactiveBlock
39 - * ReactiveBlockScope*
40 - * Place* (dependencies)
41 - * (ReactiveInstruction | ReactiveTerminal)*
40 + * ReactiveBlock
41 + * ReactiveBlockScope*
42 + * Place* (dependencies)
43 + * (ReactiveInstruction | ReactiveTerminal)*
44 *
45 * Where ReactiveTerminal may recursively contain zero or more ReactiveBlocks.
46 *
@@ -225,9 +227,7 @@ export type ReactiveTryTerminal = {
227 id: InstructionId;
228 };
229
228 -/**
229 - * A function lowered to HIR form, ie where its body is lowered to an HIR control-flow graph
230 - */
230 +// A function lowered to HIR form, ie where its body is lowered to an HIR control-flow graph
231 export type HIRFunction = {
232 loc: SourceLocation;
233 id: string | null;
@@ -239,7 +239,7 @@ export type HIRFunction = {
239 async: boolean;
240 };
241
242 -/**
242 +/*
243 * Each reactive scope may have its own control-flow, so the instructions form
244 * a control-flow graph. The graph comprises a set of basic blocks which reference
245 * each other via terminal statements, as well as a reference to the entry block.
@@ -247,7 +247,7 @@ export type HIRFunction = {
247 export type HIR = {
248 entry: BlockId;
249
250 - /**
250 + /*
251 * Basic blocks are stored as a map to aid certain operations that need to
252 * lookup blocks by their id. However, the order of the items in the map is
253 * reverse postorder, that is, barring cycles, predecessors appear before
@@ -256,7 +256,7 @@ export type HIR = {
256 blocks: Map<BlockId, BasicBlock>;
257 };
258
259 -/**
259 +/*
260 * Each basic block within an instruction graph contains zero or more instructions
261 * followed by a terminal node. Note that basic blocks always execute consecutively,
262 * there can be no branching within a block other than for an exception. Exceptions
@@ -274,7 +274,7 @@ export type BasicBlock = {
274 phis: Set<Phi>;
275 };
276
277 -/**
277 +/*
278 * Terminal nodes generally represent statements that affect control flow, such as
279 * for-of, if-else, return, etc.
280 */
@@ -313,10 +313,10 @@ function _staticInvariantTerminalHasInstructionId(
313 return terminal.id;
314 }
315
316 -/**
316 +/*
317 * Terminal nodes allowed for a value block
318 + * A terminal that couldn't be lowered correctly.
319 */
319 -// A terminal that couldn't be lowered correctly.
320 export type UnsupportedTerminal = {
321 kind: "unsupported";
322 id: InstructionId;
@@ -453,10 +453,12 @@ export type LabelTerminal = {
453
454 export type OptionalTerminal = {
455 kind: "optional";
456 - // Specifies whether this node was optional. If false, it means that the original
457 - // node was part of an optional chain but this specific item was non-optional.
458 - // For example, in `a?.b.c?.()`, the `.b` access is non-optional but appears within
459 - // an optional chain.
456 + /*
457 + * Specifies whether this node was optional. If false, it means that the original
458 + * node was part of an optional chain but this specific item was non-optional.
459 + * For example, in `a?.b.c?.()`, the `.b` access is non-optional but appears within
460 + * an optional chain.
461 + */
462 optional: boolean;
463 test: BlockId;
464 fallthrough: BlockId;
@@ -491,7 +493,7 @@ export type MaybeThrowTerminal = {
493 loc: SourceLocation;
494 };
495
494 -/**
496 +/*
497 * Instructions generally represent expressions but with all nesting flattened away,
498 * such that all operands to each instruction are either primitive values OR are
499 * references to a place, which may be a temporary that holds the results of a
@@ -570,26 +572,16 @@ export type ObjectMethod = {
572 };
573
574 export enum InstructionKind {
573 - /**
574 - * const declaration
575 - */
575 + // const declaration
576 Const = "Const",
577 - /**
578 - * let declaration
579 - */
577 + // let declaration
578 Let = "Let",
581 - /**
582 - * assing a new value to a let binding
583 - */
579 + // assing a new value to a let binding
580 Reassign = "Reassign",
585 - /**
586 - * catch clause binding
587 - */
581 + // catch clause binding
582 Catch = "Catch",
583
590 - /**
591 - * hoisted const declarations
592 - */
584 + // hoisted const declarations
585 HoistedConst = "HoistedConst",
586 }
587
@@ -607,7 +599,7 @@ export type Phi = {
599 type: Type;
600 };
601
610 -/**
602 +/*
603 * Forget currently does not handle MethodCall correctly in
604 * all cases. Specifically, we do not bind the receiver and method property
605 * before calling to args. Until we add a SequenceExpression to inline all
@@ -615,10 +607,10 @@ export type Phi = {
607 * with some constraints.
608 *
609 * Forget currently makes these assumptions (checked in codegen):
618 - * - {@link MethodCall.property} is a temporary produced by a PropertyLoad or ComputedLoad
619 - * on {@link MethodCall.receiver}
620 - * - {@link MethodCall.property} remains an rval (i.e. never promoted to a
621 - * named identifier). We currently rely on this for codegen.
610 + * - {@link MethodCall.property} is a temporary produced by a PropertyLoad or ComputedLoad
611 + * on {@link MethodCall.receiver}
612 + * - {@link MethodCall.property} remains an rval (i.e. never promoted to a
613 + * named identifier). We currently rely on this for codegen.
614 *
615 * Type inference does not currently guarantee that {@link MethodCall.property}
616 * is a FunctionType.
@@ -638,7 +630,7 @@ export type CallExpression = {
630 loc: SourceLocation;
631 };
632
641 -/**
633 +/*
634 * The value of a given instruction. Note that values are not recursive: complex
635 * values such as objects or arrays are always defined by instructions to define
636 * their operands (saving to a temporary), then passing those temporaries as
@@ -816,9 +808,11 @@ export type InstructionValue =
808 value: Place; // the collection
809 loc: SourceLocation;
810 }
819 - // Models a prefix update expression such as --x or ++y
820 - // This instructions increments or decrements the <lvalue>
821 - // but evaluates to the value of <value> prior to the update.
811 + /*
812 + * Models a prefix update expression such as --x or ++y
813 + * This instructions increments or decrements the <lvalue>
814 + * but evaluates to the value of <value> prior to the update.
815 + */
816 | {
817 kind: "PrefixUpdate";
818 lvalue: Place;
@@ -826,9 +820,11 @@ export type InstructionValue =
820 value: Place;
821 loc: SourceLocation;
822 }
829 - // Models a postfix update expression such as x-- or y++
830 - // This instructions increments or decrements the <lvalue>
831 - // and evaluates to the value after the update
823 + /*
824 + * Models a postfix update expression such as x-- or y++
825 + * This instructions increments or decrements the <lvalue>
826 + * and evaluates to the value after the update
827 + */
828 | {
829 kind: "PostfixUpdate";
830 lvalue: Place;
@@ -838,7 +834,7 @@ export type InstructionValue =
834 }
835 // `debugger` statement
836 | { kind: "Debugger"; loc: SourceLocation }
841 - /**
837 + /*
838 * Catch-all for statements such as type imports, nested class declarations, etc
839 * which are not directly represented, but included for completeness and to allow
840 * passing through in codegen.
@@ -868,7 +864,7 @@ export type Destructure = {
864 loc: SourceLocation;
865 };
866
871 -/**
867 +/*
868 * A place where data may be read from / written to:
869 * - a variable (identifier)
870 * - a path into an identifier
@@ -881,9 +877,7 @@ export type Place = {
877 loc: SourceLocation;
878 };
879
884 -/**
885 - * A primitive value with a specific (constant) value.
886 - */
880 +// A primitive value with a specific (constant) value.
881 export type Primitive = {
882 kind: "Primitive";
883 value: number | boolean | string | null | undefined;
@@ -915,24 +909,26 @@ export type MutableRange = {
909 end: InstructionId;
910 };
911
918 -/**
919 - * Represents a user-defined variable (has a name) or a temporary variable (no name).
920 - */
912 +// Represents a user-defined variable (has a name) or a temporary variable (no name).
913 export type Identifier = {
922 - // unique value to distinguish a variable, since name is not guaranteed to
923 - // exist or be unique
914 + /*
915 + * unique value to distinguish a variable, since name is not guaranteed to
916 + * exist or be unique
917 + */
918 id: IdentifierId;
919 // null for temporaries. name is primarily used for debugging.
920 name: string | null;
921 // The range for which this variable is mutable
922 mutableRange: MutableRange;
929 - // The ID of the reactive scope which will compute this value. Multiple
930 - // variables may have the same scope id.
923 + /*
924 + * The ID of the reactive scope which will compute this value. Multiple
925 + * variables may have the same scope id.
926 + */
927 scope: ReactiveScope | null;
928 type: Type;
929 };
930
935 -/**
931 +/*
932 * Distinguish between different kinds of values relevant to inference purposes:
933 * see the main docblock for the module for details.
934 */
@@ -944,9 +940,7 @@ export enum ValueKind {
940 Context = "context",
941 }
942
947 -/**
948 - * The effect with which a value is modified.
949 - */
943 +// The effect with which a value is modified.
944 export enum Effect {
945 // Default value: not allowed after lifetime inference
946 Unknown = "<unknown>",
@@ -956,15 +950,19 @@ export enum Effect {
950 Read = "read",
951 // This reference reads and stores the value
952 Capture = "capture",
959 - // This reference *may* write to (mutate) the value. This covers two similar cases:
960 - // - The compiler is being conservative and assuming that a value *may* be mutated
961 - // - The effect is polymorphic: mutable values may be mutated, non-mutable values
962 - // will not be mutated.
963 - // In both cases, we conservatively assume that mutable values will be mutated.
964 - // But we do not error if the value is known to be immutable.
953 + /*
954 + * This reference *may* write to (mutate) the value. This covers two similar cases:
955 + * - The compiler is being conservative and assuming that a value *may* be mutated
956 + * - The effect is polymorphic: mutable values may be mutated, non-mutable values
957 + * will not be mutated.
958 + * In both cases, we conservatively assume that mutable values will be mutated.
959 + * But we do not error if the value is known to be immutable.
960 + */
961 ConditionallyMutate = "mutate?",
966 - // This reference *does* write to (mutate) the value. It is an error (invalid input)
967 - // if an immutable value flows into a location with this effect.
962 + /*
963 + * This reference *does* write to (mutate) the value. It is an error (invalid input)
964 + * if an immutable value flows into a location with this effect.
965 + */
966 Mutate = "mutate",
967 // This reference may alias to (mutate) the value
968 Store = "store",
@@ -1020,7 +1018,7 @@ export type ReactiveScopeDependency = {
1018 path: Array<string>;
1019 };
1020
1023 -/**
1021 +/*
1022 * Simulated opaque type for BlockIds to prevent using normal numbers as block ids
1023 * accidentally.
1024 */
@@ -1037,7 +1035,7 @@ export function makeBlockId(id: number): BlockId {
1035 return id as BlockId;
1036 }
1037
1040 -/**
1038 +/*
1039 * Simulated opaque type for ScopeIds to prevent using normal numbers as scope ids
1040 * accidentally.
1041 */
@@ -1054,7 +1052,7 @@ export function makeScopeId(id: number): ScopeId {
1052 return id as ScopeId;
1053 }
1054
1057 -/**
1055 +/*
1056 * Simulated opaque type for IdentifierId to prevent using normal numbers as ids
1057 * accidentally.
1058 */
@@ -1071,7 +1069,7 @@ export function makeIdentifierId(id: number): IdentifierId {
1069 return id as IdentifierId;
1070 }
1071
1074 -/**
1072 +/*
1073 * Simulated opaque type for InstructionId to prevent using normal numbers as ids
1074 * accidentally.
1075 */
compiler/packages/babel-plugin-react-forget/src/HIR/HIRBuilder.ts
+65 -75
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -32,15 +32,15 @@ import {
32 mapTerminalSuccessors,
33 } from "./visitors";
34
35 -// *******************************************************************************************
36 -// *******************************************************************************************
37 -// ************************************* Lowering to HIR *************************************
38 -// *******************************************************************************************
39 -// *******************************************************************************************
40 -
41 -/**
42 - * A work-in-progress block that does not yet have a terminator
35 +/*
36 + * *******************************************************************************************
37 + * *******************************************************************************************
38 + * ************************************* Lowering to HIR *************************************
39 + * *******************************************************************************************
40 + * *******************************************************************************************
41 */
42 +
43 +// A work-in-progress block that does not yet have a terminator
44 export type WipBlock = {
45 id: BlockId;
46 instructions: Array<Instruction>;
@@ -77,20 +77,24 @@ export type Bindings = Map<
77 { node: t.Identifier; identifier: Identifier }
78 >;
79
80 -// Determines how instructions should be constructed in order to preserve
81 -// exception semantics
80 +/*
81 + * Determines how instructions should be constructed in order to preserve
82 + * exception semantics
83 + */
84 export type ExceptionsMode =
83 - // Mode used for code not covered by explicit exception handling, any
84 - // errors are assumed to be thrown out of the function
85 + /*
86 + * Mode used for code not covered by explicit exception handling, any
87 + * errors are assumed to be thrown out of the function
88 + */
89 | { kind: "ThrowExceptions" }
86 - // Mode used for code that *is* covered by explicit exception handling
87 - // (ie try/catch), which requires modeling the possibility of control
88 - // flow to the exception handler.
90 + /*
91 + * Mode used for code that *is* covered by explicit exception handling
92 + * (ie try/catch), which requires modeling the possibility of control
93 + * flow to the exception handler.
94 + */
95 | { kind: "CatchExceptions"; handler: BlockId };
96
91 -/**
92 - * Helper class for constructing a CFG
93 - */
97 +// Helper class for constructing a CFG
98 export default class HIRBuilder {
99 #completed: Map<BlockId, BasicBlock> = new Map();
100 #current: WipBlock;
@@ -137,9 +141,7 @@ export default class HIRBuilder {
141 return this.#current.kind;
142 }
143
140 - /**
141 - * Push a statement or expression onto the current block
142 - */
144 + // Push a statement or expression onto the current block
145 push(instruction: Instruction): void {
146 this.#current.instructions.push(instruction);
147 const exceptionHandler = this.#exceptionHandlerStack.at(-1);
@@ -216,7 +218,7 @@ export default class HIRBuilder {
218 return binding;
219 }
220
219 - /**
221 + /*
222 * Maps an Identifier (or JSX identifier) Babel node to an internal `Identifier`
223 * which represents the variable being referenced, according to the JS scoping rules.
224 *
@@ -230,11 +232,11 @@ export default class HIRBuilder {
232 *
233 * ```javascript
234 * function foo() {
233 - * const x = 0;
234 - * {
235 - * const x = 1;
236 - * }
237 - * return x;
235 + * const x = 0;
236 + * {
237 + * const x = 1;
238 + * }
239 + * return x;
240 * }
241 * ```
242 *
@@ -298,20 +300,12 @@ export default class HIRBuilder {
300 }
301 }
302
301 - /**
302 - * Construct a final CFG from this context
303 - */
303 + // Construct a final CFG from this context
304 build(): HIR {
305 let ir: HIR = {
306 blocks: this.#completed,
307 entry: this.#entry,
308 };
309 - // logHIR("Build (pre-shrink)", ir);
310 - // // First reduce indirections
311 - // shrink(ir);
312 - // logHIR("Build (shrunk)", ir);
313 -
314 - // then convert to reverse postorder
309 reversePostorderBlocks(ir);
310 removeUnreachableForUpdates(ir);
311 removeUnreachableFallthroughs(ir);
@@ -323,9 +317,7 @@ export default class HIRBuilder {
317 return ir;
318 }
319
326 - /**
327 - * Terminate the current block w the given terminal, and start a new block
328 - */
320 + // Terminate the current block w the given terminal, and start a new block
321 terminate(terminal: Terminal, nextBlockKind: BlockKind | null): void {
322 const { id: blockId, kind, instructions } = this.#current;
323 this.#completed.set(blockId, {
@@ -342,7 +334,7 @@ export default class HIRBuilder {
334 }
335 }
336
345 - /**
337 + /*
338 * Terminate the current block w the given terminal, and set the previously
339 * reserved block as the new current block
340 */
@@ -359,7 +351,7 @@ export default class HIRBuilder {
351 this.#current = continuation;
352 }
353
362 - /**
354 + /*
355 * Reserve a block so that it can be referenced prior to construction.
356 * Make this the current block with `terminateWithContinuation()` or
357 * call `complete()` to save it without setting it as the current block.
@@ -368,9 +360,7 @@ export default class HIRBuilder {
360 return newBlock(makeBlockId(this.#env.nextBlockId), kind);
361 }
362
371 - /**
372 - * Save a previously reserved block as completed
373 - */
363 + // Save a previously reserved block as completed
364 complete(block: WipBlock, terminal: Terminal): void {
365 const { id: blockId, kind, instructions } = block;
366 this.#completed.set(blockId, {
@@ -383,7 +373,7 @@ export default class HIRBuilder {
373 });
374 }
375
386 - /**
376 + /*
377 * Sets the given wip block as the current block, executes the provided callback to populate the block
378 * up to its terminal, and then resets the previous actively block.
379 */
@@ -403,7 +393,7 @@ export default class HIRBuilder {
393 this.#current = current;
394 }
395
406 - /**
396 + /*
397 * Create a new block and execute the provided callback with the new block
398 * set as the current, resetting to the previously active block upon exit.
399 * The lambda must return a terminal node, which is used to terminate the
@@ -463,19 +453,15 @@ export default class HIRBuilder {
453 return value;
454 }
455
466 - /**
456 + /*
457 * Executes the provided lambda inside a scope in which the provided loop
458 * information is cached for lookup with `lookupBreak()` and `lookupContinue()`
459 */
460 loop<T>(
461 label: string | null,
472 - /**
473 - * block of the loop body. "continue" jumps here.
474 - */
462 + // block of the loop body. "continue" jumps here.
463 continueBlock: BlockId,
476 - /**
477 - * block following the loop. "break" jumps here.
478 - */
464 + // block following the loop. "break" jumps here.
465 breakBlock: BlockId,
466 fn: () => T
467 ): T {
@@ -503,7 +489,7 @@ export default class HIRBuilder {
489 return value;
490 }
491
506 - /**
492 + /*
493 * Lookup the block target for a break statement, based on loops and switch statements
494 * in scope. Throws if there is no available location to break.
495 */
@@ -522,7 +508,7 @@ export default class HIRBuilder {
508 });
509 }
510
525 - /**
511 + /*
512 * Lookup the block target for a continue statement, based on loops
513 * in scope. Throws if there is no available location to continue, or if the given
514 * label does not correspond to a loop (this should also be validated at parse time).
@@ -552,12 +538,10 @@ export default class HIRBuilder {
538 }
539 }
540
555 -/**
556 - * Helper to shrink a CFG eliminate jump-only blocks.
557 - */
541 +// Helper to shrink a CFG eliminate jump-only blocks.
542 function _shrink(func: HIR): void {
543 const gotos = new Map();
560 - /**
544 + /*
545 * Given a target block for some terminator, resolves the ideal block that should be
546 * targeted instead. This transitively resolves any blocks that are simple indirections
547 * (empty blocks that terminate in a goto).
@@ -644,9 +628,11 @@ export function removeDeadDoWhileStatements(func: HIR): void {
628 visited.add(block.id);
629 }
630
647 - // If the test condition of a DoWhile is unreachable, the terminal is effectively deadcode and we
648 - // can just inline the loop body. We replace the terminal with a goto to the loop block and
649 - // MergeConsecutiveBlocks figures out how to merge as appropriate.
631 + /*
632 + * If the test condition of a DoWhile is unreachable, the terminal is effectively deadcode and we
633 + * can just inline the loop body. We replace the terminal with a goto to the loop block and
634 + * MergeConsecutiveBlocks figures out how to merge as appropriate.
635 + */
636 for (const [_, block] of func.blocks) {
637 if (block.terminal.kind === "do-while") {
638 if (!visited.has(block.terminal.test)) {
@@ -662,7 +648,7 @@ export function removeDeadDoWhileStatements(func: HIR): void {
648 }
649 }
650
665 -/**
651 +/*
652 * Converts the graph to reverse-postorder, with predecessor blocks appearing
653 * before successors except in the case of back links (ie loops).
654 */
@@ -677,18 +663,18 @@ export function reversePostorderBlocks(func: HIR): void {
663 const block = func.blocks.get(blockId)!;
664 const { terminal } = block;
665
680 - /**
666 + /*
667 * Note that we visit successors in reverse order. This ensures that when we
668 * reverse the list at the end, that "sibling" edges appear in-order. For example,
669 * ```
670 * // bb0
671 * let x;
672 * if (c) {
687 - * // bb1
688 - * x = 1;
673 + * // bb1
674 + * x = 1;
675 * } else {
690 - * // b2
691 - * x = 2;
676 + * // b2
677 + * x = 2;
678 * }
679 * // bb3
680 * x;
@@ -709,8 +695,10 @@ export function reversePostorderBlocks(func: HIR): void {
695 break;
696 }
697 case "if": {
712 - // can ignore fallthrough, if its reachable it will be reached through
713 - // consequent/alternate
698 + /*
699 + * can ignore fallthrough, if its reachable it will be reached through
700 + * consequent/alternate
701 + */
702 const { consequent, alternate } = terminal;
703 visit(alternate);
704 visit(consequent);
@@ -723,8 +711,10 @@ export function reversePostorderBlocks(func: HIR): void {
711 break;
712 }
713 case "switch": {
726 - // can ignore fallthrough, if its reachable it will be reached through
727 - // a case
714 + /*
715 + * can ignore fallthrough, if its reachable it will be reached through
716 + * a case
717 + */
718 const { cases } = terminal;
719 for (const case_ of [...cases].reverse()) {
720 visit(case_.block);
@@ -834,7 +824,7 @@ export function markPredecessors(func: HIR): void {
824 visit(func.entry, null);
825 }
826
837 -/**
827 +/*
828 * If the given block is a simple indirection — empty terminated with a goto(break) —
829 * returns the block being pointed to. Otherwise returns null.
830 */
@@ -846,7 +836,7 @@ function getTargetIfIndirection(block: BasicBlock): number | null {
836 : null;
837 }
838
849 -/**
839 +/*
840 * Finds try terminals where the handler is unreachable, and converts the try
841 * to a goto(terminal.fallthrough)
842 */
compiler/packages/babel-plugin-react-forget/src/HIR/MergeConsecutiveBlocks.ts
+12 -10
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -16,7 +16,7 @@ import {
16 import { markPredecessors, removeUnreachableFallthroughs } from "./HIRBuilder";
17 import { mapOptionalFallthroughs } from "./visitors";
18
19 -/**
19 +/*
20 * Merges sequences of blocks that will always execute consecutively —
21 * ie where the predecessor always transfers control to the successor
22 * (ie ends in a goto) and where the predecessor is the only predecessor
@@ -40,8 +40,10 @@ export function mergeConsecutiveBlocks(fn: HIRFunction): void {
40 }
41 }
42
43 - // Can only merge blocks with a single predecessor, can't merge
44 - // value blocks
43 + /*
44 + * Can only merge blocks with a single predecessor, can't merge
45 + * value blocks
46 + */
47 if (block.kind !== "block" || block.preds.size !== 1) {
48 continue;
49 }
@@ -55,8 +57,10 @@ export function mergeConsecutiveBlocks(fn: HIRFunction): void {
57 suggestions: null,
58 });
59 if (predecessor.terminal.kind !== "goto" || predecessor.kind !== "block") {
58 - // The predecessor is not guaranteed to transfer control to this block,
59 - // they aren't consecutive.
60 + /*
61 + * The predecessor is not guaranteed to transfer control to this block,
62 + * they aren't consecutive.
63 + */
64 continue;
65 }
66
@@ -109,15 +113,13 @@ export function mergeConsecutiveBlocks(fn: HIRFunction): void {
113 class MergedBlocks {
114 #map: Map<BlockId, BlockId> = new Map();
115
112 - /**
113 - * Record that @param block was merged into @param into.
114 - */
116 + // Record that @param block was merged into @param into.
117 merge(block: BlockId, into: BlockId): void {
118 const target = this.get(into);
119 this.#map.set(block, target);
120 }
121
120 - /**
122 + /*
123 * Get the id of the block that @param block has been merged into.
124 * This is transitive, in the case that eg @param block was merged
125 * into a block which later merged into another block.
compiler/packages/babel-plugin-react-forget/src/HIR/ObjectShape.ts
+30 -24
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -15,7 +15,7 @@ import {
15 PrimitiveType,
16 } from "./Types";
17
18 -/**
18 +/*
19 * This file exports types and defaults for JavaScript object shapes. These are
20 * stored and used by a Forget `Environment`. See comments in `Types.ts`,
21 * `Globals.ts`, and `Environment.ts` for more details.
@@ -26,13 +26,15 @@ const PRIMITIVE_TYPE: PrimitiveType = {
26 };
27
28 let nextAnonId = 0;
29 -// We currently use strings for anonymous ShapeIds since they are easily
30 -// debuggable, even though `Symbol()` might be more performant
29 +/*
30 + * We currently use strings for anonymous ShapeIds since they are easily
31 + * debuggable, even though `Symbol()` might be more performant
32 + */
33 function createAnonId(): string {
34 return `<generated_${nextAnonId++}>`;
35 }
36
35 -/**
37 +/*
38 * Add a non-hook function to an existing ShapeRegistry.
39 *
40 * @returns a {@link FunctionType} representing the added function.
@@ -55,7 +57,7 @@ export function addFunction(
57 };
58 }
59
58 -/**
60 +/*
61 * Add a hook to an existing ShapeRegistry.
62 *
63 * @returns a {@link FunctionType} representing the added hook function.
@@ -75,7 +77,7 @@ export function addHook(
77 };
78 }
79
78 -/**
80 +/*
81 * Add an object to an existing ShapeRegistry.
82 *
83 * @returns an {@link ObjectType} representing the added object.
@@ -124,13 +126,13 @@ export type HookKind =
126 | "useCallback"
127 | "Custom";
128
127 -/**
129 +/*
130 * Call signature of a function, used for type and effect inference.
131 *
132 * Note: Param type is not recorded since it currently does not affect inference.
133 * Specifically, we currently do not:
132 - * - infer types based on their usage in argument position
133 - * - handle inference for overloaded / generic functions
134 + * - infer types based on their usage in argument position
135 + * - handle inference for overloaded / generic functions
136 */
137 export type FunctionSignature = {
138 positionalParams: Array<Effect>;
@@ -139,7 +141,7 @@ export type FunctionSignature = {
141 returnValueKind: ValueKind;
142 calleeEffect: Effect;
143 hookKind: HookKind | null;
142 - /**
144 + /*
145 * Whether any of the parameters may be aliased by each other or the return
146 * value. Defaults to false (parameters may alias). When true, the compiler
147 * may choose not to memoize arguments if they do not otherwise escape.
@@ -147,7 +149,7 @@ export type FunctionSignature = {
149 noAlias?: boolean;
150 };
151
150 -/**
152 +/*
153 * Shape of an {@link FunctionType} if {@link ObjectShape.functionType} is present,
154 * or {@link ObjectType} otherwise.
155 *
@@ -159,7 +161,7 @@ export type ObjectShape = {
161 functionType: FunctionSignature | null;
162 };
163
162 -/**
164 +/*
165 * Every valid ShapeRegistry must contain ObjectShape definitions for
166 * {@link BuiltInArrayId} and {@link BuiltInObjectId}, since these are the
167 * the inferred types for [] and {}.
@@ -173,9 +175,7 @@ export const BuiltInUseRefId = "BuiltInUseRefId";
175 export const BuiltInRefValueId = "BuiltInRefValue";
176 export const BuiltInMixedReadonlyId = "BuiltInMixedReadonly";
177
176 -/**
177 - * ShapeRegistry with default definitions for built-ins.
178 - */
178 +// ShapeRegistry with default definitions for built-ins.
179 export const BUILTIN_SHAPES: ShapeRegistry = new Map();
180
181 /* Built-in array shape */
@@ -220,9 +220,11 @@ addObject(BUILTIN_SHAPES, BuiltInArrayId, [
220 positionalParams: [],
221 restParam: Effect.ConditionallyMutate,
222 returnType: { kind: "Object", shapeId: BuiltInArrayId },
223 - // callee is ConditionallyMutate because items of the array
224 - // flow into the lambda and may be mutated there, even though
225 - // the array object itself is not modified
223 + /*
224 + * callee is ConditionallyMutate because items of the array
225 + * flow into the lambda and may be mutated there, even though
226 + * the array object itself is not modified
227 + */
228 calleeEffect: Effect.ConditionallyMutate,
229 returnValueKind: ValueKind.Mutable,
230 noAlias: true,
@@ -234,9 +236,11 @@ addObject(BUILTIN_SHAPES, BuiltInArrayId, [
236 positionalParams: [],
237 restParam: Effect.ConditionallyMutate,
238 returnType: { kind: "Object", shapeId: BuiltInArrayId },
237 - // callee is ConditionallyMutate because items of the array
238 - // flow into the lambda and may be mutated there, even though
239 - // the array object itself is not modified
239 + /*
240 + * callee is ConditionallyMutate because items of the array
241 + * flow into the lambda and may be mutated there, even though
242 + * the array object itself is not modified
243 + */
244 calleeEffect: Effect.ConditionallyMutate,
245 returnValueKind: ValueKind.Mutable,
246 noAlias: true,
@@ -267,8 +271,10 @@ addObject(BUILTIN_SHAPES, BuiltInObjectId, [
271 returnValueKind: ValueKind.Immutable,
272 }),
273 ],
270 - // TODO:
271 - // hasOwnProperty, isPrototypeOf, propertyIsEnumerable, toLocaleString, valueOf
274 + /*
275 + * TODO:
276 + * hasOwnProperty, isPrototypeOf, propertyIsEnumerable, toLocaleString, valueOf
277 + */
278 ]);
279
280 addObject(BUILTIN_SHAPES, BuiltInUseStateId, [
compiler/packages/babel-plugin-react-forget/src/HIR/PrintHIR.ts
+1 -1
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
compiler/packages/babel-plugin-react-forget/src/HIR/Types.ts
+4 -4
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -18,7 +18,7 @@ export type Type =
18 | ObjectMethod;
19 export type PrimitiveType = { kind: "Primitive" };
20
21 -/**
21 +/*
22 * An {@link FunctionType} or {@link ObjectType} (also a JS object) may be associated with an
23 * inferred "object shape", i.e. a known property (key -> Type) map. This is
24 * subtly different from JS language semantics - `shape` represents both
@@ -27,7 +27,7 @@ export type PrimitiveType = { kind: "Primitive" };
27 * {@link ObjectShape.functionType} is always present on the shape of a {@link FunctionType},
28 * and it represents the call signature of the function. Note that Forget thinks of a
29 * {@link FunctionType} as any "callable object" (not to be confused with objects that
30 - * extend the global `Function`.)
30 + * extend the global `Function`.)
31 *
32 * If `shapeId` is present, it is a key into the ShapeRegistry used to infer this
33 * FunctionType or ObjectType instance (i.e. from an Environment).
@@ -65,7 +65,7 @@ export type ObjectMethod = {
65 kind: "ObjectMethod";
66 };
67
68 -/**
68 +/*
69 * Simulated opaque type for TypeId to prevent using normal numbers as ids
70 * accidentally.
71 */
compiler/packages/babel-plugin-react-forget/src/HIR/index.ts
+1 -1
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
compiler/packages/babel-plugin-react-forget/src/HIR/visitors.ts
+10 -10
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -553,9 +553,7 @@ export function mapPatternOperands(
553 }
554 }
555
556 -/**
557 - * Maps a terminal node's block assignments using the provided function.
558 - */
556 +// Maps a terminal node's block assignments using the provided function.
557 export function mapTerminalSuccessors(
558 terminal: Terminal,
559 fn: (block: BlockId) => BlockId
@@ -793,7 +791,7 @@ export function mapTerminalSuccessors(
791 }
792 }
793
796 -/**
794 +/*
795 * Helper to get a terminal's fallthrough. The main reason to extract this as a helper
796 * function is to ensure that we use an exhaustive switch to ensure that we add new terminal
797 * variants as appropriate.
@@ -845,10 +843,12 @@ export function mapOptionalFallthroughs(
843 case "unsupported": {
844 return;
845 }
848 - // NOTE: TypeScript has a bug where it does not correctly model properties whose values are
849 - // non-null in some cases and nullable in other cases, if those cases are joined together.
850 - // Thus we use one block per case here to ensure that any changes to the types will cause
851 - // a compiler error.
846 + /*
847 + * NOTE: TypeScript has a bug where it does not correctly model properties whose values are
848 + * non-null in some cases and nullable in other cases, if those cases are joined together.
849 + * Thus we use one block per case here to ensure that any changes to the types will cause
850 + * a compiler error.
851 + */
852 case "do-while": {
853 const _: BlockId = terminal.fallthrough;
854 break;
@@ -920,7 +920,7 @@ export function mapOptionalFallthroughs(
920 }
921 }
922
923 -/**
923 +/*
924 * Iterates over the successor block ids of the provided terminal. The function is called
925 * specifically for the successors that define the standard control flow, and not
926 * pseduo-successors such as fallthroughs.
compiler/packages/babel-plugin-react-forget/src/Inference/AnalyseFunctions.ts
+29 -23
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -25,9 +25,7 @@ import { inferMutableContextVariables } from "./InferMutableContextVariables";
25 import { inferMutableRanges } from "./InferMutableRanges";
26 import inferReferenceEffects from "./InferReferenceEffects";
27
28 -/**
29 - * Helper class to track indirections such as LoadLocal and PropertyLoad.
30 - */
28 +// Helper class to track indirections such as LoadLocal and PropertyLoad.
29 export class IdentifierState {
30 properties: Map<Identifier, ReactiveScopeDependency> = new Map();
31
@@ -85,8 +83,10 @@ export default function analyseFunctions(func: HIRFunction): void {
83 break;
84 }
85 case "ComputedLoad": {
88 - // The path is set to an empty string as the path doesn't really
89 - // matter for a computed load.
86 + /*
87 + * The path is set to an empty string as the path doesn't really
88 + * matter for a computed load.
89 + */
90 state.declareProperty(instr.lvalue, instr.value.object, "");
91 break;
92 }
@@ -144,11 +144,13 @@ function infer(
144 isRefValueType(dep.identifier) ||
145 isSetStateType(dep.identifier)
146 ) {
147 - // TODO: this is a hack to ensure we treat functions which reference refs
148 - // as having a capture and therefore being considered mutable. this ensures
149 - // the function gets a mutable range which accounts for anywhere that it
150 - // could be called, and allows us to help ensure it isn't called during
151 - // render
147 + /*
148 + * TODO: this is a hack to ensure we treat functions which reference refs
149 + * as having a capture and therefore being considered mutable. this ensures
150 + * the function gets a mutable range which accounts for anywhere that it
151 + * could be called, and allows us to help ensure it isn't called during
152 + * render
153 + */
154 dep.effect = Effect.Capture;
155 } else if (name !== null) {
156 const effect = mutations.get(name);
@@ -158,12 +160,14 @@ function infer(
160 }
161 }
162
161 - // This could potentially add duplicate deps to mutatedDeps in the case of
162 - // mutating a context ref in the child function and in this parent function.
163 - // It might be useful to dedupe this.
164 - //
165 - // In practice this never really matters because the Component function has no
166 - // context refs, so it will never have duplicate deps.
163 + /*
164 + * This could potentially add duplicate deps to mutatedDeps in the case of
165 + * mutating a context ref in the child function and in this parent function.
166 + * It might be useful to dedupe this.
167 + *
168 + * In practice this never really matters because the Component function has no
169 + * context refs, so it will never have duplicate deps.
170 + */
171 for (const place of context) {
172 CompilerError.invariant(place.identifier.name !== null, {
173 reason: "context refs should always have a name",
@@ -181,11 +185,13 @@ function infer(
185 }
186
187 function isMutatedOrReassigned(id: Identifier): boolean {
184 - // This check checks for mutation and reassingnment, so the usual check for
185 - // mutation (ie, `mutableRange.end - mutableRange.start > 1`) isn't quite
186 - // enough.
187 - //
188 - // We need to track re-assignments in context refs as we need to reflect the
189 - // re-assignment back to the captured refs.
188 + /*
189 + * This check checks for mutation and reassingnment, so the usual check for
190 + * mutation (ie, `mutableRange.end - mutableRange.start > 1`) isn't quite
191 + * enough.
192 + *
193 + * We need to track re-assignments in context refs as we need to reflect the
194 + * re-assignment back to the captured refs.
195 + */
196 return id.mutableRange.end > id.mutableRange.start;
197 }
compiler/packages/babel-plugin-react-forget/src/Inference/DropManualMemoization.ts
+27 -21
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -8,7 +8,7 @@
8 import { Effect, HIRFunction, IdentifierId } from "../HIR";
9 import { HookKind } from "../HIR/ObjectShape";
10
11 -/**
11 +/*
12 * Removes manual memoization using the `useMemo` and `useCallback` APIs. This pass is designed
13 * to compose with InlineImmediatelyInvokedFunctionExpressions, and needs to run prior to entering
14 * SSA form (alternatively we could refactor and re-EnterSSA after inlining). Therefore it cannot
@@ -55,22 +55,26 @@ export function dropManualMemoization(func: HIRFunction): void {
55 if (hookKind === "useMemo") {
56 const [fn] = instr.value.args;
57
58 - // TODO(gsn): Consider inlining the function passed to useMemo,
59 - // rather than just calling it directly.
60 - //
61 - // Replace the hook callee with the fn arg.
62 - //
63 - // before:
64 - // foo = Call useMemo$2($9, $10)
65 - //
66 - // after:
67 - // foo = Call $9()
58 + /*
59 + * TODO(gsn): Consider inlining the function passed to useMemo,
60 + * rather than just calling it directly.
61 + *
62 + * Replace the hook callee with the fn arg.
63 + *
64 + * before:
65 + * foo = Call useMemo$2($9, $10)
66 + *
67 + * after:
68 + * foo = Call $9()
69 + */
70 if (fn.kind === "Identifier") {
71 instr.value = {
72 kind: "CallExpression",
73 callee: fn,
72 - // Drop the args, including the deps array which DCE will remove
73 - // later.
74 + /*
75 + * Drop the args, including the deps array which DCE will remove
76 + * later.
77 + */
78 args: [],
79 loc: instr.value.loc,
80 };
@@ -78,13 +82,15 @@ export function dropManualMemoization(func: HIRFunction): void {
82 } else if (hookKind === "useCallback") {
83 const [fn] = instr.value.args;
84
81 - // Instead of a Call, just alias the callback directly.
82 - //
83 - // before:
84 - // foo = Call useCallback$8($19)
85 - //
86 - // after:
87 - // foo = $19
85 + /*
86 + * Instead of a Call, just alias the callback directly.
87 + *
88 + * before:
89 + * foo = Call useCallback$8($19)
90 + *
91 + * after:
92 + * foo = $19
93 + */
94 if (fn.kind === "Identifier") {
95 instr.value = {
96 kind: "LoadLocal",
compiler/packages/babel-plugin-react-forget/src/Inference/InferAlias.ts
+1 -1
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
compiler/packages/babel-plugin-react-forget/src/Inference/InferAliasForPhis.ts
+1 -1
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
compiler/packages/babel-plugin-react-forget/src/Inference/InferAliasForStores.ts
+5 -3
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -27,8 +27,10 @@ export function inferAliasForStores(
27 const { value, lvalue } = instr;
28 const isStore =
29 lvalue.effect === Effect.Store ||
30 - // Some typed functions annotate callees or arguments
31 - // as Effect.Store.
30 + /*
31 + * Some typed functions annotate callees or arguments
32 + * as Effect.Store.
33 + */
34 ![...eachInstructionValueOperand(value)].every(
35 (operand) => operand.effect !== Effect.Store
36 );
compiler/packages/babel-plugin-react-forget/src/Inference/InferMutableContextVariables.ts
+11 -9
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -12,7 +12,7 @@ import {
12 } from "../HIR/visitors";
13 import { IdentifierState } from "./AnalyseFunctions";
14
15 -/**
15 +/*
16 * This pass infers which of the given function's context (free) variables
17 * are definitively mutated by the function. This analysis is *partial*,
18 * and only annotates provable mutations, and may miss mutations via indirections.
@@ -28,9 +28,9 @@ import { IdentifierState } from "./AnalyseFunctions";
28 * ```
29 * const [x, setX] = useState(null); // x is frozen
30 * const fn = () => { // context=[x]
31 - * const z = {}; // z is mutable
32 - * foo(z, x); // potentially mutate z and x
33 - * z.a = true; // definitively mutate z
31 + * const z = {}; // z is mutable
32 + * foo(z, x); // potentially mutate z and x
33 + * z.a = true; // definitively mutate z
34 * }
35 * fn();
36 * ```
@@ -49,8 +49,8 @@ import { IdentifierState } from "./AnalyseFunctions";
49 * ```
50 * const [x, setX] = useState(null); // x is frozen
51 * const fn = () => { // context=[x]
52 - * const z = x;
53 - * z.a = true; // ERROR: mutates x
52 + * const z = x;
53 + * z.a = true; // ERROR: mutates x
54 * }
55 * fn();
56 * ```
@@ -70,8 +70,10 @@ export function inferMutableContextVariables(fn: HIRFunction): void {
70 break;
71 }
72 case "ComputedLoad": {
73 - // The path is set to an empty string as the path doesn't really
74 - // matter for a computed load.
73 + /*
74 + * The path is set to an empty string as the path doesn't really
75 + * matter for a computed load.
76 + */
77 state.declareProperty(instr.lvalue, instr.value.object, "");
78 break;
79 }
compiler/packages/babel-plugin-react-forget/src/Inference/InferMutableLifetimes.ts
+16 -12
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -21,7 +21,7 @@ import {
21 } from "../HIR/visitors";
22 import { assertExhaustive } from "../Utils/utils";
23
24 -/**
24 +/*
25 * For each usage of a value in the given function, determines if the usage
26 * may be succeeded by a mutable usage of that same value and if so updates
27 * the usage to be mutable.
@@ -30,7 +30,7 @@ import { assertExhaustive } from "../Utils/utils";
30 * each reference are as follows:
31 * - freeze: the value is frozen at this point
32 * - readonly: the value is not modified at this point *or any subsequent
33 - * point*
33 + * point*
34 * - mutable: the value is modified at this point *or some subsequent point*.
35 *
36 * Note that this refines the capabilities inferered by InferReferenceCapability,
@@ -40,10 +40,10 @@ import { assertExhaustive } from "../Utils/utils";
40 *
41 * TODO:
42 * 1. Forward data-flow analysis to determine aliasing. Unlike InferReferenceCapability
43 - * which only tracks aliasing of top-level variables (`y = x`), this analysis needs
44 - * to know if a value is aliased anywhere (`y.x = x`). The forward data flow tracks
45 - * all possible locations which may have aliased a value. The concrete result is
46 - * a mapping of each Place to the set of possibly-mutable values it may alias.
43 + * which only tracks aliasing of top-level variables (`y = x`), this analysis needs
44 + * to know if a value is aliased anywhere (`y.x = x`). The forward data flow tracks
45 + * all possible locations which may have aliased a value. The concrete result is
46 + * a mapping of each Place to the set of possibly-mutable values it may alias.
47 *
48 * ```
49 * const x = []; // {x: v0; v0: mutable []}
@@ -55,7 +55,7 @@ import { assertExhaustive } from "../Utils/utils";
55 *
56 * DONE:
57 * 2. Forward data-flow analysis to compute mutability liveness. Walk forwards over
58 - * the CFG and track which values are mutated in a successor.
58 + * the CFG and track which values are mutated in a successor.
59 *
60 * ```
61 * mutate(y); // mutable y => v0, v1 mutated
@@ -124,12 +124,16 @@ export function inferMutableLifetimes(
124 for (const operand of eachInstructionLValue(instr)) {
125 const lvalueId = operand.identifier;
126
127 - // lvalue start being mutable when they're initially assigned a
128 - // value.
127 + /*
128 + * lvalue start being mutable when they're initially assigned a
129 + * value.
130 + */
131 lvalueId.mutableRange.start = instr.id;
132
131 - // Let's be optimistic and assume this lvalue is not mutable by
132 - // default.
133 + /*
134 + * Let's be optimistic and assume this lvalue is not mutable by
135 + * default.
136 + */
137 lvalueId.mutableRange.end = makeInstructionId(instr.id + 1);
138 }
139 for (const operand of eachInstructionOperand(instr)) {
compiler/packages/babel-plugin-react-forget/src/Inference/InferMutableRanges.ts
+9 -5
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -19,12 +19,16 @@ export function inferMutableRanges(ir: HIRFunction): void {
19
20 // Calculate aliases
21 const aliases = inferAliases(ir);
22 - // Calculate aliases for try/catch, where any value created
23 - // in the try block could be aliased to the catch param
22 + /*
23 + * Calculate aliases for try/catch, where any value created
24 + * in the try block could be aliased to the catch param
25 + */
26 inferTryCatchAliases(ir, aliases);
27
26 - // Eagerly canonicalize so that if nothing changes we can bail out
27 - // after a single iteration
28 + /*
29 + * Eagerly canonicalize so that if nothing changes we can bail out
30 + * after a single iteration
31 + */
32 let prevAliases: Map<Identifier, Identifier> = aliases.canonicalize();
33 while (true) {
34 // Infer mutable ranges for aliases that are not fields
compiler/packages/babel-plugin-react-forget/src/Inference/InferMutableRangesForAlias.ts
+9 -5
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -14,8 +14,10 @@ export function inferMutableRangesForAlias(
14 ): void {
15 const aliasSets = aliases.buildSets();
16 for (const aliasSet of aliasSets) {
17 - // Update mutableRange.end only if the identifiers have actually been
18 - // mutated.
17 + /*
18 + * Update mutableRange.end only if the identifiers have actually been
19 + * mutated.
20 + */
21 const mutatingIdentifiers = [...aliasSet].filter(
22 (id) => id.mutableRange.end - id.mutableRange.start > 1
23 );
@@ -29,8 +31,10 @@ export function inferMutableRangesForAlias(
31 }
32 }
33
32 - // Update mutableRange.end for all aliases in this set ending before the
33 - // last mutation.
34 + /*
35 + * Update mutableRange.end for all aliases in this set ending before the
36 + * last mutation.
37 + */
38 for (const alias of aliasSet) {
39 if (alias.mutableRange.end < lastMutatingInstructionId) {
40 alias.mutableRange.end = lastMutatingInstructionId as InstructionId;
compiler/packages/babel-plugin-react-forget/src/Inference/InferReactivePlaces.ts
+20 -16
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -26,7 +26,7 @@ import { hasBackEdge } from "../Optimization/DeadCodeElimination";
26 import { isMutable } from "../ReactiveScopes/InferReactiveScopeVariables";
27 import { assertExhaustive } from "../Utils/utils";
28
29 -/**
29 +/*
30 * Infers which `Place`s are reactive, ie may *semantically* change
31 * over the course of the component/hook's lifetime. Places are reactive
32 * if they derive from source source of reactivity, which includes the
@@ -49,8 +49,8 @@ import { assertExhaustive } from "../Utils/utils";
49 * Ex:
50 * ```
51 * function Component(props) {
52 - * const x = {}; // not yet reactive
53 - * x.y = props.y;
52 + * const x = {}; // not yet reactive
53 + * x.y = props.y;
54 * }
55 * ```
56 *
@@ -65,13 +65,13 @@ import { assertExhaustive } from "../Utils/utils";
65 *
66 * ```
67 * function Component(props) {
68 - * let x;
69 - * if (props.cond) {
70 - * x = 1;
71 - * } else {
72 - * x = 2;
73 - * }
74 - * return x;
68 + * let x;
69 + * if (props.cond) {
70 + * x = 1;
71 + * } else {
72 + * x = 2;
73 + * }
74 + * return x;
75 * }
76 * ```
77 *
@@ -164,15 +164,19 @@ export function inferReactivePlaces(fn: HIRFunction): void {
164 for (const instruction of block.instructions) {
165 const { value } = instruction;
166 let hasReactiveInput = false;
167 - // NOTE: we want to mark all operands as reactive or not, so we
168 - // avoid short-circuting here
167 + /*
168 + * NOTE: we want to mark all operands as reactive or not, so we
169 + * avoid short-circuting here
170 + */
171 for (const operand of eachInstructionValueOperand(value)) {
172 const reactive = reactiveIdentifiers.isReactive(operand);
173 hasReactiveInput ||= reactive;
174 }
175
174 - // Hooks may always return a reactive variable, even if their inputs are
175 - // non-reactive, because they can access state or context.
176 + /*
177 + * Hooks may always return a reactive variable, even if their inputs are
178 + * non-reactive, because they can access state or context.
179 + */
180 if (
181 value.kind === "CallExpression" &&
182 getHookKind(fn.env, value.callee.identifier) != null
@@ -260,7 +264,7 @@ export function inferReactivePlaces(fn: HIRFunction): void {
264 } while (reactiveIdentifiers.snapshot() && hasLoop);
265 }
266
263 -/**
267 +/*
268 * Computes the post-dominator frontier of @param block. These are immediate successors of nodes that
269 * post-dominate @param targetId and from which execution may not reach @param block. Intuitively, these
270 * are the earliest blocks from which execution branches such that it may or may not reach the target block.
compiler/packages/babel-plugin-react-forget/src/Inference/InferReferenceEffects.ts
+127 -107
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -46,21 +46,21 @@ const UndefinedValue: InstructionValue = {
46 value: undefined,
47 };
48
49 -/**
49 +/*
50 * For every usage of a value in the given function, infers the effect or action
51 * taken at that reference. Each reference is inferred as exactly one of:
52 * - freeze: this usage freezes the value, ie converts it to frozen. This is only inferred
53 - * when the value *may* not already be frozen.
53 + * when the value *may* not already be frozen.
54 * - frozen: the value is known to already be "owned" by React and is therefore already
55 - * frozen (permanently and transitively immutable).
55 + * frozen (permanently and transitively immutable).
56 * - immutable: the value is not owned by React, but is known to be an immutable value
57 - * that therefore cannot ever change.
57 + * that therefore cannot ever change.
58 * - readonly: the value is not frozen or immutable, but this usage of the value does
59 - * not modify it. the value may be mutated by a subsequent reference. Examples include
60 - * referencing the operands of a binary expression, or referencing the items/properties
61 - * of an array or object literal.
59 + * not modify it. the value may be mutated by a subsequent reference. Examples include
60 + * referencing the operands of a binary expression, or referencing the items/properties
61 + * of an array or object literal.
62 * - mutable: the value is not frozen or immutable, and this usage *may* modify it.
63 - * Examples include passing a value to as a function argument or assigning into an object.
63 + * Examples include passing a value to as a function argument or assigning into an object.
64 *
65 * Note that the inference follows variable assignment, so assigning a frozen value
66 * to a different value will infer usages of the other variable as frozen as well.
@@ -69,10 +69,10 @@ const UndefinedValue: InstructionValue = {
69 * - React function arguments are frozen (component props, hook arguments).
70 * - Hook arguments are frozen at the point the hook is invoked.
71 * - React function return values are frozen at the point of being returned,
72 - * thus the return value of a hook call is frozen.
72 + * thus the return value of a hook call is frozen.
73 * - JSX represents invocation of a React function (the component) and
74 - * therefore all values passed to JSX become frozen at the point the JSX
75 - * is created.
74 + * therefore all values passed to JSX become frozen at the point the JSX
75 + * is created.
76 *
77 * Internally, the inference tracks the approximate type of value held by each variable,
78 * and iterates over the control flow graph. The inferred effect of reach reference is
@@ -80,7 +80,7 @@ const UndefinedValue: InstructionValue = {
80 * object; an if condition reads the condition) and the type of the value. The types of values
81 * are:
82 * - frozen: can be any type so long as the value is known to be owned by React, permanently
83 - * and transitively immutable
83 + * and transitively immutable
84 * - maybe-frozen: the value may or may not be frozen, conditionally depending on control flow.
85 * - immutable: a type with value semantics: primitives, records/tuples when standardized.
86 * - mutable: a type with reference semantics eg array, object, class instance, etc.
@@ -92,8 +92,10 @@ export default function inferReferenceEffects(
92 fn: HIRFunction,
93 options: { isFunctionExpression: boolean } = { isFunctionExpression: false }
94 ): void {
95 - // Initial state contains function params
96 - // TODO: include module declarations here as well
95 + /*
96 + * Initial state contains function params
97 + * TODO: include module declarations here as well
98 + */
99 const initialState = InferenceState.empty(fn.env);
100 const value: InstructionValue = {
101 kind: "Primitive",
@@ -141,9 +143,11 @@ export default function inferReferenceEffects(
143 // Map of blocks to the last (merged) incoming state that was processed
144 const statesByBlock: Map<BlockId, InferenceState> = new Map();
145
144 - // Multiple predecessors may be visited prior to reaching a given successor,
145 - // so track the list of incoming state for each successor block.
146 - // These are merged when reaching that block again.
146 + /*
147 + * Multiple predecessors may be visited prior to reaching a given successor,
148 + * so track the list of incoming state for each successor block.
149 + * These are merged when reaching that block again.
150 + */
151 const queuedStates: Map<BlockId, InferenceState> = new Map();
152 function queue(blockId: BlockId, state: InferenceState): void {
153 let queuedState = queuedStates.get(blockId);
@@ -152,8 +156,10 @@ export default function inferReferenceEffects(
156 state = queuedState.merge(state) ?? state;
157 queuedStates.set(blockId, state);
158 } else {
155 - // this is the first queued state for this block, see whether
156 - // there are changed relative to the last time it was processed.
159 + /*
160 + * this is the first queued state for this block, see whether
161 + * there are changed relative to the last time it was processed.
162 + */
163 const prevState = statesByBlock.get(blockId);
164 const nextState = prevState != null ? prevState.merge(state) : state;
165 if (nextState != null) {
@@ -182,17 +188,17 @@ export default function inferReferenceEffects(
188 }
189 }
190
185 -/**
186 - * Maintains a mapping of top-level variables to the kind of value they hold
187 - */
191 +// Maintains a mapping of top-level variables to the kind of value they hold
192 class InferenceState {
193 #env: Environment;
194
195 // The kind of reach value, based on its allocation site
196 #values: Map<InstructionValue, ValueKind>;
193 - // The set of values pointed to by each identifier. This is a set
194 - // to accomodate phi points (where a variable may have different
195 - // values from different control flow paths).
197 + /*
198 + * The set of values pointed to by each identifier. This is a set
199 + * to accomodate phi points (where a variable may have different
200 + * values from different control flow paths).
201 + */
202 #variables: Map<IdentifierId, Set<InstructionValue>>;
203
204 constructor(
@@ -209,9 +215,7 @@ class InferenceState {
215 return new InferenceState(env, new Map(), new Map());
216 }
217
212 - /**
213 - * (Re)initializes a @param value with its default @param kind.
214 - */
218 + // (Re)initializes a @param value with its default @param kind.
219 initialize(value: InstructionValue, kind: ValueKind): void {
220 CompilerError.invariant(value.kind !== "LoadLocal", {
221 reason:
@@ -223,9 +227,7 @@ class InferenceState {
227 this.#values.set(value, kind);
228 }
229
226 - /**
227 - * Lookup the kind of the given @param value.
228 - */
230 + // Lookup the kind of the given @param value.
231 kind(place: Place): ValueKind {
232 const values = this.#variables.get(place.identifier.id);
233 CompilerError.invariant(values != null, {
@@ -250,9 +252,7 @@ class InferenceState {
252 return mergedKind;
253 }
254
253 - /**
254 - * Updates the value at @param place to point to the same value as @param value.
255 - */
255 + // Updates the value at @param place to point to the same value as @param value.
256 alias(place: Place, value: Place): void {
257 const values = this.#variables.get(value.identifier.id);
258 CompilerError.invariant(values != null, {
@@ -264,9 +264,7 @@ class InferenceState {
264 this.#variables.set(place.identifier.id, new Set(values));
265 }
266
267 - /**
268 - * Defines (initializing or updating) a variable with a specific kind of value.
269 - */
267 + // Defines (initializing or updating) a variable with a specific kind of value.
268 define(place: Place, value: InstructionValue): void {
269 CompilerError.invariant(this.#values.has(value), {
270 reason: `Expected value to be initialized at '${printSourceLocation(
@@ -283,10 +281,10 @@ class InferenceState {
281 return this.#variables.has(place.identifier.id);
282 }
283
286 - /**
284 + /*
285 * Records that a given Place was accessed with the given kind and:
286 * - Updates the effect of @param place based on the kind of value
289 - * and the kind of reference (@param effectKind).
287 + * and the kind of reference (@param effectKind).
288 * - Updates the value kind to reflect the effect of the reference.
289 *
290 * Notably, a mutable reference is downgraded to readonly if the
@@ -383,12 +381,14 @@ class InferenceState {
381 });
382 }
383
386 - // TODO(gsn): This should be bailout once we add bailout infra.
387 - //
388 - // invariant(
389 - // valueKind === ValueKind.Mutable,
390 - // `expected valueKind to be 'Mutable' but found to be '${valueKind}'`
391 - // );
384 + /*
385 + * TODO(gsn): This should be bailout once we add bailout infra.
386 + *
387 + * invariant(
388 + * valueKind === ValueKind.Mutable,
389 + * `expected valueKind to be 'Mutable' but found to be '${valueKind}'`
390 + * );
391 + */
392 effect = isObjectType(place.identifier) ? Effect.Store : Effect.Mutate;
393 break;
394 }
@@ -433,14 +433,14 @@ class InferenceState {
433 place.effect = effect;
434 }
435
436 - /**
436 + /*
437 * Combine the contents of @param this and @param other, returning a new
438 * instance with the combined changes _if_ there are any changes, or
439 * returning null if no changes would occur. Changes include:
440 * - new entries in @param other that did not exist in @param this
441 * - entries whose values differ in @param this and @param other,
442 - * and where joining the values produces a different value than
443 - * what was in @param this.
442 + * and where joining the values produces a different value than
443 + * what was in @param this.
444 *
445 * Note that values are joined using a lattice operation to ensure
446 * termination.
@@ -503,7 +503,7 @@ class InferenceState {
503 }
504 }
505
506 - /**
506 + /*
507 * Returns a copy of this state.
508 * TODO: consider using persistent data structures to make
509 * clone cheaper.
@@ -516,7 +516,7 @@ class InferenceState {
516 );
517 }
518
519 - /**
519 + /*
520 * For debugging purposes, dumps the state to a plain
521 * object so that it can printed as JSON.
522 */
@@ -558,46 +558,46 @@ class InferenceState {
558 }
559 }
560
561 -/**
561 +/*
562 * Joins two values using the following rules:
563 * == Effect Transitions ==
564 *
565 * Freezing an immutable value has not effect:
566 - * ┌───────────────┐
567 - * │ │
568 - * ▼ │ Freeze
566 + * ┌───────────────┐
567 + * │ │
568 + * ▼ │ Freeze
569 * ┌──────────────────────────┐ │
570 * │ Immutable │──┘
571 * └──────────────────────────┘
572 *
573 * Freezing a mutable or maybe-frozen value makes it frozen. Freezing a frozen
574 * value has no effect:
575 - * ┌───────────────┐
575 + * ┌───────────────┐
576 * ┌─────────────────────────┐ Freeze │ │
577 * │ MaybeFrozen │────┐ ▼ │ Freeze
578 * └─────────────────────────┘ │ ┌──────────────────────────┐ │
579 - * ├────▶│ Frozen │──┘
580 - * │ └──────────────────────────┘
579 + * ├────▶│ Frozen │──┘
580 + * │ └──────────────────────────┘
581 * ┌─────────────────────────┐ │
582 * │ Mutable │────┘
583 * └─────────────────────────┘
584 *
585 * == Join Lattice ==
586 * - immutable | mutable => mutable
587 - * The justification is that immutable and mutable values are different types,
588 - * and functions can introspect them to tell the difference (if the argument
589 - * is null return early, else if its an object mutate it).
587 + * The justification is that immutable and mutable values are different types,
588 + * and functions can introspect them to tell the difference (if the argument
589 + * is null return early, else if its an object mutate it).
590 * - frozen | mutable => maybe-frozen
591 - * Frozen values are indistinguishable from mutable values at runtime, so callers
592 - * cannot dynamically avoid mutation of "frozen" values. If a value could be
593 - * frozen we have to distinguish it from a mutable value. But it also isn't known
594 - * frozen yet, so we distinguish as maybe-frozen.
591 + * Frozen values are indistinguishable from mutable values at runtime, so callers
592 + * cannot dynamically avoid mutation of "frozen" values. If a value could be
593 + * frozen we have to distinguish it from a mutable value. But it also isn't known
594 + * frozen yet, so we distinguish as maybe-frozen.
595 * - immutable | frozen => frozen
596 - * This is subtle and falls out of the above rules. If a value could be any of
597 - * immutable, mutable, or frozen, then at runtime it could either be a primitive
598 - * or a reference type, and callers can't distinguish frozen or not for reference
599 - * types. To ensure that any sequence of joins btw those three states yields the
600 - * correct maybe-frozen, these two have to produce a frozen value.
596 + * This is subtle and falls out of the above rules. If a value could be any of
597 + * immutable, mutable, or frozen, then at runtime it could either be a primitive
598 + * or a reference type, and callers can't distinguish frozen or not for reference
599 + * types. To ensure that any sequence of joins btw those three states yields the
600 + * correct maybe-frozen, these two have to produce a frozen value.
601 * - <any> | maybe-frozen => maybe-frozen
602 * - immutable | context => context
603 * - mutable | context => context
@@ -606,13 +606,13 @@ class InferenceState {
606 * ┌──────────────────────────┐
607 * │ Immutable │───┐
608 * └──────────────────────────┘ │
609 - * │ ┌─────────────────────────┐
610 - * ├───▶│ Frozen │──┐
609 + * │ ┌─────────────────────────┐
610 + * ├───▶│ Frozen │──┐
611 * ┌──────────────────────────┐ │ └─────────────────────────┘ │
612 * │ Frozen │───┤ │ ┌─────────────────────────┐
613 * └──────────────────────────┘ │ ├─▶│ MaybeFrozen │
614 - * │ ┌─────────────────────────┐ │ └─────────────────────────┘
615 - * ├───▶│ MaybeFrozen │──┘
614 + * │ ┌─────────────────────────┐ │ └─────────────────────────┘
615 + * ├───▶│ MaybeFrozen │──┘
616 * ┌──────────────────────────┐ │ └─────────────────────────┘
617 * │ Mutable │───┘
618 * └──────────────────────────┘
@@ -648,7 +648,7 @@ function mergeValues(a: ValueKind, b: ValueKind): ValueKind {
648 }
649 }
650
651 -/**
651 +/*
652 * Iterates over the given @param block, defining variables and
653 * recording references on the @param state according to JS semantics.
654 */
@@ -721,8 +721,10 @@ function inferBlock(
721 break;
722 }
723 case "TemplateLiteral": {
724 - // template literal (with no tag function) always produces
725 - // an immutable string
724 + /*
725 + * template literal (with no tag function) always produces
726 + * an immutable string
727 + */
728 valueKind = ValueKind.Immutable;
729 effectKind = Effect.Read;
730 break;
@@ -750,8 +752,10 @@ function inferBlock(
752 );
753 hasMutableOperand ||= isMutableEffect(operand.effect, operand.loc);
754 }
753 - // If a closure did not capture any mutable values, then we can consider it to be
754 - // frozen, which allows it to be independently memoized.
755 + /*
756 + * If a closure did not capture any mutable values, then we can consider it to be
757 + * frozen, which allows it to be independently memoized.
758 + */
759 state.initialize(
760 instrValue,
761 hasMutableOperand ? ValueKind.Mutable : ValueKind.Frozen
@@ -811,8 +815,10 @@ function inferBlock(
815 const arg = instrValue.args[i];
816 const place = arg.kind === "Identifier" ? arg : arg.place;
817 if (effects !== null) {
814 - // If effects are inferred for an argument, we should fail invalid
815 - // mutating effects
818 + /*
819 + * If effects are inferred for an argument, we should fail invalid
820 + * mutating effects
821 + */
822 state.reference(place, effects[i]);
823 } else {
824 state.reference(place, Effect.ConditionallyMutate);
@@ -889,9 +895,11 @@ function inferBlock(
895 }
896 case "Await": {
897 state.initialize(instrValue, state.kind(instrValue.value));
892 - // Awaiting a value causes it to change state (go from unresolved to resolved or error)
893 - // It also means that any side-effects which would occur as part of the promise evaluation
894 - // will occur.
898 + /*
899 + * Awaiting a value causes it to change state (go from unresolved to resolved or error)
900 + * It also means that any side-effects which would occur as part of the promise evaluation
901 + * will occur.
902 + */
903 state.reference(instrValue.value, Effect.ConditionallyMutate);
904 const lvalue = instr.lvalue;
905 lvalue.effect = Effect.ConditionallyMutate;
@@ -899,12 +907,14 @@ function inferBlock(
907 continue;
908 }
909 case "TypeCastExpression": {
902 - // A type cast expression has no effect at runtime, so it's equivalent to a raw
903 - // identifier:
904 - // ```
905 - // x = (y: type) // is equivalent to...
906 - // x = y
907 - // ```
910 + /*
911 + * A type cast expression has no effect at runtime, so it's equivalent to a raw
912 + * identifier:
913 + * ```
914 + * x = (y: type) // is equivalent to...
915 + * x = y
916 + * ```
917 + */
918 state.initialize(instrValue, state.kind(instrValue.value));
919 state.reference(instrValue.value, Effect.Read);
920 const lvalue = instr.lvalue;
@@ -973,10 +983,12 @@ function inferBlock(
983 state.alias(lvalue, instrValue.value);
984 lvalue.effect = Effect.Store;
985 state.alias(instrValue.lvalue, instrValue.value);
976 - // NOTE: *not* using state.reference since this is an assignment.
977 - // reference() checks if the effect is valid given the value kind,
978 - // but here the previous value kind doesn't matter since we are
979 - // replacing it
986 + /*
987 + * NOTE: *not* using state.reference since this is an assignment.
988 + * reference() checks if the effect is valid given the value kind,
989 + * but here the previous value kind doesn't matter since we are
990 + * replacing it
991 + */
992 instrValue.lvalue.effect = Effect.Store;
993 continue;
994 }
@@ -992,10 +1004,12 @@ function inferBlock(
1004 state.alias(lvalue, instrValue.value);
1005 lvalue.effect = Effect.Store;
1006 state.alias(instrValue.lvalue.place, instrValue.value);
995 - // NOTE: *not* using state.reference since this is an assignment.
996 - // reference() checks if the effect is valid given the value kind,
997 - // but here the previous value kind doesn't matter since we are
998 - // replacing it
1007 + /*
1008 + * NOTE: *not* using state.reference since this is an assignment.
1009 + * reference() checks if the effect is valid given the value kind,
1010 + * but here the previous value kind doesn't matter since we are
1011 + * replacing it
1012 + */
1013 instrValue.lvalue.place.effect = Effect.Store;
1014 continue;
1015 }
@@ -1026,10 +1040,12 @@ function inferBlock(
1040 lvalue.effect = Effect.Store;
1041 for (const place of eachPatternOperand(instrValue.lvalue.pattern)) {
1042 state.alias(place, instrValue.value);
1029 - // NOTE: *not* using state.reference since this is an assignment.
1030 - // reference() checks if the effect is valid given the value kind,
1031 - // but here the previous value kind doesn't matter since we are
1032 - // replacing it
1043 + /*
1044 + * NOTE: *not* using state.reference since this is an assignment.
1045 + * reference() checks if the effect is valid given the value kind,
1046 + * but here the previous value kind doesn't matter since we are
1047 + * replacing it
1048 + */
1049 place.effect = Effect.Store;
1050 }
1051 continue;
@@ -1106,7 +1122,7 @@ export function getFunctionCallSignature(
1122 return env.getFunctionSignature(type);
1123 }
1124
1109 -/**
1125 +/*
1126 * Make a best attempt at matching arguments of a {@link MethodCall} to parameter effects.
1127 * defined in its {@link FunctionSignature}.
1128 *
@@ -1122,8 +1138,10 @@ function getFunctionEffects(
1138 for (let i = 0; i < fn.args.length; i++) {
1139 const arg = fn.args[i];
1140 if (i < sig.positionalParams.length) {
1125 - // Only infer effects when there is a direct mapping positional arg --> positional param
1126 - // Otherwise, return null to indicate inference failed
1141 + /*
1142 + * Only infer effects when there is a direct mapping positional arg --> positional param
1143 + * Otherwise, return null to indicate inference failed
1144 + */
1145 if (arg.kind === "Identifier") {
1146 results.push(sig.positionalParams[i]);
1147 } else {
@@ -1132,8 +1150,10 @@ function getFunctionEffects(
1150 } else if (sig.restParam !== null) {
1151 results.push(sig.restParam);
1152 } else {
1135 - // If there are more arguments than positional arguments and a rest parameter is not
1136 - // defined, we'll also assume that inference failed
1153 + /*
1154 + * If there are more arguments than positional arguments and a rest parameter is not
1155 + * defined, we'll also assume that inference failed
1156 + */
1157 return null;
1158 }
1159 }
compiler/packages/babel-plugin-react-forget/src/Inference/InferTryCatchAliases.ts
+10 -6
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -8,7 +8,7 @@
8 import { BlockId, HIRFunction, Identifier } from "../HIR";
9 import DisjointSet from "../Utils/DisjointSet";
10
11 -/**
11 +/*
12 * Any values created within a try/catch block could be aliased to the try handler.
13 * Our lowering ensures that every instruction within a try block will be lowered into a
14 * basic block ending in a maybe-throw terminal that points to its catch block, so we can
@@ -31,12 +31,16 @@ export function inferTryCatchAliases(
31 } else if (block.terminal.kind === "maybe-throw") {
32 const handlerParam = handlerParams.get(block.terminal.handler);
33 if (handlerParam === undefined) {
34 - // There's no catch clause param, nothing to alias to so
35 - // skip this block
34 + /*
35 + * There's no catch clause param, nothing to alias to so
36 + * skip this block
37 + */
38 continue;
39 }
38 - // Otherwise alias all values created in this block to the
39 - // catch clause param
40 + /*
41 + * Otherwise alias all values created in this block to the
42 + * catch clause param
43 + */
44 for (const instr of block.instructions) {
45 aliases.union([handlerParam, instr.lvalue.identifier]);
46 }
compiler/packages/babel-plugin-react-forget/src/Inference/InlineImmediatelyInvokedFunctionExpressions.ts
+53 -41
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -27,7 +27,7 @@ import { markInstructionIds, markPredecessors } from "../HIR/HIRBuilder";
27 import { eachInstructionValueOperand } from "../HIR/visitors";
28 import { retainWhere } from "../Utils/utils";
29
30 -/**
30 +/*
31 * Inlines immediately invoked function expressions (IIFEs) to allow more fine-grained memoization
32 * of the values they produce.
33 *
@@ -35,41 +35,41 @@ import { retainWhere } from "../Utils/utils";
35 *
36 * ```
37 * const x = (() => {
38 - * const x = [];
39 - * x.push(foo());
40 - * return x;
38 + * const x = [];
39 + * x.push(foo());
40 + * return x;
41 * })();
42 *
43 * =>
44 *
45 * bb0:
46 - * // placeholder for the result, all return statements will assign here
47 - * let t0;
48 - * // Label allows using a goto (break) to exit out of the body
49 - * Label block=bb1 fallthrough=bb2
46 + * // placeholder for the result, all return statements will assign here
47 + * let t0;
48 + * // Label allows using a goto (break) to exit out of the body
49 + * Label block=bb1 fallthrough=bb2
50 * bb1:
51 - * // code within the function expression
52 - * const x0 = [];
53 - * x0.push(foo());
54 - * // return is replaced by assignment to the result variable...
55 - * t0 = x0;
56 - * // ...and a goto to the code after the function expression invocation
57 - * Goto bb2
51 + * // code within the function expression
52 + * const x0 = [];
53 + * x0.push(foo());
54 + * // return is replaced by assignment to the result variable...
55 + * t0 = x0;
56 + * // ...and a goto to the code after the function expression invocation
57 + * Goto bb2
58 * bb2:
59 - * // code after the IIFE call
60 - * const x = t0;
59 + * // code after the IIFE call
60 + * const x = t0;
61 * ```
62 *
63 * The implementation relies on HIR's ability to support labeled blocks:
64 * - We terminate the basic block just prior to the CallExpression of the IIFE
65 - * with a LabelTerminal whose fallback is the code following the CallExpression.
66 - * Just prior to the terminal we also create a named temporary variable which
67 - * will hold the result.
65 + * with a LabelTerminal whose fallback is the code following the CallExpression.
66 + * Just prior to the terminal we also create a named temporary variable which
67 + * will hold the result.
68 * - We then inline the contents of the function "in between" (conceptually) those
69 - * two blocks.
69 + * two blocks.
70 * - All return statements in the original function expression are replaced with a
71 - * StoreLocal to the temporary we allocated before plus a Goto to the fallthrough
72 - * block (code following the CallExpression).
71 + * StoreLocal to the temporary we allocated before plus a Goto to the fallthrough
72 + * block (code following the CallExpression).
73 */
74 export function inlineImmediatelyInvokedFunctionExpressions(
75 fn: HIRFunction
@@ -79,11 +79,13 @@ export function inlineImmediatelyInvokedFunctionExpressions(
79 // Functions that are inlined
80 const inlinedFunctions = new Set<IdentifierId>();
81
82 - // Iterate the *existing* blocks from the outer component to find IIFEs
83 - // and inline them. During iteration we will modify `fn` (by inlining the CFG
84 - // of IIFEs) so we explicitly copy references to just the original
85 - // function's blocks first. As blocks are split to make room for IIFE calls,
86 - // the split portions of the blocks will be added to this queue.
82 + /*
83 + * Iterate the *existing* blocks from the outer component to find IIFEs
84 + * and inline them. During iteration we will modify `fn` (by inlining the CFG
85 + * of IIFEs) so we explicitly copy references to just the original
86 + * function's blocks first. As blocks are split to make room for IIFE calls,
87 + * the split portions of the blocks will be added to this queue.
88 + */
89 const queue = Array.from(fn.body.blocks.values());
90 queue: for (const block of queue) {
91 for (let ii = 0; ii < block.instructions.length; ii++) {
@@ -130,13 +132,17 @@ export function inlineImmediatelyInvokedFunctionExpressions(
132 };
133 fn.body.blocks.set(continuationBlockId, continuationBlock);
134
133 - // Trim the original block to contain instructions up to (but not including)
134 - // the IIFE
135 + /*
136 + * Trim the original block to contain instructions up to (but not including)
137 + * the IIFE
138 + */
139 block.instructions.length = ii;
140
137 - // To account for complex control flow within the lambda, we treat the lambda
138 - // as if it were a single labeled statement, and replace all returns with gotos
139 - // to the label fallthrough.
141 + /*
142 + * To account for complex control flow within the lambda, we treat the lambda
143 + * as if it were a single labeled statement, and replace all returns with gotos
144 + * to the label fallthrough.
145 + */
146 const newTerminal: LabelTerminal = {
147 block: body.loweredFunc.func.body.entry,
148 id: makeInstructionId(0),
@@ -155,16 +161,20 @@ export function inlineImmediatelyInvokedFunctionExpressions(
161 // Promote the temporary with a name as we require this to persist
162 promoteTemporary(result.identifier);
163
158 - // Rewrite blocks from the lambda to replace any `return` with a
159 - // store to the result and `goto` the continuation block
164 + /*
165 + * Rewrite blocks from the lambda to replace any `return` with a
166 + * store to the result and `goto` the continuation block
167 + */
168 for (const [id, block] of body.loweredFunc.func.body.blocks) {
169 block.preds.clear();
170 rewriteBlock(fn.env, block, continuationBlockId, result);
171 fn.body.blocks.set(id, block);
172 }
173
166 - // Ensure we visit the continuation block, since there may have been
167 - // sequential IIFEs that need to be visited.
174 + /*
175 + * Ensure we visit the continuation block, since there may have been
176 + * sequential IIFEs that need to be visited.
177 + */
178 queue.push(continuationBlock);
179 continue queue;
180 }
@@ -187,15 +197,17 @@ export function inlineImmediatelyInvokedFunctionExpressions(
197 );
198 }
199
190 - // If terminals have changed then blocks may have become newly unreachable.
191 - // Re-run minification of the graph (incl reordering instruction ids)
200 + /*
201 + * If terminals have changed then blocks may have become newly unreachable.
202 + * Re-run minification of the graph (incl reordering instruction ids)
203 + */
204 reversePostorderBlocks(fn.body);
205 markInstructionIds(fn.body);
206 markPredecessors(fn.body);
207 }
208 }
209
198 -/**
210 +/*
211 * Rewrites the block so that all `return` terminals are replaced:
212 * * Add a StoreLocal <returnValue> = <terminal.value>
213 * * Replace the terminal with a Goto to <returnTarget>
compiler/packages/babel-plugin-react-forget/src/Inference/index.ts
+1 -1
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
compiler/packages/babel-plugin-react-forget/src/Optimization/ConstantPropagation.ts
+28 -16
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -33,7 +33,7 @@ import {
33 } from "../HIR/HIRBuilder";
34 import { eliminateRedundantPhi } from "../SSA";
35
36 -/**
36 +/*
37 * Applies constant propagation/folding to the given function. The approach is
38 * [Sparse Conditional Constant Propagation](https://en.wikipedia.org/wiki/Sparse_conditional_constant_propagation):
39 * we use abstract interpretation to record known constant values for identifiers,
@@ -67,8 +67,10 @@ function constantPropagationImpl(fn: HIRFunction, constants: Constants): void {
67 if (!haveTerminalsChanged) {
68 break;
69 }
70 - // If terminals have changed then blocks may have become newly unreachable.
71 - // Re-run minification of the graph (incl reordering instruction ids)
70 + /*
71 + * If terminals have changed then blocks may have become newly unreachable.
72 + * Re-run minification of the graph (incl reordering instruction ids)
73 + */
74 reversePostorderBlocks(fn.body);
75 removeUnreachableFallthroughs(fn.body);
76 removeUnreachableForUpdates(fn.body);
@@ -87,11 +89,15 @@ function constantPropagationImpl(fn: HIRFunction, constants: Constants): void {
89 }
90 }
91 }
90 - // By removing some phi operands, there may be phis that were not previously
91 - // redundant but now are
92 + /*
93 + * By removing some phi operands, there may be phis that were not previously
94 + * redundant but now are
95 + */
96 eliminateRedundantPhi(fn);
93 - // Finally, merge together any blocks that are now guaranteed to execute
94 - // consecutively
97 + /*
98 + * Finally, merge together any blocks that are now guaranteed to execute
99 + * consecutively
100 + */
101 mergeConsecutiveBlocks(fn);
102
103 assertConsistentIdentifiers(fn);
@@ -105,9 +111,11 @@ function applyConstantPropagation(
111 ): boolean {
112 let hasChanges = false;
113 for (const [, block] of fn.body.blocks) {
108 - // Initialize phi values if all operands have the same known constant value.
109 - // Note that this analysis uses a single-pass only, so it will never fill in
110 - // phi values for blocks that have a back-edge.
114 + /*
115 + * Initialize phi values if all operands have the same known constant value.
116 + * Note that this analysis uses a single-pass only, so it will never fill in
117 + * phi values for blocks that have a back-edge.
118 + */
119 for (const phi of block.phis) {
120 let value = evaluatePhi(phi, constants);
121 if (value !== null) {
@@ -117,8 +125,10 @@ function applyConstantPropagation(
125
126 for (let i = 0; i < block.instructions.length; i++) {
127 if (block.kind === "sequence" && i === block.instructions.length - 1) {
120 - // evaluating the last value of a value block can break order of evaluation,
121 - // skip these instructions
128 + /*
129 + * evaluating the last value of a value block can break order of evaluation,
130 + * skip these instructions
131 + */
132 continue;
133 }
134 const instr = block.instructions[i]!;
@@ -165,8 +175,10 @@ function evaluatePhi(phi: Phi, constants: Constants): Constant | null {
175 return null;
176 }
177
168 - // first iteration of the loop, let's store the operand and continue
169 - // looping.
178 + /*
179 + * first iteration of the loop, let's store the operand and continue
180 + * looping.
181 + */
182 if (value === null) {
183 value = operandValue;
184 continue;
@@ -433,7 +445,7 @@ function evaluateInstruction(
445 }
446 }
447
436 -/**
448 +/*
449 * Recursively read the value of a place: if it is a constant place, attempt to read
450 * from that place until reaching a primitive or finding a value that is unset.
451 */
compiler/packages/babel-plugin-react-forget/src/Optimization/DeadCodeElimination.ts
+56 -34
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -23,7 +23,7 @@ import {
23 } from "../HIR/visitors";
24 import { assertExhaustive, retainWhere } from "../Utils/utils";
25
26 -/**
26 +/*
27 * Implements dead-code elimination, eliminating instructions whose values are unused.
28 *
29 * Note that unreachable blocks are already pruned during HIR construction.
@@ -31,8 +31,10 @@ import { assertExhaustive, retainWhere } from "../Utils/utils";
31 export function deadCodeElimination(fn: HIRFunction): void {
32 const state = new State();
33
34 - // If there are no back-edges the algorithm can terminate after a single iteration
35 - // of the blocks
34 + /*
35 + * If there are no back-edges the algorithm can terminate after a single iteration
36 + * of the blocks
37 + */
38 const hasLoop = hasBackEdge(fn);
39
40 const reversedBlocks = [...fn.body.blocks.values()].reverse();
@@ -40,8 +42,10 @@ export function deadCodeElimination(fn: HIRFunction): void {
42 do {
43 size = state.count;
44
43 - // Iterate blocks in postorder (successors before predecessors, excepting loops)
44 - // to find usages before declarations
45 + /*
46 + * Iterate blocks in postorder (successors before predecessors, excepting loops)
47 + * to find usages before declarations
48 + */
49 for (const block of reversedBlocks) {
50 for (const operand of eachTerminalOperand(block.terminal)) {
51 state.reference(operand.identifier);
@@ -59,8 +63,10 @@ export function deadCodeElimination(fn: HIRFunction): void {
63 }
64 state.reference(instr.lvalue.identifier);
65
62 - // For the last value of a value block, if it's not pruneable we can't
63 - // rewrite it. This is necessary to preserve unused value blocks
66 + /*
67 + * For the last value of a value block, if it's not pruneable we can't
68 + * rewrite it. This is necessary to preserve unused value blocks
69 + */
70 if (block.kind !== "block" && i === block.instructions.length - 1) {
71 for (const place of eachInstructionValueOperand(instr.value)) {
72 state.reference(place.identifier);
@@ -103,9 +109,11 @@ class State {
109 }
110 }
111
106 - // Check if any version of the given identifier is used somewhere.
107 - // This checks both for usage of this specific identifer id (ssa id)
108 - // and (for named identifiers) for any usages of that identifier name.
112 + /*
113 + * Check if any version of the given identifier is used somewhere.
114 + * This checks both for usage of this specific identifer id (ssa id)
115 + * and (for named identifiers) for any usages of that identifier name.
116 + */
117 isIdOrNameUsed(identifier: Identifier): boolean {
118 return (
119 this.identifiers.has(identifier.id) ||
@@ -113,8 +121,10 @@ class State {
121 );
122 }
123
116 - // Like `used()`, but only checks for usages of this specific identifier id
117 - // (ssa id).
124 + /*
125 + * Like `used()`, but only checks for usages of this specific identifier id
126 + * (ssa id).
127 + */
128 isIdUsed(identifier: Identifier): boolean {
129 return this.identifiers.has(identifier.id);
130 }
@@ -131,9 +141,11 @@ function visitInstruction(instr: Instruction, state: State): void {
141 // Remove unused lvalues
142 switch (instr.value.lvalue.pattern.kind) {
143 case "ArrayPattern": {
134 - // For arrays, we can only eliminate unused items from the end of the array,
135 - // so we iterate from the end and break once we find a used item. Note that
136 - // we already know at least one item is used, from the pruneableValue check.
144 + /*
145 + * For arrays, we can only eliminate unused items from the end of the array,
146 + * so we iterate from the end and break once we find a used item. Note that
147 + * we already know at least one item is used, from the pruneableValue check.
148 + */
149 let nextItems: ArrayPattern["items"] | null = null;
150 const originalItems = instr.value.lvalue.pattern.items;
151 for (let i = originalItems.length - 1; i >= 0; i--) {
@@ -156,11 +168,13 @@ function visitInstruction(instr: Instruction, state: State): void {
168 break;
169 }
170 case "ObjectPattern": {
159 - // For objects we can prune any unused properties so long as there is no used rest element
160 - // (`const {x, ...y} = z`). If a rest element exists and is used, then nothing can be pruned
161 - // because it would change the set of properties which are copied into the rest value.
162 - // In the `const {x, ...y} = z` example, removing the `x` property would mean that `y` now
163 - // has an `x` property, changing the semantics.
171 + /*
172 + * For objects we can prune any unused properties so long as there is no used rest element
173 + * (`const {x, ...y} = z`). If a rest element exists and is used, then nothing can be pruned
174 + * because it would change the set of properties which are copied into the rest value.
175 + * In the `const {x, ...y} = z` example, removing the `x` property would mean that `y` now
176 + * has an `x` property, changing the semantics.
177 + */
178 let nextProperties: ObjectPattern["properties"] | null = null;
179 for (const property of instr.value.lvalue.pattern.properties) {
180 if (property.kind === "ObjectProperty") {
@@ -194,18 +208,22 @@ function visitInstruction(instr: Instruction, state: State): void {
208 instr.value.lvalue.kind !== InstructionKind.Reassign &&
209 !state.isIdUsed(instr.value.lvalue.place.identifier)
210 ) {
197 - // This is a const/let declaration where the variable is accessed later,
198 - // but where the value is always overwritten before being read. Ie the
199 - // initializer value is never read. We rewrite to a DeclareLocal so
200 - // that the initializer value can be DCE'd
211 + /*
212 + * This is a const/let declaration where the variable is accessed later,
213 + * but where the value is always overwritten before being read. Ie the
214 + * initializer value is never read. We rewrite to a DeclareLocal so
215 + * that the initializer value can be DCE'd
216 + */
217 instr.value = {
218 kind: "DeclareLocal",
219 lvalue: instr.value.lvalue,
220 loc: instr.value.loc,
221 };
222 } else {
207 - // Else we mark the initializer as referenced, since the variable itself is
208 - // referenced
223 + /*
224 + * Else we mark the initializer as referenced, since the variable itself is
225 + * referenced
226 + */
227 state.reference(instr.value.value.identifier);
228 }
229 } else {
@@ -215,7 +233,7 @@ function visitInstruction(instr: Instruction, state: State): void {
233 }
234 }
235
218 -/**
236 +/*
237 * Returns true if it is safe to prune an instruction with the given value.
238 * Functions which may have side-
239 */
@@ -268,9 +286,11 @@ function pruneableValue(value: InstructionValue, state: State): boolean {
286 case "PropertyDelete":
287 case "MethodCall":
288 case "PropertyStore": {
271 - // Mutating instructions are not safe to prune.
272 - // TODO: we could be more precise and make this conditional on whether
273 - // any arguments are actually modified
289 + /*
290 + * Mutating instructions are not safe to prune.
291 + * TODO: we could be more precise and make this conditional on whether
292 + * any arguments are actually modified
293 + */
294 return false;
295 }
296 case "NewExpression":
@@ -281,9 +301,11 @@ function pruneableValue(value: InstructionValue, state: State): boolean {
301 }
302 case "NextPropertyOf":
303 case "NextIterableOf": {
284 - // Technically a NextIterableOf/NextPropertyOf will never be unused because it's
285 - // always used later by another StoreLocal or Destructure instruction, but conceptually
286 - // we can't prune
304 + /*
305 + * Technically a NextIterableOf/NextPropertyOf will never be unused because it's
306 + * always used later by another StoreLocal or Destructure instruction, but conceptually
307 + * we can't prune
308 + */
309 return false;
310 }
311 case "LoadContext":
compiler/packages/babel-plugin-react-forget/src/Optimization/PruneMaybeThrows.ts
+14 -8
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -24,7 +24,7 @@ import {
24 } from "../HIR/HIRBuilder";
25 import { eliminateRedundantPhi } from "../SSA";
26
27 -/**
27 +/*
28 * This pass prunes `maybe-throw` terminals for blocks that can provably *never* throw.
29 * For now this is very conservative, and only affects blocks with primitives or
30 * array/object literals. Even a variable reference could throw bc of the TDZ.
@@ -32,8 +32,10 @@ import { eliminateRedundantPhi } from "../SSA";
32 export function pruneMaybeThrows(fn: HIRFunction): void {
33 const didPrune = pruneMaybeThrowsImpl(fn);
34 if (didPrune) {
35 - // If terminals have changed then blocks may have become newly unreachable.
36 - // Re-run minification of the graph (incl reordering instruction ids)
35 + /*
36 + * If terminals have changed then blocks may have become newly unreachable.
37 + * Re-run minification of the graph (incl reordering instruction ids)
38 + */
39 reversePostorderBlocks(fn.body);
40 removeUnreachableFallthroughs(fn.body);
41 removeUnreachableForUpdates(fn.body);
@@ -52,11 +54,15 @@ export function pruneMaybeThrows(fn: HIRFunction): void {
54 }
55 }
56 }
55 - // By removing some phi operands, there may be phis that were not previously
56 - // redundant but now are
57 + /*
58 + * By removing some phi operands, there may be phis that were not previously
59 + * redundant but now are
60 + */
61 eliminateRedundantPhi(fn);
58 - // Finally, merge together any blocks that are now guaranteed to execute
59 - // consecutively
62 + /*
63 + * Finally, merge together any blocks that are now guaranteed to execute
64 + * consecutively
65 + */
66 mergeConsecutiveBlocks(fn);
67
68 assertConsistentIdentifiers(fn);
compiler/packages/babel-plugin-react-forget/src/Optimization/index.ts
+1 -1
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/AlignReactiveScopesToBlockScopes.ts
+27 -21
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -16,17 +16,17 @@ import {
16 import { getPlaceScope } from "./BuildReactiveBlocks";
17 import { ReactiveFunctionVisitor, visitReactiveFunction } from "./visitors";
18
19 -/**
19 +/*
20 * Note: this is the 2nd of 4 passes that determine how to break a function into discrete
21 * reactive scopes (independently memoizeable units of code):
22 * 1. InferReactiveScopeVariables (on HIR) determines operands that mutate together and assigns
23 - * them a unique reactive scope.
23 + * them a unique reactive scope.
24 * 2. AlignReactiveScopesToBlockScopes (this pass, on ReactiveFunction) aligns reactive scopes
25 - * to block scopes.
25 + * to block scopes.
26 * 3. MergeOverlappingReactiveScopes (on ReactiveFunction) ensures that reactive scopes do not
27 - * overlap, merging any such scopes.
27 + * overlap, merging any such scopes.
28 * 4. BuildReactiveBlocks (on ReactiveFunction) groups the statements for each scope into
29 - * a ReactiveScopeBlock.
29 + * a ReactiveScopeBlock.
30 *
31 * Prior inference passes assign a reactive scope to each operand, but the ranges of these
32 * scopes are based on specific instructions at arbitrary points in the control-flow graph.
@@ -38,14 +38,14 @@ import { ReactiveFunctionVisitor, visitReactiveFunction } from "./visitors";
38 *
39 * ```javascript
40 * function foo(cond, a) {
41 - * ⌵ original scope
42 - * ⌵ expanded scope
43 - * const x = []; ⌝ ⌝
44 - * if (cond) { ⎮ ⎮
45 - * ... ⎮ ⎮
46 - * x.push(a); ⌟ ⎮
47 - * ... ⎮
48 - * } ⌟
41 + * ⌵ original scope
42 + * ⌵ expanded scope
43 + * const x = []; ⌝ ⌝
44 + * if (cond) { ⎮ ⎮
45 + * ... ⎮ ⎮
46 + * x.push(a); ⌟ ⎮
47 + * ... ⎮
48 + * } ⌟
49 * }
50 * ```
51 *
@@ -89,17 +89,23 @@ class Visitor extends ReactiveFunctionVisitor<Context> {
89 type PendingReactiveScope = { active: boolean; scope: ReactiveScope };
90
91 class Context {
92 - // For each block scope (outer array) stores a list of ReactiveScopes that start
93 - // in that block scope.
92 + /*
93 + * For each block scope (outer array) stores a list of ReactiveScopes that start
94 + * in that block scope.
95 + */
96 #blockScopes: Array<Array<PendingReactiveScope>> = [];
97
96 - // ReactiveScopes whose declaring block scope has ended but may still need to
97 - // be "closed" (ie have their range.end be updated). A given scope can be in
98 - // blockScopes OR this array but not both.
98 + /*
99 + * ReactiveScopes whose declaring block scope has ended but may still need to
100 + * be "closed" (ie have their range.end be updated). A given scope can be in
101 + * blockScopes OR this array but not both.
102 + */
103 #unclosedScopes: Array<PendingReactiveScope> = [];
104
101 - // Set of all scope ids that have been seen so far, regardless of which of
102 - // the above data structures they're in, to avoid tracking the same scope twice.
105 + /*
106 + * Set of all scope ids that have been seen so far, regardless of which of
107 + * the above data structures they're in, to avoid tracking the same scope twice.
108 + */
109 #seenScopes: Set<ScopeId> = new Set();
110
111 enter(fn: () => void): void {
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/AssertScopeInstructionsWithinScope.ts
+12 -10
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -17,25 +17,25 @@ import {
17 import { getPlaceScope } from "./BuildReactiveBlocks";
18 import { ReactiveFunctionVisitor } from "./visitors";
19
20 -/**
20 +/*
21 * Internal validation pass that checks all the instructions involved in creating
22 * values for a given scope are within the corresponding ReactiveScopeBlock. Errors
23 * in HIR/ReactiveFunction structure and alias analysis could theoretically create
24 * a structure such as:
25 *
26 * Function
27 - * LabelTerminal
27 + * LabelTerminal
28 + * Instruction in scope 0
29 * Instruction in scope 0
29 - * Instruction in scope 0
30 *
31 * Because ReactiveScopeBlocks are closed when their surrounding block ends, this
32 * structure would create reactive scopes as follows:
33 *
34 * Function
35 - * LabelTerminal
36 - * ReactiveScopeBlock scope=0
37 - * Instruction in scope 0
38 - * Instruction in scope 0
35 + * LabelTerminal
36 + * ReactiveScopeBlock scope=0
37 + * Instruction in scope 0
38 + * Instruction in scope 0
39 *
40 * This pass asserts we didn't accidentally end up with such a structure, as a guard
41 * against compiler coding mistakes in earlier passes.
@@ -74,8 +74,10 @@ class CheckInstructionsAgainstScopesVisitor extends ReactiveFunctionVisitor<
74 scope !== null &&
75 // is there a scope for this at all, or did we end up pruning this scope?
76 state.has(scope.id) &&
77 - // if the scope exists somewhere, it must be active or else this is a straggler
78 - // instruction
77 + /*
78 + * if the scope exists somewhere, it must be active or else this is a straggler
79 + * instruction
80 + */
81 !this.activeScopes.has(scope.id)
82 ) {
83 CompilerError.invariant(false, {
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/BuildReactiveBlocks.ts
+13 -11
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -22,17 +22,17 @@ import { eachInstructionLValue } from "../HIR/visitors";
22 import { assertExhaustive } from "../Utils/utils";
23 import { eachReactiveValueOperand, mapTerminalBlocks } from "./visitors";
24
25 -/**
25 +/*
26 * Note: this is the 4th of 4 passes that determine how to break a function into discrete
27 * reactive scopes (independently memoizeable units of code):
28 * 1. InferReactiveScopeVariables (on HIR) determines operands that mutate together and assigns
29 - * them a unique reactive scope.
29 + * them a unique reactive scope.
30 * 2. AlignReactiveScopesToBlockScopes (on ReactiveFunction) aligns reactive scopes
31 - * to block scopes.
31 + * to block scopes.
32 * 3. MergeOverlappingReactiveScopes (this pass, on ReactiveFunction) ensures that reactive
33 - * scopes do not overlap, merging any such scopes.
33 + * scopes do not overlap, merging any such scopes.
34 * 4. BuildReactiveBlocks (on ReactiveFunction) groups the statements for each scope into
35 - * a ReactiveScopeBlock.
35 + * a ReactiveScopeBlock.
36 *
37 * Given a function where the reactive scopes have been correctly aligned and merged,
38 * this pass groups the instructions for each reactive scope into ReactiveBlocks.
@@ -134,11 +134,13 @@ class Builder {
134 }
135
136 complete(): ReactiveBlock {
137 - // TODO: @josephsavona debug violations of this invariant
138 - // invariant(
139 - // this.#stack.length === 1,
140 - // "Expected all scopes to be closed when exiting a block"
141 - // );
137 + /*
138 + * TODO: @josephsavona debug violations of this invariant
139 + * invariant(
140 + * this.#stack.length === 1,
141 + * "Expected all scopes to be closed when exiting a block"
142 + * );
143 + */
144 const first = this.#stack[0]!;
145 CompilerError.invariant(first.kind === "block", {
146 reason: "Expected first stack item to be a basic block",
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/BuildReactiveFunction.ts
+50 -36
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -30,7 +30,7 @@ import {
30 } from "../HIR/HIR";
31 import { assertExhaustive } from "../Utils/utils";
32
33 -/**
33 +/*
34 * Converts from HIR (lower-level CFG) to ReactiveFunction, a tree representation
35 * that is closer to an AST. This pass restores the original control flow constructs,
36 * including break/continue to labeled statements. Note that this pass naively emits
@@ -184,10 +184,12 @@ class Driver {
184
185 let consequent: ReactiveBlock;
186 if (this.cx.isScheduled(case_.block)) {
187 - // cases which are empty or contain only a `break` may point to blocks
188 - // that are already scheduled. emit as follows:
189 - // - if the block is for another case branch, don't emit a break and fall-through
190 - // - else, emit an explicit break.
187 + /*
188 + * cases which are empty or contain only a `break` may point to blocks
189 + * that are already scheduled. emit as follows:
190 + * - if the block is for another case branch, don't emit a break and fall-through
191 + * - else, emit an explicit break.
192 + */
193 const break_ = this.visitBreak(case_.block, null);
194 if (
195 index === 0 &&
@@ -195,9 +197,11 @@ class Driver {
197 case_.block === terminal.fallthrough &&
198 case_.test === null
199 ) {
198 - // If the last case statement (first in reverse order) is a default that
199 - // jumps to the fallthrough, then we would emit a useless `default: {}`,
200 - // so instead skip this case.
200 + /*
201 + * If the last case statement (first in reverse order) is a default that
202 + * jumps to the fallthrough, then we would emit a useless `default: {}`,
203 + * so instead skip this case.
204 + */
205 return;
206 }
207 const block = [];
@@ -715,8 +719,10 @@ class Driver {
719 break;
720 }
721 case "maybe-throw": {
718 - // ReactiveFunction does not explicit model maybe-throw semantics,
719 - // so these terminals flatten away
722 + /*
723 + * ReactiveFunction does not explicit model maybe-throw semantics,
724 + * so these terminals flatten away
725 + */
726 if (!this.cx.isScheduled(terminal.continuation)) {
727 this.visitBlock(
728 this.cx.ir.blocks.get(terminal.continuation)!,
@@ -883,8 +889,10 @@ class Driver {
889 };
890 }
891 } else {
886 - // The value block ended in a value terminal, recurse to get the value
887 - // of that terminal
892 + /*
893 + * The value block ended in a value terminal, recurse to get the value
894 + * of that terminal
895 + */
896 const init = this.visitValueBlockTerminal(defaultBlock.terminal);
897 // Code following the logical terminal
898 const final = this.visitValueBlock(init.fallthrough, loc);
@@ -1158,7 +1166,7 @@ class Context {
1166 ir: HIR;
1167 #nextScheduleId: number = 0;
1168
1161 - /**
1169 + /*
1170 * Used to track which blocks *have been* generated already in order to
1171 * abort if a block is generated a second time. This is an error catching
1172 * mechanism for debugging purposes, and is not used by the codegen algorithm
@@ -1166,7 +1174,7 @@ class Context {
1174 */
1175 emitted: Set<BlockId> = new Set();
1176
1169 - /**
1177 + /*
1178 * A set of blocks that are already scheduled to be emitted by eg a parent.
1179 * This allows child nodes to avoid re-emitting the same block and emit eg
1180 * a break instead.
@@ -1175,7 +1183,7 @@ class Context {
1183
1184 #catchHandlers: Set<BlockId> = new Set();
1185
1178 - /**
1186 + /*
1187 * Represents which control flow operations are currently in scope, with the innermost
1188 * scope last. Roughly speaking, the last ControlFlowTarget on the stack indicates where
1189 * control will implicitly transfer, such that gotos to that block can be elided. Gotos
@@ -1196,7 +1204,7 @@ class Context {
1204 this.#catchHandlers.add(block);
1205 }
1206
1199 - /**
1207 + /*
1208 * Record that the given block will be emitted (eg by the codegen of a parent node)
1209 * so that child nodes can avoid re-emitting it.
1210 */
@@ -1246,9 +1254,7 @@ class Context {
1254 return id;
1255 }
1256
1249 - /**
1250 - * Removes a block that was scheduled; must be called after that block is emitted.
1251 - */
1257 + // Removes a block that was scheduled; must be called after that block is emitted.
1258 unschedule(scheduleId: number): void {
1259 const last = this.#controlFlowStack.pop();
1260 CompilerError.invariant(last !== undefined && last.id === scheduleId, {
@@ -1268,7 +1274,7 @@ class Context {
1274 }
1275 }
1276
1271 - /**
1277 + /*
1278 * Helper to unschedule multiple scheduled blocks. The ids should be in
1279 * the order in which they were scheduled, ie most recently scheduled last.
1280 */
@@ -1278,14 +1284,12 @@ class Context {
1284 }
1285 }
1286
1281 - /**
1282 - * Check if the given @param block is scheduled or not.
1283 - */
1287 + // Check if the given @param block is scheduled or not.
1288 isScheduled(block: BlockId): boolean {
1289 return this.#scheduled.has(block) || this.#catchHandlers.has(block);
1290 }
1291
1288 - /**
1292 + /*
1293 * Given the current control flow stack, determines how a `break` to the given @param block
1294 * must be emitted. Returns as follows:
1295 * - 'implicit' if control would implicitly transfer to that block
@@ -1304,12 +1308,16 @@ class Context {
1308 if (target.block === block) {
1309 let type: ControlFlowKind;
1310 if (target.type === "loop") {
1307 - // breaking out of a loop requires an explicit break,
1308 - // but only requires a label if breaking past the innermost loop.
1311 + /*
1312 + * breaking out of a loop requires an explicit break,
1313 + * but only requires a label if breaking past the innermost loop.
1314 + */
1315 type = hasPrecedingLoop ? "labeled" : "unlabeled";
1316 } else if (i === this.#controlFlowStack.length - 1) {
1311 - // breaking to the last break point, which is where control will transfer
1312 - // implicitly
1317 + /*
1318 + * breaking to the last break point, which is where control will transfer
1319 + * implicitly
1320 + */
1321 type = "implicit";
1322 } else {
1323 // breaking somewhere else requires an explicit break
@@ -1325,7 +1333,7 @@ class Context {
1333 return null;
1334 }
1335
1328 - /**
1336 + /*
1337 * Given the current control flow stack, determines how a `continue` to the given @param block
1338 * must be emitted. Returns as follows:
1339 * - 'implicit' if control would implicitly continue to that block
@@ -1344,16 +1352,22 @@ class Context {
1352 if (target.type == "loop" && target.continueBlock === block) {
1353 let type: ControlFlowKind;
1354 if (hasPrecedingLoop) {
1347 - // continuing to a loop that is not the innermost loop always requires
1348 - // a label
1355 + /*
1356 + * continuing to a loop that is not the innermost loop always requires
1357 + * a label
1358 + */
1359 type = "labeled";
1360 } else if (i === this.#controlFlowStack.length - 1) {
1351 - // continuing to the last break point, which is where control will
1352 - // transfer to naturally
1361 + /*
1362 + * continuing to the last break point, which is where control will
1363 + * transfer to naturally
1364 + */
1365 type = "implicit";
1366 } else {
1355 - // the continue is inside some conditional logic, requires an explicit
1356 - // continue
1367 + /*
1368 + * the continue is inside some conditional logic, requires an explicit
1369 + * continue
1370 + */
1371 type = "unlabeled";
1372 }
1373 return {
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/CodegenReactiveFunction.ts
+24 -14
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -141,8 +141,10 @@ class Context {
141 function codegenBlock(cx: Context, block: ReactiveBlock): t.BlockStatement {
142 const temp = new Map(cx.temp);
143 const result = codegenBlockNoReset(cx, block);
144 - // Check that the block only added new temporaries and did not update the
145 - // value of any existing temporary
144 + /*
145 + * Check that the block only added new temporaries and did not update the
146 + * value of any existing temporary
147 + */
148 for (const [key, value] of cx.temp) {
149 if (!temp.has(key)) {
150 continue;
@@ -158,7 +160,7 @@ function codegenBlock(cx: Context, block: ReactiveBlock): t.BlockStatement {
160 return result;
161 }
162
161 -/**
163 +/*
164 * Generates code for the block, without resetting the Context's temporary state.
165 * This should not be used unless it is expected that temporaries from this block
166 * can be referenced later, which is currently only true for sequence expressions
@@ -527,8 +529,10 @@ function codegenTerminal(
529 }
530 if (terminal.kind === "for-of") {
531 return t.forOfStatement(
530 - // Special handling here since we only want the VariableDeclarators without any inits
531 - // This needs to be updated when we handle non-trivial ForOf inits
532 + /*
533 + * Special handling here since we only want the VariableDeclarators without any inits
534 + * This needs to be updated when we handle non-trivial ForOf inits
535 + */
536 createVariableDeclaration(iterableItem.value.loc, varDeclKind, [
537 t.variableDeclarator(lval, null),
538 ]),
@@ -537,8 +541,10 @@ function codegenTerminal(
541 );
542 } else {
543 return t.forInStatement(
540 - // Special handling here since we only want the VariableDeclarators without any inits
541 - // This needs to be updated when we handle non-trivial ForOf inits
544 + /*
545 + * Special handling here since we only want the VariableDeclarators without any inits
546 + * This needs to be updated when we handle non-trivial ForOf inits
547 + */
548 createVariableDeclaration(iterableItem.value.loc, varDeclKind, [
549 t.variableDeclarator(lval, null),
550 ]),
@@ -1195,8 +1201,10 @@ function codegenInstructionValue(
1201 }
1202 case "PropertyLoad": {
1203 const object = codegenPlaceToExpression(cx, instrValue.object);
1198 - // We currently only lower single chains of optional memberexpr.
1199 - // (See BuildHIR.ts for more detail.)
1204 + /*
1205 + * We currently only lower single chains of optional memberexpr.
1206 + * (See BuildHIR.ts for more detail.)
1207 + */
1208 value = t.memberExpression(
1209 object,
1210 t.identifier(instrValue.property),
@@ -1446,10 +1454,12 @@ function codegenJsxAttribute(
1454 break;
1455 }
1456 default: {
1449 - // NOTE JSXFragment is technically allowed as an attribute value per the spec
1450 - // but many tools do not support this case. We emit fragments wrapped in an
1451 - // expression container for compatibility purposes.
1452 - // spec: https://github.com/facebook/jsx/blob/main/AST.md#jsx-attributes
1457 + /*
1458 + * NOTE JSXFragment is technically allowed as an attribute value per the spec
1459 + * but many tools do not support this case. We emit fragments wrapped in an
1460 + * expression container for compatibility purposes.
1461 + * spec: https://github.com/facebook/jsx/blob/main/AST.md#jsx-attributes
1462 + */
1463 value = createJsxExpressionContainer(attribute.place.loc, innerValue);
1464 break;
1465 }
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/CollectReactiveIdentifiers.ts
+11 -7
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -14,8 +14,10 @@ import {
14 import { ReactiveFunctionVisitor, visitReactiveFunction } from "./visitors";
15
16 class Visitor extends ReactiveFunctionVisitor<Set<IdentifierId>> {
17 - // Visitors don't visit lvalues as places by default, but we want to visit all places to
18 - // check for reactivity
17 + /*
18 + * Visitors don't visit lvalues as places by default, but we want to visit all places to
19 + * check for reactivity
20 + */
21 override visitLValue(
22 id: InstructionId,
23 lvalue: Place,
@@ -24,9 +26,11 @@ class Visitor extends ReactiveFunctionVisitor<Set<IdentifierId>> {
26 this.visitPlace(id, lvalue, state);
27 }
28
27 - // This visitor only infers data dependencies and does not account for control dependencies
28 - // where a variable may be assigned a different value based on some conditional, eg via two
29 - // different paths of an if statement.
29 + /*
30 + * This visitor only infers data dependencies and does not account for control dependencies
31 + * where a variable may be assigned a different value based on some conditional, eg via two
32 + * different paths of an if statement.
33 + */
34 override visitPlace(
35 _id: InstructionId,
36 place: Place,
@@ -38,7 +42,7 @@ class Visitor extends ReactiveFunctionVisitor<Set<IdentifierId>> {
42 }
43 }
44
41 -/**
45 +/*
46 * Computes a set of identifiers which are reactive, using the analysis previously performed
47 * in `InferReactivePlaces`.
48 */
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/DeriveMinimalDependencies.ts
+100 -78
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -10,7 +10,7 @@ import { Identifier, ReactiveScopeDependency } from "../HIR";
10 import { printIdentifier } from "../HIR/PrintHIR";
11 import { assertExhaustive } from "../Utils/utils";
12
13 -/**
13 +/*
14 * We need to understand optional member expressions only when determining
15 * dependencies of a ReactiveScope (i.e. in {@link PropagateScopeDependencies}),
16 * hence why this type lives here (not in HIR.ts)
@@ -20,34 +20,34 @@ import { assertExhaustive } from "../Utils/utils";
20 * loaded conditionally.
21 * e.g. the member expr a.b.c?.d.e?.f is represented as
22 * {
23 - * identifier: 'a';
24 - * path: ['b', 'c'],
25 - * optionalPath: ['d', 'e', 'f'].
23 + * identifier: 'a';
24 + * path: ['b', 'c'],
25 + * optionalPath: ['d', 'e', 'f'].
26 * }
27 */
28 export type ReactiveScopePropertyDependency = ReactiveScopeDependency & {
29 optionalPath: Array<string>;
30 };
31
32 -/**
32 +/*
33 * Finalizes a set of ReactiveScopeDependencies to produce a set of minimal unconditional
34 * dependencies, preserving granular accesses when possible.
35 *
36 * Correctness properties:
37 - * - All dependencies to a ReactiveBlock must be tracked.
38 - * We can always truncate a dependency's path to a subpath, due to Forget assuming
39 - * deep immutability. If the value produced by a subpath has not changed, then
40 - * dependency must have not changed.
41 - * i.e. props.a === $[..] implies props.a.b === $[..]
37 + * - All dependencies to a ReactiveBlock must be tracked.
38 + * We can always truncate a dependency's path to a subpath, due to Forget assuming
39 + * deep immutability. If the value produced by a subpath has not changed, then
40 + * dependency must have not changed.
41 + * i.e. props.a === $[..] implies props.a.b === $[..]
42 *
43 - * Note the inverse is not true, but this only means a false positive (we run the
44 - * reactive block more than needed).
45 - * i.e. props.a !== $[..] does not imply props.a.b !== $[..]
43 + * Note the inverse is not true, but this only means a false positive (we run the
44 + * reactive block more than needed).
45 + * i.e. props.a !== $[..] does not imply props.a.b !== $[..]
46 *
47 - * - The dependencies of a finalized ReactiveBlock must be all safe to access
48 - * unconditionally (i.e. preserve program semantics with respect to nullthrows).
49 - * If a dependency is only accessed within a conditional, we must track the nearest
50 - * unconditionally accessed subpath instead.
47 + * - The dependencies of a finalized ReactiveBlock must be all safe to access
48 + * unconditionally (i.e. preserve program semantics with respect to nullthrows).
49 + * If a dependency is only accessed within a conditional, we must track the nearest
50 + * unconditionally accessed subpath instead.
51 * @param initialDeps
52 * @returns
53 */
@@ -84,23 +84,29 @@ export class ReactiveScopeDependencyTree {
84 }
85
86 if (optionalPath.length === 0) {
87 - // If this property does not have a conditional path (i.e. a.b.c), the
88 - // final property node should be marked as an conditional/unconditional
89 - // `dependency` as based on control flow.
87 + /*
88 + * If this property does not have a conditional path (i.e. a.b.c), the
89 + * final property node should be marked as an conditional/unconditional
90 + * `dependency` as based on control flow.
91 + */
92 const depType = inConditional
93 ? PropertyAccessType.ConditionalDependency
94 : PropertyAccessType.UnconditionalDependency;
95
96 currNode.accessType = merge(currNode.accessType, depType);
97 } else {
96 - // Technically, we only depend on whether unconditional path `dep.path`
97 - // is nullish (not its actual value). As long as we preserve the nullthrows
98 - // behavior of `dep.path`, we can keep it as an access (and not promote
99 - // to a dependency).
100 - // See test `reduce-reactive-cond-memberexpr-join` for example.
101 -
102 - // If this property has an optional path (i.e. a?.b.c), all optional
103 - // nodes should be marked accordingly.
98 + /*
99 + * Technically, we only depend on whether unconditional path `dep.path`
100 + * is nullish (not its actual value). As long as we preserve the nullthrows
101 + * behavior of `dep.path`, we can keep it as an access (and not promote
102 + * to a dependency).
103 + * See test `reduce-reactive-cond-memberexpr-join` for example.
104 + */
105 +
106 + /*
107 + * If this property has an optional path (i.e. a?.b.c), all optional
108 + * nodes should be marked accordingly.
109 + */
110 for (const property of optionalPath) {
111 let currChild = getOrMakeProperty(currNode, property);
112 currChild.accessType = merge(
@@ -186,7 +192,7 @@ export class ReactiveScopeDependencyTree {
192 }
193 }
194
189 - /**
195 + /*
196 * Prints dependency tree to string for debugging.
197 * @param includeAccesses
198 * @returns string representation of DependencyTree
@@ -204,24 +210,24 @@ export class ReactiveScopeDependencyTree {
210 }
211 }
212
207 -/**
213 +/*
214 * Enum representing the access type of single property on a parent object.
215 * We distinguish on two independent axes:
216 * Conditional / Unconditional:
211 - * - whether this property is accessed unconditionally (within the ReactiveBlock)
217 + * - whether this property is accessed unconditionally (within the ReactiveBlock)
218 * Access / Dependency:
213 - * - Access: this property is read on the path of a dependency. We do not
214 - * need to track change variables for accessed properties. Tracking accesses
215 - * helps Forget do more granular dependency tracking.
216 - * - Dependency: this property is read as a dependency and we must track changes
217 - * to it for correctness.
219 + * - Access: this property is read on the path of a dependency. We do not
220 + * need to track change variables for accessed properties. Tracking accesses
221 + * helps Forget do more granular dependency tracking.
222 + * - Dependency: this property is read as a dependency and we must track changes
223 + * to it for correctness.
224 *
219 - * ```javascript
220 - * // props.a is a dependency here and must be tracked
221 - * deps: {props.a, props.a.b} ---> minimalDeps: {props.a}
222 - * // props.a is just an access here and does not need to be tracked
223 - * deps: {props.a.b} ---> minimalDeps: {props.a.b}
224 - * ```
225 + * ```javascript
226 + * // props.a is a dependency here and must be tracked
227 + * deps: {props.a, props.a.b} ---> minimalDeps: {props.a}
228 + * // props.a is just an access here and does not need to be tracked
229 + * deps: {props.a.b} ---> minimalDeps: {props.a.b}
230 + * ```
231 */
232 enum PropertyAccessType {
233 ConditionalAccess = "ConditionalAccess",
@@ -252,12 +258,14 @@ function merge(
258 isUnconditional(access1) || isUnconditional(access2);
259 const resultIsDependency = isDependency(access1) || isDependency(access2);
260
255 - // Straightforward merge.
256 - // This can be represented as bitwise OR, but is written out for readability
257 - //
258 - // Observe that `UnconditionalAccess | ConditionalDependency` produces an
259 - // unconditionally accessed conditional dependency. We currently use these
260 - // as we use unconditional dependencies. (i.e. to codegen change variables)
261 + /*
262 + * Straightforward merge.
263 + * This can be represented as bitwise OR, but is written out for readability
264 + *
265 + * Observe that `UnconditionalAccess | ConditionalDependency` produces an
266 + * unconditionally accessed conditional dependency. We currently use these
267 + * as we use unconditional dependencies. (i.e. to codegen change variables)
268 + */
269 if (resultIsUnconditional) {
270 if (resultIsDependency) {
271 return PropertyAccessType.UnconditionalDependency;
@@ -297,7 +305,7 @@ const promoteCondResult = [
305 },
306 ];
307
300 -/**
308 +/*
309 * Recursively calculates minimal dependencies in a subtree.
310 * @param dep DependencyNode representing a dependency subtree.
311 * @returns a minimal list of dependencies in this subtree.
@@ -332,8 +340,10 @@ function deriveMinimalDependenciesInSubtree(
340 // all children are unconditional dependencies, return them to preserve granularity
341 return results;
342 } else {
335 - // at least one child is accessed conditionally, so this node needs to be promoted to
336 - // unconditional dependency
343 + /*
344 + * at least one child is accessed conditionally, so this node needs to be promoted to
345 + * unconditional dependency
346 + */
347 return promoteUncondResult;
348 }
349 }
@@ -345,13 +355,17 @@ function deriveMinimalDependenciesInSubtree(
355 accessType === PropertyAccessType.ConditionalDependency
356 )
357 ) {
348 - // No children are accessed unconditionally, so we cannot promote this node to
349 - // unconditional access.
350 - // Truncate results of child nodes here, since we shouldn't access them anyways
358 + /*
359 + * No children are accessed unconditionally, so we cannot promote this node to
360 + * unconditional access.
361 + * Truncate results of child nodes here, since we shouldn't access them anyways
362 + */
363 return promoteCondResult;
364 } else {
353 - // at least one child is accessed unconditionally, so this node can be promoted to
354 - // unconditional dependency
365 + /*
366 + * at least one child is accessed unconditionally, so this node can be promoted to
367 + * unconditional dependency
368 + */
369 return promoteUncondResult;
370 }
371 }
@@ -364,7 +378,7 @@ function deriveMinimalDependenciesInSubtree(
378 }
379 }
380
367 -/**
381 +/*
382 * Demote all unconditional accesses + dependencies in subtree to the
383 * conditional equivalent, mutating subtree in place.
384 * @param subtree unconditional node representing a subtree of dependencies
@@ -385,15 +399,17 @@ function demoteSubtreeToConditional(subtree: DependencyNode): void {
399
400 for (const childNode of properties.values()) {
401 if (isUnconditional(accessType)) {
388 - // No conditional node can have an unconditional node as a child, so
389 - // we only process childNode if it is unconditional
402 + /*
403 + * No conditional node can have an unconditional node as a child, so
404 + * we only process childNode if it is unconditional
405 + */
406 stack.push(childNode);
407 }
408 }
409 }
410 }
411
396 -/**
412 +/*
413 * Calculates currNode = union(currNode, otherNode), mutating currNode in place
414 * If demoteOtherNode is specified, we demote the subtree represented by
415 * otherNode to conditional access/deps before taking the union.
@@ -428,8 +444,10 @@ function addSubtree(
444 // recursively calculate currChild = union(currChild, otherChild)
445 addSubtree(currChild, otherChild, demoteOtherNode);
446 } else {
431 - // if currChild doesn't exist, we can just move otherChild
432 - // currChild = otherChild.
447 + /*
448 + * if currChild doesn't exist, we can just move otherChild
449 + * currChild = otherChild.
450 + */
451 if (demoteOtherNode) {
452 demoteSubtreeToConditional(otherChild);
453 }
@@ -438,23 +456,23 @@ function addSubtree(
456 }
457 }
458
441 -/**
459 +/*
460 * Adds intersection(otherProperties) to currProperties, mutating
461 * currProperties in place. i.e.
444 - * currProperties = union(currProperties, intersection(otherProperties))
462 + * currProperties = union(currProperties, intersection(otherProperties))
463 *
464 * Used to merge unconditional accesses from exhaustive conditional branches
465 * into the parent ReactiveDeps Tree.
466 * intersection(currProperties) is determined as such:
449 - * - a node is present in the intersection iff it is present in all every
450 - * branch
451 - * - the type of an added node is `UnconditionalDependency` if it is a
452 - * dependency in at least one branch (otherwise `UnconditionalAccess`)
467 + * - a node is present in the intersection iff it is present in all every
468 + * branch
469 + * - the type of an added node is `UnconditionalDependency` if it is a
470 + * dependency in at least one branch (otherwise `UnconditionalAccess`)
471 *
472 * @param otherProperties (read-only) an array of node properties containing
455 - * only unconditionally accessed nodes. Each element represents a
456 - * subtree of reactive dependencies from a single CFG branch.
457 - * otherProperties must represent all reachable branches.
473 + * only unconditionally accessed nodes. Each element represents a
474 + * subtree of reactive dependencies from a single CFG branch.
475 + * otherProperties must represent all reachable branches.
476 * @param currProperties (mutable) return by argument properties of a node
477 *
478 * otherProperties and currProperties must be properties of disjoint nodes
@@ -472,18 +490,22 @@ function addSubtreeIntersection(
490 suggestions: null,
491 });
492
475 - // otherProperties here may contain unconditional nodes as the result of
476 - // recursively merging exhaustively conditional children with unconditionally
477 - // accessed nodes (e.g. in the test condition itself)
478 - // See `reduce-reactive-cond-deps-cfg-nested-testifelse` fixture for example
493 + /*
494 + * otherProperties here may contain unconditional nodes as the result of
495 + * recursively merging exhaustively conditional children with unconditionally
496 + * accessed nodes (e.g. in the test condition itself)
497 + * See `reduce-reactive-cond-deps-cfg-nested-testifelse` fixture for example
498 + */
499
500 for (const [propertyName, currNode] of currProperties) {
501 const otherNodes = mapNonNull(otherProperties, (properties) =>
502 properties.get(propertyName)
503 );
504
485 - // intersection(otherNodes[propertyName]) only exists if each element in
486 - // otherProperties accesses propertyName.
505 + /*
506 + * intersection(otherNodes[propertyName]) only exists if each element in
507 + * otherProperties accesses propertyName.
508 + */
509 if (otherNodes) {
510 addSubtreeIntersection(
511 otherNodes.map((node) => node.properties),
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/ExtractScopeDeclarationsFromDestructuring.ts
+17 -15
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -19,7 +19,7 @@ import {
19 import { eachPatternOperand, mapPatternOperands } from "../HIR/visitors";
20 import { ReactiveFunctionTransform, visitReactiveFunction } from "./visitors";
21
22 -/**
22 +/*
23 * Destructuring statements may sometimes define some variables which are declared by the scope,
24 * and others that are only used locally within the scope, for example:
25 *
@@ -34,13 +34,13 @@ import { ReactiveFunctionTransform, visitReactiveFunction } from "./visitors";
34 * let c_0 = $[0] !== value;
35 * let rest;
36 * if (c_0) {
37 - * // OOPS! we want to reassign `rest` here, but
38 - * // `x` isn't declared anywhere!
39 - * {x, ...rest} = value;
40 - * $[0] = value;
41 - * $[1] = rest;
37 + * // OOPS! we want to reassign `rest` here, but
38 + * // `x` isn't declared anywhere!
39 + * {x, ...rest} = value;
40 + * $[0] = value;
41 + * $[1] = rest;
42 * } else {
43 - * rest = $[1];
43 + * rest = $[1];
44 * }
45 * return rest;
46 * ```
@@ -57,12 +57,12 @@ import { ReactiveFunctionTransform, visitReactiveFunction } from "./visitors";
57 * let c_0 = $[0] !== value;
58 * let rest;
59 * if (c_0) {
60 - * const {x, ...t0} = value; <-- replace `rest` with a temporary
61 - * rest = t0; // <-- and create a separate instruction to assign that to `rest`
62 - * $[0] = value;
63 - * $[1] = rest;
60 + * const {x, ...t0} = value; <-- replace `rest` with a temporary
61 + * rest = t0; // <-- and create a separate instruction to assign that to `rest`
62 + * $[0] = value;
63 + * $[1] = rest;
64 * } else {
65 - * rest = $[1];
65 + * rest = $[1];
66 * }
67 * return rest;
68 * ```
@@ -147,8 +147,10 @@ function transformDestructuring(
147 if (reassigned.size === 0 || !hasDeclaration) {
148 return null;
149 }
150 - // Else it's a mix, replace the reassigned items in the destructuring with temporary
151 - // variables and emit separate assignment statements for them
150 + /*
151 + * Else it's a mix, replace the reassigned items in the destructuring with temporary
152 + * variables and emit separate assignment statements for them
153 + */
154 const instructions: Array<ReactiveInstruction> = [];
155 const renamed: Map<Place, Place> = new Map();
156 mapPatternOperands(destructure.lvalue.pattern, (place) => {
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/FlattenReactiveLoops.ts
+2 -2
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -19,7 +19,7 @@ import {
19 visitReactiveFunction,
20 } from "./visitors";
21
22 -/**
22 +/*
23 * Given a reactive function, flattens any scopes contained within a loop construct.
24 * We won't initially support memoization within loops though this is possible in the future.
25 */
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/FlattenScopesWithHooks.ts
+2 -2
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -20,7 +20,7 @@ import {
20 visitReactiveFunction,
21 } from "./visitors";
22
23 -/**
23 +/*
24 * Most parts of compilation do not treat hooks specially, because there is no guarantee that custom
25 * hooks obey any particular contract. For example, we can't assume that custom hooks won't modify
26 * their arguments, and we can't assume that hooks return immutable or memoized values. Therefore
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/FlattenScopesWithObjectMethods.ts
+1 -1
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/InferReactiveScopeVariables.ts
+30 -20
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -23,17 +23,17 @@ import {
23 import DisjointSet from "../Utils/DisjointSet";
24 import { assertExhaustive } from "../Utils/utils";
25
26 -/**
26 +/*
27 * Note: this is the 1st of 4 passes that determine how to break a function into discrete
28 * reactive scopes (independently memoizeable units of code):
29 * 1. InferReactiveScopeVariables (this pass, on HIR) determines operands that mutate
30 - * together and assigns them a unique reactive scope.
30 + * together and assigns them a unique reactive scope.
31 * 2. AlignReactiveScopesToBlockScopes (on ReactiveFunction) aligns reactive scopes
32 - * to block scopes.
32 + * to block scopes.
33 * 3. MergeOverlappingReactiveScopes (on ReactiveFunction) ensures that reactive
34 - * scopes do not overlap, merging any such scopes.
34 + * scopes do not overlap, merging any such scopes.
35 * 4. BuildReactiveBlocks (on ReactiveFunction) groups the statements for each scope into
36 - * a ReactiveScopeBlock.
36 + * a ReactiveScopeBlock.
37 *
38 * For each mutable variable, infers a reactive scope which will construct that
39 * variable. Variables that co-mutate are assigned to the same reactive scope.
@@ -63,10 +63,10 @@ import { assertExhaustive } from "../Utils/utils";
63 * ## Implementation
64 *
65 * 1. Iterate over all instructions in all blocks (order does not matter, single pass),
66 - * and create disjoint sets ({@link DisjointSet}) for each set of operands that
67 - * mutate together per above rules.
66 + * and create disjoint sets ({@link DisjointSet}) for each set of operands that
67 + * mutate together per above rules.
68 * 2. Iterate the contents of each set, and assign a new {@link ScopeId} to each set,
69 - * and update the `scope` property of each item in that set to that scope id.
69 + * and update the `scope` property of each item in that set to that scope id.
70 *
71 * ## Other Issues Uncovered
72 *
@@ -80,12 +80,16 @@ import { assertExhaustive } from "../Utils/utils";
80 * ```
81 */
82 export function inferReactiveScopeVariables(fn: HIRFunction): void {
83 - // Represents the set of reactive scopes as disjoint sets of identifiers
84 - // that mutate together.
83 + /*
84 + * Represents the set of reactive scopes as disjoint sets of identifiers
85 + * that mutate together.
86 + */
87 const scopeIdentifiers = new DisjointSet<Identifier>();
88 for (const [_, block] of fn.body.blocks) {
87 - // If a phi is mutated after creation, then we need to alias all of its operands such that they
88 - // are assigned to the same scope.
89 + /*
90 + * If a phi is mutated after creation, then we need to alias all of its operands such that they
91 + * are assigned to the same scope.
92 + */
93 for (const phi of block.phis) {
94 if (
95 // The phi was reset because it was not mutated after creation
@@ -141,22 +145,28 @@ export function inferReactiveScopeVariables(fn: HIRFunction): void {
145 for (const operand of eachInstructionOperand(instr)) {
146 if (
147 isMutable(instr, operand) &&
144 - // exclude global variables from being added to scopes, we can't recreate them!
145 - // TODO: improve handling of module-scoped variables and globals
148 + /*
149 + * exclude global variables from being added to scopes, we can't recreate them!
150 + * TODO: improve handling of module-scoped variables and globals
151 + */
152 operand.identifier.mutableRange.start > 0
153 ) {
154 operands.push(operand.identifier);
155 }
156 }
151 - // Ensure that the ComputedLoad to resolve the method is in the same scope as the
152 - // call itself
157 + /*
158 + * Ensure that the ComputedLoad to resolve the method is in the same scope as the
159 + * call itself
160 + */
161 operands.push(instr.value.property.identifier);
162 } else {
163 for (const operand of eachInstructionOperand(instr)) {
164 if (
165 isMutable(instr, operand) &&
158 - // exclude global variables from being added to scopes, we can't recreate them!
159 - // TODO: improve handling of module-scoped variables and globals
166 + /*
167 + * exclude global variables from being added to scopes, we can't recreate them!
168 + * TODO: improve handling of module-scoped variables and globals
169 + */
170 operand.identifier.mutableRange.start > 0
171 ) {
172 operands.push(operand.identifier);
@@ -172,7 +182,7 @@ export function inferReactiveScopeVariables(fn: HIRFunction): void {
182 // Maps each scope (by its identifying member) to a ScopeId value
183 const scopes: Map<Identifier, ReactiveScope> = new Map();
184
175 - /**
185 + /*
186 * Iterate over all the identifiers and assign a unique ScopeId
187 * for each scope (based on the set identifier).
188 *
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/MemoizeFbtOperandsInSameScope.ts
+24 -14
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -19,7 +19,7 @@ import {
19 visitReactiveFunction,
20 } from "./visitors";
21
22 -/**
22 +/*
23 * This pass supports the `fbt` translation system (https://facebook.github.io/fbt/).
24 * FBT provides the `<fbt>` JSX element and `fbt()` calls (which take params in the
25 * form of `<fbt:param>` children or `fbt.param()` arguments, respectively). These
@@ -48,8 +48,10 @@ export const FBT_TAGS: Set<string> = new Set(["fbt", "fbt:param"]);
48 export const SINGLE_CHILD_FBT_TAGS: Set<string> = new Set(["fbt:param"]);
49
50 class Transform extends ReactiveFunctionVisitor<void> {
51 - // Values that represent *potential* references of `fbt` as a JSX tag name
52 - // or as a callee.
51 + /*
52 + * Values that represent *potential* references of `fbt` as a JSX tag name
53 + * or as a callee.
54 + */
55 fbtValues: Set<IdentifierId> = new Set();
56
57 override visitInstruction(
@@ -65,8 +67,10 @@ class Transform extends ReactiveFunctionVisitor<void> {
67 typeof value.value === "string" &&
68 FBT_TAGS.has(value.value)
69 ) {
68 - // We don't distinguish between tag names and strings, so record
69 - // all `fbt` string literals in case they are used as a jsx tag.
70 + /*
71 + * We don't distinguish between tag names and strings, so record
72 + * all `fbt` string literals in case they are used as a jsx tag.
73 + */
74 this.fbtValues.add(lvalue.identifier.id);
75 } else if (value.kind === "LoadGlobal" && FBT_TAGS.has(value.name)) {
76 // Record references to `fbt` as a global
@@ -77,9 +81,11 @@ class Transform extends ReactiveFunctionVisitor<void> {
81 return;
82 }
83
80 - // if the JSX element's tag was `fbt`, mark all its operands
81 - // to ensure that they end up in the same scope as the jsx element
82 - // itself.
84 + /*
85 + * if the JSX element's tag was `fbt`, mark all its operands
86 + * to ensure that they end up in the same scope as the jsx element
87 + * itself.
88 + */
89 for (const operand of eachReactiveValueOperand(value)) {
90 operand.identifier.scope = fbtScope;
91
@@ -97,9 +103,11 @@ class Transform extends ReactiveFunctionVisitor<void> {
103 return;
104 }
105
100 - // if the JSX element's tag was `fbt`, mark all its operands
101 - // to ensure that they end up in the same scope as the jsx element
102 - // itself.
106 + /*
107 + * if the JSX element's tag was `fbt`, mark all its operands
108 + * to ensure that they end up in the same scope as the jsx element
109 + * itself.
110 + */
111 for (const operand of eachReactiveValueOperand(value)) {
112 operand.identifier.scope = fbtScope;
113
@@ -108,8 +116,10 @@ class Transform extends ReactiveFunctionVisitor<void> {
116 Math.min(fbtScope.range.start, operand.identifier.mutableRange.start)
117 );
118
111 - // NOTE: we add the operands as fbt values so that they are also
112 - // grouped with this expression
119 + /*
120 + * NOTE: we add the operands as fbt values so that they are also
121 + * grouped with this expression
122 + */
123 this.fbtValues.add(operand.identifier.id);
124 }
125 }
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/MergeOverlappingReactiveScopes.ts
+42 -36
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -20,17 +20,17 @@ import { retainWhere } from "../Utils/utils";
20 import { getPlaceScope } from "./BuildReactiveBlocks";
21 import { ReactiveFunctionVisitor, visitReactiveFunction } from "./visitors";
22
23 -/**
23 +/*
24 * Note: this is the 3rd of 4 passes that determine how to break a function into discrete
25 * reactive scopes (independently memoizeable units of code):
26 * 1. InferReactiveScopeVariables (on HIR) determines operands that mutate together and assigns
27 - * them a unique reactive scope.
27 + * them a unique reactive scope.
28 * 2. AlignReactiveScopesToBlockScopes (on ReactiveFunction) aligns reactive scopes
29 - * to block scopes.
29 + * to block scopes.
30 * 3. MergeOverlappingReactiveScopes (this pass, on ReactiveFunction) ensures that reactive
31 - * scopes do not overlap, merging any such scopes.
31 + * scopes do not overlap, merging any such scopes.
32 * 4. BuildReactiveBlocks (on ReactiveFunction) groups the statements for each scope into
33 - * a ReactiveScopeBlock.
33 + * a ReactiveScopeBlock.
34 *
35 * Previous passes may leave "overlapping" scopes, ie where one or more instructions are within
36 * the mutable range of multiple reactive scopes. We prefer to avoid executing instructions twice
@@ -47,16 +47,16 @@ import { ReactiveFunctionVisitor, visitReactiveFunction } from "./visitors";
47 *
48 * ```javascript
49 * function foo(cond, a) {
50 - * ⌵ scope for x
51 - * let x = []; ⌝
52 - * if (cond) { ⎮
53 - * ⌵ scope for y ⎮
54 - * let y = []; ⌝ ⎮
55 - * if (b) { ⎮ ⎮
56 - * y.push(b); ⌟ ⎮
57 - * } ⎮
58 - * x.push(<div>{y}</div>); ⎮
59 - * } ⌟
50 + * ⌵ scope for x
51 + * let x = []; ⌝
52 + * if (cond) { ⎮
53 + * ⌵ scope for y ⎮
54 + * let y = []; ⌝ ⎮
55 + * if (b) { ⎮ ⎮
56 + * y.push(b); ⌟ ⎮
57 + * } ⎮
58 + * x.push(<div>{y}</div>); ⎮
59 + * } ⌟
60 * }
61 * ```
62 *
@@ -182,30 +182,36 @@ class Context {
182 let index = this.scopes.length - 1;
183 let nextBlock = currentBlock;
184 while (!nextBlock.seen.has(scope.id)) {
185 - // scopes that cross control-flow boundaries are merged with overlapping
186 - // scopes
185 + /*
186 + * scopes that cross control-flow boundaries are merged with overlapping
187 + * scopes
188 + */
189 this.joinedScopes.union([scope, ...nextBlock.scopes.map((s) => s.scope)]);
190 index--;
191 if (index < 0) {
190 - // TODO: handle reassignments in multiple branches. these create new identifiers that
191 - // add an entry to this.seenScopes but which are then removed when their blocks exit.
192 - // this is also wrong for codegen, different versions of an identifier could be cached
193 - // differently and so a reassigned version of a variable needs a separate declaration.
194 - // console.log(`scope ${scope.id} not found`);
192 + /*
193 + * TODO: handle reassignments in multiple branches. these create new identifiers that
194 + * add an entry to this.seenScopes but which are then removed when their blocks exit.
195 + * this is also wrong for codegen, different versions of an identifier could be cached
196 + * differently and so a reassigned version of a variable needs a separate declaration.
197 + * console.log(`scope ${scope.id} not found`);
198 + */
199
196 - // for (let i = this.scopes.length - 1; i > index; i--) {
197 - // const s = this.scopes[i];
198 - // console.log(
199 - // JSON.stringify(
200 - // {
201 - // seen: Array.from(s.seen),
202 - // scopes: s.scopes,
203 - // },
204 - // null,
205 - // 2
206 - // )
207 - // );
208 - // }
200 + /*
201 + * for (let i = this.scopes.length - 1; i > index; i--) {
202 + * const s = this.scopes[i];
203 + * console.log(
204 + * JSON.stringify(
205 + * {
206 + * seen: Array.from(s.seen),
207 + * scopes: s.scopes,
208 + * },
209 + * null,
210 + * 2
211 + * )
212 + * );
213 + * }
214 + */
215 currentBlock.seen.add(scope.id);
216 currentBlock.scopes.push({ shadowedBy: null, scope });
217 return;
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/MergeReactiveScopesThatInvalidateTogether.ts
+47 -33
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -29,7 +29,7 @@ import {
29 visitReactiveFunction,
30 } from "./visitors";
31
32 -/**
32 +/*
33 * The primary goal of this pass is to reduce memoization overhead, specifically:
34 * - Use fewer memo slots
35 * - Reduce the number of comparisons and other memoization-related instructions
@@ -53,12 +53,12 @@ import {
53 * With that in mind we can apply the optimization in two cases. Given a block with
54 * scope A, some safe-to-memoize instructions I, and scope B, we can merge scopes when:
55 * - A and B have identical dependencies. This means they will invalidate together, so
56 - * by merging the scopes we can avoid duplicate cache slots and duplicate checks of
57 - * those dependencies.
56 + * by merging the scopes we can avoid duplicate cache slots and duplicate checks of
57 + * those dependencies.
58 * - The output of A is the input to B. Any invalidation of A will change its output
59 - * which invalidates B, so we can similarly merge scopes. Note that this optimization
60 - * may not be beneficial if the outupts of A are not guaranteed to change if its input
61 - * changes, but in practice this is generally the case.
59 + * which invalidates B, so we can similarly merge scopes. Note that this optimization
60 + * may not be beneficial if the outupts of A are not guaranteed to change if its input
61 + * changes, but in practice this is generally the case.
62 *
63 * ## Nested Scopes
64 *
@@ -173,12 +173,14 @@ class Transform extends ReactiveFunctionTransform<ReactiveScopeDependencies | nu
173 case "LoadLocal":
174 case "Primitive":
175 case "PropertyLoad": {
176 - // We can merge two scopes if there are intervening instructions, but:
177 - // - Only if the instructions are simple and it's okay to make them
178 - // execute conditionally (hence allowing a conservative subset of value kinds)
179 - // - The values produced are used at or before the next scope. If they are used
180 - // later and we move them into the scope, then they wouldn't be accessible to
181 - // subsequent code wo expanding the set of declarations, which we want to avoid
176 + /*
177 + * We can merge two scopes if there are intervening instructions, but:
178 + * - Only if the instructions are simple and it's okay to make them
179 + * execute conditionally (hence allowing a conservative subset of value kinds)
180 + * - The values produced are used at or before the next scope. If they are used
181 + * later and we move them into the scope, then they wouldn't be accessible to
182 + * subsequent code wo expanding the set of declarations, which we want to avoid
183 + */
184 if (current !== null && instr.instruction.lvalue !== null) {
185 current.lvalues.add(instr.instruction.lvalue.identifier.id);
186 }
@@ -218,17 +220,23 @@ class Transform extends ReactiveFunctionTransform<ReactiveScopeDependencies | nu
220 for (const [key, value] of instr.scope.declarations) {
221 current.scope.scope.declarations.set(key, value);
222 }
221 - // Then prune declarations - this removes declarations from the earlier
222 - // scope that are last-used at or before the newly merged subsequent scope
223 + /*
224 + * Then prune declarations - this removes declarations from the earlier
225 + * scope that are last-used at or before the newly merged subsequent scope
226 + */
227 updateScopeDeclarations(current.scope.scope, this.lastUsage);
228 current.to = i + 1;
225 - // We already checked that intermediate values were used at-or-before the merged
226 - // scoped, so we can reset
229 + /*
230 + * We already checked that intermediate values were used at-or-before the merged
231 + * scoped, so we can reset
232 + */
233 current.lvalues.clear();
234
235 if (!scopeAlwaysInvalidatesOnDependencyChanges(instr)) {
230 - // The subsequent scope that we just merged isn't guaranteed to invalidate if its
231 - // inputs change, so it is not a candidate for future merging
236 + /*
237 + * The subsequent scope that we just merged isn't guaranteed to invalidate if its
238 + * inputs change, so it is not a candidate for future merging
239 + */
240 log(
241 ` but scope @${instr.scope.id} doesnt guaranteed invalidate so it cannot merge further`
242 );
@@ -319,7 +327,7 @@ class Transform extends ReactiveFunctionTransform<ReactiveScopeDependencies | nu
327 }
328 }
329
322 -/**
330 +/*
331 * Updates @param scope's declarations to remove any declarations that are not
332 * used after the scope, based on the scope's updated range post-merging.
333 */
@@ -335,7 +343,7 @@ function updateScopeDeclarations(
343 }
344 }
345
338 -/**
346 +/*
347 * Returns whether the given @param scope is the last usage of all
348 * the given @param lvalues. Returns false if any of the lvalues
349 * are used again after the scope.
@@ -366,13 +374,15 @@ function canMergeScopes(a: ReactiveScopeBlock, b: ReactiveScopeBlock): boolean {
374 log(` canMergeScopes: dependencies are equal`);
375 return true;
376 }
369 - // Merge scopes where the outputs of the previous scope are the inputs
370 - // of the subsequent scope. Note that the output of a scope is not
371 - // guaranteed to change when its inputs change, for example `foo(x)`
372 - // may not change when `x` changes, for example `foo(x) { return x < 10}`
373 - // will not change as x changes from 0 -> 1.
374 - // Therefore we check that the outputs of the previous scope are of a type
375 - // that is guaranteed to invalidate with its inputs, and only merge in this case.
377 + /*
378 + * Merge scopes where the outputs of the previous scope are the inputs
379 + * of the subsequent scope. Note that the output of a scope is not
380 + * guaranteed to change when its inputs change, for example `foo(x)`
381 + * may not change when `x` changes, for example `foo(x) { return x < 10}`
382 + * will not change as x changes from 0 -> 1.
383 + * Therefore we check that the outputs of the previous scope are of a type
384 + * that is guaranteed to invalidate with its inputs, and only merge in this case.
385 + */
386 if (
387 areEqualDependencies(
388 new Set(
@@ -455,8 +465,10 @@ class DeclarationTypeVisitor extends ReactiveFunctionVisitor<void> {
465 instruction.lvalue === null ||
466 !this.scope.declarations.has(instruction.lvalue.identifier.id)
467 ) {
458 - // no lvalue or this instruction isn't directly constructing a
459 - // scope output value, skip
468 + /*
469 + * no lvalue or this instruction isn't directly constructing a
470 + * scope output value, skip
471 + */
472 log(
473 ` skip instruction lvalue=${
474 instruction.lvalue?.identifier.id
@@ -473,9 +485,11 @@ class DeclarationTypeVisitor extends ReactiveFunctionVisitor<void> {
485 case "JsxExpression":
486 case "JsxFragment":
487 case "ObjectExpression": {
476 - // These instruction types *always* allocate. If they execute
477 - // they will produce a new value, triggering downstream reactive
478 - // updates
488 + /*
489 + * These instruction types *always* allocate. If they execute
490 + * they will produce a new value, triggering downstream reactive
491 + * updates
492 + */
493 this.alwaysInvalidatesOnInputChange = true;
494 break;
495 }
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/PrintReactiveFunction.ts
+1 -1
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/PromoteUsedTemporaries.ts
+8 -6
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -31,11 +31,13 @@ class Visitor extends ReactiveFunctionVisitor<VisitorState> {
31 promoteTemporary(identifier, state);
32 }
33 }
34 - // This is technically optional. We could prune ReactiveScopes
35 - // whose outputs are not used in another computation or return
36 - // value.
37 - // Many of our current test fixtures do not return a value, so
38 - // it is better for now to promote (and memoize) every output.
34 + /*
35 + * This is technically optional. We could prune ReactiveScopes
36 + * whose outputs are not used in another computation or return
37 + * value.
38 + * Many of our current test fixtures do not return a value, so
39 + * it is better for now to promote (and memoize) every output.
40 + */
41 for (const [, declaration] of block.scope.declarations) {
42 if (declaration.identifier.name == null) {
43 promoteTemporary(declaration.identifier, state);
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/PropagateScopeDependencies.ts
+101 -67
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -36,7 +36,7 @@ import {
36 } from "./DeriveMinimalDependencies";
37 import { ReactiveFunctionVisitor, visitReactiveFunction } from "./visitors";
38
39 -/**
39 +/*
40 * Infers the dependencies of each scope to include variables whose values
41 * are non-stable and created prior to the start of the scope. Also propagates
42 * dependencies upwards, so that parent scope dependencies are the union of
@@ -67,8 +67,10 @@ export function propagateScopeDependencies(fn: ReactiveFunction): void {
67 }
68
69 type TemporariesUsedOutsideDefiningScope = {
70 - // tracks all relevant temporary declarations (currently LoadLocal and PropertyLoad)
71 - // and the scope where they are defined
70 + /*
71 + * tracks all relevant temporary declarations (currently LoadLocal and PropertyLoad)
72 + * and the scope where they are defined
73 + */
74 declarations: Map<IdentifierId, ReactiveScope>;
75 // temporaries used outside of their defining scope
76 usedOutsideDeclaringScope: Set<IdentifierId>;
@@ -136,18 +138,22 @@ class Context {
138 // Reactive dependencies used in the current reactive scope.
139 #dependencies: ReactiveScopeDependencyTree =
140 new ReactiveScopeDependencyTree();
139 - // We keep a sidemap for temporaries created by PropertyLoads, and do
140 - // not store any control flow (i.e. #inConditionalWithinScope) here.
141 - // - a ReactiveScope (A) containing a PropertyLoad may differ from the
142 - // ReactiveScope (B) that uses the produced temporary.
143 - // - codegen will inline these PropertyLoads back into scope (B)
141 + /*
142 + * We keep a sidemap for temporaries created by PropertyLoads, and do
143 + * not store any control flow (i.e. #inConditionalWithinScope) here.
144 + * - a ReactiveScope (A) containing a PropertyLoad may differ from the
145 + * ReactiveScope (B) that uses the produced temporary.
146 + * - codegen will inline these PropertyLoads back into scope (B)
147 + */
148 #properties: Map<Identifier, ReactiveScopePropertyDependency> = new Map();
149 #temporaries: Map<Identifier, Place> = new Map();
150 #inConditionalWithinScope: boolean = false;
147 - // Reactive dependencies used unconditionally in the current conditional.
148 - // Composed of dependencies:
149 - // - directly accessed within block (added in visitDep)
150 - // - accessed by all cfg branches (added through promoteDeps)
151 + /*
152 + * Reactive dependencies used unconditionally in the current conditional.
153 + * Composed of dependencies:
154 + * - directly accessed within block (added in visitDep)
155 + * - accessed by all cfg branches (added through promoteDeps)
156 + */
157 #depsInCurrentConditional: ReactiveScopeDependencyTree =
158 new ReactiveScopeDependencyTree();
159 #scopes: Stack<ReactiveScope> = empty();
@@ -161,10 +167,12 @@ class Context {
167 const prevInConditional = this.#inConditionalWithinScope;
168 const previousDependencies = this.#dependencies;
169
164 - // Set context for new scope
165 - // A nested scope should add all deps it directly uses as its own
166 - // unconditional deps, regardless of whether the nested scope is itself
167 - // within a conditional
170 + /*
171 + * Set context for new scope
172 + * A nested scope should add all deps it directly uses as its own
173 + * unconditional deps, regardless of whether the nested scope is itself
174 + * within a conditional
175 + */
176 const scopedDependencies = new ReactiveScopeDependencyTree();
177 this.#inConditionalWithinScope = false;
178 this.#dependencies = scopedDependencies;
@@ -181,10 +189,12 @@ class Context {
189 const minInnerScopeDependencies =
190 scopedDependencies.deriveMinimalDependencies();
191
184 - // propagate dependencies upward using the same rules as normal dependency
185 - // collection. child scopes may have dependencies on values created within
186 - // the outer scope, which necessarily cannot be dependencies of the outer
187 - // scope
192 + /*
193 + * propagate dependencies upward using the same rules as normal dependency
194 + * collection. child scopes may have dependencies on values created within
195 + * the outer scope, which necessarily cannot be dependencies of the outer
196 + * scope
197 + */
198 this.#dependencies.addDepsFromInnerScope(
199 scopedDependencies,
200 this.#inConditionalWithinScope,
@@ -197,7 +207,7 @@ class Context {
207 return this.#temporariesUsedOutsideScope.has(place.identifier.id);
208 }
209
200 - /**
210 + /*
211 * Prints dependency tree to string for debugging.
212 * @param includeAccesses
213 * @returns string representation of DependencyTree
@@ -206,7 +216,7 @@ class Context {
216 return this.#dependencies.printDeps(includeAccesses);
217 }
218
209 - /**
219 + /*
220 * We track and return unconditional accesses / deps within this conditional.
221 * If an object property is always used (i.e. in every conditional path), we
222 * want to promote it to an unconditional access / dependency.
@@ -215,11 +225,11 @@ class Context {
225 * i.e. call promoteDepsFromExhaustiveConditionals to merge returned results.
226 *
227 * e.g. we want to mark props.a.b as an unconditional dep here
218 - * if (foo(...)) {
219 - * access(props.a.b);
220 - * } else {
221 - * access(props.a.b);
222 - * }
228 + * if (foo(...)) {
229 + * access(props.a.b);
230 + * } else {
231 + * access(props.a.b);
232 + * }
233 */
234 enterConditional(fn: () => void): ReactiveScopeDependencyTree {
235 const prevInConditional = this.#inConditionalWithinScope;
@@ -233,7 +243,7 @@ class Context {
243 return result;
244 }
245
236 - /**
246 + /*
247 * Add dependencies from exhaustive CFG paths into the current ReactiveDeps
248 * tree. If a property is used in every CFG path, it is promoted to an
249 * unconditional access / dependency here.
@@ -250,7 +260,7 @@ class Context {
260 );
261 }
262
253 - /**
263 + /*
264 * Records where a value was declared, and optionally, the scope where the value originated from.
265 * This is later used to determine if a dependency should be added to a scope; if the current
266 * scope we are visiting is the same scope where the value originates, it can't be a dependency
@@ -279,8 +289,10 @@ class Context {
289 const resolvedObject = this.resolveTemporary(object);
290 const resolvedDependency = this.#properties.get(resolvedObject.identifier);
291 let objectDependency: ReactiveScopePropertyDependency;
282 - // (1) Create the base property dependency as either a LoadLocal (from a temporary)
283 - // or a deep copy of an existing property dependency.
292 + /*
293 + * (1) Create the base property dependency as either a LoadLocal (from a temporary)
294 + * or a deep copy of an existing property dependency.
295 + */
296 if (resolvedDependency === undefined) {
297 objectDependency = {
298 identifier: resolvedObject.identifier,
@@ -297,10 +309,12 @@ class Context {
309
310 // (2) Determine whether property is an optional access
311 if (objectDependency.optionalPath.length > 0) {
300 - // If the base property dependency represents a optional member expression,
301 - // property is on the optionalPath (regardless of whether this PropertyLoad
302 - // itself was conditional)
303 - // e.g. for `a.b?.c.d`, `d` should be added to optionalPath
312 + /*
313 + * If the base property dependency represents a optional member expression,
314 + * property is on the optionalPath (regardless of whether this PropertyLoad
315 + * itself was conditional)
316 + * e.g. for `a.b?.c.d`, `d` should be added to optionalPath
317 + */
318 objectDependency.optionalPath.push(property);
319 } else if (isConditional) {
320 objectDependency.optionalPath.push(property);
@@ -331,15 +345,19 @@ class Context {
345 return false;
346 }
347
334 - // object methods are not deps because they will be codegen'ed back in to
335 - // the object literal.
348 + /*
349 + * object methods are not deps because they will be codegen'ed back in to
350 + * the object literal.
351 + */
352 if (isObjectMethodType(maybeDependency.identifier)) {
353 return false;
354 }
355
356 const identifier = maybeDependency.identifier;
341 - // If this operand is used in a scope, has a dynamic value, and was defined
342 - // before this scope, then its a dependency of the scope.
357 + /*
358 + * If this operand is used in a scope, has a dynamic value, and was defined
359 + * before this scope, then its a dependency of the scope.
360 + */
361 const currentDeclaration =
362 this.#reassignments.get(identifier) ??
363 this.#declarations.get(identifier.id);
@@ -366,8 +384,10 @@ class Context {
384
385 visitOperand(place: Place): void {
386 const resolved = this.resolveTemporary(place);
369 - // if this operand is a temporary created for a property load, try to resolve it to
370 - // the expanded Place. Fall back to using the operand as-is.
387 + /*
388 + * if this operand is a temporary created for a property load, try to resolve it to
389 + * the expanded Place. Fall back to using the operand as-is.
390 + */
391
392 let dependency: ReactiveScopePropertyDependency = {
393 identifier: resolved.identifier,
@@ -389,14 +409,18 @@ class Context {
409 }
410
411 visitDependency(maybeDependency: ReactiveScopePropertyDependency): void {
392 - // Any value used after its originally defining scope has concluded must be added as an
393 - // output of its defining scope. Regardless of whether its a const or not,
394 - // some later code needs access to the value. If the current
395 - // scope we are visiting is the same scope where the value originates, it can't be a dependency
396 - // on itself.
397 -
398 - // if originalDeclaration is undefined here, then this is a free var
399 - // (all other decls e.g. `let x;` should be initialized in BuildHIR)
412 + /*
413 + * Any value used after its originally defining scope has concluded must be added as an
414 + * output of its defining scope. Regardless of whether its a const or not,
415 + * some later code needs access to the value. If the current
416 + * scope we are visiting is the same scope where the value originates, it can't be a dependency
417 + * on itself.
418 + */
419 +
420 + /*
421 + * if originalDeclaration is undefined here, then this is a free var
422 + * (all other decls e.g. `let x;` should be initialized in BuildHIR)
423 + */
424 const originalDeclaration = this.#declarations.get(
425 maybeDependency.identifier.id
426 );
@@ -416,13 +440,15 @@ class Context {
440
441 if (this.#checkValidDependency(maybeDependency)) {
442 this.#depsInCurrentConditional.add(maybeDependency, true);
419 - // Add info about this dependency to the existing tree
420 - // We do not try to join/reduce dependencies here due to missing info
443 + /*
444 + * Add info about this dependency to the existing tree
445 + * We do not try to join/reduce dependencies here due to missing info
446 + */
447 this.#dependencies.add(maybeDependency, this.#inConditionalWithinScope);
448 }
449 }
450
425 - /**
451 + /*
452 * Record a variable that is declared in some other scope and that is being reassigned in the
453 * current one as a {@link ReactiveScope.reassignments}
454 */
@@ -470,9 +496,11 @@ class PropagationVisitor extends ReactiveFunctionVisitor<Context> {
496 switch (value.kind) {
497 case "OptionalExpression": {
498 const inner = value.value;
473 - // OptionalExpression value is a SequenceExpression where the instructions
474 - // represent the code prior to the `?` and the final value represents the
475 - // conditional code that follows.
499 + /*
500 + * OptionalExpression value is a SequenceExpression where the instructions
501 + * represent the code prior to the `?` and the final value represents the
502 + * conditional code that follows.
503 + */
504 CompilerError.invariant(inner.kind === "SequenceExpression", {
505 reason:
506 "Expected OptionalExpression value to be a SequenceExpression",
@@ -562,14 +590,18 @@ class PropagationVisitor extends ReactiveFunctionVisitor<Context> {
590 value.kind === "DeclareLocal" ||
591 value.kind === "DeclareContext"
592 ) {
565 - // Some variables may be declared and never initialized. We need
566 - // to retain (and hoist) these declarations if they are included
567 - // in a reactive scope. One approach is to simply add all `DeclareLocal`s
568 - // as scope declarations.
569 -
570 - // We add context variable declarations here, not at `StoreContext`, since
571 - // context Store / Loads are modeled as reads and mutates to the underlying
572 - // variable reference (instead of through intermediate / inlined temporaries)
593 + /*
594 + * Some variables may be declared and never initialized. We need
595 + * to retain (and hoist) these declarations if they are included
596 + * in a reactive scope. One approach is to simply add all `DeclareLocal`s
597 + * as scope declarations.
598 + */
599 +
600 + /*
601 + * We add context variable declarations here, not at `StoreContext`, since
602 + * context Store / Loads are modeled as reads and mutates to the underlying
603 + * variable reference (instead of through intermediate / inlined temporaries)
604 + */
605 context.declare(value.lvalue.place.identifier, {
606 id,
607 scope: context.currentScope,
@@ -665,9 +697,11 @@ class PropagationVisitor extends ReactiveFunctionVisitor<Context> {
697 context.visitOperand(terminal.test);
698 const depsInCases = [];
699 let foundDefault = false;
668 - // This can underestimate unconditional accesses due to the current
669 - // CFG representation for fallthrough. This is safe. It only
670 - // reduces granularity of dependencies.
700 + /*
701 + * This can underestimate unconditional accesses due to the current
702 + * CFG representation for fallthrough. This is safe. It only
703 + * reduces granularity of dependencies.
704 + */
705 for (const { test, block } of terminal.cases) {
706 if (test !== null) {
707 context.visitOperand(test);
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/PruneAllReactiveScopes.ts
+2 -2
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -16,7 +16,7 @@ import {
16 visitReactiveFunction,
17 } from "./visitors";
18
19 -/**
19 +/*
20 * Removes *all* reactive scopes. Intended for experimentation only, to allow
21 * accurately removing memoization using the compiler pipeline to get a baseline
22 * for performance of a product without memoization applied.
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/PruneHoistedContexts.ts
+2 -2
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -18,7 +18,7 @@ import {
18 visitReactiveFunction,
19 } from "./visitors";
20
21 -/**
21 +/*
22 * Prunes DeclareContexts lowered for HoistedConsts, and transforms any references back to its
23 * original instruction kind.
24 */
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/PruneNonEscapingScopes.ts
+110 -84
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -36,85 +36,87 @@ import {
36 visitReactiveFunction,
37 } from "./visitors";
38
39 -/**
39 +/*
40 * This pass prunes reactive scopes that are not necessary to bound downstream computation.
41 * Specifically, the pass identifies the set of identifiers which may "escape". Values can
42 * escape in one of two ways:
43 * * They are directly returned by the function and/or transitively aliased by a return
44 - * value.
44 + * value.
45 * * They are passed as input to a hook. This is because any value passed to a hook may
46 - * have its referenced ultimately stored by React (ie, be aliased by an external value).
47 - * For example, the closure passed to useEffect escapes.
46 + * have its referenced ultimately stored by React (ie, be aliased by an external value).
47 + * For example, the closure passed to useEffect escapes.
48 *
49 * Example to build intuition:
50 *
51 * ```javascript
52 * function Component(props) {
53 - * const a = {}; // not aliased or returned: *not* memoized
54 - * const b = {}; // aliased by c, which is returned: memoized
55 - * const c = [b]; // directly returned: memoized
56 - * return c;
53 + * const a = {}; // not aliased or returned: *not* memoized
54 + * const b = {}; // aliased by c, which is returned: memoized
55 + * const c = [b]; // directly returned: memoized
56 + * return c;
57 * }
58 * ```
59 *
60 * However, this logic alone is insufficient for two reasons:
61 * - Statically memoizing JSX elements *may* be inefficient compared to using dynamic
62 - * memoization with `React.memo()`. Static memoization may be JIT'd and can look at
63 - * the precise props w/o dynamic iteration, but incurs potentially large code-size
64 - * overhead. Dynamic memoization with `React.memo()` incurs potentially increased
65 - * runtime overhead for smaller code size. We plan to experiment with both variants
66 - * for JSX.
62 + * memoization with `React.memo()`. Static memoization may be JIT'd and can look at
63 + * the precise props w/o dynamic iteration, but incurs potentially large code-size
64 + * overhead. Dynamic memoization with `React.memo()` incurs potentially increased
65 + * runtime overhead for smaller code size. We plan to experiment with both variants
66 + * for JSX.
67 * - Because we merge values whose mutations _interleave_ into a single scope, there
68 - * can be cases where a non-escaping value needs to be memoized anyway to avoid breaking
69 - * a memoization input. As a rule, for any scope that has a memoized output, all of that
70 - * scope's transitive dependencies must also be memoized _even if they don't escape_.
71 - * Failing to memoize them would cause the scope to invalidate more often than necessary
72 - * and break downstream memoization.
68 + * can be cases where a non-escaping value needs to be memoized anyway to avoid breaking
69 + * a memoization input. As a rule, for any scope that has a memoized output, all of that
70 + * scope's transitive dependencies must also be memoized _even if they don't escape_.
71 + * Failing to memoize them would cause the scope to invalidate more often than necessary
72 + * and break downstream memoization.
73 *
74 * Example of this second case:
75 *
76 * ```javascript
77 * function Component(props) {
78 - * // a can be independently memoized but it doesn't escape, so naively we may think its
79 - * // safe to not memoize. but not memoizing would break caching of b, which does
80 - * // escape.
81 - * const a = [props.a];
78 + * // a can be independently memoized but it doesn't escape, so naively we may think its
79 + * // safe to not memoize. but not memoizing would break caching of b, which does
80 + * // escape.
81 + * const a = [props.a];
82 *
83 - * // b and c are interleaved and grouped into a single scope,
84 - * // but they are independent values. c does not escape, but
85 - * // we need to ensure that a is memoized or else b will invalidate
86 - * // on every render since a is a dependency.
87 - * const b = [];
88 - * const c = {};
89 - * c.a = a;
90 - * b.push(props.b);
83 + * // b and c are interleaved and grouped into a single scope,
84 + * // but they are independent values. c does not escape, but
85 + * // we need to ensure that a is memoized or else b will invalidate
86 + * // on every render since a is a dependency.
87 + * const b = [];
88 + * const c = {};
89 + * c.a = a;
90 + * b.push(props.b);
91 *
92 - * return b;
92 + * return b;
93 * }
94 * ```
95 *
96 * ## Algorithm
97 *
98 * 1. First we build up a graph, a mapping of IdentifierId to a node describing all the
99 - * scopes and inputs involved in creating that identifier. Individual nodes are marked
100 - * as definitely aliased, conditionally aliased, or unaliased:
101 - * a. Arrays, objects, function calls all produce a new value and are always marked as aliased
102 - * b. Conditional and logical expressions (and a few others) are conditinally aliased,
103 - * depending on whether their result value is aliased.
104 - * c. JSX is always unaliased (though its props children may be)
99 + * scopes and inputs involved in creating that identifier. Individual nodes are marked
100 + * as definitely aliased, conditionally aliased, or unaliased:
101 + * a. Arrays, objects, function calls all produce a new value and are always marked as aliased
102 + * b. Conditional and logical expressions (and a few others) are conditinally aliased,
103 + * depending on whether their result value is aliased.
104 + * c. JSX is always unaliased (though its props children may be)
105 * 2. The same pass which builds the graph also stores the set of returned identifiers and set of
106 - * identifiers passed as arguments to hooks.
106 + * identifiers passed as arguments to hooks.
107 * 3. We traverse the graph starting from the returned identifiers and mark reachable dependencies
108 - * as escaping, based on the combination of the parent node's type and its children (eg a
109 - * conditional node with an aliased dep promotes to aliased).
108 + * as escaping, based on the combination of the parent node's type and its children (eg a
109 + * conditional node with an aliased dep promotes to aliased).
110 * 4. Finally we prune scopes whose outputs weren't marked.
111 */
112 export function pruneNonEscapingScopes(
113 fn: ReactiveFunction,
114 options: MemoizationOptions
115 ): void {
116 - // First build up a map of which instructions are involved in creating which values,
117 - // and which values are returned.
116 + /*
117 + * First build up a map of which instructions are involved in creating which values,
118 + * and which values are returned.
119 + */
120 const state = new State(fn.env);
121 for (const param of fn.params) {
122 if (param.kind === "Identifier") {
@@ -131,8 +133,10 @@ export function pruneNonEscapingScopes(
133
134 log(() => prettyFormat(state));
135
134 - // Then walk outward from the returned values and find all captured operands.
135 - // This forms the set of identifiers which should be memoized.
136 + /*
137 + * Then walk outward from the returned values and find all captured operands.
138 + * This forms the set of identifiers which should be memoized.
139 + */
140 const memoized = computeMemoizedIdentifiers(state);
141
142 log(() => prettyFormat(memoized));
@@ -150,18 +154,24 @@ export type MemoizationOptions = {
154 enum MemoizationLevel {
155 // The value should be memoized if it escapes
156 Memoized = "Memoized",
153 - // Values that are memoized if their dependencies are memoized (used for logical/ternary and
154 - // other expressions that propagate dependencies wo changing them)
157 + /*
158 + * Values that are memoized if their dependencies are memoized (used for logical/ternary and
159 + * other expressions that propagate dependencies wo changing them)
160 + */
161 Conditional = "Conditional",
156 - // Values that cannot be compared with Object.is, but which by default don't need to be memoized
157 - // unless forced
162 + /*
163 + * Values that cannot be compared with Object.is, but which by default don't need to be memoized
164 + * unless forced
165 + */
166 Unmemoized = "Unmemoized",
167 // The value will never be memoized: used for values that can be cheaply compared w Object.is
168 Never = "Never",
169 }
170
163 -// Given an identifier that appears as an lvalue multiple times with different memoization levels,
164 -// determines the final memoization level.
171 +/*
172 + * Given an identifier that appears as an lvalue multiple times with different memoization levels,
173 + * determines the final memoization level.
174 + */
175 function joinAliases(
176 kind1: MemoizationLevel,
177 kind2: MemoizationLevel
@@ -204,8 +214,10 @@ type ScopeNode = {
214 // Stores the identifier and scope graphs, set of returned identifiers, etc
215 class State {
216 env: Environment;
207 - // Maps lvalues for LoadLocal to the identifier being loaded, to resolve indirections
208 - // in subsequent lvalues/rvalues
217 + /*
218 + * Maps lvalues for LoadLocal to the identifier being loaded, to resolve indirections
219 + * in subsequent lvalues/rvalues
220 + */
221 definitions: Map<IdentifierId, IdentifierId> = new Map();
222
223 identifiers: Map<IdentifierId, IdentifierNode> = new Map();
@@ -216,9 +228,7 @@ class State {
228 this.env = env;
229 }
230
219 - /**
220 - * Declare a new identifier, used for function id and params
221 - */
231 + // Declare a new identifier, used for function id and params
232 declare(id: IdentifierId): void {
233 this.identifiers.set(id, {
234 level: MemoizationLevel.Never,
@@ -229,7 +239,7 @@ class State {
239 });
240 }
241
232 - /**
242 + /*
243 * Associates the identifier with its scope, if there is one and it is active for the given instruction id:
244 * - Records the scope and its dependencies
245 * - Associates the identifier with this scope
@@ -261,7 +271,7 @@ class State {
271 }
272 }
273
264 -/**
274 +/*
275 * Given a state derived from visiting the function, walks the graph from the returned nodes
276 * to determine which other values should be memoized. Returns a set of all identifiers
277 * that should be memoized.
@@ -283,8 +293,10 @@ function computeMemoizedIdentifiers(state: State): Set<IdentifierId> {
293 }
294 node.seen = true;
295
286 - // Note: in case of cycles we temporarily mark the identifier as non-memoized,
287 - // this is reset later after processing dependencies
296 + /*
297 + * Note: in case of cycles we temporarily mark the identifier as non-memoized,
298 + * this is reset later after processing dependencies
299 + */
300 node.memoized = false;
301
302 // Visit dependencies, determine if any of them are memoized
@@ -342,7 +354,7 @@ type LValueMemoization = {
354 level: MemoizationLevel;
355 };
356
345 -/**
357 +/*
358 * Given a value, returns a description of how it should be memoized:
359 * - lvalues: optional extra places that are lvalue-like in the sense of
360 * aliasing the rvalues
@@ -396,9 +408,11 @@ function computeMemoizationInputs(
408 lvalue !== null
409 ? [{ place: lvalue, level: MemoizationLevel.Conditional }]
410 : [],
399 - // Only the final value of the sequence is a true rvalue:
400 - // values from the sequence's instructions are evaluated
401 - // as separate nodes
411 + /*
412 + * Only the final value of the sequence is a true rvalue:
413 + * values from the sequence's instructions are evaluated
414 + * as separate nodes
415 + */
416 rvalues: computeMemoizationInputs(env, value.value, null, options)
417 .rvalues,
418 };
@@ -424,8 +438,10 @@ function computeMemoizationInputs(
438 ? MemoizationLevel.Memoized
439 : MemoizationLevel.Unmemoized;
440 return {
427 - // JSX elements themselves are not memoized unless forced to
428 - // avoid breaking downstream memoization
441 + /*
442 + * JSX elements themselves are not memoized unless forced to
443 + * avoid breaking downstream memoization
444 + */
445 lvalues: lvalue !== null ? [{ place: lvalue, level }] : [],
446 rvalues: operands,
447 };
@@ -435,8 +451,10 @@ function computeMemoizationInputs(
451 ? MemoizationLevel.Memoized
452 : MemoizationLevel.Unmemoized;
453 return {
438 - // JSX elements themselves are not memoized unless forced to
439 - // avoid breaking downstream memoization
454 + /*
455 + * JSX elements themselves are not memoized unless forced to
456 + * avoid breaking downstream memoization
457 + */
458 lvalues: lvalue !== null ? [{ place: lvalue, level }] : [],
459 rvalues: value.children,
460 };
@@ -578,14 +596,18 @@ function computeMemoizationInputs(
596 lvalue !== null
597 ? [{ place: lvalue, level: MemoizationLevel.Conditional }]
598 : [],
581 - // Only the object is aliased to the result, and the result only needs to be
582 - // memoized if the object is
599 + /*
600 + * Only the object is aliased to the result, and the result only needs to be
601 + * memoized if the object is
602 + */
603 rvalues: [value.object],
604 };
605 }
606 case "ComputedStore": {
587 - // The object being stored to acts as an lvalue (it aliases the value), but
588 - // the computed key is not aliased
607 + /*
608 + * The object being stored to acts as an lvalue (it aliases the value), but
609 + * the computed key is not aliased
610 + */
611 const lvalues = [
612 { place: value.object, level: MemoizationLevel.Conditional },
613 ];
@@ -670,8 +692,10 @@ function computeMemoizationInputs(
692 case "NewExpression":
693 case "ObjectExpression":
694 case "PropertyStore": {
673 - // All of these instructions may produce new values which must be memoized if
674 - // reachable from a return value. Any mutable rvalue may alias any other rvalue
695 + /*
696 + * All of these instructions may produce new values which must be memoized if
697 + * reachable from a return value. Any mutable rvalue may alias any other rvalue
698 + */
699 const operands = [...eachReactiveValueOperand(value)];
700 const lvalues = operands
701 .filter((operand) => isMutableEffect(operand.effect, operand.loc))
@@ -737,7 +761,7 @@ function computePatternLValues(pattern: Pattern): Array<LValueMemoization> {
761 return lvalues;
762 }
763
740 -/**
764 +/*
765 * Populates the input state with the set of returned identifiers and information about each
766 * identifier's and scope's dependencies.
767 */
@@ -788,8 +812,10 @@ class CollectDependenciesVisitor extends ReactiveFunctionVisitor<State> {
812 state.identifiers.set(lvalueId, node);
813 }
814 node.level = joinAliases(node.level, level);
791 - // This looks like NxM iterations but in practice all instructions with multiple
792 - // lvalues have only a single rvalue
815 + /*
816 + * This looks like NxM iterations but in practice all instructions with multiple
817 + * lvalues have only a single rvalue
818 + */
819 for (const operand of aliasing.rvalues) {
820 const operandId =
821 state.definitions.get(operand.identifier.id) ?? operand.identifier.id;
@@ -814,10 +840,12 @@ class CollectDependenciesVisitor extends ReactiveFunctionVisitor<State> {
840 this.env,
841 instruction.value.callee.identifier.type
842 );
817 - // Hook values are assumed to escape by default since they can be inputs
818 - // to reactive scopes in the hook. However if the hook is annotated as
819 - // noAlias we know that the arguments cannot escape and don't need to
820 - // be memoized.
843 + /*
844 + * Hook values are assumed to escape by default since they can be inputs
845 + * to reactive scopes in the hook. However if the hook is annotated as
846 + * noAlias we know that the arguments cannot escape and don't need to
847 + * be memoized.
848 + */
849 if (signature && signature.noAlias === true) {
850 return;
851 }
@@ -841,9 +869,7 @@ class CollectDependenciesVisitor extends ReactiveFunctionVisitor<State> {
869 }
870 }
871
844 -/**
845 - * Prune reactive scopes that do not have any memoized outputs
846 - */
872 +// Prune reactive scopes that do not have any memoized outputs
873 class PruneScopesTransform extends ReactiveFunctionTransform<
874 Set<IdentifierId>
875 > {
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/PruneNonReactiveDependencies.ts
+2 -2
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -14,7 +14,7 @@ import {
14 import { collectReactiveIdentifiers } from "./CollectReactiveIdentifiers";
15 import { ReactiveFunctionVisitor, visitReactiveFunction } from "./visitors";
16
17 -/**
17 +/*
18 * PropagateScopeDependencies infers dependencies without considering whether dependencies
19 * are actually reactive or not (ie, whether their value can change over time).
20 *
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/PruneTemporaryLValues.ts
+2 -2
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -14,7 +14,7 @@ import {
14 } from "../HIR/HIR";
15 import { ReactiveFunctionVisitor, visitReactiveFunction } from "./visitors";
16
17 -/**
17 +/*
18 * Nulls out lvalues for temporary variables that are never accessed later. This only
19 * nulls out the lvalue itself, it does not remove the corresponding instructions.
20 */
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/PruneUnusedLabels.ts
+2 -2
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -17,7 +17,7 @@ import {
17 visitReactiveFunction,
18 } from "./visitors";
19
20 -/**
20 +/*
21 * Flattens labeled terminals where the label is not reachable, and
22 * nulls out labels for other terminals where the label is unused.
23 */
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/PruneUnusedScopes.ts
+7 -7
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -16,9 +16,7 @@ import {
16 visitReactiveFunction,
17 } from "./visitors";
18
19 -/**
20 - * Converts scopes without outputs into regular blocks.
21 - */
19 +// Converts scopes without outputs into regular blocks.
20 export function pruneUnusedScopes(fn: ReactiveFunction): void {
21 visitReactiveFunction(fn, new Transform(), undefined);
22 }
@@ -32,8 +30,10 @@ class Transform extends ReactiveFunctionTransform<void> {
30 if (
31 scopeBlock.scope.reassignments.size === 0 &&
32 (scopeBlock.scope.declarations.size === 0 ||
35 - // Can prune scopes where all declarations bubbled up from inner
36 - // scopes
33 + /*
34 + * Can prune scopes where all declarations bubbled up from inner
35 + * scopes
36 + */
37 !hasOwnDeclaration(scopeBlock))
38 ) {
39 return { kind: "replace-many", value: scopeBlock.instructions };
@@ -43,7 +43,7 @@ class Transform extends ReactiveFunctionTransform<void> {
43 }
44 }
45
46 -/**
46 +/*
47 * Does the scope block declare any values of its own? This can return
48 * false if all the block's declarations are propagated from nested scopes.
49 */
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/RenameVariables.ts
+6 -4
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -22,7 +22,7 @@ import {
22 visitReactiveFunction,
23 } from "./visitors";
24
25 -/**
25 +/*
26 * Ensures that each named variable in the given function has a unique name
27 * that does not conflict with any other variables in the same block scope.
28 * Note that the scoping is based on the final inferred blocks, not the
@@ -65,8 +65,10 @@ class Visitor extends ReactiveFunctionVisitor<Scopes> {
65 }
66 }
67 override visitScope(scope: ReactiveScopeBlock, state: Scopes): void {
68 - // Intentionally bypass visitBlock() since scopes do not introduce a new
69 - // block scope
68 + /*
69 + * Intentionally bypass visitBlock() since scopes do not introduce a new
70 + * block scope
71 + */
72 this.traverseBlock(scope.instructions, state);
73 }
74 }
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/index.ts
+1 -1
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/visitors.ts
+1 -1
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
compiler/packages/babel-plugin-react-forget/src/SSA/EliminateRedundantPhi.ts
+34 -20
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -13,7 +13,7 @@ import {
13 eachTerminalOperand,
14 } from "../HIR/visitors";
15
16 -/**
16 +/*
17 * Pass to eliminate redundant phi nodes:
18 * - all operands are the same identifier, ie `x2 = phi(x1, x1, x1)`.
19 * - all operands are the same identifier *or* the output of the phi, ie `x2 = phi(x1, x2, x1, x2)`.
@@ -35,21 +35,27 @@ export function eliminateRedundantPhi(
35 const rewrites: Map<Identifier, Identifier> =
36 sharedRewrites != null ? sharedRewrites : new Map();
37
38 - // Whether or the CFG has a back-edge (a loop). We determine this dynamically
39 - // during the first iteration over the CFG by recording which blocks were already
40 - // visited, and checking if a block has any predecessors that weren't visited yet.
41 - // Because blocks are in reverse postorder, the only time this can occur is a loop.
38 + /*
39 + * Whether or the CFG has a back-edge (a loop). We determine this dynamically
40 + * during the first iteration over the CFG by recording which blocks were already
41 + * visited, and checking if a block has any predecessors that weren't visited yet.
42 + * Because blocks are in reverse postorder, the only time this can occur is a loop.
43 + */
44 let hasBackEdge = false;
45 const visited: Set<BlockId> = new Set();
46
45 - // size tracks the number of rewrites at the beginning of each iteration, so we can
46 - // compare to see if any new rewrites were added in that iteration.
47 + /*
48 + * size tracks the number of rewrites at the beginning of each iteration, so we can
49 + * compare to see if any new rewrites were added in that iteration.
50 + */
51 let size = rewrites.size;
52 do {
53 size = rewrites.size;
54 for (const [blockId, block] of ir.blocks) {
51 - // On the first iteration of the loop check for any back-edges.
52 - // if there aren't any then there won't be a second iteration
55 + /*
56 + * On the first iteration of the loop check for any back-edges.
57 + * if there aren't any then there won't be a second iteration
58 + */
59 if (!hasBackEdge) {
60 for (const predId of block.preds) {
61 if (!visited.has(predId)) {
@@ -75,12 +81,16 @@ export function eliminateRedundantPhi(
81 (same !== null && operand.id === same.id) ||
82 operand.id === phi.id.id
83 ) {
78 - // This operand is the same as the phi or is the same as the
79 - // previous non-phi operands
84 + /*
85 + * This operand is the same as the phi or is the same as the
86 + * previous non-phi operands
87 + */
88 continue;
89 } else if (same !== null) {
82 - // There are multiple operands not equal to the phi itself,
83 - // this phi can't be eliminated.
90 + /*
91 + * There are multiple operands not equal to the phi itself,
92 + * this phi can't be eliminated.
93 + */
94 continue phis;
95 } else {
96 // First non-phi operand
@@ -115,9 +125,11 @@ export function eliminateRedundantPhi(
125 rewritePlace(place, rewrites);
126 }
127
118 - // recursive call to:
119 - // - eliminate phi nodes in child node
120 - // - propagate rewrites, which may have changed between iterations
128 + /*
129 + * recursive call to:
130 + * - eliminate phi nodes in child node
131 + * - propagate rewrites, which may have changed between iterations
132 + */
133 eliminateRedundantPhi(instr.value.loweredFunc.func, rewrites);
134 }
135 }
@@ -128,9 +140,11 @@ export function eliminateRedundantPhi(
140 rewritePlace(place, rewrites);
141 }
142 }
131 - // We only need to loop if there were newly eliminated phis in this iteration
132 - // *and* the CFG has loops. If there are no loops, then all eliminated phis
133 - // have already propagated forwards since we visit in reverse postorder.
143 + /*
144 + * We only need to loop if there were newly eliminated phis in this iteration
145 + * *and* the CFG has loops. If there are no loops, then all eliminated phis
146 + * have already propagated forwards since we visit in reverse postorder.
147 + */
148 } while (rewrites.size > size && hasBackEdge);
149 }
150
compiler/packages/babel-plugin-react-forget/src/SSA/EnterSSA.ts
+18 -12
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -137,19 +137,23 @@ class SSABuilder {
137 }
138
139 if (block.preds.size == 0) {
140 - // We're at the entry block and haven't found our defintion yet.
141 - // console.log(
142 - // `Unable to find "${printIdentifier(
143 - // oldId
144 - // )}" in bb${blockId}, assuming it's a global`
145 - // );
140 + /*
141 + * We're at the entry block and haven't found our defintion yet.
142 + * console.log(
143 + * `Unable to find "${printIdentifier(
144 + * oldId
145 + * )}" in bb${blockId}, assuming it's a global`
146 + * );
147 + */
148 this.#unknown.add(oldId);
149 return oldId;
150 }
151
152 if (this.unsealedPreds.get(block)! > 0) {
151 - // We haven't visited all our predecessors, let's place an incomplete phi
152 - // for now.
153 + /*
154 + * We haven't visited all our predecessors, let's place an incomplete phi
155 + * for now.
156 + */
157 const newId = this.makeId(oldId);
158 state.incompletePhis.push({ oldId, newId });
159 state.defs.set(oldId, newId);
@@ -166,9 +170,11 @@ class SSABuilder {
170
171 // There are multiple predecessors, we may need a phi.
172 const newId = this.makeId(oldId);
169 - // Adding a phi may loop back to our block if there is a loop in the CFG. We
170 - // update our defs before adding the phi to terminate the recursion rather than
171 - // looping infinitely.
173 + /*
174 + * Adding a phi may loop back to our block if there is a loop in the CFG. We
175 + * update our defs before adding the phi to terminate the recursion rather than
176 + * looping infinitely.
177 + */
178 state.defs.set(oldId, newId);
179 return this.addPhi(block, oldId, newId);
180 }
compiler/packages/babel-plugin-react-forget/src/SSA/LeaveSSA.ts
+86 -56
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -27,7 +27,7 @@ import {
27 terminalFallthrough,
28 } from "../HIR/visitors";
29
30 -/**
30 +/*
31 * Removes SSA form by converting all phis into explicit bindings and assignments. There are two main categories
32 * of phis:
33 *
@@ -43,9 +43,9 @@ import {
43 * // Input
44 * let x1 = null;
45 * if (a) {
46 - * x2 = b;
46 + * x2 = b;
47 * } else {
48 - * x3 = c;
48 + * x3 = c;
49 * }
50 * x4 = phi(x2, x3);
51 * return x4;
@@ -54,11 +54,11 @@ import {
54 * const x1 = null;
55 * let x4; // synthesized binding for the phi identifier
56 * if (a) {
57 - * x2 = b;
58 - * x4 = x2;; // sythesized assignment to the phi identifier
57 + * x2 = b;
58 + * x4 = x2;; // sythesized assignment to the phi identifier
59 * } else {
60 - * x3 = c;
61 - * x4 = x3; // synthesized assignment
60 + * x3 = c;
61 + * x4 = x3; // synthesized assignment
62 * }
63 * // phi removed
64 * return x4;
@@ -76,16 +76,16 @@ import {
76 * ```javascript
77 * // Input
78 * for (
79 - * let i1 = 0;
80 - * { i2 = phi(i1, i2); i2 < 10 }; // note the phi in the test block
81 - * i2 += 1
79 + * let i1 = 0;
80 + * { i2 = phi(i1, i2); i2 < 10 }; // note the phi in the test block
81 + * i2 += 1
82 * ) { ... }
83 *
84 * // Output
85 * for (
86 - * let i1 = 0; // i1 is defined first, so it becomes the canonical id
87 - * i1 < 10; // rewritten to canonical id
88 - * i1 += 1 // rewritten to canonical id
86 + * let i1 = 0; // i1 is defined first, so it becomes the canonical id
87 + * i1 < 10; // rewritten to canonical id
88 + * i1 += 1 // rewritten to canonical id
89 * )
90 * ```
91 */
@@ -109,9 +109,11 @@ export function leaveSSA(fn: HIRFunction): void {
109 }
110 }
111
112 - // For non-memoizable phis, this maps original identifiers to the identifier they should be
113 - // *rewritten* to. The keys are the original identifiers, and the value will be _either_ the
114 - // phi id or, more typically, the operand that was defined prior to the phi.
112 + /*
113 + * For non-memoizable phis, this maps original identifiers to the identifier they should be
114 + * *rewritten* to. The keys are the original identifiers, and the value will be _either_ the
115 + * phi id or, more typically, the operand that was defined prior to the phi.
116 + */
117 const rewrites: Map<Identifier, Identifier> = new Map();
118
119 type PhiState = {
@@ -135,8 +137,10 @@ export function leaveSSA(fn: HIRFunction): void {
137
138 for (const [, block] of fn.body.blocks) {
139 for (const instr of block.instructions) {
138 - // Iterate the instructions and perform any rewrites as well as promoting SSA variables to
139 - // `let` or `reassign` where possible.
140 + /*
141 + * Iterate the instructions and perform any rewrites as well as promoting SSA variables to
142 + * `let` or `reassign` where possible.
143 + */
144 const { lvalue, value } = instr;
145 if (value.kind === "DeclareLocal") {
146 const name = value.lvalue.place.identifier.name;
@@ -196,8 +200,10 @@ export function leaveSSA(fn: HIRFunction): void {
200 });
201 value.lvalue.kind = InstructionKind.Const;
202 } else {
199 - // This is an instance of the original id, so we need to promote the original declaration
200 - // to a `let` and the current lval to a `reassign`
203 + /*
204 + * This is an instance of the original id, so we need to promote the original declaration
205 + * to a `let` and the current lval to a `reassign`
206 + */
207 originalLVal.lvalue.kind = InstructionKind.Let;
208 value.lvalue.kind = InstructionKind.Reassign;
209 }
@@ -290,9 +296,11 @@ export function leaveSSA(fn: HIRFunction): void {
296 rewritePlace(operand, rewrites, declarations);
297 }
298
293 - // Find any phi nodes which need a variable declaration in the current block
294 - // This includes phis in fallthrough nodes, or blocks that form part of control flow
295 - // such as for or while (and later if/switch).
299 + /*
300 + * Find any phi nodes which need a variable declaration in the current block
301 + * This includes phis in fallthrough nodes, or blocks that form part of control flow
302 + * such as for or while (and later if/switch).
303 + */
304 const reassignmentPhis: Array<PhiState> = [];
305 const rewritePhis: Array<PhiState> = [];
306 function pushPhis(phiBlock: BasicBlock): void {
@@ -307,26 +315,34 @@ export function leaveSSA(fn: HIRFunction): void {
315 phi.id.mutableRange.end >
316 (phiBlock.instructions.at(0)?.id ?? phiBlock.terminal.id);
317
310 - // Named variables whose phi doesn't have a back-edge can potentially be independenly
311 - // memoized, depending on whether the phi is after its creation.
318 + /*
319 + * Named variables whose phi doesn't have a back-edge can potentially be independenly
320 + * memoized, depending on whether the phi is after its creation.
321 + */
322 if (phi.id.name !== null && !hasBackEdge) {
323 if (!isPhiMutatedAfterCreation) {
314 - // Simple case: predecesor-only values flowing into a phi, which is never modified:
315 - // adjust the phi's range to clarify that the identifier does not mutate
324 + /*
325 + * Simple case: predecesor-only values flowing into a phi, which is never modified:
326 + * adjust the phi's range to clarify that the identifier does not mutate
327 + */
328 phi.id.mutableRange.start = terminal.id;
329 phi.id.mutableRange.end = makeInstructionId(terminal.id + 1);
330 } else {
319 - // Predecessor only values flow into a phi, which is modified later:
320 - // all operands flow into the phi and can be modified, must extend their ranges
331 + /*
332 + * Predecessor only values flow into a phi, which is modified later:
333 + * all operands flow into the phi and can be modified, must extend their ranges
334 + */
335 for (const [, operand] of phi.operands) {
336 operand.mutableRange.end = phi.id.mutableRange.end;
337 }
338 }
339 continue;
340 }
327 - // Otherwise this is a temporary phi (logical or ternary) or occurs in a loop. In either
328 - // case we can't independently memoize any of the values: unify their ranges to span the
329 - // min(start) to max(end) so that we create a single scope for all the computation.
341 + /*
342 + * Otherwise this is a temporary phi (logical or ternary) or occurs in a loop. In either
343 + * case we can't independently memoize any of the values: unify their ranges to span the
344 + * min(start) to max(end) so that we create a single scope for all the computation.
345 + */
346 let start = block.terminal.id as number;
347 let end = Number.MIN_SAFE_INTEGER;
348 const operands = [phi.id, ...phi.operands.values()];
@@ -360,8 +376,10 @@ export function leaveSSA(fn: HIRFunction): void {
376 const init = fn.body.blocks.get(terminal.init)!;
377 pushPhis(init);
378
363 - // To avoid generating a let binding for the initializer prior to the loop,
364 - // check to see if the for declares an iterator variable
379 + /*
380 + * To avoid generating a let binding for the initializer prior to the loop,
381 + * check to see if the for declares an iterator variable
382 + */
383 const initIdentifier = init.instructions.at(-1);
384 if (
385 initIdentifier !== undefined &&
@@ -389,10 +407,12 @@ export function leaveSSA(fn: HIRFunction): void {
407 }
408
409 for (const { phi, block: phiBlock } of reassignmentPhis) {
392 - // In some cases one of the phi operands can be defined *before* the let binding
393 - // we will generate. For example, a variable that is only rebound in one branch of
394 - // an if but not another. In this case we populate the let binding with this initial
395 - // value rather than generate an extra assignment.
410 + /*
411 + * In some cases one of the phi operands can be defined *before* the let binding
412 + * we will generate. For example, a variable that is only rebound in one branch of
413 + * an if but not another. In this case we populate the let binding with this initial
414 + * value rather than generate an extra assignment.
415 + */
416 let initOperand: Identifier | null = null;
417 for (const [, operand] of phi.operands) {
418 if (operand.mutableRange.start < terminal.id) {
@@ -402,14 +422,18 @@ export function leaveSSA(fn: HIRFunction): void {
422 }
423 }
424
405 - // If the phi is mutated after its creation, then any values which flow into the phi
406 - // must also have their ranges extended accordingly.
425 + /*
426 + * If the phi is mutated after its creation, then any values which flow into the phi
427 + * must also have their ranges extended accordingly.
428 + */
429 const isPhiMutatedAfterCreation: boolean =
430 phi.id.mutableRange.end >
431 (phiBlock.instructions.at(0)?.id ?? phiBlock.terminal.id);
432
411 - // If we never saw a declaration for this phi, it may have been pruned by DCE, so synthesize
412 - // a new Let binding
433 + /*
434 + * If we never saw a declaration for this phi, it may have been pruned by DCE, so synthesize
435 + * a new Let binding
436 + */
437 CompilerError.invariant(phi.id.name != null, {
438 reason: "Expected reassignment phis to have a name",
439 description: null,
@@ -424,21 +448,25 @@ export function leaveSSA(fn: HIRFunction): void {
448 suggestions: null,
449 });
450 if (isPhiMutatedAfterCreation) {
427 - // The declaration is not guaranteed to flow into the phi, for example in the case of a variable
428 - // that is reassigned in all control flow paths to a given phi. The original declaration's range
429 - // has to be extended in this case (if the phi is later mutated) since we are reusing the original
430 - // declaration instead of creating a new declaration.
431 - //
432 - // NOTE: this can *only* happen if the original declaration involves an instruction that DCE does
433 - // not prune. Otherwise, the declaration would have been pruned and we'd synthesize a new one.
451 + /*
452 + * The declaration is not guaranteed to flow into the phi, for example in the case of a variable
453 + * that is reassigned in all control flow paths to a given phi. The original declaration's range
454 + * has to be extended in this case (if the phi is later mutated) since we are reusing the original
455 + * declaration instead of creating a new declaration.
456 + *
457 + * NOTE: this can *only* happen if the original declaration involves an instruction that DCE does
458 + * not prune. Otherwise, the declaration would have been pruned and we'd synthesize a new one.
459 + */
460 declaration.place.identifier.mutableRange.end = phi.id.mutableRange.end;
461 }
462 rewrites.set(phi.id, declaration.place.identifier);
463 }
464
439 - // Similar logic for rewrite phis that occur in loops, except that instead of a new let binding
440 - // we pick one of the operands as the canonical id, and rewrite all references to the other
441 - // operands and the phi to reference this canonical id.
465 + /*
466 + * Similar logic for rewrite phis that occur in loops, except that instead of a new let binding
467 + * we pick one of the operands as the canonical id, and rewrite all references to the other
468 + * operands and the phi to reference this canonical id.
469 + */
470 for (const { phi } of rewritePhis) {
471 let canonicalId = rewrites.get(phi.id);
472 if (canonicalId === undefined) {
@@ -467,9 +495,11 @@ export function leaveSSA(fn: HIRFunction): void {
495 }
496 }
497
470 -// Rewrite @param place's identifier based on the given rewrite mapping, if the identifier
471 -// is present. Also expands the mutable range of the target identifier to include the
472 -// place's range.
498 +/*
499 + * Rewrite @param place's identifier based on the given rewrite mapping, if the identifier
500 + * is present. Also expands the mutable range of the target identifier to include the
501 + * place's range.
502 + */
503 function rewritePlace(
504 place: Place,
505 rewrites: Map<Identifier, Identifier>,
compiler/packages/babel-plugin-react-forget/src/SSA/index.ts
+1 -1
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
compiler/packages/babel-plugin-react-forget/src/TypeInference/InferTypes.ts
+10 -6
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -171,9 +171,11 @@ function* generateInstructionTypes(
171 }
172
173 case "CallExpression": {
174 - // TODO: callee could be a hook or a function, so this type equation isn't correct.
175 - // We should change Hook to a subtype of Function or change unifier logic.
176 - // (see https://github.com/facebook/react-forget/pull/1427)
174 + /*
175 + * TODO: callee could be a hook or a function, so this type equation isn't correct.
176 + * We should change Hook to a subtype of Function or change unifier logic.
177 + * (see https://github.com/facebook/react-forget/pull/1427)
178 + */
179 yield equation(value.callee.identifier.type, {
180 kind: "Function",
181 shapeId: null,
@@ -308,8 +310,10 @@ class Unifier {
310 if (propertyType !== null) {
311 this.unify(tA, propertyType);
312 }
311 - // We do not error if tB is not a known object or function (even if it
312 - // is a primitive), since JS implicit conversion to objects
313 + /*
314 + * We do not error if tB is not a known object or function (even if it
315 + * is a primitive), since JS implicit conversion to objects
316 + */
317 return;
318 }
319
compiler/packages/babel-plugin-react-forget/src/TypeInference/index.ts
+1 -1
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
compiler/packages/babel-plugin-react-forget/src/Utils/ComponentDeclaration.ts
+1 -1
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
compiler/packages/babel-plugin-react-forget/src/Utils/DisjointSet.ts
+11 -11
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -7,13 +7,11 @@
7
8 import { CompilerError } from "../CompilerError";
9
10 -/**
11 - * Represents items which form disjoint sets.
12 - */
10 +// Represents items which form disjoint sets.
11 export default class DisjointSet<T> {
12 #entries: Map<T, T> = new Map();
13
16 - /**
14 + /*
15 * Updates the graph to reflect that the given @param items form a set,
16 * linking any previous sets that the items were part of into a single
17 * set.
@@ -26,9 +24,11 @@ export default class DisjointSet<T> {
24 loc: null,
25 suggestions: null,
26 });
29 - // determine an arbitrary "root" for this set: if the first
30 - // item already has a root then use that, otherwise the first item
31 - // will be the new root.
27 + /*
28 + * determine an arbitrary "root" for this set: if the first
29 + * item already has a root then use that, otherwise the first item
30 + * will be the new root.
31 + */
32 let root = this.find(first);
33 if (root == null) {
34 root = first;
@@ -54,7 +54,7 @@ export default class DisjointSet<T> {
54 }
55 }
56
57 - /**
57 + /*
58 * Finds the set to which the given @param item is associated, if @param item
59 * is present in this set. If item is not present, returns null.
60 *
@@ -78,7 +78,7 @@ export default class DisjointSet<T> {
78 return root;
79 }
80
81 - /**
81 + /*
82 * Forces the set into canonical form, ie with all items pointing directly to
83 * their root, and returns a Map representing the mapping of items to their roots.
84 */
@@ -91,7 +91,7 @@ export default class DisjointSet<T> {
91 return entries;
92 }
93
94 - /**
94 + /*
95 * Calls the provided callback once for each item in the disjoint set,
96 * passing the @param item and the @param group to which it belongs.
97 */
compiler/packages/babel-plugin-react-forget/src/Utils/Result.ts
+17 -31
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -7,21 +7,21 @@
7
8 // Direct translation of Rust's Result type, although some ownership related methods are omitted.
9 export interface Result<T, E> {
10 - /**
10 + /*
11 * Maps a `Result<T, E>` to `Result<U, E>` by applying a function to a contained `Ok` value,
12 * leaving an `Err` value untouched.
13 *
14 * This function can be used to compose the results of two functions.
15 */
16 map<U>(fn: (val: T) => U): Result<U, E>;
17 - /**
17 + /*
18 * Maps a `Result<T, E>` to `Result<T, F>` by applying a function to a contained `Err` value,
19 * leaving an `Ok` value untouched.
20 *
21 * This function can be used to pass through a successful result while handling an error.
22 */
23 mapErr<F>(fn: (val: E) => F): Result<T, F>;
24 - /**
24 + /*
25 * Returns the provided default (if `Err`), or applies a function to the contained value
26 * (if `Ok`).
27 *
@@ -29,73 +29,59 @@ export interface Result<T, E> {
29 * function call, it is recommended to use {@link mapOrElse}, which is lazily evaluated.
30 */
31 mapOr<U>(fallback: U, fn: (val: T) => U): U;
32 - /**
32 + /*
33 * Maps a `Result<T, E>` to `U` by applying fallback function default to a contained `Err` value,
34 * or function `fn` to a contained `Ok` value.
35 *
36 * This function can be used to unpack a successful result while handling an error.
37 */
38 mapOrElse<U>(fallback: () => U, fn: (val: T) => U): U;
39 - /**
39 + /*
40 * Calls `fn` if the result is `Ok`, otherwise returns the `Err` value of self.
41 *
42 * This function can be used for control flow based on Result values.
43 */
44 andThen<U>(fn: (val: T) => Result<U, E>): Result<U, E>;
45 - /**
45 + /*
46 * Returns res if the result is `Ok`, otherwise returns the `Err` value of self.
47 *
48 * Arguments passed to {@link and} are eagerly evaluated; if you are passing the result of a
49 * function call, it is recommended to use {@link andThen}, which is lazily evaluated.
50 */
51 and<U>(res: Result<U, E>): Result<U, E>;
52 - /**
52 + /*
53 * Returns `res` if the result is `Err`, otherwise returns the `Ok` value of self.
54 *
55 * Arguments passed to {@link or} are eagerly evaluated; if you are passing the result of a
56 * function call, it is recommended to use {@link orElse}, which is lazily evaluated.
57 */
58 or(res: Result<T, E>): Result<T, E>;
59 - /**
59 + /*
60 * Calls `fn` if the result is `Err`, otherwise returns the `Ok` value of self.
61 *
62 * This function can be used for control flow based on result values.
63 */
64 orElse<F>(fn: (val: E) => Result<T, F>): Result<T, F>;
65 - /**
66 - * Returns `true` if the result is `Ok`.
67 - */
65 + // Returns `true` if the result is `Ok`.
66 isOk(): this is OkImpl<T>;
69 - /**
70 - * Returns `true` if the result is `Err`.
71 - */
67 + // Returns `true` if the result is `Err`.
68 isErr(): this is ErrImpl<E>;
73 - /**
74 - * Returns the contained `Ok` value or throws.
75 - */
69 + // Returns the contained `Ok` value or throws.
70 expect(msg: string): T;
77 - /**
78 - * Returns the contained `Err` value or throws.
79 - */
71 + // Returns the contained `Err` value or throws.
72 expectErr(msg: string): E;
81 - /**
82 - * Returns the contained `Ok` value.
83 - */
73 + // Returns the contained `Ok` value.
74 unwrap(): T;
85 - /**
75 + /*
76 * Returns the contained `Ok` value or a provided default.
77 *
78 * Arguments passed to {@link unwrapOr} are eagerly evaluated; if you are passing the result of a
79 * function call, it is recommended to use {@link unwrapOrElse}, which is lazily evaluated.
80 */
81 unwrapOr(fallback: T): T;
92 - /**
93 - * Returns the contained `Ok` value or computes it from a closure.
94 - */
82 + // Returns the contained `Ok` value or computes it from a closure.
83 unwrapOrElse(fallback: (val: E) => T): T;
96 - /**
97 - * Returns the contained `Err` value or throws.
98 - */
84 + // Returns the contained `Err` value or throws.
85 unwrapErr(): E;
86 }
87
compiler/packages/babel-plugin-react-forget/src/Utils/Stack.ts
+8 -8
@@ -1,13 +1,11 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8 -/**
9 - * An immutable stack data structure supporting O(1) push/pop operations.
10 - */
8 +// An immutable stack data structure supporting O(1) push/pop operations.
9 export type Stack<T> = Node<T> | Empty<T>;
10
11 // Static assertion that Stack<T> is a StackInterface<T>
@@ -15,10 +13,12 @@ function _assertStackInterface<T>(stack: Stack<T>): void {
13 let _: StackInterface<T> = stack;
14 }
15
18 -// Internal interface to enforce consistent behavior btw Node/Empty variants
19 -// Note that we export a union rather than the interface so that it is impossible
20 -// to create additional variants: a Stack should always be exactly a Node or Empty
21 -// instance.
16 +/*
17 + * Internal interface to enforce consistent behavior btw Node/Empty variants
18 + * Note that we export a union rather than the interface so that it is impossible
19 + * to create additional variants: a Stack should always be exactly a Node or Empty
20 + * instance.
21 + */
22 interface StackInterface<T> {
23 push(value: T): StackInterface<T>;
24
compiler/packages/babel-plugin-react-forget/src/Utils/logger.ts
+1 -1
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
compiler/packages/babel-plugin-react-forget/src/Utils/todo.ts
+1 -1
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
compiler/packages/babel-plugin-react-forget/src/Utils/utils.ts
+14 -14
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -7,22 +7,22 @@
7
8 import { NodePath } from "@babel/traverse";
9
10 -/**
10 +/*
11 * Trigger an exhaustivess check in TypeScript and throw at runtime.
12 *
13 * Example:
14 *
15 * ```ts
16 * enum ErrorCode = {
17 - * E0001 = "E0001",
18 - * E0002 = "E0002"
17 + * E0001 = "E0001",
18 + * E0002 = "E0002"
19 * }
20 *
21 * switch (code) {
22 - * case ErrorCode.E0001:
23 - * // ...
24 - * default:
25 - * assertExhaustive(code, "Unhandled error code");
22 + * case ErrorCode.E0001:
23 + * // ...
24 + * default:
25 + * assertExhaustive(code, "Unhandled error code");
26 * }
27 * ```
28 */
@@ -30,9 +30,7 @@ export function assertExhaustive(_: never, errorMsg: string): never {
30 throw new Error(errorMsg);
31 }
32
33 -/**
34 - * Modifies @param array in place, retaining only the items where the predicate returns true.
35 - */
33 +// Modifies @param array in place, retaining only the items where the predicate returns true.
34 export function retainWhere<T>(
35 array: Array<T>,
36 predicate: (item: T) => boolean
@@ -73,8 +71,10 @@ export function Set_union<T>(a: Set<T>, b: Set<T>): Set<T> {
71 export function hasNode<T>(
72 input: NodePath<T | null | undefined>
73 ): input is NodePath<NonNullable<T>> {
76 - // Internal babel is on an older version that does not have hasNode (v7.17)
77 - // See https://github.com/babel/babel/pull/13940/files for impl
78 - // https://github.com/babel/babel/blob/5ebab544af2f1c6fc6abdaae6f4e5426975c9a16/packages/babel-traverse/src/path/index.ts#L128-L130
74 + /*
75 + * Internal babel is on an older version that does not have hasNode (v7.17)
76 + * See https://github.com/babel/babel/pull/13940/files for impl
77 + * https://github.com/babel/babel/blob/5ebab544af2f1c6fc6abdaae6f4e5426975c9a16/packages/babel-traverse/src/path/index.ts#L128-L130
78 + */
79 return input.node != null;
80 }
compiler/packages/babel-plugin-react-forget/src/Validation/ValidateFrozenLambdas.ts
+10 -8
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -25,13 +25,13 @@ import {
25 eachTerminalOperand,
26 } from "../HIR/visitors";
27
28 -/**
28 +/*
29 * Various APIs in React take ownership of the values passed to them, such that it is invalid
30 * to subsequently modify those values. Examples include:
31 * - Passing a value as a prop to JSX. Subsequently mutating this value will result in undefined
32 - * behavior, since the mutation may or may not be observed depending on when the child re-renders.
33 - * In addition, the value may be used as an input to memoization in children, and mutation could
34 - * invalidate that memoization.
32 + * behavior, since the mutation may or may not be observed depending on when the child re-renders.
33 + * In addition, the value may be used as an input to memoization in children, and mutation could
34 + * invalidate that memoization.
35 * - Passing a value to `useState()`, for the same reason.
36 * - Passing a value to a hook, for the same reason.
37 *
@@ -134,9 +134,11 @@ function validateOperand(
134 state.temporaries.get(operand.identifier.id) ?? operand.identifier.id;
135 const lambda = state.lambdas.get(operandId);
136 if (lambda !== undefined) {
137 - // TODO: these seem to always be null, we should try to preserve original
138 - // names from source
139 - // TODO: figure out how to print object methods as they don't have names
137 + /*
138 + * TODO: these seem to always be null, we should try to preserve original
139 + * names from source
140 + * TODO: figure out how to print object methods as they don't have names
141 + */
142 const description =
143 lambda.kind === "FunctionExpression" &&
144 lambda.name !== null &&
compiler/packages/babel-plugin-react-forget/src/Validation/ValidateHooksUsage.ts
+2 -2
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -16,7 +16,7 @@ import {
16 eachTerminalOperand,
17 } from "../HIR/visitors";
18
19 -/**
19 +/*
20 * Validates that the function honors the [Rules of Hooks](https://react.dev/warnings/invalid-hook-call-warning)
21 * rule that hooks may only be called and not otherwise referenced as first-class values.
22 */
compiler/packages/babel-plugin-react-forget/src/Validation/ValidateNoRefAccesInRender.ts
+13 -9
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -17,7 +17,7 @@ import {
17 eachTerminalOperand,
18 } from "../HIR/visitors";
19
20 -/**
20 +/*
21 * Validates that ref values (the `current` property) are not accessed during render.
22 * This validation is conservative and only rejects accesses of known ref values:
23 *
@@ -47,9 +47,11 @@ export function validateNoRefAccessInRender(fn: HIRFunction): void {
47 case "LoadLocal":
48 case "StoreLocal":
49 case "Destructure": {
50 - // These instructions are necessary for storing the results of a useRef into
51 - // a variable and referencing them in functions. We can propagate type info
52 - // for these instructions so they ensure we have a complete analysis.
50 + /*
51 + * These instructions are necessary for storing the results of a useRef into
52 + * a variable and referencing them in functions. We can propagate type info
53 + * for these instructions so they ensure we have a complete analysis.
54 + */
55 break;
56 }
57 case "JsxExpression": {
@@ -61,10 +63,12 @@ export function validateNoRefAccessInRender(fn: HIRFunction): void {
63 }
64 case "ObjectMethod":
65 case "FunctionExpression": {
64 - // functions are allowed to capture refs, so long as the function is not called
65 - // during render. see AnalyzeFunctions for how we ensure that functions which
66 - // capture refs get assigned a mutable range so we know here whether the function
67 - // is called or not
66 + /*
67 + * functions are allowed to capture refs, so long as the function is not called
68 + * during render. see AnalyzeFunctions for how we ensure that functions which
69 + * capture refs get assigned a mutable range so we know here whether the function
70 + * is called or not
71 + */
72 const mutableRange = instr.lvalue.identifier.mutableRange;
73 if (mutableRange.end > mutableRange.start + 1) {
74 for (const operand of eachInstructionValueOperand(instr.value)) {
compiler/packages/babel-plugin-react-forget/src/Validation/ValidateNoSetStateInRender.ts
+2 -2
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -45,7 +45,7 @@ export function validateNoSetStateInRender(
45 switch (instr.value.kind) {
46 case "ObjectMethod":
47 case "FunctionExpression": {
48 - /**
48 + /*
49 * TODO: setState's return value is considered Frozen, so the lambda's mutable range
50 * does not get extended even if the lambda is called in render. The below only catches
51 * setStates where the lambda has another instruction that extends its mutable range
compiler/packages/babel-plugin-react-forget/src/Validation/ValidateUnconditionalHooks.ts
+25 -21
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -15,7 +15,7 @@ import { BlockId, HIRFunction, SourceLocation, getHookKind } from "../HIR/HIR";
15 import { findBlocksWithBackEdges } from "../Optimization/DeadCodeElimination";
16 import { Err, Ok, Result } from "../Utils/Result";
17
18 -/**
18 +/*
19 * Validates that the function honors the [Rules of Hooks](https://react.dev/warnings/invalid-hook-call-warning)
20 * rule that hooks may not be called conditionally. More precisely, a component or hook must always call the
21 * same set of hooks in the same order.
@@ -26,13 +26,13 @@ import { Err, Ok, Result } from "../Utils/Result";
26 * entry block to the exit:
27 *
28 * ```
29 - * bb0 (entry)
30 - * / \
31 - * bb1 bb2
32 - * \ /
33 - * bb3
34 - * |
35 - * (exit)
29 + * bb0 (entry)
30 + * / \
31 + * bb1 bb2
32 + * \ /
33 + * bb3
34 + * |
35 + * (exit)
36 * ```
37 *
38 * Here, neither bb1 or bb2 post dominate the entry, which corresponds to the fact that control can
@@ -43,13 +43,13 @@ import { Err, Ok, Result } from "../Utils/Result";
43 * However if for example bb2 were to early return:
44 *
45 * ```
46 - * bb0 (entry)
47 - * / \
48 - * bb1 bb2
49 - * \ |
50 - * bb3 /
51 - * | /
52 - * (exit)
46 + * bb0 (entry)
47 + * / \
48 + * bb1 bb2
49 + * \ |
50 + * bb3 /
51 + * | /
52 + * (exit)
53 * ```
54 *
55 * Now only the exit node would post dominate the entry node: there is no other node which is
@@ -62,8 +62,10 @@ export function validateUnconditionalHooks(
62 const unconditionalBlocks = new Set<BlockId>();
63 const blocksWithBackEdges = findBlocksWithBackEdges(fn);
64 const dominators = computePostDominatorTree(fn, {
65 - // Hooks must only be in a consistent order for executions that return normally,
66 - // so we opt-in to viewing throw as a non-exit node.
65 + /*
66 + * Hooks must only be in a consistent order for executions that return normally,
67 + * so we opt-in to viewing throw as a non-exit node.
68 + */
69 includeThrowsAsExitNode: false,
70 });
71 const exit = dominators.exit;
@@ -100,9 +102,11 @@ export function validateUnconditionalHooks(
102 instr.value.kind === "CallExpression" &&
103 getHookKind(fn.env, instr.value.callee.identifier) != null
104 ) {
103 - // TODO: the current ESLint rule has different error messages for code that is called conditionally, in a loop, etc.
104 - // An option would be to first record an Array<[BlockId, Place]> of problematic hooks, then compute the normal dominator graph
105 - // and walk upward to determine whether each error location was due to a loop, if, etc.
105 + /*
106 + * TODO: the current ESLint rule has different error messages for code that is called conditionally, in a loop, etc.
107 + * An option would be to first record an Array<[BlockId, Place]> of problematic hooks, then compute the normal dominator graph
108 + * and walk upward to determine whether each error location was due to a loop, if, etc.
109 + */
110 recordError(instr.loc);
111 } else if (
112 instr.value.kind === "MethodCall" &&
compiler/packages/babel-plugin-react-forget/src/Validation/ValidateUseMemo.ts
+5 -3
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
@@ -47,8 +47,10 @@ export function validateUseMemo(fn: HIRFunction): void {
47 continue;
48 }
49
50 - // If yes get the first argument and if it refers to a locally defined function
51 - // expression, validate the function
50 + /*
51 + * If yes get the first argument and if it refers to a locally defined function
52 + * expression, validate the function
53 + */
54 const [arg] = value.args;
55 if (arg.kind !== "Identifier") {
56 continue;
compiler/packages/babel-plugin-react-forget/src/Validation/index.ts
+1 -1
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
compiler/packages/babel-plugin-react-forget/src/index.ts
+1 -1
@@ -1,4 +1,4 @@
1 -/**
1 +/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the