Compare commits

..

4 Commits

Author SHA1 Message Date
Joe Savona
5eda822159 [compiler] Alternate take on ref validation
Some of the false positives we've seen have to do with the need to align our ref validation with our understanding of which functions may be called during render. The new mutability/aliasing model makes this much more explicit, with the ability to create Impure effects which we then throw as errors if they are reachable during render. This means we can now revisit ref validation by just emitting impure effects.

That's what this new pass does. It's a bit simpler: it implements the check for `ref.current == null` guarded if blocks. Otherwise it disallows access to `ref.current` specifically. Unlike before, we intentionally allow passing ref objects to functions — we just see a lot of many false positives on disallowing things like `children({ref})` or similar.

Open to feedback! This is also still WIP.
2025-06-25 09:49:17 -07:00
Joe Savona
2de9ddac73 [compiler] Consolidate HIRFunction return information
We now have `HIRFunction.returns: Place` as well as `returnType: Type`. I want to add additional return information, so as a first step i'm consolidating everything under an object at `HIRFunction.returns: {place: Place}`. We use the type of this place as the return type. Next step is to add more properties to this object to represent things like the return kind.
2025-06-25 09:49:17 -07:00
Joe Savona
6ef9e5bd49 [compiler] Avoid empty switch cases
Small cosmetic win, found this when i was looking at some code internally with lots of cases that all share the same logic. Previously, all the but last one would have an empty block.
2025-06-25 09:49:17 -07:00
Joe Savona
440d8c2876 [compiler] Fix bug with reassigning function param in destructuring
Closes #33577, a bug with ExtractScopeDeclarationsFromDestructuring and codegen when a function param is reassigned.
2025-06-25 09:49:17 -07:00
420 changed files with 6301 additions and 10575 deletions

View File

@@ -474,7 +474,7 @@ module.exports = {
{
files: ['packages/react-server-dom-turbopack/**/*.js'],
globals: {
__turbopack_load_by_url__: 'readonly',
__turbopack_load__: 'readonly',
__turbopack_require__: 'readonly',
},
},
@@ -622,7 +622,6 @@ module.exports = {
ScrollTimeline: 'readonly',
EventListenerOptionsOrUseCapture: 'readonly',
FocusOptions: 'readonly',
OptionalEffectTiming: 'readonly',
spyOnDev: 'readonly',
spyOnDevAndProd: 'readonly',

View File

@@ -6,12 +6,6 @@ on:
pull_request:
paths-ignore:
- compiler/**
workflow_dispatch:
inputs:
commit_sha:
required: false
type: string
default: ''
permissions: {}
@@ -34,7 +28,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Check cache hit
uses: actions/cache/restore@v4
id: node_modules
@@ -75,7 +69,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Check cache hit
uses: actions/cache/restore@v4
id: node_modules
@@ -123,7 +117,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/github-script@v7
id: set-matrix
with:
@@ -142,7 +136,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -172,7 +166,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -204,7 +198,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -260,7 +254,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -331,7 +325,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -426,7 +420,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -471,7 +465,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -499,7 +493,7 @@ jobs:
merge-multiple: true
- name: Display structure of build
run: ls -R build
- run: echo ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }} >> build/COMMIT_SHA
- run: echo ${{ github.event.pull_request.head.sha || github.sha }} >> build/COMMIT_SHA
- name: Scrape warning messages
run: |
mkdir -p ./build/__test_utils__
@@ -536,7 +530,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -576,7 +570,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -613,7 +607,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -654,7 +648,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -728,7 +722,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -785,7 +779,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -830,7 +824,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -879,7 +873,7 @@ jobs:
node ./scripts/print-warnings/print-warnings.js > build/__test_utils__/ReactAllWarnings.js
- name: Display structure of build for PR
run: ls -R build
- run: echo ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }} >> build/COMMIT_SHA
- run: echo ${{ github.event.pull_request.head.sha || github.sha }} >> build/COMMIT_SHA
- run: node ./scripts/tasks/danger
- name: Archive sizebot results
uses: actions/upload-artifact@v4

View File

@@ -19,11 +19,11 @@ An Owner Stack is a string representing the components that are directly respons
* Updated `useId` to use valid CSS selectors, changing format from `:r123:` to `«r123»`. [#32001](https://github.com/facebook/react/pull/32001)
* Added a dev-only warning for null/undefined created in useEffect, useInsertionEffect, and useLayoutEffect. [#32355](https://github.com/facebook/react/pull/32355)
* Fixed a bug where dev-only methods were exported in production builds. React.act is no longer available in production builds. [#32200](https://github.com/facebook/react/pull/32200)
* Improved consistency across prod and dev to improve compatibility with Google Closure Compiler and bindings [#31808](https://github.com/facebook/react/pull/31808)
* Improved consistency across prod and dev to improve compatibility with Google Closure Complier and bindings [#31808](https://github.com/facebook/react/pull/31808)
* Improve passive effect scheduling for consistent task yielding. [#31785](https://github.com/facebook/react/pull/31785)
* Fixed asserts in React Native when passChildrenWhenCloningPersistedNodes is enabled for OffscreenComponent rendering. [#32528](https://github.com/facebook/react/pull/32528)
* Fixed component name resolution for Portal [#32640](https://github.com/facebook/react/pull/32640)
* Added support for beforetoggle and toggle events on the dialog element. [#32479](https://github.com/facebook/react/pull/32479)
* Added support for beforetoggle and toggle events on the dialog element. #32479 [#32479](https://github.com/facebook/react/pull/32479)
### React DOM
* Fixed double warning when the `href` attribute is an empty string [#31783](https://github.com/facebook/react/pull/31783)

View File

@@ -44,7 +44,6 @@ import {
PrintedCompilerPipelineValue,
} from './Output';
import {transformFromAstSync} from '@babel/core';
import {LoggerEvent} from 'babel-plugin-react-compiler/dist/Entrypoint';
function parseInput(
input: string,
@@ -144,7 +143,6 @@ const COMMON_HOOKS: Array<[string, Hook]> = [
function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] {
const results = new Map<string, Array<PrintedCompilerPipelineValue>>();
const error = new CompilerError();
const otherErrors: Array<CompilerErrorDetail> = [];
const upsert: (result: PrintedCompilerPipelineValue) => void = result => {
const entry = results.get(result.name);
if (Array.isArray(entry)) {
@@ -212,11 +210,7 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] {
},
logger: {
debugLogIRs: logIR,
logEvent: (_filename: string | null, event: LoggerEvent) => {
if (event.kind === 'CompileError') {
otherErrors.push(new CompilerErrorDetail(event.detail));
}
},
logEvent: () => {},
},
});
transformOutput = invokeCompiler(source, language, opts);
@@ -243,10 +237,6 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] {
);
}
}
// Only include logger errors if there weren't other errors
if (!error.hasErrors() && otherErrors.length !== 0) {
otherErrors.forEach(e => error.push(e));
}
if (error.hasErrors()) {
return [{kind: 'err', results, error: error}, language];
}

View File

@@ -15,11 +15,6 @@ export enum ErrorSeverity {
* misunderstanding on the users part.
*/
InvalidJS = 'InvalidJS',
/**
* JS syntax that is not supported and which we do not plan to support. Developers should
* rewrite to use supported forms.
*/
UnsupportedJS = 'UnsupportedJS',
/**
* Code that breaks the rules of React.
*/
@@ -246,16 +241,12 @@ export class CompilerError extends Error {
case ErrorSeverity.InvalidJS:
case ErrorSeverity.InvalidReact:
case ErrorSeverity.InvalidConfig:
case ErrorSeverity.UnsupportedJS: {
return true;
}
case ErrorSeverity.CannotPreserveMemoization:
case ErrorSeverity.Todo: {
case ErrorSeverity.Todo:
return false;
}
default: {
default:
assertExhaustive(detail.severity, 'Unhandled error severity');
}
}
});
}

View File

