@samitouri / QOS-React / commits / c61e75b76d

[compiler] Avoid failing builds when import specifiers conflict or shadow vars (#32663)

Avoid failing builds when imported function specifiers conflict by using babel's `generateUid`. Failing a build is very disruptive, as it usually presents to developers similar to a javascript parse error. ```js import {logRender as _logRender} from 'instrument-runtime'; const logRender = () => { /* local conflicting implementation */ } function Component_optimized() { _logRender(); // inserted by compiler } ``` Currently, we fail builds (even in `panicThreshold:none` cases) when import specifiers are detected to conflict with existing local variables. The reason we destructively throw (instead of bailing out) is because (1) we first generate identifier references to the conflicting name in compiled functions, (2) replaced original functions with compiled functions, and then (3) finally check for conflicts. When we finally check for conflicts, it's too late to bail out. ```js // import {logRender} from 'instrument-runtime'; const logRender = () => { /* local conflicting implementation */ } function Component_optimized() { logRender(); // inserted by compiler } ```

mofeiZ committed Mar 24, 2025 at 09:31 UTC c61e75b76d5ff6707ad75c8beb777e721d982207
61 files changed +1013 -409
compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Gating.ts
+21 -12
@@ -7,8 +7,9 @@
7
8 import {NodePath} from '@babel/core';
9 import * as t from '@babel/types';
10 -import {PluginOptions} from './Options';
10 import {CompilerError} from '../CompilerError';
11 +import {ProgramContext} from './Imports';
12 +import {ExternalFunction} from '..';
13
14 /**
15 * Gating rewrite for function declarations which are referenced before their
@@ -34,7 +35,8 @@ import {CompilerError} from '../CompilerError';
35 function insertAdditionalFunctionDeclaration(
36 fnPath: NodePath<t.FunctionDeclaration>,
37 compiled: t.FunctionDeclaration,
37 - gating: NonNullable<PluginOptions['gating']>,
38 + programContext: ProgramContext,
39 + gatingFunctionIdentifierName: string,
40 ): void {
41 const originalFnName = fnPath.node.id;
42 const originalFnParams = fnPath.node.params;
@@ -57,14 +59,14 @@ function insertAdditionalFunctionDeclaration(
59 loc: fnPath.node.loc ?? null,
60 });
61
60 - const gatingCondition = fnPath.scope.generateUidIdentifier(
61 - `${gating.importSpecifierName}_result`,
62 + const gatingCondition = t.identifier(
63 + programContext.newUid(`${gatingFunctionIdentifierName}_result`),
64 );
63 - const unoptimizedFnName = fnPath.scope.generateUidIdentifier(
64 - `${originalFnName.name}_unoptimized`,
65 + const unoptimizedFnName = t.identifier(
66 + programContext.newUid(`${originalFnName.name}_unoptimized`),
67 );
66 - const optimizedFnName = fnPath.scope.generateUidIdentifier(
67 - `${originalFnName.name}_optimized`,
68 + const optimizedFnName = t.identifier(
69 + programContext.newUid(`${originalFnName.name}_optimized`),
70 );
71 /**
72 * Step 1: rename existing functions
@@ -115,7 +117,7 @@ function insertAdditionalFunctionDeclaration(
117 t.variableDeclaration('const', [
118 t.variableDeclarator(
119 gatingCondition,
118 - t.callExpression(t.identifier(gating.importSpecifierName), []),
120 + t.callExpression(t.identifier(gatingFunctionIdentifierName), []),
121 ),
122 ]),
123 );
@@ -129,19 +131,26 @@ export function insertGatedFunctionDeclaration(
131 | t.FunctionDeclaration
132 | t.ArrowFunctionExpression
133 | t.FunctionExpression,
132 - gating: NonNullable<PluginOptions['gating']>,
134 + programContext: ProgramContext,
135 + gating: ExternalFunction,
136 referencedBeforeDeclaration: boolean,
137 ): void {
138 + const gatingImportedName = programContext.addImportSpecifier(gating).name;
139 if (referencedBeforeDeclaration && fnPath.isFunctionDeclaration()) {
140 CompilerError.invariant(compiled.type === 'FunctionDeclaration', {
141 reason: 'Expected compiled node type to match input type',
142 description: `Got ${compiled.type} but expected FunctionDeclaration`,
143 loc: fnPath.node.loc ?? null,
144 });
141 - insertAdditionalFunctionDeclaration(fnPath, compiled, gating);
145 + insertAdditionalFunctionDeclaration(
146 + fnPath,
147 + compiled,
148 + programContext,
149 + gatingImportedName,
150 + );
151 } else {
152 const gatingExpression = t.conditionalExpression(
144 - t.callExpression(t.identifier(gating.importSpecifierName), []),
153 + t.callExpression(t.identifier(gatingImportedName), []),
154 buildFunctionExpression(compiled),
155 buildFunctionExpression(fnPath.node),
156 );
compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts
+206 -129
@@ -7,9 +7,19 @@
7
8 import {NodePath} from '@babel/core';
9 import * as t from '@babel/types';
10 +import {Scope as BabelScope} from '@babel/traverse';
11 +
12 import {CompilerError, ErrorSeverity} from '../CompilerError';
11 -import {EnvironmentConfig, ExternalFunction, GeneratedSource} from '../HIR';
12 -import {getOrInsertDefault} from '../Utils/utils';
13 +import {
14 + EnvironmentConfig,
15 + GeneratedSource,
16 + NonLocalImportSpecifier,
17 +} from '../HIR';
18 +import {getOrInsertWith} from '../Utils/utils';
19 +import {ExternalFunction, isHookName} from '../HIR/Environment';
20 +import {Err, Ok, Result} from '../Utils/Result';
21 +import {CompilerReactTarget} from './Options';
22 +import {getReactCompilerRuntimeModule} from './Program';
23
24 export function validateRestrictedImports(
25 path: NodePath<t.Program>,
@@ -42,159 +52,226 @@ export function validateRestrictedImports(
52 }
53 }
54
45 -export function addImportsToProgram(
46 - path: NodePath<t.Program>,
47 - importList: Array<ExternalFunction>,
48 -): void {
49 - const identifiers: Set<string> = new Set();
50 - const sortedImports: Map<string, Array<string>> = new Map();
51 - for (const {importSpecifierName, source} of importList) {
52 - /*
53 - * Codegen currently does not rename import specifiers, so we do additional
54 - * validation here
55 +export class ProgramContext {
56 + /* Program and environment context */
57 + scope: BabelScope;
58 + reactRuntimeModule: string;
59 + hookPattern: string | null;
60 +
61 + // known generated or referenced identifiers in the program
62 + knownReferencedNames: Set<string> = new Set();
63 + // generated imports
64 + imports: Map<string, Map<string, NonLocalImportSpecifier>> = new Map();
65 +
66 + constructor(
67 + program: NodePath<t.Program>,
68 + reactRuntimeModule: CompilerReactTarget,
69 + hookPattern: string | null,
70 + ) {
71 + this.hookPattern = hookPattern;
72 + this.scope = program.scope;
73 + this.reactRuntimeModule = getReactCompilerRuntimeModule(reactRuntimeModule);
74 + }
75 +
76 + isHookName(name: string): boolean {
77 + if (this.hookPattern == null) {
78 + return isHookName(name);
79 + } else {
80 + const match = new RegExp(this.hookPattern).exec(name);
81 + return (
82 + match != null && typeof match[1] === 'string' && isHookName(match[1])
83 + );
84 + }
85 + }
86 +
87 + hasReference(name: string): boolean {
88 + return (
89 + this.knownReferencedNames.has(name) ||
90 + this.scope.hasBinding(name) ||
91 + this.scope.hasGlobal(name) ||
92 + this.scope.hasReference(name)
93 + );
94 + }
95 +
96 + newUid(name: string): string {
97 + /**
98 + * Don't call babel's generateUid for known hook imports, as
99 + * InferTypes might eventually type `HookKind` based on callee naming
100 + * convention and `_useFoo` is not named as a hook.
101 + *
102 + * Local uid generation is susceptible to check-before-use bugs since we're
103 + * checking for naming conflicts / references long before we actually insert
104 + * the import. (see similar logic in HIRBuilder:resolveBinding)
105 */
56 - CompilerError.invariant(identifiers.has(importSpecifierName) === false, {
57 - reason: `Encountered conflicting import specifier for ${importSpecifierName} in Forget config.`,
58 - description: null,
59 - loc: GeneratedSource,
60 - suggestions: null,
61 - });
62 - CompilerError.invariant(
63 - path.scope.hasBinding(importSpecifierName) === false,
106 + let uid;
107 + if (this.isHookName(name)) {
108 + uid = name;
109 + let i = 0;
110 + while (this.hasReference(uid)) {
111 + this.knownReferencedNames.add(uid);
112 + uid = `${name}_${i++}`;
113 + }
114 + } else if (!this.hasReference(name)) {
115 + uid = name;
116 + } else {
117 + uid = this.scope.generateUid(name);
118 + }
119 + this.knownReferencedNames.add(uid);
120 + return uid;
121 + }
122 +
123 + addMemoCacheImport(): NonLocalImportSpecifier {
124 + return this.addImportSpecifier(
125 {
65 - reason: `Encountered conflicting import specifiers for ${importSpecifierName} in generated program.`,
66 - description: null,
67 - loc: GeneratedSource,
68 - suggestions: null,
126 + source: this.reactRuntimeModule,
127 + importSpecifierName: 'c',
128 },
129 + '_c',
130 );
71 - identifiers.add(importSpecifierName);
72 -
73 - const importSpecifierNameList = getOrInsertDefault(
74 - sortedImports,
75 - source,
76 - [],
77 - );
78 - importSpecifierNameList.push(importSpecifierName);
131 }
132
81 - const stmts: Array<t.ImportDeclaration> = [];
82 - for (const [source, importSpecifierNameList] of sortedImports) {
83 - const importSpecifiers = importSpecifierNameList.map(name => {
84 - const id = t.identifier(name);
85 - return t.importSpecifier(id, id);
86 - });
133 + /**
134 + *
135 + * @param externalFunction
136 + * @param nameHint if defined, will be used as the name of the import specifier
137 + * @returns
138 + */
139 + addImportSpecifier(
140 + {source: module, importSpecifierName: specifier}: ExternalFunction,
141 + nameHint?: string,
142 + ): NonLocalImportSpecifier {
143 + const maybeBinding = this.imports.get(module)?.get(specifier);
144 + if (maybeBinding != null) {
145 + return {...maybeBinding};
146 + }
147
88 - stmts.push(t.importDeclaration(importSpecifiers, t.stringLiteral(source)));
148 + const binding: NonLocalImportSpecifier = {
149 + kind: 'ImportSpecifier',
150 + name: this.newUid(nameHint ?? specifier),
151 + module,
152 + imported: specifier,
153 + };
154 + getOrInsertWith(this.imports, module, () => new Map()).set(specifier, {
155 + ...binding,
156 + });
157 + return binding;
158 }
90 - path.unshiftContainer('body', stmts);
91 -}
92 -
93 -/*
94 - * Matches `import { ... } from <moduleName>;`
95 - * but not `import * as React from <moduleName>;`
96 - */
97 -function isNonNamespacedImport(
98 - importDeclPath: NodePath<t.ImportDeclaration>,
99 - moduleName: string,
100 -): boolean {
101 - return (
102 - importDeclPath.get('source').node.value === moduleName &&
103 - importDeclPath
104 - .get('specifiers')
105 - .every(specifier => specifier.isImportSpecifier()) &&
106 - importDeclPath.node.importKind !== 'type' &&
107 - importDeclPath.node.importKind !== 'typeof'
108 - );
109 -}
159
111 -function hasExistingNonNamespacedImportOfModule(
112 - program: NodePath<t.Program>,
113 - moduleName: string,
114 -): boolean {
115 - let hasExistingImport = false;
116 - program.traverse({
117 - ImportDeclaration(importDeclPath) {
118 - if (isNonNamespacedImport(importDeclPath, moduleName)) {
119 - hasExistingImport = true;
120 - }
121 - },
122 - });
160 + addNewReference(name: string): void {
161 + this.knownReferencedNames.add(name);
162 + }
163
124 - return hasExistingImport;
164 + assertGlobalBinding(
165 + name: string,
166 + localScope?: BabelScope,
167 + ): Result<void, CompilerError> {
168 + const scope = localScope ?? this.scope;
169 + if (!scope.hasReference(name) && !scope.hasBinding(name)) {
170 + return Ok(undefined);
171 + }
172 + const error = new CompilerError();
173 + error.push({
174 + severity: ErrorSeverity.Todo,
175 + reason: 'Encountered conflicting global in generated program',
176 + description: `Conflict from local binding ${name}`,
177 + loc: scope.getBinding(name)?.path.node.loc ?? null,
178 + suggestions: null,
179 + });
180 + return Err(error);
181 + }
182 }
183
127 -/*
128 - * If an existing import of React exists (ie `import { ... } from '<moduleName>'`), inject useMemoCache
129 - * into the list of destructured variables.
130 - */
131 -function addMemoCacheFunctionSpecifierToExistingImport(
184 +function getExistingImports(
185 program: NodePath<t.Program>,
133 - moduleName: string,
134 - identifierName: string,
135 -): boolean {
136 - let didInsertUseMemoCache = false;
186 +): Map<string, NodePath<t.ImportDeclaration>> {
187 + const existingImports = new Map<string, NodePath<t.ImportDeclaration>>();
188 program.traverse({
138 - ImportDeclaration(importDeclPath) {
139 - if (
140 - !didInsertUseMemoCache &&
141 - isNonNamespacedImport(importDeclPath, moduleName)
142 - ) {
143 - importDeclPath.pushContainer(
144 - 'specifiers',
145 - t.importSpecifier(t.identifier(identifierName), t.identifier('c')),
146 - );
147 - didInsertUseMemoCache = true;
189 + ImportDeclaration(path) {
190 + if (isNonNamespacedImport(path)) {
191 + existingImports.set(path.node.source.value, path);
192 }
193 },
194 });
151 - return didInsertUseMemoCache;
195 + return existingImports;
196 }
197
154 -export function updateMemoCacheFunctionImport(
155 - program: NodePath<t.Program>,
156 - moduleName: string,
157 - useMemoCacheIdentifier: string,
198 +export function addImportsToProgram(
199 + path: NodePath<t.Program>,
200 + programContext: ProgramContext,
201 ): void {
159 - /*
160 - * If there isn't already an import of * as React, insert it so useMemoCache doesn't
161 - * throw
162 - */
163 - const hasExistingImport = hasExistingNonNamespacedImportOfModule(
164 - program,
165 - moduleName,
202 + const existingImports = getExistingImports(path);
203 + const stmts: Array<t.ImportDeclaration> = [];
204 + const sortedModules = [...programContext.imports.entries()].sort(([a], [b]) =>
205 + a.localeCompare(b),
206 );
207 + for (const [moduleName, importsMap] of sortedModules) {
208 + for (const [specifierName, loweredImport] of importsMap) {
209 + /**
210 + * Assert that the import identifier hasn't already be declared in the program.
211 + * Note: we use getBinding here since `Scope.hasBinding` pessimistically returns true
212 + * for all allocated uids (from `Scope.getUid`)
213 + */
214 + CompilerError.invariant(
215 + path.scope.getBinding(loweredImport.name) == null,
216 + {
217 + reason:
218 + 'Encountered conflicting import specifiers in generated program',
219 + description: `Conflict from import ${loweredImport.module}:(${loweredImport.imported} as ${loweredImport.name}).`,
220 + loc: GeneratedSource,
221 + suggestions: null,
222 + },
223 + );
224 + CompilerError.invariant(
225 + loweredImport.module === moduleName &&
226 + loweredImport.imported === specifierName,
227 + {
228 + reason:
229 + 'Found inconsistent import specifier. This is an internal bug.',
230 + description: `Expected import ${moduleName}:${specifierName} but found ${loweredImport.module}:${loweredImport.imported}`,
231 + loc: GeneratedSource,
232 + },
233 + );
234 + }
235 + const sortedImport: Array<NonLocalImportSpecifier> = [
236 + ...importsMap.values(),
237 + ].sort(({imported: a}, {imported: b}) => a.localeCompare(b));
238 + const importSpecifiers = sortedImport.map(specifier => {
239 + return t.importSpecifier(
240 + t.identifier(specifier.name),
241 + t.identifier(specifier.imported),
242 + );
243 + });
244
168 - if (hasExistingImport) {
169 - const didUpdateImport = addMemoCacheFunctionSpecifierToExistingImport(
170 - program,
171 - moduleName,
172 - useMemoCacheIdentifier,
173 - );
174 - if (!didUpdateImport) {
175 - throw new Error(
176 - `Expected an ImportDeclaration of \`${moduleName}\` in order to update ImportSpecifiers with useMemoCache`,
245 + /**
246 + * If an existing import of this module exists (ie `import { ... } from
247 + * '<moduleName>'`), inject new imported specifiers into the list of
248 + * destructured variables.
249 + */
250 + const maybeExistingImports = existingImports.get(moduleName);
251 + if (maybeExistingImports != null) {
252 + maybeExistingImports.pushContainer('specifiers', importSpecifiers);
253 + } else {
254 + stmts.push(
255 + t.importDeclaration(importSpecifiers, t.stringLiteral(moduleName)),
256 );
257 }
179 - } else {
180 - addMemoCacheFunctionImportDeclaration(
181 - program,
182 - moduleName,
183 - useMemoCacheIdentifier,
184 - );
258 }
259 + path.unshiftContainer('body', stmts);
260 }
261
188 -function addMemoCacheFunctionImportDeclaration(
189 - program: NodePath<t.Program>,
190 - moduleName: string,
191 - localName: string,
192 -): void {
193 - program.unshiftContainer(
194 - 'body',
195 - t.importDeclaration(
196 - [t.importSpecifier(t.identifier(localName), t.identifier('c'))],
197 - t.stringLiteral(moduleName),
198 - ),
262 +/*
263 + * Matches `import { ... } from <moduleName>;`
264 + * but not `import * as React from <moduleName>;`
265 + * `import type { Foo } from <moduleName>;`
266 + */
267 +function isNonNamespacedImport(
268 + importDeclPath: NodePath<t.ImportDeclaration>,
269 +): boolean {
270 + return (
271 + importDeclPath
272 + .get('specifiers')
273 + .every(specifier => specifier.isImportSpecifier()) &&
274 + importDeclPath.node.importKind !== 'type' &&
275 + importDeclPath.node.importKind !== 'typeof'
276 );
277 }
compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts
+5 -5
@@ -8,7 +8,7 @@
8 import {NodePath} from '@babel/traverse';
9 import * as t from '@babel/types';
10 import prettyFormat from 'pretty-format';
11 -import {Logger} from '.';
11 +import {Logger, ProgramContext} from '.';
12 import {
13 HIRFunction,
14 ReactiveFunction,
@@ -117,7 +117,7 @@ function run(
117 config: EnvironmentConfig,
118 fnType: ReactFunctionType,
119 mode: CompilerMode,
120 - useMemoCacheIdentifier: string,
120 + programContext: ProgramContext,
121 logger: Logger | null,
122 filename: string | null,
123 code: string | null,
@@ -132,7 +132,7 @@ function run(
132 logger,
133 filename,
134 code,
135 - useMemoCacheIdentifier,
135 + programContext,
136 );
137 env.logger?.debugLogIRs?.({
138 kind: 'debug',
@@ -552,7 +552,7 @@ export function compileFn(
552 config: EnvironmentConfig,
553 fnType: ReactFunctionType,
554 mode: CompilerMode,
555 - useMemoCacheIdentifier: string,
555 + programContext: ProgramContext,
556 logger: Logger | null,
557 filename: string | null,
558 code: string | null,
@@ -562,7 +562,7 @@ export function compileFn(
562 config,
563 fnType,
564 mode,
565 - useMemoCacheIdentifier,
565 + programContext,
566 logger,
567 filename,
568 code,
compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts
+34 -97
@@ -12,11 +12,7 @@ import {
12 CompilerErrorDetail,
13 ErrorSeverity,
14 } from '../CompilerError';
15 -import {
16 - EnvironmentConfig,
17 - ExternalFunction,
18 - ReactFunctionType,
19 -} from '../HIR/Environment';
15 +import {EnvironmentConfig, ReactFunctionType} from '../HIR/Environment';
16 import {CodegenFunction} from '../ReactiveScopes';
17 import {isComponentDeclaration} from '../Utils/ComponentDeclaration';
18 import {isHookDeclaration} from '../Utils/HookDeclaration';
@@ -24,10 +20,10 @@ import {assertExhaustive} from '../Utils/utils';
20 import {insertGatedFunctionDeclaration} from './Gating';
21 import {
22 addImportsToProgram,
27 - updateMemoCacheFunctionImport,
23 + ProgramContext,
24 validateRestrictedImports,
25 } from './Imports';
30 -import {PluginOptions} from './Options';
26 +import {CompilerReactTarget, PluginOptions} from './Options';
27 import {compileFn} from './Pipeline';
28 import {
29 filterSuppressionsThatAffectFunction,
@@ -299,8 +295,12 @@ export function compileProgram(
295 handleError(restrictedImportsErr, pass, null);
296 return null;
297 }
302 - const useMemoCacheIdentifier = program.scope.generateUidIdentifier('c');
298
299 + const programContext = new ProgramContext(
300 + program,
301 + pass.opts.target,
302 + environment.hookPattern,
303 + );
304 /*
305 * Record lint errors and critical errors as depending on Forget's config,
306 * we may still need to run Forget's analysis on every function (even if we
@@ -410,7 +410,7 @@ export function compileProgram(
410 environment,
411 fnType,
412 'all_features',
413 - useMemoCacheIdentifier.name,
413 + programContext,
414 pass.opts.logger,
415 pass.filename,
416 pass.code,
@@ -445,7 +445,7 @@ export function compileProgram(
445 environment,
446 fnType,
447 'no_inferred_memo',
448 - useMemoCacheIdentifier.name,
448 + programContext,
449 pass.opts.logger,
450 pass.filename,
451 pass.code,
@@ -453,7 +453,7 @@ export function compileProgram(
453 };
454 if (
455 !compileResult.compiledFn.hasFireRewrite &&
456 - !compileResult.compiledFn.hasLoweredContextAccess
456 + !compileResult.compiledFn.hasInferredEffect
457 ) {
458 return null;
459 }
@@ -554,79 +554,29 @@ export function compileProgram(
554 if (moduleScopeOptOutDirectives.length > 0) {
555 return null;
556 }
557 - let gating: null | {
558 - gatingFn: ExternalFunction;
559 - referencedBeforeDeclared: Set<CompileResult>;
560 - } = null;
561 - if (pass.opts.gating != null) {
562 - gating = {
563 - gatingFn: pass.opts.gating,
564 - referencedBeforeDeclared:
565 - getFunctionReferencedBeforeDeclarationAtTopLevel(program, compiledFns),
566 - };
567 - }
568 -
569 - const hasLoweredContextAccess = compiledFns.some(
570 - c => c.compiledFn.hasLoweredContextAccess,
571 - );
572 - const externalFunctions: Array<ExternalFunction> = [];
573 - try {
574 - // TODO: check for duplicate import specifiers
575 - if (gating != null) {
576 - externalFunctions.push(gating.gatingFn);
577 - }
578 -
579 - const lowerContextAccess = environment.lowerContextAccess;
580 - if (lowerContextAccess && hasLoweredContextAccess) {
581 - externalFunctions.push(lowerContextAccess);
582 - }
583 -
584 - const enableEmitInstrumentForget = environment.enableEmitInstrumentForget;
585 - if (enableEmitInstrumentForget != null) {
586 - externalFunctions.push(enableEmitInstrumentForget.fn);
587 - if (enableEmitInstrumentForget.gating != null) {
588 - externalFunctions.push(enableEmitInstrumentForget.gating);
589 - }
590 - }
591 -
592 - if (environment.enableEmitFreeze != null) {
593 - externalFunctions.push(environment.enableEmitFreeze);
594 - }
595 -
596 - if (environment.enableEmitHookGuards != null) {
597 - externalFunctions.push(environment.enableEmitHookGuards);
598 - }
599 -
600 - if (environment.enableChangeDetectionForDebugging != null) {
601 - externalFunctions.push(environment.enableChangeDetectionForDebugging);
602 - }
603 -
604 - const hasFireRewrite = compiledFns.some(c => c.compiledFn.hasFireRewrite);
605 - if (environment.enableFire && hasFireRewrite) {
606 - externalFunctions.push({
607 - source: getReactCompilerRuntimeModule(pass.opts),
608 - importSpecifierName: 'useFire',
609 - });
610 - }
611 - } catch (err) {
612 - handleError(err, pass, null);
613 - return null;
614 - }
615 -
557 /*
558 * Only insert Forget-ified functions if we have not encountered a critical
559 * error elsewhere in the file, regardless of bailout mode.
560 */
561 + const referencedBeforeDeclared =
562 + pass.opts.gating != null
563 + ? getFunctionReferencedBeforeDeclarationAtTopLevel(program, compiledFns)
564 + : null;
565 for (const result of compiledFns) {
566 const {kind, originalFn, compiledFn} = result;
567 const transformedFn = createNewFunctionNode(originalFn, compiledFn);
568
624 - if (gating != null && kind === 'original') {
569 + if (referencedBeforeDeclared != null && kind === 'original') {
570 + CompilerError.invariant(pass.opts.gating != null, {
571 + reason: "Expected 'gating' import to be present",
572 + loc: null,
573 + });
574 insertGatedFunctionDeclaration(
575 originalFn,
576 transformedFn,
628 - gating.gatingFn,
629 - gating.referencedBeforeDeclared.has(result),
577 + programContext,
578 + pass.opts.gating,
579 + referencedBeforeDeclared.has(result),
580 );
581 } else {
582 originalFn.replaceWith(transformedFn);
@@ -635,22 +585,7 @@ export function compileProgram(
585
586 // Forget compiled the component, we need to update existing imports of useMemoCache
587 if (compiledFns.length > 0) {
638 - let needsMemoCacheFunctionImport = false;
639 - for (const fn of compiledFns) {
640 - if (fn.compiledFn.memoSlotsUsed > 0) {
641 - needsMemoCacheFunctionImport = true;
642 - break;
643 - }
644 - }
645 -
646 - if (needsMemoCacheFunctionImport) {
647 - updateMemoCacheFunctionImport(
648 - program,
649 - getReactCompilerRuntimeModule(pass.opts),
650 - useMemoCacheIdentifier.name,
651 - );
652 - }
653 - addImportsToProgram(program, externalFunctions);
588 + addImportsToProgram(program, programContext);
589 }
590 return {retryErrors};
591 }
@@ -683,7 +618,7 @@ function shouldSkipCompilation(
618 if (
619 hasMemoCacheFunctionImport(
620 program,
686 - getReactCompilerRuntimeModule(pass.opts),
621 + getReactCompilerRuntimeModule(pass.opts.target),
622 )
623 ) {
624 return true;
@@ -1177,16 +1112,18 @@ function getFunctionReferencedBeforeDeclarationAtTopLevel(
1112 return referencedBeforeDeclaration;
1113 }
1114
1180 -function getReactCompilerRuntimeModule(opts: PluginOptions): string {
1181 - if (opts.target === '19') {
1115 +export function getReactCompilerRuntimeModule(
1116 + target: CompilerReactTarget,
1117 +): string {
1118 + if (target === '19') {
1119 return 'react/compiler-runtime'; // from react namespace
1183 - } else if (opts.target === '17' || opts.target === '18') {
1120 + } else if (target === '17' || target === '18') {
1121 return 'react-compiler-runtime'; // npm package
1122 } else {
1123 CompilerError.invariant(
1187 - opts.target != null &&
1188 - opts.target.kind === 'donotuse_meta_internal' &&
1189 - typeof opts.target.runtimeModule === 'string',
1124 + target != null &&
1125 + target.kind === 'donotuse_meta_internal' &&
1126 + typeof target.runtimeModule === 'string',
1127 {
1128 reason: 'Expected target to already be validated',
1129 description: null,
@@ -1194,6 +1131,6 @@ function getReactCompilerRuntimeModule(opts: PluginOptions): string {
1131 suggestions: null,
1132 },
1133 );
1197 - return opts.target.runtimeModule;
1134 + return target.runtimeModule;
1135 }
1136 }
compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts
+12 -5
@@ -15,6 +15,7 @@ import {
15 PanicThresholdOptions,
16 parsePluginOptions,
17 PluginOptions,
18 + ProgramContext,
19 } from '../Entrypoint';
20 import {Err, Ok, Result} from '../Utils/Result';
21 import {
@@ -84,6 +85,8 @@ export const InstrumentationSchema = z
85 );
86
87 export type ExternalFunction = z.infer<typeof ExternalFunctionSchema>;
88 +export const USE_FIRE_FUNCTION_NAME = 'useFire';
89 +export const EMIT_FREEZE_GLOBAL_GATING = '__DEV__';
90
91 export const MacroMethodSchema = z.union([
92 z.object({type: z.literal('wildcard')}),
@@ -846,9 +849,9 @@ export class Environment {
849 config: EnvironmentConfig;
850 fnType: ReactFunctionType;
851 compilerMode: CompilerMode;
849 - useMemoCacheIdentifier: string;
850 - hasLoweredContextAccess: boolean;
852 + programContext: ProgramContext;
853 hasFireRewrite: boolean;
854 + hasInferredEffect: boolean;
855
856 #contextIdentifiers: Set<t.Identifier>;
857 #hoistedIdentifiers: Set<t.Identifier>;
@@ -862,7 +865,7 @@ export class Environment {
865 logger: Logger | null,
866 filename: string | null,
867 code: string | null,
865 - useMemoCacheIdentifier: string,
868 + programContext: ProgramContext,
869 ) {
870 this.#scope = scope;
871 this.fnType = fnType;
@@ -871,11 +874,11 @@ export class Environment {
874 this.filename = filename;
875 this.code = code;
876 this.logger = logger;
874 - this.useMemoCacheIdentifier = useMemoCacheIdentifier;
877 + this.programContext = programContext;
878 this.#shapes = new Map(DEFAULT_SHAPES);
879 this.#globals = new Map(DEFAULT_GLOBALS);
877 - this.hasLoweredContextAccess = false;
880 this.hasFireRewrite = false;
881 + this.hasInferredEffect = false;
882
883 if (
884 config.disableMemoizationForDebugging &&
@@ -937,6 +940,10 @@ export class Environment {
940 return makeScopeId(this.#nextScope++);
941 }
942
943 + get scope(): BabelScope {
944 + return this.#scope;
945 + }
946 +
947 logErrors(errors: Result<void, CompilerError>): void {
948 if (errors.isOk() || this.logger == null) {
949 return;
compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts
+10 -7
@@ -1167,18 +1167,21 @@ export type VariableBinding =
1167 // bindings declard outside the current component/hook
1168 | NonLocalBinding;
1169
1170 +// `import {bar as baz} from 'foo'`: name=baz, module=foo, imported=bar
1171 +export type NonLocalImportSpecifier = {
1172 + kind: 'ImportSpecifier';
1173 + name: string;
1174 + module: string;
1175 + imported: string;
1176 +};
1177 +
1178 export type NonLocalBinding =
1179 // `import Foo from 'foo'`: name=Foo, module=foo
1180 | {kind: 'ImportDefault'; name: string; module: string}
1181 // `import * as Foo from 'foo'`: name=Foo, module=foo
1182 | {kind: 'ImportNamespace'; name: string; module: string}
1175 - // `import {bar as baz} from 'foo'`: name=baz, module=foo, imported=bar
1176 - | {
1177 - kind: 'ImportSpecifier';
1178 - name: string;
1179 - module: string;
1180 - imported: string;
1181 - }
1183 + // `import {bar as baz} from 'foo'`
1184 + | NonLocalImportSpecifier
1185 // let, const, function, etc declared in the module but outside the current component/hook
1186 | {kind: 'ModuleLocal'; name: string}
1187 // an unresolved binding
compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts
+1
@@ -331,6 +331,7 @@ export default class HIRBuilder {
331 type: makeType(),
332 loc: node.loc ?? GeneratedSource,
333 };
334 + this.#env.programContext.addNewReference(name);
335 this.#bindings.set(name, {node, identifier});
336 return identifier;
337 } else if (mapping.node === node) {
compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts
+1
@@ -249,6 +249,7 @@ export function inferEffectDependencies(fn: HIRFunction): void {
249 // Renumber instructions and fix scope ranges
250 markInstructionIds(fn.body);
251 fixScopeAndIdentifierRanges(fn.body);
252 + fn.env.hasInferredEffect = true;
253 }
254 }
255
compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts
+11 -9
@@ -18,6 +18,7 @@ import {
18 Instruction,
19 LoadGlobal,
20 LoadLocal,
21 + NonLocalImportSpecifier,
22 Place,
23 PropertyLoad,
24 isUseContextHookType,
@@ -35,7 +36,7 @@ import {inferTypes} from '../TypeInference';
36
37 export function lowerContextAccess(
38 fn: HIRFunction,
38 - loweredContextCallee: ExternalFunction,
39 + loweredContextCalleeConfig: ExternalFunction,
40 ): void {
41 const contextAccess: Map<IdentifierId, CallExpression> = new Map();
42 const contextKeys: Map<IdentifierId, Array<string>> = new Map();
@@ -79,6 +80,8 @@ export function lowerContextAccess(
80 }
81 }
82
83 + let importLoweredContextCallee: NonLocalImportSpecifier | null = null;
84 +
85 if (contextAccess.size > 0 && contextKeys.size > 0) {
86 for (const [, block] of fn.body.blocks) {
87 let nextInstructions: Array<Instruction> | null = null;
@@ -91,9 +94,13 @@ export function lowerContextAccess(
94 isUseContextHookType(value.callee.identifier) &&
95 contextKeys.has(lvalue.identifier.id)
96 ) {
97 + importLoweredContextCallee ??=
98 + fn.env.programContext.addImportSpecifier(
99 + loweredContextCalleeConfig,
100 + );
101 const loweredContextCalleeInstr = emitLoadLoweredContextCallee(
102 fn.env,
96 - loweredContextCallee,
103 + importLoweredContextCallee,
104 );
105
106 if (nextInstructions === null) {
@@ -122,21 +129,16 @@ export function lowerContextAccess(
129 }
130 markInstructionIds(fn.body);
131 inferTypes(fn);
125 - fn.env.hasLoweredContextAccess = true;
132 }
133 }
134
135 function emitLoadLoweredContextCallee(
136 env: Environment,
131 - loweredContextCallee: ExternalFunction,
137 + importedLowerContextCallee: NonLocalImportSpecifier,
138 ): Instruction {
139 const loadGlobal: LoadGlobal = {
140 kind: 'LoadGlobal',
135 - binding: {
136 - kind: 'ImportNamespace',
137 - module: loweredContextCallee.source,
138 - name: loweredContextCallee.importSpecifierName,
139 - },
141 + binding: {...importedLowerContextCallee},
142 loc: GeneratedSource,
143 };
144
compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineJsx.ts
+3 -1
@@ -196,7 +196,7 @@ function process(
196 return null;
197 }
198
199 - const props = collectProps(jsx);
199 + const props = collectProps(fn.env, jsx);
200 if (!props) return null;
201
202 const outlinedTag = fn.env.generateGloballyUniqueIdentifierName(null).value;
@@ -217,6 +217,7 @@ type OutlinedJsxAttribute = {
217 };
218
219 function collectProps(
220 + env: Environment,
221 instructions: Array<JsxInstruction>,
222 ): Array<OutlinedJsxAttribute> | null {
223 let id = 1;
@@ -227,6 +228,7 @@ function collectProps(
228 newName = `${oldName}${id++}`;
229 }
230 seen.add(newName);
231 + env.programContext.addNewReference(newName);
232 return newName;
233 }
234
compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts
+67 -39
@@ -52,7 +52,8 @@ import {assertExhaustive} from '../Utils/utils';
52 import {buildReactiveFunction} from './BuildReactiveFunction';
53 import {SINGLE_CHILD_FBT_TAGS} from './MemoizeFbtAndMacroOperandsInSameScope';
54 import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors';
55 -import {ReactFunctionType} from '../HIR/Environment';
55 +import {EMIT_FREEZE_GLOBAL_GATING, ReactFunctionType} from '../HIR/Environment';
56 +import {ProgramContext} from '../Entrypoint';
57
58 export const MEMO_CACHE_SENTINEL = 'react.memo_cache_sentinel';
59 export const EARLY_RETURN_SENTINEL = 'react.early_return_sentinel';
@@ -100,9 +101,9 @@ export type CodegenFunction = {
101 }>;
102
103 /**
103 - * This is true if the compiler has the lowered useContext calls.
104 + * This is true if the compiler has compiled inferred effect dependencies
105 */
105 - hasLoweredContextAccess: boolean;
106 + hasInferredEffect: boolean;
107
108 /**
109 * This is true if the compiler has compiled a fire to a useFire call
@@ -160,6 +161,7 @@ export function codegenFunction(
161 compiled.body = t.blockStatement([
162 createHookGuard(
163 hookGuard,
164 + fn.env.programContext,
165 compiled.body.body,
166 GuardKind.PushHookGuard,
167 GuardKind.PopHookGuard,
@@ -170,13 +172,15 @@ export function codegenFunction(
172 const cacheCount = compiled.memoSlotsUsed;
173 if (cacheCount !== 0) {
174 const preface: Array<t.Statement> = [];
175 + const useMemoCacheIdentifier =
176 + fn.env.programContext.addMemoCacheImport().name;
177
178 // The import declaration for `useMemoCache` is inserted in the Babel plugin
179 preface.push(
180 t.variableDeclaration('const', [
181 t.variableDeclarator(
182 t.identifier(cx.synthesizeName('$')),
179 - t.callExpression(t.identifier(fn.env.useMemoCacheIdentifier), [
183 + t.callExpression(t.identifier(useMemoCacheIdentifier), [
184 t.numericLiteral(cacheCount),
185 ]),
186 ),
@@ -259,34 +263,54 @@ export function codegenFunction(
263 * Technically, this is a conditional hook call. However, we expect
264 * __DEV__ and gating identifier to be runtime constants
265 */
262 - let gating: t.Expression;
263 - if (
264 - emitInstrumentForget.gating != null &&
266 + const gating =
267 + emitInstrumentForget.gating != null
268 + ? t.identifier(
269 + fn.env.programContext.addImportSpecifier(
270 + emitInstrumentForget.gating,
271 + ).name,
272 + )
273 + : null;
274 +
275 + const globalGating =
276 emitInstrumentForget.globalGating != null
266 - ) {
267 - gating = t.logicalExpression(
268 - '&&',
269 - t.identifier(emitInstrumentForget.globalGating),
270 - t.identifier(emitInstrumentForget.gating.importSpecifierName),
277 + ? t.identifier(emitInstrumentForget.globalGating)
278 + : null;
279 +
280 + if (emitInstrumentForget.globalGating != null) {
281 + const assertResult = fn.env.programContext.assertGlobalBinding(
282 + emitInstrumentForget.globalGating,
283 );
272 - } else if (emitInstrumentForget.gating != null) {
273 - gating = t.identifier(emitInstrumentForget.gating.importSpecifierName);
284 + if (assertResult.isErr()) {
285 + return assertResult;
286 + }
287 + }
288 +
289 + let ifTest: t.Expression;
290 + if (gating != null && globalGating != null) {
291 + ifTest = t.logicalExpression('&&', globalGating, gating);
292 + } else if (gating != null) {
293 + ifTest = gating;
294 } else {
275 - CompilerError.invariant(emitInstrumentForget.globalGating != null, {
295 + CompilerError.invariant(globalGating != null, {
296 reason:
297 'Bad config not caught! Expected at least one of gating or globalGating',
298 loc: null,
299 suggestions: null,
300 });
281 - gating = t.identifier(emitInstrumentForget.globalGating);
301 + ifTest = globalGating;
302 }
303 +
304 + const instrumentFnIdentifier = fn.env.programContext.addImportSpecifier(
305 + emitInstrumentForget.fn,
306 + ).name;
307 const test: t.IfStatement = t.ifStatement(
284 - gating,
308 + ifTest,
309 t.expressionStatement(
286 - t.callExpression(
287 - t.identifier(emitInstrumentForget.fn.importSpecifierName),
288 - [t.stringLiteral(fn.id), t.stringLiteral(fn.env.filename ?? '')],
289 - ),
310 + t.callExpression(t.identifier(instrumentFnIdentifier), [
311 + t.stringLiteral(fn.id),
312 + t.stringLiteral(fn.env.filename ?? ''),
313 + ]),
314 ),
315 );
316 compiled.body.body.unshift(test);
@@ -363,8 +387,8 @@ function codegenReactiveFunction(
387 prunedMemoBlocks: countMemoBlockVisitor.prunedMemoBlocks,
388 prunedMemoValues: countMemoBlockVisitor.prunedMemoValues,
389 outlined: [],
366 - hasLoweredContextAccess: fn.env.hasLoweredContextAccess,
390 hasFireRewrite: fn.env.hasFireRewrite,
391 + hasInferredEffect: fn.env.hasInferredEffect,
392 });
393 }
394
@@ -553,13 +577,18 @@ function codegenBlockNoReset(
577
578 function wrapCacheDep(cx: Context, value: t.Expression): t.Expression {
579 if (cx.env.config.enableEmitFreeze != null && cx.env.isInferredMemoEnabled) {
556 - // The import declaration for emitFreeze is inserted in the Babel plugin
580 + const emitFreezeIdentifier = cx.env.programContext.addImportSpecifier(
581 + cx.env.config.enableEmitFreeze,
582 + ).name;
583 + cx.env.programContext
584 + .assertGlobalBinding(EMIT_FREEZE_GLOBAL_GATING, cx.env.scope)
585 + .unwrap();
586 return t.conditionalExpression(
558 - t.identifier('__DEV__'),
559 - t.callExpression(
560 - t.identifier(cx.env.config.enableEmitFreeze.importSpecifierName),
561 - [value, t.stringLiteral(cx.fnName)],
562 - ),
587 + t.identifier(EMIT_FREEZE_GLOBAL_GATING),
588 + t.callExpression(t.identifier(emitFreezeIdentifier), [
589 + value,
590 + t.stringLiteral(cx.fnName),
591 + ]),
592 value,
593 );
594 } else {
@@ -713,16 +742,14 @@ function codegenReactiveScope(
742 let computationBlock = codegenBlock(cx, block);
743
744 let memoStatement;
716 - if (
717 - cx.env.config.enableChangeDetectionForDebugging != null &&
718 - changeExpressions.length > 0
719 - ) {
745 + const detectionFunction = cx.env.config.enableChangeDetectionForDebugging;
746 + if (detectionFunction != null && changeExpressions.length > 0) {
747 const loc =
748 typeof scope.loc === 'symbol'
749 ? 'unknown location'
750 : `(${scope.loc.start.line}:${scope.loc.end.line})`;
724 - const detectionFunction =
725 - cx.env.config.enableChangeDetectionForDebugging.importSpecifierName;
751 + const importedDetectionFunctionIdentifier =
752 + cx.env.programContext.addImportSpecifier(detectionFunction).name;
753 const cacheLoadOldValueStatements: Array<t.Statement> = [];
754 const changeDetectionStatements: Array<t.Statement> = [];
755 const idempotenceDetectionStatements: Array<t.Statement> = [];
@@ -744,7 +771,7 @@ function codegenReactiveScope(
771 );
772 changeDetectionStatements.push(
773 t.expressionStatement(
747 - t.callExpression(t.identifier(detectionFunction), [
774 + t.callExpression(t.identifier(importedDetectionFunctionIdentifier), [
775 t.identifier(loadName),
776 t.cloneNode(name, true),
777 t.stringLiteral(name.name),
@@ -756,7 +783,7 @@ function codegenReactiveScope(
783 );
784 idempotenceDetectionStatements.push(
785 t.expressionStatement(
759 - t.callExpression(t.identifier(detectionFunction), [
786 + t.callExpression(t.identifier(importedDetectionFunctionIdentifier), [
787 t.cloneNode(slot, true),
788 t.cloneNode(name, true),
789 t.stringLiteral(name.name),
@@ -1518,15 +1545,15 @@ const createStringLiteral = withLoc(t.stringLiteral);
1545
1546 function createHookGuard(
1547 guard: ExternalFunction,
1548 + context: ProgramContext,
1549 stmts: Array<t.Statement>,
1550 before: GuardKind,
1551 after: GuardKind,
1552 ): t.TryStatement {
1553 + const guardFnName = context.addImportSpecifier(guard).name;
1554 function createHookGuardImpl(kind: number): t.ExpressionStatement {
1555 return t.expressionStatement(
1527 - t.callExpression(t.identifier(guard.importSpecifierName), [
1528 - t.numericLiteral(kind),
1529 - ]),
1556 + t.callExpression(t.identifier(guardFnName), [t.numericLiteral(kind)]),
1557 );
1558 }
1559
@@ -1576,6 +1603,7 @@ function createCallExpression(
1603 t.blockStatement([
1604 createHookGuard(
1605 hookGuard,
1606 + env.programContext,
1607 [t.returnStatement(callExpr)],
1608 GuardKind.AllowHook,
1609 GuardKind.DisallowHook,
compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/RenameVariables.ts
+6 -2
@@ -5,6 +5,7 @@
5 * LICENSE file in the root directory of this source tree.
6 */
7
8 +import {ProgramContext} from '..';
9 import {CompilerError} from '../CompilerError';
10 import {
11 DeclarationId,
@@ -47,7 +48,7 @@ import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors';
48 */
49 export function renameVariables(fn: ReactiveFunction): Set<string> {
50 const globals = collectReferencedGlobals(fn);
50 - const scopes = new Scopes(globals);
51 + const scopes = new Scopes(globals, fn.env.programContext);
52 renameVariablesImpl(fn, new Visitor(), scopes);
53 return new Set([...scopes.names, ...globals]);
54 }
@@ -124,10 +125,12 @@ class Scopes {
125 #seen: Map<DeclarationId, IdentifierName> = new Map();
126 #stack: Array<Map<string, DeclarationId>> = [new Map()];
127 #globals: Set<string>;
128 + #programContext: ProgramContext;
129 names: Set<ValidIdentifierName> = new Set();
130
129 - constructor(globals: Set<string>) {
131 + constructor(globals: Set<string>, programContext: ProgramContext) {
132 this.#globals = globals;
133 + this.#programContext = programContext;
134 }
135
136 visit(identifier: Identifier): void {
@@ -156,6 +159,7 @@ class Scopes {
159 name = `${originalName.value}$${id++}`;
160 }
161 }
162 + this.#programContext.addNewReference(name);
163 const identifierName = makeIdentifierName(name);
164 identifier.name = identifierName;
165 this.#seen.set(identifier.declarationId, identifierName);
compiler/packages/babel-plugin-react-compiler/src/Transform/TransformFire.ts
+17 -8
@@ -28,6 +28,7 @@ import {
28 isUseEffectHookType,
29 LoadLocal,
30 makeInstructionId,
31 + NonLocalImportSpecifier,
32 Place,
33 promoteTemporary,
34 } from '../HIR';
@@ -36,6 +37,7 @@ import {getOrInsertWith} from '../Utils/utils';
37 import {BuiltInFireId, DefaultNonmutatingHook} from '../HIR/ObjectShape';
38 import {eachInstructionOperand} from '../HIR/visitors';
39 import {printSourceLocationLine} from '../HIR/PrintHIR';
40 +import {USE_FIRE_FUNCTION_NAME} from '../HIR/Environment';
41
42 /*
43 * TODO(jmbrown):
@@ -56,6 +58,7 @@ export function transformFire(fn: HIRFunction): void {
58 }
59
60 function replaceFireFunctions(fn: HIRFunction, context: Context): void {
61 + let importedUseFire: NonLocalImportSpecifier | null = null;
62 let hasRewrite = false;
63 for (const [, block] of fn.body.blocks) {
64 const rewriteInstrs = new Map<InstructionId, Array<Instruction>>();
@@ -87,7 +90,15 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void {
90 ] of capturedCallees.entries()) {
91 if (!context.hasCalleeWithInsertedFire(fireCalleePlace)) {
92 context.addCalleeWithInsertedFire(fireCalleePlace);
90 - const loadUseFireInstr = makeLoadUseFireInstruction(fn.env);
93 +
94 + importedUseFire ??= fn.env.programContext.addImportSpecifier({
95 + source: fn.env.programContext.reactRuntimeModule,
96 + importSpecifierName: USE_FIRE_FUNCTION_NAME,
97 + });
98 + const loadUseFireInstr = makeLoadUseFireInstruction(
99 + fn.env,
100 + importedUseFire,
101 + );
102 const loadFireCalleeInstr = makeLoadFireCalleeInstruction(
103 fn.env,
104 fireCalleeInfo.capturedCalleeIdentifier,
@@ -404,18 +415,16 @@ function ensureNoMoreFireUses(fn: HIRFunction, context: Context): void {
415 }
416 }
417
407 -function makeLoadUseFireInstruction(env: Environment): Instruction {
418 +function makeLoadUseFireInstruction(
419 + env: Environment,
420 + importedLoadUseFire: NonLocalImportSpecifier,
421 +): Instruction {
422 const useFirePlace = createTemporaryPlace(env, GeneratedSource);
423 useFirePlace.effect = Effect.Read;
424 useFirePlace.identifier.type = DefaultNonmutatingHook;
425 const instrValue: InstructionValue = {
426 kind: 'LoadGlobal',
413 - binding: {
414 - kind: 'ImportSpecifier',
415 - name: 'useFire',
416 - module: 'react',
417 - imported: 'useFire',
418 - },
427 + binding: {...importedLoadUseFire},
428 loc: GeneratedSource,
429 };
430 return {
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/babel-existing-react-runtime-import.expect.md new
+73
@@ -0,0 +1,73 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +import * as React from 'react';
6 +import {someImport} from 'react/compiler-runtime';
7 +import {calculateExpensiveNumber} from 'shared-runtime';
8 +
9 +function Component(props) {
10 + const [x] = React.useState(0);
11 + const expensiveNumber = React.useMemo(() => calculateExpensiveNumber(x), [x]);
12 +
13 + return (
14 + <div>
15 + {expensiveNumber}
16 + {`${someImport}`}
17 + </div>
18 + );
19 +}
20 +
21 +export const FIXTURE_ENTRYPOINT = {
22 + fn: Component,
23 + params: [],
24 +};
25 +
26 +```
27 +
28 +## Code
29 +
30 +```javascript
31 +import * as React from "react";
32 +import { someImport, c as _c } from "react/compiler-runtime";
33 +import { calculateExpensiveNumber } from "shared-runtime";
34 +
35 +function Component(props) {
36 + const $ = _c(4);
37 + const [x] = React.useState(0);
38 + let t0;
39 + let t1;
40 + if ($[0] !== x) {
41 + t1 = calculateExpensiveNumber(x);
42 + $[0] = x;
43 + $[1] = t1;
44 + } else {
45 + t1 = $[1];
46 + }
47 + t0 = t1;
48 + const expensiveNumber = t0;
49 + let t2;
50 + if ($[2] !== expensiveNumber) {
51 + t2 = (
52 + <div>
53 + {expensiveNumber}
54 + {`${someImport}`}
55 + </div>
56 + );
57 + $[2] = expensiveNumber;
58 + $[3] = t2;
59 + } else {
60 + t2 = $[3];
61 + }
62 + return t2;
63 +}
64 +
65 +export const FIXTURE_ENTRYPOINT = {
66 + fn: Component,
67 + params: [],
68 +};
69 +
70 +```
71 +
72 +### Eval output
73 +(kind: ok) <div>0undefined</div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/babel-existing-react-runtime-import.js new
+20
@@ -0,0 +1,20 @@
1 +import * as React from 'react';
2 +import {someImport} from 'react/compiler-runtime';
3 +import {calculateExpensiveNumber} from 'shared-runtime';
4 +
5 +function Component(props) {
6 + const [x] = React.useState(0);
7 + const expensiveNumber = React.useMemo(() => calculateExpensiveNumber(x), [x]);
8 +
9 + return (
10 + <div>
11 + {expensiveNumber}
12 + {`${someImport}`}
13 + </div>
14 + );
15 +}
16 +
17 +export const FIXTURE_ENTRYPOINT = {
18 + fn: Component,
19 + params: [],
20 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-imports-same-source.expect.md
+2 -2
@@ -14,9 +14,9 @@ function useFoo(props) {
14
15 ```javascript
16 import {
17 - useRenderCounter,
18 - shouldInstrument,
17 makeReadOnly,
18 + shouldInstrument,
19 + useRenderCounter,
20 } from "react-compiler-runtime";
21 import { c as _c } from "react/compiler-runtime"; // @enableEmitFreeze @enableEmitInstrumentForget
22
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.expect.md
+1 -1
@@ -23,7 +23,7 @@ function Foo(props) {
23 ## Code
24
25 ```javascript
26 -import { useRenderCounter, shouldInstrument } from "react-compiler-runtime";
26 +import { shouldInstrument, useRenderCounter } from "react-compiler-runtime";
27 import { c as _c } from "react/compiler-runtime"; // @enableEmitInstrumentForget @compilationMode(annotation)
28
29 function Bar(props) {
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conflict-codegen-instrument-forget.expect.md new
+97
@@ -0,0 +1,97 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @enableEmitInstrumentForget @compilationMode(annotation)
6 +
7 +import {identity} from 'shared-runtime';
8 +
9 +function Bar(props) {
10 + 'use forget';
11 + const shouldInstrument = identity(null);
12 + const _shouldInstrument = identity(null);
13 + const _x2 = () => {
14 + const _shouldInstrument2 = 'hello world';
15 + return identity({_shouldInstrument2});
16 + };
17 + return (
18 + <div style={shouldInstrument} other={_shouldInstrument}>
19 + {props.bar}
20 + </div>
21 + );
22 +}
23 +
24 +function Foo(props) {
25 + 'use forget';
26 + return <Foo>{props.bar}</Foo>;
27 +}
28 +
29 +```
30 +
31 +## Code
32 +
33 +```javascript
34 +import {
35 + shouldInstrument as _shouldInstrument3,
36 + useRenderCounter,
37 +} from "react-compiler-runtime";
38 +import { c as _c } from "react/compiler-runtime"; // @enableEmitInstrumentForget @compilationMode(annotation)
39 +
40 +import { identity } from "shared-runtime";
41 +
42 +function Bar(props) {
43 + "use forget";
44 + if (DEV && _shouldInstrument3)
45 + useRenderCounter("Bar", "/conflict-codegen-instrument-forget.ts");
46 + const $ = _c(4);
47 + let t0;
48 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
49 + t0 = identity(null);
50 + $[0] = t0;
51 + } else {
52 + t0 = $[0];
53 + }
54 + const shouldInstrument = t0;
55 + let t1;
56 + if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
57 + t1 = identity(null);
58 + $[1] = t1;
59 + } else {
60 + t1 = $[1];
61 + }
62 + const _shouldInstrument = t1;
63 + let t2;
64 + if ($[2] !== props.bar) {
65 + t2 = (
66 + <div style={shouldInstrument} other={_shouldInstrument}>
67 + {props.bar}
68 + </div>
69 + );
70 + $[2] = props.bar;
71 + $[3] = t2;
72 + } else {
73 + t2 = $[3];
74 + }
75 + return t2;
76 +}
77 +
78 +function Foo(props) {
79 + "use forget";
80 + if (DEV && _shouldInstrument3)
81 + useRenderCounter("Foo", "/conflict-codegen-instrument-forget.ts");
82 + const $ = _c(2);
83 + let t0;
84 + if ($[0] !== props.bar) {
85 + t0 = <Foo>{props.bar}</Foo>;
86 + $[0] = props.bar;
87 + $[1] = t0;
88 + } else {
89 + t0 = $[1];
90 + }
91 + return t0;
92 +}
93 +
94 +```
95 +
96 +### Eval output
97 +(kind: exception) Fixture not implemented
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conflict-codegen-instrument-forget.js new
+23
@@ -0,0 +1,23 @@
1 +// @enableEmitInstrumentForget @compilationMode(annotation)
2 +
3 +import {identity} from 'shared-runtime';
4 +
5 +function Bar(props) {
6 + 'use forget';
7 + const shouldInstrument = identity(null);
8 + const _shouldInstrument = identity(null);
9 + const _x2 = () => {
10 + const _shouldInstrument2 = 'hello world';
11 + return identity({_shouldInstrument2});
12 + };
13 + return (
14 + <div style={shouldInstrument} other={_shouldInstrument}>
15 + {props.bar}
16 + </div>
17 + );
18 +}
19 +
20 +function Foo(props) {
21 + 'use forget';
22 + return <Foo>{props.bar}</Foo>;
23 +}
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/emit-freeze-conflicting-imports.expect.md new
+37
@@ -0,0 +1,37 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @enableEmitFreeze @instrumentForget
6 +
7 +let makeReadOnly = 'conflicting identifier';
8 +function useFoo(props) {
9 + return foo(props.x);
10 +}
11 +
12 +```
13 +
14 +## Code
15 +
16 +```javascript
17 +import { makeReadOnly as _makeReadOnly } from "react-compiler-runtime";
18 +import { c as _c } from "react/compiler-runtime"; // @enableEmitFreeze @instrumentForget
19 +
20 +let makeReadOnly = "conflicting identifier";
21 +function useFoo(props) {
22 + const $ = _c(2);
23 + let t0;
24 + if ($[0] !== props.x) {
25 + t0 = foo(props.x);
26 + $[0] = props.x;
27 + $[1] = __DEV__ ? _makeReadOnly(t0, "useFoo") : t0;
28 + } else {
29 + t0 = $[1];
30 + }
31 + return t0;
32 +}
33 +
34 +```
35 +
36 +### Eval output
37 +(kind: exception) Fixture not implemented
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/emit-freeze-conflicting-imports.js renamed
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/emit-freeze-nonconflicting-global-reference.expect.md new
+33
@@ -0,0 +1,33 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @enableEmitFreeze @instrumentForget
6 +function useFoo(props) {
7 + return foo(props.x, __DEV__);
8 +}
9 +
10 +```
11 +
12 +## Code
13 +
14 +```javascript
15 +import { makeReadOnly } from "react-compiler-runtime";
16 +import { c as _c } from "react/compiler-runtime"; // @enableEmitFreeze @instrumentForget
17 +function useFoo(props) {
18 + const $ = _c(2);
19 + let t0;
20 + if ($[0] !== props.x) {
21 + t0 = foo(props.x, __DEV__);
22 + $[0] = props.x;
23 + $[1] = __DEV__ ? makeReadOnly(t0, "useFoo") : t0;
24 + } else {
25 + t0 = $[1];
26 + }
27 + return t0;
28 +}
29 +
30 +```
31 +
32 +### Eval output
33 +(kind: exception) Fixture not implemented
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/emit-freeze-nonconflicting-global-reference.js new
+4
@@ -0,0 +1,4 @@
1 +// @enableEmitFreeze @instrumentForget
2 +function useFoo(props) {
3 + return foo(props.x, __DEV__);
4 +}
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.codegen-error-on-conflicting-imports.expect.md deleted
-21
@@ -1,21 +0,0 @@
1 -
2 -## Input
3 -
4 -```javascript
5 -// @enableEmitFreeze @instrumentForget
6 -
7 -let makeReadOnly = 'conflicting identifier';
8 -function useFoo(props) {
9 - return foo(props.x);
10 -}
11 -
12 -```
13 -
14 -
15 -## Error
16 -
17 -```
18 -Invariant: Encountered conflicting import specifiers for makeReadOnly in generated program.
19 -```
20 -
21 -
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.emit-freeze-conflicting-global.expect.md new
+27
@@ -0,0 +1,27 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @enableEmitFreeze @instrumentForget
6 +function useFoo(props) {
7 + const __DEV__ = 'conflicting global';
8 + console.log(__DEV__);
9 + return foo(props.x);
10 +}
11 +
12 +```
13 +
14 +
15 +## Error
16 +
17 +```
18 + 1 | // @enableEmitFreeze @instrumentForget
19 + 2 | function useFoo(props) {
20 +> 3 | const __DEV__ = 'conflicting global';
21 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Todo: Encountered conflicting global in generated program. Conflict from local binding __DEV__ (3:3)
22 + 4 | console.log(__DEV__);
23 + 5 | return foo(props.x);
24 + 6 | }
25 +```
26 +
27 +
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.emit-freeze-conflicting-global.js new
+6
@@ -0,0 +1,6 @@
1 +// @enableEmitFreeze @instrumentForget
2 +function useFoo(props) {
3 + const __DEV__ = 'conflicting global';
4 + console.log(__DEV__);
5 + return foo(props.x);
6 +}
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/arrow-function-expr-gating-test.expect.md
+2 -2
@@ -18,8 +18,8 @@ export const FIXTURE_ENTRYPOINT = {
18 ## Code
19
20 ```javascript
21 -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
22 -import { c as _c } from "react/compiler-runtime"; // @gating
21 +import { c as _c } from "react/compiler-runtime";
22 +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating
23 import { Stringify } from "shared-runtime";
24 const ErrorView = isForgetEnabled_Fixtures()
25 ? (t0) => {
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/codegen-instrument-forget-gating-test.expect.md
+3 -3
@@ -36,9 +36,9 @@ export const FIXTURE_ENTRYPOINT = {
36 ## Code
37
38 ```javascript
39 -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
40 -import { useRenderCounter, shouldInstrument } from "react-compiler-runtime";
41 -import { c as _c } from "react/compiler-runtime"; // @enableEmitInstrumentForget @compilationMode(annotation) @gating
39 +import { shouldInstrument, useRenderCounter } from "react-compiler-runtime";
40 +import { c as _c } from "react/compiler-runtime";
41 +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @enableEmitInstrumentForget @compilationMode(annotation) @gating
42 const Bar = isForgetEnabled_Fixtures()
43 ? function Bar(props) {
44 "use forget";
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/component-syntax-ref-gating.flow.expect.md
+6 -7
@@ -20,14 +20,14 @@ export const FIXTURE_ENTRYPOINT = {
20 ## Code
21
22 ```javascript
23 -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
23 import { c as _c } from "react/compiler-runtime";
24 +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
25 import { Stringify } from "shared-runtime";
26 import * as React from "react";
27
28 const Foo = React.forwardRef(Foo_withRef);
29 -const _isForgetEnabled_Fixtures_result = isForgetEnabled_Fixtures();
30 -function _Foo_withRef_optimized(_$$empty_props_placeholder$$, ref) {
29 +const isForgetEnabled_Fixtures_result = isForgetEnabled_Fixtures();
30 +function Foo_withRef_optimized(_$$empty_props_placeholder$$, ref) {
31 const $ = _c(2);
32 let t0;
33 if ($[0] !== ref) {
@@ -39,16 +39,15 @@ function _Foo_withRef_optimized(_$$empty_props_placeholder$$, ref) {
39 }
40 return t0;
41 }
42 -function _Foo_withRef_unoptimized(
42 +function Foo_withRef_unoptimized(
43 _$$empty_props_placeholder$$: $ReadOnly<{}>,
44 ref: React.RefSetter<Controls>,
45 ): React.Node {
46 return <Stringify ref={ref} />;
47 }
48 function Foo_withRef(arg0, arg1) {
49 - if (_isForgetEnabled_Fixtures_result)
50 - return _Foo_withRef_optimized(arg0, arg1);
51 - else return _Foo_withRef_unoptimized(arg0, arg1);
49 + if (isForgetEnabled_Fixtures_result) return Foo_withRef_optimized(arg0, arg1);
50 + else return Foo_withRef_unoptimized(arg0, arg1);
51 }
52
53 export const FIXTURE_ENTRYPOINT = {
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/conflicting-gating-fn.expect.md new
+62
@@ -0,0 +1,62 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @gating
6 +
7 +export const isForgetEnabled_Fixtures = () => {
8 + 'use no forget';
9 + return false;
10 +};
11 +
12 +export function Bar(props) {
13 + 'use forget';
14 + return <div>{props.bar}</div>;
15 +}
16 +
17 +export const FIXTURE_ENTRYPOINT = {
18 + fn: eval('Bar'),
19 + params: [{bar: 2}],
20 +};
21 +
22 +```
23 +
24 +## Code
25 +
26 +```javascript
27 +import { c as _c } from "react/compiler-runtime";
28 +import { isForgetEnabled_Fixtures as _isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating
29 +
30 +export const isForgetEnabled_Fixtures = () => {
31 + "use no forget";
32 + return false;
33 +};
34 +
35 +export const Bar = _isForgetEnabled_Fixtures()
36 + ? function Bar(props) {
37 + "use forget";
38 + const $ = _c(2);
39 + let t0;
40 + if ($[0] !== props.bar) {
41 + t0 = <div>{props.bar}</div>;
42 + $[0] = props.bar;
43 + $[1] = t0;
44 + } else {
45 + t0 = $[1];
46 + }
47 + return t0;
48 + }
49 + : function Bar(props) {
50 + "use forget";
51 + return <div>{props.bar}</div>;
52 + };
53 +
54 +export const FIXTURE_ENTRYPOINT = {
55 + fn: eval("Bar"),
56 + params: [{ bar: 2 }],
57 +};
58 +
59 +```
60 +
61 +### Eval output
62 +(kind: ok) <div>2</div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/conflicting-gating-fn.js new
+16
@@ -0,0 +1,16 @@
1 +// @gating
2 +
3 +export const isForgetEnabled_Fixtures = () => {
4 + 'use no forget';
5 + return false;
6 +};
7 +
8 +export function Bar(props) {
9 + 'use forget';
10 + return <div>{props.bar}</div>;
11 +}
12 +
13 +export const FIXTURE_ENTRYPOINT = {
14 + fn: eval('Bar'),
15 + params: [{bar: 2}],
16 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-access-function-name-in-component.expect.md
+2 -2
@@ -18,8 +18,8 @@ export const FIXTURE_ENTRYPOINT = {
18 ## Code
19
20 ```javascript
21 -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
22 -import { c as _c } from "react/compiler-runtime"; // @gating
21 +import { c as _c } from "react/compiler-runtime";
22 +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating
23 const Component = isForgetEnabled_Fixtures()
24 ? function Component() {
25 const $ = _c(1);
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-nonreferenced-identifier-collision.expect.md
+2 -2
@@ -24,8 +24,8 @@ export const FIXTURE_ENTRYPOINT = {
24 ## Code
25
26 ```javascript
27 -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
28 -import { c as _c } from "react/compiler-runtime"; // @gating
27 +import { c as _c } from "react/compiler-runtime";
28 +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating
29 import { identity, useHook as useRenamed } from "shared-runtime";
30 const _ = {
31 useHook: isForgetEnabled_Fixtures() ? () => {} : () => {},
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-preserves-function-properties.expect.md
+2 -2
@@ -26,8 +26,8 @@ export const FIXTURE_ENTRYPOINT = {
26 ## Code
27
28 ```javascript
29 -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
30 -import { c as _c } from "react/compiler-runtime"; // @gating
29 +import { c as _c } from "react/compiler-runtime";
30 +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating
31 const Component = isForgetEnabled_Fixtures()
32 ? function Component() {
33 const $ = _c(1);
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-default-function.expect.md
+2 -2
@@ -27,8 +27,8 @@ export const FIXTURE_ENTRYPOINT = {
27 ## Code
28
29 ```javascript
30 -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
31 -import { c as _c } from "react/compiler-runtime"; // @gating @compilationMode(annotation)
30 +import { c as _c } from "react/compiler-runtime";
31 +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode(annotation)
32 const Bar = isForgetEnabled_Fixtures()
33 ? function Bar(props) {
34 "use forget";
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function-and-default.expect.md
+2 -2
@@ -34,8 +34,8 @@ export const FIXTURE_ENTRYPOINT = {
34 ## Code
35
36 ```javascript
37 -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
38 -import { c as _c } from "react/compiler-runtime"; // @gating @compilationMode(annotation)
37 +import { c as _c } from "react/compiler-runtime";
38 +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode(annotation)
39 const Bar = isForgetEnabled_Fixtures()
40 ? function Bar(props) {
41 "use forget";
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function.expect.md
+2 -2
@@ -27,8 +27,8 @@ export const FIXTURE_ENTRYPOINT = {
27 ## Code
28
29 ```javascript
30 -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
31 -import { c as _c } from "react/compiler-runtime"; // @gating @compilationMode(annotation)
30 +import { c as _c } from "react/compiler-runtime";
31 +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode(annotation)
32 export const Bar = isForgetEnabled_Fixtures()
33 ? function Bar(props) {
34 "use forget";
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test.expect.md
+2 -2
@@ -27,8 +27,8 @@ export const FIXTURE_ENTRYPOINT = {
27 ## Code
28
29 ```javascript
30 -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
31 -import { c as _c } from "react/compiler-runtime"; // @gating @compilationMode(annotation)
30 +import { c as _c } from "react/compiler-runtime";
31 +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode(annotation)
32 const Bar = isForgetEnabled_Fixtures()
33 ? function Bar(props) {
34 "use forget";
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-use-before-decl-ref.expect.md
+7 -8
@@ -21,14 +21,14 @@ export const FIXTURE_ENTRYPOINT = {
21 ## Code
22
23 ```javascript
24 -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
25 -import { c as _c } from "react/compiler-runtime"; // @gating
24 +import { c as _c } from "react/compiler-runtime";
25 +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating
26 import { createRef, forwardRef } from "react";
27 import { Stringify } from "shared-runtime";
28
29 const Foo = forwardRef(Foo_withRef);
30 -const _isForgetEnabled_Fixtures_result = isForgetEnabled_Fixtures();
31 -function _Foo_withRef_optimized(props, ref) {
30 +const isForgetEnabled_Fixtures_result = isForgetEnabled_Fixtures();
31 +function Foo_withRef_optimized(props, ref) {
32 const $ = _c(3);
33 let t0;
34 if ($[0] !== props || $[1] !== ref) {
@@ -41,13 +41,12 @@ function _Foo_withRef_optimized(props, ref) {
41 }
42 return t0;
43 }
44 -function _Foo_withRef_unoptimized(props, ref) {
44 +function Foo_withRef_unoptimized(props, ref) {
45 return <Stringify ref={ref} {...props} />;
46 }
47 function Foo_withRef(arg0, arg1) {
48 - if (_isForgetEnabled_Fixtures_result)
49 - return _Foo_withRef_optimized(arg0, arg1);
50 - else return _Foo_withRef_unoptimized(arg0, arg1);
48 + if (isForgetEnabled_Fixtures_result) return Foo_withRef_optimized(arg0, arg1);
49 + else return Foo_withRef_unoptimized(arg0, arg1);
50 }
51
52 export const FIXTURE_ENTRYPOINT = {
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-use-before-decl.expect.md
+7 -7
@@ -22,14 +22,14 @@ export const FIXTURE_ENTRYPOINT = {
22 ## Code
23
24 ```javascript
25 -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
26 -import { c as _c } from "react/compiler-runtime"; // @gating
25 +import { c as _c } from "react/compiler-runtime";
26 +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating
27 import { memo } from "react";
28 import { Stringify } from "shared-runtime";
29
30 export default memo(Foo);
31 -const _isForgetEnabled_Fixtures_result = isForgetEnabled_Fixtures();
32 -function _Foo_optimized(t0) {
31 +const isForgetEnabled_Fixtures_result = isForgetEnabled_Fixtures();
32 +function Foo_optimized(t0) {
33 "use memo";
34 const $ = _c(3);
35 const { prop1, prop2 } = t0;
@@ -44,13 +44,13 @@ function _Foo_optimized(t0) {
44 }
45 return t1;
46 }
47 -function _Foo_unoptimized({ prop1, prop2 }) {
47 +function Foo_unoptimized({ prop1, prop2 }) {
48 "use memo";
49 return <Stringify prop1={prop1} prop2={prop2} />;
50 }
51 function Foo(arg0) {
52 - if (_isForgetEnabled_Fixtures_result) return _Foo_optimized(arg0);
53 - else return _Foo_unoptimized(arg0);
52 + if (isForgetEnabled_Fixtures_result) return Foo_optimized(arg0);
53 + else return Foo_unoptimized(arg0);
54 }
55
56 export const FIXTURE_ENTRYPOINT = {
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-with-hoisted-type-reference.flow.expect.md
+1 -1
@@ -23,8 +23,8 @@ export const FIXTURE_ENTRYPOINT = {
23 ## Code
24
25 ```javascript
26 -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
26 import { c as _c } from "react/compiler-runtime";
27 +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
28 import { memo } from "react";
29
30 type Props = React.ElementConfig<typeof Component>;
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/infer-function-expression-React-memo-gating.expect.md
+2 -2
@@ -13,8 +13,8 @@ export default React.forwardRef(function notNamedLikeAComponent(props) {
13 ## Code
14
15 ```javascript
16 -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
17 -import { c as _c } from "react/compiler-runtime"; // @gating @compilationMode(infer)
16 +import { c as _c } from "react/compiler-runtime";
17 +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode(infer)
18 import React from "react";
19 export default React.forwardRef(
20 isForgetEnabled_Fixtures()
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/invalid-fnexpr-reference.expect.md
+2 -2
@@ -23,8 +23,8 @@ export const FIXTURE_ENTRYPOINT = {
23 ## Code
24
25 ```javascript
26 -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
27 -import { c as _c } from "react/compiler-runtime"; // @gating
26 +import { c as _c } from "react/compiler-runtime";
27 +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating
28 import * as React from "react";
29
30 let Foo;
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/multi-arrow-expr-export-default-gating-test.expect.md
+2 -2
@@ -19,8 +19,8 @@ export default props => (
19 ## Code
20
21 ```javascript
22 -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
23 -import { c as _c } from "react/compiler-runtime"; // @gating
22 +import { c as _c } from "react/compiler-runtime";
23 +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating
24 import { Stringify } from "shared-runtime";
25
26 const ErrorView = isForgetEnabled_Fixtures()
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/multi-arrow-expr-export-gating-test.expect.md
+2 -2
@@ -24,8 +24,8 @@ export const FIXTURE_ENTRYPOINT = {
24 ## Code
25
26 ```javascript
27 -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
28 -import { c as _c } from "react/compiler-runtime"; // @gating
27 +import { c as _c } from "react/compiler-runtime";
28 +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating
29 import { Stringify } from "shared-runtime";
30
31 const ErrorView = isForgetEnabled_Fixtures()
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/multi-arrow-expr-gating-test.expect.md
+2 -2
@@ -26,8 +26,8 @@ export const FIXTURE_ENTRYPOINT = {
26 ## Code
27
28 ```javascript
29 -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
30 -import { c as _c } from "react/compiler-runtime"; // @gating
29 +import { c as _c } from "react/compiler-runtime";
30 +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating
31 import { Stringify } from "shared-runtime";
32
33 const ErrorView = isForgetEnabled_Fixtures()
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/reassigned-fnexpr-variable.expect.md
+2 -2
@@ -31,8 +31,8 @@ export const FIXTURE_ENTRYPOINT = {
31 ## Code
32
33 ```javascript
34 -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
35 -import { c as _c } from "react/compiler-runtime"; // @gating
34 +import { c as _c } from "react/compiler-runtime";
35 +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating
36 import * as React from "react";
37
38 /**
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-access-hook-guard.expect.md new
+66
@@ -0,0 +1,66 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @lowerContextAccess @enableEmitHookGuards
6 +function App() {
7 + const {foo} = useContext(MyContext);
8 + const {bar} = useContext(MyContext);
9 + return <Bar foo={foo} bar={bar} />;
10 +}
11 +
12 +```
13 +
14 +## Code
15 +
16 +```javascript
17 +import {
18 + $dispatcherGuard,
19 + useContext_withSelector,
20 +} from "react-compiler-runtime";
21 +import { c as _c } from "react/compiler-runtime"; // @lowerContextAccess @enableEmitHookGuards
22 +function App() {
23 + const $ = _c(3);
24 + try {
25 + $dispatcherGuard(0);
26 + const { foo } = (function () {
27 + try {
28 + $dispatcherGuard(2);
29 + return useContext_withSelector(MyContext, _temp);
30 + } finally {
31 + $dispatcherGuard(3);
32 + }
33 + })();
34 + const { bar } = (function () {
35 + try {
36 + $dispatcherGuard(2);
37 + return useContext_withSelector(MyContext, _temp2);
38 + } finally {
39 + $dispatcherGuard(3);
40 + }
41 + })();
42 + let t0;
43 + if ($[0] !== bar || $[1] !== foo) {
44 + t0 = <Bar foo={foo} bar={bar} />;
45 + $[0] = bar;
46 + $[1] = foo;
47 + $[2] = t0;
48 + } else {
49 + t0 = $[2];
50 + }
51 + return t0;
52 + } finally {
53 + $dispatcherGuard(1);
54 + }
55 +}
56 +function _temp2(t0) {
57 + return [t0.bar];
58 +}
59 +function _temp(t0) {
60 + return [t0.foo];
61 +}
62 +
63 +```
64 +
65 +### Eval output
66 +(kind: exception) Fixture not implemented
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-access-hook-guard.js new
+6
@@ -0,0 +1,6 @@
1 +// @lowerContextAccess @enableEmitHookGuards
2 +function App() {
3 + const {foo} = useContext(MyContext);
4 + const {bar} = useContext(MyContext);
5 + return <Bar foo={foo} bar={bar} />;
6 +}
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/no-fire-todo-syntax-shouldnt-throw.expect.md
+1 -2
@@ -43,8 +43,7 @@ function FireComponent(props) {
43 ## Code
44
45 ```javascript
46 -import { useFire } from "react/compiler-runtime";
47 -import { c as _c } from "react/compiler-runtime"; // @enableFire @panicThreshold(none)
46 +import { c as _c, useFire } from "react/compiler-runtime"; // @enableFire @panicThreshold(none)
47 import { fire } from "react";
48
49 /**
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/basic.expect.md
+1 -2
@@ -21,8 +21,7 @@ function Component(props) {
21 ## Code
22
23 ```javascript
24 -import { useFire } from "react/compiler-runtime";
25 -import { c as _c } from "react/compiler-runtime"; // @enableFire
24 +import { c as _c, useFire } from "react/compiler-runtime"; // @enableFire
25 import { fire } from "react";
26
27 function Component(props) {
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/deep-scope.expect.md
+1 -2
@@ -30,8 +30,7 @@ function Component(props) {
30 ## Code
31
32 ```javascript
33 -import { useFire } from "react/compiler-runtime";
34 -import { c as _c } from "react/compiler-runtime"; // @enableFire
33 +import { c as _c, useFire } from "react/compiler-runtime"; // @enableFire
34 import { fire } from "react";
35
36 function Component(props) {
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/fire-and-autodeps.expect.md
+1 -2
@@ -21,8 +21,7 @@ function Component(props) {
21 ## Code
22
23 ```javascript
24 -import { useFire } from "react/compiler-runtime";
25 -import { c as _c } from "react/compiler-runtime"; // @enableFire @inferEffectDependencies
24 +import { c as _c, useFire } from "react/compiler-runtime"; // @enableFire @inferEffectDependencies
25 import { fire, useEffect } from "react";
26
27 function Component(props) {
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/hook-guard.expect.md new
+72
@@ -0,0 +1,72 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @enableFire @enableEmitHookGuards
6 +import {fire} from 'react';
7 +
8 +function Component(props) {
9 + const foo = props => {
10 + console.log(props);
11 + };
12 + useEffect(() => {
13 + fire(foo(props));
14 + });
15 +
16 + return null;
17 +}
18 +
19 +```
20 +
21 +## Code
22 +
23 +```javascript
24 +import { $dispatcherGuard } from "react-compiler-runtime";
25 +import { c as _c, useFire } from "react/compiler-runtime"; // @enableFire @enableEmitHookGuards
26 +import { fire } from "react";
27 +
28 +function Component(props) {
29 + const $ = _c(3);
30 + try {
31 + $dispatcherGuard(0);
32 + const foo = _temp;
33 + const t0 = (function () {
34 + try {
35 + $dispatcherGuard(2);
36 + return useFire(foo);
37 + } finally {
38 + $dispatcherGuard(3);
39 + }
40 + })();
41 + let t1;
42 + if ($[0] !== props || $[1] !== t0) {
43 + t1 = () => {
44 + t0(props);
45 + };
46 + $[0] = props;
47 + $[1] = t0;
48 + $[2] = t1;
49 + } else {
50 + t1 = $[2];
51 + }
52 + (function () {
53 + try {
54 + $dispatcherGuard(2);
55 + return useEffect(t1);
56 + } finally {
57 + $dispatcherGuard(3);
58 + }
59 + })();
60 + return null;
61 + } finally {
62 + $dispatcherGuard(1);
63 + }
64 +}
65 +function _temp(props_0) {
66 + console.log(props_0);
67 +}
68 +
69 +```
70 +
71 +### Eval output
72 +(kind: exception) Fixture not implemented
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/hook-guard.js new
+13
@@ -0,0 +1,13 @@
1 +// @enableFire @enableEmitHookGuards
2 +import {fire} from 'react';
3 +
4 +function Component(props) {
5 + const foo = props => {
6 + console.log(props);
7 + };
8 + useEffect(() => {
9 + fire(foo(props));
10 + });
11 +
12 + return null;
13 +}
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/multiple-scope.expect.md
+1 -2
@@ -29,8 +29,7 @@ function Component(props) {
29 ## Code
30
31 ```javascript
32 -import { useFire } from "react/compiler-runtime";
33 -import { c as _c } from "react/compiler-runtime"; // @enableFire
32 +import { c as _c, useFire } from "react/compiler-runtime"; // @enableFire
33 import { fire } from "react";
34
35 function Component(props) {
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/repeated-calls.expect.md
+1 -2
@@ -22,8 +22,7 @@ function Component(props) {
22 ## Code
23
24 ```javascript
25 -import { useFire } from "react/compiler-runtime";
26 -import { c as _c } from "react/compiler-runtime"; // @enableFire
25 +import { c as _c, useFire } from "react/compiler-runtime"; // @enableFire
26 import { fire } from "react";
27
28 function Component(props) {
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/repro-dont-add-hook-guards-on-retry.expect.md
-1
@@ -23,7 +23,6 @@ function Component(props, useDynamicHook) {
23 ## Code
24
25 ```javascript
26 -import { $dispatcherGuard } from "react-compiler-runtime";
26 import { useFire } from "react/compiler-runtime";
27 import { useEffect, fire } from "react";
28
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/rewrite-deps.expect.md
+1 -2
@@ -21,8 +21,7 @@ function Component(props) {
21 ## Code
22
23 ```javascript
24 -import { useFire } from "react/compiler-runtime";
25 -import { c as _c } from "react/compiler-runtime"; // @enableFire
24 +import { c as _c, useFire } from "react/compiler-runtime"; // @enableFire
25 import { fire } from "react";
26
27 function Component(props) {
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/shared-hook-calls.expect.md
+1 -2
@@ -26,8 +26,7 @@ function Component({bar, baz}) {
26 ## Code
27
28 ```javascript
29 -import { useFire } from "react/compiler-runtime";
30 -import { c as _c } from "react/compiler-runtime"; // @enableFire
29 +import { c as _c, useFire } from "react/compiler-runtime"; // @enableFire
30 import { fire } from "react";
31
32 function Component(t0) {
compiler/packages/babel-plugin-react-compiler/src/index.ts
+1
@@ -19,6 +19,7 @@ export {
19 parsePluginOptions,
20 OPT_OUT_DIRECTIVES,
21 OPT_IN_DIRECTIVES,
22 + ProgramContext,
23 findDirectiveEnablingMemoization,
24 findDirectiveDisablingMemoization,
25 type CompilerPipelineValue,