mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Merge 760c59fa84 into sapling-pr-archive-poteto
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
name: (Compiler) Discord Notify
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
pull_request_target:
|
||||
types: [opened, ready_for_review]
|
||||
paths:
|
||||
- compiler/**
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
name: (Runtime) Discord Notify
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
pull_request_target:
|
||||
types: [opened, ready_for_review]
|
||||
paths-ignore:
|
||||
- compiler/**
|
||||
|
||||
@@ -6,10 +6,6 @@ on:
|
||||
actor:
|
||||
required: true
|
||||
type: string
|
||||
is_remote:
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
outputs:
|
||||
is_core_team:
|
||||
value: ${{ jobs.check_maintainer.outputs.is_core_team }}
|
||||
@@ -30,7 +26,6 @@ jobs:
|
||||
outputs:
|
||||
is_core_team: ${{ steps.check_if_actor_is_maintainer.outputs.result }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Check if actor is maintainer
|
||||
id: check_if_actor_is_maintainer
|
||||
uses: actions/github-script@v7
|
||||
@@ -38,33 +33,20 @@ jobs:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
const actor = '${{ inputs.actor }}';
|
||||
let isRemote = ${{ inputs.is_remote }};
|
||||
if (typeof isRemote === 'string') {
|
||||
isRemote = isRemote === 'true';
|
||||
const res = await github.rest.repos.getContent({
|
||||
owner: 'facebook',
|
||||
repo: 'react',
|
||||
path: 'MAINTAINERS',
|
||||
ref: 'main',
|
||||
headers: { Accept: 'application/vnd.github+json' }
|
||||
});
|
||||
if (res.status !== 200) {
|
||||
console.error(res);
|
||||
throw new Error('Unable to fetch MAINTAINERS file');
|
||||
}
|
||||
if (typeof isRemote !== 'boolean') {
|
||||
throw new Error(`Invalid \`isRemote\` input. Expected a boolean, got: ${isRemote}`);
|
||||
}
|
||||
|
||||
let content = null;
|
||||
if (isRemote === true) {
|
||||
const res = await github.rest.repos.getContent({
|
||||
owner: 'facebook',
|
||||
repo: 'react',
|
||||
path: 'MAINTAINERS',
|
||||
ref: 'main',
|
||||
headers: { Accept: 'application/vnd.github+json' }
|
||||
});
|
||||
if (res.status !== 200) {
|
||||
console.error(res);
|
||||
throw new Error('Unable to fetch MAINTAINERS file');
|
||||
}
|
||||
content = Buffer.from(res.data.content, 'base64').toString();
|
||||
} else {
|
||||
content = await fs.readFileSync('./MAINTAINERS', { encoding: 'utf8' });
|
||||
}
|
||||
if (content === null) {
|
||||
throw new Error('Unable to retrieve local or http MAINTAINERS file');
|
||||
content = Buffer.from(res.data.content, 'base64').toString();
|
||||
if (content == null || typeof content !== 'string') {
|
||||
throw new Error('Unable to retrieve MAINTAINERS file');
|
||||
}
|
||||
|
||||
const maintainers = new Set(content.split('\n'));
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
name: (Shared) Label Core Team PRs
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
pull_request_target:
|
||||
|
||||
permissions: {}
|
||||
|
||||
|
||||
@@ -7,7 +7,4 @@
|
||||
|
||||
const makeE2EConfig = require('../jest/makeE2EConfig');
|
||||
|
||||
const config = makeE2EConfig('e2e with forget', true);
|
||||
config.setupFilesAfterEnv = ['<rootDir>/../scripts/jest/setupEnvE2E.js'];
|
||||
|
||||
module.exports = config;
|
||||
module.exports = makeE2EConfig('e2e with forget', true);
|
||||
|
||||
@@ -5,19 +5,16 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {jsx} from '@babel/plugin-syntax-jsx';
|
||||
import babelJest from 'babel-jest';
|
||||
import {compile} from 'babel-plugin-react-compiler';
|
||||
import {
|
||||
validateEnvironmentConfig,
|
||||
EnvironmentConfig,
|
||||
} from 'babel-plugin-react-compiler';
|
||||
import {execSync} from 'child_process';
|
||||
|
||||
import type {NodePath, Visitor} from '@babel/traverse';
|
||||
import type {CallExpression, FunctionDeclaration} from '@babel/types';
|
||||
import * as t from '@babel/types';
|
||||
import {
|
||||
EnvironmentConfig,
|
||||
validateEnvironmentConfig,
|
||||
} from 'babel-plugin-react-compiler';
|
||||
import {basename} from 'path';
|
||||
import type {CallExpression} from '@babel/types';
|
||||
import BabelPluginReactCompiler from 'babel-plugin-react-compiler';
|
||||
|
||||
/**
|
||||
* -- IMPORTANT --
|
||||
@@ -28,10 +25,19 @@ import {basename} from 'path';
|
||||
const e2eTransformerCacheKey = 1;
|
||||
const forgetOptions: EnvironmentConfig = validateEnvironmentConfig({
|
||||
enableAssumeHooksFollowRulesOfReact: true,
|
||||
enableFunctionOutlining: false,
|
||||
});
|
||||
const debugMode = process.env['DEBUG_FORGET_COMPILER'] != null;
|
||||
|
||||
const compilerCacheKey = execSync(
|
||||
'yarn --silent --cwd ../.. hash packages/babel-plugin-react-compiler/dist',
|
||||
)
|
||||
.toString()
|
||||
.trim();
|
||||
|
||||
if (debugMode) {
|
||||
console.log('cachebreaker', compilerCacheKey);
|
||||
}
|
||||
|
||||
module.exports = (useForget: boolean) => {
|
||||
function createTransformer() {
|
||||
return babelJest.createTransformer({
|
||||
@@ -42,15 +48,14 @@ module.exports = (useForget: boolean) => {
|
||||
plugins: [
|
||||
useForget
|
||||
? [
|
||||
ReactForgetFunctionTransform,
|
||||
BabelPluginReactCompiler,
|
||||
{
|
||||
environment: forgetOptions,
|
||||
/*
|
||||
* Jest hashes the babel config as a cache breaker.
|
||||
* (see https://github.com/jestjs/jest/blob/v29.6.2/packages/babel-jest/src/index.ts#L84)
|
||||
*/
|
||||
compilerCacheKey: execSync(
|
||||
'yarn --silent --cwd ../.. hash packages/babel-plugin-react-compiler/dist',
|
||||
).toString(),
|
||||
compilerCacheKey,
|
||||
transformOptionsCacheKey: forgetOptions,
|
||||
e2eTransformerCacheKey,
|
||||
},
|
||||
@@ -105,104 +110,3 @@ module.exports = (useForget: boolean) => {
|
||||
createTransformer,
|
||||
};
|
||||
};
|
||||
|
||||
// Mostly copied from react/scripts/babel/transform-forget.js
|
||||
function isReactComponentLike(fn: NodePath<FunctionDeclaration>): boolean {
|
||||
let isReactComponent = false;
|
||||
let hasNoUseForgetDirective = false;
|
||||
|
||||
/*
|
||||
* React components start with an upper case letter,
|
||||
* React hooks start with `use`
|
||||
*/
|
||||
if (
|
||||
fn.node.id == null ||
|
||||
(fn.node.id.name[0].toUpperCase() !== fn.node.id.name[0] &&
|
||||
!/^use[A-Z0-9]/.test(fn.node.id.name))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
fn.traverse({
|
||||
DirectiveLiteral(path) {
|
||||
if (path.node.value === 'use no forget') {
|
||||
hasNoUseForgetDirective = true;
|
||||
}
|
||||
},
|
||||
|
||||
JSX(path) {
|
||||
// Is there is a JSX node created in the current function context?
|
||||
if (path.scope.getFunctionParent()?.path.node === fn.node) {
|
||||
isReactComponent = true;
|
||||
}
|
||||
},
|
||||
|
||||
CallExpression(path) {
|
||||
// Is there hook usage?
|
||||
if (
|
||||
path.node.callee.type === 'Identifier' &&
|
||||
!/^use[A-Z0-9]/.test(path.node.callee.name)
|
||||
) {
|
||||
isReactComponent = true;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
if (hasNoUseForgetDirective) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isReactComponent;
|
||||
}
|
||||
|
||||
function ReactForgetFunctionTransform() {
|
||||
const compiledFns = new Set();
|
||||
const visitor = {
|
||||
FunctionDeclaration(fn: NodePath<FunctionDeclaration>, state: any): void {
|
||||
if (compiledFns.has(fn.node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isReactComponentLike(fn)) {
|
||||
return;
|
||||
}
|
||||
if (debugMode) {
|
||||
const filename = basename(state.file.opts.filename);
|
||||
if (fn.node.loc && fn.node.id) {
|
||||
console.log(
|
||||
` Compiling ${filename}:${fn.node.loc.start.line}:${fn.node.loc.start.column} ${fn.node.id.name}`,
|
||||
);
|
||||
} else {
|
||||
console.log(` Compiling ${filename} ${fn.node.id?.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
const compiled = compile(
|
||||
fn,
|
||||
forgetOptions,
|
||||
'Other',
|
||||
'all_features',
|
||||
'_c',
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
compiledFns.add(compiled);
|
||||
|
||||
const fun = t.functionDeclaration(
|
||||
compiled.id,
|
||||
compiled.params,
|
||||
compiled.body,
|
||||
compiled.generator,
|
||||
compiled.async,
|
||||
);
|
||||
fn.replaceWith(fun);
|
||||
fn.skip();
|
||||
},
|
||||
};
|
||||
return {
|
||||
name: 'react-forget-e2e',
|
||||
inherits: jsx,
|
||||
visitor,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
const ReactCompilerRuntime = require('react/compiler-runtime');
|
||||
|
||||
/*
|
||||
* Our e2e babel transform currently only compiles functions, not programs.
|
||||
* As a result, our e2e transpiled code does not contain an import for the
|
||||
* memo cache function. As a temporary hack, we add a `_c` global, which is
|
||||
* the name that is used for the import by default.
|
||||
*/
|
||||
globalThis._c = ReactCompilerRuntime.c;
|
||||
@@ -7,8 +7,9 @@
|
||||
|
||||
import {NodePath} from '@babel/core';
|
||||
import * as t from '@babel/types';
|
||||
import {PluginOptions} from './Options';
|
||||
import {CompilerError} from '../CompilerError';
|
||||
import {ProgramContext} from './Imports';
|
||||
import {ExternalFunction} from '..';
|
||||
|
||||
/**
|
||||
* Gating rewrite for function declarations which are referenced before their
|
||||
@@ -34,7 +35,8 @@ import {CompilerError} from '../CompilerError';
|
||||
function insertAdditionalFunctionDeclaration(
|
||||
fnPath: NodePath<t.FunctionDeclaration>,
|
||||
compiled: t.FunctionDeclaration,
|
||||
gating: NonNullable<PluginOptions['gating']>,
|
||||
programContext: ProgramContext,
|
||||
gatingFunctionIdentifierName: string,
|
||||
): void {
|
||||
const originalFnName = fnPath.node.id;
|
||||
const originalFnParams = fnPath.node.params;
|
||||
@@ -57,14 +59,14 @@ function insertAdditionalFunctionDeclaration(
|
||||
loc: fnPath.node.loc ?? null,
|
||||
});
|
||||
|
||||
const gatingCondition = fnPath.scope.generateUidIdentifier(
|
||||
`${gating.importSpecifierName}_result`,
|
||||
const gatingCondition = t.identifier(
|
||||
programContext.newUid(`${gatingFunctionIdentifierName}_result`),
|
||||
);
|
||||
const unoptimizedFnName = fnPath.scope.generateUidIdentifier(
|
||||
`${originalFnName.name}_unoptimized`,
|
||||
const unoptimizedFnName = t.identifier(
|
||||
programContext.newUid(`${originalFnName.name}_unoptimized`),
|
||||
);
|
||||
const optimizedFnName = fnPath.scope.generateUidIdentifier(
|
||||
`${originalFnName.name}_optimized`,
|
||||
const optimizedFnName = t.identifier(
|
||||
programContext.newUid(`${originalFnName.name}_optimized`),
|
||||
);
|
||||
/**
|
||||
* Step 1: rename existing functions
|
||||
@@ -115,7 +117,7 @@ function insertAdditionalFunctionDeclaration(
|
||||
t.variableDeclaration('const', [
|
||||
t.variableDeclarator(
|
||||
gatingCondition,
|
||||
t.callExpression(t.identifier(gating.importSpecifierName), []),
|
||||
t.callExpression(t.identifier(gatingFunctionIdentifierName), []),
|
||||
),
|
||||
]),
|
||||
);
|
||||
@@ -129,19 +131,26 @@ export function insertGatedFunctionDeclaration(
|
||||
| t.FunctionDeclaration
|
||||
| t.ArrowFunctionExpression
|
||||
| t.FunctionExpression,
|
||||
gating: NonNullable<PluginOptions['gating']>,
|
||||
programContext: ProgramContext,
|
||||
gating: ExternalFunction,
|
||||
referencedBeforeDeclaration: boolean,
|
||||
): void {
|
||||
const gatingImportedName = programContext.addImportSpecifier(gating).name;
|
||||
if (referencedBeforeDeclaration && fnPath.isFunctionDeclaration()) {
|
||||
CompilerError.invariant(compiled.type === 'FunctionDeclaration', {
|
||||
reason: 'Expected compiled node type to match input type',
|
||||
description: `Got ${compiled.type} but expected FunctionDeclaration`,
|
||||
loc: fnPath.node.loc ?? null,
|
||||
});
|
||||
insertAdditionalFunctionDeclaration(fnPath, compiled, gating);
|
||||
insertAdditionalFunctionDeclaration(
|
||||
fnPath,
|
||||
compiled,
|
||||
programContext,
|
||||
gatingImportedName,
|
||||
);
|
||||
} else {
|
||||
const gatingExpression = t.conditionalExpression(
|
||||
t.callExpression(t.identifier(gating.importSpecifierName), []),
|
||||
t.callExpression(t.identifier(gatingImportedName), []),
|
||||
buildFunctionExpression(compiled),
|
||||
buildFunctionExpression(fnPath.node),
|
||||
);
|
||||
|
||||
@@ -7,9 +7,19 @@
|
||||
|
||||
import {NodePath} from '@babel/core';
|
||||
import * as t from '@babel/types';
|
||||
import {Scope as BabelScope} from '@babel/traverse';
|
||||
|
||||
import {CompilerError, ErrorSeverity} from '../CompilerError';
|
||||
import {EnvironmentConfig, ExternalFunction, GeneratedSource} from '../HIR';
|
||||
import {getOrInsertDefault} from '../Utils/utils';
|
||||
import {
|
||||
EnvironmentConfig,
|
||||
GeneratedSource,
|
||||
NonLocalImportSpecifier,
|
||||
} from '../HIR';
|
||||
import {getOrInsertWith} from '../Utils/utils';
|
||||
import {ExternalFunction, isHookName} from '../HIR/Environment';
|
||||
import {Err, Ok, Result} from '../Utils/Result';
|
||||
import {CompilerReactTarget} from './Options';
|
||||
import {getReactCompilerRuntimeModule} from './Program';
|
||||
|
||||
export function validateRestrictedImports(
|
||||
path: NodePath<t.Program>,
|
||||
@@ -42,50 +52,209 @@ export function validateRestrictedImports(
|
||||
}
|
||||
}
|
||||
|
||||
export function addImportsToProgram(
|
||||
path: NodePath<t.Program>,
|
||||
importList: Array<ExternalFunction>,
|
||||
): void {
|
||||
const identifiers: Set<string> = new Set();
|
||||
const sortedImports: Map<string, Array<string>> = new Map();
|
||||
for (const {importSpecifierName, source} of importList) {
|
||||
/*
|
||||
* Codegen currently does not rename import specifiers, so we do additional
|
||||
* validation here
|
||||
*/
|
||||
CompilerError.invariant(identifiers.has(importSpecifierName) === false, {
|
||||
reason: `Encountered conflicting import specifier for ${importSpecifierName} in Forget config.`,
|
||||
description: null,
|
||||
loc: GeneratedSource,
|
||||
suggestions: null,
|
||||
});
|
||||
CompilerError.invariant(
|
||||
path.scope.hasBinding(importSpecifierName) === false,
|
||||
{
|
||||
reason: `Encountered conflicting import specifiers for ${importSpecifierName} in generated program.`,
|
||||
description: null,
|
||||
loc: GeneratedSource,
|
||||
suggestions: null,
|
||||
},
|
||||
);
|
||||
identifiers.add(importSpecifierName);
|
||||
export class ProgramContext {
|
||||
/* Program and environment context */
|
||||
scope: BabelScope;
|
||||
reactRuntimeModule: string;
|
||||
hookPattern: string | null;
|
||||
|
||||
const importSpecifierNameList = getOrInsertDefault(
|
||||
sortedImports,
|
||||
source,
|
||||
[],
|
||||
);
|
||||
importSpecifierNameList.push(importSpecifierName);
|
||||
// known generated or referenced identifiers in the program
|
||||
knownReferencedNames: Set<string> = new Set();
|
||||
// generated imports
|
||||
imports: Map<string, Map<string, NonLocalImportSpecifier>> = new Map();
|
||||
|
||||
constructor(
|
||||
program: NodePath<t.Program>,
|
||||
reactRuntimeModule: CompilerReactTarget,
|
||||
hookPattern: string | null,
|
||||
) {
|
||||
this.hookPattern = hookPattern;
|
||||
this.scope = program.scope;
|
||||
this.reactRuntimeModule = getReactCompilerRuntimeModule(reactRuntimeModule);
|
||||
}
|
||||
|
||||
isHookName(name: string): boolean {
|
||||
if (this.hookPattern == null) {
|
||||
return isHookName(name);
|
||||
} else {
|
||||
const match = new RegExp(this.hookPattern).exec(name);
|
||||
return (
|
||||
match != null && typeof match[1] === 'string' && isHookName(match[1])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
hasReference(name: string): boolean {
|
||||
return (
|
||||
this.knownReferencedNames.has(name) ||
|
||||
this.scope.hasBinding(name) ||
|
||||
this.scope.hasGlobal(name) ||
|
||||
this.scope.hasReference(name)
|
||||
);
|
||||
}
|
||||
|
||||
newUid(name: string): string {
|
||||
/**
|
||||
* Don't call babel's generateUid for known hook imports, as
|
||||
* InferTypes might eventually type `HookKind` based on callee naming
|
||||
* convention and `_useFoo` is not named as a hook.
|
||||
*
|
||||
* Local uid generation is susceptible to check-before-use bugs since we're
|
||||
* checking for naming conflicts / references long before we actually insert
|
||||
* the import. (see similar logic in HIRBuilder:resolveBinding)
|
||||
*/
|
||||
let uid;
|
||||
if (this.isHookName(name)) {
|
||||
uid = name;
|
||||
let i = 0;
|
||||
while (this.hasReference(uid)) {
|
||||
this.knownReferencedNames.add(uid);
|
||||
uid = `${name}_${i++}`;
|
||||
}
|
||||
} else if (!this.hasReference(name)) {
|
||||
uid = name;
|
||||
} else {
|
||||
uid = this.scope.generateUid(name);
|
||||
}
|
||||
this.knownReferencedNames.add(uid);
|
||||
return uid;
|
||||
}
|
||||
|
||||
addMemoCacheImport(): NonLocalImportSpecifier {
|
||||
return this.addImportSpecifier(
|
||||
{
|
||||
source: this.reactRuntimeModule,
|
||||
importSpecifierName: 'c',
|
||||
},
|
||||
'_c',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param externalFunction
|
||||
* @param nameHint if defined, will be used as the name of the import specifier
|
||||
* @returns
|
||||
*/
|
||||
addImportSpecifier(
|
||||
{source: module, importSpecifierName: specifier}: ExternalFunction,
|
||||
nameHint?: string,
|
||||
): NonLocalImportSpecifier {
|
||||
const maybeBinding = this.imports.get(module)?.get(specifier);
|
||||
if (maybeBinding != null) {
|
||||
return {...maybeBinding};
|
||||
}
|
||||
|
||||
const binding: NonLocalImportSpecifier = {
|
||||
kind: 'ImportSpecifier',
|
||||
name: this.newUid(nameHint ?? specifier),
|
||||
module,
|
||||
imported: specifier,
|
||||
};
|
||||
getOrInsertWith(this.imports, module, () => new Map()).set(specifier, {
|
||||
...binding,
|
||||
});
|
||||
return binding;
|
||||
}
|
||||
|
||||
addNewReference(name: string): void {
|
||||
this.knownReferencedNames.add(name);
|
||||
}
|
||||
|
||||
assertGlobalBinding(
|
||||
name: string,
|
||||
localScope?: BabelScope,
|
||||
): Result<void, CompilerError> {
|
||||
const scope = localScope ?? this.scope;
|
||||
if (!scope.hasReference(name) && !scope.hasBinding(name)) {
|
||||
return Ok(undefined);
|
||||
}
|
||||
const error = new CompilerError();
|
||||
error.push({
|
||||
severity: ErrorSeverity.Todo,
|
||||
reason: 'Encountered conflicting global in generated program',
|
||||
description: `Conflict from local binding ${name}`,
|
||||
loc: scope.getBinding(name)?.path.node.loc ?? null,
|
||||
suggestions: null,
|
||||
});
|
||||
return Err(error);
|
||||
}
|
||||
}
|
||||
|
||||
function getExistingImports(
|
||||
program: NodePath<t.Program>,
|
||||
): Map<string, NodePath<t.ImportDeclaration>> {
|
||||
const existingImports = new Map<string, NodePath<t.ImportDeclaration>>();
|
||||
program.traverse({
|
||||
ImportDeclaration(path) {
|
||||
if (isNonNamespacedImport(path)) {
|
||||
existingImports.set(path.node.source.value, path);
|
||||
}
|
||||
},
|
||||
});
|
||||
return existingImports;
|
||||
}
|
||||
|
||||
export function addImportsToProgram(
|
||||
path: NodePath<t.Program>,
|
||||
programContext: ProgramContext,
|
||||
): void {
|
||||
const existingImports = getExistingImports(path);
|
||||
const stmts: Array<t.ImportDeclaration> = [];
|
||||
for (const [source, importSpecifierNameList] of sortedImports) {
|
||||
const importSpecifiers = importSpecifierNameList.map(name => {
|
||||
const id = t.identifier(name);
|
||||
return t.importSpecifier(id, id);
|
||||
const sortedModules = [...programContext.imports.entries()].sort(([a], [b]) =>
|
||||
a.localeCompare(b),
|
||||
);
|
||||
for (const [moduleName, importsMap] of sortedModules) {
|
||||
for (const [specifierName, loweredImport] of importsMap) {
|
||||
/**
|
||||
* Assert that the import identifier hasn't already be declared in the program.
|
||||
* Note: we use getBinding here since `Scope.hasBinding` pessimistically returns true
|
||||
* for all allocated uids (from `Scope.getUid`)
|
||||
*/
|
||||
CompilerError.invariant(
|
||||
path.scope.getBinding(loweredImport.name) == null,
|
||||
{
|
||||
reason:
|
||||
'Encountered conflicting import specifiers in generated program',
|
||||
description: `Conflict from import ${loweredImport.module}:(${loweredImport.imported} as ${loweredImport.name}).`,
|
||||
loc: GeneratedSource,
|
||||
suggestions: null,
|
||||
},
|
||||
);
|
||||
CompilerError.invariant(
|
||||
loweredImport.module === moduleName &&
|
||||
loweredImport.imported === specifierName,
|
||||
{
|
||||
reason:
|
||||
'Found inconsistent import specifier. This is an internal bug.',
|
||||
description: `Expected import ${moduleName}:${specifierName} but found ${loweredImport.module}:${loweredImport.imported}`,
|
||||
loc: GeneratedSource,
|
||||
},
|
||||
);
|
||||
}
|
||||
const sortedImport: Array<NonLocalImportSpecifier> = [
|
||||
...importsMap.values(),
|
||||
].sort(({imported: a}, {imported: b}) => a.localeCompare(b));
|
||||
const importSpecifiers = sortedImport.map(specifier => {
|
||||
return t.importSpecifier(
|
||||
t.identifier(specifier.name),
|
||||
t.identifier(specifier.imported),
|
||||
);
|
||||
});
|
||||
|
||||
stmts.push(t.importDeclaration(importSpecifiers, t.stringLiteral(source)));
|
||||
/**
|
||||
* If an existing import of this module exists (ie `import { ... } from
|
||||
* '<moduleName>'`), inject new imported specifiers into the list of
|
||||
* destructured variables.
|
||||
*/
|
||||
const maybeExistingImports = existingImports.get(moduleName);
|
||||
if (maybeExistingImports != null) {
|
||||
maybeExistingImports.pushContainer('specifiers', importSpecifiers);
|
||||
} else {
|
||||
stmts.push(
|
||||
t.importDeclaration(importSpecifiers, t.stringLiteral(moduleName)),
|
||||
);
|
||||
}
|
||||
}
|
||||
path.unshiftContainer('body', stmts);
|
||||
}
|
||||
@@ -93,13 +262,12 @@ export function addImportsToProgram(
|
||||
/*
|
||||
* Matches `import { ... } from <moduleName>;`
|
||||
* but not `import * as React from <moduleName>;`
|
||||
* `import type { Foo } from <moduleName>;`
|
||||
*/
|
||||
function isNonNamespacedImport(
|
||||
importDeclPath: NodePath<t.ImportDeclaration>,
|
||||
moduleName: string,
|
||||
): boolean {
|
||||
return (
|
||||
importDeclPath.get('source').node.value === moduleName &&
|
||||
importDeclPath
|
||||
.get('specifiers')
|
||||
.every(specifier => specifier.isImportSpecifier()) &&
|
||||
@@ -107,94 +275,3 @@ function isNonNamespacedImport(
|
||||
importDeclPath.node.importKind !== 'typeof'
|
||||
);
|
||||
}
|
||||
|
||||
function hasExistingNonNamespacedImportOfModule(
|
||||
program: NodePath<t.Program>,
|
||||
moduleName: string,
|
||||
): boolean {
|
||||
let hasExistingImport = false;
|
||||
program.traverse({
|
||||
ImportDeclaration(importDeclPath) {
|
||||
if (isNonNamespacedImport(importDeclPath, moduleName)) {
|
||||
hasExistingImport = true;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return hasExistingImport;
|
||||
}
|
||||
|
||||
/*
|
||||
* If an existing import of React exists (ie `import { ... } from '<moduleName>'`), inject useMemoCache
|
||||
* into the list of destructured variables.
|
||||
*/
|
||||
function addMemoCacheFunctionSpecifierToExistingImport(
|
||||
program: NodePath<t.Program>,
|
||||
moduleName: string,
|
||||
identifierName: string,
|
||||
): boolean {
|
||||
let didInsertUseMemoCache = false;
|
||||
program.traverse({
|
||||
ImportDeclaration(importDeclPath) {
|
||||
if (
|
||||
!didInsertUseMemoCache &&
|
||||
isNonNamespacedImport(importDeclPath, moduleName)
|
||||
) {
|
||||
importDeclPath.pushContainer(
|
||||
'specifiers',
|
||||
t.importSpecifier(t.identifier(identifierName), t.identifier('c')),
|
||||
);
|
||||
didInsertUseMemoCache = true;
|
||||
}
|
||||
},
|
||||
});
|
||||
return didInsertUseMemoCache;
|
||||
}
|
||||
|
||||
export function updateMemoCacheFunctionImport(
|
||||
program: NodePath<t.Program>,
|
||||
moduleName: string,
|
||||
useMemoCacheIdentifier: string,
|
||||
): void {
|
||||
/*
|
||||
* If there isn't already an import of * as React, insert it so useMemoCache doesn't
|
||||
* throw
|
||||
*/
|
||||
const hasExistingImport = hasExistingNonNamespacedImportOfModule(
|
||||
program,
|
||||
moduleName,
|
||||
);
|
||||
|
||||
if (hasExistingImport) {
|
||||
const didUpdateImport = addMemoCacheFunctionSpecifierToExistingImport(
|
||||
program,
|
||||
moduleName,
|
||||
useMemoCacheIdentifier,
|
||||
);
|
||||
if (!didUpdateImport) {
|
||||
throw new Error(
|
||||
`Expected an ImportDeclaration of \`${moduleName}\` in order to update ImportSpecifiers with useMemoCache`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
addMemoCacheFunctionImportDeclaration(
|
||||
program,
|
||||
moduleName,
|
||||
useMemoCacheIdentifier,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function addMemoCacheFunctionImportDeclaration(
|
||||
program: NodePath<t.Program>,
|
||||
moduleName: string,
|
||||
localName: string,
|
||||
): void {
|
||||
program.unshiftContainer(
|
||||
'body',
|
||||
t.importDeclaration(
|
||||
[t.importSpecifier(t.identifier(localName), t.identifier('c'))],
|
||||
t.stringLiteral(moduleName),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import {NodePath} from '@babel/traverse';
|
||||
import * as t from '@babel/types';
|
||||
import prettyFormat from 'pretty-format';
|
||||
import {Logger} from '.';
|
||||
import {Logger, ProgramContext} from '.';
|
||||
import {
|
||||
HIRFunction,
|
||||
ReactiveFunction,
|
||||
@@ -117,7 +117,7 @@ function run(
|
||||
config: EnvironmentConfig,
|
||||
fnType: ReactFunctionType,
|
||||
mode: CompilerMode,
|
||||
useMemoCacheIdentifier: string,
|
||||
programContext: ProgramContext,
|
||||
logger: Logger | null,
|
||||
filename: string | null,
|
||||
code: string | null,
|
||||
@@ -132,7 +132,7 @@ function run(
|
||||
logger,
|
||||
filename,
|
||||
code,
|
||||
useMemoCacheIdentifier,
|
||||
programContext,
|
||||
);
|
||||
env.logger?.debugLogIRs?.({
|
||||
kind: 'debug',
|
||||
@@ -552,7 +552,7 @@ export function compileFn(
|
||||
config: EnvironmentConfig,
|
||||
fnType: ReactFunctionType,
|
||||
mode: CompilerMode,
|
||||
useMemoCacheIdentifier: string,
|
||||
programContext: ProgramContext,
|
||||
logger: Logger | null,
|
||||
filename: string | null,
|
||||
code: string | null,
|
||||
@@ -562,7 +562,7 @@ export function compileFn(
|
||||
config,
|
||||
fnType,
|
||||
mode,
|
||||
useMemoCacheIdentifier,
|
||||
programContext,
|
||||
logger,
|
||||
filename,
|
||||
code,
|
||||
|
||||
@@ -12,11 +12,7 @@ import {
|
||||
CompilerErrorDetail,
|
||||
ErrorSeverity,
|
||||
} from '../CompilerError';
|
||||
import {
|
||||
EnvironmentConfig,
|
||||
ExternalFunction,
|
||||
ReactFunctionType,
|
||||
} from '../HIR/Environment';
|
||||
import {EnvironmentConfig, ReactFunctionType} from '../HIR/Environment';
|
||||
import {CodegenFunction} from '../ReactiveScopes';
|
||||
import {isComponentDeclaration} from '../Utils/ComponentDeclaration';
|
||||
import {isHookDeclaration} from '../Utils/HookDeclaration';
|
||||
@@ -24,10 +20,10 @@ import {assertExhaustive} from '../Utils/utils';
|
||||
import {insertGatedFunctionDeclaration} from './Gating';
|
||||
import {
|
||||
addImportsToProgram,
|
||||
updateMemoCacheFunctionImport,
|
||||
ProgramContext,
|
||||
validateRestrictedImports,
|
||||
} from './Imports';
|
||||
import {PluginOptions} from './Options';
|
||||
import {CompilerReactTarget, PluginOptions} from './Options';
|
||||
import {compileFn} from './Pipeline';
|
||||
import {
|
||||
filterSuppressionsThatAffectFunction,
|
||||
@@ -299,8 +295,12 @@ export function compileProgram(
|
||||
handleError(restrictedImportsErr, pass, null);
|
||||
return null;
|
||||
}
|
||||
const useMemoCacheIdentifier = program.scope.generateUidIdentifier('c');
|
||||
|
||||
const programContext = new ProgramContext(
|
||||
program,
|
||||
pass.opts.target,
|
||||
environment.hookPattern,
|
||||
);
|
||||
/*
|
||||
* Record lint errors and critical errors as depending on Forget's config,
|
||||
* we may still need to run Forget's analysis on every function (even if we
|
||||
@@ -410,7 +410,7 @@ export function compileProgram(
|
||||
environment,
|
||||
fnType,
|
||||
'all_features',
|
||||
useMemoCacheIdentifier.name,
|
||||
programContext,
|
||||
pass.opts.logger,
|
||||
pass.filename,
|
||||
pass.code,
|
||||
@@ -445,7 +445,7 @@ export function compileProgram(
|
||||
environment,
|
||||
fnType,
|
||||
'no_inferred_memo',
|
||||
useMemoCacheIdentifier.name,
|
||||
programContext,
|
||||
pass.opts.logger,
|
||||
pass.filename,
|
||||
pass.code,
|
||||
@@ -453,7 +453,7 @@ export function compileProgram(
|
||||
};
|
||||
if (
|
||||
!compileResult.compiledFn.hasFireRewrite &&
|
||||
!compileResult.compiledFn.hasLoweredContextAccess
|
||||
!compileResult.compiledFn.hasInferredEffect
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
@@ -554,79 +554,29 @@ export function compileProgram(
|
||||
if (moduleScopeOptOutDirectives.length > 0) {
|
||||
return null;
|
||||
}
|
||||
let gating: null | {
|
||||
gatingFn: ExternalFunction;
|
||||
referencedBeforeDeclared: Set<CompileResult>;
|
||||
} = null;
|
||||
if (pass.opts.gating != null) {
|
||||
gating = {
|
||||
gatingFn: pass.opts.gating,
|
||||
referencedBeforeDeclared:
|
||||
getFunctionReferencedBeforeDeclarationAtTopLevel(program, compiledFns),
|
||||
};
|
||||
}
|
||||
|
||||
const hasLoweredContextAccess = compiledFns.some(
|
||||
c => c.compiledFn.hasLoweredContextAccess,
|
||||
);
|
||||
const externalFunctions: Array<ExternalFunction> = [];
|
||||
try {
|
||||
// TODO: check for duplicate import specifiers
|
||||
if (gating != null) {
|
||||
externalFunctions.push(gating.gatingFn);
|
||||
}
|
||||
|
||||
const lowerContextAccess = environment.lowerContextAccess;
|
||||
if (lowerContextAccess && hasLoweredContextAccess) {
|
||||
externalFunctions.push(lowerContextAccess);
|
||||
}
|
||||
|
||||
const enableEmitInstrumentForget = environment.enableEmitInstrumentForget;
|
||||
if (enableEmitInstrumentForget != null) {
|
||||
externalFunctions.push(enableEmitInstrumentForget.fn);
|
||||
if (enableEmitInstrumentForget.gating != null) {
|
||||
externalFunctions.push(enableEmitInstrumentForget.gating);
|
||||
}
|
||||
}
|
||||
|
||||
if (environment.enableEmitFreeze != null) {
|
||||
externalFunctions.push(environment.enableEmitFreeze);
|
||||
}
|
||||
|
||||
if (environment.enableEmitHookGuards != null) {
|
||||
externalFunctions.push(environment.enableEmitHookGuards);
|
||||
}
|
||||
|
||||
if (environment.enableChangeDetectionForDebugging != null) {
|
||||
externalFunctions.push(environment.enableChangeDetectionForDebugging);
|
||||
}
|
||||
|
||||
const hasFireRewrite = compiledFns.some(c => c.compiledFn.hasFireRewrite);
|
||||
if (environment.enableFire && hasFireRewrite) {
|
||||
externalFunctions.push({
|
||||
source: getReactCompilerRuntimeModule(pass.opts),
|
||||
importSpecifierName: 'useFire',
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
handleError(err, pass, null);
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* Only insert Forget-ified functions if we have not encountered a critical
|
||||
* error elsewhere in the file, regardless of bailout mode.
|
||||
*/
|
||||
const referencedBeforeDeclared =
|
||||
pass.opts.gating != null
|
||||
? getFunctionReferencedBeforeDeclarationAtTopLevel(program, compiledFns)
|
||||
: null;
|
||||
for (const result of compiledFns) {
|
||||
const {kind, originalFn, compiledFn} = result;
|
||||
const transformedFn = createNewFunctionNode(originalFn, compiledFn);
|
||||
|
||||
if (gating != null && kind === 'original') {
|
||||
if (referencedBeforeDeclared != null && kind === 'original') {
|
||||
CompilerError.invariant(pass.opts.gating != null, {
|
||||
reason: "Expected 'gating' import to be present",
|
||||
loc: null,
|
||||
});
|
||||
insertGatedFunctionDeclaration(
|
||||
originalFn,
|
||||
transformedFn,
|
||||
gating.gatingFn,
|
||||
gating.referencedBeforeDeclared.has(result),
|
||||
programContext,
|
||||
pass.opts.gating,
|
||||
referencedBeforeDeclared.has(result),
|
||||
);
|
||||
} else {
|
||||
originalFn.replaceWith(transformedFn);
|
||||
@@ -635,22 +585,7 @@ export function compileProgram(
|
||||
|
||||
// Forget compiled the component, we need to update existing imports of useMemoCache
|
||||
if (compiledFns.length > 0) {
|
||||
let needsMemoCacheFunctionImport = false;
|
||||
for (const fn of compiledFns) {
|
||||
if (fn.compiledFn.memoSlotsUsed > 0) {
|
||||
needsMemoCacheFunctionImport = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (needsMemoCacheFunctionImport) {
|
||||
updateMemoCacheFunctionImport(
|
||||
program,
|
||||
getReactCompilerRuntimeModule(pass.opts),
|
||||
useMemoCacheIdentifier.name,
|
||||
);
|
||||
}
|
||||
addImportsToProgram(program, externalFunctions);
|
||||
addImportsToProgram(program, programContext);
|
||||
}
|
||||
return {retryErrors};
|
||||
}
|
||||
@@ -683,7 +618,7 @@ function shouldSkipCompilation(
|
||||
if (
|
||||
hasMemoCacheFunctionImport(
|
||||
program,
|
||||
getReactCompilerRuntimeModule(pass.opts),
|
||||
getReactCompilerRuntimeModule(pass.opts.target),
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
@@ -1008,31 +943,39 @@ function callsHooksOrCreatesJsx(
|
||||
return invokesHooks || createsJsx;
|
||||
}
|
||||
|
||||
function isNonNode(node?: t.Expression | null): boolean {
|
||||
if (!node) {
|
||||
return true;
|
||||
}
|
||||
switch (node.type) {
|
||||
case 'ObjectExpression':
|
||||
case 'ArrowFunctionExpression':
|
||||
case 'FunctionExpression':
|
||||
case 'BigIntLiteral':
|
||||
case 'ClassExpression':
|
||||
case 'NewExpression': // technically `new Array()` is legit, but unlikely
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function returnsNonNode(
|
||||
node: NodePath<
|
||||
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
|
||||
>,
|
||||
): boolean {
|
||||
let hasReturn = false;
|
||||
let returnsNonNode = false;
|
||||
if (
|
||||
// node.traverse#ArrowFunctionExpression isn't called for the root node
|
||||
node.type === 'ArrowFunctionExpression' &&
|
||||
node.node.body.type !== 'BlockStatement'
|
||||
) {
|
||||
returnsNonNode = isNonNode(node.node.body);
|
||||
}
|
||||
|
||||
node.traverse({
|
||||
ReturnStatement(ret) {
|
||||
hasReturn = true;
|
||||
const argument = ret.node.argument;
|
||||
if (argument == null) {
|
||||
returnsNonNode = true;
|
||||
} else {
|
||||
switch (argument.type) {
|
||||
case 'ObjectExpression':
|
||||
case 'ArrowFunctionExpression':
|
||||
case 'FunctionExpression':
|
||||
case 'BigIntLiteral':
|
||||
case 'ClassExpression':
|
||||
case 'NewExpression': // technically `new Array()` is legit, but unlikely
|
||||
returnsNonNode = true;
|
||||
}
|
||||
}
|
||||
returnsNonNode = isNonNode(ret.node.argument);
|
||||
},
|
||||
// Skip traversing all nested functions and their return statements
|
||||
ArrowFunctionExpression: skipNestedFunctions(node),
|
||||
@@ -1041,7 +984,7 @@ function returnsNonNode(
|
||||
ObjectMethod: node => node.skip(),
|
||||
});
|
||||
|
||||
return !hasReturn || returnsNonNode;
|
||||
return returnsNonNode;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1169,16 +1112,18 @@ function getFunctionReferencedBeforeDeclarationAtTopLevel(
|
||||
return referencedBeforeDeclaration;
|
||||
}
|
||||
|
||||
function getReactCompilerRuntimeModule(opts: PluginOptions): string {
|
||||
if (opts.target === '19') {
|
||||
export function getReactCompilerRuntimeModule(
|
||||
target: CompilerReactTarget,
|
||||
): string {
|
||||
if (target === '19') {
|
||||
return 'react/compiler-runtime'; // from react namespace
|
||||
} else if (opts.target === '17' || opts.target === '18') {
|
||||
} else if (target === '17' || target === '18') {
|
||||
return 'react-compiler-runtime'; // npm package
|
||||
} else {
|
||||
CompilerError.invariant(
|
||||
opts.target != null &&
|
||||
opts.target.kind === 'donotuse_meta_internal' &&
|
||||
typeof opts.target.runtimeModule === 'string',
|
||||
target != null &&
|
||||
target.kind === 'donotuse_meta_internal' &&
|
||||
typeof target.runtimeModule === 'string',
|
||||
{
|
||||
reason: 'Expected target to already be validated',
|
||||
description: null,
|
||||
@@ -1186,6 +1131,6 @@ function getReactCompilerRuntimeModule(opts: PluginOptions): string {
|
||||
suggestions: null,
|
||||
},
|
||||
);
|
||||
return opts.target.runtimeModule;
|
||||
return target.runtimeModule;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
PanicThresholdOptions,
|
||||
parsePluginOptions,
|
||||
PluginOptions,
|
||||
ProgramContext,
|
||||
} from '../Entrypoint';
|
||||
import {Err, Ok, Result} from '../Utils/Result';
|
||||
import {
|
||||
@@ -84,6 +85,8 @@ export const InstrumentationSchema = z
|
||||
);
|
||||
|
||||
export type ExternalFunction = z.infer<typeof ExternalFunctionSchema>;
|
||||
export const USE_FIRE_FUNCTION_NAME = 'useFire';
|
||||
export const EMIT_FREEZE_GLOBAL_GATING = '__DEV__';
|
||||
|
||||
export const MacroMethodSchema = z.union([
|
||||
z.object({type: z.literal('wildcard')}),
|
||||
@@ -846,9 +849,9 @@ export class Environment {
|
||||
config: EnvironmentConfig;
|
||||
fnType: ReactFunctionType;
|
||||
compilerMode: CompilerMode;
|
||||
useMemoCacheIdentifier: string;
|
||||
hasLoweredContextAccess: boolean;
|
||||
programContext: ProgramContext;
|
||||
hasFireRewrite: boolean;
|
||||
hasInferredEffect: boolean;
|
||||
|
||||
#contextIdentifiers: Set<t.Identifier>;
|
||||
#hoistedIdentifiers: Set<t.Identifier>;
|
||||
@@ -862,7 +865,7 @@ export class Environment {
|
||||
logger: Logger | null,
|
||||
filename: string | null,
|
||||
code: string | null,
|
||||
useMemoCacheIdentifier: string,
|
||||
programContext: ProgramContext,
|
||||
) {
|
||||
this.#scope = scope;
|
||||
this.fnType = fnType;
|
||||
@@ -871,11 +874,11 @@ export class Environment {
|
||||
this.filename = filename;
|
||||
this.code = code;
|
||||
this.logger = logger;
|
||||
this.useMemoCacheIdentifier = useMemoCacheIdentifier;
|
||||
this.programContext = programContext;
|
||||
this.#shapes = new Map(DEFAULT_SHAPES);
|
||||
this.#globals = new Map(DEFAULT_GLOBALS);
|
||||
this.hasLoweredContextAccess = false;
|
||||
this.hasFireRewrite = false;
|
||||
this.hasInferredEffect = false;
|
||||
|
||||
if (
|
||||
config.disableMemoizationForDebugging &&
|
||||
@@ -937,6 +940,10 @@ export class Environment {
|
||||
return makeScopeId(this.#nextScope++);
|
||||
}
|
||||
|
||||
get scope(): BabelScope {
|
||||
return this.#scope;
|
||||
}
|
||||
|
||||
logErrors(errors: Result<void, CompilerError>): void {
|
||||
if (errors.isOk() || this.logger == null) {
|
||||
return;
|
||||
|
||||
@@ -10,7 +10,10 @@ import {
|
||||
BUILTIN_SHAPES,
|
||||
BuiltInArrayId,
|
||||
BuiltInFireId,
|
||||
BuiltInMapId,
|
||||
BuiltInMixedReadonlyId,
|
||||
BuiltInObjectId,
|
||||
BuiltInSetId,
|
||||
BuiltInUseActionStateId,
|
||||
BuiltInUseContextHookId,
|
||||
BuiltInUseEffectHookId,
|
||||
@@ -45,29 +48,23 @@ 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',
|
||||
@@ -75,16 +72,8 @@ 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]> = [
|
||||
@@ -101,6 +90,23 @@ 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,
|
||||
}),
|
||||
],
|
||||
]),
|
||||
],
|
||||
[
|
||||
@@ -132,7 +138,7 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
|
||||
'from',
|
||||
addFunction(DEFAULT_SHAPES, [], {
|
||||
positionalParams: [
|
||||
Effect.ConditionallyMutate,
|
||||
Effect.ConditionallyMutateIterator,
|
||||
Effect.ConditionallyMutate,
|
||||
Effect.ConditionallyMutate,
|
||||
],
|
||||
@@ -372,6 +378,118 @@ 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
|
||||
];
|
||||
|
||||
|
||||
@@ -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 {HookKind} from './ObjectShape';
|
||||
import type {HookKind} from './ObjectShape';
|
||||
import {Type, makeType} from './Types';
|
||||
import {z} from 'zod';
|
||||
|
||||
@@ -829,6 +829,13 @@ 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;
|
||||
@@ -894,12 +901,7 @@ export type InstructionValue =
|
||||
right: Place;
|
||||
loc: SourceLocation;
|
||||
}
|
||||
| {
|
||||
kind: 'NewExpression';
|
||||
callee: Place;
|
||||
args: Array<Place | SpreadPattern>;
|
||||
loc: SourceLocation;
|
||||
}
|
||||
| NewExpression
|
||||
| CallExpression
|
||||
| MethodCall
|
||||
| {
|
||||
@@ -1165,18 +1167,21 @@ export type VariableBinding =
|
||||
// bindings declard outside the current component/hook
|
||||
| NonLocalBinding;
|
||||
|
||||
// `import {bar as baz} from 'foo'`: name=baz, module=foo, imported=bar
|
||||
export type NonLocalImportSpecifier = {
|
||||
kind: 'ImportSpecifier';
|
||||
name: string;
|
||||
module: string;
|
||||
imported: string;
|
||||
};
|
||||
|
||||
export type NonLocalBinding =
|
||||
// `import Foo from 'foo'`: name=Foo, module=foo
|
||||
| {kind: 'ImportDefault'; name: string; module: string}
|
||||
// `import * as Foo from 'foo'`: name=Foo, module=foo
|
||||
| {kind: 'ImportNamespace'; name: string; module: string}
|
||||
// `import {bar as baz} from 'foo'`: name=baz, module=foo, imported=bar
|
||||
| {
|
||||
kind: 'ImportSpecifier';
|
||||
name: string;
|
||||
module: string;
|
||||
imported: string;
|
||||
}
|
||||
// `import {bar as baz} from 'foo'`
|
||||
| NonLocalImportSpecifier
|
||||
// let, const, function, etc declared in the module but outside the current component/hook
|
||||
| {kind: 'ModuleLocal'; name: string}
|
||||
// an unresolved binding
|
||||
@@ -1394,6 +1399,7 @@ 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
|
||||
@@ -1412,11 +1418,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,
|
||||
@@ -1430,6 +1436,7 @@ export function isMutableEffect(
|
||||
case Effect.Capture:
|
||||
case Effect.Store:
|
||||
case Effect.ConditionallyMutate:
|
||||
case Effect.ConditionallyMutateIterator:
|
||||
case Effect.Mutate: {
|
||||
return true;
|
||||
}
|
||||
@@ -1649,6 +1656,14 @@ 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';
|
||||
}
|
||||
|
||||
@@ -331,6 +331,7 @@ export default class HIRBuilder {
|
||||
type: makeType(),
|
||||
loc: node.loc ?? GeneratedSource,
|
||||
};
|
||||
this.#env.programContext.addNewReference(name);
|
||||
this.#bindings.set(name, {node, identifier});
|
||||
return identifier;
|
||||
} else if (mapping.node === node) {
|
||||
|
||||
@@ -44,6 +44,7 @@ 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, {
|
||||
@@ -54,6 +55,7 @@ export function addFunction(
|
||||
kind: 'Function',
|
||||
return: fn.returnType,
|
||||
shapeId,
|
||||
isConstructor,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -73,6 +75,7 @@ export function addHook(
|
||||
kind: 'Function',
|
||||
return: fn.returnType,
|
||||
shapeId,
|
||||
isConstructor: false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -198,6 +201,8 @@ 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';
|
||||
@@ -451,6 +456,313 @@ 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'}],
|
||||
[
|
||||
|
||||
@@ -38,6 +38,7 @@ export type FunctionType = {
|
||||
kind: 'Function';
|
||||
shapeId: string | null;
|
||||
return: Type;
|
||||
isConstructor: boolean;
|
||||
};
|
||||
|
||||
export type ObjectType = {
|
||||
@@ -111,6 +112,7 @@ export function duplicateType(type: Type): Type {
|
||||
kind: 'Function',
|
||||
return: duplicateType(type.return),
|
||||
shapeId: type.shapeId,
|
||||
isConstructor: type.isConstructor,
|
||||
};
|
||||
}
|
||||
case 'Object': {
|
||||
|
||||
@@ -249,6 +249,7 @@ export function inferEffectDependencies(fn: HIRFunction): void {
|
||||
// Renumber instructions and fix scope ranges
|
||||
markInstructionIds(fn.body);
|
||||
fixScopeAndIdentifierRanges(fn.body);
|
||||
fn.env.hasInferredEffect = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,10 @@ import {
|
||||
Identifier,
|
||||
InstructionId,
|
||||
InstructionKind,
|
||||
isArrayType,
|
||||
isMapType,
|
||||
isRefOrRefValue,
|
||||
isSetType,
|
||||
makeInstructionId,
|
||||
Place,
|
||||
} from '../HIR/HIR';
|
||||
@@ -90,6 +93,17 @@ 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);
|
||||
|
||||
@@ -230,6 +230,7 @@ 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);
|
||||
|
||||
+215
-200
@@ -12,6 +12,7 @@ import {
|
||||
BasicBlock,
|
||||
BlockId,
|
||||
CallExpression,
|
||||
NewExpression,
|
||||
Effect,
|
||||
FunctionEffect,
|
||||
GeneratedSource,
|
||||
@@ -23,12 +24,15 @@ import {
|
||||
Phi,
|
||||
Place,
|
||||
SpreadPattern,
|
||||
TInstruction,
|
||||
Type,
|
||||
ValueKind,
|
||||
ValueReason,
|
||||
isArrayType,
|
||||
isMapType,
|
||||
isMutableEffect,
|
||||
isObjectType,
|
||||
isSetType,
|
||||
} from '../HIR/HIR';
|
||||
import {FunctionSignature} from '../HIR/ObjectShape';
|
||||
import {
|
||||
@@ -38,7 +42,6 @@ import {
|
||||
printSourceLocation,
|
||||
} from '../HIR/PrintHIR';
|
||||
import {
|
||||
eachCallArgument,
|
||||
eachInstructionOperand,
|
||||
eachInstructionValueOperand,
|
||||
eachPatternOperand,
|
||||
@@ -251,7 +254,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>;
|
||||
@@ -267,7 +270,7 @@ class InferenceState {
|
||||
values: Map<InstructionValue, AbstractValue>,
|
||||
variables: Map<IdentifierId, Set<InstructionValue>>,
|
||||
) {
|
||||
this.#env = env;
|
||||
this.env = env;
|
||||
this.#values = values;
|
||||
this.#variables = variables;
|
||||
}
|
||||
@@ -409,8 +412,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);
|
||||
@@ -468,6 +471,25 @@ 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;
|
||||
@@ -590,7 +612,7 @@ class InferenceState {
|
||||
return null;
|
||||
} else {
|
||||
return new InferenceState(
|
||||
this.#env,
|
||||
this.env,
|
||||
nextValues ?? new Map(this.#values),
|
||||
nextVariables ?? new Map(this.#variables),
|
||||
);
|
||||
@@ -604,7 +626,7 @@ class InferenceState {
|
||||
*/
|
||||
clone(): InferenceState {
|
||||
return new InferenceState(
|
||||
this.#env,
|
||||
this.env,
|
||||
new Map(this.#values),
|
||||
new Map(this.#variables),
|
||||
);
|
||||
@@ -879,9 +901,7 @@ function inferBlock(
|
||||
state.referenceAndRecordEffects(
|
||||
freezeActions,
|
||||
element.place,
|
||||
isArrayType(element.place.identifier)
|
||||
? Effect.Capture
|
||||
: Effect.ConditionallyMutate,
|
||||
Effect.ConditionallyMutateIterator,
|
||||
ValueReason.Other,
|
||||
);
|
||||
} else if (element.kind === 'Identifier') {
|
||||
@@ -904,43 +924,12 @@ function inferBlock(
|
||||
break;
|
||||
}
|
||||
case '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(
|
||||
inferCallEffects(
|
||||
state,
|
||||
instr as TInstruction<NewExpression>,
|
||||
freezeActions,
|
||||
instrValue.callee,
|
||||
Effect.Read,
|
||||
ValueReason.Other,
|
||||
getFunctionCallSignature(env, instrValue.callee.identifier.type),
|
||||
);
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -1238,62 +1227,12 @@ function inferBlock(
|
||||
break;
|
||||
}
|
||||
case 'CallExpression': {
|
||||
const signature = getFunctionCallSignature(
|
||||
env,
|
||||
instrValue.callee.identifier.type,
|
||||
inferCallEffects(
|
||||
state,
|
||||
instr as TInstruction<CallExpression>,
|
||||
freezeActions,
|
||||
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;
|
||||
}
|
||||
@@ -1311,102 +1250,12 @@ function inferBlock(
|
||||
Effect.Read,
|
||||
ValueReason.Other,
|
||||
);
|
||||
|
||||
const signature = getFunctionCallSignature(
|
||||
env,
|
||||
instrValue.property.identifier.type,
|
||||
inferCallEffects(
|
||||
state,
|
||||
instr as TInstruction<MethodCall>,
|
||||
freezeActions,
|
||||
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;
|
||||
}
|
||||
@@ -1813,7 +1662,13 @@ function inferBlock(
|
||||
kind === ValueKind.Mutable || kind === ValueKind.Context;
|
||||
let effect;
|
||||
let valueKind: AbstractValue;
|
||||
if (!isMutable || isArrayType(instrValue.collection.identifier)) {
|
||||
const iterator = instrValue.collection.identifier;
|
||||
if (
|
||||
!isMutable ||
|
||||
isArrayType(iterator) ||
|
||||
isMapType(iterator) ||
|
||||
isSetType(iterator)
|
||||
) {
|
||||
// Case 1, assume iterator is a separate mutable object
|
||||
effect = {
|
||||
kind: Effect.Read,
|
||||
@@ -1854,7 +1709,7 @@ function inferBlock(
|
||||
state.referenceAndRecordEffects(
|
||||
freezeActions,
|
||||
instrValue.iterator,
|
||||
Effect.ConditionallyMutate,
|
||||
Effect.ConditionallyMutateIterator,
|
||||
ValueReason.Other,
|
||||
);
|
||||
/**
|
||||
@@ -1983,7 +1838,7 @@ export function getFunctionCallSignature(
|
||||
* @returns Inferred effects of function arguments, or null if inference fails.
|
||||
*/
|
||||
export function getFunctionEffects(
|
||||
fn: MethodCall | CallExpression,
|
||||
fn: MethodCall | CallExpression | NewExpression,
|
||||
sig: FunctionSignature,
|
||||
): Array<Effect> | null {
|
||||
const results = [];
|
||||
@@ -2012,6 +1867,33 @@ 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
|
||||
@@ -2023,10 +1905,20 @@ 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: {
|
||||
/*
|
||||
@@ -2037,6 +1929,10 @@ function areArgumentsImmutableAndNonMutating(
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
/**
|
||||
* Globals, module locals, and other locally defined functions may
|
||||
* mutate their arguments.
|
||||
*/
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -2079,9 +1975,128 @@ function getArgumentEffect(
|
||||
});
|
||||
}
|
||||
// effects[i] is Effect.Capture | Effect.Read | Effect.Store
|
||||
return Effect.ConditionallyMutate;
|
||||
return Effect.ConditionallyMutateIterator;
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
|
||||
+11
-9
@@ -18,6 +18,7 @@ import {
|
||||
Instruction,
|
||||
LoadGlobal,
|
||||
LoadLocal,
|
||||
NonLocalImportSpecifier,
|
||||
Place,
|
||||
PropertyLoad,
|
||||
isUseContextHookType,
|
||||
@@ -35,7 +36,7 @@ import {inferTypes} from '../TypeInference';
|
||||
|
||||
export function lowerContextAccess(
|
||||
fn: HIRFunction,
|
||||
loweredContextCallee: ExternalFunction,
|
||||
loweredContextCalleeConfig: ExternalFunction,
|
||||
): void {
|
||||
const contextAccess: Map<IdentifierId, CallExpression> = new Map();
|
||||
const contextKeys: Map<IdentifierId, Array<string>> = new Map();
|
||||
@@ -79,6 +80,8 @@ export function lowerContextAccess(
|
||||
}
|
||||
}
|
||||
|
||||
let importLoweredContextCallee: NonLocalImportSpecifier | null = null;
|
||||
|
||||
if (contextAccess.size > 0 && contextKeys.size > 0) {
|
||||
for (const [, block] of fn.body.blocks) {
|
||||
let nextInstructions: Array<Instruction> | null = null;
|
||||
@@ -91,9 +94,13 @@ export function lowerContextAccess(
|
||||
isUseContextHookType(value.callee.identifier) &&
|
||||
contextKeys.has(lvalue.identifier.id)
|
||||
) {
|
||||
importLoweredContextCallee ??=
|
||||
fn.env.programContext.addImportSpecifier(
|
||||
loweredContextCalleeConfig,
|
||||
);
|
||||
const loweredContextCalleeInstr = emitLoadLoweredContextCallee(
|
||||
fn.env,
|
||||
loweredContextCallee,
|
||||
importLoweredContextCallee,
|
||||
);
|
||||
|
||||
if (nextInstructions === null) {
|
||||
@@ -122,21 +129,16 @@ export function lowerContextAccess(
|
||||
}
|
||||
markInstructionIds(fn.body);
|
||||
inferTypes(fn);
|
||||
fn.env.hasLoweredContextAccess = true;
|
||||
}
|
||||
}
|
||||
|
||||
function emitLoadLoweredContextCallee(
|
||||
env: Environment,
|
||||
loweredContextCallee: ExternalFunction,
|
||||
importedLowerContextCallee: NonLocalImportSpecifier,
|
||||
): Instruction {
|
||||
const loadGlobal: LoadGlobal = {
|
||||
kind: 'LoadGlobal',
|
||||
binding: {
|
||||
kind: 'ImportNamespace',
|
||||
module: loweredContextCallee.source,
|
||||
name: loweredContextCallee.importSpecifierName,
|
||||
},
|
||||
binding: {...importedLowerContextCallee},
|
||||
loc: GeneratedSource,
|
||||
};
|
||||
|
||||
|
||||
@@ -196,7 +196,7 @@ function process(
|
||||
return null;
|
||||
}
|
||||
|
||||
const props = collectProps(jsx);
|
||||
const props = collectProps(fn.env, jsx);
|
||||
if (!props) return null;
|
||||
|
||||
const outlinedTag = fn.env.generateGloballyUniqueIdentifierName(null).value;
|
||||
@@ -217,6 +217,7 @@ type OutlinedJsxAttribute = {
|
||||
};
|
||||
|
||||
function collectProps(
|
||||
env: Environment,
|
||||
instructions: Array<JsxInstruction>,
|
||||
): Array<OutlinedJsxAttribute> | null {
|
||||
let id = 1;
|
||||
@@ -227,6 +228,7 @@ function collectProps(
|
||||
newName = `${oldName}${id++}`;
|
||||
}
|
||||
seen.add(newName);
|
||||
env.programContext.addNewReference(newName);
|
||||
return newName;
|
||||
}
|
||||
|
||||
|
||||
+67
-39
@@ -52,7 +52,8 @@ import {assertExhaustive} from '../Utils/utils';
|
||||
import {buildReactiveFunction} from './BuildReactiveFunction';
|
||||
import {SINGLE_CHILD_FBT_TAGS} from './MemoizeFbtAndMacroOperandsInSameScope';
|
||||
import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors';
|
||||
import {ReactFunctionType} from '../HIR/Environment';
|
||||
import {EMIT_FREEZE_GLOBAL_GATING, ReactFunctionType} from '../HIR/Environment';
|
||||
import {ProgramContext} from '../Entrypoint';
|
||||
|
||||
export const MEMO_CACHE_SENTINEL = 'react.memo_cache_sentinel';
|
||||
export const EARLY_RETURN_SENTINEL = 'react.early_return_sentinel';
|
||||
@@ -100,9 +101,9 @@ export type CodegenFunction = {
|
||||
}>;
|
||||
|
||||
/**
|
||||
* This is true if the compiler has the lowered useContext calls.
|
||||
* This is true if the compiler has compiled inferred effect dependencies
|
||||
*/
|
||||
hasLoweredContextAccess: boolean;
|
||||
hasInferredEffect: boolean;
|
||||
|
||||
/**
|
||||
* This is true if the compiler has compiled a fire to a useFire call
|
||||
@@ -160,6 +161,7 @@ export function codegenFunction(
|
||||
compiled.body = t.blockStatement([
|
||||
createHookGuard(
|
||||
hookGuard,
|
||||
fn.env.programContext,
|
||||
compiled.body.body,
|
||||
GuardKind.PushHookGuard,
|
||||
GuardKind.PopHookGuard,
|
||||
@@ -170,13 +172,15 @@ export function codegenFunction(
|
||||
const cacheCount = compiled.memoSlotsUsed;
|
||||
if (cacheCount !== 0) {
|
||||
const preface: Array<t.Statement> = [];
|
||||
const useMemoCacheIdentifier =
|
||||
fn.env.programContext.addMemoCacheImport().name;
|
||||
|
||||
// The import declaration for `useMemoCache` is inserted in the Babel plugin
|
||||
preface.push(
|
||||
t.variableDeclaration('const', [
|
||||
t.variableDeclarator(
|
||||
t.identifier(cx.synthesizeName('$')),
|
||||
t.callExpression(t.identifier(fn.env.useMemoCacheIdentifier), [
|
||||
t.callExpression(t.identifier(useMemoCacheIdentifier), [
|
||||
t.numericLiteral(cacheCount),
|
||||
]),
|
||||
),
|
||||
@@ -259,34 +263,54 @@ export function codegenFunction(
|
||||
* Technically, this is a conditional hook call. However, we expect
|
||||
* __DEV__ and gating identifier to be runtime constants
|
||||
*/
|
||||
let gating: t.Expression;
|
||||
if (
|
||||
emitInstrumentForget.gating != null &&
|
||||
const gating =
|
||||
emitInstrumentForget.gating != null
|
||||
? t.identifier(
|
||||
fn.env.programContext.addImportSpecifier(
|
||||
emitInstrumentForget.gating,
|
||||
).name,
|
||||
)
|
||||
: null;
|
||||
|
||||
const globalGating =
|
||||
emitInstrumentForget.globalGating != null
|
||||
) {
|
||||
gating = t.logicalExpression(
|
||||
'&&',
|
||||
t.identifier(emitInstrumentForget.globalGating),
|
||||
t.identifier(emitInstrumentForget.gating.importSpecifierName),
|
||||
? t.identifier(emitInstrumentForget.globalGating)
|
||||
: null;
|
||||
|
||||
if (emitInstrumentForget.globalGating != null) {
|
||||
const assertResult = fn.env.programContext.assertGlobalBinding(
|
||||
emitInstrumentForget.globalGating,
|
||||
);
|
||||
} else if (emitInstrumentForget.gating != null) {
|
||||
gating = t.identifier(emitInstrumentForget.gating.importSpecifierName);
|
||||
if (assertResult.isErr()) {
|
||||
return assertResult;
|
||||
}
|
||||
}
|
||||
|
||||
let ifTest: t.Expression;
|
||||
if (gating != null && globalGating != null) {
|
||||
ifTest = t.logicalExpression('&&', globalGating, gating);
|
||||
} else if (gating != null) {
|
||||
ifTest = gating;
|
||||
} else {
|
||||
CompilerError.invariant(emitInstrumentForget.globalGating != null, {
|
||||
CompilerError.invariant(globalGating != null, {
|
||||
reason:
|
||||
'Bad config not caught! Expected at least one of gating or globalGating',
|
||||
loc: null,
|
||||
suggestions: null,
|
||||
});
|
||||
gating = t.identifier(emitInstrumentForget.globalGating);
|
||||
ifTest = globalGating;
|
||||
}
|
||||
|
||||
const instrumentFnIdentifier = fn.env.programContext.addImportSpecifier(
|
||||
emitInstrumentForget.fn,
|
||||
).name;
|
||||
const test: t.IfStatement = t.ifStatement(
|
||||
gating,
|
||||
ifTest,
|
||||
t.expressionStatement(
|
||||
t.callExpression(
|
||||
t.identifier(emitInstrumentForget.fn.importSpecifierName),
|
||||
[t.stringLiteral(fn.id), t.stringLiteral(fn.env.filename ?? '')],
|
||||
),
|
||||
t.callExpression(t.identifier(instrumentFnIdentifier), [
|
||||
t.stringLiteral(fn.id),
|
||||
t.stringLiteral(fn.env.filename ?? ''),
|
||||
]),
|
||||
),
|
||||
);
|
||||
compiled.body.body.unshift(test);
|
||||
@@ -363,8 +387,8 @@ function codegenReactiveFunction(
|
||||
prunedMemoBlocks: countMemoBlockVisitor.prunedMemoBlocks,
|
||||
prunedMemoValues: countMemoBlockVisitor.prunedMemoValues,
|
||||
outlined: [],
|
||||
hasLoweredContextAccess: fn.env.hasLoweredContextAccess,
|
||||
hasFireRewrite: fn.env.hasFireRewrite,
|
||||
hasInferredEffect: fn.env.hasInferredEffect,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -553,13 +577,18 @@ function codegenBlockNoReset(
|
||||
|
||||
function wrapCacheDep(cx: Context, value: t.Expression): t.Expression {
|
||||
if (cx.env.config.enableEmitFreeze != null && cx.env.isInferredMemoEnabled) {
|
||||
// The import declaration for emitFreeze is inserted in the Babel plugin
|
||||
const emitFreezeIdentifier = cx.env.programContext.addImportSpecifier(
|
||||
cx.env.config.enableEmitFreeze,
|
||||
).name;
|
||||
cx.env.programContext
|
||||
.assertGlobalBinding(EMIT_FREEZE_GLOBAL_GATING, cx.env.scope)
|
||||
.unwrap();
|
||||
return t.conditionalExpression(
|
||||
t.identifier('__DEV__'),
|
||||
t.callExpression(
|
||||
t.identifier(cx.env.config.enableEmitFreeze.importSpecifierName),
|
||||
[value, t.stringLiteral(cx.fnName)],
|
||||
),
|
||||
t.identifier(EMIT_FREEZE_GLOBAL_GATING),
|
||||
t.callExpression(t.identifier(emitFreezeIdentifier), [
|
||||
value,
|
||||
t.stringLiteral(cx.fnName),
|
||||
]),
|
||||
value,
|
||||
);
|
||||
} else {
|
||||
@@ -713,16 +742,14 @@ function codegenReactiveScope(
|
||||
let computationBlock = codegenBlock(cx, block);
|
||||
|
||||
let memoStatement;
|
||||
if (
|
||||
cx.env.config.enableChangeDetectionForDebugging != null &&
|
||||
changeExpressions.length > 0
|
||||
) {
|
||||
const detectionFunction = cx.env.config.enableChangeDetectionForDebugging;
|
||||
if (detectionFunction != null && changeExpressions.length > 0) {
|
||||
const loc =
|
||||
typeof scope.loc === 'symbol'
|
||||
? 'unknown location'
|
||||
: `(${scope.loc.start.line}:${scope.loc.end.line})`;
|
||||
const detectionFunction =
|
||||
cx.env.config.enableChangeDetectionForDebugging.importSpecifierName;
|
||||
const importedDetectionFunctionIdentifier =
|
||||
cx.env.programContext.addImportSpecifier(detectionFunction).name;
|
||||
const cacheLoadOldValueStatements: Array<t.Statement> = [];
|
||||
const changeDetectionStatements: Array<t.Statement> = [];
|
||||
const idempotenceDetectionStatements: Array<t.Statement> = [];
|
||||
@@ -744,7 +771,7 @@ function codegenReactiveScope(
|
||||
);
|
||||
changeDetectionStatements.push(
|
||||
t.expressionStatement(
|
||||
t.callExpression(t.identifier(detectionFunction), [
|
||||
t.callExpression(t.identifier(importedDetectionFunctionIdentifier), [
|
||||
t.identifier(loadName),
|
||||
t.cloneNode(name, true),
|
||||
t.stringLiteral(name.name),
|
||||
@@ -756,7 +783,7 @@ function codegenReactiveScope(
|
||||
);
|
||||
idempotenceDetectionStatements.push(
|
||||
t.expressionStatement(
|
||||
t.callExpression(t.identifier(detectionFunction), [
|
||||
t.callExpression(t.identifier(importedDetectionFunctionIdentifier), [
|
||||
t.cloneNode(slot, true),
|
||||
t.cloneNode(name, true),
|
||||
t.stringLiteral(name.name),
|
||||
@@ -1518,15 +1545,15 @@ const createStringLiteral = withLoc(t.stringLiteral);
|
||||
|
||||
function createHookGuard(
|
||||
guard: ExternalFunction,
|
||||
context: ProgramContext,
|
||||
stmts: Array<t.Statement>,
|
||||
before: GuardKind,
|
||||
after: GuardKind,
|
||||
): t.TryStatement {
|
||||
const guardFnName = context.addImportSpecifier(guard).name;
|
||||
function createHookGuardImpl(kind: number): t.ExpressionStatement {
|
||||
return t.expressionStatement(
|
||||
t.callExpression(t.identifier(guard.importSpecifierName), [
|
||||
t.numericLiteral(kind),
|
||||
]),
|
||||
t.callExpression(t.identifier(guardFnName), [t.numericLiteral(kind)]),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1576,6 +1603,7 @@ function createCallExpression(
|
||||
t.blockStatement([
|
||||
createHookGuard(
|
||||
hookGuard,
|
||||
env.programContext,
|
||||
[t.returnStatement(callExpr)],
|
||||
GuardKind.AllowHook,
|
||||
GuardKind.DisallowHook,
|
||||
|
||||
+80
-21
@@ -5,6 +5,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {CompilerError} from '..';
|
||||
import {
|
||||
DeclarationId,
|
||||
InstructionKind,
|
||||
@@ -27,7 +28,17 @@ export function pruneHoistedContexts(fn: ReactiveFunction): void {
|
||||
visitReactiveFunction(fn, new Visitor(), hoistedIdentifiers);
|
||||
}
|
||||
|
||||
type HoistedIdentifiers = Map<DeclarationId, InstructionKind>;
|
||||
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
|
||||
>;
|
||||
|
||||
class Visitor extends ReactiveFunctionTransform<HoistedIdentifiers> {
|
||||
override transformInstruction(
|
||||
@@ -35,6 +46,10 @@ class Visitor extends ReactiveFunctionTransform<HoistedIdentifiers> {
|
||||
state: HoistedIdentifiers,
|
||||
): Transformed<ReactiveStatement> {
|
||||
this.visitInstruction(instruction, state);
|
||||
|
||||
/**
|
||||
* Remove hoisted declarations to preserve TDZ
|
||||
*/
|
||||
if (
|
||||
instruction.value.kind === 'DeclareContext' &&
|
||||
instruction.value.lvalue.kind === 'HoistedConst'
|
||||
@@ -68,31 +83,75 @@ class Visitor extends ReactiveFunctionTransform<HoistedIdentifiers> {
|
||||
return {kind: 'remove'};
|
||||
}
|
||||
|
||||
if (
|
||||
instruction.value.kind === 'StoreContext' &&
|
||||
state.has(instruction.value.lvalue.place.identifier.declarationId)
|
||||
) {
|
||||
if (instruction.value.kind === 'StoreContext') {
|
||||
const kind = state.get(
|
||||
instruction.value.lvalue.place.identifier.declarationId,
|
||||
)!;
|
||||
return {
|
||||
kind: 'replace',
|
||||
value: {
|
||||
kind: 'instruction',
|
||||
instruction: {
|
||||
...instruction,
|
||||
);
|
||||
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',
|
||||
value: {
|
||||
...instruction.value,
|
||||
lvalue: {
|
||||
...instruction.value.lvalue,
|
||||
kind,
|
||||
kind: 'instruction',
|
||||
instruction: {
|
||||
...instruction,
|
||||
value: {
|
||||
...instruction.value,
|
||||
lvalue: {
|
||||
...instruction.value.lvalue,
|
||||
kind,
|
||||
},
|
||||
type: null,
|
||||
kind: 'StoreLocal',
|
||||
},
|
||||
},
|
||||
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'};
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {ProgramContext} from '..';
|
||||
import {CompilerError} from '../CompilerError';
|
||||
import {
|
||||
DeclarationId,
|
||||
@@ -47,7 +48,7 @@ import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors';
|
||||
*/
|
||||
export function renameVariables(fn: ReactiveFunction): Set<string> {
|
||||
const globals = collectReferencedGlobals(fn);
|
||||
const scopes = new Scopes(globals);
|
||||
const scopes = new Scopes(globals, fn.env.programContext);
|
||||
renameVariablesImpl(fn, new Visitor(), scopes);
|
||||
return new Set([...scopes.names, ...globals]);
|
||||
}
|
||||
@@ -124,10 +125,12 @@ class Scopes {
|
||||
#seen: Map<DeclarationId, IdentifierName> = new Map();
|
||||
#stack: Array<Map<string, DeclarationId>> = [new Map()];
|
||||
#globals: Set<string>;
|
||||
#programContext: ProgramContext;
|
||||
names: Set<ValidIdentifierName> = new Set();
|
||||
|
||||
constructor(globals: Set<string>) {
|
||||
constructor(globals: Set<string>, programContext: ProgramContext) {
|
||||
this.#globals = globals;
|
||||
this.#programContext = programContext;
|
||||
}
|
||||
|
||||
visit(identifier: Identifier): void {
|
||||
@@ -156,6 +159,7 @@ class Scopes {
|
||||
name = `${originalName.value}$${id++}`;
|
||||
}
|
||||
}
|
||||
this.#programContext.addNewReference(name);
|
||||
const identifierName = makeIdentifierName(name);
|
||||
identifier.name = identifierName;
|
||||
this.#seen.set(identifier.declarationId, identifierName);
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
isUseEffectHookType,
|
||||
LoadLocal,
|
||||
makeInstructionId,
|
||||
NonLocalImportSpecifier,
|
||||
Place,
|
||||
promoteTemporary,
|
||||
} from '../HIR';
|
||||
@@ -36,6 +37,7 @@ import {getOrInsertWith} from '../Utils/utils';
|
||||
import {BuiltInFireId, DefaultNonmutatingHook} from '../HIR/ObjectShape';
|
||||
import {eachInstructionOperand} from '../HIR/visitors';
|
||||
import {printSourceLocationLine} from '../HIR/PrintHIR';
|
||||
import {USE_FIRE_FUNCTION_NAME} from '../HIR/Environment';
|
||||
|
||||
/*
|
||||
* TODO(jmbrown):
|
||||
@@ -56,6 +58,7 @@ export function transformFire(fn: HIRFunction): void {
|
||||
}
|
||||
|
||||
function replaceFireFunctions(fn: HIRFunction, context: Context): void {
|
||||
let importedUseFire: NonLocalImportSpecifier | null = null;
|
||||
let hasRewrite = false;
|
||||
for (const [, block] of fn.body.blocks) {
|
||||
const rewriteInstrs = new Map<InstructionId, Array<Instruction>>();
|
||||
@@ -87,7 +90,15 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void {
|
||||
] of capturedCallees.entries()) {
|
||||
if (!context.hasCalleeWithInsertedFire(fireCalleePlace)) {
|
||||
context.addCalleeWithInsertedFire(fireCalleePlace);
|
||||
const loadUseFireInstr = makeLoadUseFireInstruction(fn.env);
|
||||
|
||||
importedUseFire ??= fn.env.programContext.addImportSpecifier({
|
||||
source: fn.env.programContext.reactRuntimeModule,
|
||||
importSpecifierName: USE_FIRE_FUNCTION_NAME,
|
||||
});
|
||||
const loadUseFireInstr = makeLoadUseFireInstruction(
|
||||
fn.env,
|
||||
importedUseFire,
|
||||
);
|
||||
const loadFireCalleeInstr = makeLoadFireCalleeInstruction(
|
||||
fn.env,
|
||||
fireCalleeInfo.capturedCalleeIdentifier,
|
||||
@@ -404,18 +415,16 @@ function ensureNoMoreFireUses(fn: HIRFunction, context: Context): void {
|
||||
}
|
||||
}
|
||||
|
||||
function makeLoadUseFireInstruction(env: Environment): Instruction {
|
||||
function makeLoadUseFireInstruction(
|
||||
env: Environment,
|
||||
importedLoadUseFire: NonLocalImportSpecifier,
|
||||
): Instruction {
|
||||
const useFirePlace = createTemporaryPlace(env, GeneratedSource);
|
||||
useFirePlace.effect = Effect.Read;
|
||||
useFirePlace.identifier.type = DefaultNonmutatingHook;
|
||||
const instrValue: InstructionValue = {
|
||||
kind: 'LoadGlobal',
|
||||
binding: {
|
||||
kind: 'ImportSpecifier',
|
||||
name: 'useFire',
|
||||
module: 'react',
|
||||
imported: 'useFire',
|
||||
},
|
||||
binding: {...importedLoadUseFire},
|
||||
loc: GeneratedSource,
|
||||
};
|
||||
return {
|
||||
|
||||
@@ -261,6 +261,7 @@ function* generateInstructionTypes(
|
||||
kind: 'Function',
|
||||
shapeId: null,
|
||||
return: returnType,
|
||||
isConstructor: false,
|
||||
});
|
||||
yield equation(left, returnType);
|
||||
break;
|
||||
@@ -277,6 +278,7 @@ function* generateInstructionTypes(
|
||||
kind: 'Function',
|
||||
shapeId: null,
|
||||
return: returnType,
|
||||
isConstructor: false,
|
||||
});
|
||||
yield equation(left, returnType);
|
||||
break;
|
||||
@@ -333,6 +335,7 @@ function* generateInstructionTypes(
|
||||
kind: 'Function',
|
||||
return: returnType,
|
||||
shapeId: null,
|
||||
isConstructor: false,
|
||||
});
|
||||
|
||||
yield equation(left, returnType);
|
||||
@@ -405,6 +408,7 @@ function* generateInstructionTypes(
|
||||
kind: 'Function',
|
||||
shapeId: BuiltInFunctionId,
|
||||
return: value.loweredFunc.func.returnType,
|
||||
isConstructor: false,
|
||||
});
|
||||
break;
|
||||
}
|
||||
@@ -425,9 +429,20 @@ 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':
|
||||
@@ -505,7 +520,11 @@ class Unifier {
|
||||
return;
|
||||
}
|
||||
|
||||
if (tB.kind === 'Function' && tA.kind === 'Function') {
|
||||
if (
|
||||
tB.kind === 'Function' &&
|
||||
tA.kind === 'Function' &&
|
||||
tA.isConstructor === tB.isConstructor
|
||||
) {
|
||||
this.unify(tA.return, tB.return);
|
||||
return;
|
||||
}
|
||||
@@ -648,6 +667,7 @@ class Unifier {
|
||||
kind: 'Function',
|
||||
return: returnType,
|
||||
shapeId: type.shapeId,
|
||||
isConstructor: type.isConstructor,
|
||||
};
|
||||
}
|
||||
case 'ObjectMethod':
|
||||
|
||||
@@ -12,6 +12,7 @@ globalThis.constantValue = 'global test value';
|
||||
|
||||
test('literal-constant-propagation', () => {
|
||||
function Component() {
|
||||
'use memo';
|
||||
const x = 'test value 1';
|
||||
return <div>{x}</div>;
|
||||
}
|
||||
@@ -38,6 +39,7 @@ test('literal-constant-propagation', () => {
|
||||
|
||||
test('global-constant-propagation', () => {
|
||||
function Component() {
|
||||
'use memo';
|
||||
const x = constantValue;
|
||||
|
||||
return <div>{x}</div>;
|
||||
@@ -65,6 +67,7 @@ test('global-constant-propagation', () => {
|
||||
|
||||
test('lambda-constant-propagation', () => {
|
||||
function Component() {
|
||||
'use memo';
|
||||
const x = 'test value 1';
|
||||
const getDiv = () => <div>{x}</div>;
|
||||
return getDiv();
|
||||
@@ -92,6 +95,7 @@ test('lambda-constant-propagation', () => {
|
||||
|
||||
test('lambda-constant-propagation-of-phi-node', () => {
|
||||
function Component({noopCallback}) {
|
||||
'use memo';
|
||||
const x = 'test value 1';
|
||||
if (constantValue) {
|
||||
noopCallback();
|
||||
|
||||
@@ -16,6 +16,7 @@ function Button({label}) {
|
||||
|
||||
let currentTheme = 'light';
|
||||
function useTheme() {
|
||||
'use memo';
|
||||
return currentTheme;
|
||||
}
|
||||
|
||||
|
||||
+1
@@ -10,6 +10,7 @@ import * as React from 'react';
|
||||
import {expectLogsAndClear, log} from './expectLogs';
|
||||
|
||||
function Counter(props) {
|
||||
'use memo';
|
||||
let value = props.value;
|
||||
let a = value++;
|
||||
expect(a).toBe(props.value); // postfix
|
||||
|
||||
+44
-17
@@ -50,28 +50,55 @@ import { useIdentity, Stringify } from "shared-runtime";
|
||||
* (2) the 1st argument might mutate its callee
|
||||
*/
|
||||
function Component(t0) {
|
||||
const $ = _c(4);
|
||||
const $ = _c(10);
|
||||
const { value } = t0;
|
||||
const arr = [{ value: "foo" }, { value: "bar" }, { value }];
|
||||
useIdentity();
|
||||
const derived = Array.from(arr, _temp);
|
||||
let t1;
|
||||
if ($[0] !== derived) {
|
||||
t1 = derived.at(-1);
|
||||
$[0] = derived;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
let t2;
|
||||
if ($[2] !== t1) {
|
||||
t2 = <Stringify>{t1}</Stringify>;
|
||||
$[2] = t1;
|
||||
$[3] = t2;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t1 = { value: "foo" };
|
||||
t2 = { value: "bar" };
|
||||
$[0] = t1;
|
||||
$[1] = t2;
|
||||
} else {
|
||||
t2 = $[3];
|
||||
t1 = $[0];
|
||||
t2 = $[1];
|
||||
}
|
||||
return t2;
|
||||
let t3;
|
||||
if ($[2] !== value) {
|
||||
t3 = [t1, t2, { value }];
|
||||
$[2] = value;
|
||||
$[3] = t3;
|
||||
} else {
|
||||
t3 = $[3];
|
||||
}
|
||||
const arr = t3;
|
||||
useIdentity();
|
||||
let t4;
|
||||
if ($[4] !== arr) {
|
||||
t4 = Array.from(arr, _temp);
|
||||
$[4] = arr;
|
||||
$[5] = t4;
|
||||
} else {
|
||||
t4 = $[5];
|
||||
}
|
||||
const derived = t4;
|
||||
let t5;
|
||||
if ($[6] !== derived) {
|
||||
t5 = derived.at(-1);
|
||||
$[6] = derived;
|
||||
$[7] = t5;
|
||||
} else {
|
||||
t5 = $[7];
|
||||
}
|
||||
let t6;
|
||||
if ($[8] !== t5) {
|
||||
t6 = <Stringify>{t5}</Stringify>;
|
||||
$[8] = t5;
|
||||
$[9] = t6;
|
||||
} else {
|
||||
t6 = $[9];
|
||||
}
|
||||
return t6;
|
||||
}
|
||||
function _temp(x, idx) {
|
||||
return { ...x, id: idx };
|
||||
|
||||
+44
-17
@@ -50,28 +50,55 @@ import { useIdentity, Stringify } from "shared-runtime";
|
||||
* (2) the 1st argument might mutate its callee
|
||||
*/
|
||||
function Component(t0) {
|
||||
const $ = _c(4);
|
||||
const $ = _c(10);
|
||||
const { value } = t0;
|
||||
const arr = [{ value: "foo" }, { value: "bar" }, { value }];
|
||||
useIdentity();
|
||||
const derived = Array.from(arr);
|
||||
let t1;
|
||||
if ($[0] !== derived) {
|
||||
t1 = derived.at(-1);
|
||||
$[0] = derived;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
let t2;
|
||||
if ($[2] !== t1) {
|
||||
t2 = <Stringify>{t1}</Stringify>;
|
||||
$[2] = t1;
|
||||
$[3] = t2;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t1 = { value: "foo" };
|
||||
t2 = { value: "bar" };
|
||||
$[0] = t1;
|
||||
$[1] = t2;
|
||||
} else {
|
||||
t2 = $[3];
|
||||
t1 = $[0];
|
||||
t2 = $[1];
|
||||
}
|
||||
return t2;
|
||||
let t3;
|
||||
if ($[2] !== value) {
|
||||
t3 = [t1, t2, { value }];
|
||||
$[2] = value;
|
||||
$[3] = t3;
|
||||
} else {
|
||||
t3 = $[3];
|
||||
}
|
||||
const arr = t3;
|
||||
useIdentity();
|
||||
let t4;
|
||||
if ($[4] !== arr) {
|
||||
t4 = Array.from(arr);
|
||||
$[4] = arr;
|
||||
$[5] = t4;
|
||||
} else {
|
||||
t4 = $[5];
|
||||
}
|
||||
const derived = t4;
|
||||
let t5;
|
||||
if ($[6] !== derived) {
|
||||
t5 = derived.at(-1);
|
||||
$[6] = derived;
|
||||
$[7] = t5;
|
||||
} else {
|
||||
t5 = $[7];
|
||||
}
|
||||
let t6;
|
||||
if ($[8] !== t5) {
|
||||
t6 = <Stringify>{t5}</Stringify>;
|
||||
$[8] = t5;
|
||||
$[9] = t6;
|
||||
} else {
|
||||
t6 = $[9];
|
||||
}
|
||||
return t6;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
|
||||
+34
-14
@@ -7,14 +7,19 @@ import {mutateAndReturn, Stringify, useIdentity} from 'shared-runtime';
|
||||
function Component({value}) {
|
||||
const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
|
||||
useIdentity();
|
||||
const derived = Array.from(arr, mutateAndReturn);
|
||||
return <Stringify>{derived.at(-1)}</Stringify>;
|
||||
const derived = Array.from(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}],
|
||||
sequentialRenders: [{value: 5}, {value: 6}, {value: 6}, {value: 7}],
|
||||
};
|
||||
|
||||
```
|
||||
@@ -26,39 +31,54 @@ import { c as _c } from "react/compiler-runtime";
|
||||
import { mutateAndReturn, Stringify, useIdentity } from "shared-runtime";
|
||||
|
||||
function Component(t0) {
|
||||
const $ = _c(4);
|
||||
const $ = _c(7);
|
||||
const { value } = t0;
|
||||
const arr = [{ value: "foo" }, { value: "bar" }, { value }];
|
||||
useIdentity();
|
||||
const derived = Array.from(arr, mutateAndReturn);
|
||||
const derived = Array.from(arr).map(mutateAndReturn);
|
||||
let t1;
|
||||
if ($[0] !== derived) {
|
||||
t1 = derived.at(-1);
|
||||
t1 = derived.at(0);
|
||||
$[0] = derived;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
let t2;
|
||||
if ($[2] !== t1) {
|
||||
t2 = <Stringify>{t1}</Stringify>;
|
||||
$[2] = t1;
|
||||
if ($[2] !== derived) {
|
||||
t2 = derived.at(-1);
|
||||
$[2] = derived;
|
||||
$[3] = t2;
|
||||
} else {
|
||||
t2 = $[3];
|
||||
}
|
||||
return t2;
|
||||
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 }],
|
||||
sequentialRenders: [{ value: 5 }, { value: 6 }, { value: 6 }, { value: 7 }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>{"children":{"value":5,"wat0":"joe"}}</div>
|
||||
<div>{"children":{"value":6,"wat0":"joe"}}</div>
|
||||
<div>{"children":{"value":6,"wat0":"joe"}}</div>
|
||||
(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>
|
||||
+8
-3
@@ -3,12 +3,17 @@ import {mutateAndReturn, Stringify, useIdentity} from 'shared-runtime';
|
||||
function Component({value}) {
|
||||
const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
|
||||
useIdentity();
|
||||
const derived = Array.from(arr, mutateAndReturn);
|
||||
return <Stringify>{derived.at(-1)}</Stringify>;
|
||||
const derived = Array.from(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}],
|
||||
sequentialRenders: [{value: 5}, {value: 6}, {value: 6}, {value: 7}],
|
||||
};
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @compilationMode(infer)
|
||||
const Test = () => <div />;
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Test,
|
||||
params: [{}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer)
|
||||
const Test = () => {
|
||||
const $ = _c(1);
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = <div />;
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
return t0;
|
||||
};
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Test,
|
||||
params: [{}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div></div>
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// @compilationMode(infer)
|
||||
const Test = () => <div />;
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Test,
|
||||
params: [{}],
|
||||
};
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import * as React from 'react';
|
||||
import {someImport} from 'react/compiler-runtime';
|
||||
import {calculateExpensiveNumber} from 'shared-runtime';
|
||||
|
||||
function Component(props) {
|
||||
const [x] = React.useState(0);
|
||||
const expensiveNumber = React.useMemo(() => calculateExpensiveNumber(x), [x]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{expensiveNumber}
|
||||
{`${someImport}`}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import * as React from "react";
|
||||
import { someImport, c as _c } from "react/compiler-runtime";
|
||||
import { calculateExpensiveNumber } from "shared-runtime";
|
||||
|
||||
function Component(props) {
|
||||
const $ = _c(4);
|
||||
const [x] = React.useState(0);
|
||||
let t0;
|
||||
let t1;
|
||||
if ($[0] !== x) {
|
||||
t1 = calculateExpensiveNumber(x);
|
||||
$[0] = x;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
t0 = t1;
|
||||
const expensiveNumber = t0;
|
||||
let t2;
|
||||
if ($[2] !== expensiveNumber) {
|
||||
t2 = (
|
||||
<div>
|
||||
{expensiveNumber}
|
||||
{`${someImport}`}
|
||||
</div>
|
||||
);
|
||||
$[2] = expensiveNumber;
|
||||
$[3] = t2;
|
||||
} else {
|
||||
t2 = $[3];
|
||||
}
|
||||
return t2;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>0undefined</div>
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
import * as React from 'react';
|
||||
import {someImport} from 'react/compiler-runtime';
|
||||
import {calculateExpensiveNumber} from 'shared-runtime';
|
||||
|
||||
function Component(props) {
|
||||
const [x] = React.useState(0);
|
||||
const expensiveNumber = React.useMemo(() => calculateExpensiveNumber(x), [x]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{expensiveNumber}
|
||||
{`${someImport}`}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [],
|
||||
};
|
||||
+2
-2
@@ -14,9 +14,9 @@ function useFoo(props) {
|
||||
|
||||
```javascript
|
||||
import {
|
||||
useRenderCounter,
|
||||
shouldInstrument,
|
||||
makeReadOnly,
|
||||
shouldInstrument,
|
||||
useRenderCounter,
|
||||
} from "react-compiler-runtime";
|
||||
import { c as _c } from "react/compiler-runtime"; // @enableEmitFreeze @enableEmitInstrumentForget
|
||||
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ function Foo(props) {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { useRenderCounter, shouldInstrument } from "react-compiler-runtime";
|
||||
import { shouldInstrument, useRenderCounter } from "react-compiler-runtime";
|
||||
import { c as _c } from "react/compiler-runtime"; // @enableEmitInstrumentForget @compilationMode(annotation)
|
||||
|
||||
function Bar(props) {
|
||||
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enableEmitInstrumentForget @compilationMode(annotation)
|
||||
|
||||
import {identity} from 'shared-runtime';
|
||||
|
||||
function Bar(props) {
|
||||
'use forget';
|
||||
const shouldInstrument = identity(null);
|
||||
const _shouldInstrument = identity(null);
|
||||
const _x2 = () => {
|
||||
const _shouldInstrument2 = 'hello world';
|
||||
return identity({_shouldInstrument2});
|
||||
};
|
||||
return (
|
||||
<div style={shouldInstrument} other={_shouldInstrument}>
|
||||
{props.bar}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Foo(props) {
|
||||
'use forget';
|
||||
return <Foo>{props.bar}</Foo>;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import {
|
||||
shouldInstrument as _shouldInstrument3,
|
||||
useRenderCounter,
|
||||
} from "react-compiler-runtime";
|
||||
import { c as _c } from "react/compiler-runtime"; // @enableEmitInstrumentForget @compilationMode(annotation)
|
||||
|
||||
import { identity } from "shared-runtime";
|
||||
|
||||
function Bar(props) {
|
||||
"use forget";
|
||||
if (DEV && _shouldInstrument3)
|
||||
useRenderCounter("Bar", "/conflict-codegen-instrument-forget.ts");
|
||||
const $ = _c(4);
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = identity(null);
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
const shouldInstrument = t0;
|
||||
let t1;
|
||||
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t1 = identity(null);
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
const _shouldInstrument = t1;
|
||||
let t2;
|
||||
if ($[2] !== props.bar) {
|
||||
t2 = (
|
||||
<div style={shouldInstrument} other={_shouldInstrument}>
|
||||
{props.bar}
|
||||
</div>
|
||||
);
|
||||
$[2] = props.bar;
|
||||
$[3] = t2;
|
||||
} else {
|
||||
t2 = $[3];
|
||||
}
|
||||
return t2;
|
||||
}
|
||||
|
||||
function Foo(props) {
|
||||
"use forget";
|
||||
if (DEV && _shouldInstrument3)
|
||||
useRenderCounter("Foo", "/conflict-codegen-instrument-forget.ts");
|
||||
const $ = _c(2);
|
||||
let t0;
|
||||
if ($[0] !== props.bar) {
|
||||
t0 = <Foo>{props.bar}</Foo>;
|
||||
$[0] = props.bar;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: exception) Fixture not implemented
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
// @enableEmitInstrumentForget @compilationMode(annotation)
|
||||
|
||||
import {identity} from 'shared-runtime';
|
||||
|
||||
function Bar(props) {
|
||||
'use forget';
|
||||
const shouldInstrument = identity(null);
|
||||
const _shouldInstrument = identity(null);
|
||||
const _x2 = () => {
|
||||
const _shouldInstrument2 = 'hello world';
|
||||
return identity({_shouldInstrument2});
|
||||
};
|
||||
return (
|
||||
<div style={shouldInstrument} other={_shouldInstrument}>
|
||||
{props.bar}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Foo(props) {
|
||||
'use forget';
|
||||
return <Foo>{props.bar}</Foo>;
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enableEmitFreeze @instrumentForget
|
||||
|
||||
let makeReadOnly = 'conflicting identifier';
|
||||
function useFoo(props) {
|
||||
return foo(props.x);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { makeReadOnly as _makeReadOnly } from "react-compiler-runtime";
|
||||
import { c as _c } from "react/compiler-runtime"; // @enableEmitFreeze @instrumentForget
|
||||
|
||||
let makeReadOnly = "conflicting identifier";
|
||||
function useFoo(props) {
|
||||
const $ = _c(2);
|
||||
let t0;
|
||||
if ($[0] !== props.x) {
|
||||
t0 = foo(props.x);
|
||||
$[0] = props.x;
|
||||
$[1] = __DEV__ ? _makeReadOnly(t0, "useFoo") : t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: exception) Fixture not implemented
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enableEmitFreeze @instrumentForget
|
||||
function useFoo(props) {
|
||||
return foo(props.x, __DEV__);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { makeReadOnly } from "react-compiler-runtime";
|
||||
import { c as _c } from "react/compiler-runtime"; // @enableEmitFreeze @instrumentForget
|
||||
function useFoo(props) {
|
||||
const $ = _c(2);
|
||||
let t0;
|
||||
if ($[0] !== props.x) {
|
||||
t0 = foo(props.x, __DEV__);
|
||||
$[0] = props.x;
|
||||
$[1] = __DEV__ ? makeReadOnly(t0, "useFoo") : t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: exception) Fixture not implemented
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
// @enableEmitFreeze @instrumentForget
|
||||
function useFoo(props) {
|
||||
return foo(props.x, __DEV__);
|
||||
}
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enableEmitFreeze @instrumentForget
|
||||
|
||||
let makeReadOnly = 'conflicting identifier';
|
||||
function useFoo(props) {
|
||||
return foo(props.x);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
Invariant: Encountered conflicting import specifiers for makeReadOnly in generated program.
|
||||
```
|
||||
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enableEmitFreeze @instrumentForget
|
||||
function useFoo(props) {
|
||||
const __DEV__ = 'conflicting global';
|
||||
console.log(__DEV__);
|
||||
return foo(props.x);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
1 | // @enableEmitFreeze @instrumentForget
|
||||
2 | function useFoo(props) {
|
||||
> 3 | const __DEV__ = 'conflicting global';
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Todo: Encountered conflicting global in generated program. Conflict from local binding __DEV__ (3:3)
|
||||
4 | console.log(__DEV__);
|
||||
5 | return foo(props.x);
|
||||
6 | }
|
||||
```
|
||||
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// @enableEmitFreeze @instrumentForget
|
||||
function useFoo(props) {
|
||||
const __DEV__ = 'conflicting global';
|
||||
console.log(__DEV__);
|
||||
return foo(props.x);
|
||||
}
|
||||
+2
-2
@@ -18,8 +18,8 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
|
||||
import { c as _c } from "react/compiler-runtime"; // @gating
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating
|
||||
import { Stringify } from "shared-runtime";
|
||||
const ErrorView = isForgetEnabled_Fixtures()
|
||||
? (t0) => {
|
||||
|
||||
+3
-3
@@ -36,9 +36,9 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
|
||||
import { useRenderCounter, shouldInstrument } from "react-compiler-runtime";
|
||||
import { c as _c } from "react/compiler-runtime"; // @enableEmitInstrumentForget @compilationMode(annotation) @gating
|
||||
import { shouldInstrument, useRenderCounter } from "react-compiler-runtime";
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @enableEmitInstrumentForget @compilationMode(annotation) @gating
|
||||
const Bar = isForgetEnabled_Fixtures()
|
||||
? function Bar(props) {
|
||||
"use forget";
|
||||
|
||||
+6
-7
@@ -20,14 +20,14 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
|
||||
import { Stringify } from "shared-runtime";
|
||||
import * as React from "react";
|
||||
|
||||
const Foo = React.forwardRef(Foo_withRef);
|
||||
const _isForgetEnabled_Fixtures_result = isForgetEnabled_Fixtures();
|
||||
function _Foo_withRef_optimized(_$$empty_props_placeholder$$, ref) {
|
||||
const isForgetEnabled_Fixtures_result = isForgetEnabled_Fixtures();
|
||||
function Foo_withRef_optimized(_$$empty_props_placeholder$$, ref) {
|
||||
const $ = _c(2);
|
||||
let t0;
|
||||
if ($[0] !== ref) {
|
||||
@@ -39,16 +39,15 @@ function _Foo_withRef_optimized(_$$empty_props_placeholder$$, ref) {
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
function _Foo_withRef_unoptimized(
|
||||
function Foo_withRef_unoptimized(
|
||||
_$$empty_props_placeholder$$: $ReadOnly<{}>,
|
||||
ref: React.RefSetter<Controls>,
|
||||
): React.Node {
|
||||
return <Stringify ref={ref} />;
|
||||
}
|
||||
function Foo_withRef(arg0, arg1) {
|
||||
if (_isForgetEnabled_Fixtures_result)
|
||||
return _Foo_withRef_optimized(arg0, arg1);
|
||||
else return _Foo_withRef_unoptimized(arg0, arg1);
|
||||
if (isForgetEnabled_Fixtures_result) return Foo_withRef_optimized(arg0, arg1);
|
||||
else return Foo_withRef_unoptimized(arg0, arg1);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @gating
|
||||
|
||||
export const isForgetEnabled_Fixtures = () => {
|
||||
'use no forget';
|
||||
return false;
|
||||
};
|
||||
|
||||
export function Bar(props) {
|
||||
'use forget';
|
||||
return <div>{props.bar}</div>;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: eval('Bar'),
|
||||
params: [{bar: 2}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { isForgetEnabled_Fixtures as _isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating
|
||||
|
||||
export const isForgetEnabled_Fixtures = () => {
|
||||
"use no forget";
|
||||
return false;
|
||||
};
|
||||
|
||||
export const Bar = _isForgetEnabled_Fixtures()
|
||||
? function Bar(props) {
|
||||
"use forget";
|
||||
const $ = _c(2);
|
||||
let t0;
|
||||
if ($[0] !== props.bar) {
|
||||
t0 = <div>{props.bar}</div>;
|
||||
$[0] = props.bar;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
: function Bar(props) {
|
||||
"use forget";
|
||||
return <div>{props.bar}</div>;
|
||||
};
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: eval("Bar"),
|
||||
params: [{ bar: 2 }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>2</div>
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
// @gating
|
||||
|
||||
export const isForgetEnabled_Fixtures = () => {
|
||||
'use no forget';
|
||||
return false;
|
||||
};
|
||||
|
||||
export function Bar(props) {
|
||||
'use forget';
|
||||
return <div>{props.bar}</div>;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: eval('Bar'),
|
||||
params: [{bar: 2}],
|
||||
};
|
||||
+2
-2
@@ -18,8 +18,8 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
|
||||
import { c as _c } from "react/compiler-runtime"; // @gating
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating
|
||||
const Component = isForgetEnabled_Fixtures()
|
||||
? function Component() {
|
||||
const $ = _c(1);
|
||||
|
||||
+2
-2
@@ -24,8 +24,8 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
|
||||
import { c as _c } from "react/compiler-runtime"; // @gating
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating
|
||||
import { identity, useHook as useRenamed } from "shared-runtime";
|
||||
const _ = {
|
||||
useHook: isForgetEnabled_Fixtures() ? () => {} : () => {},
|
||||
|
||||
+2
-2
@@ -26,8 +26,8 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
|
||||
import { c as _c } from "react/compiler-runtime"; // @gating
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating
|
||||
const Component = isForgetEnabled_Fixtures()
|
||||
? function Component() {
|
||||
const $ = _c(1);
|
||||
|
||||
+2
-2
@@ -27,8 +27,8 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
|
||||
import { c as _c } from "react/compiler-runtime"; // @gating @compilationMode(annotation)
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode(annotation)
|
||||
const Bar = isForgetEnabled_Fixtures()
|
||||
? function Bar(props) {
|
||||
"use forget";
|
||||
|
||||
+2
-2
@@ -34,8 +34,8 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
|
||||
import { c as _c } from "react/compiler-runtime"; // @gating @compilationMode(annotation)
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode(annotation)
|
||||
const Bar = isForgetEnabled_Fixtures()
|
||||
? function Bar(props) {
|
||||
"use forget";
|
||||
|
||||
+2
-2
@@ -27,8 +27,8 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
|
||||
import { c as _c } from "react/compiler-runtime"; // @gating @compilationMode(annotation)
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode(annotation)
|
||||
export const Bar = isForgetEnabled_Fixtures()
|
||||
? function Bar(props) {
|
||||
"use forget";
|
||||
|
||||
+2
-2
@@ -27,8 +27,8 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
|
||||
import { c as _c } from "react/compiler-runtime"; // @gating @compilationMode(annotation)
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode(annotation)
|
||||
const Bar = isForgetEnabled_Fixtures()
|
||||
? function Bar(props) {
|
||||
"use forget";
|
||||
|
||||
+7
-8
@@ -21,14 +21,14 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
|
||||
import { c as _c } from "react/compiler-runtime"; // @gating
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating
|
||||
import { createRef, forwardRef } from "react";
|
||||
import { Stringify } from "shared-runtime";
|
||||
|
||||
const Foo = forwardRef(Foo_withRef);
|
||||
const _isForgetEnabled_Fixtures_result = isForgetEnabled_Fixtures();
|
||||
function _Foo_withRef_optimized(props, ref) {
|
||||
const isForgetEnabled_Fixtures_result = isForgetEnabled_Fixtures();
|
||||
function Foo_withRef_optimized(props, ref) {
|
||||
const $ = _c(3);
|
||||
let t0;
|
||||
if ($[0] !== props || $[1] !== ref) {
|
||||
@@ -41,13 +41,12 @@ function _Foo_withRef_optimized(props, ref) {
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
function _Foo_withRef_unoptimized(props, ref) {
|
||||
function Foo_withRef_unoptimized(props, ref) {
|
||||
return <Stringify ref={ref} {...props} />;
|
||||
}
|
||||
function Foo_withRef(arg0, arg1) {
|
||||
if (_isForgetEnabled_Fixtures_result)
|
||||
return _Foo_withRef_optimized(arg0, arg1);
|
||||
else return _Foo_withRef_unoptimized(arg0, arg1);
|
||||
if (isForgetEnabled_Fixtures_result) return Foo_withRef_optimized(arg0, arg1);
|
||||
else return Foo_withRef_unoptimized(arg0, arg1);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
|
||||
+7
-7
@@ -22,14 +22,14 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
|
||||
import { c as _c } from "react/compiler-runtime"; // @gating
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating
|
||||
import { memo } from "react";
|
||||
import { Stringify } from "shared-runtime";
|
||||
|
||||
export default memo(Foo);
|
||||
const _isForgetEnabled_Fixtures_result = isForgetEnabled_Fixtures();
|
||||
function _Foo_optimized(t0) {
|
||||
const isForgetEnabled_Fixtures_result = isForgetEnabled_Fixtures();
|
||||
function Foo_optimized(t0) {
|
||||
"use memo";
|
||||
const $ = _c(3);
|
||||
const { prop1, prop2 } = t0;
|
||||
@@ -44,13 +44,13 @@ function _Foo_optimized(t0) {
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
function _Foo_unoptimized({ prop1, prop2 }) {
|
||||
function Foo_unoptimized({ prop1, prop2 }) {
|
||||
"use memo";
|
||||
return <Stringify prop1={prop1} prop2={prop2} />;
|
||||
}
|
||||
function Foo(arg0) {
|
||||
if (_isForgetEnabled_Fixtures_result) return _Foo_optimized(arg0);
|
||||
else return _Foo_unoptimized(arg0);
|
||||
if (isForgetEnabled_Fixtures_result) return Foo_optimized(arg0);
|
||||
else return Foo_unoptimized(arg0);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
|
||||
+1
-1
@@ -23,8 +23,8 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
|
||||
import { memo } from "react";
|
||||
|
||||
type Props = React.ElementConfig<typeof Component>;
|
||||
|
||||
+2
-2
@@ -13,8 +13,8 @@ export default React.forwardRef(function notNamedLikeAComponent(props) {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
|
||||
import { c as _c } from "react/compiler-runtime"; // @gating @compilationMode(infer)
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode(infer)
|
||||
import React from "react";
|
||||
export default React.forwardRef(
|
||||
isForgetEnabled_Fixtures()
|
||||
|
||||
+2
-2
@@ -23,8 +23,8 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
|
||||
import { c as _c } from "react/compiler-runtime"; // @gating
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating
|
||||
import * as React from "react";
|
||||
|
||||
let Foo;
|
||||
|
||||
+2
-2
@@ -19,8 +19,8 @@ export default props => (
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
|
||||
import { c as _c } from "react/compiler-runtime"; // @gating
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating
|
||||
import { Stringify } from "shared-runtime";
|
||||
|
||||
const ErrorView = isForgetEnabled_Fixtures()
|
||||
|
||||
+2
-2
@@ -24,8 +24,8 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
|
||||
import { c as _c } from "react/compiler-runtime"; // @gating
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating
|
||||
import { Stringify } from "shared-runtime";
|
||||
|
||||
const ErrorView = isForgetEnabled_Fixtures()
|
||||
|
||||
+2
-2
@@ -26,8 +26,8 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
|
||||
import { c as _c } from "react/compiler-runtime"; // @gating
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating
|
||||
import { Stringify } from "shared-runtime";
|
||||
|
||||
const ErrorView = isForgetEnabled_Fixtures()
|
||||
|
||||
+2
-2
@@ -31,8 +31,8 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
|
||||
import { c as _c } from "react/compiler-runtime"; // @gating
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating
|
||||
import * as React from "react";
|
||||
|
||||
/**
|
||||
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
|
||||
## 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]}]
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
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: [{}, {}],
|
||||
};
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
|
||||
## 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
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
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'},
|
||||
],
|
||||
};
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
|
||||
## 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>
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
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}],
|
||||
};
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
|
||||
## 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>
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
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}],
|
||||
};
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
|
||||
## 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>
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
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}],
|
||||
};
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
|
||||
## 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>
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
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}],
|
||||
};
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
|
||||
## 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
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
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'},
|
||||
],
|
||||
};
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
|
||||
## 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]}]
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
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]}],
|
||||
};
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
|
||||
## 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
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
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'},
|
||||
],
|
||||
};
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
|
||||
## 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 ]]"]}]
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
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]},
|
||||
],
|
||||
};
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
|
||||
## 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 ]]"]}]
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
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]},
|
||||
],
|
||||
};
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
|
||||
## 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 ]]
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
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}],
|
||||
};
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
|
||||
## 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
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
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: [],
|
||||
};
|
||||
+2
-1
@@ -36,7 +36,8 @@ function hoisting(cond) {
|
||||
items.push(bar());
|
||||
};
|
||||
|
||||
let bar = _temp;
|
||||
let bar;
|
||||
bar = _temp;
|
||||
foo();
|
||||
}
|
||||
$[0] = cond;
|
||||
|
||||
+4
-2
@@ -41,9 +41,11 @@ function hoisting() {
|
||||
return result;
|
||||
};
|
||||
|
||||
let foo = () => bar + baz;
|
||||
let foo;
|
||||
foo = () => bar + baz;
|
||||
|
||||
let bar = 3;
|
||||
let bar;
|
||||
bar = 3;
|
||||
const baz = 2;
|
||||
t0 = qux();
|
||||
$[0] = t0;
|
||||
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
|
||||
## 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>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user