mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 12:20:20 +01:00
[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:
parent
7c908bcf4e
commit
c61e75b76d
|
|
@ -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),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
export class ProgramContext {
|
||||
/* Program and environment context */
|
||||
scope: BabelScope;
|
||||
reactRuntimeModule: string;
|
||||
hookPattern: string | null;
|
||||
|
||||
// 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)
|
||||
*/
|
||||
CompilerError.invariant(identifiers.has(importSpecifierName) === false, {
|
||||
reason: `Encountered conflicting import specifier for ${importSpecifierName} in Forget config.`,
|
||||
description: null,
|
||||
loc: GeneratedSource,
|
||||
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> = [];
|
||||
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.hasBinding(importSpecifierName) === false,
|
||||
path.scope.getBinding(loweredImport.name) == null,
|
||||
{
|
||||
reason: `Encountered conflicting import specifiers for ${importSpecifierName} in generated program.`,
|
||||
description: null,
|
||||
reason:
|
||||
'Encountered conflicting import specifiers in generated program',
|
||||
description: `Conflict from import ${loweredImport.module}:(${loweredImport.imported} as ${loweredImport.name}).`,
|
||||
loc: GeneratedSource,
|
||||
suggestions: null,
|
||||
},
|
||||
);
|
||||
identifiers.add(importSpecifierName);
|
||||
|
||||
const importSpecifierNameList = getOrInsertDefault(
|
||||
sortedImports,
|
||||
source,
|
||||
[],
|
||||
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,
|
||||
},
|
||||
);
|
||||
importSpecifierNameList.push(importSpecifierName);
|
||||
}
|
||||
|
||||
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 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),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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: [],
|
||||
};
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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>;
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
// @enableEmitFreeze @instrumentForget
|
||||
function useFoo(props) {
|
||||
return foo(props.x, __DEV__);
|
||||
}
|
||||
|
|
@ -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.
|
||||
```
|
||||
|
||||
|
||||
|
|
@ -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 | }
|
||||
```
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
// @enableEmitFreeze @instrumentForget
|
||||
function useFoo(props) {
|
||||
const __DEV__ = 'conflicting global';
|
||||
console.log(__DEV__);
|
||||
return foo(props.x);
|
||||
}
|
||||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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}],
|
||||
};
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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() ? () => {} : () => {},
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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>;
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
// @lowerContextAccess @enableEmitHookGuards
|
||||
function App() {
|
||||
const {foo} = useContext(MyContext);
|
||||
const {bar} = useContext(MyContext);
|
||||
return <Bar foo={foo} bar={bar} />;
|
||||
}
|
||||
|
|
@ -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";
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ export {
|
|||
parsePluginOptions,
|
||||
OPT_OUT_DIRECTIVES,
|
||||
OPT_IN_DIRECTIVES,
|
||||
ProgramContext,
|
||||
findDirectiveEnablingMemoization,
|
||||
findDirectiveDisablingMemoization,
|
||||
type CompilerPipelineValue,
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user