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

Avoid failing builds when imported function specifiers conflict by using
babel's `generateUid`. Failing a build is very disruptive, as it usually
presents to developers similar to a javascript parse error.
```js
import {logRender as _logRender} from 'instrument-runtime';

const logRender = () => { /* local conflicting implementation */ }

function Component_optimized() {
  _logRender(); // inserted by compiler
}
```

Currently, we fail builds (even in `panicThreshold:none` cases) when
import specifiers are detected to conflict with existing local
variables. The reason we destructively throw (instead of bailing out) is
because (1) we first generate identifier references to the conflicting
name in compiled functions, (2) replaced original functions with
compiled functions, and then (3) finally check for conflicts.

When we finally check for conflicts, it's too late to bail out.
```js
// import {logRender} from 'instrument-runtime';

const logRender = () => { /* local conflicting implementation */ }

function Component_optimized() {
  logRender(); // inserted by compiler
}
```
This commit is contained in:
mofeiZ 2025-03-24 09:31:51 -04:00 committed by GitHub
parent 7c908bcf4e
commit c61e75b76d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
61 changed files with 1017 additions and 413 deletions

View File

@ -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<t.FunctionDeclaration>,
compiled: t.FunctionDeclaration,
gating: NonNullable<PluginOptions['gating']>,
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<PluginOptions['gating']>,
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),
);

View File

