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