Compare commits

..

14 Commits

Author SHA1 Message Date
Sebastian Sebbie Silbermann
ede3840137 Version 19.1.2 2025-12-03 15:33:20 +01:00
Sebastian Markbage
0e2ae74309 Bring ReactFlightClient fixes to FlightReplyServer
We did a bunch of refactors to ReactFlightClient in PRs like #29823 and #33664.

This brings a bunch of those related refactors to the equivalent FlightReplyServer. Such as deep resolution of cycles and deferred error handling.
2025-12-03 15:32:26 +01:00
Sebastian "Sebbie" Silbermann
6e79ecf60b [release] Allow building single release channel with processed versions (#35270) 2025-12-03 01:38:04 +01:00
Jack Pope
02ef495809 Reset packages we are not releasing to currently published versions 2025-07-24 16:35:10 -04:00
Riccardo Cipolleschi
3f178f55fc [Release] Update build script to properly set React Native's renderers version (#33972) 2025-07-24 16:49:56 +01:00
Jack Pope
87e33ca2b7 Set release versions to 19.1.1 2025-07-16 13:05:36 -04:00
lauren
52cf381c72 [eprh] Bump stable version (#32978)
https://www.npmjs.com/package/eslint-plugin-react-hooks/v/6.0.0 was just
released, so we can bump this now.
2025-07-16 12:55:41 -04:00
lauren
b793948e15 Bump next prerelease version numbers (#32782)
Updates the version numbers in the prerelease channels.
2025-07-16 12:55:24 -04:00
Jack Pope
73e4ba42cd Allow runtime_build_and_test action to trigger manually (#33796) 2025-07-16 12:52:44 -04:00
Ruslan Lesiutin
5a1eb6f61a fix: rename bottom stack frame (#33680)
`react-stack-bottom-frame` -> `react_stack_bottom_frame`.

This survives `@babel/plugin-transform-function-name`, but now frames
will be displayed as `at Object.react_stack_bottom_frame (...)` in V8.
Checks that were relying on exact function name match were updated to
use either `.indexOf()` or `.includes()`

For backwards compatibility, both React DevTools and Flight Client will
look for both options. I am not so sure about the latter and if React
version is locked.
2025-07-16 11:53:56 -04:00
Sebastian Markbåge
01eae200bf [DevTools] Get source location from structured callsites in prepareStackTrace (#33143)
When we get the source location for "View source for this element" we
should be using the enclosing function of the callsite of the child. So
that we don't just point to some random line within the component.

This is similar to the technique in #33136.

This technique is now really better than the fake throw technique, when
available. So I now favor the owner technique. The only problem it's
only available in DEV and only if it has a child that's owned (and not
filtered).

We could implement this same technique for the error that's thrown in
the fake throwing solution. However, we really shouldn't need that at
all because for client components we should be able to call
`inspect(fn)` at least in Chrome which is even better.
2025-07-16 11:11:51 -04:00
Timothy Yung
0e6781a06b Enable the enableEagerAlternateStateNodeCleanup Feature Flag (#33447)
Enables the `enableEagerAlternateStateNodeCleanup` feature flag for all
variants, while maintaining the `__VARIANT__` for the internal React
Native flavor for backtesting reasons.

```
$ yarn test
```
2025-07-15 15:29:02 -04:00
Samuel Susla
2cd3c424ea Add eager alternate.stateNode cleanup (#33161)
This is a fix for a problem where React retains shadow nodes longer than
it needs to. The behaviour is shown in React Native test:
https://github.com/facebook/react-native/blob/main/packages/react-native/src/private/__tests__/utilities/__tests__/ShadowNodeReferenceCounter-itest.js#L169

When React commits a new shadow tree, old shadow nodes are stored inside
`fiber.alternate.stateNode`. This is not cleared up until React clones
the node again. This may be problematic if mutation deletes a subtree,
in that case `fiber.alternate.stateNode` will retain entire subtree
until next update. In case of image nodes, this means retaining entire
images.

So when React goes from revision A: `<View><View /></View>` to revision
B: `<View />`, `fiber.alternate.stateNode` will be pointing to Shadow
Node that represents revision A..

![image](https://github.com/user-attachments/assets/076b677e-d152-4763-8c9d-4f923212b424)

To fix this, this PR adds a new feature flag
`enableEagerAlternateStateNodeCleanup`. When enabled,
`alternate.stateNode` is proactively pointed towards finishedWork's
stateNode, releasing resources sooner.

I have verified this fixes the issue [demonstrated by React Native
tests](https://github.com/facebook/react-native/blob/main/packages/react-native/src/private/__tests__/utilities/__tests__/ShadowNodeReferenceCounter-itest.js#L169).
All existing React tests pass when the flag is enabled.
2025-07-15 15:24:50 -04:00
Jack Pope
a24654e65b Ship enableFabricCompleteRootInCommitPhase (#33064)
This was shipped internally. Cleaning up the flag.
2025-07-15 15:16:15 -04:00
175 changed files with 1932 additions and 4484 deletions

View File

@@ -6,6 +6,12 @@ on:
pull_request:
paths-ignore:
- compiler/**
workflow_dispatch:
inputs:
commit_sha:
required: false
type: string
default: ''
permissions: {}
@@ -28,7 +34,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
- name: Check cache hit
uses: actions/cache/restore@v4
id: node_modules
@@ -69,7 +75,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
- name: Check cache hit
uses: actions/cache/restore@v4
id: node_modules
@@ -117,7 +123,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
- uses: actions/github-script@v7
id: set-matrix
with:
@@ -136,7 +142,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -166,7 +172,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -198,7 +204,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -254,7 +260,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -294,7 +300,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -389,7 +395,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -430,7 +436,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -458,7 +464,7 @@ jobs:
merge-multiple: true
- name: Display structure of build
run: ls -R build
- run: echo ${{ github.event.pull_request.head.sha || github.sha }} >> build/COMMIT_SHA
- run: echo ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }} >> build/COMMIT_SHA
- name: Scrape warning messages
run: |
mkdir -p ./build/__test_utils__
@@ -483,7 +489,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -523,7 +529,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -560,7 +566,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -601,7 +607,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -675,7 +681,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -732,7 +738,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -777,7 +783,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -822,7 +828,7 @@ jobs:
node ./scripts/print-warnings/print-warnings.js > build/__test_utils__/ReactAllWarnings.js
- name: Display structure of build for PR
run: ls -R build
- run: echo ${{ github.event.pull_request.head.sha || github.sha }} >> build/COMMIT_SHA
- run: echo ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }} >> build/COMMIT_SHA
- run: node ./scripts/tasks/danger
- name: Archive sizebot results
uses: actions/upload-artifact@v4

View File

@@ -7,18 +7,18 @@
//
// The @latest channel uses the version as-is, e.g.:
//
// 19.1.0
// 19.1.2
//
// The @canary channel appends additional information, with the scheme
// <version>-<label>-<commit_sha>, e.g.:
//
// 19.1.0-canary-a1c2d3e4
// 19.1.2-canary-a1c2d3e4
//
// The @experimental channel doesn't include a version, only a date and a sha, e.g.:
//
// 0.0.0-experimental-241c4467e-20200129
const ReactVersion = '19.1.0';
const ReactVersion = '19.1.2';
// The label used by the @canary channel. Represents the upcoming release's
// stability. Most of the time, this will be "canary", but we may temporarily
@@ -34,7 +34,7 @@ const rcNumber = 0;
const stablePackages = {
'eslint-plugin-react-hooks': '5.2.0',
'jest-react': '0.17.0',
'jest-react': '0.16.0',
react: ReactVersion,
'react-art': ReactVersion,
'react-dom': ReactVersion,

View File

@@ -7,9 +7,8 @@
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
@@ -35,8 +34,7 @@ import {ExternalFunction} from '..';
function insertAdditionalFunctionDeclaration(
fnPath: NodePath<t.FunctionDeclaration>,
compiled: t.FunctionDeclaration,
programContext: ProgramContext,
gatingFunctionIdentifierName: string,
gating: NonNullable<PluginOptions['gating']>,
): void {
const originalFnName = fnPath.node.id;
const originalFnParams = fnPath.node.params;
@@ -59,14 +57,14 @@ function insertAdditionalFunctionDeclaration(
loc: fnPath.node.loc ?? null,
});
const gatingCondition = t.identifier(
programContext.newUid(`${gatingFunctionIdentifierName}_result`),
const gatingCondition = fnPath.scope.generateUidIdentifier(
`${gating.importSpecifierName}_result`,
);
const unoptimizedFnName = t.identifier(
programContext.newUid(`${originalFnName.name}_unoptimized`),
const unoptimizedFnName = fnPath.scope.generateUidIdentifier(
`${originalFnName.name}_unoptimized`,
);
const optimizedFnName = t.identifier(
programContext.newUid(`${originalFnName.name}_optimized`),
const optimizedFnName = fnPath.scope.generateUidIdentifier(
`${originalFnName.name}_optimized`,
);
/**
* Step 1: rename existing functions
@@ -117,7 +115,7 @@ function insertAdditionalFunctionDeclaration(
t.variableDeclaration('const', [
t.variableDeclarator(
gatingCondition,
t.callExpression(t.identifier(gatingFunctionIdentifierName), []),
t.callExpression(t.identifier(gating.importSpecifierName), []),
),
]),
);
@@ -131,26 +129,19 @@ export function insertGatedFunctionDeclaration(
| t.FunctionDeclaration
| t.ArrowFunctionExpression
| t.FunctionExpression,
programContext: ProgramContext,
gating: ExternalFunction,
gating: NonNullable<PluginOptions['gating']>,
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,
programContext,
gatingImportedName,
);
insertAdditionalFunctionDeclaration(fnPath, compiled, gating);
} else {
const gatingExpression = t.conditionalExpression(
t.callExpression(t.identifier(gatingImportedName), []),
t.callExpression(t.identifier(gating.importSpecifierName), []),
buildFunctionExpression(compiled),
buildFunctionExpression(fnPath.node),
);

View File

@@ -7,19 +7,9 @@
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,
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';
import {EnvironmentConfig, ExternalFunction, GeneratedSource} from '../HIR';
import {getOrInsertDefault} from '../Utils/utils';
export function validateRestrictedImports(
path: NodePath<t.Program>,
@@ -52,209 +42,50 @@ export function validateRestrictedImports(
}
}
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)
*/
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,
importList: Array<ExternalFunction>,
): void {
const existingImports = getExistingImports(path);
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);
const importSpecifierNameList = getOrInsertDefault(
sortedImports,
source,
[],
);
importSpecifierNameList.push(importSpecifierName);
}
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.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),
);
for (const [source, importSpecifierNameList] of sortedImports) {
const importSpecifiers = importSpecifierNameList.map(name => {
const id = t.identifier(name);
return t.importSpecifier(id, id);
});
/**
* 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)),
);
}
stmts.push(t.importDeclaration(importSpecifiers, t.stringLiteral(source)));
}
path.unshiftContainer('body', stmts);
}
@@ -262,12 +93,13 @@ 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()) &&
@@ -275,3 +107,94 @@ 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, ProgramContext} from '.';
import {Logger} from '.';
import {
HIRFunction,
ReactiveFunction,
@@ -117,7 +117,7 @@ function run(
config: EnvironmentConfig,
fnType: ReactFunctionType,
mode: CompilerMode,
programContext: ProgramContext,
useMemoCacheIdentifier: string,
logger: Logger | null,
filename: string | null,
code: string | null,
@@ -132,7 +132,7 @@ function run(
logger,
filename,
code,
programContext,
useMemoCacheIdentifier,
);
env.logger?.debugLogIRs?.({
kind: 'debug',
@@ -552,7 +552,7 @@ export function compileFn(
config: EnvironmentConfig,
fnType: ReactFunctionType,
mode: CompilerMode,
programContext: ProgramContext,
useMemoCacheIdentifier: string,
logger: Logger | null,
filename: string | null,
code: string | null,
@@ -562,7 +562,7 @@ export function compileFn(
config,
fnType,
mode,
programContext,
useMemoCacheIdentifier,
logger,
filename,
code,

View File

@@ -12,7 +12,11 @@ import {
CompilerErrorDetail,
ErrorSeverity,
} from '../CompilerError';
import {EnvironmentConfig, ReactFunctionType} from '../HIR/Environment';
import {
EnvironmentConfig,
ExternalFunction,
ReactFunctionType,
} from '../HIR/Environment';
import {CodegenFunction} from '../ReactiveScopes';
import {isComponentDeclaration} from '../Utils/ComponentDeclaration';
import {isHookDeclaration} from '../Utils/HookDeclaration';
@@ -20,10 +24,10 @@ import {assertExhaustive} from '../Utils/utils';
import {insertGatedFunctionDeclaration} from './Gating';
import {
addImportsToProgram,
ProgramContext,
updateMemoCacheFunctionImport,
validateRestrictedImports,
} from './Imports';
import {CompilerReactTarget, PluginOptions} from './Options';
import {PluginOptions} from './Options';
import {compileFn} from './Pipeline';
import {
filterSuppressionsThatAffectFunction,
@@ -295,12 +299,8 @@ 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',
programContext,
useMemoCacheIdentifier.name,
pass.opts.logger,
pass.filename,
pass.code,
@@ -445,7 +445,7 @@ export function compileProgram(
environment,
fnType,
'no_inferred_memo',
programContext,
useMemoCacheIdentifier.name,
pass.opts.logger,
pass.filename,
pass.code,
@@ -453,7 +453,7 @@ export function compileProgram(
};
if (
!compileResult.compiledFn.hasFireRewrite &&
!compileResult.compiledFn.hasInferredEffect
!compileResult.compiledFn.hasLoweredContextAccess
) {
return null;
}
@@ -554,29 +554,79 @@ 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 (referencedBeforeDeclared != null && kind === 'original') {
CompilerError.invariant(pass.opts.gating != null, {
reason: "Expected 'gating' import to be present",
loc: null,
});
if (gating != null && kind === 'original') {
insertGatedFunctionDeclaration(
originalFn,
transformedFn,
programContext,
pass.opts.gating,
referencedBeforeDeclared.has(result),
gating.gatingFn,
gating.referencedBeforeDeclared.has(result),
);
} else {
originalFn.replaceWith(transformedFn);
@@ -585,7 +635,22 @@ export function compileProgram(
// Forget compiled the component, we need to update existing imports of useMemoCache
if (compiledFns.length > 0) {
addImportsToProgram(program, programContext);
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);
}
return {retryErrors};
}
@@ -618,7 +683,7 @@ function shouldSkipCompilation(
if (
hasMemoCacheFunctionImport(
program,
getReactCompilerRuntimeModule(pass.opts.target),
getReactCompilerRuntimeModule(pass.opts),
)
) {
return true;
@@ -1112,18 +1177,16 @@ function getFunctionReferencedBeforeDeclarationAtTopLevel(
return referencedBeforeDeclaration;
}
export function getReactCompilerRuntimeModule(
target: CompilerReactTarget,
): string {
if (target === '19') {
function getReactCompilerRuntimeModule(opts: PluginOptions): string {
if (opts.target === '19') {
return 'react/compiler-runtime'; // from react namespace
} else if (target === '17' || target === '18') {
} else if (opts.target === '17' || opts.target === '18') {
return 'react-compiler-runtime'; // npm package
} else {
CompilerError.invariant(
target != null &&
target.kind === 'donotuse_meta_internal' &&
typeof target.runtimeModule === 'string',
opts.target != null &&
opts.target.kind === 'donotuse_meta_internal' &&
typeof opts.target.runtimeModule === 'string',
{
reason: 'Expected target to already be validated',
description: null,
@@ -1131,6 +1194,6 @@ export function getReactCompilerRuntimeModule(
suggestions: null,
},
);
return target.runtimeModule;
return opts.target.runtimeModule;
}
}

View File

@@ -15,7 +15,6 @@ import {
PanicThresholdOptions,
parsePluginOptions,
PluginOptions,
ProgramContext,
} from '../Entrypoint';
import {Err, Ok, Result} from '../Utils/Result';
import {
@@ -85,8 +84,6 @@ 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')}),
@@ -849,9 +846,9 @@ export class Environment {
config: EnvironmentConfig;
fnType: ReactFunctionType;
compilerMode: CompilerMode;
programContext: ProgramContext;
useMemoCacheIdentifier: string;
hasLoweredContextAccess: boolean;
hasFireRewrite: boolean;
hasInferredEffect: boolean;
#contextIdentifiers: Set<t.Identifier>;
#hoistedIdentifiers: Set<t.Identifier>;
@@ -865,7 +862,7 @@ export class Environment {
logger: Logger | null,
filename: string | null,
code: string | null,
programContext: ProgramContext,
useMemoCacheIdentifier: string,
) {
this.#scope = scope;
this.fnType = fnType;
@@ -874,11 +871,11 @@ export class Environment {
this.filename = filename;
this.code = code;
this.logger = logger;
this.programContext = programContext;
this.useMemoCacheIdentifier = useMemoCacheIdentifier;
this.#shapes = new Map(DEFAULT_SHAPES);
this.#globals = new Map(DEFAULT_GLOBALS);
this.hasLoweredContextAccess = false;
this.hasFireRewrite = false;
this.hasInferredEffect = false;
if (
config.disableMemoizationForDebugging &&
@@ -940,10 +937,6 @@ 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

@@ -10,10 +10,7 @@ import {
BUILTIN_SHAPES,
BuiltInArrayId,
BuiltInFireId,
BuiltInMapId,
BuiltInMixedReadonlyId,
BuiltInObjectId,
BuiltInSetId,
BuiltInUseActionStateId,
BuiltInUseContextHookId,
BuiltInUseEffectHookId,
@@ -48,23 +45,29 @@ export const DEFAULT_SHAPES: ShapeRegistry = new Map(BUILTIN_SHAPES);
// Hack until we add ObjectShapes for all globals
const UNTYPED_GLOBALS: Set<string> = new Set([
'String',
'Object',
'Function',
'Number',
'RegExp',
'Date',
'Error',
'Function',
'TypeError',
'RangeError',
'ReferenceError',
'SyntaxError',
'URIError',
'EvalError',
'Boolean',
'DataView',
'Float32Array',
'Float64Array',
'Int8Array',
'Int16Array',
'Int32Array',
'Map',
'Set',
'WeakMap',
'Uint8Array',
'Uint8ClampedArray',
@@ -72,8 +75,16 @@ const UNTYPED_GLOBALS: Set<string> = new Set([
'Uint32Array',
'ArrayBuffer',
'JSON',
'parseFloat',
'parseInt',
'console',
'isNaN',
'eval',
'isFinite',
'encodeURI',
'decodeURI',
'encodeURIComponent',
'decodeURIComponent',
]);
const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
@@ -90,23 +101,6 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
returnValueKind: ValueKind.Mutable,
}),
],
[
/**
* Object.fromEntries(iterable)
* iterable: An iterable, such as an Array or Map, containing a list of
* objects. Each object should have two properties.
* Returns a new object whose properties are given by the entries of the
* iterable.
*/
'fromEntries',
addFunction(DEFAULT_SHAPES, [], {
positionalParams: [Effect.ConditionallyMutate],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInObjectId},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Mutable,
}),
],
]),
],
[
@@ -138,7 +132,7 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
'from',
addFunction(DEFAULT_SHAPES, [], {
positionalParams: [
Effect.ConditionallyMutateIterator,
Effect.ConditionallyMutate,
Effect.ConditionallyMutate,
Effect.ConditionallyMutate,
],
@@ -378,118 +372,6 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
returnValueKind: ValueKind.Primitive,
}),
],
[
'parseInt',
addFunction(DEFAULT_SHAPES, [], {
positionalParams: [],
restParam: Effect.Read,
returnType: {kind: 'Primitive'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
'parseFloat',
addFunction(DEFAULT_SHAPES, [], {
positionalParams: [],
restParam: Effect.Read,
returnType: {kind: 'Primitive'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
'isNaN',
addFunction(DEFAULT_SHAPES, [], {
positionalParams: [],
restParam: Effect.Read,
returnType: {kind: 'Primitive'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
'isFinite',
addFunction(DEFAULT_SHAPES, [], {
positionalParams: [],
restParam: Effect.Read,
returnType: {kind: 'Primitive'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
'encodeURI',
addFunction(DEFAULT_SHAPES, [], {
positionalParams: [],
restParam: Effect.Read,
returnType: {kind: 'Primitive'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
'encodeURIComponent',
addFunction(DEFAULT_SHAPES, [], {
positionalParams: [],
restParam: Effect.Read,
returnType: {kind: 'Primitive'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
'decodeURI',
addFunction(DEFAULT_SHAPES, [], {
positionalParams: [],
restParam: Effect.Read,
returnType: {kind: 'Primitive'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
'decodeURIComponent',
addFunction(DEFAULT_SHAPES, [], {
positionalParams: [],
restParam: Effect.Read,
returnType: {kind: 'Primitive'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
'Map',
addFunction(
DEFAULT_SHAPES,
[],
{
positionalParams: [Effect.ConditionallyMutateIterator],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInMapId},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Mutable,
},
null,
true,
),
],
[
'Set',
addFunction(
DEFAULT_SHAPES,
[],
{
positionalParams: [Effect.ConditionallyMutateIterator],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInSetId},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Mutable,
},
null,
true,
),
],
// TODO: rest of Global objects
];

View File

@@ -10,7 +10,7 @@ import * as t from '@babel/types';
import {CompilerError, CompilerErrorDetailOptions} from '../CompilerError';
import {assertExhaustive} from '../Utils/utils';
import {Environment, ReactFunctionType} from './Environment';
import type {HookKind} from './ObjectShape';
import {HookKind} from './ObjectShape';
import {Type, makeType} from './Types';
import {z} from 'zod';
@@ -746,27 +746,6 @@ export enum InstructionKind {
Function = 'Function',
}
export function convertHoistedLValueKind(
kind: InstructionKind,
): InstructionKind | null {
switch (kind) {
case InstructionKind.HoistedLet:
return InstructionKind.Let;
case InstructionKind.HoistedConst:
return InstructionKind.Const;
case InstructionKind.HoistedFunction:
return InstructionKind.Function;
case InstructionKind.Let:
case InstructionKind.Const:
case InstructionKind.Function:
case InstructionKind.Reassign:
case InstructionKind.Catch:
return null;
default:
assertExhaustive(kind, 'Unexpected lvalue kind');
}
}
function _staticInvariantInstructionValueHasLocation(
value: InstructionValue,
): SourceLocation {
@@ -850,13 +829,6 @@ export type CallExpression = {
typeArguments?: Array<t.FlowType>;
};
export type NewExpression = {
kind: 'NewExpression';
callee: Place;
args: Array<Place | SpreadPattern>;
loc: SourceLocation;
};
export type LoadLocal = {
kind: 'LoadLocal';
place: Place;
@@ -922,7 +894,12 @@ export type InstructionValue =
right: Place;
loc: SourceLocation;
}
| NewExpression
| {
kind: 'NewExpression';
callee: Place;
args: Array<Place | SpreadPattern>;
loc: SourceLocation;
}
| CallExpression
| MethodCall
| {
@@ -1188,21 +1165,18 @@ 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'`
| NonLocalImportSpecifier
// `import {bar as baz} from 'foo'`: name=baz, module=foo, imported=bar
| {
kind: 'ImportSpecifier';
name: string;
module: string;
imported: string;
}
// let, const, function, etc declared in the module but outside the current component/hook
| {kind: 'ModuleLocal'; name: string}
// an unresolved binding
@@ -1420,7 +1394,6 @@ export enum Effect {
Read = 'read',
// This reference reads and stores the value
Capture = 'capture',
ConditionallyMutateIterator = 'mutate-iterator?',
/*
* This reference *may* write to (mutate) the value. This covers two similar cases:
* - The compiler is being conservative and assuming that a value *may* be mutated
@@ -1439,11 +1412,11 @@ export enum Effect {
// This reference may alias to (mutate) the value
Store = 'store',
}
export const EffectSchema = z.enum([
Effect.Read,
Effect.Mutate,
Effect.ConditionallyMutate,
Effect.ConditionallyMutateIterator,
Effect.Capture,
Effect.Store,
Effect.Freeze,
@@ -1457,7 +1430,6 @@ export function isMutableEffect(
case Effect.Capture:
case Effect.Store:
case Effect.ConditionallyMutate:
case Effect.ConditionallyMutateIterator:
case Effect.Mutate: {
return true;
}
@@ -1677,14 +1649,6 @@ export function isArrayType(id: Identifier): boolean {
return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInArray';
}
export function isMapType(id: Identifier): boolean {
return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInMap';
}
export function isSetType(id: Identifier): boolean {
return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInSet';
}
export function isPropsType(id: Identifier): boolean {
return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInProps';
}

View File

@@ -331,7 +331,6 @@ 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

@@ -44,7 +44,6 @@ export function addFunction(
properties: Iterable<[string, BuiltInType | PolyType]>,
fn: Omit<FunctionSignature, 'hookKind'>,
id: string | null = null,
isConstructor: boolean = false,
): FunctionType {
const shapeId = id ?? createAnonId();
addShape(registry, shapeId, properties, {
@@ -55,7 +54,6 @@ export function addFunction(
kind: 'Function',
return: fn.returnType,
shapeId,
isConstructor,
};
}
@@ -75,7 +73,6 @@ export function addHook(
kind: 'Function',
return: fn.returnType,
shapeId,
isConstructor: false,
};
}
@@ -201,8 +198,6 @@ export type ObjectShape = {
export type ShapeRegistry = Map<string, ObjectShape>;
export const BuiltInPropsId = 'BuiltInProps';
export const BuiltInArrayId = 'BuiltInArray';
export const BuiltInSetId = 'BuiltInSet';
export const BuiltInMapId = 'BuiltInMap';
export const BuiltInFunctionId = 'BuiltInFunction';
export const BuiltInJsxId = 'BuiltInJsx';
export const BuiltInObjectId = 'BuiltInObject';
@@ -456,313 +451,6 @@ addObject(BUILTIN_SHAPES, BuiltInObjectId, [
*/
]);
/* Built-in Set shape */
addObject(BUILTIN_SHAPES, BuiltInSetId, [
[
/**
* add(value)
* Parameters
* value: the value of the element to add to the Set object.
* Returns the Set object with added value.
*/
'add',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Capture],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInSetId},
calleeEffect: Effect.Store,
// returnValueKind is technically dependent on the ValueKind of the set itself
returnValueKind: ValueKind.Mutable,
}),
],
[
/**
* clear()
* Parameters none
* Returns undefined
*/
'clear',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: null,
returnType: PRIMITIVE_TYPE,
calleeEffect: Effect.Store,
returnValueKind: ValueKind.Primitive,
}),
],
[
/**
* setInstance.delete(value)
* Returns true if value was already in Set; otherwise false.
*/
'delete',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Read],
restParam: null,
returnType: PRIMITIVE_TYPE,
calleeEffect: Effect.Store,
returnValueKind: ValueKind.Primitive,
}),
],
[
'has',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Read],
restParam: null,
returnType: PRIMITIVE_TYPE,
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
['size', PRIMITIVE_TYPE],
[
/**
* difference(other)
* Parameters
* other: A Set object, or set-like object.
* Returns a new Set object containing elements in this set but not in the other set.
*/
'difference',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Capture],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInSetId},
calleeEffect: Effect.Capture,
returnValueKind: ValueKind.Mutable,
}),
],
[
/**
* union(other)
* Parameters
* other: A Set object, or set-like object.
* Returns a new Set object containing elements in either this set or the other set.
*/
'union',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Capture],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInSetId},
calleeEffect: Effect.Capture,
returnValueKind: ValueKind.Mutable,
}),
],
[
/**
* symmetricalDifference(other)
* Parameters
* other: A Set object, or set-like object.
* A new Set object containing elements which are in either this set or the other set, but not in both.
*/
'symmetricalDifference',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Capture],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInSetId},
calleeEffect: Effect.Capture,
returnValueKind: ValueKind.Mutable,
}),
],
[
/**
* isSubsetOf(other)
* Parameters
* other: A Set object, or set-like object.
* Returns true if all elements in this set are also in the other set, and false otherwise.
*/
'isSubsetOf',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Read],
restParam: null,
returnType: PRIMITIVE_TYPE,
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
/**
* isSupersetOf(other)
* Parameters
* other: A Set object, or set-like object.
* Returns true if all elements in the other set are also in this set, and false otherwise.
*/
'isSupersetOf',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Read],
restParam: null,
returnType: PRIMITIVE_TYPE,
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
/**
* forEach(callbackFn)
* forEach(callbackFn, thisArg)
*/
'forEach',
addFunction(BUILTIN_SHAPES, [], {
/**
* see Array.map explanation for why arguments are marked `ConditionallyMutate`
*/
positionalParams: [],
restParam: Effect.ConditionallyMutate,
returnType: PRIMITIVE_TYPE,
calleeEffect: Effect.ConditionallyMutate,
returnValueKind: ValueKind.Primitive,
noAlias: true,
mutableOnlyIfOperandsAreMutable: true,
}),
],
/**
* Iterators
*/
[
'entries',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: null,
returnType: {kind: 'Poly'},
calleeEffect: Effect.Capture,
returnValueKind: ValueKind.Mutable,
}),
],
[
'keys',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: null,
returnType: {kind: 'Poly'},
calleeEffect: Effect.Capture,
returnValueKind: ValueKind.Mutable,
}),
],
[
'values',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: null,
returnType: {kind: 'Poly'},
calleeEffect: Effect.Capture,
returnValueKind: ValueKind.Mutable,
}),
],
]);
addObject(BUILTIN_SHAPES, BuiltInMapId, [
[
/**
* clear()
* Parameters none
* Returns undefined
*/
'clear',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: null,
returnType: PRIMITIVE_TYPE,
calleeEffect: Effect.Store,
returnValueKind: ValueKind.Primitive,
}),
],
[
'delete',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Read],
restParam: null,
returnType: PRIMITIVE_TYPE,
calleeEffect: Effect.Store,
returnValueKind: ValueKind.Primitive,
}),
],
[
'get',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Read],
restParam: null,
returnType: {kind: 'Poly'},
calleeEffect: Effect.Capture,
returnValueKind: ValueKind.Mutable,
}),
],
[
'has',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Read],
restParam: null,
returnType: PRIMITIVE_TYPE,
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
/**
* Params
* key: the key of the element to add to the Map object. The key may be
* any JavaScript type (any primitive value or any type of JavaScript
* object).
* value: the value of the element to add to the Map object.
* Returns the Map object.
*/
'set',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Capture, Effect.Capture],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInMapId},
calleeEffect: Effect.Store,
returnValueKind: ValueKind.Mutable,
}),
],
['size', PRIMITIVE_TYPE],
[
'forEach',
addFunction(BUILTIN_SHAPES, [], {
/**
* see Array.map explanation for why arguments are marked `ConditionallyMutate`
*/
positionalParams: [],
restParam: Effect.ConditionallyMutate,
returnType: PRIMITIVE_TYPE,
calleeEffect: Effect.ConditionallyMutate,
returnValueKind: ValueKind.Primitive,
noAlias: true,
mutableOnlyIfOperandsAreMutable: true,
}),
],
/**
* Iterators
*/
[
'entries',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: null,
returnType: {kind: 'Poly'},
calleeEffect: Effect.Capture,
returnValueKind: ValueKind.Mutable,
}),
],
[
'keys',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: null,
returnType: {kind: 'Poly'},
calleeEffect: Effect.Capture,
returnValueKind: ValueKind.Mutable,
}),
],
[
'values',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: null,
returnType: {kind: 'Poly'},
calleeEffect: Effect.Capture,
returnValueKind: ValueKind.Mutable,
}),
],
]);
addObject(BUILTIN_SHAPES, BuiltInUseStateId, [
['0', {kind: 'Poly'}],
[

View File

@@ -23,9 +23,7 @@ import {
FunctionExpression,
ObjectMethod,
PropertyLiteral,
convertHoistedLValueKind,
} from './HIR';
import {
collectHoistablePropertyLoads,
keyByScopeId,
@@ -466,9 +464,6 @@ class Context {
}
this.#reassignments.set(identifier, decl);
}
hasDeclared(identifier: Identifier): boolean {
return this.#declarations.has(identifier.declarationId);
}
// Checks if identifier is a valid dependency in the current scope
#checkValidDependency(maybeDependency: ReactiveScopeDependency): boolean {
@@ -667,21 +662,21 @@ function handleInstruction(instr: Instruction, context: Context): void {
});
} else if (value.kind === 'DeclareLocal' || value.kind === 'DeclareContext') {
/*
* Some variables may be declared and never initialized. We need to retain
* (and hoist) these declarations if they are included in a reactive scope.
* One approach is to simply add all `DeclareLocal`s as scope declarations.
*
* Context variables with hoisted declarations only become live after their
* first assignment. We only declare real DeclareLocal / DeclareContext
* instructions (not hoisted ones) to avoid generating dependencies on
* hoisted declarations.
* Some variables may be declared and never initialized. We need
* to retain (and hoist) these declarations if they are included
* in a reactive scope. One approach is to simply add all `DeclareLocal`s
* as scope declarations.
*/
if (convertHoistedLValueKind(value.lvalue.kind) === null) {
context.declare(value.lvalue.place.identifier, {
id,
scope: context.currentScope,
});
}
/*
* We add context variable declarations here, not at `StoreContext`, since
* context Store / Loads are modeled as reads and mutates to the underlying
* variable reference (instead of through intermediate / inlined temporaries)
*/
context.declare(value.lvalue.place.identifier, {
id,
scope: context.currentScope,
});
} else if (value.kind === 'Destructure') {
context.visitOperand(value.value);
for (const place of eachPatternOperand(value.lvalue.pattern)) {
@@ -693,23 +688,6 @@ function handleInstruction(instr: Instruction, context: Context): void {
scope: context.currentScope,
});
}
} else if (value.kind === 'StoreContext') {
/**
* Some StoreContext variables have hoisted declarations. If we're storing
* to a context variable that hasn't yet been declared, the StoreContext is
* the declaration.
* (see corresponding logic in PruneHoistedContext)
*/
if (!context.hasDeclared(value.lvalue.place.identifier)) {
context.declare(value.lvalue.place.identifier, {
id,
scope: context.currentScope,
});
}
for (const operand of eachInstructionValueOperand(value)) {
context.visitOperand(operand);
}
} else {
for (const operand of eachInstructionValueOperand(value)) {
context.visitOperand(operand);

View File

@@ -38,7 +38,6 @@ export type FunctionType = {
kind: 'Function';
shapeId: string | null;
return: Type;
isConstructor: boolean;
};
export type ObjectType = {
@@ -112,7 +111,6 @@ export function duplicateType(type: Type): Type {
kind: 'Function',
return: duplicateType(type.return),
shapeId: type.shapeId,
isConstructor: type.isConstructor,
};
}
case 'Object': {

View File

@@ -249,7 +249,6 @@ export function inferEffectDependencies(fn: HIRFunction): void {
// Renumber instructions and fix scope ranges
markInstructionIds(fn.body);
fixScopeAndIdentifierRanges(fn.body);
fn.env.hasInferredEffect = true;
}
}

View File

@@ -11,10 +11,7 @@ import {
Identifier,
InstructionId,
InstructionKind,
isArrayType,
isMapType,
isRefOrRefValue,
isSetType,
makeInstructionId,
Place,
} from '../HIR/HIR';
@@ -93,17 +90,6 @@ function inferPlace(
infer(place, instrId);
}
return;
case Effect.ConditionallyMutateIterator: {
const identifier = place.identifier;
if (
!isArrayType(identifier) &&
!isSetType(identifier) &&
!isMapType(identifier)
) {
infer(place, instrId);
}
return;
}
case Effect.ConditionallyMutate:
case Effect.Mutate: {
infer(place, instrId);

View File

@@ -230,7 +230,6 @@ export function inferReactivePlaces(fn: HIRFunction): void {
case Effect.Capture:
case Effect.Store:
case Effect.ConditionallyMutate:
case Effect.ConditionallyMutateIterator:
case Effect.Mutate: {
if (isMutable(instruction, operand)) {
reactiveIdentifiers.markReactive(operand);

View File

@@ -12,7 +12,6 @@ import {
BasicBlock,
BlockId,
CallExpression,
NewExpression,
Effect,
FunctionEffect,
GeneratedSource,
@@ -24,15 +23,12 @@ import {
Phi,
Place,
SpreadPattern,
TInstruction,
Type,
ValueKind,
ValueReason,
isArrayType,
isMapType,
isMutableEffect,
isObjectType,
isSetType,
} from '../HIR/HIR';
import {FunctionSignature} from '../HIR/ObjectShape';
import {
@@ -42,6 +38,7 @@ import {
printSourceLocation,
} from '../HIR/PrintHIR';
import {
eachCallArgument,
eachInstructionOperand,
eachInstructionValueOperand,
eachPatternOperand,
@@ -254,7 +251,7 @@ type FreezeAction = {values: Set<InstructionValue>; reason: Set<ValueReason>};
// Maintains a mapping of top-level variables to the kind of value they hold
class InferenceState {
env: Environment;
#env: Environment;
// The kind of each value, based on its allocation site
#values: Map<InstructionValue, AbstractValue>;
@@ -270,7 +267,7 @@ class InferenceState {
values: Map<InstructionValue, AbstractValue>,
variables: Map<IdentifierId, Set<InstructionValue>>,
) {
this.env = env;
this.#env = env;
this.#values = values;
this.#variables = variables;
}
@@ -412,8 +409,8 @@ class InferenceState {
});
if (
value.kind === 'FunctionExpression' &&
(this.env.config.enablePreserveExistingMemoizationGuarantees ||
this.env.config.enableTransitivelyFreezeFunctionExpressions)
(this.#env.config.enablePreserveExistingMemoizationGuarantees ||
this.#env.config.enableTransitivelyFreezeFunctionExpressions)
) {
for (const operand of value.loweredFunc.func.context) {
const operandValues = this.#variables.get(operand.identifier.id);
@@ -471,25 +468,6 @@ class InferenceState {
}
break;
}
case Effect.ConditionallyMutateIterator: {
if (
valueKind.kind === ValueKind.Mutable ||
valueKind.kind === ValueKind.Context
) {
if (
isArrayType(place.identifier) ||
isSetType(place.identifier) ||
isMapType(place.identifier)
) {
effect = Effect.Capture;
} else {
effect = Effect.ConditionallyMutate;
}
} else {
effect = Effect.Read;
}
break;
}
case Effect.Mutate: {
effect = Effect.Mutate;
break;
@@ -612,7 +590,7 @@ class InferenceState {
return null;
} else {
return new InferenceState(
this.env,
this.#env,
nextValues ?? new Map(this.#values),
nextVariables ?? new Map(this.#variables),
);
@@ -626,7 +604,7 @@ class InferenceState {
*/
clone(): InferenceState {
return new InferenceState(
this.env,
this.#env,
new Map(this.#values),
new Map(this.#variables),
);
@@ -901,7 +879,9 @@ function inferBlock(
state.referenceAndRecordEffects(
freezeActions,
element.place,
Effect.ConditionallyMutateIterator,
isArrayType(element.place.identifier)
? Effect.Capture
: Effect.ConditionallyMutate,
ValueReason.Other,
);
} else if (element.kind === 'Identifier') {
@@ -924,12 +904,43 @@ function inferBlock(
break;
}
case 'NewExpression': {
inferCallEffects(
state,
instr as TInstruction<NewExpression>,
/**
* For new expressions, we infer a `read` effect on the Class / Function type
* to avoid extending mutable ranges of locally created classes, e.g.
* ```js
* const MyClass = getClass();
* const value = new MyClass(val1, val2)
* ^ (read) ^ (conditionally mutate)
* ```
*
* Risks:
* Classes / functions created during render could technically capture and
* mutate their enclosing scope, which we currently do not detect.
*/
const valueKind: AbstractValue = {
kind: ValueKind.Mutable,
reason: new Set([ValueReason.Other]),
context: new Set(),
};
state.referenceAndRecordEffects(
freezeActions,
getFunctionCallSignature(env, instrValue.callee.identifier.type),
instrValue.callee,
Effect.Read,
ValueReason.Other,
);
for (const operand of eachCallArgument(instrValue.args)) {
state.referenceAndRecordEffects(
freezeActions,
operand,
Effect.ConditionallyMutate,
ValueReason.Other,
);
}
state.initialize(instrValue, valueKind);
state.define(instr.lvalue, instrValue);
instr.lvalue.effect = Effect.ConditionallyMutate;
continuation = {kind: 'funeffects'};
break;
}
@@ -1227,12 +1238,62 @@ function inferBlock(
break;
}
case 'CallExpression': {
inferCallEffects(
state,
instr as TInstruction<CallExpression>,
freezeActions,
getFunctionCallSignature(env, instrValue.callee.identifier.type),
const signature = getFunctionCallSignature(
env,
instrValue.callee.identifier.type,
);
const effects =
signature !== null ? getFunctionEffects(instrValue, signature) : null;
const returnValueKind: AbstractValue =
signature !== null
? {
kind: signature.returnValueKind,
reason: new Set([
signature.returnValueReason ??
ValueReason.KnownReturnSignature,
]),
context: new Set(),
}
: {
kind: ValueKind.Mutable,
reason: new Set([ValueReason.Other]),
context: new Set(),
};
let hasCaptureArgument = false;
for (let i = 0; i < instrValue.args.length; i++) {
const arg = instrValue.args[i];
const place = arg.kind === 'Identifier' ? arg : arg.place;
state.referenceAndRecordEffects(
freezeActions,
place,
getArgumentEffect(effects != null ? effects[i] : null, arg),
ValueReason.Other,
);
hasCaptureArgument ||= place.effect === Effect.Capture;
}
if (signature !== null) {
state.referenceAndRecordEffects(
freezeActions,
instrValue.callee,
signature.calleeEffect,
ValueReason.Other,
);
} else {
state.referenceAndRecordEffects(
freezeActions,
instrValue.callee,
Effect.ConditionallyMutate,
ValueReason.Other,
);
}
hasCaptureArgument ||= instrValue.callee.effect === Effect.Capture;
state.initialize(instrValue, returnValueKind);
state.define(instr.lvalue, instrValue);
instr.lvalue.effect = hasCaptureArgument
? Effect.Store
: Effect.ConditionallyMutate;
continuation = {kind: 'funeffects'};
break;
}
@@ -1250,12 +1311,102 @@ function inferBlock(
Effect.Read,
ValueReason.Other,
);
inferCallEffects(
state,
instr as TInstruction<MethodCall>,
freezeActions,
getFunctionCallSignature(env, instrValue.property.identifier.type),
const signature = getFunctionCallSignature(
env,
instrValue.property.identifier.type,
);
const returnValueKind: AbstractValue =
signature !== null
? {
kind: signature.returnValueKind,
reason: new Set([
signature.returnValueReason ??
ValueReason.KnownReturnSignature,
]),
context: new Set(),
}
: {
kind: ValueKind.Mutable,
reason: new Set([ValueReason.Other]),
context: new Set(),
};
if (
signature !== null &&
signature.mutableOnlyIfOperandsAreMutable &&
areArgumentsImmutableAndNonMutating(state, instrValue.args)
) {
/*
* None of the args are mutable or mutate their params, we can downgrade to
* treating as all reads (except that the receiver may be captured)
*/
for (const arg of instrValue.args) {
const place = arg.kind === 'Identifier' ? arg : arg.place;
state.referenceAndRecordEffects(
freezeActions,
place,
Effect.Read,
ValueReason.Other,
);
}
state.referenceAndRecordEffects(
freezeActions,
instrValue.receiver,
Effect.Capture,
ValueReason.Other,
);
state.initialize(instrValue, returnValueKind);
state.define(instr.lvalue, instrValue);
instr.lvalue.effect =
instrValue.receiver.effect === Effect.Capture
? Effect.Store
: Effect.ConditionallyMutate;
continuation = {kind: 'funeffects'};
break;
}
const effects =
signature !== null ? getFunctionEffects(instrValue, signature) : null;
let hasCaptureArgument = false;
for (let i = 0; i < instrValue.args.length; i++) {
const arg = instrValue.args[i];
const place = arg.kind === 'Identifier' ? arg : arg.place;
/*
* If effects are inferred for an argument, we should fail invalid
* mutating effects
*/
state.referenceAndRecordEffects(
freezeActions,
place,
getArgumentEffect(effects != null ? effects[i] : null, arg),
ValueReason.Other,
);
hasCaptureArgument ||= place.effect === Effect.Capture;
}
if (signature !== null) {
state.referenceAndRecordEffects(
freezeActions,
instrValue.receiver,
signature.calleeEffect,
ValueReason.Other,
);
} else {
state.referenceAndRecordEffects(
freezeActions,
instrValue.receiver,
Effect.ConditionallyMutate,
ValueReason.Other,
);
}
hasCaptureArgument ||= instrValue.receiver.effect === Effect.Capture;
state.initialize(instrValue, returnValueKind);
state.define(instr.lvalue, instrValue);
instr.lvalue.effect = hasCaptureArgument
? Effect.Store
: Effect.ConditionallyMutate;
continuation = {kind: 'funeffects'};
break;
}
@@ -1662,13 +1813,7 @@ function inferBlock(
kind === ValueKind.Mutable || kind === ValueKind.Context;
let effect;
let valueKind: AbstractValue;
const iterator = instrValue.collection.identifier;
if (
!isMutable ||
isArrayType(iterator) ||
isMapType(iterator) ||
isSetType(iterator)
) {
if (!isMutable || isArrayType(instrValue.collection.identifier)) {
// Case 1, assume iterator is a separate mutable object
effect = {
kind: Effect.Read,
@@ -1709,7 +1854,7 @@ function inferBlock(
state.referenceAndRecordEffects(
freezeActions,
instrValue.iterator,
Effect.ConditionallyMutateIterator,
Effect.ConditionallyMutate,
ValueReason.Other,
);
/**
@@ -1838,7 +1983,7 @@ export function getFunctionCallSignature(
* @returns Inferred effects of function arguments, or null if inference fails.
*/
export function getFunctionEffects(
fn: MethodCall | CallExpression | NewExpression,
fn: MethodCall | CallExpression,
sig: FunctionSignature,
): Array<Effect> | null {
const results = [];
@@ -1867,33 +2012,6 @@ export function getFunctionEffects(
return results;
}
export function isKnownMutableEffect(effect: Effect): boolean {
switch (effect) {
case Effect.Store:
case Effect.ConditionallyMutate:
case Effect.ConditionallyMutateIterator:
case Effect.Mutate: {
return true;
}
case Effect.Unknown: {
CompilerError.invariant(false, {
reason: 'Unexpected unknown effect',
description: null,
loc: GeneratedSource,
suggestions: null,
});
}
case Effect.Read:
case Effect.Capture:
case Effect.Freeze: {
return false;
}
default: {
assertExhaustive(effect, `Unexpected effect \`${effect}\``);
}
}
}
/**
* Returns true if all of the arguments are both non-mutable (immutable or frozen)
* _and_ are not functions which might mutate their arguments. Note that function
@@ -1905,20 +2023,10 @@ function areArgumentsImmutableAndNonMutating(
args: MethodCall['args'],
): boolean {
for (const arg of args) {
if (arg.kind === 'Identifier' && arg.identifier.type.kind === 'Function') {
const fnShape = state.env.getFunctionSignature(arg.identifier.type);
if (fnShape != null) {
return (
!fnShape.positionalParams.some(isKnownMutableEffect) &&
(fnShape.restParam == null ||
!isKnownMutableEffect(fnShape.restParam))
);
}
}
const place = arg.kind === 'Identifier' ? arg : arg.place;
const kind = state.kind(place).kind;
switch (kind) {
case ValueKind.Global:
case ValueKind.Primitive:
case ValueKind.Frozen: {
/*
@@ -1929,10 +2037,6 @@ function areArgumentsImmutableAndNonMutating(
break;
}
default: {
/**
* Globals, module locals, and other locally defined functions may
* mutate their arguments.
*/
return false;
}
}
@@ -1975,128 +2079,9 @@ function getArgumentEffect(
});
}
// effects[i] is Effect.Capture | Effect.Read | Effect.Store
return Effect.ConditionallyMutateIterator;
return Effect.ConditionallyMutate;
}
} else {
return Effect.ConditionallyMutate;
}
}
function inferCallEffects(
state: InferenceState,
instr:
| TInstruction<CallExpression>
| TInstruction<MethodCall>
| TInstruction<NewExpression>,
freezeActions: Array<FreezeAction>,
signature: FunctionSignature | null,
): void {
const instrValue = instr.value;
const returnValueKind: AbstractValue =
signature !== null
? {
kind: signature.returnValueKind,
reason: new Set([
signature.returnValueReason ?? ValueReason.KnownReturnSignature,
]),
context: new Set(),
}
: {
kind: ValueKind.Mutable,
reason: new Set([ValueReason.Other]),
context: new Set(),
};
if (
instrValue.kind === 'MethodCall' &&
signature !== null &&
signature.mutableOnlyIfOperandsAreMutable &&
areArgumentsImmutableAndNonMutating(state, instrValue.args)
) {
/*
* None of the args are mutable or mutate their params, we can downgrade to
* treating as all reads (except that the receiver may be captured)
*/
for (const arg of instrValue.args) {
const place = arg.kind === 'Identifier' ? arg : arg.place;
state.referenceAndRecordEffects(
freezeActions,
place,
Effect.Read,
ValueReason.Other,
);
}
state.referenceAndRecordEffects(
freezeActions,
instrValue.receiver,
Effect.Capture,
ValueReason.Other,
);
state.initialize(instrValue, returnValueKind);
state.define(instr.lvalue, instrValue);
instr.lvalue.effect =
instrValue.receiver.effect === Effect.Capture
? Effect.Store
: Effect.ConditionallyMutate;
return;
}
const effects =
signature !== null ? getFunctionEffects(instrValue, signature) : null;
let hasCaptureArgument = false;
for (let i = 0; i < instrValue.args.length; i++) {
const arg = instrValue.args[i];
const place = arg.kind === 'Identifier' ? arg : arg.place;
/*
* If effects are inferred for an argument, we should fail invalid
* mutating effects
*/
state.referenceAndRecordEffects(
freezeActions,
place,
getArgumentEffect(effects != null ? effects[i] : null, arg),
ValueReason.Other,
);
hasCaptureArgument ||= place.effect === Effect.Capture;
}
const callee =
instrValue.kind === 'MethodCall' ? instrValue.receiver : instrValue.callee;
if (signature !== null) {
state.referenceAndRecordEffects(
freezeActions,
callee,
signature.calleeEffect,
ValueReason.Other,
);
} else {
/**
* For new expressions, we infer a `read` effect on the Class / Function type
* to avoid extending mutable ranges of locally created classes, e.g.
* ```js
* const MyClass = getClass();
* const value = new MyClass(val1, val2)
* ^ (read) ^ (conditionally mutate)
* ```
*
* Risks:
* Classes / functions created during render could technically capture and
* mutate their enclosing scope, which we currently do not detect.
*/
state.referenceAndRecordEffects(
freezeActions,
callee,
instrValue.kind === 'NewExpression'
? Effect.Read
: Effect.ConditionallyMutate,
ValueReason.Other,
);
}
hasCaptureArgument ||= callee.effect === Effect.Capture;
state.initialize(instrValue, returnValueKind);
state.define(instr.lvalue, instrValue);
instr.lvalue.effect = hasCaptureArgument
? Effect.Store
: Effect.ConditionallyMutate;
}

View File

@@ -18,7 +18,6 @@ import {
Instruction,
LoadGlobal,
LoadLocal,
NonLocalImportSpecifier,
Place,
PropertyLoad,
isUseContextHookType,
@@ -36,7 +35,7 @@ import {inferTypes} from '../TypeInference';
export function lowerContextAccess(
fn: HIRFunction,
loweredContextCalleeConfig: ExternalFunction,
loweredContextCallee: ExternalFunction,
): void {
const contextAccess: Map<IdentifierId, CallExpression> = new Map();
const contextKeys: Map<IdentifierId, Array<string>> = new Map();
@@ -80,8 +79,6 @@ 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;
@@ -94,13 +91,9 @@ export function lowerContextAccess(
isUseContextHookType(value.callee.identifier) &&
contextKeys.has(lvalue.identifier.id)
) {
importLoweredContextCallee ??=
fn.env.programContext.addImportSpecifier(
loweredContextCalleeConfig,
);
const loweredContextCalleeInstr = emitLoadLoweredContextCallee(
fn.env,
importLoweredContextCallee,
loweredContextCallee,
);
if (nextInstructions === null) {
@@ -129,16 +122,21 @@ export function lowerContextAccess(
}
markInstructionIds(fn.body);
inferTypes(fn);
fn.env.hasLoweredContextAccess = true;
}
}
function emitLoadLoweredContextCallee(
env: Environment,
importedLowerContextCallee: NonLocalImportSpecifier,
loweredContextCallee: ExternalFunction,
): Instruction {
const loadGlobal: LoadGlobal = {
kind: 'LoadGlobal',
binding: {...importedLowerContextCallee},
binding: {
kind: 'ImportNamespace',
module: loweredContextCallee.source,
name: loweredContextCallee.importSpecifierName,
},
loc: GeneratedSource,
};

View File

@@ -196,7 +196,7 @@ function process(
return null;
}
const props = collectProps(fn.env, jsx);
const props = collectProps(jsx);
if (!props) return null;
const outlinedTag = fn.env.generateGloballyUniqueIdentifierName(null).value;
@@ -217,7 +217,6 @@ type OutlinedJsxAttribute = {
};
function collectProps(
env: Environment,
instructions: Array<JsxInstruction>,
): Array<OutlinedJsxAttribute> | null {
let id = 1;
@@ -228,7 +227,6 @@ function collectProps(
newName = `${oldName}${id++}`;
}
seen.add(newName);
env.programContext.addNewReference(newName);
return newName;
}

View File

@@ -52,8 +52,7 @@ import {assertExhaustive} from '../Utils/utils';
import {buildReactiveFunction} from './BuildReactiveFunction';
import {SINGLE_CHILD_FBT_TAGS} from './MemoizeFbtAndMacroOperandsInSameScope';
import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors';
import {EMIT_FREEZE_GLOBAL_GATING, ReactFunctionType} from '../HIR/Environment';
import {ProgramContext} from '../Entrypoint';
import {ReactFunctionType} from '../HIR/Environment';
export const MEMO_CACHE_SENTINEL = 'react.memo_cache_sentinel';
export const EARLY_RETURN_SENTINEL = 'react.early_return_sentinel';
@@ -101,9 +100,9 @@ export type CodegenFunction = {
}>;
/**
* This is true if the compiler has compiled inferred effect dependencies
* This is true if the compiler has the lowered useContext calls.
*/
hasInferredEffect: boolean;
hasLoweredContextAccess: boolean;
/**
* This is true if the compiler has compiled a fire to a useFire call
@@ -161,7 +160,6 @@ export function codegenFunction(
compiled.body = t.blockStatement([
createHookGuard(
hookGuard,
fn.env.programContext,
compiled.body.body,
GuardKind.PushHookGuard,
GuardKind.PopHookGuard,
@@ -172,15 +170,13 @@ 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(useMemoCacheIdentifier), [
t.callExpression(t.identifier(fn.env.useMemoCacheIdentifier), [
t.numericLiteral(cacheCount),
]),
),
@@ -263,54 +259,34 @@ export function codegenFunction(
* Technically, this is a conditional hook call. However, we expect
* __DEV__ and gating identifier to be runtime constants
*/
const gating =
emitInstrumentForget.gating != null
? t.identifier(
fn.env.programContext.addImportSpecifier(
emitInstrumentForget.gating,
).name,
)
: null;
const globalGating =
let gating: t.Expression;
if (
emitInstrumentForget.gating != null &&
emitInstrumentForget.globalGating != null
? t.identifier(emitInstrumentForget.globalGating)
: null;
if (emitInstrumentForget.globalGating != null) {
const assertResult = fn.env.programContext.assertGlobalBinding(
emitInstrumentForget.globalGating,
) {
gating = t.logicalExpression(
'&&',
t.identifier(emitInstrumentForget.globalGating),
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 if (emitInstrumentForget.gating != null) {
gating = t.identifier(emitInstrumentForget.gating.importSpecifierName);
} else {
CompilerError.invariant(globalGating != null, {
CompilerError.invariant(emitInstrumentForget.globalGating != null, {
reason:
'Bad config not caught! Expected at least one of gating or globalGating',
loc: null,
suggestions: null,
});
ifTest = globalGating;
gating = t.identifier(emitInstrumentForget.globalGating);
}
const instrumentFnIdentifier = fn.env.programContext.addImportSpecifier(
emitInstrumentForget.fn,
).name;
const test: t.IfStatement = t.ifStatement(
ifTest,
gating,
t.expressionStatement(
t.callExpression(t.identifier(instrumentFnIdentifier), [
t.stringLiteral(fn.id),
t.stringLiteral(fn.env.filename ?? ''),
]),
t.callExpression(
t.identifier(emitInstrumentForget.fn.importSpecifierName),
[t.stringLiteral(fn.id), t.stringLiteral(fn.env.filename ?? '')],
),
),
);
compiled.body.body.unshift(test);
@@ -387,8 +363,8 @@ function codegenReactiveFunction(
prunedMemoBlocks: countMemoBlockVisitor.prunedMemoBlocks,
prunedMemoValues: countMemoBlockVisitor.prunedMemoValues,
outlined: [],
hasLoweredContextAccess: fn.env.hasLoweredContextAccess,
hasFireRewrite: fn.env.hasFireRewrite,
hasInferredEffect: fn.env.hasInferredEffect,
});
}
@@ -577,18 +553,13 @@ function codegenBlockNoReset(
function wrapCacheDep(cx: Context, value: t.Expression): t.Expression {
if (cx.env.config.enableEmitFreeze != null && cx.env.isInferredMemoEnabled) {
const emitFreezeIdentifier = cx.env.programContext.addImportSpecifier(
cx.env.config.enableEmitFreeze,
).name;
cx.env.programContext
.assertGlobalBinding(EMIT_FREEZE_GLOBAL_GATING, cx.env.scope)
.unwrap();
// The import declaration for emitFreeze is inserted in the Babel plugin
return t.conditionalExpression(
t.identifier(EMIT_FREEZE_GLOBAL_GATING),
t.callExpression(t.identifier(emitFreezeIdentifier), [
value,
t.stringLiteral(cx.fnName),
]),
t.identifier('__DEV__'),
t.callExpression(
t.identifier(cx.env.config.enableEmitFreeze.importSpecifierName),
[value, t.stringLiteral(cx.fnName)],
),
value,
);
} else {
@@ -742,14 +713,16 @@ function codegenReactiveScope(
let computationBlock = codegenBlock(cx, block);
let memoStatement;
const detectionFunction = cx.env.config.enableChangeDetectionForDebugging;
if (detectionFunction != null && changeExpressions.length > 0) {
if (
cx.env.config.enableChangeDetectionForDebugging != null &&
changeExpressions.length > 0
) {
const loc =
typeof scope.loc === 'symbol'
? 'unknown location'
: `(${scope.loc.start.line}:${scope.loc.end.line})`;
const importedDetectionFunctionIdentifier =
cx.env.programContext.addImportSpecifier(detectionFunction).name;
const detectionFunction =
cx.env.config.enableChangeDetectionForDebugging.importSpecifierName;
const cacheLoadOldValueStatements: Array<t.Statement> = [];
const changeDetectionStatements: Array<t.Statement> = [];
const idempotenceDetectionStatements: Array<t.Statement> = [];
@@ -771,7 +744,7 @@ function codegenReactiveScope(
);
changeDetectionStatements.push(
t.expressionStatement(
t.callExpression(t.identifier(importedDetectionFunctionIdentifier), [
t.callExpression(t.identifier(detectionFunction), [
t.identifier(loadName),
t.cloneNode(name, true),
t.stringLiteral(name.name),
@@ -783,7 +756,7 @@ function codegenReactiveScope(
);
idempotenceDetectionStatements.push(
t.expressionStatement(
t.callExpression(t.identifier(importedDetectionFunctionIdentifier), [
t.callExpression(t.identifier(detectionFunction), [
t.cloneNode(slot, true),
t.cloneNode(name, true),
t.stringLiteral(name.name),
@@ -1545,15 +1518,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(guardFnName), [t.numericLiteral(kind)]),
t.callExpression(t.identifier(guard.importSpecifierName), [
t.numericLiteral(kind),
]),
);
}
@@ -1603,7 +1576,6 @@ function createCallExpression(
t.blockStatement([
createHookGuard(
hookGuard,
env.programContext,
[t.returnStatement(callExpr)],
GuardKind.AllowHook,
GuardKind.DisallowHook,

View File

@@ -5,9 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import {CompilerError} from '..';
import {
convertHoistedLValueKind,
DeclarationId,
InstructionKind,
ReactiveFunction,
@@ -29,17 +27,7 @@ export function pruneHoistedContexts(fn: ReactiveFunction): void {
visitReactiveFunction(fn, new Visitor(), hoistedIdentifiers);
}
const REWRITTEN_HOISTED_CONST: unique symbol = Symbol(
'REWRITTEN_HOISTED_CONST',
);
const REWRITTEN_HOISTED_LET: unique symbol = Symbol('REWRITTEN_HOISTED_LET');
type HoistedIdentifiers = Map<
DeclarationId,
| InstructionKind
| typeof REWRITTEN_HOISTED_CONST
| typeof REWRITTEN_HOISTED_LET
>;
type HoistedIdentifiers = Map<DeclarationId, InstructionKind>;
class Visitor extends ReactiveFunctionTransform<HoistedIdentifiers> {
override transformInstruction(
@@ -47,101 +35,64 @@ class Visitor extends ReactiveFunctionTransform<HoistedIdentifiers> {
state: HoistedIdentifiers,
): Transformed<ReactiveStatement> {
this.visitInstruction(instruction, state);
/**
* Remove hoisted declarations to preserve TDZ
*/
if (instruction.value.kind === 'DeclareContext') {
const maybeNonHoisted = convertHoistedLValueKind(
instruction.value.lvalue.kind,
if (
instruction.value.kind === 'DeclareContext' &&
instruction.value.lvalue.kind === 'HoistedConst'
) {
state.set(
instruction.value.lvalue.place.identifier.declarationId,
InstructionKind.Const,
);
if (maybeNonHoisted != null) {
state.set(
instruction.value.lvalue.place.identifier.declarationId,
maybeNonHoisted,
);
return {kind: 'remove'};
}
if (instruction.value.lvalue.kind === 'Let') {
/**
* We don't expect const context variables to be hoisted
*/
state.set(
instruction.value.lvalue.place.identifier.declarationId,
REWRITTEN_HOISTED_LET,
);
}
return {kind: 'remove'};
}
if (instruction.value.kind === 'StoreContext') {
if (
instruction.value.kind === 'DeclareContext' &&
instruction.value.lvalue.kind === 'HoistedLet'
) {
state.set(
instruction.value.lvalue.place.identifier.declarationId,
InstructionKind.Let,
);
return {kind: 'remove'};
}
if (
instruction.value.kind === 'DeclareContext' &&
instruction.value.lvalue.kind === 'HoistedFunction'
) {
state.set(
instruction.value.lvalue.place.identifier.declarationId,
InstructionKind.Function,
);
return {kind: 'remove'};
}
if (
instruction.value.kind === 'StoreContext' &&
state.has(instruction.value.lvalue.place.identifier.declarationId)
) {
const kind = state.get(
instruction.value.lvalue.place.identifier.declarationId,
);
if (kind != null) {
CompilerError.invariant(kind !== REWRITTEN_HOISTED_CONST, {
reason: 'Expected exactly one store to a hoisted const variable',
loc: instruction.loc,
});
if (
kind === InstructionKind.Const ||
kind === InstructionKind.Function
) {
state.set(
instruction.value.lvalue.place.identifier.declarationId,
REWRITTEN_HOISTED_CONST,
);
return {
kind: 'replace',
)!;
return {
kind: 'replace',
value: {
kind: 'instruction',
instruction: {
...instruction,
value: {
kind: 'instruction',
instruction: {
...instruction,
value: {
...instruction.value,
lvalue: {
...instruction.value.lvalue,
kind,
},
type: null,
kind: 'StoreLocal',
},
...instruction.value,
lvalue: {
...instruction.value.lvalue,
kind,
},
type: null,
kind: 'StoreLocal',
},
};
} else if (kind !== REWRITTEN_HOISTED_LET) {
/**
* Context variables declared with let may have reassignments. Only
* insert a `DeclareContext` for the first encountered `StoreContext`
* instruction.
*/
state.set(
instruction.value.lvalue.place.identifier.declarationId,
REWRITTEN_HOISTED_LET,
);
return {
kind: 'replace-many',
value: [
{
kind: 'instruction',
instruction: {
id: instruction.id,
lvalue: null,
value: {
kind: 'DeclareContext',
lvalue: {
kind: InstructionKind.Let,
place: {...instruction.value.lvalue.place},
},
loc: instruction.value.loc,
},
loc: instruction.loc,
},
},
{kind: 'instruction', instruction},
],
};
}
}
},
},
};
}
return {kind: 'keep'};

View File

@@ -5,7 +5,6 @@
* LICENSE file in the root directory of this source tree.
*/
import {ProgramContext} from '..';
import {CompilerError} from '../CompilerError';
import {
DeclarationId,
@@ -48,7 +47,7 @@ import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors';
*/
export function renameVariables(fn: ReactiveFunction): Set<string> {
const globals = collectReferencedGlobals(fn);
const scopes = new Scopes(globals, fn.env.programContext);
const scopes = new Scopes(globals);
renameVariablesImpl(fn, new Visitor(), scopes);
return new Set([...scopes.names, ...globals]);
}
@@ -125,12 +124,10 @@ 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>, programContext: ProgramContext) {
constructor(globals: Set<string>) {
this.#globals = globals;
this.#programContext = programContext;
}
visit(identifier: Identifier): void {
@@ -159,7 +156,6 @@ 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,7 +28,6 @@ import {
isUseEffectHookType,
LoadLocal,
makeInstructionId,
NonLocalImportSpecifier,
Place,
promoteTemporary,
} from '../HIR';
@@ -37,7 +36,6 @@ 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):
@@ -58,7 +56,6 @@ 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>>();
@@ -90,15 +87,7 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void {
] of capturedCallees.entries()) {
if (!context.hasCalleeWithInsertedFire(fireCalleePlace)) {
context.addCalleeWithInsertedFire(fireCalleePlace);
importedUseFire ??= fn.env.programContext.addImportSpecifier({
source: fn.env.programContext.reactRuntimeModule,
importSpecifierName: USE_FIRE_FUNCTION_NAME,
});
const loadUseFireInstr = makeLoadUseFireInstruction(
fn.env,
importedUseFire,
);
const loadUseFireInstr = makeLoadUseFireInstruction(fn.env);
const loadFireCalleeInstr = makeLoadFireCalleeInstruction(
fn.env,
fireCalleeInfo.capturedCalleeIdentifier,
@@ -415,16 +404,18 @@ function ensureNoMoreFireUses(fn: HIRFunction, context: Context): void {
}
}
function makeLoadUseFireInstruction(
env: Environment,
importedLoadUseFire: NonLocalImportSpecifier,
): Instruction {
function makeLoadUseFireInstruction(env: Environment): Instruction {
const useFirePlace = createTemporaryPlace(env, GeneratedSource);
useFirePlace.effect = Effect.Read;
useFirePlace.identifier.type = DefaultNonmutatingHook;
const instrValue: InstructionValue = {
kind: 'LoadGlobal',
binding: {...importedLoadUseFire},
binding: {
kind: 'ImportSpecifier',
name: 'useFire',
module: 'react',
imported: 'useFire',
},
loc: GeneratedSource,
};
return {

View File

@@ -261,7 +261,6 @@ function* generateInstructionTypes(
kind: 'Function',
shapeId: null,
return: returnType,
isConstructor: false,
});
yield equation(left, returnType);
break;
@@ -278,7 +277,6 @@ function* generateInstructionTypes(
kind: 'Function',
shapeId: null,
return: returnType,
isConstructor: false,
});
yield equation(left, returnType);
break;
@@ -335,7 +333,6 @@ function* generateInstructionTypes(
kind: 'Function',
return: returnType,
shapeId: null,
isConstructor: false,
});
yield equation(left, returnType);
@@ -408,7 +405,6 @@ function* generateInstructionTypes(
kind: 'Function',
shapeId: BuiltInFunctionId,
return: value.loweredFunc.func.returnType,
isConstructor: false,
});
break;
}
@@ -429,20 +425,9 @@ function* generateInstructionTypes(
yield equation(left, {kind: 'Object', shapeId: BuiltInJsxId});
break;
}
case 'NewExpression': {
const returnType = makeType();
yield equation(value.callee.identifier.type, {
kind: 'Function',
return: returnType,
shapeId: null,
isConstructor: true,
});
yield equation(left, returnType);
break;
}
case 'PropertyStore':
case 'DeclareLocal':
case 'NewExpression':
case 'RegExpLiteral':
case 'MetaProperty':
case 'ComputedStore':
@@ -520,11 +505,7 @@ class Unifier {
return;
}
if (
tB.kind === 'Function' &&
tA.kind === 'Function' &&
tA.isConstructor === tB.isConstructor
) {
if (tB.kind === 'Function' && tA.kind === 'Function') {
this.unify(tA.return, tB.return);
return;
}
@@ -667,7 +648,6 @@ class Unifier {
kind: 'Function',
return: returnType,
shapeId: type.shapeId,
isConstructor: type.isConstructor,
};
}
case 'ObjectMethod':

View File

@@ -50,55 +50,28 @@ import { useIdentity, Stringify } from "shared-runtime";
* (2) the 1st argument might mutate its callee
*/
function Component(t0) {
const $ = _c(10);
const $ = _c(4);
const { value } = t0;
let t1;
let t2;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t1 = { value: "foo" };
t2 = { value: "bar" };
$[0] = t1;
$[1] = t2;
} else {
t1 = $[0];
t2 = $[1];
}
let t3;
if ($[2] !== value) {
t3 = [t1, t2, { value }];
$[2] = value;
$[3] = t3;
} else {
t3 = $[3];
}
const arr = t3;
const arr = [{ value: "foo" }, { value: "bar" }, { value }];
useIdentity();
let t4;
if ($[4] !== arr) {
t4 = Array.from(arr, _temp);
$[4] = arr;
$[5] = t4;
const derived = Array.from(arr, _temp);
let t1;
if ($[0] !== derived) {
t1 = derived.at(-1);
$[0] = derived;
$[1] = t1;
} else {
t4 = $[5];
t1 = $[1];
}
const derived = t4;
let t5;
if ($[6] !== derived) {
t5 = derived.at(-1);
$[6] = derived;
$[7] = t5;
let t2;
if ($[2] !== t1) {
t2 = <Stringify>{t1}</Stringify>;
$[2] = t1;
$[3] = t2;
} else {
t5 = $[7];
t2 = $[3];
}
let t6;
if ($[8] !== t5) {
t6 = <Stringify>{t5}</Stringify>;
$[8] = t5;
$[9] = t6;
} else {
t6 = $[9];
}
return t6;
return t2;
}
function _temp(x, idx) {
return { ...x, id: idx };

View File

@@ -50,55 +50,28 @@ import { useIdentity, Stringify } from "shared-runtime";
* (2) the 1st argument might mutate its callee
*/
function Component(t0) {
const $ = _c(10);
const $ = _c(4);
const { value } = t0;
let t1;
let t2;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t1 = { value: "foo" };
t2 = { value: "bar" };
$[0] = t1;
$[1] = t2;
} else {
t1 = $[0];
t2 = $[1];
}
let t3;
if ($[2] !== value) {
t3 = [t1, t2, { value }];
$[2] = value;
$[3] = t3;
} else {
t3 = $[3];
}
const arr = t3;
const arr = [{ value: "foo" }, { value: "bar" }, { value }];
useIdentity();
let t4;
if ($[4] !== arr) {
t4 = Array.from(arr);
$[4] = arr;
$[5] = t4;
const derived = Array.from(arr);
let t1;
if ($[0] !== derived) {
t1 = derived.at(-1);
$[0] = derived;
$[1] = t1;
} else {
t4 = $[5];
t1 = $[1];
}
const derived = t4;
let t5;
if ($[6] !== derived) {
t5 = derived.at(-1);
$[6] = derived;
$[7] = t5;
let t2;
if ($[2] !== t1) {
t2 = <Stringify>{t1}</Stringify>;
$[2] = t1;
$[3] = t2;
} else {
t5 = $[7];
t2 = $[3];
}
let t6;
if ($[8] !== t5) {
t6 = <Stringify>{t5}</Stringify>;
$[8] = t5;
$[9] = t6;
} else {
t6 = $[9];
}
return t6;
return t2;
}
export const FIXTURE_ENTRYPOINT = {

View File

@@ -7,19 +7,14 @@ import {mutateAndReturn, Stringify, useIdentity} from 'shared-runtime';
function Component({value}) {
const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
useIdentity();
const derived = Array.from(arr).map(mutateAndReturn);
return (
<Stringify>
{derived.at(0)}
{derived.at(-1)}
</Stringify>
);
const derived = Array.from(arr, mutateAndReturn);
return <Stringify>{derived.at(-1)}</Stringify>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: 5}],
sequentialRenders: [{value: 5}, {value: 6}, {value: 6}, {value: 7}],
sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
};
```
@@ -31,54 +26,39 @@ import { c as _c } from "react/compiler-runtime";
import { mutateAndReturn, Stringify, useIdentity } from "shared-runtime";
function Component(t0) {
const $ = _c(7);
const $ = _c(4);
const { value } = t0;
const arr = [{ value: "foo" }, { value: "bar" }, { value }];
useIdentity();
const derived = Array.from(arr).map(mutateAndReturn);
const derived = Array.from(arr, mutateAndReturn);
let t1;
if ($[0] !== derived) {
t1 = derived.at(0);
t1 = derived.at(-1);
$[0] = derived;
$[1] = t1;
} else {
t1 = $[1];
}
let t2;
if ($[2] !== derived) {
t2 = derived.at(-1);
$[2] = derived;
if ($[2] !== t1) {
t2 = <Stringify>{t1}</Stringify>;
$[2] = t1;
$[3] = t2;
} else {
t2 = $[3];
}
let t3;
if ($[4] !== t1 || $[5] !== t2) {
t3 = (
<Stringify>
{t1}
{t2}
</Stringify>
);
$[4] = t1;
$[5] = t2;
$[6] = t3;
} else {
t3 = $[6];
}
return t3;
return t2;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ value: 5 }],
sequentialRenders: [{ value: 5 }, { value: 6 }, { value: 6 }, { value: 7 }],
sequentialRenders: [{ value: 5 }, { value: 6 }, { value: 6 }],
};
```
### Eval output
(kind: ok) <div>{"children":[{"value":"foo","wat0":"joe"},{"value":5,"wat0":"joe"}]}</div>
<div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>
<div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>
<div>{"children":[{"value":"foo","wat0":"joe"},{"value":7,"wat0":"joe"}]}</div>
(kind: ok) <div>{"children":{"value":5,"wat0":"joe"}}</div>
<div>{"children":{"value":6,"wat0":"joe"}}</div>
<div>{"children":{"value":6,"wat0":"joe"}}</div>

View File

@@ -3,17 +3,12 @@ import {mutateAndReturn, Stringify, useIdentity} from 'shared-runtime';
function Component({value}) {
const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
useIdentity();
const derived = Array.from(arr).map(mutateAndReturn);
return (
<Stringify>
{derived.at(0)}
{derived.at(-1)}
</Stringify>
);
const derived = Array.from(arr, mutateAndReturn);
return <Stringify>{derived.at(-1)}</Stringify>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: 5}],
sequentialRenders: [{value: 5}, {value: 6}, {value: 6}, {value: 7}],
sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
};

View File

@@ -1,73 +0,0 @@
## 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

@@ -1,20 +0,0 @@
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 {
makeReadOnly,
shouldInstrument,
useRenderCounter,
shouldInstrument,
makeReadOnly,
} 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 { shouldInstrument, useRenderCounter } from "react-compiler-runtime";
import { useRenderCounter, shouldInstrument } from "react-compiler-runtime";
import { c as _c } from "react/compiler-runtime"; // @enableEmitInstrumentForget @compilationMode(annotation)
function Bar(props) {

View File

@@ -1,97 +0,0 @@
## 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

@@ -1,23 +0,0 @@
// @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

@@ -1,37 +0,0 @@
## 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

@@ -1,33 +0,0 @@
## 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

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

View File

@@ -0,0 +1,21 @@
## 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

@@ -1,27 +0,0 @@
## 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

@@ -1,6 +0,0 @@
// @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 { c as _c } from "react/compiler-runtime";
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
import { c as _c } from "react/compiler-runtime"; // @gating
import { Stringify } from "shared-runtime";
const ErrorView = isForgetEnabled_Fixtures()
? (t0) => {

View File

@@ -36,9 +36,9 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { shouldInstrument, useRenderCounter } from "react-compiler-runtime";
import { c as _c } from "react/compiler-runtime";
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @enableEmitInstrumentForget @compilationMode(annotation) @gating
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
import { useRenderCounter, shouldInstrument } from "react-compiler-runtime";
import { c as _c } from "react/compiler-runtime"; // @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 { c as _c } from "react/compiler-runtime";
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
import { c as _c } from "react/compiler-runtime";
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,15 +39,16 @@ 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

@@ -1,62 +0,0 @@
## 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

@@ -1,16 +0,0 @@
// @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 { c as _c } from "react/compiler-runtime";
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
import { c as _c } from "react/compiler-runtime"; // @gating
const Component = isForgetEnabled_Fixtures()
? function Component() {
const $ = _c(1);

View File

@@ -24,8 +24,8 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
import { c as _c } from "react/compiler-runtime"; // @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 { c as _c } from "react/compiler-runtime";
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
import { c as _c } from "react/compiler-runtime"; // @gating
const Component = isForgetEnabled_Fixtures()
? function Component() {
const $ = _c(1);

View File

@@ -27,8 +27,8 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode(annotation)
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
import { c as _c } from "react/compiler-runtime"; // @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 { c as _c } from "react/compiler-runtime";
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode(annotation)
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
import { c as _c } from "react/compiler-runtime"; // @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 { c as _c } from "react/compiler-runtime";
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode(annotation)
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
import { c as _c } from "react/compiler-runtime"; // @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 { c as _c } from "react/compiler-runtime";
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode(annotation)
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
import { c as _c } from "react/compiler-runtime"; // @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 { c as _c } from "react/compiler-runtime";
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
import { c as _c } from "react/compiler-runtime"; // @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,12 +41,13 @@ 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 { c as _c } from "react/compiler-runtime";
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
import { c as _c } from "react/compiler-runtime"; // @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 { c as _c } from "react/compiler-runtime";
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
import { c as _c } from "react/compiler-runtime";
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 { c as _c } from "react/compiler-runtime";
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode(infer)
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
import { c as _c } from "react/compiler-runtime"; // @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 { c as _c } from "react/compiler-runtime";
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
import { c as _c } from "react/compiler-runtime"; // @gating
import * as React from "react";
let Foo;

View File

@@ -19,8 +19,8 @@ export default props => (
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
import { c as _c } from "react/compiler-runtime"; // @gating
import { Stringify } from "shared-runtime";
const ErrorView = isForgetEnabled_Fixtures()

View File

@@ -24,8 +24,8 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
import { c as _c } from "react/compiler-runtime"; // @gating
import { Stringify } from "shared-runtime";
const ErrorView = isForgetEnabled_Fixtures()

View File

@@ -26,8 +26,8 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
import { c as _c } from "react/compiler-runtime"; // @gating
import { Stringify } from "shared-runtime";
const ErrorView = isForgetEnabled_Fixtures()

View File

@@ -31,8 +31,8 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
import { c as _c } from "react/compiler-runtime"; // @gating
import * as React from "react";
/**

View File

@@ -1,66 +0,0 @@
## Input
```javascript
import {useIdentity} from 'shared-runtime';
/**
* Forked version of call-spread-argument-mutable-iterator that is known to not mutate
* the spread argument since it is a Set
*/
function useFoo() {
const s = new Set([1, 2]);
useIdentity(null);
return [Math.max(...s), s];
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{}],
sequentialRenders: [{}, {}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { useIdentity } from "shared-runtime";
/**
* Forked version of call-spread-argument-mutable-iterator that is known to not mutate
* the spread argument since it is a Set
*/
function useFoo() {
const $ = _c(2);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = new Set([1, 2]);
$[0] = t0;
} else {
t0 = $[0];
}
const s = t0;
useIdentity(null);
let t1;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t1 = [Math.max(...s), s];
$[1] = t1;
} else {
t1 = $[1];
}
return t1;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{}],
sequentialRenders: [{}, {}],
};
```
### Eval output
(kind: ok) [2,{"kind":"Set","value":[1,2]}]
[2,{"kind":"Set","value":[1,2]}]

View File

@@ -1,17 +0,0 @@
import {useIdentity} from 'shared-runtime';
/**
* Forked version of call-spread-argument-mutable-iterator that is known to not mutate
* the spread argument since it is a Set
*/
function useFoo() {
const s = new Set([1, 2]);
useIdentity(null);
return [Math.max(...s), s];
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{}],
sequentialRenders: [{}, {}],
};

View File

@@ -1,77 +0,0 @@
## Input
```javascript
import {makeArray} from 'shared-runtime';
function useHook({el1, el2}) {
const s = new Map();
s.set(el1, makeArray(el1));
s.set(el2, makeArray(el2));
return s.size;
}
export const FIXTURE_ENTRYPOINT = {
fn: useHook,
params: [{el1: 1, el2: 'foo'}],
sequentialRenders: [
{el1: 1, el2: 'foo'},
{el1: 2, el2: 'foo'},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { makeArray } from "shared-runtime";
function useHook(t0) {
const $ = _c(7);
const { el1, el2 } = t0;
let s;
if ($[0] !== el1 || $[1] !== el2) {
s = new Map();
let t1;
if ($[3] !== el1) {
t1 = makeArray(el1);
$[3] = el1;
$[4] = t1;
} else {
t1 = $[4];
}
s.set(el1, t1);
let t2;
if ($[5] !== el2) {
t2 = makeArray(el2);
$[5] = el2;
$[6] = t2;
} else {
t2 = $[6];
}
s.set(el2, t2);
$[0] = el1;
$[1] = el2;
$[2] = s;
} else {
s = $[2];
}
return s.size;
}
export const FIXTURE_ENTRYPOINT = {
fn: useHook,
params: [{ el1: 1, el2: "foo" }],
sequentialRenders: [
{ el1: 1, el2: "foo" },
{ el1: 2, el2: "foo" },
],
};
```
### Eval output
(kind: ok) 2
2

View File

@@ -1,17 +0,0 @@
import {makeArray} from 'shared-runtime';
function useHook({el1, el2}) {
const s = new Map();
s.set(el1, makeArray(el1));
s.set(el2, makeArray(el2));
return s.size;
}
export const FIXTURE_ENTRYPOINT = {
fn: useHook,
params: [{el1: 1, el2: 'foo'}],
sequentialRenders: [
{el1: 1, el2: 'foo'},
{el1: 2, el2: 'foo'},
],
};

View File

@@ -1,113 +0,0 @@
## Input
```javascript
import {mutateAndReturn, Stringify, useIdentity} from 'shared-runtime';
/**
* Repro for bug with `mutableOnlyIfOperandsAreMutable` flag
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok)
* <div>{"children":[{"value":"foo","wat0":"joe"},{"value":5,"wat0":"joe"}]}</div>
* <div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>
* <div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>
* Forget:
* (kind: ok)
* <div>{"children":[{"value":"foo","wat0":"joe"},{"value":5,"wat0":"joe"}]}</div>
* <div>{"children":[{"value":"foo","wat0":"joe","wat1":"joe"},{"value":6,"wat0":"joe"}]}</div>
* <div>{"children":[{"value":"foo","wat0":"joe","wat1":"joe"},{"value":6,"wat0":"joe"}]}</div>
*/
function Component({value}) {
const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
useIdentity(null);
const derived = arr.filter(mutateAndReturn);
return (
<Stringify>
{derived.at(0)}
{derived.at(-1)}
</Stringify>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: 5}],
sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { mutateAndReturn, Stringify, useIdentity } from "shared-runtime";
/**
* Repro for bug with `mutableOnlyIfOperandsAreMutable` flag
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok)
* <div>{"children":[{"value":"foo","wat0":"joe"},{"value":5,"wat0":"joe"}]}</div>
* <div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>
* <div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>
* Forget:
* (kind: ok)
* <div>{"children":[{"value":"foo","wat0":"joe"},{"value":5,"wat0":"joe"}]}</div>
* <div>{"children":[{"value":"foo","wat0":"joe","wat1":"joe"},{"value":6,"wat0":"joe"}]}</div>
* <div>{"children":[{"value":"foo","wat0":"joe","wat1":"joe"},{"value":6,"wat0":"joe"}]}</div>
*/
function Component(t0) {
const $ = _c(7);
const { value } = t0;
const arr = [{ value: "foo" }, { value: "bar" }, { value }];
useIdentity(null);
const derived = arr.filter(mutateAndReturn);
let t1;
if ($[0] !== derived) {
t1 = derived.at(0);
$[0] = derived;
$[1] = t1;
} else {
t1 = $[1];
}
let t2;
if ($[2] !== derived) {
t2 = derived.at(-1);
$[2] = derived;
$[3] = t2;
} else {
t2 = $[3];
}
let t3;
if ($[4] !== t1 || $[5] !== t2) {
t3 = (
<Stringify>
{t1}
{t2}
</Stringify>
);
$[4] = t1;
$[5] = t2;
$[6] = t3;
} else {
t3 = $[6];
}
return t3;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ value: 5 }],
sequentialRenders: [{ value: 5 }, { value: 6 }, { value: 6 }],
};
```
### Eval output
(kind: ok) <div>{"children":[{"value":"foo","wat0":"joe"},{"value":5,"wat0":"joe"}]}</div>
<div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>
<div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>

View File

@@ -1,34 +0,0 @@
import {mutateAndReturn, Stringify, useIdentity} from 'shared-runtime';
/**
* Repro for bug with `mutableOnlyIfOperandsAreMutable` flag
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok)
* <div>{"children":[{"value":"foo","wat0":"joe"},{"value":5,"wat0":"joe"}]}</div>
* <div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>
* <div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>
* Forget:
* (kind: ok)
* <div>{"children":[{"value":"foo","wat0":"joe"},{"value":5,"wat0":"joe"}]}</div>
* <div>{"children":[{"value":"foo","wat0":"joe","wat1":"joe"},{"value":6,"wat0":"joe"}]}</div>
* <div>{"children":[{"value":"foo","wat0":"joe","wat1":"joe"},{"value":6,"wat0":"joe"}]}</div>
*/
function Component({value}) {
const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
useIdentity(null);
const derived = arr.filter(mutateAndReturn);
return (
<Stringify>
{derived.at(0)}
{derived.at(-1)}
</Stringify>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: 5}],
sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
};

View File

@@ -1,118 +0,0 @@
## Input
```javascript
import {Stringify, useIdentity} from 'shared-runtime';
/**
* Also see repro-array-map-known-mutate-shape, which calls a global function
* that mutates its operands.
*/
function Component({value}) {
const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
useIdentity(null);
const derived = arr.filter(Boolean);
return (
<Stringify>
{derived.at(0)}
{derived.at(-1)}
</Stringify>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: 5}],
sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { Stringify, useIdentity } from "shared-runtime";
/**
* Also see repro-array-map-known-mutate-shape, which calls a global function
* that mutates its operands.
*/
function Component(t0) {
const $ = _c(13);
const { value } = t0;
let t1;
let t2;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t1 = { value: "foo" };
t2 = { value: "bar" };
$[0] = t1;
$[1] = t2;
} else {
t1 = $[0];
t2 = $[1];
}
let t3;
if ($[2] !== value) {
t3 = [t1, t2, { value }];
$[2] = value;
$[3] = t3;
} else {
t3 = $[3];
}
const arr = t3;
useIdentity(null);
let t4;
if ($[4] !== arr) {
t4 = arr.filter(Boolean);
$[4] = arr;
$[5] = t4;
} else {
t4 = $[5];
}
const derived = t4;
let t5;
if ($[6] !== derived) {
t5 = derived.at(0);
$[6] = derived;
$[7] = t5;
} else {
t5 = $[7];
}
let t6;
if ($[8] !== derived) {
t6 = derived.at(-1);
$[8] = derived;
$[9] = t6;
} else {
t6 = $[9];
}
let t7;
if ($[10] !== t5 || $[11] !== t6) {
t7 = (
<Stringify>
{t5}
{t6}
</Stringify>
);
$[10] = t5;
$[11] = t6;
$[12] = t7;
} else {
t7 = $[12];
}
return t7;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ value: 5 }],
sequentialRenders: [{ value: 5 }, { value: 6 }, { value: 6 }],
};
```
### Eval output
(kind: ok) <div>{"children":[{"value":"foo"},{"value":5}]}</div>
<div>{"children":[{"value":"foo"},{"value":6}]}</div>
<div>{"children":[{"value":"foo"},{"value":6}]}</div>

View File

@@ -1,23 +0,0 @@
import {Stringify, useIdentity} from 'shared-runtime';
/**
* Also see repro-array-map-known-mutate-shape, which calls a global function
* that mutates its operands.
*/
function Component({value}) {
const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
useIdentity(null);
const derived = arr.filter(Boolean);
return (
<Stringify>
{derived.at(0)}
{derived.at(-1)}
</Stringify>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: 5}],
sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
};

View File

@@ -1,91 +0,0 @@
## Input
```javascript
import {mutateAndReturn, Stringify, useIdentity} from 'shared-runtime';
/**
* Copy of repro-array-map-capture-mutate-bug, showing that the same issue applies to any
* function call which captures its callee when applying an operand.
*/
function Component({value}) {
const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
useIdentity(null);
const derived = arr.map(mutateAndReturn);
return (
<Stringify>
{derived.at(0)}
{derived.at(-1)}
</Stringify>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: 5}],
sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { mutateAndReturn, Stringify, useIdentity } from "shared-runtime";
/**
* Copy of repro-array-map-capture-mutate-bug, showing that the same issue applies to any
* function call which captures its callee when applying an operand.
*/
function Component(t0) {
const $ = _c(7);
const { value } = t0;
const arr = [{ value: "foo" }, { value: "bar" }, { value }];
useIdentity(null);
const derived = arr.map(mutateAndReturn);
let t1;
if ($[0] !== derived) {
t1 = derived.at(0);
$[0] = derived;
$[1] = t1;
} else {
t1 = $[1];
}
let t2;
if ($[2] !== derived) {
t2 = derived.at(-1);
$[2] = derived;
$[3] = t2;
} else {
t2 = $[3];
}
let t3;
if ($[4] !== t1 || $[5] !== t2) {
t3 = (
<Stringify>
{t1}
{t2}
</Stringify>
);
$[4] = t1;
$[5] = t2;
$[6] = t3;
} else {
t3 = $[6];
}
return t3;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ value: 5 }],
sequentialRenders: [{ value: 5 }, { value: 6 }, { value: 6 }],
};
```
### Eval output
(kind: ok) <div>{"children":[{"value":"foo","wat0":"joe"},{"value":5,"wat0":"joe"}]}</div>
<div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>
<div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>

View File

@@ -1,23 +0,0 @@
import {mutateAndReturn, Stringify, useIdentity} from 'shared-runtime';
/**
* Copy of repro-array-map-capture-mutate-bug, showing that the same issue applies to any
* function call which captures its callee when applying an operand.
*/
function Component({value}) {
const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
useIdentity(null);
const derived = arr.map(mutateAndReturn);
return (
<Stringify>
{derived.at(0)}
{derived.at(-1)}
</Stringify>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: 5}],
sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
};

View File

@@ -1,100 +0,0 @@
## Input
```javascript
import {Stringify, useIdentity} from 'shared-runtime';
/**
* Also see repro-array-map-known-nonmutate-Boolean, which calls a global
* function that does *not* mutate its operands.
*/
function Component({value}) {
const arr = [
new Set([['foo', 2]]).values(),
new Set([['bar', 4]]).values(),
[['baz', value]],
];
useIdentity(null);
const derived = arr.map(Object.fromEntries);
return (
<Stringify>
{derived.at(0)}
{derived.at(-1)}
</Stringify>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: 5}],
sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { Stringify, useIdentity } from "shared-runtime";
/**
* Also see repro-array-map-known-nonmutate-Boolean, which calls a global
* function that does *not* mutate its operands.
*/
function Component(t0) {
const $ = _c(7);
const { value } = t0;
const arr = [
new Set([["foo", 2]]).values(),
new Set([["bar", 4]]).values(),
[["baz", value]],
];
useIdentity(null);
const derived = arr.map(Object.fromEntries);
let t1;
if ($[0] !== derived) {
t1 = derived.at(0);
$[0] = derived;
$[1] = t1;
} else {
t1 = $[1];
}
let t2;
if ($[2] !== derived) {
t2 = derived.at(-1);
$[2] = derived;
$[3] = t2;
} else {
t2 = $[3];
}
let t3;
if ($[4] !== t1 || $[5] !== t2) {
t3 = (
<Stringify>
{t1}
{t2}
</Stringify>
);
$[4] = t1;
$[5] = t2;
$[6] = t3;
} else {
t3 = $[6];
}
return t3;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ value: 5 }],
sequentialRenders: [{ value: 5 }, { value: 6 }, { value: 6 }],
};
```
### Eval output
(kind: ok) <div>{"children":[{"foo":2},{"baz":5}]}</div>
<div>{"children":[{"foo":2},{"baz":6}]}</div>
<div>{"children":[{"foo":2},{"baz":6}]}</div>

View File

@@ -1,27 +0,0 @@
import {Stringify, useIdentity} from 'shared-runtime';
/**
* Also see repro-array-map-known-nonmutate-Boolean, which calls a global
* function that does *not* mutate its operands.
*/
function Component({value}) {
const arr = [
new Set([['foo', 2]]).values(),
new Set([['bar', 4]]).values(),
[['baz', value]],
];
useIdentity(null);
const derived = arr.map(Object.fromEntries);
return (
<Stringify>
{derived.at(0)}
{derived.at(-1)}
</Stringify>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: 5}],
sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
};

View File

@@ -1,76 +0,0 @@
## Input
```javascript
import {makeArray} from 'shared-runtime';
function useHook({el1, el2}) {
const s = new Set();
const arr = makeArray(el1);
s.add(arr);
// Mutate after store
arr.push(el2);
s.add(makeArray(el2));
return s.size;
}
export const FIXTURE_ENTRYPOINT = {
fn: useHook,
params: [{el1: 1, el2: 'foo'}],
sequentialRenders: [
{el1: 1, el2: 'foo'},
{el1: 2, el2: 'foo'},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { makeArray } from "shared-runtime";
function useHook(t0) {
const $ = _c(5);
const { el1, el2 } = t0;
let s;
if ($[0] !== el1 || $[1] !== el2) {
s = new Set();
const arr = makeArray(el1);
s.add(arr);
arr.push(el2);
let t1;
if ($[3] !== el2) {
t1 = makeArray(el2);
$[3] = el2;
$[4] = t1;
} else {
t1 = $[4];
}
s.add(t1);
$[0] = el1;
$[1] = el2;
$[2] = s;
} else {
s = $[2];
}
return s.size;
}
export const FIXTURE_ENTRYPOINT = {
fn: useHook,
params: [{ el1: 1, el2: "foo" }],
sequentialRenders: [
{ el1: 1, el2: "foo" },
{ el1: 2, el2: "foo" },
],
};
```
### Eval output
(kind: ok) 2
2

View File

@@ -1,21 +0,0 @@
import {makeArray} from 'shared-runtime';
function useHook({el1, el2}) {
const s = new Set();
const arr = makeArray(el1);
s.add(arr);
// Mutate after store
arr.push(el2);
s.add(makeArray(el2));
return s.size;
}
export const FIXTURE_ENTRYPOINT = {
fn: useHook,
params: [{el1: 1, el2: 'foo'}],
sequentialRenders: [
{el1: 1, el2: 'foo'},
{el1: 2, el2: 'foo'},
],
};

View File

@@ -1,108 +0,0 @@
## Input
```javascript
const MODULE_LOCAL = new Set([4, 5, 6]);
function useFoo({propArr}: {propArr: Array<number>}) {
/* Array can be memoized separately of the Set */
const s1 = new Set([1, 2, 3]);
s1.add(propArr[0]);
/* but `.values` cannot be memoized separately */
const s2 = new Set(MODULE_LOCAL.values());
s2.add(propArr[1]);
const s3 = new Set(s2.values());
s3.add(propArr[2]);
/**
* s4 should be memoized separately from s3
*/
const s4 = new Set(s3);
s4.add(propArr[3]);
return [s1, s2, s3, s4];
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{propArr: [7, 8, 9]}],
sequentialRenders: [{propArr: [7, 8, 9]}, {propArr: [7, 8, 10]}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
const MODULE_LOCAL = new Set([4, 5, 6]);
function useFoo(t0) {
const $ = _c(15);
const { propArr } = t0;
let t1;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t1 = [1, 2, 3];
$[0] = t1;
} else {
t1 = $[0];
}
let s1;
if ($[1] !== propArr[0]) {
s1 = new Set(t1);
s1.add(propArr[0]);
$[1] = propArr[0];
$[2] = s1;
} else {
s1 = $[2];
}
let s2;
let s3;
if ($[3] !== propArr[1] || $[4] !== propArr[2]) {
s2 = new Set(MODULE_LOCAL.values());
s2.add(propArr[1]);
s3 = new Set(s2.values());
s3.add(propArr[2]);
$[3] = propArr[1];
$[4] = propArr[2];
$[5] = s2;
$[6] = s3;
} else {
s2 = $[5];
s3 = $[6];
}
let s4;
if ($[7] !== propArr[3] || $[8] !== s3) {
s4 = new Set(s3);
s4.add(propArr[3]);
$[7] = propArr[3];
$[8] = s3;
$[9] = s4;
} else {
s4 = $[9];
}
let t2;
if ($[10] !== s1 || $[11] !== s2 || $[12] !== s3 || $[13] !== s4) {
t2 = [s1, s2, s3, s4];
$[10] = s1;
$[11] = s2;
$[12] = s3;
$[13] = s4;
$[14] = t2;
} else {
t2 = $[14];
}
return t2;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ propArr: [7, 8, 9] }],
sequentialRenders: [{ propArr: [7, 8, 9] }, { propArr: [7, 8, 10] }],
};
```
### Eval output
(kind: ok) [{"kind":"Set","value":[1,2,3,7]},{"kind":"Set","value":[4,5,6,8]},{"kind":"Set","value":[4,5,6,8,9]},{"kind":"Set","value":[4,5,6,8,9,null]}]
[{"kind":"Set","value":[1,2,3,7]},{"kind":"Set","value":[4,5,6,8]},{"kind":"Set","value":[4,5,6,8,10]},{"kind":"Set","value":[4,5,6,8,10,null]}]

View File

@@ -1,26 +0,0 @@
const MODULE_LOCAL = new Set([4, 5, 6]);
function useFoo({propArr}: {propArr: Array<number>}) {
/* Array can be memoized separately of the Set */
const s1 = new Set([1, 2, 3]);
s1.add(propArr[0]);
/* but `.values` cannot be memoized separately */
const s2 = new Set(MODULE_LOCAL.values());
s2.add(propArr[1]);
const s3 = new Set(s2.values());
s3.add(propArr[2]);
/**
* s4 should be memoized separately from s3
*/
const s4 = new Set(s3);
s4.add(propArr[3]);
return [s1, s2, s3, s4];
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{propArr: [7, 8, 9]}],
sequentialRenders: [{propArr: [7, 8, 9]}, {propArr: [7, 8, 10]}],
};

View File

@@ -1,77 +0,0 @@
## Input
```javascript
import {makeArray} from 'shared-runtime';
function useHook({el1, el2}) {
const s = new Set();
s.add(makeArray(el1));
s.add(makeArray(el2));
return s.size;
}
export const FIXTURE_ENTRYPOINT = {
fn: useHook,
params: [{el1: 1, el2: 'foo'}],
sequentialRenders: [
{el1: 1, el2: 'foo'},
{el1: 2, el2: 'foo'},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { makeArray } from "shared-runtime";
function useHook(t0) {
const $ = _c(7);
const { el1, el2 } = t0;
let s;
if ($[0] !== el1 || $[1] !== el2) {
s = new Set();
let t1;
if ($[3] !== el1) {
t1 = makeArray(el1);
$[3] = el1;
$[4] = t1;
} else {
t1 = $[4];
}
s.add(t1);
let t2;
if ($[5] !== el2) {
t2 = makeArray(el2);
$[5] = el2;
$[6] = t2;
} else {
t2 = $[6];
}
s.add(t2);
$[0] = el1;
$[1] = el2;
$[2] = s;
} else {
s = $[2];
}
return s.size;
}
export const FIXTURE_ENTRYPOINT = {
fn: useHook,
params: [{ el1: 1, el2: "foo" }],
sequentialRenders: [
{ el1: 1, el2: "foo" },
{ el1: 2, el2: "foo" },
],
};
```
### Eval output
(kind: ok) 2
2

View File

@@ -1,17 +0,0 @@
import {makeArray} from 'shared-runtime';
function useHook({el1, el2}) {
const s = new Set();
s.add(makeArray(el1));
s.add(makeArray(el2));
return s.size;
}
export const FIXTURE_ENTRYPOINT = {
fn: useHook,
params: [{el1: 1, el2: 'foo'}],
sequentialRenders: [
{el1: 1, el2: 'foo'},
{el1: 2, el2: 'foo'},
],
};

View File

@@ -1,82 +0,0 @@
## Input
```javascript
import {makeArray, mutate} from 'shared-runtime';
function useFoo({propArr}: {propArr: Array<number>}) {
const s1 = new Set<number | Array<number>>([1, 2, 3]);
s1.add(makeArray(propArr[0]));
const s2 = new Set(s1);
// this may also may mutate s1
mutate(s2);
return [s1, s2];
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{propArr: [7, 8, 9]}],
sequentialRenders: [
{propArr: [7, 8, 9]},
{propArr: [7, 8, 9]},
{propArr: [7, 8, 10]},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { makeArray, mutate } from "shared-runtime";
function useFoo(t0) {
const $ = _c(6);
const { propArr } = t0;
let s1;
let s2;
if ($[0] !== propArr[0]) {
s1 = new Set([1, 2, 3]);
s1.add(makeArray(propArr[0]));
s2 = new Set(s1);
mutate(s2);
$[0] = propArr[0];
$[1] = s1;
$[2] = s2;
} else {
s1 = $[1];
s2 = $[2];
}
let t1;
if ($[3] !== s1 || $[4] !== s2) {
t1 = [s1, s2];
$[3] = s1;
$[4] = s2;
$[5] = t1;
} else {
t1 = $[5];
}
return t1;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ propArr: [7, 8, 9] }],
sequentialRenders: [
{ propArr: [7, 8, 9] },
{ propArr: [7, 8, 9] },
{ propArr: [7, 8, 10] },
],
};
```
### Eval output
(kind: ok) [{"kind":"Set","value":[1,2,3,[7]]},{"kind":"Set","value":[1,2,3,"[[ cyclic ref *2 ]]"]}]
[{"kind":"Set","value":[1,2,3,[7]]},{"kind":"Set","value":[1,2,3,"[[ cyclic ref *2 ]]"]}]
[{"kind":"Set","value":[1,2,3,[7]]},{"kind":"Set","value":[1,2,3,"[[ cyclic ref *2 ]]"]}]

View File

@@ -1,22 +0,0 @@
import {makeArray, mutate} from 'shared-runtime';
function useFoo({propArr}: {propArr: Array<number>}) {
const s1 = new Set<number | Array<number>>([1, 2, 3]);
s1.add(makeArray(propArr[0]));
const s2 = new Set(s1);
// this may also may mutate s1
mutate(s2);
return [s1, s2];
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{propArr: [7, 8, 9]}],
sequentialRenders: [
{propArr: [7, 8, 9]},
{propArr: [7, 8, 9]},
{propArr: [7, 8, 10]},
],
};

View File

@@ -1,65 +0,0 @@
## Input
```javascript
import {makeArray, useHook} from 'shared-runtime';
function useFoo({propArr}: {propArr: Array<number>}) {
const s1 = new Set<number | Array<number>>([1, 2, 3]);
s1.add(makeArray(propArr[0]));
useHook();
const s2 = new Set();
for (const el of s1.values()) {
s2.add(el);
}
return [s1, s2];
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{propArr: [7, 8, 9]}],
sequentialRenders: [
{propArr: [7, 8, 9]},
{propArr: [7, 8, 9]},
{propArr: [7, 8, 10]},
],
};
```
## Code
```javascript
import { makeArray, useHook } from "shared-runtime";
function useFoo(t0) {
const { propArr } = t0;
const s1 = new Set([1, 2, 3]);
s1.add(makeArray(propArr[0]));
useHook();
const s2 = new Set();
for (const el of s1.values()) {
s2.add(el);
}
return [s1, s2];
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ propArr: [7, 8, 9] }],
sequentialRenders: [
{ propArr: [7, 8, 9] },
{ propArr: [7, 8, 9] },
{ propArr: [7, 8, 10] },
],
};
```
### Eval output
(kind: ok) [{"kind":"Set","value":[1,2,3,[7]]},{"kind":"Set","value":[1,2,3,"[[ cyclic ref *2 ]]"]}]
[{"kind":"Set","value":[1,2,3,[7]]},{"kind":"Set","value":[1,2,3,"[[ cyclic ref *2 ]]"]}]
[{"kind":"Set","value":[1,2,3,[7]]},{"kind":"Set","value":[1,2,3,"[[ cyclic ref *2 ]]"]}]

View File

@@ -1,24 +0,0 @@
import {makeArray, useHook} from 'shared-runtime';
function useFoo({propArr}: {propArr: Array<number>}) {
const s1 = new Set<number | Array<number>>([1, 2, 3]);
s1.add(makeArray(propArr[0]));
useHook();
const s2 = new Set();
for (const el of s1.values()) {
s2.add(el);
}
return [s1, s2];
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{propArr: [7, 8, 9]}],
sequentialRenders: [
{propArr: [7, 8, 9]},
{propArr: [7, 8, 9]},
{propArr: [7, 8, 10]},
],
};

View File

@@ -1,57 +0,0 @@
## Input
```javascript
import {mutateAndReturn, Stringify, useIdentity} from 'shared-runtime';
function Component({value}) {
const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
useIdentity();
const derived = new Set(arr).forEach(mutateAndReturn);
return <Stringify>{[...derived]}</Stringify>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: 5}],
sequentialRenders: [{value: 5}, {value: 6}, {value: 6}, {value: 7}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { mutateAndReturn, Stringify, useIdentity } from "shared-runtime";
function Component(t0) {
const $ = _c(2);
const { value } = t0;
const arr = [{ value: "foo" }, { value: "bar" }, { value }];
useIdentity();
const derived = new Set(arr).forEach(mutateAndReturn);
let t1;
if ($[0] !== derived) {
t1 = <Stringify>{[...derived]}</Stringify>;
$[0] = derived;
$[1] = t1;
} else {
t1 = $[1];
}
return t1;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ value: 5 }],
sequentialRenders: [{ value: 5 }, { value: 6 }, { value: 6 }, { value: 7 }],
};
```
### Eval output
(kind: ok) [[ (exception in render) TypeError: derived is not iterable ]]
[[ (exception in render) TypeError: derived is not iterable ]]
[[ (exception in render) TypeError: derived is not iterable ]]
[[ (exception in render) TypeError: derived is not iterable ]]

View File

@@ -1,14 +0,0 @@
import {mutateAndReturn, Stringify, useIdentity} from 'shared-runtime';
function Component({value}) {
const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
useIdentity();
const derived = new Set(arr).forEach(mutateAndReturn);
return <Stringify>{[...derived]}</Stringify>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: 5}],
sequentialRenders: [{value: 5}, {value: 6}, {value: 6}, {value: 7}],
};

View File

@@ -1,82 +0,0 @@
## Input
```javascript
import {CONST_TRUE, useIdentity} from 'shared-runtime';
const hidden = CONST_TRUE;
function useFoo() {
const makeCb = useIdentity(() => {
const logIntervalId = () => {
log(intervalId);
};
let intervalId;
if (!hidden) {
intervalId = 2;
}
return () => {
logIntervalId();
};
});
return <Stringify fn={makeCb()} shouldInvokeFns={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { CONST_TRUE, useIdentity } from "shared-runtime";
const hidden = CONST_TRUE;
function useFoo() {
const $ = _c(4);
const makeCb = useIdentity(_temp);
let t0;
if ($[0] !== makeCb) {
t0 = makeCb();
$[0] = makeCb;
$[1] = t0;
} else {
t0 = $[1];
}
let t1;
if ($[2] !== t0) {
t1 = <Stringify fn={t0} shouldInvokeFns={true} />;
$[2] = t0;
$[3] = t1;
} else {
t1 = $[3];
}
return t1;
}
function _temp() {
const logIntervalId = () => {
log(intervalId);
};
let intervalId;
if (!hidden) {
intervalId = 2;
}
return () => {
logIntervalId();
};
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [],
};
```
### Eval output
(kind: exception) Stringify is not defined

View File

@@ -1,25 +0,0 @@
import {CONST_TRUE, useIdentity} from 'shared-runtime';
const hidden = CONST_TRUE;
function useFoo() {
const makeCb = useIdentity(() => {
const logIntervalId = () => {
log(intervalId);
};
let intervalId;
if (!hidden) {
intervalId = 2;
}
return () => {
logIntervalId();
};
});
return <Stringify fn={makeCb()} shouldInvokeFns={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [],
};

View File

@@ -1,59 +0,0 @@
## Input
```javascript
function Foo() {
const getX = () => x;
console.log(getX());
let x = 4;
x += 5;
return <Stringify getX={getX} shouldInvokeFns={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
function Foo() {
const $ = _c(2);
let getX;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
getX = () => x;
console.log(getX());
let x;
x = 4;
x = x + 5;
$[0] = getX;
} else {
getX = $[0];
}
x;
let t0;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t0 = <Stringify getX={getX} shouldInvokeFns={true} />;
$[1] = t0;
} else {
t0 = $[1];
}
return t0;
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [],
};
```
### Eval output
(kind: exception) Cannot access 'x' before initialization

View File

@@ -1,14 +0,0 @@
function Foo() {
const getX = () => x;
console.log(getX());
let x = 4;
x += 5;
return <Stringify getX={getX} shouldInvokeFns={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [],
};

View File

@@ -1,63 +0,0 @@
## Input
```javascript
import {CONST_NUMBER1, Stringify} from 'shared-runtime';
function useHook({cond}) {
const getX = () => x;
let x;
if (cond) {
x = CONST_NUMBER1;
}
return <Stringify getX={getX} shouldInvokeFns={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: useHook,
params: [{cond: true}],
sequentialRenders: [{cond: true}, {cond: true}, {cond: false}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { CONST_NUMBER1, Stringify } from "shared-runtime";
function useHook(t0) {
const $ = _c(2);
const { cond } = t0;
let t1;
if ($[0] !== cond) {
const getX = () => x;
let x;
if (cond) {
x = CONST_NUMBER1;
}
t1 = <Stringify getX={getX} shouldInvokeFns={true} />;
$[0] = cond;
$[1] = t1;
} else {
t1 = $[1];
}
return t1;
}
export const FIXTURE_ENTRYPOINT = {
fn: useHook,
params: [{ cond: true }],
sequentialRenders: [{ cond: true }, { cond: true }, { cond: false }],
};
```
### Eval output
(kind: ok) <div>{"getX":{"kind":"Function","result":1},"shouldInvokeFns":true}</div>
<div>{"getX":{"kind":"Function","result":1},"shouldInvokeFns":true}</div>
<div>{"getX":{"kind":"Function"},"shouldInvokeFns":true}</div>

View File

@@ -1,17 +0,0 @@
import {CONST_NUMBER1, Stringify} from 'shared-runtime';
function useHook({cond}) {
const getX = () => x;
let x;
if (cond) {
x = CONST_NUMBER1;
}
return <Stringify getX={getX} shouldInvokeFns={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: useHook,
params: [{cond: true}],
sequentialRenders: [{cond: true}, {cond: true}, {cond: false}],
};

View File

@@ -36,8 +36,7 @@ function hoisting(cond) {
items.push(bar());
};
let bar;
bar = _temp;
let bar = _temp;
foo();
}
$[0] = cond;

View File

@@ -41,11 +41,9 @@ function hoisting() {
return result;
};
let foo;
foo = () => bar + baz;
let foo = () => bar + baz;
let bar;
bar = 3;
let bar = 3;
const baz = 2;
t0 = qux();
$[0] = t0;

View File

@@ -1,67 +0,0 @@
## Input
```javascript
import {CONST_NUMBER0, CONST_NUMBER1, Stringify} from 'shared-runtime';
function useHook({cond}) {
'use memo';
const getX = () => x;
let x = CONST_NUMBER0;
if (cond) {
x += CONST_NUMBER1;
}
return <Stringify getX={getX} shouldInvokeFns={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: useHook,
params: [{cond: true}],
sequentialRenders: [{cond: true}, {cond: true}, {cond: false}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { CONST_NUMBER0, CONST_NUMBER1, Stringify } from "shared-runtime";
function useHook(t0) {
"use memo";
const $ = _c(2);
const { cond } = t0;
let t1;
if ($[0] !== cond) {
const getX = () => x;
let x;
x = CONST_NUMBER0;
if (cond) {
x = x + CONST_NUMBER1;
x;
}
t1 = <Stringify getX={getX} shouldInvokeFns={true} />;
$[0] = cond;
$[1] = t1;
} else {
t1 = $[1];
}
return t1;
}
export const FIXTURE_ENTRYPOINT = {
fn: useHook,
params: [{ cond: true }],
sequentialRenders: [{ cond: true }, { cond: true }, { cond: false }],
};
```
### Eval output
(kind: ok) <div>{"getX":{"kind":"Function","result":1},"shouldInvokeFns":true}</div>
<div>{"getX":{"kind":"Function","result":1},"shouldInvokeFns":true}</div>
<div>{"getX":{"kind":"Function","result":0},"shouldInvokeFns":true}</div>

View File

@@ -1,18 +0,0 @@
import {CONST_NUMBER0, CONST_NUMBER1, Stringify} from 'shared-runtime';
function useHook({cond}) {
'use memo';
const getX = () => x;
let x = CONST_NUMBER0;
if (cond) {
x += CONST_NUMBER1;
}
return <Stringify getX={getX} shouldInvokeFns={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: useHook,
params: [{cond: true}],
sequentialRenders: [{cond: true}, {cond: true}, {cond: false}],
};

View File

@@ -1,69 +0,0 @@
## Input
```javascript
import {CONST_NUMBER0, CONST_NUMBER1, Stringify} from 'shared-runtime';
function useHook({cond}) {
'use memo';
const getX = () => x;
let x = CONST_NUMBER0;
if (cond) {
x += CONST_NUMBER1;
x = Math.min(x, 100);
}
return <Stringify getX={getX} shouldInvokeFns={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: useHook,
params: [{cond: true}],
sequentialRenders: [{cond: true}, {cond: true}, {cond: false}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { CONST_NUMBER0, CONST_NUMBER1, Stringify } from "shared-runtime";
function useHook(t0) {
"use memo";
const $ = _c(2);
const { cond } = t0;
let t1;
if ($[0] !== cond) {
const getX = () => x;
let x;
x = CONST_NUMBER0;
if (cond) {
x = x + CONST_NUMBER1;
x;
x = Math.min(x, 100);
}
t1 = <Stringify getX={getX} shouldInvokeFns={true} />;
$[0] = cond;
$[1] = t1;
} else {
t1 = $[1];
}
return t1;
}
export const FIXTURE_ENTRYPOINT = {
fn: useHook,
params: [{ cond: true }],
sequentialRenders: [{ cond: true }, { cond: true }, { cond: false }],
};
```
### Eval output
(kind: ok) <div>{"getX":{"kind":"Function","result":1},"shouldInvokeFns":true}</div>
<div>{"getX":{"kind":"Function","result":1},"shouldInvokeFns":true}</div>
<div>{"getX":{"kind":"Function","result":0},"shouldInvokeFns":true}</div>

View File

@@ -1,19 +0,0 @@
import {CONST_NUMBER0, CONST_NUMBER1, Stringify} from 'shared-runtime';
function useHook({cond}) {
'use memo';
const getX = () => x;
let x = CONST_NUMBER0;
if (cond) {
x += CONST_NUMBER1;
x = Math.min(x, 100);
}
return <Stringify getX={getX} shouldInvokeFns={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: useHook,
params: [{cond: true}],
sequentialRenders: [{cond: true}, {cond: true}, {cond: false}],
};

View File

@@ -29,10 +29,8 @@ function hoisting() {
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
foo = () => bar + baz;
let bar;
bar = 3;
let baz;
baz = 2;
let bar = 3;
let baz = 2;
$[0] = foo;
} else {
foo = $[0];

View File

@@ -1,66 +0,0 @@
## 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

Some files were not shown because too many files have changed in this diff Show More