@ -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<t.Program>,
@ -42,50 +52,209 @@ export function validateRestrictedImports(
}
}
export function addImportsToProgram(
path: NodePath<t.Program>,
importList: Array<ExternalFunction>,
): void {
const identifiers: Set<string> = new Set();
const sortedImports: Map<string, Array<string>> = new Map();
for (const {importSpecifierName, source} of importList) {
/*
* Codegen currently does not rename import specifiers, so we do additional
* validation here
*/
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,
{
reason: `Encountered conflicting import specifiers for ${importSpecifierName} in generated program.`,
description: null,
loc: GeneratedSource,
suggestions: null,
},
);
identifiers.add(importSpecifierName);
export class ProgramContext {
/* Program and environment context */
scope: BabelScope;
reactRuntimeModule: string;
hookPattern: string | null;
const importSpecifierNameList = getOrInsertDefault(
sortedImports,
source,
[],
);
importSpecifierNameList.push(importSpecifierName);
// known generated or referenced identifiers in the program
knownReferencedNames: Set<string> = new Set();
// generated imports
imports: Map<string, Map<string, NonLocalImportSpecifier>> = new Map();
constructor(
program: NodePath<t.Program>,
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)
*/
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(
{
source: this.reactRuntimeModule,
importSpecifierName: 'c',
},
'_c',
);
}
/**
*
* @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};
}
const binding: NonLocalImportSpecifier = {
kind: 'ImportSpecifier',
name: this.newUid(nameHint ?? specifier),
module,
imported: specifier,
};
getOrInsertWith(this.imports, module, () => new Map()).set(specifier, {
...binding,
});
return binding;
}
addNewReference(name: string): void {
this.knownReferencedNames.add(name);
}
assertGlobalBinding(
name: string,
localScope?: BabelScope,
): Result<void, CompilerError> {
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);
}
}
function getExistingImports(
program: NodePath<t.Program>,
): Map<string, NodePath<t.ImportDeclaration>> {
const existingImports = new Map<string, NodePath<t.ImportDeclaration>>();
program.traverse({
ImportDeclaration(path) {
if (isNonNamespacedImport(path)) {
existingImports.set(path.node.source.value, path);
}
},
});
return existingImports;
}
export function addImportsToProgram(
path: NodePath<t.Program>,
programContext: ProgramContext,
): void {
const existingImports = getExistingImports(path);
const stmts: Array<t.ImportDeclaration> = [];
for (const [source, importSpecifierNameList] of sortedImports) {
const importSpecifiers = importSpecifierNameList.map(name => {
const id = t.identifier(name);
return t.importSpecifier(id, id);
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<NonLocalImportSpecifier> = [
...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),
);
});
stmts.push(t.importDeclaration(importSpecifiers, t.stringLiteral(source)));
/**
* If an existing import of this module exists (ie `import { ... } from
* '<moduleName>'`), 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)),
);
}
}
path.unshiftContainer('body', stmts);
}
@ -93,13 +262,12 @@ export function addImportsToProgram(
/*
* Matches `import { ... } from <moduleName>;`
* but not `import * as React from <moduleName>;`
* `import type { Foo } from <moduleName>;`
*/
function isNonNamespacedImport(
importDeclPath: NodePath<t.ImportDeclaration>,
moduleName: string,
): boolean {
return (
importDeclPath.get('source').node.value === moduleName &&
importDeclPath
.get('specifiers')
.every(specifier => specifier.isImportSpecifier()) &&
@ -107,94 +275,3 @@ function isNonNamespacedImport(
importDeclPath.node.importKind !== 'typeof'
);
}
function hasExistingNonNamespacedImportOfModule(
program: NodePath<t.Program>,
moduleName: string,
): boolean {
let hasExistingImport = false;
program.traverse({
ImportDeclaration(importDeclPath) {
if (isNonNamespacedImport(importDeclPath, moduleName)) {
hasExistingImport = true;
}
},
});
return hasExistingImport;
}
/*
* If an existing import of React exists (ie `import { ... } from '<moduleName>'`), inject useMemoCache
* into the list of destructured variables.
*/
function addMemoCacheFunctionSpecifierToExistingImport(
program: NodePath<t.Program>,
moduleName: string,
identifierName: string,
): boolean {
let didInsertUseMemoCache = false;
program.traverse({
ImportDeclaration(importDeclPath) {
if (
!didInsertUseMemoCache &&
isNonNamespacedImport(importDeclPath, moduleName)
) {
importDeclPath.pushContainer(
'specifiers',
t.importSpecifier(t.identifier(identifierName), t.identifier('c')),
);
didInsertUseMemoCache = true;
}
},
});
return didInsertUseMemoCache;
}
export function updateMemoCacheFunctionImport(
program: NodePath<t.Program>,
moduleName: string,
useMemoCacheIdentifier: string,
): void {
/*
* If there isn't already an import of * as React, insert it so useMemoCache doesn't
* throw
*/
const hasExistingImport = hasExistingNonNamespacedImportOfModule(
program,
moduleName,
);
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`,
);
}
} else {
addMemoCacheFunctionImportDeclaration(
program,
moduleName,
useMemoCacheIdentifier,
);
}
}
function addMemoCacheFunctionImportDeclaration(
program: NodePath<t.Program>,
moduleName: string,
localName: string,
): void {
program.unshiftContainer(
'body',
t.importDeclaration(
[t.importSpecifier(t.identifier(localName), t.identifier('c'))],
t.stringLiteral(moduleName),
),
);
}

View File

@ -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,

View File

@ -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<CompileResult>;
} = 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<ExternalFunction> = [];
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;
}
}

View File

@ -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<typeof ExternalFunctionSchema>;
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<t.Identifier>;
#hoistedIdentifiers: Set<t.Identifier>;
@ -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, CompilerError>): void {
if (errors.isOk() || this.logger == null) {
return;

View File

@ -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

View File

@ -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) {

View File

@ -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;
}
}

View File

@ -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<IdentifierId, CallExpression> = new Map();
const contextKeys: Map<IdentifierId, Array<string>> = 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<Instruction> | 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,
};

View File

@ -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<JsxInstruction>,
): Array<OutlinedJsxAttribute> | null {
let id = 1;
@ -227,6 +228,7 @@ function collectProps(
newName = `${oldName}${id++}`;
}
seen.add(newName);
env.programContext.addNewReference(newName);
return newName;
}

View File

@ -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<t.Statement> = [];
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<t.Statement> = [];
const changeDetectionStatements: Array<t.Statement> = [];
const idempotenceDetectionStatements: Array<t.Statement> = [];
@ -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<t.Statement>,
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,

View File

@ -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<string> {
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<DeclarationId, IdentifierName> = new Map();
#stack: Array<Map<string, DeclarationId>> = [new Map()];
#globals: Set<string>;
#programContext: ProgramContext;
names: Set<ValidIdentifierName> = new Set();
constructor(globals: Set<string>) {
constructor(globals: Set<string>, 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);

View File

@ -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<InstructionId, Array<Instruction>>();
@ -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 {

View File

@ -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 (
<div>
{expensiveNumber}
{`${someImport}`}
</div>
);
}
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 = (
<div>
{expensiveNumber}
{`${someImport}`}
</div>
);
$[2] = expensiveNumber;
$[3] = t2;
} else {
t2 = $[3];
}
return t2;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [],
};
```
### Eval output
(kind: ok) <div>0undefined</div>

View File

@ -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 (
<div>
{expensiveNumber}
{`${someImport}`}
</div>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [],
};

View File

@ -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

View File

@ -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) {

View File

@ -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 (
<div style={shouldInstrument} other={_shouldInstrument}>
{props.bar}
</div>
);
}
function Foo(props) {
'use forget';
return <Foo>{props.bar}</Foo>;
}
```
## 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 = (
<div style={shouldInstrument} other={_shouldInstrument}>
{props.bar}
</div>
);
$[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 = <Foo>{props.bar}</Foo>;
$[0] = props.bar;
$[1] = t0;
} else {
t0 = $[1];
}
return t0;
}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@ -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 (
<div style={shouldInstrument} other={_shouldInstrument}>
{props.bar}
</div>
);
}
function Foo(props) {
'use forget';
return <Foo>{props.bar}</Foo>;
}

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,4 @@
// @enableEmitFreeze @instrumentForget
function useFoo(props) {
return foo(props.x, __DEV__);
}

View File

@ -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.
```

View File

@ -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 | }
```

View File

@ -0,0 +1,6 @@
// @enableEmitFreeze @instrumentForget
function useFoo(props) {
const __DEV__ = 'conflicting global';
console.log(__DEV__);
return foo(props.x);
}

View File

@ -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) => {

View File

@ -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";

View File

@ -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<Controls>,
): React.Node {
return <Stringify ref={ref} />;
}
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 = {

View File

@ -0,0 +1,62 @@
## Input
```javascript
// @gating
export const isForgetEnabled_Fixtures = () => {
'use no forget';
return false;
};
export function Bar(props) {
'use forget';
return <div>{props.bar}</div>;
}
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 = <div>{props.bar}</div>;
$[0] = props.bar;
$[1] = t0;
} else {
t0 = $[1];
}
return t0;
}
: function Bar(props) {
"use forget";
return <div>{props.bar}</div>;
};
export const FIXTURE_ENTRYPOINT = {
fn: eval("Bar"),
params: [{ bar: 2 }],
};
```
### Eval output
(kind: ok) <div>2</div>

View File

@ -0,0 +1,16 @@
// @gating
export const isForgetEnabled_Fixtures = () => {
'use no forget';
return false;
};
export function Bar(props) {
'use forget';
return <div>{props.bar}</div>;
}
export const FIXTURE_ENTRYPOINT = {
fn: eval('Bar'),
params: [{bar: 2}],
};

View File

@ -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);

View File

@ -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() ? () => {} : () => {},

View File

@ -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);

View File

@ -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";

View File

@ -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";

View File

@ -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";

View File

@ -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";

View File

@ -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 <Stringify ref={ref} {...props} />;
}
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 = {

View File

@ -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 <Stringify prop1={prop1} prop2={prop2} />;
}
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 = {

View File

@ -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<typeof Component>;

View File

@ -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()

View File

@ -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;

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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";
/**

View File

@ -0,0 +1,66 @@
## Input
```javascript
// @lowerContextAccess @enableEmitHookGuards
function App() {
const {foo} = useContext(MyContext);
const {bar} = useContext(MyContext);
return <Bar foo={foo} bar={bar} />;
}
```
## 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 = <Bar foo={foo} bar={bar} />;
$[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

View File

@ -0,0 +1,6 @@
// @lowerContextAccess @enableEmitHookGuards
function App() {
const {foo} = useContext(MyContext);
const {bar} = useContext(MyContext);
return <Bar foo={foo} bar={bar} />;
}

View File

@ -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";
/**

View File

@ -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) {

View File

@ -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) {

View File

@ -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) {

View File

@ -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

View File

@ -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;
}

View File

@ -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) {

View File

@ -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) {

View File

@ -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";

View File

@ -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) {

View File

@ -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) {

View File

@ -19,6 +19,7 @@ export {
parsePluginOptions,
OPT_OUT_DIRECTIVES,
OPT_IN_DIRECTIVES,
ProgramContext,
findDirectiveEnablingMemoization,
findDirectiveDisablingMemoization,
type CompilerPipelineValue,