@@ -94,7 +94,7 @@ import {validateLocalsNotReassignedAfterRender} from '../Validation/ValidateLoca
import {outlineFunctions} from '../Optimization/OutlineFunctions';
import {propagatePhiTypes} from '../TypeInference/PropagatePhiTypes';
import {lowerContextAccess} from '../Optimization/LowerContextAccess';
import {validateNoSetStateInEffects} from '../Validation/ValidateNoSetStateInEffects';
import {validateNoSetStateInPassiveEffects} from '../Validation/ValidateNoSetStateInPassiveEffects';
import {validateNoJSXInTryStatement} from '../Validation/ValidateNoJSXInTryStatement';
import {propagateScopeDependenciesHIR} from '../HIR/PropagateScopeDependenciesHIR';
import {outlineJSX} from '../Optimization/OutlineJsx';
@@ -285,15 +285,15 @@ function runWithEnvironment(
}
if (env.config.validateRefAccessDuringRender) {
validateNoRefAccessInRender(hir).unwrap();
// validateNoRefAccessInRender(hir).unwrap();
}
if (env.config.validateNoSetStateInRender) {
validateNoSetStateInRender(hir).unwrap();
}
if (env.config.validateNoSetStateInEffects) {
env.logErrors(validateNoSetStateInEffects(hir));
if (env.config.validateNoSetStateInPassiveEffects) {
env.logErrors(validateNoSetStateInPassiveEffects(hir));
}
if (env.config.validateNoJSXInTryStatements) {

View File

@@ -35,41 +35,8 @@ function throwInvalidReact(
});
CompilerError.throw(detail);
}
function isAutodepsSigil(
arg: NodePath<t.ArgumentPlaceholder | t.SpreadElement | t.Expression>,
): boolean {
// Check for AUTODEPS identifier imported from React
if (arg.isIdentifier() && arg.node.name === 'AUTODEPS') {
const binding = arg.scope.getBinding(arg.node.name);
if (binding && binding.path.isImportSpecifier()) {
const importSpecifier = binding.path.node as t.ImportSpecifier;
if (importSpecifier.imported.type === 'Identifier') {
return (importSpecifier.imported as t.Identifier).name === 'AUTODEPS';
}
}
return false;
}
// Check for React.AUTODEPS member expression
if (arg.isMemberExpression() && !arg.node.computed) {
const object = arg.get('object');
const property = arg.get('property');
if (
object.isIdentifier() &&
object.node.name === 'React' &&
property.isIdentifier() &&
property.node.name === 'AUTODEPS'
) {
return true;
}
}
return false;
}
function assertValidEffectImportReference(
autodepsIndex: number,
numArgs: number,
paths: Array<NodePath<t.Node>>,
context: TraversalState,
): void {
@@ -82,10 +49,11 @@ function assertValidEffectImportReference(
maybeCalleeLoc != null &&
context.inferredEffectLocations.has(maybeCalleeLoc);
/**
* Error on effect calls that still have AUTODEPS in their args
* Only error on untransformed references of the form `useMyEffect(...)`
* or `moduleNamespace.useMyEffect(...)`, with matching argument counts.
* TODO: do we also want a mode to also hard error on non-call references?
*/
const hasAutodepsArg = args.some(isAutodepsSigil);
if (hasAutodepsArg && !hasInferredEffect) {
if (args.length === numArgs && !hasInferredEffect) {
const maybeErrorDiagnostic = matchCompilerDiagnostic(
path,
context.transformErrors,
@@ -160,12 +128,12 @@ export default function validateNoUntransformedReferences(
if (env.inferEffectDependencies) {
for (const {
function: {source, importSpecifierName},
autodepsIndex,
numRequiredArgs,
} of env.inferEffectDependencies) {
const module = getOrInsertWith(moduleLoadChecks, source, () => new Map());
module.set(
importSpecifierName,
assertValidEffectImportReference.bind(null, autodepsIndex),
assertValidEffectImportReference.bind(null, numRequiredArgs),
);
}
}

View File

@@ -1355,85 +1355,13 @@ function lowerStatement(
return;
}
case 'WithStatement': {
builder.errors.push({
reason: `JavaScript 'with' syntax is not supported`,
description: `'with' syntax is considered deprecated and removed from JavaScript standards, consider alternatives`,
severity: ErrorSeverity.UnsupportedJS,
loc: stmtPath.node.loc ?? null,
suggestions: null,
});
lowerValueToTemporary(builder, {
kind: 'UnsupportedNode',
loc: stmtPath.node.loc ?? GeneratedSource,
node: stmtPath.node,
});
return;
}
case 'ClassDeclaration': {
/**
* In theory we could support inline class declarations, but this is rare enough in practice
* and complex enough to support that we don't anticipate supporting anytime soon. Developers
* are encouraged to lift classes out of component/hook declarations.
*/
builder.errors.push({
reason: 'Inline `class` declarations are not supported',
description: `Move class declarations outside of components/hooks`,
severity: ErrorSeverity.UnsupportedJS,
loc: stmtPath.node.loc ?? null,
suggestions: null,
});
lowerValueToTemporary(builder, {
kind: 'UnsupportedNode',
loc: stmtPath.node.loc ?? GeneratedSource,
node: stmtPath.node,
});
return;
}
case 'EnumDeclaration':
case 'TSEnumDeclaration': {
lowerValueToTemporary(builder, {
kind: 'UnsupportedNode',
loc: stmtPath.node.loc ?? GeneratedSource,
node: stmtPath.node,
});
return;
}
case 'ExportAllDeclaration':
case 'ExportDefaultDeclaration':
case 'ExportNamedDeclaration':
case 'ImportDeclaration':
case 'TSExportAssignment':
case 'TSImportEqualsDeclaration': {
builder.errors.push({
reason:
'JavaScript `import` and `export` statements may only appear at the top level of a module',
severity: ErrorSeverity.InvalidJS,
loc: stmtPath.node.loc ?? null,
suggestions: null,
});
lowerValueToTemporary(builder, {
kind: 'UnsupportedNode',
loc: stmtPath.node.loc ?? GeneratedSource,
node: stmtPath.node,
});
return;
}
case 'TSNamespaceExportDeclaration': {
builder.errors.push({
reason:
'TypeScript `namespace` statements may only appear at the top level of a module',
severity: ErrorSeverity.InvalidJS,
loc: stmtPath.node.loc ?? null,
suggestions: null,
});
lowerValueToTemporary(builder, {
kind: 'UnsupportedNode',
loc: stmtPath.node.loc ?? GeneratedSource,
node: stmtPath.node,
});
case 'TypeAlias':
case 'TSInterfaceDeclaration':
case 'TSTypeAliasDeclaration': {
// We do not preserve type annotations/syntax through transformation
return;
}
case 'ClassDeclaration':
case 'DeclareClass':
case 'DeclareExportAllDeclaration':
case 'DeclareExportDeclaration':
@@ -1444,14 +1372,31 @@ function lowerStatement(
case 'DeclareOpaqueType':
case 'DeclareTypeAlias':
case 'DeclareVariable':
case 'EnumDeclaration':
case 'ExportAllDeclaration':
case 'ExportDefaultDeclaration':
case 'ExportNamedDeclaration':
case 'ImportDeclaration':
case 'InterfaceDeclaration':
case 'OpaqueType':
case 'TSDeclareFunction':
case 'TSInterfaceDeclaration':
case 'TSEnumDeclaration':
case 'TSExportAssignment':
case 'TSImportEqualsDeclaration':
case 'TSModuleDeclaration':
case 'TSTypeAliasDeclaration':
case 'TypeAlias': {
// We do not preserve type annotations/syntax through transformation
case 'TSNamespaceExportDeclaration':
case 'WithStatement': {
builder.errors.push({
reason: `(BuildHIR::lowerStatement) Handle ${stmtPath.type} statements`,
severity: ErrorSeverity.Todo,
loc: stmtPath.node.loc ?? null,
suggestions: null,
});
lowerValueToTemporary(builder, {
kind: 'UnsupportedNode',
loc: stmtPath.node.loc ?? GeneratedSource,
node: stmtPath.node,
});
return;
}
default: {
@@ -3001,8 +2946,6 @@ function isReorderableExpression(
}
}
}
case 'TSAsExpression':
case 'TSNonNullExpression':
case 'TypeCastExpression': {
return isReorderableExpression(
builder,
@@ -3559,16 +3502,6 @@ function lowerIdentifier(
return place;
}
default: {
if (binding.kind === 'Global' && binding.name === 'eval') {
builder.errors.push({
reason: `The 'eval' function is not supported`,
description:
'Eval is an anti-pattern in JavaScript, and the code executed cannot be evaluated by React Compiler',
severity: ErrorSeverity.UnsupportedJS,
loc: exprPath.node.loc ?? null,
suggestions: null,
});
}
return lowerValueToTemporary(builder, {
kind: 'LoadGlobal',
binding,

View File

@@ -265,19 +265,21 @@ export const EnvironmentConfigSchema = z.object({
* {
* module: 'react',
* imported: 'useEffect',
* autodepsIndex: 1,
* numRequiredArgs: 1,
* },{
* module: 'MyExperimentalEffectHooks',
* imported: 'useExperimentalEffect',
* autodepsIndex: 2,
* numRequiredArgs: 2,
* },
* ]
* would insert dependencies for calls of `useEffect` imported from `react` and calls of
* useExperimentalEffect` from `MyExperimentalEffectHooks`.
*
* `autodepsIndex` tells the compiler which index we expect the AUTODEPS to appear in.
* With the configuration above, we'd insert dependencies for `useEffect` if it has two
* arguments, and the second is AUTODEPS.
* `numRequiredArgs` tells the compiler the amount of arguments required to append a dependency
* array to the end of the call. With the configuration above, we'd insert dependencies for
* `useEffect` if it is only given a single argument and it would be appended to the argument list.
*
* numRequiredArgs must always be greater than 0, otherwise there is no function to analyze for dependencies
*
* Still experimental.
*/
@@ -286,7 +288,7 @@ export const EnvironmentConfigSchema = z.object({
z.array(
z.object({
function: ExternalFunctionSchema,
autodepsIndex: z.number().min(1, 'autodepsIndex must be > 0'),
numRequiredArgs: z.number().min(1, 'numRequiredArgs must be > 0'),
}),
),
)
@@ -318,10 +320,10 @@ export const EnvironmentConfigSchema = z.object({
validateNoSetStateInRender: z.boolean().default(true),
/**
* Validates that setState is not called synchronously within an effect (useEffect and friends).
* Validates that setState is not called directly within a passive effect (useEffect).
* Scheduling a setState (with an event listener, subscription, etc) is valid.
*/
validateNoSetStateInEffects: z.boolean().default(false),
validateNoSetStateInPassiveEffects: z.boolean().default(false),
/**
* Validates against creating JSX within a try block and recommends using an error boundary

View File

@@ -9,7 +9,6 @@ import {Effect, ValueKind, ValueReason} from './HIR';
import {
BUILTIN_SHAPES,
BuiltInArrayId,
BuiltInAutodepsId,
BuiltInFireFunctionId,
BuiltInFireId,
BuiltInMapId,
@@ -781,7 +780,6 @@ const REACT_APIS: Array<[string, BuiltInType]> = [
BuiltInUseEffectEventId,
),
],
['AUTODEPS', addObject(DEFAULT_SHAPES, BuiltInAutodepsId, [])],
];
TYPED_GLOBALS.push(

View File

@@ -107,17 +107,6 @@ export function mergeConsecutiveBlocks(fn: HIRFunction): void {
merged.merge(block.id, predecessorId);
fn.body.blocks.delete(block.id);
}
for (const [, block] of fn.body.blocks) {
for (const phi of block.phis) {
for (const [predecessorId, operand] of phi.operands) {
const mapped = merged.get(predecessorId);
if (mapped !== predecessorId) {
phi.operands.delete(predecessorId);
phi.operands.set(mapped, operand);
}
}
}
}
markPredecessors(fn.body);
for (const [, {terminal}] of fn.body.blocks) {
if (terminalHasFallthrough(terminal)) {

View File

@@ -384,7 +384,6 @@ export const BuiltInFireId = 'BuiltInFire';
export const BuiltInFireFunctionId = 'BuiltInFireFunction';
export const BuiltInUseEffectEventId = 'BuiltInUseEffectEvent';
export const BuiltinEffectEventId = 'BuiltInEffectEventFunction';
export const BuiltInAutodepsId = 'BuiltInAutoDepsId';
// See getReanimatedModuleType() in Globals.ts — this is part of supporting Reanimated's ref-like types
export const ReanimatedSharedValueId = 'ReanimatedSharedValueId';

View File

@@ -5,6 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import generate from '@babel/generator';
import {CompilerError} from '../CompilerError';
import {printReactiveScopeSummary} from '../ReactiveScopes/PrintReactiveFunction';
import DisjointSet from '../Utils/DisjointSet';
@@ -465,7 +466,7 @@ export function printInstructionValue(instrValue: ReactiveValue): string {
break;
}
case 'UnsupportedNode': {
value = `UnsupportedNode ${instrValue.node.type}`;
value = `UnsupportedNode(${generate(instrValue.node).code})`;
break;
}
case 'LoadLocal': {
@@ -714,7 +715,7 @@ export function printInstructionValue(instrValue: ReactiveValue): string {
break;
}
case 'FinishMemoize': {
value = `FinishMemoize decl=${printPlace(instrValue.decl)}${instrValue.pruned ? ' pruned' : ''}`;
value = `FinishMemoize decl=${printPlace(instrValue.decl)}`;
break;
}
default: {

View File

@@ -57,8 +57,6 @@ import {
} from '../HIR/visitors';
import {empty} from '../Utils/Stack';
import {getOrInsertWith} from '../Utils/utils';
import {deadCodeElimination} from '../Optimization';
import {BuiltInAutodepsId} from '../HIR/ObjectShape';
/**
* Infers reactive dependencies captured by useEffect lambdas and adds them as
@@ -79,7 +77,7 @@ export function inferEffectDependencies(fn: HIRFunction): void {
);
moduleTargets.set(
effectTarget.function.importSpecifierName,
effectTarget.autodepsIndex,
effectTarget.numRequiredArgs,
);
}
const autodepFnLoads = new Map<IdentifierId, number>();
@@ -137,6 +135,7 @@ export function inferEffectDependencies(fn: HIRFunction): void {
}
} else if (value.kind === 'LoadGlobal') {
loadGlobals.add(lvalue.identifier.id);
/*
* TODO: Handle properties on default exports, like
* import React from 'react';
@@ -170,22 +169,8 @@ export function inferEffectDependencies(fn: HIRFunction): void {
) {
const callee =
value.kind === 'CallExpression' ? value.callee : value.property;
const autodepsArgIndex = value.args.findIndex(
arg =>
arg.kind === 'Identifier' &&
arg.identifier.type.kind === 'Object' &&
arg.identifier.type.shapeId === BuiltInAutodepsId,
);
const autodepsArgExpectedIndex = autodepFnLoads.get(
callee.identifier.id,
);
if (
value.args.length > 0 &&
autodepsArgExpectedIndex != null &&
autodepsArgIndex === autodepsArgExpectedIndex &&
autodepFnLoads.has(callee.identifier.id) &&
value.args.length === autodepFnLoads.get(callee.identifier.id) &&
value.args[0].kind === 'Identifier'
) {
// We have a useEffect call with no deps array, so we need to infer the deps
@@ -275,10 +260,7 @@ export function inferEffectDependencies(fn: HIRFunction): void {
effects: null,
},
});
value.args[autodepsArgIndex] = {
...depsPlace,
effect: Effect.Freeze,
};
value.args.push({...depsPlace, effect: Effect.Freeze});
fn.env.inferredEffectLocations.add(callee.loc);
} else if (loadGlobals.has(value.args[0].identifier.id)) {
// Global functions have no reactive dependencies, so we can insert an empty array
@@ -293,10 +275,7 @@ export function inferEffectDependencies(fn: HIRFunction): void {
effects: null,
},
});
value.args[autodepsArgIndex] = {
...depsPlace,
effect: Effect.Freeze,
};
value.args.push({...depsPlace, effect: Effect.Freeze});
fn.env.inferredEffectLocations.add(callee.loc);
}
} else if (
@@ -344,7 +323,6 @@ export function inferEffectDependencies(fn: HIRFunction): void {
// Renumber instructions and fix scope ranges
markInstructionIds(fn.body);
fixScopeAndIdentifierRanges(fn.body);
deadCodeElimination(fn);
fn.env.hasInferredEffect = true;
}
@@ -430,7 +408,6 @@ function rewriteSplices(
rewriteBlocks.push(currBlock);
let cursor = 0;
for (const rewrite of splices) {
while (originalInstrs[cursor].id < rewrite.location) {
CompilerError.invariant(
@@ -452,7 +429,7 @@ function rewriteSplices(
if (rewrite.kind === 'instr') {
currBlock.instructions.push(rewrite.value);
} else if (rewrite.kind === 'block') {
} else {
const {entry, blocks} = rewrite.value;
const entryBlock = blocks.get(entry)!;
// splice in all instructions from the entry block

View File

@@ -28,7 +28,9 @@ import {
isMapType,
isPrimitiveType,
isRefOrRefValue,
isRefValueType,
isSetType,
isUseRefType,
makeIdentifierId,
Phi,
Place,
@@ -219,6 +221,9 @@ export function inferMutationAliasingEffects(
}
}
}
if (fn.env.config.validateRefAccessDuringRender) {
inferRefAccessEffects(fn, isFunctionExpression);
}
return Ok(undefined);
}
@@ -2513,3 +2518,127 @@ export type AbstractValue = {
kind: ValueKind;
reason: ReadonlySet<ValueReason>;
};
function inferRefAccessEffects(
fn: HIRFunction,
_isFunctionExpression: boolean,
): void {
const nullish = new Set<IdentifierId>();
const nullishTest = new Map<IdentifierId, Place>();
let guard: {ref: IdentifierId; fallthrough: BlockId} | null = null;
const temporaries: Map<IdentifierId, Place> = new Map();
function visitOperand(operand: Place): AliasingEffect | null {
const nullTestRef = nullishTest.get(operand.identifier.id);
if (isRefValueType(operand.identifier) || nullTestRef != null) {
const refOperand = nullTestRef ?? operand;
return {
kind: 'Impure',
error: {
severity: ErrorSeverity.InvalidReact,
reason:
'Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)',
loc: refOperand.loc,
description:
refOperand.identifier.name !== null &&
refOperand.identifier.name.kind === 'named'
? `Cannot access ref value \`${refOperand.identifier.name.value}\``
: null,
suggestions: null,
},
place: refOperand,
};
}
return null;
}
for (const block of fn.body.blocks.values()) {
if (guard !== null && guard.fallthrough === block.id) {
guard = null;
}
for (const instr of block.instructions) {
const {lvalue, value} = instr;
if (value.kind === 'LoadLocal' && isUseRefType(value.place.identifier)) {
temporaries.set(lvalue.identifier.id, value.place);
} else if (
value.kind === 'StoreLocal' &&
isUseRefType(value.value.identifier)
) {
temporaries.set(value.lvalue.place.identifier.id, value.value);
temporaries.set(lvalue.identifier.id, value.value);
} else if (
value.kind === 'BinaryExpression' &&
((isRefValueType(value.left.identifier) &&
nullish.has(value.right.identifier.id)) ||
(nullish.has(value.left.identifier.id) &&
isRefValueType(value.right.identifier)))
) {
const refOperand = isRefValueType(value.left.identifier)
? value.left
: value.right;
const operand = temporaries.get(refOperand.identifier.id) ?? refOperand;
nullishTest.set(lvalue.identifier.id, operand);
} else if (value.kind === 'Primitive' && value.value == null) {
nullish.add(lvalue.identifier.id);
} else if (
value.kind === 'PropertyLoad' &&
isUseRefType(value.object.identifier) &&
value.property === 'current'
) {
const refOperand =
temporaries.get(value.object.identifier.id) ?? value.object;
temporaries.set(lvalue.identifier.id, refOperand);
} else if (
value.kind === 'PropertyStore' &&
value.property === 'current' &&
isUseRefType(value.object.identifier)
) {
const refOperand =
temporaries.get(value.object.identifier.id) ?? value.object;
if (guard != null && refOperand.identifier.id === guard.ref) {
// Allow a single write within the guard
guard = null;
} else {
instr.effects ??= [];
instr.effects.push({
kind: 'Impure',
error: {
severity: ErrorSeverity.InvalidReact,
reason:
'Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)',
loc: value.loc,
description:
value.object.identifier.name !== null &&
value.object.identifier.name.kind === 'named'
? `Cannot access ref value \`${value.object.identifier.name.value}\``
: null,
suggestions: null,
},
place: value.object,
});
}
} else {
for (const operand of eachInstructionValueOperand(value)) {
const error = visitOperand(operand);
if (error) {
instr.effects ??= [];
instr.effects.push(error);
}
}
}
}
if (
guard == null &&
block.terminal.kind === 'if' &&
nullishTest.has(block.terminal.test.identifier.id)
) {
const ref = nullishTest.get(block.terminal.test.identifier.id)!;
guard = {ref: ref.identifier.id, fallthrough: block.terminal.fallthrough};
} else {
for (const operand of eachTerminalOperand(block.terminal)) {
const _effect = visitOperand(operand);
// TODO: need a place to store terminal effects generically
}
}
}
}

View File

@@ -11,7 +11,6 @@ import {
Environment,
FunctionExpression,
GeneratedSource,
GotoTerminal,
GotoVariant,
HIRFunction,
IdentifierId,
@@ -20,7 +19,6 @@ import {
Place,
isStatementBlockKind,
makeInstructionId,
mergeConsecutiveBlocks,
promoteTemporary,
reversePostorderBlocks,
} from '../HIR';
@@ -75,10 +73,6 @@ import {retainWhere} from '../Utils/utils';
* - All return statements in the original function expression are replaced with a
* StoreLocal to the temporary we allocated before plus a Goto to the fallthrough
* block (code following the CallExpression).
*
* Note that if the inliined function has only one return, we avoid the labeled block
* and fully inline the code. The original return is replaced with an assignmen to the
* IIFE's call expression lvalue.
*/
export function inlineImmediatelyInvokedFunctionExpressions(
fn: HIRFunction,
@@ -152,75 +146,37 @@ export function inlineImmediatelyInvokedFunctionExpressions(
*/
block.instructions.length = ii;
if (hasSingleExitReturnTerminal(body.loweredFunc.func)) {
block.terminal = {
kind: 'goto',
block: body.loweredFunc.func.body.entry,
id: block.terminal.id,
loc: block.terminal.loc,
variant: GotoVariant.Break,
} as GotoTerminal;
for (const block of body.loweredFunc.func.body.blocks.values()) {
if (block.terminal.kind === 'return') {
block.instructions.push({
id: makeInstructionId(0),
loc: block.terminal.loc,
lvalue: instr.lvalue,
value: {
kind: 'LoadLocal',
loc: block.terminal.loc,
place: block.terminal.value,
},
effects: null,
});
block.terminal = {
kind: 'goto',
block: continuationBlockId,
id: block.terminal.id,
loc: block.terminal.loc,
variant: GotoVariant.Break,
} as GotoTerminal;
}
}
for (const [id, block] of body.loweredFunc.func.body.blocks) {
block.preds.clear();
fn.body.blocks.set(id, block);
}
} else {
/*
* To account for multiple returns within the lambda, we treat the lambda
* as if it were a single labeled statement, and replace all returns with gotos
* to the label fallthrough.
*/
const newTerminal: LabelTerminal = {
block: body.loweredFunc.func.body.entry,
id: makeInstructionId(0),
kind: 'label',
fallthrough: continuationBlockId,
loc: block.terminal.loc,
};
block.terminal = newTerminal;
/*
* To account for complex control flow within the lambda, we treat the lambda
* as if it were a single labeled statement, and replace all returns with gotos
* to the label fallthrough.
*/
const newTerminal: LabelTerminal = {
block: body.loweredFunc.func.body.entry,
id: makeInstructionId(0),
kind: 'label',
fallthrough: continuationBlockId,
loc: block.terminal.loc,
};
block.terminal = newTerminal;
// We store the result in the IIFE temporary
const result = instr.lvalue;
// We store the result in the IIFE temporary
const result = instr.lvalue;
// Declare the IIFE temporary
declareTemporary(fn.env, block, result);
// Declare the IIFE temporary
declareTemporary(fn.env, block, result);
// Promote the temporary with a name as we require this to persist
if (result.identifier.name == null) {
promoteTemporary(result.identifier);
}
// Promote the temporary with a name as we require this to persist
promoteTemporary(result.identifier);
/*
* Rewrite blocks from the lambda to replace any `return` with a
* store to the result and `goto` the continuation block
*/
for (const [id, block] of body.loweredFunc.func.body.blocks) {
block.preds.clear();
rewriteBlock(fn.env, block, continuationBlockId, result);
fn.body.blocks.set(id, block);
}
/*
* Rewrite blocks from the lambda to replace any `return` with a
* store to the result and `goto` the continuation block
*/
for (const [id, block] of body.loweredFunc.func.body.blocks) {
block.preds.clear();
rewriteBlock(fn.env, block, continuationBlockId, result);
fn.body.blocks.set(id, block);
}
/*
@@ -243,7 +199,7 @@ export function inlineImmediatelyInvokedFunctionExpressions(
if (inlinedFunctions.size !== 0) {
// Remove instructions that define lambdas which we inlined
for (const block of fn.body.blocks.values()) {
for (const [, block] of fn.body.blocks) {
retainWhere(
block.instructions,
instr => !inlinedFunctions.has(instr.lvalue.identifier.id),
@@ -257,25 +213,9 @@ export function inlineImmediatelyInvokedFunctionExpressions(
reversePostorderBlocks(fn.body);
markInstructionIds(fn.body);
markPredecessors(fn.body);
mergeConsecutiveBlocks(fn);
}
}
/**
* Returns true if the function has a single exit terminal (throw/return) which is a return
*/
function hasSingleExitReturnTerminal(fn: HIRFunction): boolean {
let hasReturn = false;
let exitCount = 0;
for (const [, block] of fn.body.blocks) {
if (block.terminal.kind === 'return' || block.terminal.kind === 'throw') {
hasReturn ||= block.terminal.kind === 'return';
exitCount++;
}
}
return exitCount === 1 && hasReturn;
}
/*
* Rewrites the block so that all `return` terminals are replaced:
* * Add a StoreLocal <returnValue> = <terminal.value>

View File

@@ -829,14 +829,12 @@ class CollectDependenciesVisitor extends ReactiveFunctionVisitor<
};
}
case 'UnsupportedNode': {
const lvalues = [];
if (lvalue !== null) {
lvalues.push({place: lvalue, level: MemoizationLevel.Never});
}
return {
lvalues,
rvalues: [],
};
CompilerError.invariant(false, {
reason: `Unexpected unsupported node`,
description: null,
loc: value.loc,
suggestions: null,
});
}
default: {
assertExhaustive(
@@ -1066,29 +1064,12 @@ class PruneScopesTransform extends ReactiveFunctionTransform<
const value = instruction.value;
if (value.kind === 'StoreLocal' && value.lvalue.kind === 'Reassign') {
// Complex cases of useMemo inlining result in a temporary that is reassigned
const ids = getOrInsertDefault(
this.reassignments,
value.lvalue.place.identifier.declarationId,
new Set(),
);
ids.add(value.value.identifier);
} else if (
value.kind === 'LoadLocal' &&
value.place.identifier.scope != null &&
instruction.lvalue != null &&
instruction.lvalue.identifier.scope == null
) {
/*
* Simpler cases result in a direct assignment to the original lvalue, with a
* LoadLocal
*/
const ids = getOrInsertDefault(
this.reassignments,
instruction.lvalue.identifier.declarationId,
new Set(),
);
ids.add(value.place.identifier);
} else if (value.kind === 'FinishMemoize') {
let decls;
if (value.decl.identifier.scope == null) {

View File

@@ -75,21 +75,21 @@ const testComplexConfigDefaults: PartialEnvironmentConfig = {
source: 'react',
importSpecifierName: 'useEffect',
},
autodepsIndex: 1,
numRequiredArgs: 1,
},
{
function: {
source: 'shared-runtime',
importSpecifierName: 'useSpecialEffect',
},
autodepsIndex: 2,
numRequiredArgs: 2,
},
{
function: {
source: 'useEffectWrapper',
importSpecifierName: 'default',
},
autodepsIndex: 1,
numRequiredArgs: 1,
},
],
};

View File

@@ -11,22 +11,20 @@ import {
IdentifierId,
isSetStateType,
isUseEffectHookType,
isUseInsertionEffectHookType,
isUseLayoutEffectHookType,
Place,
} from '../HIR';
import {eachInstructionValueOperand} from '../HIR/visitors';
import {Result} from '../Utils/Result';
/**
* Validates against calling setState in the body of an effect (useEffect and friends),
* Validates against calling setState in the body of a *passive* effect (useEffect),
* while allowing calling setState in callbacks scheduled by the effect.
*
* Calling setState during execution of a useEffect triggers a re-render, which is
* often bad for performance and frequently has more efficient and straightforward
* alternatives. See https://react.dev/learn/you-might-not-need-an-effect for examples.
*/
export function validateNoSetStateInEffects(
export function validateNoSetStateInPassiveEffects(
fn: HIRFunction,
): Result<void, CompilerError> {
const setStateFunctions: Map<IdentifierId, Place> = new Map();
@@ -81,11 +79,7 @@ export function validateNoSetStateInEffects(
instr.value.kind === 'MethodCall'
? instr.value.receiver
: instr.value.callee;
if (
isUseEffectHookType(callee.identifier) ||
isUseLayoutEffectHookType(callee.identifier) ||
isUseInsertionEffectHookType(callee.identifier)
) {
if (isUseEffectHookType(callee.identifier)) {
const arg = instr.value.args[0];
if (arg !== undefined && arg.kind === 'Identifier') {
const setState = setStateFunctions.get(arg.identifier.id);

View File

@@ -445,13 +445,11 @@ class Visitor extends ReactiveFunctionVisitor<VisitorState> {
*/
this.recordTemporaries(instruction, state);
const value = instruction.value;
// Track reassignments from inlining of manual memo
if (
value.kind === 'StoreLocal' &&
value.lvalue.kind === 'Reassign' &&
state.manualMemoState != null
) {
// Complex cases of inlining end up with a temporary that is reassigned
const ids = getOrInsertDefault(
state.manualMemoState.reassignments,
value.lvalue.place.identifier.declarationId,
@@ -459,21 +457,6 @@ class Visitor extends ReactiveFunctionVisitor<VisitorState> {
);
ids.add(value.value.identifier);
}
if (
value.kind === 'LoadLocal' &&
value.place.identifier.scope != null &&
instruction.lvalue != null &&
instruction.lvalue.identifier.scope == null &&
state.manualMemoState != null
) {
// Simpler cases of inlining assign to the original IIFE lvalue
const ids = getOrInsertDefault(
state.manualMemoState.reassignments,
instruction.lvalue.identifier.declarationId,
new Set(),
);
ids.add(value.place.identifier);
}
if (value.kind === 'StartMemoize') {
let depsFromSource: Array<ManualMemoDependency> | null = null;
if (value.deps != null) {

View File

@@ -33,12 +33,12 @@ describe('parseConfigPragma()', () => {
source: 'react',
importSpecifierName: 'useEffect',
},
autodepsIndex: 0,
numRequiredArgs: 0,
},
],
} as any);
}).toThrowErrorMatchingInlineSnapshot(
`"InvalidConfig: Could not validate environment config. Update React Compiler config to fix the error. Validation error: autodepsIndex must be > 0 at "inferEffectDependencies[0].autodepsIndex""`,
`"InvalidConfig: Could not validate environment config. Update React Compiler config to fix the error. Validation error: numRequiredArgs must be > 0 at "inferEffectDependencies[0].numRequiredArgs""`,
);
});

View File

@@ -26,16 +26,20 @@ import { c as _c } from "react/compiler-runtime";
import { getNull } from "shared-runtime";
function Component(props) {
const $ = _c(2);
const $ = _c(3);
let t0;
let items;
if ($[0] !== props.a) {
items = getNull() ?? [];
t0 = getNull() ?? [];
items = t0;
items.push(props.a);
$[0] = props.a;
$[1] = items;
$[2] = t0;
} else {
items = $[1];
t0 = $[2];
}
return items;
}

View File

@@ -52,13 +52,15 @@ function Component(t0) {
}
const onClick = t1;
let t2;
let t3;
if ($[2] !== onClick) {
t2 = <div onClick={onClick}>{someGlobal.value}</div>;
t3 = <div onClick={onClick}>{someGlobal.value}</div>;
$[2] = onClick;
$[3] = t2;
$[3] = t3;
} else {
t2 = $[3];
t3 = $[3];
}
t2 = t3;
return t2;
}

View File

@@ -30,46 +30,50 @@ function Component(props) {
const $ = _c(4);
const [x] = useState(0);
let t0;
if ($[0] !== x) {
t0 = calculateExpensiveNumber(x);
$[0] = x;
$[1] = t0;
} else {
t0 = $[1];
}
const expensiveNumber = t0;
let t1;
if ($[2] !== expensiveNumber) {
t1 = <div>{expensiveNumber}</div>;
$[2] = expensiveNumber;
$[3] = t1;
if ($[0] !== x) {
t1 = calculateExpensiveNumber(x);
$[0] = x;
$[1] = t1;
} else {
t1 = $[3];
t1 = $[1];
}
return t1;
t0 = t1;
const expensiveNumber = t0;
let t2;
if ($[2] !== expensiveNumber) {
t2 = <div>{expensiveNumber}</div>;
$[2] = expensiveNumber;
$[3] = t2;
} else {
t2 = $[3];
}
return t2;
}
function Component2(props) {
const $ = _c(4);
const [x] = useState(0);
let t0;
if ($[0] !== x) {
t0 = calculateExpensiveNumber(x);
$[0] = x;
$[1] = t0;
} else {
t0 = $[1];
}
const expensiveNumber = t0;
let t1;
if ($[2] !== expensiveNumber) {
t1 = <div>{expensiveNumber}</div>;
$[2] = expensiveNumber;
$[3] = t1;
if ($[0] !== x) {
t1 = calculateExpensiveNumber(x);
$[0] = x;
$[1] = t1;
} else {
t1 = $[3];
t1 = $[1];
}
return t1;
t0 = t1;
const expensiveNumber = t0;
let t2;
if ($[2] !== expensiveNumber) {
t2 = <div>{expensiveNumber}</div>;
$[2] = expensiveNumber;
$[3] = t2;
} else {
t2 = $[3];
}
return t2;
}
```

View File

@@ -32,46 +32,50 @@ function Component(props) {
const $ = _c(4);
const [x] = useState(0);
let t0;
if ($[0] !== x) {
t0 = calculateExpensiveNumber(x);
$[0] = x;
$[1] = t0;
} else {
t0 = $[1];
}
const expensiveNumber = t0;
let t1;
if ($[2] !== expensiveNumber) {
t1 = <div>{expensiveNumber}</div>;
$[2] = expensiveNumber;
$[3] = t1;
if ($[0] !== x) {
t1 = calculateExpensiveNumber(x);
$[0] = x;
$[1] = t1;
} else {
t1 = $[3];
t1 = $[1];
}
return t1;
t0 = t1;
const expensiveNumber = t0;
let t2;
if ($[2] !== expensiveNumber) {
t2 = <div>{expensiveNumber}</div>;
$[2] = expensiveNumber;
$[3] = t2;
} else {
t2 = $[3];
}
return t2;
}
function Component2(props) {
const $ = _c(4);
const [x] = useState(0);
let t0;
if ($[0] !== x) {
t0 = calculateExpensiveNumber(x);
$[0] = x;
$[1] = t0;
} else {
t0 = $[1];
}
const expensiveNumber = t0;
let t1;
if ($[2] !== expensiveNumber) {
t1 = <div>{expensiveNumber}</div>;
$[2] = expensiveNumber;
$[3] = t1;
if ($[0] !== x) {
t1 = calculateExpensiveNumber(x);
$[0] = x;
$[1] = t1;
} else {
t1 = $[3];
t1 = $[1];
}
return t1;
t0 = t1;
const expensiveNumber = t0;
let t2;
if ($[2] !== expensiveNumber) {
t2 = <div>{expensiveNumber}</div>;
$[2] = expensiveNumber;
$[3] = t2;
} else {
t2 = $[3];
}
return t2;
}
```

View File

@@ -30,23 +30,25 @@ function Component(props) {
const $ = _c(4);
const [x] = React.useState(0);
let t0;
if ($[0] !== x) {
t0 = calculateExpensiveNumber(x);
$[0] = x;
$[1] = t0;
} else {
t0 = $[1];
}
const expensiveNumber = t0;
let t1;
if ($[2] !== expensiveNumber) {
t1 = <div>{expensiveNumber}</div>;
$[2] = expensiveNumber;
$[3] = t1;
if ($[0] !== x) {
t1 = calculateExpensiveNumber(x);
$[0] = x;
$[1] = t1;
} else {
t1 = $[3];
t1 = $[1];
}
return t1;
t0 = t1;
const expensiveNumber = t0;
let t2;
if ($[2] !== expensiveNumber) {
t2 = <div>{expensiveNumber}</div>;
$[2] = expensiveNumber;
$[3] = t2;
} else {
t2 = $[3];
}
return t2;
}
export const FIXTURE_ENTRYPOINT = {

View File

@@ -36,28 +36,30 @@ function Component(props) {
const $ = _c(4);
const [x] = React.useState(0);
let t0;
if ($[0] !== x) {
t0 = calculateExpensiveNumber(x);
$[0] = x;
$[1] = t0;
} else {
t0 = $[1];
}
const expensiveNumber = 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) {
t1 = (
t2 = (
<div>
{expensiveNumber}
{`${someImport}`}
</div>
);
$[2] = expensiveNumber;
$[3] = t1;
$[3] = t2;
} else {
t1 = $[3];
t2 = $[3];
}
return t1;
return t2;
}
export const FIXTURE_ENTRYPOINT = {

View File

@@ -36,14 +36,15 @@ import { useMemo } from "react";
function Component(props) {
const $ = _c(2);
let t0;
let t1;
if ($[0] !== props.value) {
t0 = { value: props.value };
t1 = { value: props.value };
$[0] = props.value;
$[1] = t0;
$[1] = t1;
} else {
t0 = $[1];
t1 = $[1];
}
const handlers = t0;
const handlers = t1;
bb0: switch (props.test) {
case true: {
console.log(handlers.value);
@@ -51,7 +52,9 @@ function Component(props) {
}
default:
}
const outerHandlers = handlers;
t0 = handlers;
const outerHandlers = t0;
return outerHandlers;
}

View File

@@ -37,9 +37,11 @@ function useTest() {
const t1 = (w = 42);
const t2 = w;
let t3;
w = 999;
t0 = makeArray(t1, t2, 2);
t3 = 2;
t0 = makeArray(t1, t2, t3);
$[0] = t0;
} else {
t0 = $[0];

View File

@@ -37,9 +37,11 @@ function useTest() {
const t1 = (w.x = 42);
const t2 = w.x;
let t3;
w.x = 999;
t0 = makeArray(t1, t2, 2);
t3 = 2;
t0 = makeArray(t1, t2, t3);
$[0] = t0;
} else {
t0 = $[0];

View File

@@ -32,9 +32,11 @@ function useTest() {
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
const t1 = print(1);
let t2;
print(2);
t0 = makeArray(t1, 2);
t2 = 2;
t0 = makeArray(t1, t2);
$[0] = t0;
} else {
t0 = $[0];

View File

@@ -29,33 +29,37 @@ function useHook(t0) {
const $ = _c(7);
const { a, b } = t0;
let t1;
if ($[0] !== a) {
t1 = identity({ a });
$[0] = a;
$[1] = t1;
} else {
t1 = $[1];
}
const valA = t1;
let t2;
if ($[2] !== b) {
t2 = identity([b]);
$[2] = b;
$[3] = t2;
if ($[0] !== a) {
t2 = identity({ a });
$[0] = a;
$[1] = t2;
} else {
t2 = $[3];
t2 = $[1];
}
const valB = t2;
t1 = t2;
const valA = t1;
let t3;
let t4;
if ($[2] !== b) {
t4 = identity([b]);
$[2] = b;
$[3] = t4;
} else {
t4 = $[3];
}
t3 = t4;
const valB = t3;
let t5;
if ($[4] !== valA || $[5] !== valB) {
t3 = [valA, valB];
t5 = [valA, valB];
$[4] = valA;
$[5] = valB;
$[6] = t3;
$[6] = t5;
} else {
t3 = $[6];
t5 = $[6];
}
return t3;
return t5;
}
export const FIXTURE_ENTRYPOINT = {

View File

@@ -34,8 +34,10 @@ function Component(props) {
let Component;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
Component = Stringify;
let t0;
Component = Component;
t0 = Component;
Component = t0;
$[0] = Component;
} else {
Component = $[0];

View File

@@ -28,18 +28,20 @@ import { c as _c } from "react/compiler-runtime";
function Foo() {
const $ = _c(1);
let t0;
let t1;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = function a(t1) {
const x_0 = t1 === undefined ? _temp : t1;
return (function b(t2) {
const y_0 = t2 === undefined ? [] : t2;
t1 = function a(t2) {
const x_0 = t2 === undefined ? _temp : t2;
return (function b(t3) {
const y_0 = t3 === undefined ? [] : t3;
return [x_0, y_0];
})();
};
$[0] = t0;
$[0] = t1;
} else {
t0 = $[0];
t1 = $[0];
}
t0 = t1;
return t0;
}
function _temp() {}

View File

@@ -28,6 +28,7 @@ import * as React from "react";
function Component(props) {
const $ = _c(2);
let t0;
let x;
if ($[0] !== props.value) {
x = [];
@@ -37,7 +38,8 @@ function Component(props) {
} else {
x = $[1];
}
const x_0 = x;
t0 = x;
const x_0 = t0;
return x_0;
}

View File

@@ -24,8 +24,6 @@ export const FIXTURE_ENTRYPOINT = {
4 | const ref = useRef();
> 5 | useEffect(() => {}, [ref.current]);
| ^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (5:5)
InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (5:5)
6 | }
7 |
8 | export const FIXTURE_ENTRYPOINT = {

View File

@@ -19,6 +19,8 @@ function Component(props) {
3 | const ref = useRef(null);
> 4 | const value = ref.current;
| ^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (4:4)
InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef). Cannot access ref value `value` (5:5)
5 | return value;
6 | }
7 |

View File

@@ -19,12 +19,17 @@ function Component(props) {
## Error
```
7 | return <Foo item={item} current={current} />;
8 | };
> 9 | return <Items>{props.items.map(item => renderItem(item))}</Items>;
| ^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (9:9)
10 | }
11 |
4 | const renderItem = item => {
5 | const aliasedRef = ref;
> 6 | const current = aliasedRef.current;
| ^^^^^^^^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (6:6)
InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef). Cannot access ref value `current` (7:7)
InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (7:7)
7 | return <Foo item={item} current={current} />;
8 | };
9 | return <Items>{props.items.map(item => renderItem(item))}</Items>;
```

View File

@@ -21,15 +21,13 @@ function Component() {
## Error
```
7 | };
8 | const changeRef = setRef;
> 9 | changeRef();
| ^^^^^^^^^ InvalidReact: This function accesses a ref value (the `current` property), which may not be accessed during render. (https://react.dev/reference/react/useRef) (9:9)
InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (9:9)
10 |
11 | return <button ref={ref} />;
12 | }
4 |
5 | const setRef = () => {
> 6 | ref.current = false;
| ^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (6:6)
7 | };
8 | const changeRef = setRef;
9 | changeRef();
```

View File

@@ -1,24 +0,0 @@
## Input
```javascript
function Component(props) {
eval('props.x = true');
return <div />;
}
```
## Error
```
1 | function Component(props) {
> 2 | eval('props.x = true');
| ^^^^ UnsupportedJS: The 'eval' function is not supported. Eval is an anti-pattern in JavaScript, and the code executed cannot be evaluated by React Compiler (2:2)
3 | return <div />;
4 | }
5 |
```

View File

@@ -1,4 +0,0 @@
function Component(props) {
eval('props.x = true');
return <div />;
}

View File

@@ -18,6 +18,10 @@ function Component({ref}) {
2 | function Component({ref}) {
> 3 | const value = ref.current;
| ^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (3:3)
InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef). Cannot access ref value `value` (4:4)
InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (4:4)
4 | return <div>{value}</div>;
5 | }
6 |

View File

@@ -18,6 +18,10 @@ function Component(props) {
2 | function Component(props) {
> 3 | const value = props.ref.current;
| ^^^^^^^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (3:3)
InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef). Cannot access ref value `value` (4:4)
InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (4:4)
4 | return <div>{value}</div>;
5 | }
6 |

View File

@@ -18,12 +18,17 @@ function Component(props) {
## Error
```
6 | return <Foo item={item} current={current} />;
7 | };
> 8 | return <Items>{props.items.map(item => renderItem(item))}</Items>;
| ^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (8:8)
9 | }
10 |
3 | const ref = useRef(null);
4 | const renderItem = item => {
> 5 | const current = ref.current;
| ^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (5:5)
InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef). Cannot access ref value `current` (6:6)
InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (6:6)
6 | return <Foo item={item} current={current} />;
7 | };
8 | return <Items>{props.items.map(item => renderItem(item))}</Items>;
```

View File

@@ -19,8 +19,6 @@ function Component(props) {
3 | const ref = useRef(null);
> 4 | ref.current = props.value;
| ^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (4:4)
InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (5:5)
5 | return ref.current;
6 | }
7 |

View File

@@ -27,9 +27,9 @@ export const FIXTURE_ENTRYPOINT = {
4 | component C() {
5 | const r = useRef(null);
> 6 | const guard = r.current == null;
| ^^^^^^^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (6:6)
| ^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef). Cannot access ref value `r` (6:6)
InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef). Cannot access ref value `guard` (7:7)
InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (8:8)
7 | if (guard) {
8 | r.current = 1;
9 | }

View File

@@ -84,7 +84,7 @@ let moduleLocal = false;
> 3 | var x = [];
| ^^^^^^^^^^^ Todo: (BuildHIR::lowerStatement) Handle var kinds in VariableDeclaration (3:3)
UnsupportedJS: Inline `class` declarations are not supported. Move class declarations outside of components/hooks (5:10)
Todo: (BuildHIR::lowerStatement) Handle ClassDeclaration statements (5:10)
Todo: (BuildHIR::lowerStatement) Handle non-variable initialization in ForStatement (20:22)

View File

@@ -34,15 +34,13 @@ export const FIXTURE_ENTRYPOINT = {
## Error
```
15 | ref.current.inner = null;
13 | // The ref is modified later, extending its range and preventing memoization of onChange
14 | const reset = () => {
> 15 | ref.current.inner = null;
| ^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (15:15)
16 | };
> 17 | reset();
| ^^^^^ InvalidReact: This function accesses a ref value (the `current` property), which may not be accessed during render. (https://react.dev/reference/react/useRef) (17:17)
InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (17:17)
17 | reset();
18 |
19 | return <input onChange={onChange} />;
20 | }
```

View File

@@ -42,32 +42,34 @@ function Component(props) {
const c1 = __c;
const $c = c1;
let t0;
if ($[0] !== $c) {
t0 = [$c];
$[0] = $c;
$[1] = t0;
} else {
t0 = $[1];
}
const array = t0;
let t1;
if ($[2] !== state) {
t1 = [state];
$[2] = state;
$[3] = t1;
if ($[0] !== $c) {
t1 = [$c];
$[0] = $c;
$[1] = t1;
} else {
t1 = $[3];
t1 = $[1];
}
t0 = t1;
const array = t0;
let t2;
if ($[4] !== array || $[5] !== t1) {
t2 = <ValidateMemoization inputs={t1} output={array} />;
$[4] = array;
$[5] = t1;
$[6] = t2;
if ($[2] !== state) {
t2 = [state];
$[2] = state;
$[3] = t2;
} else {
t2 = $[6];
t2 = $[3];
}
return t2;
let t3;
if ($[4] !== array || $[5] !== t2) {
t3 = <ValidateMemoization inputs={t2} output={array} />;
$[4] = array;
$[5] = t2;
$[6] = t3;
} else {
t3 = $[6];
}
return t3;
}
export const FIXTURE_ENTRYPOINT = {

View File

@@ -63,21 +63,23 @@ function Component() {
unsafeUpdateConst();
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = [{ pretendConst }];
$[0] = t0;
} else {
t0 = $[0];
}
const value = t0;
let t1;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t1 = <ValidateMemoization inputs={[]} output={value} />;
$[1] = t1;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t1 = [{ pretendConst }];
$[0] = t1;
} else {
t1 = $[1];
t1 = $[0];
}
return t1;
t0 = t1;
const value = t0;
let t2;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t2 = <ValidateMemoization inputs={[]} output={value} />;
$[1] = t2;
} else {
t2 = $[1];
}
return t2;
}
function _temp() {
unsafeResetConst();

View File

@@ -74,21 +74,23 @@ function Component() {
unsafeUpdateConst();
let t0;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t0 = [{ pretendConst }];
$[1] = t0;
} else {
t0 = $[1];
}
const value = t0;
let t1;
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
t1 = <ValidateMemoization inputs={[pretendConst]} output={value} />;
$[2] = t1;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t1 = [{ pretendConst }];
$[1] = t1;
} else {
t1 = $[2];
t1 = $[1];
}
return t1;
t0 = t1;
const value = t0;
let t2;
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
t2 = <ValidateMemoization inputs={[pretendConst]} output={value} />;
$[2] = t2;
} else {
t2 = $[2];
}
return t2;
}
function _temp() {
unsafeResetConst();

View File

@@ -38,34 +38,36 @@ function Component(props) {
$[0] = "20945b0193e529df490847c66111b38d7b02485d5b53d0829ff3b23af87b105c";
}
const [state] = useState(0);
const t0 = state * 2;
let t1;
if ($[1] !== t0) {
t1 = [t0];
$[1] = t0;
$[2] = t1;
} else {
t1 = $[2];
}
const doubled = t1;
let t0;
const t1 = state * 2;
let t2;
if ($[3] !== state) {
t2 = [state];
$[3] = state;
$[4] = t2;
if ($[1] !== t1) {
t2 = [t1];
$[1] = t1;
$[2] = t2;
} else {
t2 = $[4];
t2 = $[2];
}
t0 = t2;
const doubled = t0;
let t3;
if ($[5] !== doubled || $[6] !== t2) {
t3 = <ValidateMemoization inputs={t2} output={doubled} />;
$[5] = doubled;
$[6] = t2;
$[7] = t3;
if ($[3] !== state) {
t3 = [state];
$[3] = state;
$[4] = t3;
} else {
t3 = $[7];
t3 = $[4];
}
return t3;
let t4;
if ($[5] !== doubled || $[6] !== t3) {
t4 = <ValidateMemoization inputs={t3} output={doubled} />;
$[5] = doubled;
$[6] = t3;
$[7] = t4;
} else {
t4 = $[7];
}
return t4;
}
export const FIXTURE_ENTRYPOINT = {

View File

@@ -40,34 +40,36 @@ function Component(t0) {
const $ = _c(7);
const { data } = t0;
let t1;
let t2;
if ($[0] !== data.name) {
t1 = fbt._("{name}", [fbt._param("name", data.name ?? "")], {
t2 = fbt._("{name}", [fbt._param("name", data.name ?? "")], {
hk: "csQUH",
});
$[0] = data.name;
$[1] = t1;
$[1] = t2;
} else {
t1 = $[1];
t2 = $[1];
}
t1 = t2;
const el = t1;
let t2;
if ($[2] !== data.name) {
t2 = [data.name];
$[2] = data.name;
$[3] = t2;
} else {
t2 = $[3];
}
let t3;
if ($[4] !== el || $[5] !== t2) {
t3 = <ValidateMemoization inputs={t2} output={el} />;
$[4] = el;
$[5] = t2;
$[6] = t3;
if ($[2] !== data.name) {
t3 = [data.name];
$[2] = data.name;
$[3] = t3;
} else {
t3 = $[6];
t3 = $[3];
}
return t3;
let t4;
if ($[4] !== el || $[5] !== t3) {
t4 = <ValidateMemoization inputs={t3} output={el} />;
$[4] = el;
$[5] = t3;
$[6] = t4;
} else {
t4 = $[6];
}
return t4;
}
const props1 = { data: { name: "Mike" } };

View File

@@ -1,60 +0,0 @@
## Input
```javascript
// @flow
function Component(props) {
enum Bool {
True = 'true',
False = 'false',
}
let bool: Bool = Bool.False;
if (props.value) {
bool = Bool.True;
}
return <div>{bool}</div>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: true}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
function Component(props) {
const $ = _c(2);
enum Bool {
True = "true",
False = "false",
}
let bool = Bool.False;
if (props.value) {
bool = Bool.True;
}
let t0;
if ($[0] !== bool) {
t0 = <div>{bool}</div>;
$[0] = bool;
$[1] = t0;
} else {
t0 = $[1];
}
return t0;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ value: true }],
};
```
### Eval output
(kind: exception) Bool is not defined

View File

@@ -1,18 +0,0 @@
// @flow
function Component(props) {
enum Bool {
True = 'true',
False = 'false',
}
let bool: Bool = Bool.False;
if (props.value) {
bool = Bool.True;
}
return <div>{bool}</div>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: true}],
};

View File

@@ -47,14 +47,17 @@ function Component(t0) {
const $ = _c(19);
const { a, b } = t0;
let t1;
let t2;
if ($[0] !== a) {
t1 = [a];
t2 = [a];
$[0] = a;
$[1] = t1;
$[1] = t2;
} else {
t1 = $[1];
t2 = $[1];
}
t1 = t2;
const x = t1;
let t3;
let items;
if ($[2] !== b || $[3] !== x) {
items = [b];
@@ -67,57 +70,59 @@ function Component(t0) {
} else {
items = $[4];
}
const y = items;
let t2;
if ($[5] !== a) {
t2 = [a];
$[5] = a;
$[6] = t2;
} else {
t2 = $[6];
}
let t3;
if ($[7] !== t2 || $[8] !== x) {
t3 = <ValidateMemoization inputs={t2} output={x} />;
$[7] = t2;
$[8] = x;
$[9] = t3;
} else {
t3 = $[9];
}
t3 = items;
const y = t3;
let t4;
if ($[10] !== b || $[11] !== x) {
t4 = [x, b];
$[10] = b;
$[11] = x;
$[12] = t4;
if ($[5] !== a) {
t4 = [a];
$[5] = a;
$[6] = t4;
} else {
t4 = $[12];
t4 = $[6];
}
let t5;
if ($[13] !== t4 || $[14] !== y) {
t5 = <ValidateMemoization inputs={t4} output={y} />;
$[13] = t4;
$[14] = y;
$[15] = t5;
if ($[7] !== t4 || $[8] !== x) {
t5 = <ValidateMemoization inputs={t4} output={x} />;
$[7] = t4;
$[8] = x;
$[9] = t5;
} else {
t5 = $[15];
t5 = $[9];
}
let t6;
if ($[16] !== t3 || $[17] !== t5) {
t6 = (
if ($[10] !== b || $[11] !== x) {
t6 = [x, b];
$[10] = b;
$[11] = x;
$[12] = t6;
} else {
t6 = $[12];
}
let t7;
if ($[13] !== t6 || $[14] !== y) {
t7 = <ValidateMemoization inputs={t6} output={y} />;
$[13] = t6;
$[14] = y;
$[15] = t7;
} else {
t7 = $[15];
}
let t8;
if ($[16] !== t5 || $[17] !== t7) {
t8 = (
<>
{t3}
{t5}
{t7}
</>
);
$[16] = t3;
$[17] = t5;
$[18] = t6;
$[16] = t5;
$[17] = t7;
$[18] = t8;
} else {
t6 = $[18];
t8 = $[18];
}
return t6;
return t8;
}
export const FIXTURE_ENTRYPOINT = {

View File

@@ -34,6 +34,7 @@ import { ValidateMemoization } from "shared-runtime";
function Component(props) {
const $ = _c(7);
let t0;
let a;
if ($[0] !== props.name) {
a = [];
@@ -47,25 +48,26 @@ function Component(props) {
} else {
a = $[1];
}
const a_0 = a;
let t0;
if ($[2] !== props.name) {
t0 = [props.name];
$[2] = props.name;
$[3] = t0;
} else {
t0 = $[3];
}
t0 = a;
const a_0 = t0;
let t1;
if ($[4] !== a_0 || $[5] !== t0) {
t1 = <ValidateMemoization inputs={t0} output={a_0} />;
$[4] = a_0;
$[5] = t0;
$[6] = t1;
if ($[2] !== props.name) {
t1 = [props.name];
$[2] = props.name;
$[3] = t1;
} else {
t1 = $[6];
t1 = $[3];
}
return t1;
let t2;
if ($[4] !== a_0 || $[5] !== t1) {
t2 = <ValidateMemoization inputs={t1} output={a_0} />;
$[4] = a_0;
$[5] = t1;
$[6] = t2;
} else {
t2 = $[6];
}
return t2;
}
export const FIXTURE_ENTRYPOINT = {

View File

@@ -3,13 +3,13 @@
```javascript
// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @inferEffectDependencies
import {useEffect, AUTODEPS} from 'react';
import {useEffect} from 'react';
import {print} from 'shared-runtime';
function ReactiveVariable({propVal}) {
'use memo if(invalid identifier)';
const arr = [propVal];
useEffect(() => print(arr), AUTODEPS);
useEffect(() => print(arr));
}
export const FIXTURE_ENTRYPOINT = {
@@ -25,8 +25,8 @@ export const FIXTURE_ENTRYPOINT = {
```
6 | 'use memo if(invalid identifier)';
7 | const arr = [propVal];
> 8 | useEffect(() => print(arr), AUTODEPS);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (8:8)
> 8 | useEffect(() => print(arr));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (8:8)
9 | }
10 |
11 | export const FIXTURE_ENTRYPOINT = {

View File

@@ -1,11 +1,11 @@
// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @inferEffectDependencies
import {useEffect, AUTODEPS} from 'react';
import {useEffect} from 'react';
import {print} from 'shared-runtime';
function ReactiveVariable({propVal}) {
'use memo if(invalid identifier)';
const arr = [propVal];
useEffect(() => print(arr), AUTODEPS);
useEffect(() => print(arr));
}
export const FIXTURE_ENTRYPOINT = {

View File

@@ -46,7 +46,7 @@ const React$useMemo = React.useMemo;
const Internal$Reassigned$useHook = useHook;
function Component() {
const $ = _c(7);
const $ = _c(8);
const [state] = React$useState(0);
const object = Internal$Reassigned$useHook();
let t0;
@@ -59,30 +59,34 @@ function Component() {
}
const json = t0;
let t1;
if ($[2] !== state) {
const doubledArray = makeArray(state);
t1 = doubledArray.join("");
$[2] = state;
$[3] = t1;
} else {
t1 = $[3];
}
let t2;
if ($[4] !== json || $[5] !== t1) {
t2 = (
if ($[2] !== state) {
t1 = makeArray(state);
const doubledArray = t1;
t2 = doubledArray.join("");
$[2] = state;
$[3] = t2;
$[4] = t1;
} else {
t2 = $[3];
t1 = $[4];
}
let t3;
if ($[5] !== json || $[6] !== t2) {
t3 = (
<div>
{t1}
{t2}
{json}
</div>
);
$[4] = json;
$[5] = t1;
$[5] = json;
$[6] = t2;
$[7] = t3;
} else {
t2 = $[6];
t3 = $[7];
}
return t2;
return t3;
}
export const FIXTURE_ENTRYPOINT = {

View File

@@ -22,16 +22,20 @@ export const FIXTURE_ENTRYPOINT = {
```javascript
import { c as _c } from "react/compiler-runtime";
function Component(props) {
const $ = _c(2);
const $ = _c(3);
let t0;
let items;
if ($[0] !== props.a) {
items = [];
t0 = [];
items = t0;
items.push(props.a);
$[0] = props.a;
$[1] = items;
$[2] = t0;
} else {
items = $[1];
t0 = $[2];
}
return items;
}

View File

@@ -4,10 +4,9 @@
```javascript
// @inferEffectDependencies @compilationMode:"infer" @panicThreshold:"none"
import useMyEffect from 'useEffectWrapper';
import {AUTODEPS} from 'react';
function nonReactFn(arg) {
useMyEffect(() => [1, 2, arg], AUTODEPS);
useMyEffect(() => [1, 2, arg]);
}
```
@@ -16,12 +15,12 @@ function nonReactFn(arg) {
## Error
```
4 |
5 | function nonReactFn(arg) {
> 6 | useMyEffect(() => [1, 2, arg], AUTODEPS);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (6:6)
7 | }
8 |
3 |
4 | function nonReactFn(arg) {
> 5 | useMyEffect(() => [1, 2, arg]);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (5:5)
6 | }
7 |
```

View File

@@ -1,7 +1,6 @@
// @inferEffectDependencies @compilationMode:"infer" @panicThreshold:"none"
import useMyEffect from 'useEffectWrapper';
import {AUTODEPS} from 'react';
function nonReactFn(arg) {
useMyEffect(() => [1, 2, arg], AUTODEPS);
useMyEffect(() => [1, 2, arg]);
}

View File

@@ -3,10 +3,10 @@
```javascript
// @inferEffectDependencies @compilationMode:"infer" @panicThreshold:"none"
import {useEffect, AUTODEPS} from 'react';
import {useEffect} from 'react';
function nonReactFn(arg) {
useEffect(() => [1, 2, arg], AUTODEPS);
useEffect(() => [1, 2, arg]);
}
```
@@ -17,8 +17,8 @@ function nonReactFn(arg) {
```
3 |
4 | function nonReactFn(arg) {
> 5 | useEffect(() => [1, 2, arg], AUTODEPS);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (5:5)
> 5 | useEffect(() => [1, 2, arg]);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (5:5)
6 | }
7 |
```

View File

@@ -1,6 +1,6 @@
// @inferEffectDependencies @compilationMode:"infer" @panicThreshold:"none"
import {useEffect, AUTODEPS} from 'react';
import {useEffect} from 'react';
function nonReactFn(arg) {
useEffect(() => [1, 2, arg], AUTODEPS);
useEffect(() => [1, 2, arg]);
}

View File

@@ -3,7 +3,7 @@
```javascript
// @inferEffectDependencies @panicThreshold:"none"
import {useEffect, AUTODEPS} from 'react';
import {useEffect} from 'react';
/**
* Error on non-inlined effect functions:
@@ -21,7 +21,7 @@ function Component({foo}) {
}
// No inferred dep array, the argument is not a lambda
useEffect(f, AUTODEPS);
useEffect(f);
}
```
@@ -32,8 +32,8 @@ function Component({foo}) {
```
18 |
19 | // No inferred dep array, the argument is not a lambda
> 20 | useEffect(f, AUTODEPS);
| ^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (20:20)
> 20 | useEffect(f);
| ^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (20:20)
21 | }
22 |
```

View File

@@ -1,5 +1,5 @@
// @inferEffectDependencies @panicThreshold:"none"
import {useEffect, AUTODEPS} from 'react';
import {useEffect} from 'react';
/**
* Error on non-inlined effect functions:
@@ -17,5 +17,5 @@ function Component({foo}) {
}
// No inferred dep array, the argument is not a lambda
useEffect(f, AUTODEPS);
useEffect(f);
}

View File

@@ -5,7 +5,6 @@
// @dynamicGating:{"source":"shared-runtime"} @inferEffectDependencies @panicThreshold:"none"
import useEffectWrapper from 'useEffectWrapper';
import {AUTODEPS} from 'react';
/**
* TODO: run the non-forget enabled version through the effect inference
@@ -14,7 +13,7 @@ import {AUTODEPS} from 'react';
function Component({foo}) {
'use memo if(getTrue)';
const arr = [];
useEffectWrapper(() => arr.push(foo), AUTODEPS);
useEffectWrapper(() => arr.push(foo));
arr.push(2);
return arr;
}
@@ -31,13 +30,13 @@ export const FIXTURE_ENTRYPOINT = {
## Error
```
11 | 'use memo if(getTrue)';
12 | const arr = [];
> 13 | useEffectWrapper(() => arr.push(foo), AUTODEPS);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (13:13)
14 | arr.push(2);
15 | return arr;
16 | }
10 | 'use memo if(getTrue)';
11 | const arr = [];
> 12 | useEffectWrapper(() => arr.push(foo));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (12:12)
13 | arr.push(2);
14 | return arr;
15 | }
```

View File

@@ -1,7 +1,6 @@
// @dynamicGating:{"source":"shared-runtime"} @inferEffectDependencies @panicThreshold:"none"
import useEffectWrapper from 'useEffectWrapper';
import {AUTODEPS} from 'react';
/**
* TODO: run the non-forget enabled version through the effect inference
@@ -10,7 +9,7 @@ import {AUTODEPS} from 'react';
function Component({foo}) {
'use memo if(getTrue)';
const arr = [];
useEffectWrapper(() => arr.push(foo), AUTODEPS);
useEffectWrapper(() => arr.push(foo));
arr.push(2);
return arr;
}

View File

@@ -4,7 +4,6 @@
```javascript
// @gating @inferEffectDependencies @panicThreshold:"none"
import useEffectWrapper from 'useEffectWrapper';
import {AUTODEPS} from 'react';
/**
* TODO: run the non-forget enabled version through the effect inference
@@ -12,7 +11,7 @@ import {AUTODEPS} from 'react';
*/
function Component({foo}) {
const arr = [];
useEffectWrapper(() => arr.push(foo), AUTODEPS);
useEffectWrapper(() => arr.push(foo));
arr.push(2);
return arr;
}
@@ -29,13 +28,13 @@ export const FIXTURE_ENTRYPOINT = {
## Error
```
9 | function Component({foo}) {
10 | const arr = [];
> 11 | useEffectWrapper(() => arr.push(foo), AUTODEPS);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (11:11)
12 | arr.push(2);
13 | return arr;
14 | }
8 | function Component({foo}) {
9 | const arr = [];
> 10 | useEffectWrapper(() => arr.push(foo));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (10:10)
11 | arr.push(2);
12 | return arr;
13 | }
```

View File

@@ -1,6 +1,5 @@
// @gating @inferEffectDependencies @panicThreshold:"none"
import useEffectWrapper from 'useEffectWrapper';
import {AUTODEPS} from 'react';
/**
* TODO: run the non-forget enabled version through the effect inference
@@ -8,7 +7,7 @@ import {AUTODEPS} from 'react';
*/
function Component({foo}) {
const arr = [];
useEffectWrapper(() => arr.push(foo), AUTODEPS);
useEffectWrapper(() => arr.push(foo));
arr.push(2);
return arr;
}

View File

@@ -7,7 +7,7 @@ import React from 'react';
function NonReactiveDepInEffect() {
const obj = makeObject_Primitives();
React.useEffect(() => print(obj), React.AUTODEPS);
React.useEffect(() => print(obj));
}
```
@@ -18,8 +18,8 @@ function NonReactiveDepInEffect() {
```
4 | function NonReactiveDepInEffect() {
5 | const obj = makeObject_Primitives();
> 6 | React.useEffect(() => print(obj), React.AUTODEPS);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (6:6)
> 6 | React.useEffect(() => print(obj));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (6:6)
7 | }
8 |
```

View File

@@ -3,5 +3,5 @@ import React from 'react';
function NonReactiveDepInEffect() {
const obj = makeObject_Primitives();
React.useEffect(() => print(obj), React.AUTODEPS);
React.useEffect(() => print(obj));
}

View File

@@ -4,7 +4,6 @@
```javascript
// @inferEffectDependencies @panicThreshold:"none"
import {useSpecialEffect} from 'shared-runtime';
import {AUTODEPS} from 'react';
/**
* Note that a react compiler-based transform still has limitations on JS syntax.
@@ -12,17 +11,13 @@ import {AUTODEPS} from 'react';
*/
function Component({prop1}) {
'use memo';
useSpecialEffect(
() => {
try {
console.log(prop1);
} finally {
console.log('exiting');
}
},
[prop1],
AUTODEPS
);
useSpecialEffect(() => {
try {
console.log(prop1);
} finally {
console.log('exiting');
}
}, [prop1]);
return <div>{prop1}</div>;
}
@@ -32,33 +27,25 @@ function Component({prop1}) {
## Error
```
9 | function Component({prop1}) {
10 | 'use memo';
> 11 | useSpecialEffect(
| ^^^^^^^^^^^^^^^^^
> 12 | () => {
| ^^^^^^^^^^^
> 13 | try {
| ^^^^^^^^^^^
> 14 | console.log(prop1);
| ^^^^^^^^^^^
> 15 | } finally {
| ^^^^^^^^^^^
> 16 | console.log('exiting');
| ^^^^^^^^^^^
> 17 | }
| ^^^^^^^^^^^
> 18 | },
| ^^^^^^^^^^^
> 19 | [prop1],
| ^^^^^^^^^^^
> 20 | AUTODEPS
| ^^^^^^^^^^^
> 21 | );
| ^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics.. (Bailout reason: Todo: (BuildHIR::lowerStatement) Handle TryStatement without a catch clause (13:17)) (11:21)
22 | return <div>{prop1}</div>;
23 | }
24 |
8 | function Component({prop1}) {
9 | 'use memo';
> 10 | useSpecialEffect(() => {
| ^^^^^^^^^^^^^^^^^^^^^^^^
> 11 | try {
| ^^^^^^^^^
> 12 | console.log(prop1);
| ^^^^^^^^^
> 13 | } finally {
| ^^^^^^^^^
> 14 | console.log('exiting');
| ^^^^^^^^^
> 15 | }
| ^^^^^^^^^
> 16 | }, [prop1]);
| ^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics.. (Bailout reason: Todo: (BuildHIR::lowerStatement) Handle TryStatement without a catch clause (11:15)) (10:16)
17 | return <div>{prop1}</div>;
18 | }
19 |
```

View File

@@ -1,6 +1,5 @@
// @inferEffectDependencies @panicThreshold:"none"
import {useSpecialEffect} from 'shared-runtime';
import {AUTODEPS} from 'react';
/**
* Note that a react compiler-based transform still has limitations on JS syntax.
@@ -8,16 +7,12 @@ import {AUTODEPS} from 'react';
*/
function Component({prop1}) {
'use memo';
useSpecialEffect(
() => {
try {
console.log(prop1);
} finally {
console.log('exiting');
}
},
[prop1],
AUTODEPS
);
useSpecialEffect(() => {
try {
console.log(prop1);
} finally {
console.log('exiting');
}
}, [prop1]);
return <div>{prop1}</div>;
}

View File

@@ -3,11 +3,11 @@
```javascript
// @inferEffectDependencies @panicThreshold:"none"
import {useEffect, AUTODEPS} from 'react';
import {useEffect} from 'react';
function Component({propVal}) {
'use no memo';
useEffect(() => [propVal], AUTODEPS);
useEffect(() => [propVal]);
}
```
@@ -18,8 +18,8 @@ function Component({propVal}) {
```
4 | function Component({propVal}) {
5 | 'use no memo';
> 6 | useEffect(() => [propVal], AUTODEPS);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (6:6)
> 6 | useEffect(() => [propVal]);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (6:6)
7 | }
8 |
```

View File

@@ -1,7 +1,7 @@
// @inferEffectDependencies @panicThreshold:"none"
import {useEffect, AUTODEPS} from 'react';
import {useEffect} from 'react';
function Component({propVal}) {
'use no memo';
useEffect(() => [propVal], AUTODEPS);
useEffect(() => [propVal]);
}

View File

@@ -3,14 +3,14 @@
```javascript
// @inferEffectDependencies @panicThreshold:"none"
import {useEffect, AUTODEPS} from 'react';
import {useEffect} from 'react';
import {print} from 'shared-runtime';
function Component({foo}) {
const arr = [];
// Taking either arr[0].value or arr as a dependency is reasonable
// as long as developers know what to expect.
useEffect(() => print(arr[0].value), AUTODEPS);
useEffect(() => print(arr[0].value));
arr.push({value: foo});
return arr;
}
@@ -21,7 +21,7 @@ function Component({foo}) {
```javascript
// @inferEffectDependencies @panicThreshold:"none"
import { useEffect, AUTODEPS } from "react";
import { useEffect } from "react";
import { print } from "shared-runtime";
function Component(t0) {

View File

@@ -1,12 +1,12 @@
// @inferEffectDependencies @panicThreshold:"none"
import {useEffect, AUTODEPS} from 'react';
import {useEffect} from 'react';
import {print} from 'shared-runtime';
function Component({foo}) {
const arr = [];
// Taking either arr[0].value or arr as a dependency is reasonable
// as long as developers know what to expect.
useEffect(() => print(arr[0].value), AUTODEPS);
useEffect(() => print(arr[0].value));
arr.push({value: foo});
return arr;
}

View File

@@ -3,14 +3,14 @@
```javascript
// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly
import {useEffect, AUTODEPS} from 'react';
import {useEffect} from 'react';
import {print} from 'shared-runtime';
function Component({foo}) {
const arr = [];
// Taking either arr[0].value or arr as a dependency is reasonable
// as long as developers know what to expect.
useEffect(() => print(arr[0]?.value), AUTODEPS);
useEffect(() => print(arr[0]?.value));
arr.push({value: foo});
return arr;
}
@@ -26,7 +26,7 @@ export const FIXTURE_ENTRYPOINT = {
```javascript
// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly
import { useEffect, AUTODEPS } from "react";
import { useEffect } from "react";
import { print } from "shared-runtime";
function Component(t0) {
@@ -48,9 +48,9 @@ export const FIXTURE_ENTRYPOINT = {
## Logs
```
{"kind":"CompileError","fnLoc":{"start":{"line":5,"column":0,"index":149},"end":{"line":12,"column":1,"index":404},"filename":"mutate-after-useeffect-optional-chain.ts"},"detail":{"reason":"Updating a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the mutation before calling useEffect()","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":10,"column":2,"index":365},"end":{"line":10,"column":5,"index":368},"filename":"mutate-after-useeffect-optional-chain.ts","identifierName":"arr"}}}
{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":9,"column":2,"index":314},"end":{"line":9,"column":49,"index":361},"filename":"mutate-after-useeffect-optional-chain.ts"},"decorations":[{"start":{"line":9,"column":24,"index":336},"end":{"line":9,"column":27,"index":339},"filename":"mutate-after-useeffect-optional-chain.ts","identifierName":"arr"}]}
{"kind":"CompileSuccess","fnLoc":{"start":{"line":5,"column":0,"index":149},"end":{"line":12,"column":1,"index":404},"filename":"mutate-after-useeffect-optional-chain.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0}
{"kind":"CompileError","fnLoc":{"start":{"line":5,"column":0,"index":139},"end":{"line":12,"column":1,"index":384},"filename":"mutate-after-useeffect-optional-chain.ts"},"detail":{"reason":"Updating a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the mutation before calling useEffect()","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":10,"column":2,"index":345},"end":{"line":10,"column":5,"index":348},"filename":"mutate-after-useeffect-optional-chain.ts","identifierName":"arr"}}}
{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":9,"column":2,"index":304},"end":{"line":9,"column":39,"index":341},"filename":"mutate-after-useeffect-optional-chain.ts"},"decorations":[{"start":{"line":9,"column":24,"index":326},"end":{"line":9,"column":27,"index":329},"filename":"mutate-after-useeffect-optional-chain.ts","identifierName":"arr"}]}
{"kind":"CompileSuccess","fnLoc":{"start":{"line":5,"column":0,"index":139},"end":{"line":12,"column":1,"index":384},"filename":"mutate-after-useeffect-optional-chain.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0}
```
### Eval output

View File

@@ -1,12 +1,12 @@
// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly
import {useEffect, AUTODEPS} from 'react';
import {useEffect} from 'react';
import {print} from 'shared-runtime';
function Component({foo}) {
const arr = [];
// Taking either arr[0].value or arr as a dependency is reasonable
// as long as developers know what to expect.
useEffect(() => print(arr[0]?.value), AUTODEPS);
useEffect(() => print(arr[0]?.value));
arr.push({value: foo});
return arr;
}

View File

@@ -4,12 +4,12 @@
```javascript
// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly
import {useEffect, useRef, AUTODEPS} from 'react';
import {useEffect, useRef} from 'react';
import {print} from 'shared-runtime';
function Component({arrRef}) {
// Avoid taking arr.current as a dependency
useEffect(() => print(arrRef.current), AUTODEPS);
useEffect(() => print(arrRef.current));
arrRef.current.val = 2;
return arrRef;
}
@@ -26,7 +26,7 @@ export const FIXTURE_ENTRYPOINT = {
```javascript
// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly
import { useEffect, useRef, AUTODEPS } from "react";
import { useEffect, useRef } from "react";
import { print } from "shared-runtime";
function Component(t0) {
@@ -47,9 +47,9 @@ export const FIXTURE_ENTRYPOINT = {
## Logs
```
{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":158},"end":{"line":11,"column":1,"index":331},"filename":"mutate-after-useeffect-ref-access.ts"},"detail":{"reason":"Mutating component props or hook arguments is not allowed. Consider using a local variable instead","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":9,"column":2,"index":289},"end":{"line":9,"column":16,"index":303},"filename":"mutate-after-useeffect-ref-access.ts"}}}
{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":8,"column":2,"index":237},"end":{"line":8,"column":50,"index":285},"filename":"mutate-after-useeffect-ref-access.ts"},"decorations":[{"start":{"line":8,"column":24,"index":259},"end":{"line":8,"column":30,"index":265},"filename":"mutate-after-useeffect-ref-access.ts","identifierName":"arrRef"}]}
{"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":158},"end":{"line":11,"column":1,"index":331},"filename":"mutate-after-useeffect-ref-access.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0}
{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":148},"end":{"line":11,"column":1,"index":311},"filename":"mutate-after-useeffect-ref-access.ts"},"detail":{"reason":"Mutating component props or hook arguments is not allowed. Consider using a local variable instead","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":9,"column":2,"index":269},"end":{"line":9,"column":16,"index":283},"filename":"mutate-after-useeffect-ref-access.ts"}}}
{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":8,"column":2,"index":227},"end":{"line":8,"column":40,"index":265},"filename":"mutate-after-useeffect-ref-access.ts"},"decorations":[{"start":{"line":8,"column":24,"index":249},"end":{"line":8,"column":30,"index":255},"filename":"mutate-after-useeffect-ref-access.ts","identifierName":"arrRef"}]}
{"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":148},"end":{"line":11,"column":1,"index":311},"filename":"mutate-after-useeffect-ref-access.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0}
```
### Eval output

View File

@@ -1,11 +1,11 @@
// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly
import {useEffect, useRef, AUTODEPS} from 'react';
import {useEffect, useRef} from 'react';
import {print} from 'shared-runtime';
function Component({arrRef}) {
// Avoid taking arr.current as a dependency
useEffect(() => print(arrRef.current), AUTODEPS);
useEffect(() => print(arrRef.current));
arrRef.current.val = 2;
return arrRef;
}

View File

@@ -3,13 +3,13 @@
```javascript
// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly
import {useEffect, AUTODEPS} from 'react';
import {useEffect} from 'react';
function Component({foo}) {
const arr = [];
useEffect(() => {
arr.push(foo);
}, AUTODEPS);
});
arr.push(2);
return arr;
}
@@ -25,7 +25,7 @@ export const FIXTURE_ENTRYPOINT = {
```javascript
// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly
import { useEffect, AUTODEPS } from "react";
import { useEffect } from "react";
function Component(t0) {
const { foo } = t0;
@@ -47,9 +47,9 @@ export const FIXTURE_ENTRYPOINT = {
## Logs
```
{"kind":"CompileError","fnLoc":{"start":{"line":4,"column":0,"index":111},"end":{"line":11,"column":1,"index":242},"filename":"mutate-after-useeffect.ts"},"detail":{"reason":"Updating a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the mutation before calling useEffect()","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":9,"column":2,"index":214},"end":{"line":9,"column":5,"index":217},"filename":"mutate-after-useeffect.ts","identifierName":"arr"}}}
{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":6,"column":2,"index":159},"end":{"line":8,"column":14,"index":210},"filename":"mutate-after-useeffect.ts"},"decorations":[{"start":{"line":7,"column":4,"index":181},"end":{"line":7,"column":7,"index":184},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},{"start":{"line":7,"column":4,"index":181},"end":{"line":7,"column":7,"index":184},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},{"start":{"line":7,"column":13,"index":190},"end":{"line":7,"column":16,"index":193},"filename":"mutate-after-useeffect.ts","identifierName":"foo"}]}
{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":111},"end":{"line":11,"column":1,"index":242},"filename":"mutate-after-useeffect.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0}
{"kind":"CompileError","fnLoc":{"start":{"line":4,"column":0,"index":101},"end":{"line":11,"column":1,"index":222},"filename":"mutate-after-useeffect.ts"},"detail":{"reason":"Updating a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the mutation before calling useEffect()","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":9,"column":2,"index":194},"end":{"line":9,"column":5,"index":197},"filename":"mutate-after-useeffect.ts","identifierName":"arr"}}}
{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":6,"column":2,"index":149},"end":{"line":8,"column":4,"index":190},"filename":"mutate-after-useeffect.ts"},"decorations":[{"start":{"line":7,"column":4,"index":171},"end":{"line":7,"column":7,"index":174},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},{"start":{"line":7,"column":4,"index":171},"end":{"line":7,"column":7,"index":174},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},{"start":{"line":7,"column":13,"index":180},"end":{"line":7,"column":16,"index":183},"filename":"mutate-after-useeffect.ts","identifierName":"foo"}]}
{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":101},"end":{"line":11,"column":1,"index":222},"filename":"mutate-after-useeffect.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0}
```
### Eval output

View File

@@ -1,11 +1,11 @@
// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly
import {useEffect, AUTODEPS} from 'react';
import {useEffect} from 'react';
function Component({foo}) {
const arr = [];
useEffect(() => {
arr.push(foo);
}, AUTODEPS);
});
arr.push(2);
return arr;
}

View File

@@ -1,26 +0,0 @@
## Input
```javascript
// @inferEffectDependencies
import {useEffect, AUTODEPS} from 'react';
function Component({foo}) {
useEffect(AUTODEPS);
}
```
## Error
```
3 |
4 | function Component({foo}) {
> 5 | useEffect(AUTODEPS);
| ^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (5:5)
6 | }
7 |
```

View File

@@ -1,6 +0,0 @@
// @inferEffectDependencies
import {useEffect, AUTODEPS} from 'react';
function Component({foo}) {
useEffect(AUTODEPS);
}

View File

@@ -1,45 +0,0 @@
## Input
```javascript
// @inferEffectDependencies
import {AUTODEPS} from 'react';
import useEffectWrapper from 'useEffectWrapper';
function Component({foo}) {
useEffectWrapper(
() => {
console.log(foo);
},
[foo],
AUTODEPS
);
}
```
## Error
```
4 |
5 | function Component({foo}) {
> 6 | useEffectWrapper(
| ^^^^^^^^^^^^^^^^^
> 7 | () => {
| ^^^^^^^^^^^
> 8 | console.log(foo);
| ^^^^^^^^^^^
> 9 | },
| ^^^^^^^^^^^
> 10 | [foo],
| ^^^^^^^^^^^
> 11 | AUTODEPS
| ^^^^^^^^^^^
> 12 | );
| ^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (6:12)
13 | }
14 |
```

View File

@@ -1,13 +0,0 @@
// @inferEffectDependencies
import {AUTODEPS} from 'react';
import useEffectWrapper from 'useEffectWrapper';
function Component({foo}) {
useEffectWrapper(
() => {
console.log(foo);
},
[foo],
AUTODEPS
);
}

View File

@@ -3,7 +3,7 @@
```javascript
// @inferEffectDependencies
import {useEffect, useRef, AUTODEPS} from 'react';
import {useEffect, useRef} from 'react';
function useCustomRef() {
const ref = useRef();
return ref;
@@ -12,7 +12,7 @@ function NonReactiveWrapper() {
const ref = useCustomRef();
useEffect(() => {
print(ref);
}, AUTODEPS);
});
}
```
@@ -21,7 +21,7 @@ function NonReactiveWrapper() {
```javascript
import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies
import { useEffect, useRef, AUTODEPS } from "react";
import { useEffect, useRef } from "react";
function useCustomRef() {
const ref = useRef();
return ref;

View File

@@ -1,5 +1,5 @@
// @inferEffectDependencies
import {useEffect, useRef, AUTODEPS} from 'react';
import {useEffect, useRef} from 'react';
function useCustomRef() {
const ref = useRef();
return ref;
@@ -8,5 +8,5 @@ function NonReactiveWrapper() {
const ref = useCustomRef();
useEffect(() => {
print(ref);
}, AUTODEPS);
});
}

View File

@@ -8,8 +8,8 @@ import * as SharedRuntime from 'shared-runtime';
function NonReactiveDepInEffect() {
const obj = makeObject_Primitives();
React.useEffect(() => print(obj), React.AUTODEPS);
SharedRuntime.useSpecialEffect(() => print(obj), [obj], React.AUTODEPS);
React.useEffect(() => print(obj));
SharedRuntime.useSpecialEffect(() => print(obj), [obj]);
}
```

View File

@@ -4,6 +4,6 @@ import * as SharedRuntime from 'shared-runtime';
function NonReactiveDepInEffect() {
const obj = makeObject_Primitives();
React.useEffect(() => print(obj), React.AUTODEPS);
SharedRuntime.useSpecialEffect(() => print(obj), [obj], React.AUTODEPS);
React.useEffect(() => print(obj));
SharedRuntime.useSpecialEffect(() => print(obj), [obj]);
}

View File

@@ -4,11 +4,10 @@
```javascript
// @inferEffectDependencies
import {print, useSpecialEffect} from 'shared-runtime';
import {AUTODEPS} from 'react';
function CustomConfig({propVal}) {
// Insertion
useSpecialEffect(() => print(propVal), [propVal], AUTODEPS);
useSpecialEffect(() => print(propVal), [propVal]);
// No insertion
useSpecialEffect(() => print(propVal), [propVal], [propVal]);
}
@@ -20,7 +19,6 @@ function CustomConfig({propVal}) {
```javascript
import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies
import { print, useSpecialEffect } from "shared-runtime";
import { AUTODEPS } from "react";
function CustomConfig(t0) {
const $ = _c(7);

View File

@@ -1,10 +1,9 @@
// @inferEffectDependencies
import {print, useSpecialEffect} from 'shared-runtime';
import {AUTODEPS} from 'react';
function CustomConfig({propVal}) {
// Insertion
useSpecialEffect(() => print(propVal), [propVal], AUTODEPS);
useSpecialEffect(() => print(propVal), [propVal]);
// No insertion
useSpecialEffect(() => print(propVal), [propVal], [propVal]);
}

View File

@@ -3,7 +3,7 @@
```javascript
// @inferEffectDependencies
import {useEffect, useRef, AUTODEPS} from 'react';
import {useEffect, useRef} from 'react';
import useEffectWrapper from 'useEffectWrapper';
const moduleNonReactive = 0;
@@ -24,7 +24,7 @@ function Component({foo, bar}) {
console.log(ref.current);
console.log(localNonPrimitiveReactive);
console.log(localNonPrimitiveNonreactive);
}, AUTODEPS);
});
// Optional chains and property accesses
// TODO: we may be able to save bytes by omitting property accesses if the
@@ -32,11 +32,11 @@ function Component({foo, bar}) {
useEffect(() => {
console.log(bar?.baz);
console.log(bar.qux);
}, AUTODEPS);
});
useEffectWrapper(() => {
console.log(foo);
}, AUTODEPS);
});
}
```
@@ -45,7 +45,7 @@ function Component({foo, bar}) {
```javascript
import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies
import { useEffect, useRef, AUTODEPS } from "react";
import { useEffect, useRef } from "react";
import useEffectWrapper from "useEffectWrapper";
const moduleNonReactive = 0;

View File

@@ -1,5 +1,5 @@
// @inferEffectDependencies
import {useEffect, useRef, AUTODEPS} from 'react';
import {useEffect, useRef} from 'react';
import useEffectWrapper from 'useEffectWrapper';
const moduleNonReactive = 0;
@@ -20,7 +20,7 @@ function Component({foo, bar}) {
console.log(ref.current);
console.log(localNonPrimitiveReactive);
console.log(localNonPrimitiveNonreactive);
}, AUTODEPS);
});
// Optional chains and property accesses
// TODO: we may be able to save bytes by omitting property accesses if the
@@ -28,9 +28,9 @@ function Component({foo, bar}) {
useEffect(() => {
console.log(bar?.baz);
console.log(bar.qux);
}, AUTODEPS);
});
useEffectWrapper(() => {
console.log(foo);
}, AUTODEPS);
});
}

View File

@@ -5,11 +5,10 @@
// @inferEffectDependencies @noEmit
import {print} from 'shared-runtime';
import useEffectWrapper from 'useEffectWrapper';
import {AUTODEPS} from 'react';
function ReactiveVariable({propVal}) {
const arr = [propVal];
useEffectWrapper(() => print(arr), AUTODEPS);
useEffectWrapper(() => print(arr));
}
```
@@ -20,11 +19,10 @@ function ReactiveVariable({propVal}) {
// @inferEffectDependencies @noEmit
import { print } from "shared-runtime";
import useEffectWrapper from "useEffectWrapper";
import { AUTODEPS } from "react";
function ReactiveVariable({ propVal }) {
const arr = [propVal];
useEffectWrapper(() => print(arr), AUTODEPS);
useEffectWrapper(() => print(arr));
}
```

View File

@@ -1,9 +1,8 @@
// @inferEffectDependencies @noEmit
import {print} from 'shared-runtime';
import useEffectWrapper from 'useEffectWrapper';
import {AUTODEPS} from 'react';
function ReactiveVariable({propVal}) {
const arr = [propVal];
useEffectWrapper(() => print(arr), AUTODEPS);
useEffectWrapper(() => print(arr));
}

View File

@@ -5,14 +5,13 @@
// @inferEffectDependencies @noEmit @panicThreshold:"none" @loggerTestOnly
import {print} from 'shared-runtime';
import useEffectWrapper from 'useEffectWrapper';
import {AUTODEPS} from 'react';
function Foo({propVal}) {
const arr = [propVal];
useEffectWrapper(() => print(arr), AUTODEPS);
useEffectWrapper(() => print(arr));
const arr2 = [];
useEffectWrapper(() => arr2.push(propVal), AUTODEPS);
useEffectWrapper(() => arr2.push(propVal));
arr2.push(2);
return {arr, arr2};
}
@@ -31,14 +30,13 @@ export const FIXTURE_ENTRYPOINT = {
// @inferEffectDependencies @noEmit @panicThreshold:"none" @loggerTestOnly
import { print } from "shared-runtime";
import useEffectWrapper from "useEffectWrapper";
import { AUTODEPS } from "react";
function Foo({ propVal }) {
const arr = [propVal];
useEffectWrapper(() => print(arr), AUTODEPS);
useEffectWrapper(() => print(arr));
const arr2 = [];
useEffectWrapper(() => arr2.push(propVal), AUTODEPS);
useEffectWrapper(() => arr2.push(propVal));
arr2.push(2);
return { arr, arr2 };
}
@@ -54,10 +52,10 @@ export const FIXTURE_ENTRYPOINT = {
## Logs
```
{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":195},"end":{"line":14,"column":1,"index":409},"filename":"retry-no-emit.ts"},"detail":{"reason":"Updating a value previously passed as an argument to a hook is not allowed. Consider moving the mutation before calling the hook","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":12,"column":2,"index":372},"end":{"line":12,"column":6,"index":376},"filename":"retry-no-emit.ts","identifierName":"arr2"}}}
{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":8,"column":2,"index":248},"end":{"line":8,"column":46,"index":292},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":8,"column":31,"index":277},"end":{"line":8,"column":34,"index":280},"filename":"retry-no-emit.ts","identifierName":"arr"}]}
{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":11,"column":2,"index":316},"end":{"line":11,"column":54,"index":368},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":11,"column":25,"index":339},"end":{"line":11,"column":29,"index":343},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":11,"column":25,"index":339},"end":{"line":11,"column":29,"index":343},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":11,"column":35,"index":349},"end":{"line":11,"column":42,"index":356},"filename":"retry-no-emit.ts","identifierName":"propVal"}]}
{"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":195},"end":{"line":14,"column":1,"index":409},"filename":"retry-no-emit.ts"},"fnName":"Foo","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0}
{"kind":"CompileError","fnLoc":{"start":{"line":5,"column":0,"index":163},"end":{"line":13,"column":1,"index":357},"filename":"retry-no-emit.ts"},"detail":{"reason":"Updating a value previously passed as an argument to a hook is not allowed. Consider moving the mutation before calling the hook","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":11,"column":2,"index":320},"end":{"line":11,"column":6,"index":324},"filename":"retry-no-emit.ts","identifierName":"arr2"}}}
{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":7,"column":2,"index":216},"end":{"line":7,"column":36,"index":250},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":7,"column":31,"index":245},"end":{"line":7,"column":34,"index":248},"filename":"retry-no-emit.ts","identifierName":"arr"}]}
{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":10,"column":2,"index":274},"end":{"line":10,"column":44,"index":316},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":10,"column":25,"index":297},"end":{"line":10,"column":29,"index":301},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":10,"column":25,"index":297},"end":{"line":10,"column":29,"index":301},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":10,"column":35,"index":307},"end":{"line":10,"column":42,"index":314},"filename":"retry-no-emit.ts","identifierName":"propVal"}]}
{"kind":"CompileSuccess","fnLoc":{"start":{"line":5,"column":0,"index":163},"end":{"line":13,"column":1,"index":357},"filename":"retry-no-emit.ts"},"fnName":"Foo","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0}
```
### Eval output

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