From d2a5085b7c243c25183ed3866651200d99ae931e Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Sun, 23 Mar 2025 23:29:20 -0400 Subject: [PATCH] [compiler] Avoid failing builds when import specifiers conflict or shadow vars 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 } ``` --- .../src/Entrypoint/Gating.ts | 33 +- .../src/Entrypoint/Imports.ts | 335 +++++++++++------- .../src/Entrypoint/Pipeline.ts | 10 +- .../src/Entrypoint/Program.ts | 131 ++----- .../src/HIR/Environment.ts | 17 +- .../src/HIR/HIR.ts | 17 +- .../src/HIR/HIRBuilder.ts | 1 + .../src/Inference/InferEffectDependencies.ts | 1 + .../src/Optimization/LowerContextAccess.ts | 20 +- .../src/Optimization/OutlineJsx.ts | 4 +- .../ReactiveScopes/CodegenReactiveFunction.ts | 106 ++++-- .../src/ReactiveScopes/RenameVariables.ts | 8 +- .../src/Transform/TransformFire.ts | 25 +- ...el-existing-react-runtime-import.expect.md | 73 ++++ .../babel-existing-react-runtime-import.js | 20 ++ ...codegen-emit-imports-same-source.expect.md | 4 +- .../codegen-instrument-forget-test.expect.md | 2 +- ...nflict-codegen-instrument-forget.expect.md | 97 +++++ .../conflict-codegen-instrument-forget.js | 23 ++ .../emit-freeze-conflicting-imports.expect.md | 37 ++ ....js => emit-freeze-conflicting-imports.js} | 0 ...-nonconflicting-global-reference.expect.md | 33 ++ ...-freeze-nonconflicting-global-reference.js | 4 + ...gen-error-on-conflicting-imports.expect.md | 21 -- ...r.emit-freeze-conflicting-global.expect.md | 27 ++ .../error.emit-freeze-conflicting-global.js | 6 + .../arrow-function-expr-gating-test.expect.md | 4 +- ...en-instrument-forget-gating-test.expect.md | 6 +- ...component-syntax-ref-gating.flow.expect.md | 13 +- .../gating/conflicting-gating-fn.expect.md | 62 ++++ .../compiler/gating/conflicting-gating-fn.js | 16 + ...ccess-function-name-in-component.expect.md | 4 +- ...nreferenced-identifier-collision.expect.md | 4 +- ...ng-preserves-function-properties.expect.md | 4 +- ...ing-test-export-default-function.expect.md | 4 +- ...test-export-function-and-default.expect.md | 4 +- .../gating-test-export-function.expect.md | 4 +- .../compiler/gating/gating-test.expect.md | 4 +- .../gating-use-before-decl-ref.expect.md | 15 +- .../gating/gating-use-before-decl.expect.md | 14 +- ...with-hoisted-type-reference.flow.expect.md | 2 +- ...ion-expression-React-memo-gating.expect.md | 4 +- .../gating/invalid-fnexpr-reference.expect.md | 4 +- ...-expr-export-default-gating-test.expect.md | 4 +- ...ti-arrow-expr-export-gating-test.expect.md | 4 +- .../multi-arrow-expr-gating-test.expect.md | 4 +- .../reassigned-fnexpr-variable.expect.md | 4 +- .../lower-context-access-hook-guard.expect.md | 66 ++++ .../lower-context-access-hook-guard.js | 6 + ...-fire-todo-syntax-shouldnt-throw.expect.md | 3 +- .../compiler/transform-fire/basic.expect.md | 3 +- .../transform-fire/deep-scope.expect.md | 3 +- .../fire-and-autodeps.expect.md | 3 +- .../transform-fire/hook-guard.expect.md | 72 ++++ .../compiler/transform-fire/hook-guard.js | 13 + .../transform-fire/multiple-scope.expect.md | 3 +- .../transform-fire/repeated-calls.expect.md | 3 +- ...ro-dont-add-hook-guards-on-retry.expect.md | 1 - .../transform-fire/rewrite-deps.expect.md | 3 +- .../shared-hook-calls.expect.md | 3 +- .../babel-plugin-react-compiler/src/index.ts | 1 + 61 files changed, 1013 insertions(+), 409 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/babel-existing-react-runtime-import.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/babel-existing-react-runtime-import.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conflict-codegen-instrument-forget.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conflict-codegen-instrument-forget.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/emit-freeze-conflicting-imports.expect.md rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{error.codegen-error-on-conflicting-imports.js => emit-freeze-conflicting-imports.js} (100%) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/emit-freeze-nonconflicting-global-reference.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/emit-freeze-nonconflicting-global-reference.js delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.codegen-error-on-conflicting-imports.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.emit-freeze-conflicting-global.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.emit-freeze-conflicting-global.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/conflicting-gating-fn.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/conflicting-gating-fn.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-access-hook-guard.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-access-hook-guard.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/hook-guard.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/hook-guard.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Gating.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Gating.ts index 16ef7986ef16..679389277b59 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Gating.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Gating.ts @@ -7,8 +7,9 @@ import {NodePath} from '@babel/core'; import * as t from '@babel/types'; -import {PluginOptions} from './Options'; import {CompilerError} from '../CompilerError'; +import {ProgramContext} from './Imports'; +import {ExternalFunction} from '..'; /** * Gating rewrite for function declarations which are referenced before their @@ -34,7 +35,8 @@ import {CompilerError} from '../CompilerError'; function insertAdditionalFunctionDeclaration( fnPath: NodePath, compiled: t.FunctionDeclaration, - gating: NonNullable, + programContext: ProgramContext, + gatingFunctionIdentifierName: string, ): void { const originalFnName = fnPath.node.id; const originalFnParams = fnPath.node.params; @@ -57,14 +59,14 @@ function insertAdditionalFunctionDeclaration( loc: fnPath.node.loc ?? null, }); - const gatingCondition = fnPath.scope.generateUidIdentifier( - `${gating.importSpecifierName}_result`, + const gatingCondition = t.identifier( + programContext.newUid(`${gatingFunctionIdentifierName}_result`), ); - const unoptimizedFnName = fnPath.scope.generateUidIdentifier( - `${originalFnName.name}_unoptimized`, + const unoptimizedFnName = t.identifier( + programContext.newUid(`${originalFnName.name}_unoptimized`), ); - const optimizedFnName = fnPath.scope.generateUidIdentifier( - `${originalFnName.name}_optimized`, + const optimizedFnName = t.identifier( + programContext.newUid(`${originalFnName.name}_optimized`), ); /** * Step 1: rename existing functions @@ -115,7 +117,7 @@ function insertAdditionalFunctionDeclaration( t.variableDeclaration('const', [ t.variableDeclarator( gatingCondition, - t.callExpression(t.identifier(gating.importSpecifierName), []), + t.callExpression(t.identifier(gatingFunctionIdentifierName), []), ), ]), ); @@ -129,19 +131,26 @@ export function insertGatedFunctionDeclaration( | t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression, - gating: NonNullable, + programContext: ProgramContext, + gating: ExternalFunction, referencedBeforeDeclaration: boolean, ): void { + const gatingImportedName = programContext.addImportSpecifier(gating).name; if (referencedBeforeDeclaration && fnPath.isFunctionDeclaration()) { CompilerError.invariant(compiled.type === 'FunctionDeclaration', { reason: 'Expected compiled node type to match input type', description: `Got ${compiled.type} but expected FunctionDeclaration`, loc: fnPath.node.loc ?? null, }); - insertAdditionalFunctionDeclaration(fnPath, compiled, gating); + insertAdditionalFunctionDeclaration( + fnPath, + compiled, + programContext, + gatingImportedName, + ); } else { const gatingExpression = t.conditionalExpression( - t.callExpression(t.identifier(gating.importSpecifierName), []), + t.callExpression(t.identifier(gatingImportedName), []), buildFunctionExpression(compiled), buildFunctionExpression(fnPath.node), ); diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts index 1a19d0197665..704f696b3b86 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts @@ -7,9 +7,19 @@ import {NodePath} from '@babel/core'; import * as t from '@babel/types'; +import {Scope as BabelScope} from '@babel/traverse'; + import {CompilerError, ErrorSeverity} from '../CompilerError'; -import {EnvironmentConfig, ExternalFunction, GeneratedSource} from '../HIR'; -import {getOrInsertDefault} from '../Utils/utils'; +import { + EnvironmentConfig, + GeneratedSource, + NonLocalImportSpecifier, +} from '../HIR'; +import {getOrInsertWith} from '../Utils/utils'; +import {ExternalFunction, isHookName} from '../HIR/Environment'; +import {Err, Ok, Result} from '../Utils/Result'; +import {CompilerReactTarget} from './Options'; +import {getReactCompilerRuntimeModule} from './Program'; export function validateRestrictedImports( path: NodePath, @@ -42,159 +52,226 @@ export function validateRestrictedImports( } } -export function addImportsToProgram( - path: NodePath, - importList: Array, -): void { - const identifiers: Set = new Set(); - const sortedImports: Map> = new Map(); - for (const {importSpecifierName, source} of importList) { - /* - * Codegen currently does not rename import specifiers, so we do additional - * validation here +export class ProgramContext { + /* Program and environment context */ + scope: BabelScope; + reactRuntimeModule: string; + hookPattern: string | null; + + // known generated or referenced identifiers in the program + knownReferencedNames: Set = new Set(); + // generated imports + imports: Map> = new Map(); + + constructor( + program: NodePath, + reactRuntimeModule: CompilerReactTarget, + hookPattern: string | null, + ) { + this.hookPattern = hookPattern; + this.scope = program.scope; + this.reactRuntimeModule = getReactCompilerRuntimeModule(reactRuntimeModule); + } + + isHookName(name: string): boolean { + if (this.hookPattern == null) { + return isHookName(name); + } else { + const match = new RegExp(this.hookPattern).exec(name); + return ( + match != null && typeof match[1] === 'string' && isHookName(match[1]) + ); + } + } + + hasReference(name: string): boolean { + return ( + this.knownReferencedNames.has(name) || + this.scope.hasBinding(name) || + this.scope.hasGlobal(name) || + this.scope.hasReference(name) + ); + } + + newUid(name: string): string { + /** + * Don't call babel's generateUid for known hook imports, as + * InferTypes might eventually type `HookKind` based on callee naming + * convention and `_useFoo` is not named as a hook. + * + * Local uid generation is susceptible to check-before-use bugs since we're + * checking for naming conflicts / references long before we actually insert + * the import. (see similar logic in HIRBuilder:resolveBinding) */ - CompilerError.invariant(identifiers.has(importSpecifierName) === false, { - reason: `Encountered conflicting import specifier for ${importSpecifierName} in Forget config.`, - description: null, - loc: GeneratedSource, - suggestions: null, - }); - CompilerError.invariant( - path.scope.hasBinding(importSpecifierName) === false, + let uid; + if (this.isHookName(name)) { + uid = name; + let i = 0; + while (this.hasReference(uid)) { + this.knownReferencedNames.add(uid); + uid = `${name}_${i++}`; + } + } else if (!this.hasReference(name)) { + uid = name; + } else { + uid = this.scope.generateUid(name); + } + this.knownReferencedNames.add(uid); + return uid; + } + + addMemoCacheImport(): NonLocalImportSpecifier { + return this.addImportSpecifier( { - reason: `Encountered conflicting import specifiers for ${importSpecifierName} in generated program.`, - description: null, - loc: GeneratedSource, - suggestions: null, + source: this.reactRuntimeModule, + importSpecifierName: 'c', }, + '_c', ); - identifiers.add(importSpecifierName); - - const importSpecifierNameList = getOrInsertDefault( - sortedImports, - source, - [], - ); - importSpecifierNameList.push(importSpecifierName); } - const stmts: Array = []; - for (const [source, importSpecifierNameList] of sortedImports) { - const importSpecifiers = importSpecifierNameList.map(name => { - const id = t.identifier(name); - return t.importSpecifier(id, id); - }); + /** + * + * @param externalFunction + * @param nameHint if defined, will be used as the name of the import specifier + * @returns + */ + addImportSpecifier( + {source: module, importSpecifierName: specifier}: ExternalFunction, + nameHint?: string, + ): NonLocalImportSpecifier { + const maybeBinding = this.imports.get(module)?.get(specifier); + if (maybeBinding != null) { + return {...maybeBinding}; + } - stmts.push(t.importDeclaration(importSpecifiers, t.stringLiteral(source))); + const binding: NonLocalImportSpecifier = { + kind: 'ImportSpecifier', + name: this.newUid(nameHint ?? specifier), + module, + imported: specifier, + }; + getOrInsertWith(this.imports, module, () => new Map()).set(specifier, { + ...binding, + }); + return binding; } - path.unshiftContainer('body', stmts); -} - -/* - * Matches `import { ... } from ;` - * but not `import * as React from ;` - */ -function isNonNamespacedImport( - importDeclPath: NodePath, - moduleName: string, -): boolean { - return ( - importDeclPath.get('source').node.value === moduleName && - importDeclPath - .get('specifiers') - .every(specifier => specifier.isImportSpecifier()) && - importDeclPath.node.importKind !== 'type' && - importDeclPath.node.importKind !== 'typeof' - ); -} -function hasExistingNonNamespacedImportOfModule( - program: NodePath, - moduleName: string, -): boolean { - let hasExistingImport = false; - program.traverse({ - ImportDeclaration(importDeclPath) { - if (isNonNamespacedImport(importDeclPath, moduleName)) { - hasExistingImport = true; - } - }, - }); + addNewReference(name: string): void { + this.knownReferencedNames.add(name); + } - return hasExistingImport; + assertGlobalBinding( + name: string, + localScope?: BabelScope, + ): Result { + const scope = localScope ?? this.scope; + if (!scope.hasReference(name) && !scope.hasBinding(name)) { + return Ok(undefined); + } + const error = new CompilerError(); + error.push({ + severity: ErrorSeverity.Todo, + reason: 'Encountered conflicting global in generated program', + description: `Conflict from local binding ${name}`, + loc: scope.getBinding(name)?.path.node.loc ?? null, + suggestions: null, + }); + return Err(error); + } } -/* - * If an existing import of React exists (ie `import { ... } from ''`), inject useMemoCache - * into the list of destructured variables. - */ -function addMemoCacheFunctionSpecifierToExistingImport( +function getExistingImports( program: NodePath, - moduleName: string, - identifierName: string, -): boolean { - let didInsertUseMemoCache = false; +): Map> { + const existingImports = new Map>(); program.traverse({ - ImportDeclaration(importDeclPath) { - if ( - !didInsertUseMemoCache && - isNonNamespacedImport(importDeclPath, moduleName) - ) { - importDeclPath.pushContainer( - 'specifiers', - t.importSpecifier(t.identifier(identifierName), t.identifier('c')), - ); - didInsertUseMemoCache = true; + ImportDeclaration(path) { + if (isNonNamespacedImport(path)) { + existingImports.set(path.node.source.value, path); } }, }); - return didInsertUseMemoCache; + return existingImports; } -export function updateMemoCacheFunctionImport( - program: NodePath, - moduleName: string, - useMemoCacheIdentifier: string, +export function addImportsToProgram( + path: NodePath, + programContext: ProgramContext, ): void { - /* - * If there isn't already an import of * as React, insert it so useMemoCache doesn't - * throw - */ - const hasExistingImport = hasExistingNonNamespacedImportOfModule( - program, - moduleName, + const existingImports = getExistingImports(path); + const stmts: Array = []; + const sortedModules = [...programContext.imports.entries()].sort(([a], [b]) => + a.localeCompare(b), ); + for (const [moduleName, importsMap] of sortedModules) { + for (const [specifierName, loweredImport] of importsMap) { + /** + * Assert that the import identifier hasn't already be declared in the program. + * Note: we use getBinding here since `Scope.hasBinding` pessimistically returns true + * for all allocated uids (from `Scope.getUid`) + */ + CompilerError.invariant( + path.scope.getBinding(loweredImport.name) == null, + { + reason: + 'Encountered conflicting import specifiers in generated program', + description: `Conflict from import ${loweredImport.module}:(${loweredImport.imported} as ${loweredImport.name}).`, + loc: GeneratedSource, + suggestions: null, + }, + ); + CompilerError.invariant( + loweredImport.module === moduleName && + loweredImport.imported === specifierName, + { + reason: + 'Found inconsistent import specifier. This is an internal bug.', + description: `Expected import ${moduleName}:${specifierName} but found ${loweredImport.module}:${loweredImport.imported}`, + loc: GeneratedSource, + }, + ); + } + const sortedImport: Array = [ + ...importsMap.values(), + ].sort(({imported: a}, {imported: b}) => a.localeCompare(b)); + const importSpecifiers = sortedImport.map(specifier => { + return t.importSpecifier( + t.identifier(specifier.name), + t.identifier(specifier.imported), + ); + }); - if (hasExistingImport) { - const didUpdateImport = addMemoCacheFunctionSpecifierToExistingImport( - program, - moduleName, - useMemoCacheIdentifier, - ); - if (!didUpdateImport) { - throw new Error( - `Expected an ImportDeclaration of \`${moduleName}\` in order to update ImportSpecifiers with useMemoCache`, + /** + * If an existing import of this module exists (ie `import { ... } from + * ''`), inject new imported specifiers into the list of + * destructured variables. + */ + const maybeExistingImports = existingImports.get(moduleName); + if (maybeExistingImports != null) { + maybeExistingImports.pushContainer('specifiers', importSpecifiers); + } else { + stmts.push( + t.importDeclaration(importSpecifiers, t.stringLiteral(moduleName)), ); } - } else { - addMemoCacheFunctionImportDeclaration( - program, - moduleName, - useMemoCacheIdentifier, - ); } + path.unshiftContainer('body', stmts); } -function addMemoCacheFunctionImportDeclaration( - program: NodePath, - moduleName: string, - localName: string, -): void { - program.unshiftContainer( - 'body', - t.importDeclaration( - [t.importSpecifier(t.identifier(localName), t.identifier('c'))], - t.stringLiteral(moduleName), - ), +/* + * Matches `import { ... } from ;` + * but not `import * as React from ;` + * `import type { Foo } from ;` + */ +function isNonNamespacedImport( + importDeclPath: NodePath, +): boolean { + return ( + importDeclPath + .get('specifiers') + .every(specifier => specifier.isImportSpecifier()) && + importDeclPath.node.importKind !== 'type' && + importDeclPath.node.importKind !== 'typeof' ); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts index ed41ce2eedc4..61f57f68cfeb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts @@ -8,7 +8,7 @@ import {NodePath} from '@babel/traverse'; import * as t from '@babel/types'; import prettyFormat from 'pretty-format'; -import {Logger} from '.'; +import {Logger, ProgramContext} from '.'; import { HIRFunction, ReactiveFunction, @@ -117,7 +117,7 @@ function run( config: EnvironmentConfig, fnType: ReactFunctionType, mode: CompilerMode, - useMemoCacheIdentifier: string, + programContext: ProgramContext, logger: Logger | null, filename: string | null, code: string | null, @@ -132,7 +132,7 @@ function run( logger, filename, code, - useMemoCacheIdentifier, + programContext, ); env.logger?.debugLogIRs?.({ kind: 'debug', @@ -552,7 +552,7 @@ export function compileFn( config: EnvironmentConfig, fnType: ReactFunctionType, mode: CompilerMode, - useMemoCacheIdentifier: string, + programContext: ProgramContext, logger: Logger | null, filename: string | null, code: string | null, @@ -562,7 +562,7 @@ export function compileFn( config, fnType, mode, - useMemoCacheIdentifier, + programContext, logger, filename, code, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts index ec1a2d2935fc..c00c672b2cbd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -12,11 +12,7 @@ import { CompilerErrorDetail, ErrorSeverity, } from '../CompilerError'; -import { - EnvironmentConfig, - ExternalFunction, - ReactFunctionType, -} from '../HIR/Environment'; +import {EnvironmentConfig, ReactFunctionType} from '../HIR/Environment'; import {CodegenFunction} from '../ReactiveScopes'; import {isComponentDeclaration} from '../Utils/ComponentDeclaration'; import {isHookDeclaration} from '../Utils/HookDeclaration'; @@ -24,10 +20,10 @@ import {assertExhaustive} from '../Utils/utils'; import {insertGatedFunctionDeclaration} from './Gating'; import { addImportsToProgram, - updateMemoCacheFunctionImport, + ProgramContext, validateRestrictedImports, } from './Imports'; -import {PluginOptions} from './Options'; +import {CompilerReactTarget, PluginOptions} from './Options'; import {compileFn} from './Pipeline'; import { filterSuppressionsThatAffectFunction, @@ -299,8 +295,12 @@ export function compileProgram( handleError(restrictedImportsErr, pass, null); return null; } - const useMemoCacheIdentifier = program.scope.generateUidIdentifier('c'); + const programContext = new ProgramContext( + program, + pass.opts.target, + environment.hookPattern, + ); /* * Record lint errors and critical errors as depending on Forget's config, * we may still need to run Forget's analysis on every function (even if we @@ -410,7 +410,7 @@ export function compileProgram( environment, fnType, 'all_features', - useMemoCacheIdentifier.name, + programContext, pass.opts.logger, pass.filename, pass.code, @@ -445,7 +445,7 @@ export function compileProgram( environment, fnType, 'no_inferred_memo', - useMemoCacheIdentifier.name, + programContext, pass.opts.logger, pass.filename, pass.code, @@ -453,7 +453,7 @@ export function compileProgram( }; if ( !compileResult.compiledFn.hasFireRewrite && - !compileResult.compiledFn.hasLoweredContextAccess + !compileResult.compiledFn.hasInferredEffect ) { return null; } @@ -554,79 +554,29 @@ export function compileProgram( if (moduleScopeOptOutDirectives.length > 0) { return null; } - let gating: null | { - gatingFn: ExternalFunction; - referencedBeforeDeclared: Set; - } = null; - if (pass.opts.gating != null) { - gating = { - gatingFn: pass.opts.gating, - referencedBeforeDeclared: - getFunctionReferencedBeforeDeclarationAtTopLevel(program, compiledFns), - }; - } - - const hasLoweredContextAccess = compiledFns.some( - c => c.compiledFn.hasLoweredContextAccess, - ); - const externalFunctions: Array = []; - try { - // TODO: check for duplicate import specifiers - if (gating != null) { - externalFunctions.push(gating.gatingFn); - } - - const lowerContextAccess = environment.lowerContextAccess; - if (lowerContextAccess && hasLoweredContextAccess) { - externalFunctions.push(lowerContextAccess); - } - - const enableEmitInstrumentForget = environment.enableEmitInstrumentForget; - if (enableEmitInstrumentForget != null) { - externalFunctions.push(enableEmitInstrumentForget.fn); - if (enableEmitInstrumentForget.gating != null) { - externalFunctions.push(enableEmitInstrumentForget.gating); - } - } - - if (environment.enableEmitFreeze != null) { - externalFunctions.push(environment.enableEmitFreeze); - } - - if (environment.enableEmitHookGuards != null) { - externalFunctions.push(environment.enableEmitHookGuards); - } - - if (environment.enableChangeDetectionForDebugging != null) { - externalFunctions.push(environment.enableChangeDetectionForDebugging); - } - - const hasFireRewrite = compiledFns.some(c => c.compiledFn.hasFireRewrite); - if (environment.enableFire && hasFireRewrite) { - externalFunctions.push({ - source: getReactCompilerRuntimeModule(pass.opts), - importSpecifierName: 'useFire', - }); - } - } catch (err) { - handleError(err, pass, null); - return null; - } - /* * Only insert Forget-ified functions if we have not encountered a critical * error elsewhere in the file, regardless of bailout mode. */ + const referencedBeforeDeclared = + pass.opts.gating != null + ? getFunctionReferencedBeforeDeclarationAtTopLevel(program, compiledFns) + : null; for (const result of compiledFns) { const {kind, originalFn, compiledFn} = result; const transformedFn = createNewFunctionNode(originalFn, compiledFn); - if (gating != null && kind === 'original') { + if (referencedBeforeDeclared != null && kind === 'original') { + CompilerError.invariant(pass.opts.gating != null, { + reason: "Expected 'gating' import to be present", + loc: null, + }); insertGatedFunctionDeclaration( originalFn, transformedFn, - gating.gatingFn, - gating.referencedBeforeDeclared.has(result), + programContext, + pass.opts.gating, + referencedBeforeDeclared.has(result), ); } else { originalFn.replaceWith(transformedFn); @@ -635,22 +585,7 @@ export function compileProgram( // Forget compiled the component, we need to update existing imports of useMemoCache if (compiledFns.length > 0) { - let needsMemoCacheFunctionImport = false; - for (const fn of compiledFns) { - if (fn.compiledFn.memoSlotsUsed > 0) { - needsMemoCacheFunctionImport = true; - break; - } - } - - if (needsMemoCacheFunctionImport) { - updateMemoCacheFunctionImport( - program, - getReactCompilerRuntimeModule(pass.opts), - useMemoCacheIdentifier.name, - ); - } - addImportsToProgram(program, externalFunctions); + addImportsToProgram(program, programContext); } return {retryErrors}; } @@ -683,7 +618,7 @@ function shouldSkipCompilation( if ( hasMemoCacheFunctionImport( program, - getReactCompilerRuntimeModule(pass.opts), + getReactCompilerRuntimeModule(pass.opts.target), ) ) { return true; @@ -1177,16 +1112,18 @@ function getFunctionReferencedBeforeDeclarationAtTopLevel( return referencedBeforeDeclaration; } -function getReactCompilerRuntimeModule(opts: PluginOptions): string { - if (opts.target === '19') { +export function getReactCompilerRuntimeModule( + target: CompilerReactTarget, +): string { + if (target === '19') { return 'react/compiler-runtime'; // from react namespace - } else if (opts.target === '17' || opts.target === '18') { + } else if (target === '17' || target === '18') { return 'react-compiler-runtime'; // npm package } else { CompilerError.invariant( - opts.target != null && - opts.target.kind === 'donotuse_meta_internal' && - typeof opts.target.runtimeModule === 'string', + target != null && + target.kind === 'donotuse_meta_internal' && + typeof target.runtimeModule === 'string', { reason: 'Expected target to already be validated', description: null, @@ -1194,6 +1131,6 @@ function getReactCompilerRuntimeModule(opts: PluginOptions): string { suggestions: null, }, ); - return opts.target.runtimeModule; + return target.runtimeModule; } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index a8f5b15dbd06..2594ac31c6b5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -15,6 +15,7 @@ import { PanicThresholdOptions, parsePluginOptions, PluginOptions, + ProgramContext, } from '../Entrypoint'; import {Err, Ok, Result} from '../Utils/Result'; import { @@ -84,6 +85,8 @@ export const InstrumentationSchema = z ); export type ExternalFunction = z.infer; +export const USE_FIRE_FUNCTION_NAME = 'useFire'; +export const EMIT_FREEZE_GLOBAL_GATING = '__DEV__'; export const MacroMethodSchema = z.union([ z.object({type: z.literal('wildcard')}), @@ -846,9 +849,9 @@ export class Environment { config: EnvironmentConfig; fnType: ReactFunctionType; compilerMode: CompilerMode; - useMemoCacheIdentifier: string; - hasLoweredContextAccess: boolean; + programContext: ProgramContext; hasFireRewrite: boolean; + hasInferredEffect: boolean; #contextIdentifiers: Set; #hoistedIdentifiers: Set; @@ -862,7 +865,7 @@ export class Environment { logger: Logger | null, filename: string | null, code: string | null, - useMemoCacheIdentifier: string, + programContext: ProgramContext, ) { this.#scope = scope; this.fnType = fnType; @@ -871,11 +874,11 @@ export class Environment { this.filename = filename; this.code = code; this.logger = logger; - this.useMemoCacheIdentifier = useMemoCacheIdentifier; + this.programContext = programContext; this.#shapes = new Map(DEFAULT_SHAPES); this.#globals = new Map(DEFAULT_GLOBALS); - this.hasLoweredContextAccess = false; this.hasFireRewrite = false; + this.hasInferredEffect = false; if ( config.disableMemoizationForDebugging && @@ -937,6 +940,10 @@ export class Environment { return makeScopeId(this.#nextScope++); } + get scope(): BabelScope { + return this.#scope; + } + logErrors(errors: Result): void { if (errors.isOk() || this.logger == null) { return; diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts index f58cdfbb081b..3a8cb89ca0fc 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts @@ -1167,18 +1167,21 @@ export type VariableBinding = // bindings declard outside the current component/hook | NonLocalBinding; +// `import {bar as baz} from 'foo'`: name=baz, module=foo, imported=bar +export type NonLocalImportSpecifier = { + kind: 'ImportSpecifier'; + name: string; + module: string; + imported: string; +}; + export type NonLocalBinding = // `import Foo from 'foo'`: name=Foo, module=foo | {kind: 'ImportDefault'; name: string; module: string} // `import * as Foo from 'foo'`: name=Foo, module=foo | {kind: 'ImportNamespace'; name: string; module: string} - // `import {bar as baz} from 'foo'`: name=baz, module=foo, imported=bar - | { - kind: 'ImportSpecifier'; - name: string; - module: string; - imported: string; - } + // `import {bar as baz} from 'foo'` + | NonLocalImportSpecifier // let, const, function, etc declared in the module but outside the current component/hook | {kind: 'ModuleLocal'; name: string} // an unresolved binding diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts index 9202f2145f27..44dd34b7d6ca 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts @@ -331,6 +331,7 @@ export default class HIRBuilder { type: makeType(), loc: node.loc ?? GeneratedSource, }; + this.#env.programContext.addNewReference(name); this.#bindings.set(name, {node, identifier}); return identifier; } else if (mapping.node === node) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts index 4d295aad060f..85cb0236659e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts @@ -249,6 +249,7 @@ export function inferEffectDependencies(fn: HIRFunction): void { // Renumber instructions and fix scope ranges markInstructionIds(fn.body); fixScopeAndIdentifierRanges(fn.body); + fn.env.hasInferredEffect = true; } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts index b636c7b1718c..834f60195af2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts @@ -18,6 +18,7 @@ import { Instruction, LoadGlobal, LoadLocal, + NonLocalImportSpecifier, Place, PropertyLoad, isUseContextHookType, @@ -35,7 +36,7 @@ import {inferTypes} from '../TypeInference'; export function lowerContextAccess( fn: HIRFunction, - loweredContextCallee: ExternalFunction, + loweredContextCalleeConfig: ExternalFunction, ): void { const contextAccess: Map = new Map(); const contextKeys: Map> = new Map(); @@ -79,6 +80,8 @@ export function lowerContextAccess( } } + let importLoweredContextCallee: NonLocalImportSpecifier | null = null; + if (contextAccess.size > 0 && contextKeys.size > 0) { for (const [, block] of fn.body.blocks) { let nextInstructions: Array | null = null; @@ -91,9 +94,13 @@ export function lowerContextAccess( isUseContextHookType(value.callee.identifier) && contextKeys.has(lvalue.identifier.id) ) { + importLoweredContextCallee ??= + fn.env.programContext.addImportSpecifier( + loweredContextCalleeConfig, + ); const loweredContextCalleeInstr = emitLoadLoweredContextCallee( fn.env, - loweredContextCallee, + importLoweredContextCallee, ); if (nextInstructions === null) { @@ -122,21 +129,16 @@ export function lowerContextAccess( } markInstructionIds(fn.body); inferTypes(fn); - fn.env.hasLoweredContextAccess = true; } } function emitLoadLoweredContextCallee( env: Environment, - loweredContextCallee: ExternalFunction, + importedLowerContextCallee: NonLocalImportSpecifier, ): Instruction { const loadGlobal: LoadGlobal = { kind: 'LoadGlobal', - binding: { - kind: 'ImportNamespace', - module: loweredContextCallee.source, - name: loweredContextCallee.importSpecifierName, - }, + binding: {...importedLowerContextCallee}, loc: GeneratedSource, }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineJsx.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineJsx.ts index a6b94075cce2..d35c4d77362d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineJsx.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineJsx.ts @@ -196,7 +196,7 @@ function process( return null; } - const props = collectProps(jsx); + const props = collectProps(fn.env, jsx); if (!props) return null; const outlinedTag = fn.env.generateGloballyUniqueIdentifierName(null).value; @@ -217,6 +217,7 @@ type OutlinedJsxAttribute = { }; function collectProps( + env: Environment, instructions: Array, ): Array | null { let id = 1; @@ -227,6 +228,7 @@ function collectProps( newName = `${oldName}${id++}`; } seen.add(newName); + env.programContext.addNewReference(newName); return newName; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts index ce535a9b3866..b90e4e417c6a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts @@ -52,7 +52,8 @@ import {assertExhaustive} from '../Utils/utils'; import {buildReactiveFunction} from './BuildReactiveFunction'; import {SINGLE_CHILD_FBT_TAGS} from './MemoizeFbtAndMacroOperandsInSameScope'; import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors'; -import {ReactFunctionType} from '../HIR/Environment'; +import {EMIT_FREEZE_GLOBAL_GATING, ReactFunctionType} from '../HIR/Environment'; +import {ProgramContext} from '../Entrypoint'; export const MEMO_CACHE_SENTINEL = 'react.memo_cache_sentinel'; export const EARLY_RETURN_SENTINEL = 'react.early_return_sentinel'; @@ -100,9 +101,9 @@ export type CodegenFunction = { }>; /** - * This is true if the compiler has the lowered useContext calls. + * This is true if the compiler has compiled inferred effect dependencies */ - hasLoweredContextAccess: boolean; + hasInferredEffect: boolean; /** * This is true if the compiler has compiled a fire to a useFire call @@ -160,6 +161,7 @@ export function codegenFunction( compiled.body = t.blockStatement([ createHookGuard( hookGuard, + fn.env.programContext, compiled.body.body, GuardKind.PushHookGuard, GuardKind.PopHookGuard, @@ -170,13 +172,15 @@ export function codegenFunction( const cacheCount = compiled.memoSlotsUsed; if (cacheCount !== 0) { const preface: Array = []; + const useMemoCacheIdentifier = + fn.env.programContext.addMemoCacheImport().name; // The import declaration for `useMemoCache` is inserted in the Babel plugin preface.push( t.variableDeclaration('const', [ t.variableDeclarator( t.identifier(cx.synthesizeName('$')), - t.callExpression(t.identifier(fn.env.useMemoCacheIdentifier), [ + t.callExpression(t.identifier(useMemoCacheIdentifier), [ t.numericLiteral(cacheCount), ]), ), @@ -259,34 +263,54 @@ export function codegenFunction( * Technically, this is a conditional hook call. However, we expect * __DEV__ and gating identifier to be runtime constants */ - let gating: t.Expression; - if ( - emitInstrumentForget.gating != null && + const gating = + emitInstrumentForget.gating != null + ? t.identifier( + fn.env.programContext.addImportSpecifier( + emitInstrumentForget.gating, + ).name, + ) + : null; + + const globalGating = emitInstrumentForget.globalGating != null - ) { - gating = t.logicalExpression( - '&&', - t.identifier(emitInstrumentForget.globalGating), - t.identifier(emitInstrumentForget.gating.importSpecifierName), + ? t.identifier(emitInstrumentForget.globalGating) + : null; + + if (emitInstrumentForget.globalGating != null) { + const assertResult = fn.env.programContext.assertGlobalBinding( + emitInstrumentForget.globalGating, ); - } else if (emitInstrumentForget.gating != null) { - gating = t.identifier(emitInstrumentForget.gating.importSpecifierName); + if (assertResult.isErr()) { + return assertResult; + } + } + + let ifTest: t.Expression; + if (gating != null && globalGating != null) { + ifTest = t.logicalExpression('&&', globalGating, gating); + } else if (gating != null) { + ifTest = gating; } else { - CompilerError.invariant(emitInstrumentForget.globalGating != null, { + CompilerError.invariant(globalGating != null, { reason: 'Bad config not caught! Expected at least one of gating or globalGating', loc: null, suggestions: null, }); - gating = t.identifier(emitInstrumentForget.globalGating); + ifTest = globalGating; } + + const instrumentFnIdentifier = fn.env.programContext.addImportSpecifier( + emitInstrumentForget.fn, + ).name; const test: t.IfStatement = t.ifStatement( - gating, + ifTest, t.expressionStatement( - t.callExpression( - t.identifier(emitInstrumentForget.fn.importSpecifierName), - [t.stringLiteral(fn.id), t.stringLiteral(fn.env.filename ?? '')], - ), + t.callExpression(t.identifier(instrumentFnIdentifier), [ + t.stringLiteral(fn.id), + t.stringLiteral(fn.env.filename ?? ''), + ]), ), ); compiled.body.body.unshift(test); @@ -363,8 +387,8 @@ function codegenReactiveFunction( prunedMemoBlocks: countMemoBlockVisitor.prunedMemoBlocks, prunedMemoValues: countMemoBlockVisitor.prunedMemoValues, outlined: [], - hasLoweredContextAccess: fn.env.hasLoweredContextAccess, hasFireRewrite: fn.env.hasFireRewrite, + hasInferredEffect: fn.env.hasInferredEffect, }); } @@ -553,13 +577,18 @@ function codegenBlockNoReset( function wrapCacheDep(cx: Context, value: t.Expression): t.Expression { if (cx.env.config.enableEmitFreeze != null && cx.env.isInferredMemoEnabled) { - // The import declaration for emitFreeze is inserted in the Babel plugin + const emitFreezeIdentifier = cx.env.programContext.addImportSpecifier( + cx.env.config.enableEmitFreeze, + ).name; + cx.env.programContext + .assertGlobalBinding(EMIT_FREEZE_GLOBAL_GATING, cx.env.scope) + .unwrap(); return t.conditionalExpression( - t.identifier('__DEV__'), - t.callExpression( - t.identifier(cx.env.config.enableEmitFreeze.importSpecifierName), - [value, t.stringLiteral(cx.fnName)], - ), + t.identifier(EMIT_FREEZE_GLOBAL_GATING), + t.callExpression(t.identifier(emitFreezeIdentifier), [ + value, + t.stringLiteral(cx.fnName), + ]), value, ); } else { @@ -713,16 +742,14 @@ function codegenReactiveScope( let computationBlock = codegenBlock(cx, block); let memoStatement; - if ( - cx.env.config.enableChangeDetectionForDebugging != null && - changeExpressions.length > 0 - ) { + const detectionFunction = cx.env.config.enableChangeDetectionForDebugging; + if (detectionFunction != null && changeExpressions.length > 0) { const loc = typeof scope.loc === 'symbol' ? 'unknown location' : `(${scope.loc.start.line}:${scope.loc.end.line})`; - const detectionFunction = - cx.env.config.enableChangeDetectionForDebugging.importSpecifierName; + const importedDetectionFunctionIdentifier = + cx.env.programContext.addImportSpecifier(detectionFunction).name; const cacheLoadOldValueStatements: Array = []; const changeDetectionStatements: Array = []; const idempotenceDetectionStatements: Array = []; @@ -744,7 +771,7 @@ function codegenReactiveScope( ); changeDetectionStatements.push( t.expressionStatement( - t.callExpression(t.identifier(detectionFunction), [ + t.callExpression(t.identifier(importedDetectionFunctionIdentifier), [ t.identifier(loadName), t.cloneNode(name, true), t.stringLiteral(name.name), @@ -756,7 +783,7 @@ function codegenReactiveScope( ); idempotenceDetectionStatements.push( t.expressionStatement( - t.callExpression(t.identifier(detectionFunction), [ + t.callExpression(t.identifier(importedDetectionFunctionIdentifier), [ t.cloneNode(slot, true), t.cloneNode(name, true), t.stringLiteral(name.name), @@ -1518,15 +1545,15 @@ const createStringLiteral = withLoc(t.stringLiteral); function createHookGuard( guard: ExternalFunction, + context: ProgramContext, stmts: Array, before: GuardKind, after: GuardKind, ): t.TryStatement { + const guardFnName = context.addImportSpecifier(guard).name; function createHookGuardImpl(kind: number): t.ExpressionStatement { return t.expressionStatement( - t.callExpression(t.identifier(guard.importSpecifierName), [ - t.numericLiteral(kind), - ]), + t.callExpression(t.identifier(guardFnName), [t.numericLiteral(kind)]), ); } @@ -1576,6 +1603,7 @@ function createCallExpression( t.blockStatement([ createHookGuard( hookGuard, + env.programContext, [t.returnStatement(callExpr)], GuardKind.AllowHook, GuardKind.DisallowHook, diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/RenameVariables.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/RenameVariables.ts index f84965b92e6e..5b39055e7d61 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/RenameVariables.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/RenameVariables.ts @@ -5,6 +5,7 @@ * LICENSE file in the root directory of this source tree. */ +import {ProgramContext} from '..'; import {CompilerError} from '../CompilerError'; import { DeclarationId, @@ -47,7 +48,7 @@ import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors'; */ export function renameVariables(fn: ReactiveFunction): Set { const globals = collectReferencedGlobals(fn); - const scopes = new Scopes(globals); + const scopes = new Scopes(globals, fn.env.programContext); renameVariablesImpl(fn, new Visitor(), scopes); return new Set([...scopes.names, ...globals]); } @@ -124,10 +125,12 @@ class Scopes { #seen: Map = new Map(); #stack: Array> = [new Map()]; #globals: Set; + #programContext: ProgramContext; names: Set = new Set(); - constructor(globals: Set) { + constructor(globals: Set, programContext: ProgramContext) { this.#globals = globals; + this.#programContext = programContext; } visit(identifier: Identifier): void { @@ -156,6 +159,7 @@ class Scopes { name = `${originalName.value}$${id++}`; } } + this.#programContext.addNewReference(name); const identifierName = makeIdentifierName(name); identifier.name = identifierName; this.#seen.set(identifier.declarationId, identifierName); diff --git a/compiler/packages/babel-plugin-react-compiler/src/Transform/TransformFire.ts b/compiler/packages/babel-plugin-react-compiler/src/Transform/TransformFire.ts index a480b5d7c78d..943b6b8eca2b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Transform/TransformFire.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Transform/TransformFire.ts @@ -28,6 +28,7 @@ import { isUseEffectHookType, LoadLocal, makeInstructionId, + NonLocalImportSpecifier, Place, promoteTemporary, } from '../HIR'; @@ -36,6 +37,7 @@ import {getOrInsertWith} from '../Utils/utils'; import {BuiltInFireId, DefaultNonmutatingHook} from '../HIR/ObjectShape'; import {eachInstructionOperand} from '../HIR/visitors'; import {printSourceLocationLine} from '../HIR/PrintHIR'; +import {USE_FIRE_FUNCTION_NAME} from '../HIR/Environment'; /* * TODO(jmbrown): @@ -56,6 +58,7 @@ export function transformFire(fn: HIRFunction): void { } function replaceFireFunctions(fn: HIRFunction, context: Context): void { + let importedUseFire: NonLocalImportSpecifier | null = null; let hasRewrite = false; for (const [, block] of fn.body.blocks) { const rewriteInstrs = new Map>(); @@ -87,7 +90,15 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void { ] of capturedCallees.entries()) { if (!context.hasCalleeWithInsertedFire(fireCalleePlace)) { context.addCalleeWithInsertedFire(fireCalleePlace); - const loadUseFireInstr = makeLoadUseFireInstruction(fn.env); + + importedUseFire ??= fn.env.programContext.addImportSpecifier({ + source: fn.env.programContext.reactRuntimeModule, + importSpecifierName: USE_FIRE_FUNCTION_NAME, + }); + const loadUseFireInstr = makeLoadUseFireInstruction( + fn.env, + importedUseFire, + ); const loadFireCalleeInstr = makeLoadFireCalleeInstruction( fn.env, fireCalleeInfo.capturedCalleeIdentifier, @@ -404,18 +415,16 @@ function ensureNoMoreFireUses(fn: HIRFunction, context: Context): void { } } -function makeLoadUseFireInstruction(env: Environment): Instruction { +function makeLoadUseFireInstruction( + env: Environment, + importedLoadUseFire: NonLocalImportSpecifier, +): Instruction { const useFirePlace = createTemporaryPlace(env, GeneratedSource); useFirePlace.effect = Effect.Read; useFirePlace.identifier.type = DefaultNonmutatingHook; const instrValue: InstructionValue = { kind: 'LoadGlobal', - binding: { - kind: 'ImportSpecifier', - name: 'useFire', - module: 'react', - imported: 'useFire', - }, + binding: {...importedLoadUseFire}, loc: GeneratedSource, }; return { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/babel-existing-react-runtime-import.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/babel-existing-react-runtime-import.expect.md new file mode 100644 index 000000000000..5bb87a2b032c --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/babel-existing-react-runtime-import.expect.md @@ -0,0 +1,73 @@ + +## Input + +```javascript +import * as React from 'react'; +import {someImport} from 'react/compiler-runtime'; +import {calculateExpensiveNumber} from 'shared-runtime'; + +function Component(props) { + const [x] = React.useState(0); + const expensiveNumber = React.useMemo(() => calculateExpensiveNumber(x), [x]); + + return ( +
+ {expensiveNumber} + {`${someImport}`} +
+ ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [], +}; + +``` + +## Code + +```javascript +import * as React from "react"; +import { someImport, c as _c } from "react/compiler-runtime"; +import { calculateExpensiveNumber } from "shared-runtime"; + +function Component(props) { + const $ = _c(4); + const [x] = React.useState(0); + let t0; + let t1; + if ($[0] !== x) { + t1 = calculateExpensiveNumber(x); + $[0] = x; + $[1] = t1; + } else { + t1 = $[1]; + } + t0 = t1; + const expensiveNumber = t0; + let t2; + if ($[2] !== expensiveNumber) { + t2 = ( +
+ {expensiveNumber} + {`${someImport}`} +
+ ); + $[2] = expensiveNumber; + $[3] = t2; + } else { + t2 = $[3]; + } + return t2; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [], +}; + +``` + +### Eval output +(kind: ok)
0undefined
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/babel-existing-react-runtime-import.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/babel-existing-react-runtime-import.js new file mode 100644 index 000000000000..80a2006dd12b --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/babel-existing-react-runtime-import.js @@ -0,0 +1,20 @@ +import * as React from 'react'; +import {someImport} from 'react/compiler-runtime'; +import {calculateExpensiveNumber} from 'shared-runtime'; + +function Component(props) { + const [x] = React.useState(0); + const expensiveNumber = React.useMemo(() => calculateExpensiveNumber(x), [x]); + + return ( +
+ {expensiveNumber} + {`${someImport}`} +
+ ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-imports-same-source.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-imports-same-source.expect.md index 161b42dc6d7d..6ec8520f9ec9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-imports-same-source.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-imports-same-source.expect.md @@ -14,9 +14,9 @@ function useFoo(props) { ```javascript import { - useRenderCounter, - shouldInstrument, makeReadOnly, + shouldInstrument, + useRenderCounter, } from "react-compiler-runtime"; import { c as _c } from "react/compiler-runtime"; // @enableEmitFreeze @enableEmitInstrumentForget diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.expect.md index 319de18794f2..8c2f0b94ef88 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.expect.md @@ -23,7 +23,7 @@ function Foo(props) { ## Code ```javascript -import { useRenderCounter, shouldInstrument } from "react-compiler-runtime"; +import { shouldInstrument, useRenderCounter } from "react-compiler-runtime"; import { c as _c } from "react/compiler-runtime"; // @enableEmitInstrumentForget @compilationMode(annotation) function Bar(props) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conflict-codegen-instrument-forget.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conflict-codegen-instrument-forget.expect.md new file mode 100644 index 000000000000..a2df134b2a86 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conflict-codegen-instrument-forget.expect.md @@ -0,0 +1,97 @@ + +## Input + +```javascript +// @enableEmitInstrumentForget @compilationMode(annotation) + +import {identity} from 'shared-runtime'; + +function Bar(props) { + 'use forget'; + const shouldInstrument = identity(null); + const _shouldInstrument = identity(null); + const _x2 = () => { + const _shouldInstrument2 = 'hello world'; + return identity({_shouldInstrument2}); + }; + return ( +
+ {props.bar} +
+ ); +} + +function Foo(props) { + 'use forget'; + return {props.bar}; +} + +``` + +## Code + +```javascript +import { + shouldInstrument as _shouldInstrument3, + useRenderCounter, +} from "react-compiler-runtime"; +import { c as _c } from "react/compiler-runtime"; // @enableEmitInstrumentForget @compilationMode(annotation) + +import { identity } from "shared-runtime"; + +function Bar(props) { + "use forget"; + if (DEV && _shouldInstrument3) + useRenderCounter("Bar", "/conflict-codegen-instrument-forget.ts"); + const $ = _c(4); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = identity(null); + $[0] = t0; + } else { + t0 = $[0]; + } + const shouldInstrument = t0; + let t1; + if ($[1] === Symbol.for("react.memo_cache_sentinel")) { + t1 = identity(null); + $[1] = t1; + } else { + t1 = $[1]; + } + const _shouldInstrument = t1; + let t2; + if ($[2] !== props.bar) { + t2 = ( +
+ {props.bar} +
+ ); + $[2] = props.bar; + $[3] = t2; + } else { + t2 = $[3]; + } + return t2; +} + +function Foo(props) { + "use forget"; + if (DEV && _shouldInstrument3) + useRenderCounter("Foo", "/conflict-codegen-instrument-forget.ts"); + const $ = _c(2); + let t0; + if ($[0] !== props.bar) { + t0 = {props.bar}; + $[0] = props.bar; + $[1] = t0; + } else { + t0 = $[1]; + } + return t0; +} + +``` + +### Eval output +(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conflict-codegen-instrument-forget.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conflict-codegen-instrument-forget.js new file mode 100644 index 000000000000..88ffefe220d5 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conflict-codegen-instrument-forget.js @@ -0,0 +1,23 @@ +// @enableEmitInstrumentForget @compilationMode(annotation) + +import {identity} from 'shared-runtime'; + +function Bar(props) { + 'use forget'; + const shouldInstrument = identity(null); + const _shouldInstrument = identity(null); + const _x2 = () => { + const _shouldInstrument2 = 'hello world'; + return identity({_shouldInstrument2}); + }; + return ( +
+ {props.bar} +
+ ); +} + +function Foo(props) { + 'use forget'; + return {props.bar}; +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/emit-freeze-conflicting-imports.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/emit-freeze-conflicting-imports.expect.md new file mode 100644 index 000000000000..9b30c1b8cbbd --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/emit-freeze-conflicting-imports.expect.md @@ -0,0 +1,37 @@ + +## Input + +```javascript +// @enableEmitFreeze @instrumentForget + +let makeReadOnly = 'conflicting identifier'; +function useFoo(props) { + return foo(props.x); +} + +``` + +## Code + +```javascript +import { makeReadOnly as _makeReadOnly } from "react-compiler-runtime"; +import { c as _c } from "react/compiler-runtime"; // @enableEmitFreeze @instrumentForget + +let makeReadOnly = "conflicting identifier"; +function useFoo(props) { + const $ = _c(2); + let t0; + if ($[0] !== props.x) { + t0 = foo(props.x); + $[0] = props.x; + $[1] = __DEV__ ? _makeReadOnly(t0, "useFoo") : t0; + } else { + t0 = $[1]; + } + return t0; +} + +``` + +### Eval output +(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.codegen-error-on-conflicting-imports.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/emit-freeze-conflicting-imports.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.codegen-error-on-conflicting-imports.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/emit-freeze-conflicting-imports.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/emit-freeze-nonconflicting-global-reference.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/emit-freeze-nonconflicting-global-reference.expect.md new file mode 100644 index 000000000000..ee18d3d1a2d1 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/emit-freeze-nonconflicting-global-reference.expect.md @@ -0,0 +1,33 @@ + +## Input + +```javascript +// @enableEmitFreeze @instrumentForget +function useFoo(props) { + return foo(props.x, __DEV__); +} + +``` + +## Code + +```javascript +import { makeReadOnly } from "react-compiler-runtime"; +import { c as _c } from "react/compiler-runtime"; // @enableEmitFreeze @instrumentForget +function useFoo(props) { + const $ = _c(2); + let t0; + if ($[0] !== props.x) { + t0 = foo(props.x, __DEV__); + $[0] = props.x; + $[1] = __DEV__ ? makeReadOnly(t0, "useFoo") : t0; + } else { + t0 = $[1]; + } + return t0; +} + +``` + +### Eval output +(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/emit-freeze-nonconflicting-global-reference.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/emit-freeze-nonconflicting-global-reference.js new file mode 100644 index 000000000000..62c313b67d33 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/emit-freeze-nonconflicting-global-reference.js @@ -0,0 +1,4 @@ +// @enableEmitFreeze @instrumentForget +function useFoo(props) { + return foo(props.x, __DEV__); +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.codegen-error-on-conflicting-imports.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.codegen-error-on-conflicting-imports.expect.md deleted file mode 100644 index 2bd0eee1b1fa..000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.codegen-error-on-conflicting-imports.expect.md +++ /dev/null @@ -1,21 +0,0 @@ - -## Input - -```javascript -// @enableEmitFreeze @instrumentForget - -let makeReadOnly = 'conflicting identifier'; -function useFoo(props) { - return foo(props.x); -} - -``` - - -## Error - -``` -Invariant: Encountered conflicting import specifiers for makeReadOnly in generated program. -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.emit-freeze-conflicting-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.emit-freeze-conflicting-global.expect.md new file mode 100644 index 000000000000..a54cc98708f1 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.emit-freeze-conflicting-global.expect.md @@ -0,0 +1,27 @@ + +## Input + +```javascript +// @enableEmitFreeze @instrumentForget +function useFoo(props) { + const __DEV__ = 'conflicting global'; + console.log(__DEV__); + return foo(props.x); +} + +``` + + +## Error + +``` + 1 | // @enableEmitFreeze @instrumentForget + 2 | function useFoo(props) { +> 3 | const __DEV__ = 'conflicting global'; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Todo: Encountered conflicting global in generated program. Conflict from local binding __DEV__ (3:3) + 4 | console.log(__DEV__); + 5 | return foo(props.x); + 6 | } +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.emit-freeze-conflicting-global.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.emit-freeze-conflicting-global.js new file mode 100644 index 000000000000..4391ad76e70a --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.emit-freeze-conflicting-global.js @@ -0,0 +1,6 @@ +// @enableEmitFreeze @instrumentForget +function useFoo(props) { + const __DEV__ = 'conflicting global'; + console.log(__DEV__); + return foo(props.x); +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/arrow-function-expr-gating-test.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/arrow-function-expr-gating-test.expect.md index 659e2c9ff9e4..298b4d59dfcf 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/arrow-function-expr-gating-test.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/arrow-function-expr-gating-test.expect.md @@ -18,8 +18,8 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; -import { c as _c } from "react/compiler-runtime"; // @gating +import { c as _c } from "react/compiler-runtime"; +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating import { Stringify } from "shared-runtime"; const ErrorView = isForgetEnabled_Fixtures() ? (t0) => { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/codegen-instrument-forget-gating-test.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/codegen-instrument-forget-gating-test.expect.md index fe85e38e106a..6256e5ec90ff 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/codegen-instrument-forget-gating-test.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/codegen-instrument-forget-gating-test.expect.md @@ -36,9 +36,9 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; -import { useRenderCounter, shouldInstrument } from "react-compiler-runtime"; -import { c as _c } from "react/compiler-runtime"; // @enableEmitInstrumentForget @compilationMode(annotation) @gating +import { shouldInstrument, useRenderCounter } from "react-compiler-runtime"; +import { c as _c } from "react/compiler-runtime"; +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @enableEmitInstrumentForget @compilationMode(annotation) @gating const Bar = isForgetEnabled_Fixtures() ? function Bar(props) { "use forget"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/component-syntax-ref-gating.flow.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/component-syntax-ref-gating.flow.expect.md index 25215580c81f..410b65124460 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/component-syntax-ref-gating.flow.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/component-syntax-ref-gating.flow.expect.md @@ -20,14 +20,14 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; import { c as _c } from "react/compiler-runtime"; +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; import { Stringify } from "shared-runtime"; import * as React from "react"; const Foo = React.forwardRef(Foo_withRef); -const _isForgetEnabled_Fixtures_result = isForgetEnabled_Fixtures(); -function _Foo_withRef_optimized(_$$empty_props_placeholder$$, ref) { +const isForgetEnabled_Fixtures_result = isForgetEnabled_Fixtures(); +function Foo_withRef_optimized(_$$empty_props_placeholder$$, ref) { const $ = _c(2); let t0; if ($[0] !== ref) { @@ -39,16 +39,15 @@ function _Foo_withRef_optimized(_$$empty_props_placeholder$$, ref) { } return t0; } -function _Foo_withRef_unoptimized( +function Foo_withRef_unoptimized( _$$empty_props_placeholder$$: $ReadOnly<{}>, ref: React.RefSetter, ): React.Node { return ; } function Foo_withRef(arg0, arg1) { - if (_isForgetEnabled_Fixtures_result) - return _Foo_withRef_optimized(arg0, arg1); - else return _Foo_withRef_unoptimized(arg0, arg1); + if (isForgetEnabled_Fixtures_result) return Foo_withRef_optimized(arg0, arg1); + else return Foo_withRef_unoptimized(arg0, arg1); } export const FIXTURE_ENTRYPOINT = { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/conflicting-gating-fn.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/conflicting-gating-fn.expect.md new file mode 100644 index 000000000000..ca6f518032bc --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/conflicting-gating-fn.expect.md @@ -0,0 +1,62 @@ + +## Input + +```javascript +// @gating + +export const isForgetEnabled_Fixtures = () => { + 'use no forget'; + return false; +}; + +export function Bar(props) { + 'use forget'; + return
{props.bar}
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: eval('Bar'), + params: [{bar: 2}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { isForgetEnabled_Fixtures as _isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating + +export const isForgetEnabled_Fixtures = () => { + "use no forget"; + return false; +}; + +export const Bar = _isForgetEnabled_Fixtures() + ? function Bar(props) { + "use forget"; + const $ = _c(2); + let t0; + if ($[0] !== props.bar) { + t0 =
{props.bar}
; + $[0] = props.bar; + $[1] = t0; + } else { + t0 = $[1]; + } + return t0; + } + : function Bar(props) { + "use forget"; + return
{props.bar}
; + }; + +export const FIXTURE_ENTRYPOINT = { + fn: eval("Bar"), + params: [{ bar: 2 }], +}; + +``` + +### Eval output +(kind: ok)
2
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/conflicting-gating-fn.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/conflicting-gating-fn.js new file mode 100644 index 000000000000..3e5757dc9157 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/conflicting-gating-fn.js @@ -0,0 +1,16 @@ +// @gating + +export const isForgetEnabled_Fixtures = () => { + 'use no forget'; + return false; +}; + +export function Bar(props) { + 'use forget'; + return
{props.bar}
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: eval('Bar'), + params: [{bar: 2}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-access-function-name-in-component.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-access-function-name-in-component.expect.md index e056b6fa1c02..9b3633472d6e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-access-function-name-in-component.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-access-function-name-in-component.expect.md @@ -18,8 +18,8 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; -import { c as _c } from "react/compiler-runtime"; // @gating +import { c as _c } from "react/compiler-runtime"; +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating const Component = isForgetEnabled_Fixtures() ? function Component() { const $ = _c(1); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-nonreferenced-identifier-collision.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-nonreferenced-identifier-collision.expect.md index a8f2a8dc58b2..40791a2454b6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-nonreferenced-identifier-collision.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-nonreferenced-identifier-collision.expect.md @@ -24,8 +24,8 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; -import { c as _c } from "react/compiler-runtime"; // @gating +import { c as _c } from "react/compiler-runtime"; +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating import { identity, useHook as useRenamed } from "shared-runtime"; const _ = { useHook: isForgetEnabled_Fixtures() ? () => {} : () => {}, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-preserves-function-properties.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-preserves-function-properties.expect.md index 053fff651e54..cc852d253844 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-preserves-function-properties.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-preserves-function-properties.expect.md @@ -26,8 +26,8 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; -import { c as _c } from "react/compiler-runtime"; // @gating +import { c as _c } from "react/compiler-runtime"; +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating const Component = isForgetEnabled_Fixtures() ? function Component() { const $ = _c(1); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-default-function.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-default-function.expect.md index 4e4a35c37557..ae7896e7a5ec 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-default-function.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-default-function.expect.md @@ -27,8 +27,8 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; -import { c as _c } from "react/compiler-runtime"; // @gating @compilationMode(annotation) +import { c as _c } from "react/compiler-runtime"; +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode(annotation) const Bar = isForgetEnabled_Fixtures() ? function Bar(props) { "use forget"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function-and-default.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function-and-default.expect.md index 95c629aa11f9..4c368a8254d5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function-and-default.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function-and-default.expect.md @@ -34,8 +34,8 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; -import { c as _c } from "react/compiler-runtime"; // @gating @compilationMode(annotation) +import { c as _c } from "react/compiler-runtime"; +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode(annotation) const Bar = isForgetEnabled_Fixtures() ? function Bar(props) { "use forget"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function.expect.md index ab31d2939d44..c3e2e4a04281 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function.expect.md @@ -27,8 +27,8 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; -import { c as _c } from "react/compiler-runtime"; // @gating @compilationMode(annotation) +import { c as _c } from "react/compiler-runtime"; +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode(annotation) export const Bar = isForgetEnabled_Fixtures() ? function Bar(props) { "use forget"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test.expect.md index e68e4840cf52..8ccce5671344 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test.expect.md @@ -27,8 +27,8 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; -import { c as _c } from "react/compiler-runtime"; // @gating @compilationMode(annotation) +import { c as _c } from "react/compiler-runtime"; +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode(annotation) const Bar = isForgetEnabled_Fixtures() ? function Bar(props) { "use forget"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-use-before-decl-ref.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-use-before-decl-ref.expect.md index d09c98dffd91..9730119c2504 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-use-before-decl-ref.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-use-before-decl-ref.expect.md @@ -21,14 +21,14 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; -import { c as _c } from "react/compiler-runtime"; // @gating +import { c as _c } from "react/compiler-runtime"; +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating import { createRef, forwardRef } from "react"; import { Stringify } from "shared-runtime"; const Foo = forwardRef(Foo_withRef); -const _isForgetEnabled_Fixtures_result = isForgetEnabled_Fixtures(); -function _Foo_withRef_optimized(props, ref) { +const isForgetEnabled_Fixtures_result = isForgetEnabled_Fixtures(); +function Foo_withRef_optimized(props, ref) { const $ = _c(3); let t0; if ($[0] !== props || $[1] !== ref) { @@ -41,13 +41,12 @@ function _Foo_withRef_optimized(props, ref) { } return t0; } -function _Foo_withRef_unoptimized(props, ref) { +function Foo_withRef_unoptimized(props, ref) { return ; } function Foo_withRef(arg0, arg1) { - if (_isForgetEnabled_Fixtures_result) - return _Foo_withRef_optimized(arg0, arg1); - else return _Foo_withRef_unoptimized(arg0, arg1); + if (isForgetEnabled_Fixtures_result) return Foo_withRef_optimized(arg0, arg1); + else return Foo_withRef_unoptimized(arg0, arg1); } export const FIXTURE_ENTRYPOINT = { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-use-before-decl.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-use-before-decl.expect.md index 0bbfc9675677..937af490f1db 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-use-before-decl.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-use-before-decl.expect.md @@ -22,14 +22,14 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; -import { c as _c } from "react/compiler-runtime"; // @gating +import { c as _c } from "react/compiler-runtime"; +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating import { memo } from "react"; import { Stringify } from "shared-runtime"; export default memo(Foo); -const _isForgetEnabled_Fixtures_result = isForgetEnabled_Fixtures(); -function _Foo_optimized(t0) { +const isForgetEnabled_Fixtures_result = isForgetEnabled_Fixtures(); +function Foo_optimized(t0) { "use memo"; const $ = _c(3); const { prop1, prop2 } = t0; @@ -44,13 +44,13 @@ function _Foo_optimized(t0) { } return t1; } -function _Foo_unoptimized({ prop1, prop2 }) { +function Foo_unoptimized({ prop1, prop2 }) { "use memo"; return ; } function Foo(arg0) { - if (_isForgetEnabled_Fixtures_result) return _Foo_optimized(arg0); - else return _Foo_unoptimized(arg0); + if (isForgetEnabled_Fixtures_result) return Foo_optimized(arg0); + else return Foo_unoptimized(arg0); } export const FIXTURE_ENTRYPOINT = { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-with-hoisted-type-reference.flow.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-with-hoisted-type-reference.flow.expect.md index 26c6e510d147..f81680970c4b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-with-hoisted-type-reference.flow.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-with-hoisted-type-reference.flow.expect.md @@ -23,8 +23,8 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; import { c as _c } from "react/compiler-runtime"; +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; import { memo } from "react"; type Props = React.ElementConfig; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/infer-function-expression-React-memo-gating.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/infer-function-expression-React-memo-gating.expect.md index 4931b87b0176..aff21ee0bd3d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/infer-function-expression-React-memo-gating.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/infer-function-expression-React-memo-gating.expect.md @@ -13,8 +13,8 @@ export default React.forwardRef(function notNamedLikeAComponent(props) { ## Code ```javascript -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; -import { c as _c } from "react/compiler-runtime"; // @gating @compilationMode(infer) +import { c as _c } from "react/compiler-runtime"; +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode(infer) import React from "react"; export default React.forwardRef( isForgetEnabled_Fixtures() diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/invalid-fnexpr-reference.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/invalid-fnexpr-reference.expect.md index 47b58453ca03..cdf9acc2b65b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/invalid-fnexpr-reference.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/invalid-fnexpr-reference.expect.md @@ -23,8 +23,8 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; -import { c as _c } from "react/compiler-runtime"; // @gating +import { c as _c } from "react/compiler-runtime"; +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating import * as React from "react"; let Foo; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/multi-arrow-expr-export-default-gating-test.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/multi-arrow-expr-export-default-gating-test.expect.md index b3dc5011f9ca..1300bc8982d7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/multi-arrow-expr-export-default-gating-test.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/multi-arrow-expr-export-default-gating-test.expect.md @@ -19,8 +19,8 @@ export default props => ( ## Code ```javascript -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; -import { c as _c } from "react/compiler-runtime"; // @gating +import { c as _c } from "react/compiler-runtime"; +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating import { Stringify } from "shared-runtime"; const ErrorView = isForgetEnabled_Fixtures() diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/multi-arrow-expr-export-gating-test.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/multi-arrow-expr-export-gating-test.expect.md index ea2a1c154129..c8905939391b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/multi-arrow-expr-export-gating-test.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/multi-arrow-expr-export-gating-test.expect.md @@ -24,8 +24,8 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; -import { c as _c } from "react/compiler-runtime"; // @gating +import { c as _c } from "react/compiler-runtime"; +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating import { Stringify } from "shared-runtime"; const ErrorView = isForgetEnabled_Fixtures() diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/multi-arrow-expr-gating-test.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/multi-arrow-expr-gating-test.expect.md index cc5195edb222..74be7ac5666a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/multi-arrow-expr-gating-test.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/multi-arrow-expr-gating-test.expect.md @@ -26,8 +26,8 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; -import { c as _c } from "react/compiler-runtime"; // @gating +import { c as _c } from "react/compiler-runtime"; +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating import { Stringify } from "shared-runtime"; const ErrorView = isForgetEnabled_Fixtures() diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/reassigned-fnexpr-variable.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/reassigned-fnexpr-variable.expect.md index 5f18d98491ff..267da62b89ab 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/reassigned-fnexpr-variable.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/reassigned-fnexpr-variable.expect.md @@ -31,8 +31,8 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; -import { c as _c } from "react/compiler-runtime"; // @gating +import { c as _c } from "react/compiler-runtime"; +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating import * as React from "react"; /** diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-access-hook-guard.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-access-hook-guard.expect.md new file mode 100644 index 000000000000..c6e179a6e7fe --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-access-hook-guard.expect.md @@ -0,0 +1,66 @@ + +## Input + +```javascript +// @lowerContextAccess @enableEmitHookGuards +function App() { + const {foo} = useContext(MyContext); + const {bar} = useContext(MyContext); + return ; +} + +``` + +## Code + +```javascript +import { + $dispatcherGuard, + useContext_withSelector, +} from "react-compiler-runtime"; +import { c as _c } from "react/compiler-runtime"; // @lowerContextAccess @enableEmitHookGuards +function App() { + const $ = _c(3); + try { + $dispatcherGuard(0); + const { foo } = (function () { + try { + $dispatcherGuard(2); + return useContext_withSelector(MyContext, _temp); + } finally { + $dispatcherGuard(3); + } + })(); + const { bar } = (function () { + try { + $dispatcherGuard(2); + return useContext_withSelector(MyContext, _temp2); + } finally { + $dispatcherGuard(3); + } + })(); + let t0; + if ($[0] !== bar || $[1] !== foo) { + t0 = ; + $[0] = bar; + $[1] = foo; + $[2] = t0; + } else { + t0 = $[2]; + } + return t0; + } finally { + $dispatcherGuard(1); + } +} +function _temp2(t0) { + return [t0.bar]; +} +function _temp(t0) { + return [t0.foo]; +} + +``` + +### Eval output +(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-access-hook-guard.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-access-hook-guard.js new file mode 100644 index 000000000000..da881ea124a4 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lower-context-access-hook-guard.js @@ -0,0 +1,6 @@ +// @lowerContextAccess @enableEmitHookGuards +function App() { + const {foo} = useContext(MyContext); + const {bar} = useContext(MyContext); + return ; +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/no-fire-todo-syntax-shouldnt-throw.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/no-fire-todo-syntax-shouldnt-throw.expect.md index fecc28bb0015..7ba4ee28115d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/no-fire-todo-syntax-shouldnt-throw.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/no-fire-todo-syntax-shouldnt-throw.expect.md @@ -43,8 +43,7 @@ function FireComponent(props) { ## Code ```javascript -import { useFire } from "react/compiler-runtime"; -import { c as _c } from "react/compiler-runtime"; // @enableFire @panicThreshold(none) +import { c as _c, useFire } from "react/compiler-runtime"; // @enableFire @panicThreshold(none) import { fire } from "react"; /** diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/basic.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/basic.expect.md index 8d8bc179a245..36146cff00fa 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/basic.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/basic.expect.md @@ -21,8 +21,7 @@ function Component(props) { ## Code ```javascript -import { useFire } from "react/compiler-runtime"; -import { c as _c } from "react/compiler-runtime"; // @enableFire +import { c as _c, useFire } from "react/compiler-runtime"; // @enableFire import { fire } from "react"; function Component(props) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/deep-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/deep-scope.expect.md index a335fea8867b..de003f7007f8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/deep-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/deep-scope.expect.md @@ -30,8 +30,7 @@ function Component(props) { ## Code ```javascript -import { useFire } from "react/compiler-runtime"; -import { c as _c } from "react/compiler-runtime"; // @enableFire +import { c as _c, useFire } from "react/compiler-runtime"; // @enableFire import { fire } from "react"; function Component(props) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/fire-and-autodeps.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/fire-and-autodeps.expect.md index 5767ff0746c1..20260bd5e694 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/fire-and-autodeps.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/fire-and-autodeps.expect.md @@ -21,8 +21,7 @@ function Component(props) { ## Code ```javascript -import { useFire } from "react/compiler-runtime"; -import { c as _c } from "react/compiler-runtime"; // @enableFire @inferEffectDependencies +import { c as _c, useFire } from "react/compiler-runtime"; // @enableFire @inferEffectDependencies import { fire, useEffect } from "react"; function Component(props) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/hook-guard.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/hook-guard.expect.md new file mode 100644 index 000000000000..d94cce558845 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/hook-guard.expect.md @@ -0,0 +1,72 @@ + +## Input + +```javascript +// @enableFire @enableEmitHookGuards +import {fire} from 'react'; + +function Component(props) { + const foo = props => { + console.log(props); + }; + useEffect(() => { + fire(foo(props)); + }); + + return null; +} + +``` + +## Code + +```javascript +import { $dispatcherGuard } from "react-compiler-runtime"; +import { c as _c, useFire } from "react/compiler-runtime"; // @enableFire @enableEmitHookGuards +import { fire } from "react"; + +function Component(props) { + const $ = _c(3); + try { + $dispatcherGuard(0); + const foo = _temp; + const t0 = (function () { + try { + $dispatcherGuard(2); + return useFire(foo); + } finally { + $dispatcherGuard(3); + } + })(); + let t1; + if ($[0] !== props || $[1] !== t0) { + t1 = () => { + t0(props); + }; + $[0] = props; + $[1] = t0; + $[2] = t1; + } else { + t1 = $[2]; + } + (function () { + try { + $dispatcherGuard(2); + return useEffect(t1); + } finally { + $dispatcherGuard(3); + } + })(); + return null; + } finally { + $dispatcherGuard(1); + } +} +function _temp(props_0) { + console.log(props_0); +} + +``` + +### Eval output +(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/hook-guard.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/hook-guard.js new file mode 100644 index 000000000000..bc0b1a2d7cea --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/hook-guard.js @@ -0,0 +1,13 @@ +// @enableFire @enableEmitHookGuards +import {fire} from 'react'; + +function Component(props) { + const foo = props => { + console.log(props); + }; + useEffect(() => { + fire(foo(props)); + }); + + return null; +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/multiple-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/multiple-scope.expect.md index 02f393517125..796c4397eeca 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/multiple-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/multiple-scope.expect.md @@ -29,8 +29,7 @@ function Component(props) { ## Code ```javascript -import { useFire } from "react/compiler-runtime"; -import { c as _c } from "react/compiler-runtime"; // @enableFire +import { c as _c, useFire } from "react/compiler-runtime"; // @enableFire import { fire } from "react"; function Component(props) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/repeated-calls.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/repeated-calls.expect.md index 1734ca3ab458..e528823550f2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/repeated-calls.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/repeated-calls.expect.md @@ -22,8 +22,7 @@ function Component(props) { ## Code ```javascript -import { useFire } from "react/compiler-runtime"; -import { c as _c } from "react/compiler-runtime"; // @enableFire +import { c as _c, useFire } from "react/compiler-runtime"; // @enableFire import { fire } from "react"; function Component(props) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/repro-dont-add-hook-guards-on-retry.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/repro-dont-add-hook-guards-on-retry.expect.md index 5a27845f079e..c7ed50ceba20 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/repro-dont-add-hook-guards-on-retry.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/repro-dont-add-hook-guards-on-retry.expect.md @@ -23,7 +23,6 @@ function Component(props, useDynamicHook) { ## Code ```javascript -import { $dispatcherGuard } from "react-compiler-runtime"; import { useFire } from "react/compiler-runtime"; import { useEffect, fire } from "react"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/rewrite-deps.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/rewrite-deps.expect.md index ae71f6039328..e569536ad3a0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/rewrite-deps.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/rewrite-deps.expect.md @@ -21,8 +21,7 @@ function Component(props) { ## Code ```javascript -import { useFire } from "react/compiler-runtime"; -import { c as _c } from "react/compiler-runtime"; // @enableFire +import { c as _c, useFire } from "react/compiler-runtime"; // @enableFire import { fire } from "react"; function Component(props) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/shared-hook-calls.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/shared-hook-calls.expect.md index 9b689b31c7ba..92dbf9843ad6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/shared-hook-calls.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/shared-hook-calls.expect.md @@ -26,8 +26,7 @@ function Component({bar, baz}) { ## Code ```javascript -import { useFire } from "react/compiler-runtime"; -import { c as _c } from "react/compiler-runtime"; // @enableFire +import { c as _c, useFire } from "react/compiler-runtime"; // @enableFire import { fire } from "react"; function Component(t0) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/index.ts b/compiler/packages/babel-plugin-react-compiler/src/index.ts index fa330f5582fc..3310581462dc 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/index.ts @@ -19,6 +19,7 @@ export { parsePluginOptions, OPT_OUT_DIRECTIVES, OPT_IN_DIRECTIVES, + ProgramContext, findDirectiveEnablingMemoization, findDirectiveDisablingMemoization, type CompilerPipelineValue,