Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0fba073934 | ||
|
|
735e9ac54e | ||
|
|
5d64f74211 | ||
|
|
3302d1f791 | ||
|
|
7697a9f62e | ||
|
|
3168e08f83 | ||
|
|
2805f0ed9e | ||
|
|
ac3e705a18 | ||
|
|
8e60cb7ed5 | ||
|
|
6a58b80020 | ||
|
|
b1b0955f2b | ||
|
|
1549bda33f | ||
|
|
bb6f0c8d2f | ||
|
|
aad7c664ff | ||
|
|
3fe51c9e14 | ||
|
|
4082b0e7d3 | ||
|
|
6b49c449b6 | ||
|
|
872b4fef6d |
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"packages": ["packages/react", "packages/react-dom", "packages/react-server-dom-webpack", "packages/scheduler"],
|
||||
"buildCommand": "download-build-in-codesandbox-ci",
|
||||
"node": "18",
|
||||
"node": "20",
|
||||
"publishDirectory": {
|
||||
"react": "build/oss-experimental/react",
|
||||
"react-dom": "build/oss-experimental/react-dom",
|
||||
|
||||
@@ -6,52 +6,51 @@
|
||||
*/
|
||||
|
||||
import MonacoEditor, {loader, type Monaco} from '@monaco-editor/react';
|
||||
import {parseConfigPragmaAsString} from 'babel-plugin-react-compiler';
|
||||
import type {editor} from 'monaco-editor';
|
||||
import * as monaco from 'monaco-editor';
|
||||
import parserBabel from 'prettier/plugins/babel';
|
||||
import * as prettierPluginEstree from 'prettier/plugins/estree';
|
||||
import * as prettier from 'prettier/standalone';
|
||||
import {useState, useEffect} from 'react';
|
||||
import {useState} from 'react';
|
||||
import {Resizable} from 're-resizable';
|
||||
import {useStore} from '../StoreContext';
|
||||
import {useStore, useStoreDispatch} from '../StoreContext';
|
||||
import {monacoOptions} from './monacoOptions';
|
||||
import {
|
||||
generateOverridePragmaFromConfig,
|
||||
updateSourceWithOverridePragma,
|
||||
} from '../../lib/configUtils';
|
||||
|
||||
loader.config({monaco});
|
||||
|
||||
export default function ConfigEditor(): JSX.Element {
|
||||
const [, setMonaco] = useState<Monaco | null>(null);
|
||||
const store = useStore();
|
||||
const dispatchStore = useStoreDispatch();
|
||||
|
||||
// Parse string-based override config from pragma comment and format it
|
||||
const [configJavaScript, setConfigJavaScript] = useState('');
|
||||
const handleChange: (value: string | undefined) => void = async value => {
|
||||
if (value === undefined) return;
|
||||
|
||||
useEffect(() => {
|
||||
const pragma = store.source.substring(0, store.source.indexOf('\n'));
|
||||
const configString = `(${parseConfigPragmaAsString(pragma)})`;
|
||||
try {
|
||||
const newPragma = await generateOverridePragmaFromConfig(value);
|
||||
const updatedSource = updateSourceWithOverridePragma(
|
||||
store.source,
|
||||
newPragma,
|
||||
);
|
||||
|
||||
prettier
|
||||
.format(configString, {
|
||||
semi: true,
|
||||
parser: 'babel-ts',
|
||||
plugins: [parserBabel, prettierPluginEstree],
|
||||
})
|
||||
.then(formatted => {
|
||||
setConfigJavaScript(formatted);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error formatting config:', error);
|
||||
setConfigJavaScript('({})'); // Return empty object if not valid for now
|
||||
//TODO: Add validation and error handling for config
|
||||
// Update the store with both the new config and updated source
|
||||
dispatchStore({
|
||||
type: 'updateFile',
|
||||
payload: {
|
||||
source: updatedSource,
|
||||
config: value,
|
||||
},
|
||||
});
|
||||
console.log('Config:', configString);
|
||||
}, [store.source]);
|
||||
|
||||
const handleChange: (value: string | undefined) => void = value => {
|
||||
if (!value) return;
|
||||
|
||||
// TODO: Implement sync logic to update pragma comments in the source
|
||||
console.log('Config changed:', value);
|
||||
} catch (_) {
|
||||
dispatchStore({
|
||||
type: 'updateFile',
|
||||
payload: {
|
||||
source: store.source,
|
||||
config: value,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleMount: (
|
||||
@@ -81,12 +80,11 @@ export default function ConfigEditor(): JSX.Element {
|
||||
<MonacoEditor
|
||||
path={'config.js'}
|
||||
language={'javascript'}
|
||||
value={configJavaScript}
|
||||
value={store.config}
|
||||
onMount={handleMount}
|
||||
onChange={handleChange}
|
||||
options={{
|
||||
...monacoOptions,
|
||||
readOnly: true,
|
||||
lineNumbers: 'off',
|
||||
folding: false,
|
||||
renderLineHighlight: 'none',
|
||||
|
||||
@@ -48,6 +48,7 @@ import {
|
||||
import {transformFromAstSync} from '@babel/core';
|
||||
import {LoggerEvent} from 'babel-plugin-react-compiler/dist/Entrypoint';
|
||||
import {useSearchParams} from 'next/navigation';
|
||||
import {parseAndFormatConfig} from '../../lib/configUtils';
|
||||
|
||||
function parseInput(
|
||||
input: string,
|
||||
@@ -315,9 +316,17 @@ export default function Editor(): JSX.Element {
|
||||
});
|
||||
mountStore = defaultStore;
|
||||
}
|
||||
dispatchStore({
|
||||
type: 'setStore',
|
||||
payload: {store: mountStore},
|
||||
|
||||
parseAndFormatConfig(mountStore.source).then(config => {
|
||||
dispatchStore({
|
||||
type: 'setStore',
|
||||
payload: {
|
||||
store: {
|
||||
...mountStore,
|
||||
config,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import {useStore, useStoreDispatch} from '../StoreContext';
|
||||
import {monacoOptions} from './monacoOptions';
|
||||
// @ts-expect-error TODO: Make TS recognize .d.ts files, in addition to loading them with webpack.
|
||||
import React$Types from '../../node_modules/@types/react/index.d.ts';
|
||||
import {parseAndFormatConfig} from '../../lib/configUtils.ts';
|
||||
|
||||
loader.config({monaco});
|
||||
|
||||
@@ -79,13 +80,17 @@ export default function Input({errors, language}: Props): JSX.Element {
|
||||
});
|
||||
}, [monaco, language]);
|
||||
|
||||
const handleChange: (value: string | undefined) => void = value => {
|
||||
const handleChange: (value: string | undefined) => void = async value => {
|
||||
if (!value) return;
|
||||
|
||||
// Parse and format the config
|
||||
const config = await parseAndFormatConfig(value);
|
||||
|
||||
dispatchStore({
|
||||
type: 'updateFile',
|
||||
payload: {
|
||||
source: value,
|
||||
config,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -56,6 +56,7 @@ type ReducerAction =
|
||||
type: 'updateFile';
|
||||
payload: {
|
||||
source: string;
|
||||
config?: string;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -66,10 +67,11 @@ function storeReducer(store: Store, action: ReducerAction): Store {
|
||||
return newStore;
|
||||
}
|
||||
case 'updateFile': {
|
||||
const {source} = action.payload;
|
||||
const {source, config} = action.payload;
|
||||
const newStore = {
|
||||
...store,
|
||||
source,
|
||||
config,
|
||||
};
|
||||
return newStore;
|
||||
}
|
||||
|
||||
87
compiler/apps/playground/lib/configUtils.ts
Normal file
87
compiler/apps/playground/lib/configUtils.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import parserBabel from 'prettier/plugins/babel';
|
||||
import prettierPluginEstree from 'prettier/plugins/estree';
|
||||
import * as prettier from 'prettier/standalone';
|
||||
import {parseConfigPragmaAsString} from '../../../packages/babel-plugin-react-compiler/src/Utils/TestUtils';
|
||||
|
||||
/**
|
||||
* Parse config from pragma and format it with prettier
|
||||
*/
|
||||
export async function parseAndFormatConfig(source: string): Promise<string> {
|
||||
const pragma = source.substring(0, source.indexOf('\n'));
|
||||
let configString = parseConfigPragmaAsString(pragma);
|
||||
if (configString !== '') {
|
||||
configString = `(${configString})`;
|
||||
}
|
||||
|
||||
try {
|
||||
const formatted = await prettier.format(configString, {
|
||||
semi: true,
|
||||
parser: 'babel-ts',
|
||||
plugins: [parserBabel, prettierPluginEstree],
|
||||
});
|
||||
return formatted;
|
||||
} catch (error) {
|
||||
console.error('Error formatting config:', error);
|
||||
return ''; // Return empty string if not valid for now
|
||||
}
|
||||
}
|
||||
|
||||
function extractCurlyBracesContent(input: string): string {
|
||||
const startIndex = input.indexOf('{');
|
||||
const endIndex = input.lastIndexOf('}');
|
||||
if (startIndex === -1 || endIndex === -1 || endIndex <= startIndex) {
|
||||
throw new Error('No outer curly braces found in input');
|
||||
}
|
||||
return input.slice(startIndex, endIndex + 1);
|
||||
}
|
||||
|
||||
function cleanContent(content: string): string {
|
||||
return content
|
||||
.replace(/[\r\n]+/g, ' ')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a the override pragma comment from a formatted config object string
|
||||
*/
|
||||
export async function generateOverridePragmaFromConfig(
|
||||
formattedConfigString: string,
|
||||
): Promise<string> {
|
||||
const content = extractCurlyBracesContent(formattedConfigString);
|
||||
const cleanConfig = cleanContent(content);
|
||||
|
||||
// Format the config to ensure it's valid
|
||||
await prettier.format(`(${cleanConfig})`, {
|
||||
semi: false,
|
||||
parser: 'babel-ts',
|
||||
plugins: [parserBabel, prettierPluginEstree],
|
||||
});
|
||||
|
||||
return `// @OVERRIDE:${cleanConfig}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the override pragma comment in source code.
|
||||
*/
|
||||
export function updateSourceWithOverridePragma(
|
||||
source: string,
|
||||
newPragma: string,
|
||||
): string {
|
||||
const firstLineEnd = source.indexOf('\n');
|
||||
const firstLine = source.substring(0, firstLineEnd);
|
||||
|
||||
const pragmaRegex = /^\/\/\s*@/;
|
||||
if (firstLineEnd !== -1 && pragmaRegex.test(firstLine.trim())) {
|
||||
return newPragma + source.substring(firstLineEnd);
|
||||
} else {
|
||||
return newPragma + '\n' + source;
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,10 @@ export default function MyApp() {
|
||||
|
||||
export const defaultStore: Store = {
|
||||
source: index,
|
||||
config: '',
|
||||
};
|
||||
|
||||
export const emptyStore: Store = {
|
||||
source: '',
|
||||
config: '',
|
||||
};
|
||||
|
||||
@@ -17,6 +17,7 @@ import {defaultStore} from '../defaultStore';
|
||||
*/
|
||||
export interface Store {
|
||||
source: string;
|
||||
config?: string;
|
||||
}
|
||||
export function encodeStore(store: Store): string {
|
||||
return compressToEncodedURIComponent(JSON.stringify(store));
|
||||
@@ -65,5 +66,14 @@ export function initStoreFromUrlOrLocalStorage(): Store {
|
||||
const raw = decodeStore(encodedSource);
|
||||
|
||||
invariant(isValidStore(raw), 'Invalid Store');
|
||||
|
||||
// Add config property if missing for backwards compatibility
|
||||
if (!('config' in raw)) {
|
||||
return {
|
||||
...raw,
|
||||
config: '',
|
||||
};
|
||||
}
|
||||
|
||||
return raw;
|
||||
}
|
||||
|
||||
1
compiler/apps/playground/next-env.d.ts
vendored
1
compiler/apps/playground/next-env.d.ts
vendored
@@ -1,5 +1,6 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
/// <reference path="./.next/types/routes.d.ts" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
|
||||
@@ -34,26 +34,30 @@
|
||||
"invariant": "^2.2.4",
|
||||
"lz-string": "^1.5.0",
|
||||
"monaco-editor": "^0.52.0",
|
||||
"next": "^15.2.0-canary.64",
|
||||
"next": "15.5.2",
|
||||
"notistack": "^3.0.0-alpha.7",
|
||||
"prettier": "^3.3.3",
|
||||
"pretty-format": "^29.3.1",
|
||||
"re-resizable": "^6.9.16",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
"react": "19.1.1",
|
||||
"react-dom": "19.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "18.11.9",
|
||||
"@types/react": "^19.0.0",
|
||||
"@types/react-dom": "^19.0.0",
|
||||
"@types/react": "19.1.12",
|
||||
"@types/react-dom": "19.1.9",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"clsx": "^1.2.1",
|
||||
"concurrently": "^7.4.0",
|
||||
"eslint": "^8.28.0",
|
||||
"eslint-config-next": "^15.0.1",
|
||||
"eslint-config-next": "15.5.2",
|
||||
"monaco-editor-webpack-plugin": "^7.1.0",
|
||||
"postcss": "^8.4.31",
|
||||
"tailwindcss": "^3.2.4",
|
||||
"wait-on": "^7.2.0"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/react": "19.1.12",
|
||||
"@types/react-dom": "19.1.9"
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -36,6 +36,14 @@ export enum ErrorSeverity {
|
||||
* memoization.
|
||||
*/
|
||||
CannotPreserveMemoization = 'CannotPreserveMemoization',
|
||||
/**
|
||||
* An API that is known to be incompatible with the compiler. Generally as a result of
|
||||
* the library using "interior mutability", ie having a value whose referential identity
|
||||
* stays the same but which provides access to values that can change. For example a
|
||||
* function that doesn't change but returns different results, or an object that doesn't
|
||||
* change identity but whose properties change.
|
||||
*/
|
||||
IncompatibleLibrary = 'IncompatibleLibrary',
|
||||
/**
|
||||
* Unhandled syntax that we don't support yet.
|
||||
*/
|
||||
@@ -458,7 +466,8 @@ export class CompilerError extends Error {
|
||||
case ErrorSeverity.InvalidJS:
|
||||
case ErrorSeverity.InvalidReact:
|
||||
case ErrorSeverity.InvalidConfig:
|
||||
case ErrorSeverity.UnsupportedJS: {
|
||||
case ErrorSeverity.UnsupportedJS:
|
||||
case ErrorSeverity.IncompatibleLibrary: {
|
||||
return true;
|
||||
}
|
||||
case ErrorSeverity.CannotPreserveMemoization:
|
||||
@@ -506,8 +515,9 @@ function printErrorSummary(severity: ErrorSeverity, message: string): string {
|
||||
severityCategory = 'Error';
|
||||
break;
|
||||
}
|
||||
case ErrorSeverity.IncompatibleLibrary:
|
||||
case ErrorSeverity.CannotPreserveMemoization: {
|
||||
severityCategory = 'Memoization';
|
||||
severityCategory = 'Compilation Skipped';
|
||||
break;
|
||||
}
|
||||
case ErrorSeverity.Invariant: {
|
||||
@@ -547,6 +557,9 @@ export enum ErrorCategory {
|
||||
// Checks that manual memoization is preserved
|
||||
PreserveManualMemo = 'PreserveManualMemo',
|
||||
|
||||
// Checks for known incompatible libraries
|
||||
IncompatibleLibrary = 'IncompatibleLibrary',
|
||||
|
||||
// Checking for no mutations of props, hook arguments, hook return values
|
||||
Immutability = 'Immutability',
|
||||
|
||||
@@ -870,6 +883,15 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
recommended: true,
|
||||
};
|
||||
}
|
||||
case ErrorCategory.IncompatibleLibrary: {
|
||||
return {
|
||||
category,
|
||||
name: 'incompatible-library',
|
||||
description:
|
||||
'Validates against usage of libraries which are incompatible with memoization (manual or automatic)',
|
||||
recommended: true,
|
||||
};
|
||||
}
|
||||
default: {
|
||||
assertExhaustive(category, `Unsupported category ${category}`);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {Effect, ValueKind} from '..';
|
||||
import {TypeConfig} from './TypeSchema';
|
||||
|
||||
/**
|
||||
* Libraries developed before we officially documented the [Rules of React](https://react.dev/reference/rules)
|
||||
* implement APIs which cannot be memoized safely, either via manual or automatic memoization.
|
||||
*
|
||||
* Any non-hook API that is designed to be called during render (not events/effects) should be safe to memoize:
|
||||
*
|
||||
* ```js
|
||||
* function Component() {
|
||||
* const {someFunction} = useLibrary();
|
||||
* // it should always be safe to memoize functions like this
|
||||
* const result = useMemo(() => someFunction(), [someFunction]);
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* However, some APIs implement "interior mutability" — mutating values rather than copying into a new value
|
||||
* and setting state with the new value. Such functions (`someFunction()` in the example) could return different
|
||||
* values even though the function itself is the same object. This breaks memoization, since React relies on
|
||||
* the outer object (or function) changing if part of its value has changed.
|
||||
*
|
||||
* Given that we didn't have the Rules of React precisely documented prior to the introduction of React compiler,
|
||||
* it's understandable that some libraries accidentally shipped APIs that break this rule. However, developers
|
||||
* can easily run into pitfalls with these APIs. They may manually memoize them, which can break their app. Or
|
||||
* they may try using React Compiler, and think that the compiler has broken their code.
|
||||
*
|
||||
* To help ensure that developers can successfully use the compiler with existing code, this file teaches the
|
||||
* compiler about specific APIs that are known to be incompatible with memoization. We've tried to be as precise
|
||||
* as possible.
|
||||
*
|
||||
* The React team is open to collaborating with library authors to help develop compatible versions of these APIs,
|
||||
* and we have already reached out to the teams who own any API listed here to ensure they are aware of the issue.
|
||||
*/
|
||||
export function defaultModuleTypeProvider(
|
||||
moduleName: string,
|
||||
): TypeConfig | null {
|
||||
switch (moduleName) {
|
||||
case 'react-hook-form': {
|
||||
return {
|
||||
kind: 'object',
|
||||
properties: {
|
||||
useForm: {
|
||||
kind: 'hook',
|
||||
returnType: {
|
||||
kind: 'object',
|
||||
properties: {
|
||||
// Only the `watch()` function returned by react-hook-form's `useForm()` API is incompatible
|
||||
watch: {
|
||||
kind: 'function',
|
||||
positionalParams: [],
|
||||
restParam: Effect.Read,
|
||||
calleeEffect: Effect.Read,
|
||||
returnType: {kind: 'type', name: 'Any'},
|
||||
returnValueKind: ValueKind.Mutable,
|
||||
knownIncompatible: `React Hook Form's \`useForm()\` API returns a \`watch()\` function which cannot be memoized safely.`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
case '@tanstack/react-table': {
|
||||
return {
|
||||
kind: 'object',
|
||||
properties: {
|
||||
/*
|
||||
* Many of the properties of `useReactTable()`'s return value are incompatible, so we mark the entire hook
|
||||
* as incompatible
|
||||
*/
|
||||
useReactTable: {
|
||||
kind: 'hook',
|
||||
positionalParams: [],
|
||||
restParam: Effect.Read,
|
||||
returnType: {kind: 'type', name: 'Any'},
|
||||
knownIncompatible: `TanStack Table's \`useReactTable()\` API returns functions that cannot be memoized safely`,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -50,6 +50,7 @@ import {
|
||||
import {Scope as BabelScope, NodePath} from '@babel/traverse';
|
||||
import {TypeSchema} from './TypeSchema';
|
||||
import {FlowTypeEnv} from '../Flood/Types';
|
||||
import {defaultModuleTypeProvider} from './DefaultModuleTypeProvider';
|
||||
|
||||
export const ReactElementSymbolSchema = z.object({
|
||||
elementSymbol: z.union([
|
||||
@@ -860,10 +861,16 @@ export class Environment {
|
||||
#resolveModuleType(moduleName: string, loc: SourceLocation): Global | null {
|
||||
let moduleType = this.#moduleTypes.get(moduleName);
|
||||
if (moduleType === undefined) {
|
||||
if (this.config.moduleTypeProvider == null) {
|
||||
/*
|
||||
* NOTE: Zod doesn't work when specifying a function as a default, so we have to
|
||||
* fallback to the default value here
|
||||
*/
|
||||
const moduleTypeProvider =
|
||||
this.config.moduleTypeProvider ?? defaultModuleTypeProvider;
|
||||
if (moduleTypeProvider == null) {
|
||||
return null;
|
||||
}
|
||||
const unparsedModuleConfig = this.config.moduleTypeProvider(moduleName);
|
||||
const unparsedModuleConfig = moduleTypeProvider(moduleName);
|
||||
if (unparsedModuleConfig != null) {
|
||||
const parsedModuleConfig = TypeSchema.safeParse(unparsedModuleConfig);
|
||||
if (!parsedModuleConfig.success) {
|
||||
|
||||
@@ -1001,6 +1001,7 @@ export function installTypeConfig(
|
||||
mutableOnlyIfOperandsAreMutable:
|
||||
typeConfig.mutableOnlyIfOperandsAreMutable === true,
|
||||
aliasing: typeConfig.aliasing,
|
||||
knownIncompatible: typeConfig.knownIncompatible ?? null,
|
||||
});
|
||||
}
|
||||
case 'hook': {
|
||||
@@ -1019,6 +1020,7 @@ export function installTypeConfig(
|
||||
returnValueKind: typeConfig.returnValueKind ?? ValueKind.Frozen,
|
||||
noAlias: typeConfig.noAlias === true,
|
||||
aliasing: typeConfig.aliasing,
|
||||
knownIncompatible: typeConfig.knownIncompatible ?? null,
|
||||
});
|
||||
}
|
||||
case 'object': {
|
||||
|
||||
@@ -332,6 +332,7 @@ export type FunctionSignature = {
|
||||
mutableOnlyIfOperandsAreMutable?: boolean;
|
||||
|
||||
impure?: boolean;
|
||||
knownIncompatible?: string | null | undefined;
|
||||
|
||||
canonicalName?: string;
|
||||
|
||||
|
||||
@@ -251,6 +251,7 @@ export type FunctionTypeConfig = {
|
||||
impure?: boolean | null | undefined;
|
||||
canonicalName?: string | null | undefined;
|
||||
aliasing?: AliasingSignatureConfig | null | undefined;
|
||||
knownIncompatible?: string | null | undefined;
|
||||
};
|
||||
export const FunctionTypeSchema: z.ZodType<FunctionTypeConfig> = z.object({
|
||||
kind: z.literal('function'),
|
||||
@@ -264,6 +265,7 @@ export const FunctionTypeSchema: z.ZodType<FunctionTypeConfig> = z.object({
|
||||
impure: z.boolean().nullable().optional(),
|
||||
canonicalName: z.string().nullable().optional(),
|
||||
aliasing: AliasingSignatureSchema.nullable().optional(),
|
||||
knownIncompatible: z.string().nullable().optional(),
|
||||
});
|
||||
|
||||
export type HookTypeConfig = {
|
||||
@@ -274,6 +276,7 @@ export type HookTypeConfig = {
|
||||
returnValueKind?: ValueKind | null | undefined;
|
||||
noAlias?: boolean | null | undefined;
|
||||
aliasing?: AliasingSignatureConfig | null | undefined;
|
||||
knownIncompatible?: string | null | undefined;
|
||||
};
|
||||
export const HookTypeSchema: z.ZodType<HookTypeConfig> = z.object({
|
||||
kind: z.literal('hook'),
|
||||
@@ -283,6 +286,7 @@ export const HookTypeSchema: z.ZodType<HookTypeConfig> = z.object({
|
||||
returnValueKind: ValueKindSchema.nullable().optional(),
|
||||
noAlias: z.boolean().nullable().optional(),
|
||||
aliasing: AliasingSignatureSchema.nullable().optional(),
|
||||
knownIncompatible: z.string().nullable().optional(),
|
||||
});
|
||||
|
||||
export type BuiltInTypeConfig =
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
IdentifierId,
|
||||
Instruction,
|
||||
InstructionKind,
|
||||
InstructionValue,
|
||||
isArrayType,
|
||||
isJsxType,
|
||||
isMapType,
|
||||
@@ -56,6 +57,7 @@ import {
|
||||
printAliasingSignature,
|
||||
printIdentifier,
|
||||
printInstruction,
|
||||
printInstructionValue,
|
||||
printPlace,
|
||||
printSourceLocation,
|
||||
} from '../HIR/PrintHIR';
|
||||
@@ -106,11 +108,11 @@ export function inferMutationAliasingEffects(
|
||||
const statesByBlock: Map<BlockId, InferenceState> = new Map();
|
||||
|
||||
for (const ref of fn.context) {
|
||||
const value: AliasingEffect = {
|
||||
kind: 'Create',
|
||||
into: ref,
|
||||
value: ValueKind.Context,
|
||||
reason: ValueReason.Other,
|
||||
// TODO: using InstructionValue as a bit of a hack, but it's pragmatic
|
||||
const value: InstructionValue = {
|
||||
kind: 'ObjectExpression',
|
||||
properties: [],
|
||||
loc: ref.loc,
|
||||
};
|
||||
initialState.initialize(value, {
|
||||
kind: ValueKind.Context,
|
||||
@@ -143,11 +145,10 @@ export function inferMutationAliasingEffects(
|
||||
}
|
||||
if (ref != null) {
|
||||
const place = ref.kind === 'Identifier' ? ref : ref.place;
|
||||
const value: AliasingEffect = {
|
||||
kind: 'Create',
|
||||
into: place,
|
||||
value: ValueKind.Mutable,
|
||||
reason: ValueReason.Other,
|
||||
const value: InstructionValue = {
|
||||
kind: 'ObjectExpression',
|
||||
properties: [],
|
||||
loc: place.loc,
|
||||
};
|
||||
initialState.initialize(value, {
|
||||
kind: ValueKind.Mutable,
|
||||
@@ -264,6 +265,8 @@ function findHoistedContextDeclarations(
|
||||
class Context {
|
||||
internedEffects: Map<string, AliasingEffect> = new Map();
|
||||
instructionSignatureCache: Map<Instruction, InstructionSignature> = new Map();
|
||||
effectInstructionValueCache: Map<AliasingEffect, InstructionValue> =
|
||||
new Map();
|
||||
applySignatureCache: Map<
|
||||
AliasingSignature,
|
||||
Map<AliasingEffect, Array<AliasingEffect> | null>
|
||||
@@ -315,11 +318,10 @@ function inferParam(
|
||||
paramKind: AbstractValue,
|
||||
): void {
|
||||
const place = param.kind === 'Identifier' ? param : param.place;
|
||||
const value: AliasingEffect = {
|
||||
kind: 'Create',
|
||||
into: place,
|
||||
value: paramKind.kind,
|
||||
reason: ValueReason.Other,
|
||||
const value: InstructionValue = {
|
||||
kind: 'Primitive',
|
||||
loc: place.loc,
|
||||
value: undefined,
|
||||
};
|
||||
initialState.initialize(value, paramKind);
|
||||
initialState.define(place, value);
|
||||
@@ -540,11 +542,20 @@ function applyEffect(
|
||||
});
|
||||
initialized.add(effect.into.identifier.id);
|
||||
|
||||
state.initialize(effect, {
|
||||
let value = context.effectInstructionValueCache.get(effect);
|
||||
if (value == null) {
|
||||
value = {
|
||||
kind: 'ObjectExpression',
|
||||
properties: [],
|
||||
loc: effect.into.loc,
|
||||
};
|
||||
context.effectInstructionValueCache.set(effect, value);
|
||||
}
|
||||
state.initialize(value, {
|
||||
kind: effect.value,
|
||||
reason: new Set([effect.reason]),
|
||||
});
|
||||
state.define(effect.into, effect);
|
||||
state.define(effect.into, value);
|
||||
effects.push(effect);
|
||||
break;
|
||||
}
|
||||
@@ -571,11 +582,20 @@ function applyEffect(
|
||||
initialized.add(effect.into.identifier.id);
|
||||
|
||||
const fromValue = state.kind(effect.from);
|
||||
state.initialize(effect, {
|
||||
let value = context.effectInstructionValueCache.get(effect);
|
||||
if (value == null) {
|
||||
value = {
|
||||
kind: 'ObjectExpression',
|
||||
properties: [],
|
||||
loc: effect.into.loc,
|
||||
};
|
||||
context.effectInstructionValueCache.set(effect, value);
|
||||
}
|
||||
state.initialize(value, {
|
||||
kind: fromValue.kind,
|
||||
reason: new Set(fromValue.reason),
|
||||
});
|
||||
state.define(effect.into, effect);
|
||||
state.define(effect.into, value);
|
||||
switch (fromValue.kind) {
|
||||
case ValueKind.Primitive:
|
||||
case ValueKind.Global: {
|
||||
@@ -663,11 +683,11 @@ function applyEffect(
|
||||
operand.effect = Effect.Read;
|
||||
}
|
||||
}
|
||||
state.initialize(effect, {
|
||||
state.initialize(effect.function, {
|
||||
kind: isMutable ? ValueKind.Mutable : ValueKind.Frozen,
|
||||
reason: new Set([]),
|
||||
});
|
||||
state.define(effect.into, effect);
|
||||
state.define(effect.into, effect.function);
|
||||
for (const capture of effect.captures) {
|
||||
applyEffect(
|
||||
context,
|
||||
@@ -773,20 +793,38 @@ function applyEffect(
|
||||
initialized,
|
||||
effects,
|
||||
);
|
||||
state.initialize(effect, {
|
||||
let value = context.effectInstructionValueCache.get(effect);
|
||||
if (value == null) {
|
||||
value = {
|
||||
kind: 'Primitive',
|
||||
value: undefined,
|
||||
loc: effect.from.loc,
|
||||
};
|
||||
context.effectInstructionValueCache.set(effect, value);
|
||||
}
|
||||
state.initialize(value, {
|
||||
kind: fromKind,
|
||||
reason: new Set(fromValue.reason),
|
||||
});
|
||||
state.define(effect.into, effect);
|
||||
state.define(effect.into, value);
|
||||
break;
|
||||
}
|
||||
case ValueKind.Global:
|
||||
case ValueKind.Primitive: {
|
||||
state.initialize(effect, {
|
||||
let value = context.effectInstructionValueCache.get(effect);
|
||||
if (value == null) {
|
||||
value = {
|
||||
kind: 'Primitive',
|
||||
value: undefined,
|
||||
loc: effect.from.loc,
|
||||
};
|
||||
context.effectInstructionValueCache.set(effect, value);
|
||||
}
|
||||
state.initialize(value, {
|
||||
kind: fromKind,
|
||||
reason: new Set(fromValue.reason),
|
||||
});
|
||||
state.define(effect.into, effect);
|
||||
state.define(effect.into, value);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
@@ -801,15 +839,14 @@ function applyEffect(
|
||||
const functionValues = state.values(effect.function);
|
||||
if (
|
||||
functionValues.length === 1 &&
|
||||
functionValues[0].kind === 'CreateFunction' &&
|
||||
functionValues[0].function.kind === 'FunctionExpression' &&
|
||||
functionValues[0].function.loweredFunc.func.aliasingEffects != null
|
||||
functionValues[0].kind === 'FunctionExpression' &&
|
||||
functionValues[0].loweredFunc.func.aliasingEffects != null
|
||||
) {
|
||||
/*
|
||||
* We're calling a locally declared function, we already know it's effects!
|
||||
* We just have to substitute in the args for the params
|
||||
*/
|
||||
const functionExpr = functionValues[0].function;
|
||||
const functionExpr = functionValues[0];
|
||||
let signature = context.functionSignatureCache.get(functionExpr);
|
||||
if (signature == null) {
|
||||
signature = buildSignatureFromFunctionExpression(
|
||||
@@ -1099,19 +1136,19 @@ class InferenceState {
|
||||
#isFunctionExpression: boolean;
|
||||
|
||||
// The kind of each value, based on its allocation site
|
||||
#values: Map<AliasingEffect, AbstractValue>;
|
||||
#values: Map<InstructionValue, AbstractValue>;
|
||||
/*
|
||||
* The set of values pointed to by each identifier. This is a set
|
||||
* to accomodate phi points (where a variable may have different
|
||||
* values from different control flow paths).
|
||||
*/
|
||||
#variables: Map<IdentifierId, Set<AliasingEffect>>;
|
||||
#variables: Map<IdentifierId, Set<InstructionValue>>;
|
||||
|
||||
constructor(
|
||||
env: Environment,
|
||||
isFunctionExpression: boolean,
|
||||
values: Map<AliasingEffect, AbstractValue>,
|
||||
variables: Map<IdentifierId, Set<AliasingEffect>>,
|
||||
values: Map<InstructionValue, AbstractValue>,
|
||||
variables: Map<IdentifierId, Set<InstructionValue>>,
|
||||
) {
|
||||
this.env = env;
|
||||
this.#isFunctionExpression = isFunctionExpression;
|
||||
@@ -1131,11 +1168,18 @@ class InferenceState {
|
||||
}
|
||||
|
||||
// (Re)initializes a @param value with its default @param kind.
|
||||
initialize(value: AliasingEffect, kind: AbstractValue): void {
|
||||
initialize(value: InstructionValue, kind: AbstractValue): void {
|
||||
CompilerError.invariant(value.kind !== 'LoadLocal', {
|
||||
reason:
|
||||
'[InferMutationAliasingEffects] Expected all top-level identifiers to be defined as variables, not values',
|
||||
description: null,
|
||||
loc: value.loc,
|
||||
suggestions: null,
|
||||
});
|
||||
this.#values.set(value, kind);
|
||||
}
|
||||
|
||||
values(place: Place): Array<AliasingEffect> {
|
||||
values(place: Place): Array<InstructionValue> {
|
||||
const values = this.#variables.get(place.identifier.id);
|
||||
CompilerError.invariant(values != null, {
|
||||
reason: `[InferMutationAliasingEffects] Expected value kind to be initialized`,
|
||||
@@ -1198,13 +1242,13 @@ class InferenceState {
|
||||
}
|
||||
|
||||
// Defines (initializing or updating) a variable with a specific kind of value.
|
||||
define(place: Place, value: AliasingEffect): void {
|
||||
define(place: Place, value: InstructionValue): void {
|
||||
CompilerError.invariant(this.#values.has(value), {
|
||||
reason: `[InferMutationAliasingEffects] Expected value to be initialized at '${printSourceLocation(
|
||||
place.loc,
|
||||
value.loc,
|
||||
)}'`,
|
||||
description: printAliasingEffect(value),
|
||||
loc: place.loc,
|
||||
description: printInstructionValue(value),
|
||||
loc: value.loc,
|
||||
suggestions: null,
|
||||
});
|
||||
this.#variables.set(place.identifier.id, new Set([value]));
|
||||
@@ -1244,17 +1288,17 @@ class InferenceState {
|
||||
}
|
||||
}
|
||||
|
||||
freezeValue(value: AliasingEffect, reason: ValueReason): void {
|
||||
freezeValue(value: InstructionValue, reason: ValueReason): void {
|
||||
this.#values.set(value, {
|
||||
kind: ValueKind.Frozen,
|
||||
reason: new Set([reason]),
|
||||
});
|
||||
if (
|
||||
value.kind === 'CreateFunction' &&
|
||||
value.kind === 'FunctionExpression' &&
|
||||
(this.env.config.enablePreserveExistingMemoizationGuarantees ||
|
||||
this.env.config.enableTransitivelyFreezeFunctionExpressions)
|
||||
) {
|
||||
for (const place of value.function.loweredFunc.func.context) {
|
||||
for (const place of value.loweredFunc.func.context) {
|
||||
this.freeze(place, reason);
|
||||
}
|
||||
}
|
||||
@@ -1329,8 +1373,8 @@ class InferenceState {
|
||||
* termination.
|
||||
*/
|
||||
merge(other: InferenceState): InferenceState | null {
|
||||
let nextValues: Map<AliasingEffect, AbstractValue> | null = null;
|
||||
let nextVariables: Map<IdentifierId, Set<AliasingEffect>> | null = null;
|
||||
let nextValues: Map<InstructionValue, AbstractValue> | null = null;
|
||||
let nextVariables: Map<IdentifierId, Set<InstructionValue>> | null = null;
|
||||
|
||||
for (const [id, thisValue] of this.#values) {
|
||||
const otherValue = other.#values.get(id);
|
||||
@@ -1354,7 +1398,7 @@ class InferenceState {
|
||||
for (const [id, thisValues] of this.#variables) {
|
||||
const otherValues = other.#variables.get(id);
|
||||
if (otherValues !== undefined) {
|
||||
let mergedValues: Set<AliasingEffect> | null = null;
|
||||
let mergedValues: Set<InstructionValue> | null = null;
|
||||
for (const otherValue of otherValues) {
|
||||
if (!thisValues.has(otherValue)) {
|
||||
mergedValues = mergedValues ?? new Set(thisValues);
|
||||
@@ -1407,8 +1451,8 @@ class InferenceState {
|
||||
*/
|
||||
debug(): any {
|
||||
const result: any = {values: {}, variables: {}};
|
||||
const objects: Map<AliasingEffect, number> = new Map();
|
||||
function identify(value: AliasingEffect): number {
|
||||
const objects: Map<InstructionValue, number> = new Map();
|
||||
function identify(value: InstructionValue): number {
|
||||
let id = objects.get(value);
|
||||
if (id == null) {
|
||||
id = objects.size;
|
||||
@@ -1420,7 +1464,7 @@ class InferenceState {
|
||||
const id = identify(value);
|
||||
result.values[id] = {
|
||||
abstract: this.debugAbstractValue(kind),
|
||||
value: printAliasingEffect(value),
|
||||
value: printInstructionValue(value),
|
||||
};
|
||||
}
|
||||
for (const [variable, values] of this.#variables) {
|
||||
@@ -1437,7 +1481,7 @@ class InferenceState {
|
||||
}
|
||||
|
||||
inferPhi(phi: Phi): void {
|
||||
const values: Set<AliasingEffect> = new Set();
|
||||
const values: Set<InstructionValue> = new Set();
|
||||
for (const [_, operand] of phi.operands) {
|
||||
const operandValues = this.#variables.get(operand.identifier.id);
|
||||
// This is a backedge that will be handled later by State.merge
|
||||
@@ -2045,7 +2089,7 @@ function computeSignatureForInstruction(
|
||||
effects.push({
|
||||
kind: 'Freeze',
|
||||
value: operand,
|
||||
reason: ValueReason.Other,
|
||||
reason: ValueReason.HookCaptured,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2126,6 +2170,27 @@ function computeEffectsForLegacySignature(
|
||||
}),
|
||||
});
|
||||
}
|
||||
if (signature.knownIncompatible != null && state.env.isInferredMemoEnabled) {
|
||||
const errors = new CompilerError();
|
||||
errors.pushDiagnostic(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.IncompatibleLibrary,
|
||||
severity: ErrorSeverity.IncompatibleLibrary,
|
||||
reason: 'Use of incompatible library',
|
||||
description: [
|
||||
'This API returns functions which cannot be memoized without leading to stale UI. ' +
|
||||
'To prevent this, by default React Compiler will skip memoizing this component/hook. ' +
|
||||
'However, you may see issues if values from this API are passed to other components/hooks that are ' +
|
||||
'memoized.',
|
||||
].join(''),
|
||||
}).withDetail({
|
||||
kind: 'error',
|
||||
loc: receiver.loc,
|
||||
message: signature.knownIncompatible,
|
||||
}),
|
||||
);
|
||||
throw errors;
|
||||
}
|
||||
const stores: Array<Place> = [];
|
||||
const captures: Array<Place> = [];
|
||||
function visit(place: Place, effect: Effect): void {
|
||||
@@ -2303,9 +2368,8 @@ function areArgumentsImmutableAndNonMutating(
|
||||
const values = state.values(place);
|
||||
for (const value of values) {
|
||||
if (
|
||||
value.kind === 'CreateFunction' &&
|
||||
value.function.kind === 'FunctionExpression' &&
|
||||
value.function.loweredFunc.func.params.some(param => {
|
||||
value.kind === 'FunctionExpression' &&
|
||||
value.loweredFunc.func.params.some(param => {
|
||||
const place = param.kind === 'Identifier' ? param : param.place;
|
||||
const range = place.identifier.mutableRange;
|
||||
return range.end > range.start + 1;
|
||||
@@ -2498,47 +2562,10 @@ function computeEffectsForSignature(
|
||||
break;
|
||||
}
|
||||
case 'CreateFunction': {
|
||||
const applyInto = substitutions.get(effect.into.identifier.id);
|
||||
if (applyInto == null || applyInto.length !== 1) {
|
||||
return null;
|
||||
}
|
||||
const captures: Array<Place> = [];
|
||||
for (let i = 0; i < effect.captures.length; i++) {
|
||||
const substitution = substitutions.get(
|
||||
effect.captures[i].identifier.id,
|
||||
);
|
||||
if (substitution == null || substitution.length !== 1) {
|
||||
return null;
|
||||
}
|
||||
captures.push(substitution[0]);
|
||||
}
|
||||
const context: Array<Place> = [];
|
||||
const originalContext = effect.function.loweredFunc.func.context;
|
||||
for (let i = 0; i < originalContext.length; i++) {
|
||||
const substitution = substitutions.get(
|
||||
originalContext[i].identifier.id,
|
||||
);
|
||||
if (substitution == null || substitution.length !== 1) {
|
||||
return null;
|
||||
}
|
||||
context.push(substitution[0]);
|
||||
}
|
||||
effects.push({
|
||||
kind: 'CreateFunction',
|
||||
into: applyInto[0],
|
||||
function: {
|
||||
...effect.function,
|
||||
loweredFunc: {
|
||||
...effect.function.loweredFunc,
|
||||
func: {
|
||||
...effect.function.loweredFunc.func,
|
||||
context,
|
||||
},
|
||||
},
|
||||
},
|
||||
captures,
|
||||
CompilerError.throwTodo({
|
||||
reason: `Support CreateFrom effects in signatures`,
|
||||
loc: receiver.loc,
|
||||
});
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
assertExhaustive(
|
||||
|
||||
@@ -141,7 +141,7 @@ export function inferMutationAliasingRanges(
|
||||
} else if (effect.kind === 'CreateFunction') {
|
||||
state.create(effect.into, {
|
||||
kind: 'Function',
|
||||
effect,
|
||||
function: effect.function.loweredFunc.func,
|
||||
});
|
||||
} else if (effect.kind === 'CreateFrom') {
|
||||
state.createFrom(index++, effect.from, effect.into);
|
||||
@@ -156,7 +156,7 @@ export function inferMutationAliasingRanges(
|
||||
* invariant here.
|
||||
*/
|
||||
if (!state.nodes.has(effect.into.identifier)) {
|
||||
state.create(effect.into, {kind: 'Assign'});
|
||||
state.create(effect.into, {kind: 'Object'});
|
||||
}
|
||||
state.assign(index++, effect.from, effect.into);
|
||||
} else if (effect.kind === 'Alias') {
|
||||
@@ -474,99 +474,6 @@ export function inferMutationAliasingRanges(
|
||||
}
|
||||
}
|
||||
|
||||
const tracked: Array<Place> = [];
|
||||
for (const param of [...fn.params, ...fn.context, fn.returns]) {
|
||||
const place = param.kind === 'Identifier' ? param : param.place;
|
||||
tracked.push(place);
|
||||
}
|
||||
|
||||
const returned: Set<Node> = new Set();
|
||||
const queue: Array<Node> = [state.nodes.get(fn.returns.identifier)!];
|
||||
const seen: Set<Node> = new Set();
|
||||
while (queue.length !== 0) {
|
||||
const node = queue.pop()!;
|
||||
if (seen.has(node)) {
|
||||
continue;
|
||||
}
|
||||
seen.add(node);
|
||||
for (const id of node.aliases.keys()) {
|
||||
queue.push(state.nodes.get(id)!);
|
||||
}
|
||||
for (const id of node.createdFrom.keys()) {
|
||||
queue.push(state.nodes.get(id)!);
|
||||
}
|
||||
if (node.id.id === fn.returns.identifier.id) {
|
||||
continue;
|
||||
}
|
||||
switch (node.value.kind) {
|
||||
case 'Assign':
|
||||
case 'CreateFrom': {
|
||||
break;
|
||||
}
|
||||
case 'Phi':
|
||||
case 'Object':
|
||||
case 'Function': {
|
||||
returned.add(node);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
assertExhaustive(
|
||||
node.value,
|
||||
`Unexpected node value kind '${(node.value as any).kind}'`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
const returnedValues = [...returned];
|
||||
if (
|
||||
returnedValues.length === 1 &&
|
||||
returnedValues[0].value.kind === 'Object' &&
|
||||
tracked.some(place => place.identifier.id === returnedValues[0].id.id)
|
||||
) {
|
||||
const from = tracked.find(
|
||||
place => place.identifier.id === returnedValues[0].id.id,
|
||||
)!;
|
||||
functionEffects.push({
|
||||
kind: 'Assign',
|
||||
from,
|
||||
into: fn.returns,
|
||||
});
|
||||
} else if (
|
||||
returnedValues.length === 1 &&
|
||||
returnedValues[0].value.kind === 'Function'
|
||||
) {
|
||||
const outerContext = new Set(fn.context.map(p => p.identifier.id));
|
||||
const effect = returnedValues[0].value.effect;
|
||||
functionEffects.push({
|
||||
kind: 'CreateFunction',
|
||||
function: {
|
||||
...effect.function,
|
||||
loweredFunc: {
|
||||
func: {
|
||||
...effect.function.loweredFunc.func,
|
||||
context: effect.function.loweredFunc.func.context.filter(p =>
|
||||
outerContext.has(p.identifier.id),
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
captures: effect.captures.filter(p => outerContext.has(p.identifier.id)),
|
||||
into: fn.returns,
|
||||
});
|
||||
} else {
|
||||
const returns = fn.returns.identifier;
|
||||
functionEffects.push({
|
||||
kind: 'Create',
|
||||
into: fn.returns,
|
||||
value: isPrimitiveType(returns)
|
||||
? ValueKind.Primitive
|
||||
: isJsxType(returns.type)
|
||||
? ValueKind.Frozen
|
||||
: ValueKind.Mutable,
|
||||
reason: ValueReason.KnownReturnSignature,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Part 3
|
||||
* Finish populating the externally visible effects. Above we bubble-up the side effects
|
||||
@@ -574,12 +481,28 @@ export function inferMutationAliasingRanges(
|
||||
* Here we populate an effect to create the return value as well as populating alias/capture
|
||||
* effects for how data flows between the params, context vars, and return.
|
||||
*/
|
||||
const returns = fn.returns.identifier;
|
||||
functionEffects.push({
|
||||
kind: 'Create',
|
||||
into: fn.returns,
|
||||
value: isPrimitiveType(returns)
|
||||
? ValueKind.Primitive
|
||||
: isJsxType(returns.type)
|
||||
? ValueKind.Frozen
|
||||
: ValueKind.Mutable,
|
||||
reason: ValueReason.KnownReturnSignature,
|
||||
});
|
||||
/**
|
||||
* Determine precise data-flow effects by simulating transitive mutations of the params/
|
||||
* captures and seeing what other params/context variables are affected. Anything that
|
||||
* would be transitively mutated needs a capture relationship.
|
||||
*/
|
||||
const tracked: Array<Place> = [];
|
||||
const ignoredErrors = new CompilerError();
|
||||
for (const param of [...fn.params, ...fn.context, fn.returns]) {
|
||||
const place = param.kind === 'Identifier' ? param : param.place;
|
||||
tracked.push(place);
|
||||
}
|
||||
for (const into of tracked) {
|
||||
const mutationIndex = index++;
|
||||
state.mutate(
|
||||
@@ -665,14 +588,9 @@ type Node = {
|
||||
lastMutated: number;
|
||||
mutationReason: MutationReason | null;
|
||||
value:
|
||||
| {kind: 'Assign'}
|
||||
| {kind: 'CreateFrom'}
|
||||
| {kind: 'Object'}
|
||||
| {kind: 'Phi'}
|
||||
| {
|
||||
kind: 'Function';
|
||||
effect: Extract<AliasingEffect, {kind: 'CreateFunction'}>;
|
||||
};
|
||||
| {kind: 'Function'; function: HIRFunction};
|
||||
};
|
||||
class AliasingState {
|
||||
nodes: Map<Identifier, Node> = new Map();
|
||||
@@ -694,7 +612,7 @@ class AliasingState {
|
||||
}
|
||||
|
||||
createFrom(index: number, from: Place, into: Place): void {
|
||||
this.create(into, {kind: 'CreateFrom'});
|
||||
this.create(into, {kind: 'Object'});
|
||||
const fromNode = this.nodes.get(from.identifier);
|
||||
const toNode = this.nodes.get(into.identifier);
|
||||
if (fromNode == null || toNode == null) {
|
||||
@@ -756,10 +674,7 @@ class AliasingState {
|
||||
continue;
|
||||
}
|
||||
if (node.value.kind === 'Function') {
|
||||
appendFunctionErrors(
|
||||
errors,
|
||||
node.value.effect.function.loweredFunc.func,
|
||||
);
|
||||
appendFunctionErrors(errors, node.value.function);
|
||||
}
|
||||
for (const [alias, when] of node.createdFrom) {
|
||||
if (when >= index) {
|
||||
@@ -823,10 +738,7 @@ class AliasingState {
|
||||
node.transitive == null &&
|
||||
node.local == null
|
||||
) {
|
||||
appendFunctionErrors(
|
||||
errors,
|
||||
node.value.effect.function.loweredFunc.func,
|
||||
);
|
||||
appendFunctionErrors(errors, node.value.function);
|
||||
}
|
||||
if (transitive) {
|
||||
if (node.transitive == null || node.transitive.kind < kind) {
|
||||
|
||||
@@ -175,6 +175,41 @@ export function alignReactiveScopesToBlockScopesHIR(fn: HIRFunction): void {
|
||||
if (node != null) {
|
||||
valueBlockNodes.set(fallthrough, node);
|
||||
}
|
||||
} else if (terminal.kind === 'goto') {
|
||||
/**
|
||||
* If we encounter a goto that is not to the natural fallthrough of the current
|
||||
* block (not the topmost fallthrough on the stack), then this is a goto to a
|
||||
* label. Any scopes that extend beyond the goto must be extended to include
|
||||
* the labeled range, so that the break statement doesn't accidentally jump
|
||||
* out of the scope. We do this by extending the start and end of the scope's
|
||||
* range to the label and its fallthrough respectively.
|
||||
*/
|
||||
const start = activeBlockFallthroughRanges.find(
|
||||
range => range.fallthrough === terminal.block,
|
||||
);
|
||||
if (start != null && start !== activeBlockFallthroughRanges.at(-1)) {
|
||||
const fallthroughBlock = fn.body.blocks.get(start.fallthrough)!;
|
||||
const firstId =
|
||||
fallthroughBlock.instructions[0]?.id ?? fallthroughBlock.terminal.id;
|
||||
for (const scope of activeScopes) {
|
||||
/**
|
||||
* activeScopes is only filtered at block start points, so some of the
|
||||
* scopes may not actually be active anymore, ie we've past their end
|
||||
* instruction. Only extend ranges for scopes that are actually active.
|
||||
*
|
||||
* TODO: consider pruning activeScopes per instruction
|
||||
*/
|
||||
if (scope.range.end <= terminal.id) {
|
||||
continue;
|
||||
}
|
||||
scope.range.start = makeInstructionId(
|
||||
Math.min(start.range.start, scope.range.start),
|
||||
);
|
||||
scope.range.end = makeInstructionId(
|
||||
Math.max(firstId, scope.range.end),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -411,7 +411,9 @@ class CollectDependenciesVisitor extends ReactiveFunctionVisitor<
|
||||
this.state = state;
|
||||
this.options = {
|
||||
memoizeJsxElements: !this.env.config.enableForest,
|
||||
forceMemoizePrimitives: this.env.config.enableForest,
|
||||
forceMemoizePrimitives:
|
||||
this.env.config.enableForest ||
|
||||
this.env.config.enablePreserveExistingMemoizationGuarantees,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -534,9 +536,23 @@ class CollectDependenciesVisitor extends ReactiveFunctionVisitor<
|
||||
case 'JSXText':
|
||||
case 'BinaryExpression':
|
||||
case 'UnaryExpression': {
|
||||
const level = options.forceMemoizePrimitives
|
||||
? MemoizationLevel.Memoized
|
||||
: MemoizationLevel.Never;
|
||||
if (options.forceMemoizePrimitives) {
|
||||
/**
|
||||
* Because these instructions produce primitives we usually don't consider
|
||||
* them as escape points: they are known to copy, not return references.
|
||||
* However if we're forcing memoization of primitives then we mark these
|
||||
* instructions as needing memoization and walk their rvalues to ensure
|
||||
* any scopes transitively reachable from the rvalues are considered for
|
||||
* memoization. Note: we may still prune primitive-producing scopes if
|
||||
* they don't ultimately escape at all.
|
||||
*/
|
||||
const level = MemoizationLevel.Conditional;
|
||||
return {
|
||||
lvalues: lvalue !== null ? [{place: lvalue, level}] : [],
|
||||
rvalues: [...eachReactiveValueOperand(value)],
|
||||
};
|
||||
}
|
||||
const level = MemoizationLevel.Never;
|
||||
return {
|
||||
// All of these instructions return a primitive value and never need to be memoized
|
||||
lvalues: lvalue !== null ? [{place: lvalue, level}] : [],
|
||||
@@ -685,9 +701,7 @@ class CollectDependenciesVisitor extends ReactiveFunctionVisitor<
|
||||
}
|
||||
case 'ComputedLoad':
|
||||
case 'PropertyLoad': {
|
||||
const level = options.forceMemoizePrimitives
|
||||
? MemoizationLevel.Memoized
|
||||
: MemoizationLevel.Conditional;
|
||||
const level = MemoizationLevel.Conditional;
|
||||
return {
|
||||
// Indirection for the inner value, memoized if the value is
|
||||
lvalues: lvalue !== null ? [{place: lvalue, level}] : [],
|
||||
|
||||
@@ -255,11 +255,16 @@ function parseConfigStringAsJS(
|
||||
|
||||
console.log('OVERRIDE:', parsedConfig);
|
||||
|
||||
const environment = parseConfigPragmaEnvironmentForTest(
|
||||
'',
|
||||
defaults.environment ?? {},
|
||||
);
|
||||
|
||||
const options: Record<keyof PluginOptions, unknown> = {
|
||||
...defaultOptions,
|
||||
panicThreshold: 'all_errors',
|
||||
compilationMode: defaults.compilationMode,
|
||||
environment: defaults.environment ?? defaultOptions.environment,
|
||||
environment,
|
||||
};
|
||||
|
||||
// Apply parsed config, merging environment if it exists
|
||||
@@ -269,22 +274,9 @@ function parseConfigStringAsJS(
|
||||
...parsedConfig.environment,
|
||||
};
|
||||
|
||||
// Apply complex defaults for environment flags that are set to true
|
||||
const environmentConfig: Partial<Record<keyof EnvironmentConfig, unknown>> =
|
||||
{};
|
||||
for (const [key, value] of Object.entries(mergedEnvironment)) {
|
||||
if (hasOwnProperty(EnvironmentConfigSchema.shape, key)) {
|
||||
if (value === true && key in testComplexConfigDefaults) {
|
||||
environmentConfig[key] = testComplexConfigDefaults[key];
|
||||
} else {
|
||||
environmentConfig[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validate environment config
|
||||
const validatedEnvironment =
|
||||
EnvironmentConfigSchema.safeParse(environmentConfig);
|
||||
EnvironmentConfigSchema.safeParse(mergedEnvironment);
|
||||
if (!validatedEnvironment.success) {
|
||||
CompilerError.invariant(false, {
|
||||
reason: 'Invalid environment configuration in config pragma',
|
||||
@@ -294,10 +286,6 @@ function parseConfigStringAsJS(
|
||||
});
|
||||
}
|
||||
|
||||
if (validatedEnvironment.data.enableResetCacheOnSourceFileChanges == null) {
|
||||
validatedEnvironment.data.enableResetCacheOnSourceFileChanges = false;
|
||||
}
|
||||
|
||||
options.environment = validatedEnvironment.data;
|
||||
}
|
||||
|
||||
@@ -308,9 +296,7 @@ function parseConfigStringAsJS(
|
||||
}
|
||||
|
||||
if (hasOwnProperty(defaultOptions, key)) {
|
||||
if (value === true && key in testComplexPluginOptionDefaults) {
|
||||
options[key] = testComplexPluginOptionDefaults[key];
|
||||
} else if (key === 'target' && value === 'donotuse_meta_internal') {
|
||||
if (key === 'target' && value === 'donotuse_meta_internal') {
|
||||
options[key] = {
|
||||
kind: value,
|
||||
runtimeModule: 'react',
|
||||
|
||||
@@ -284,8 +284,7 @@ function validateInferredDep(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.PreserveManualMemo,
|
||||
severity: ErrorSeverity.CannotPreserveMemoization,
|
||||
reason:
|
||||
'Compilation skipped because existing memoization could not be preserved',
|
||||
reason: 'Existing memoization could not be preserved',
|
||||
description: [
|
||||
'React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. ',
|
||||
'The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. ',
|
||||
@@ -539,8 +538,7 @@ class Visitor extends ReactiveFunctionVisitor<VisitorState> {
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.PreserveManualMemo,
|
||||
severity: ErrorSeverity.CannotPreserveMemoization,
|
||||
reason:
|
||||
'Compilation skipped because existing memoization could not be preserved',
|
||||
reason: 'Existing memoization could not be preserved',
|
||||
description: [
|
||||
'React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. ',
|
||||
'This dependency may be mutated later, which could cause the value to change unexpectedly.',
|
||||
@@ -588,8 +586,7 @@ class Visitor extends ReactiveFunctionVisitor<VisitorState> {
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.PreserveManualMemo,
|
||||
severity: ErrorSeverity.CannotPreserveMemoization,
|
||||
reason:
|
||||
'Compilation skipped because existing memoization could not be preserved',
|
||||
reason: 'Existing memoization could not be preserved',
|
||||
description: [
|
||||
'React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This value was memoized in source but not in compilation output. ',
|
||||
DEBUG
|
||||
|
||||
@@ -46,14 +46,16 @@ function useFoo(t0) {
|
||||
t1 = $[0];
|
||||
}
|
||||
let items = t1;
|
||||
bb0: if ($[1] !== cond) {
|
||||
if (cond) {
|
||||
items = [];
|
||||
} else {
|
||||
break bb0;
|
||||
}
|
||||
if ($[1] !== cond) {
|
||||
bb0: {
|
||||
if (cond) {
|
||||
items = [];
|
||||
} else {
|
||||
break bb0;
|
||||
}
|
||||
|
||||
items.push(2);
|
||||
items.push(2);
|
||||
}
|
||||
$[1] = cond;
|
||||
$[2] = items;
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @compilationMode:"infer" @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees
|
||||
import {useMemo} from 'react';
|
||||
import {makeObject_Primitives, ValidateMemoization} from 'shared-runtime';
|
||||
|
||||
function Component(props) {
|
||||
const result = useMemo(
|
||||
() => makeObject(props.value).value + 1,
|
||||
[props.value]
|
||||
);
|
||||
console.log(result);
|
||||
return 'ok';
|
||||
}
|
||||
|
||||
function makeObject(value) {
|
||||
console.log(value);
|
||||
return {value};
|
||||
}
|
||||
|
||||
export const TODO_FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{value: 42}],
|
||||
sequentialRenders: [
|
||||
{value: 42},
|
||||
{value: 42},
|
||||
{value: 3.14},
|
||||
{value: 3.14},
|
||||
{value: 42},
|
||||
{value: 3.14},
|
||||
{value: 42},
|
||||
{value: 3.14},
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @compilationMode:"infer" @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees
|
||||
import { useMemo } from "react";
|
||||
import { makeObject_Primitives, ValidateMemoization } from "shared-runtime";
|
||||
|
||||
function Component(props) {
|
||||
const result = makeObject(props.value).value + 1;
|
||||
|
||||
console.log(result);
|
||||
return "ok";
|
||||
}
|
||||
|
||||
function makeObject(value) {
|
||||
console.log(value);
|
||||
return { value };
|
||||
}
|
||||
|
||||
export const TODO_FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ value: 42 }],
|
||||
sequentialRenders: [
|
||||
{ value: 42 },
|
||||
{ value: 42 },
|
||||
{ value: 3.14 },
|
||||
{ value: 3.14 },
|
||||
{ value: 42 },
|
||||
{ value: 3.14 },
|
||||
{ value: 42 },
|
||||
{ value: 3.14 },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: exception) Fixture not implemented
|
||||
@@ -0,0 +1,32 @@
|
||||
// @compilationMode:"infer" @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees
|
||||
import {useMemo} from 'react';
|
||||
import {makeObject_Primitives, ValidateMemoization} from 'shared-runtime';
|
||||
|
||||
function Component(props) {
|
||||
const result = useMemo(
|
||||
() => makeObject(props.value).value + 1,
|
||||
[props.value]
|
||||
);
|
||||
console.log(result);
|
||||
return 'ok';
|
||||
}
|
||||
|
||||
function makeObject(value) {
|
||||
console.log(value);
|
||||
return {value};
|
||||
}
|
||||
|
||||
export const TODO_FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{value: 42}],
|
||||
sequentialRenders: [
|
||||
{value: 42},
|
||||
{value: 42},
|
||||
{value: 3.14},
|
||||
{value: 3.14},
|
||||
{value: 42},
|
||||
{value: 3.14},
|
||||
{value: 42},
|
||||
{value: 3.14},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,81 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @compilationMode:"infer" @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees
|
||||
import {useMemo} from 'react';
|
||||
import {makeObject_Primitives, ValidateMemoization} from 'shared-runtime';
|
||||
|
||||
function Component(props) {
|
||||
const result = makeObject(props.value).value + 1;
|
||||
console.log(result);
|
||||
return 'ok';
|
||||
}
|
||||
|
||||
function makeObject(value) {
|
||||
console.log(value);
|
||||
return {value};
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{value: 42}],
|
||||
sequentialRenders: [
|
||||
{value: 42},
|
||||
{value: 42},
|
||||
{value: 3.14},
|
||||
{value: 3.14},
|
||||
{value: 42},
|
||||
{value: 3.14},
|
||||
{value: 42},
|
||||
{value: 3.14},
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @compilationMode:"infer" @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees
|
||||
import { useMemo } from "react";
|
||||
import { makeObject_Primitives, ValidateMemoization } from "shared-runtime";
|
||||
|
||||
function Component(props) {
|
||||
const result = makeObject(props.value).value + 1;
|
||||
console.log(result);
|
||||
return "ok";
|
||||
}
|
||||
|
||||
function makeObject(value) {
|
||||
console.log(value);
|
||||
return { value };
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ value: 42 }],
|
||||
sequentialRenders: [
|
||||
{ value: 42 },
|
||||
{ value: 42 },
|
||||
{ value: 3.14 },
|
||||
{ value: 3.14 },
|
||||
{ value: 42 },
|
||||
{ value: 3.14 },
|
||||
{ value: 42 },
|
||||
{ value: 3.14 },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) "ok"
|
||||
"ok"
|
||||
"ok"
|
||||
"ok"
|
||||
"ok"
|
||||
"ok"
|
||||
"ok"
|
||||
"ok"
|
||||
logs: [42,43,42,43,3.14,4.140000000000001,3.14,4.140000000000001,42,43,3.14,4.140000000000001,42,43,3.14,4.140000000000001]
|
||||
@@ -0,0 +1,29 @@
|
||||
// @compilationMode:"infer" @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees
|
||||
import {useMemo} from 'react';
|
||||
import {makeObject_Primitives, ValidateMemoization} from 'shared-runtime';
|
||||
|
||||
function Component(props) {
|
||||
const result = makeObject(props.value).value + 1;
|
||||
console.log(result);
|
||||
return 'ok';
|
||||
}
|
||||
|
||||
function makeObject(value) {
|
||||
console.log(value);
|
||||
return {value};
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{value: 42}],
|
||||
sequentialRenders: [
|
||||
{value: 42},
|
||||
{value: 42},
|
||||
{value: 3.14},
|
||||
{value: 3.14},
|
||||
{value: 42},
|
||||
{value: 3.14},
|
||||
{value: 42},
|
||||
{value: 3.14},
|
||||
],
|
||||
};
|
||||
@@ -26,7 +26,7 @@ function Component(props) {
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Memoization: Compilation skipped because existing memoization could not be preserved
|
||||
Compilation Skipped: Existing memoization could not be preserved
|
||||
|
||||
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `props.items`, but the source dependencies were [props?.items, props.cond]. Inferred different dependency than source.
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ function Component(props) {
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Memoization: Compilation skipped because existing memoization could not be preserved
|
||||
Compilation Skipped: Existing memoization could not be preserved
|
||||
|
||||
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `props.items`, but the source dependencies were [props?.items, props.cond]. Inferred different dependency than source.
|
||||
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
function Component() {
|
||||
const f = () => () => {
|
||||
global.property = true;
|
||||
};
|
||||
f()();
|
||||
return <div>Ooops</div>;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {knownIncompatible} from 'ReactCompilerKnownIncompatibleTest';
|
||||
|
||||
function Component() {
|
||||
const data = knownIncompatible();
|
||||
return <div>Error</div>;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Compilation Skipped: Use of incompatible library
|
||||
|
||||
This API returns functions which cannot be memoized without leading to stale UI. To prevent this, by default React Compiler will skip memoizing this component/hook. However, you may see issues if values from this API are passed to other components/hooks that are memoized.
|
||||
|
||||
error.invalid-known-incompatible-function.ts:4:15
|
||||
2 |
|
||||
3 | function Component() {
|
||||
> 4 | const data = knownIncompatible();
|
||||
| ^^^^^^^^^^^^^^^^^ useKnownIncompatible is known to be incompatible
|
||||
5 | return <div>Error</div>;
|
||||
6 | }
|
||||
7 |
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import {knownIncompatible} from 'ReactCompilerKnownIncompatibleTest';
|
||||
|
||||
function Component() {
|
||||
const data = knownIncompatible();
|
||||
return <div>Error</div>;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {useKnownIncompatibleIndirect} from 'ReactCompilerKnownIncompatibleTest';
|
||||
|
||||
function Component() {
|
||||
const {incompatible} = useKnownIncompatibleIndirect();
|
||||
return <div>{incompatible()}</div>;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Compilation Skipped: Use of incompatible library
|
||||
|
||||
This API returns functions which cannot be memoized without leading to stale UI. To prevent this, by default React Compiler will skip memoizing this component/hook. However, you may see issues if values from this API are passed to other components/hooks that are memoized.
|
||||
|
||||
error.invalid-known-incompatible-hook-return-property.ts:5:15
|
||||
3 | function Component() {
|
||||
4 | const {incompatible} = useKnownIncompatibleIndirect();
|
||||
> 5 | return <div>{incompatible()}</div>;
|
||||
| ^^^^^^^^^^^^ useKnownIncompatibleIndirect returns an incompatible() function that is known incompatible
|
||||
6 | }
|
||||
7 |
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import {useKnownIncompatibleIndirect} from 'ReactCompilerKnownIncompatibleTest';
|
||||
|
||||
function Component() {
|
||||
const {incompatible} = useKnownIncompatibleIndirect();
|
||||
return <div>{incompatible()}</div>;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {useKnownIncompatible} from 'ReactCompilerKnownIncompatibleTest';
|
||||
|
||||
function Component() {
|
||||
const data = useKnownIncompatible();
|
||||
return <div>Error</div>;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Compilation Skipped: Use of incompatible library
|
||||
|
||||
This API returns functions which cannot be memoized without leading to stale UI. To prevent this, by default React Compiler will skip memoizing this component/hook. However, you may see issues if values from this API are passed to other components/hooks that are memoized.
|
||||
|
||||
error.invalid-known-incompatible-hook.ts:4:15
|
||||
2 |
|
||||
3 | function Component() {
|
||||
> 4 | const data = useKnownIncompatible();
|
||||
| ^^^^^^^^^^^^^^^^^^^^ useKnownIncompatible is known to be incompatible
|
||||
5 | return <div>Error</div>;
|
||||
6 | }
|
||||
7 |
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import {useKnownIncompatible} from 'ReactCompilerKnownIncompatibleTest';
|
||||
|
||||
function Component() {
|
||||
const data = useKnownIncompatible();
|
||||
return <div>Error</div>;
|
||||
}
|
||||
@@ -20,7 +20,7 @@ function Component(props) {
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Memoization: Compilation skipped because existing memoization could not be preserved
|
||||
Compilation Skipped: Existing memoization could not be preserved
|
||||
|
||||
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `props.items.edges.nodes`, but the source dependencies were [props.items?.edges?.nodes]. Inferred different dependency than source.
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ function Component(props) {
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Memoization: React Compiler has skipped optimizing this component because the effect dependencies could not be memoized. Unmemoized effect dependencies can trigger an infinite loop or other unexpected behavior
|
||||
Compilation Skipped: React Compiler has skipped optimizing this component because the effect dependencies could not be memoized. Unmemoized effect dependencies can trigger an infinite loop or other unexpected behavior
|
||||
|
||||
error.invalid-useEffect-dep-not-memoized-bc-range-overlaps-hook.ts:9:2
|
||||
7 |
|
||||
|
||||
@@ -22,7 +22,7 @@ function Component(props) {
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Memoization: React Compiler has skipped optimizing this component because the effect dependencies could not be memoized. Unmemoized effect dependencies can trigger an infinite loop or other unexpected behavior
|
||||
Compilation Skipped: React Compiler has skipped optimizing this component because the effect dependencies could not be memoized. Unmemoized effect dependencies can trigger an infinite loop or other unexpected behavior
|
||||
|
||||
error.invalid-useEffect-dep-not-memoized.ts:6:2
|
||||
4 | function Component(props) {
|
||||
|
||||
@@ -22,7 +22,7 @@ function Component(props) {
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Memoization: React Compiler has skipped optimizing this component because the effect dependencies could not be memoized. Unmemoized effect dependencies can trigger an infinite loop or other unexpected behavior
|
||||
Compilation Skipped: React Compiler has skipped optimizing this component because the effect dependencies could not be memoized. Unmemoized effect dependencies can trigger an infinite loop or other unexpected behavior
|
||||
|
||||
error.invalid-useInsertionEffect-dep-not-memoized.ts:6:2
|
||||
4 | function Component(props) {
|
||||
|
||||
@@ -22,7 +22,7 @@ function Component(props) {
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Memoization: React Compiler has skipped optimizing this component because the effect dependencies could not be memoized. Unmemoized effect dependencies can trigger an infinite loop or other unexpected behavior
|
||||
Compilation Skipped: React Compiler has skipped optimizing this component because the effect dependencies could not be memoized. Unmemoized effect dependencies can trigger an infinite loop or other unexpected behavior
|
||||
|
||||
error.invalid-useLayoutEffect-dep-not-memoized.ts:6:2
|
||||
4 | function Component(props) {
|
||||
|
||||
@@ -33,7 +33,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Memoization: Compilation skipped because existing memoization could not be preserved
|
||||
Compilation Skipped: Existing memoization could not be preserved
|
||||
|
||||
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `Ref.current`, but the source dependencies were []. Inferred dependency not present in source.
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Memoization: Compilation skipped because existing memoization could not be preserved
|
||||
Compilation Skipped: Existing memoization could not be preserved
|
||||
|
||||
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `notaref.current`, but the source dependencies were []. Inferred dependency not present in source.
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ component Component() {
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Memoization: Compilation skipped because existing memoization could not be preserved
|
||||
Compilation Skipped: Existing memoization could not be preserved
|
||||
|
||||
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This value was memoized in source but not in compilation output.
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ component Component(id) {
|
||||
```
|
||||
Found 3 errors:
|
||||
|
||||
Memoization: Compilation skipped because existing memoization could not be preserved
|
||||
Compilation Skipped: Existing memoization could not be preserved
|
||||
|
||||
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This value was memoized in source but not in compilation output.
|
||||
|
||||
@@ -76,7 +76,7 @@ React Compiler has skipped optimizing this component because the existing manual
|
||||
18 | const setCurrentIndex = useCallback(
|
||||
19 | (index: number) => {
|
||||
|
||||
Memoization: Compilation skipped because existing memoization could not be preserved
|
||||
Compilation Skipped: Existing memoization could not be preserved
|
||||
|
||||
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This dependency may be mutated later, which could cause the value to change unexpectedly.
|
||||
|
||||
@@ -88,7 +88,7 @@ React Compiler has skipped optimizing this component because the existing manual
|
||||
30 |
|
||||
31 | if (prevId !== id) {
|
||||
|
||||
Memoization: Compilation skipped because existing memoization could not be preserved
|
||||
Compilation Skipped: Existing memoization could not be preserved
|
||||
|
||||
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This value was memoized in source but not in compilation output.
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Memoization: Compilation skipped because existing memoization could not be preserved
|
||||
Compilation Skipped: Existing memoization could not be preserved
|
||||
|
||||
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This value was memoized in source but not in compilation output.
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Memoization: React Compiler has skipped optimizing this component because the effect dependencies could not be memoized. Unmemoized effect dependencies can trigger an infinite loop or other unexpected behavior
|
||||
Compilation Skipped: React Compiler has skipped optimizing this component because the effect dependencies could not be memoized. Unmemoized effect dependencies can trigger an infinite loop or other unexpected behavior
|
||||
|
||||
error.validate-memoized-effect-deps-invalidated-dep-value.ts:11:2
|
||||
9 | const y = [x];
|
||||
|
||||
@@ -27,7 +27,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
```
|
||||
Found 2 errors:
|
||||
|
||||
Memoization: Compilation skipped because existing memoization could not be preserved
|
||||
Compilation Skipped: Existing memoization could not be preserved
|
||||
|
||||
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This dependency may be mutated later, which could cause the value to change unexpectedly.
|
||||
|
||||
@@ -40,7 +40,7 @@ error.validate-object-entries-mutation.ts:6:57
|
||||
8 | value.updated = true;
|
||||
9 | });
|
||||
|
||||
Memoization: Compilation skipped because existing memoization could not be preserved
|
||||
Compilation Skipped: Existing memoization could not be preserved
|
||||
|
||||
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This value was memoized in source but not in compilation output.
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
```
|
||||
Found 2 errors:
|
||||
|
||||
Memoization: Compilation skipped because existing memoization could not be preserved
|
||||
Compilation Skipped: Existing memoization could not be preserved
|
||||
|
||||
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This dependency may be mutated later, which could cause the value to change unexpectedly.
|
||||
|
||||
@@ -40,7 +40,7 @@ error.validate-object-values-mutation.ts:6:55
|
||||
8 | value.updated = true;
|
||||
9 | });
|
||||
|
||||
Memoization: Compilation skipped because existing memoization could not be preserved
|
||||
Compilation Skipped: Existing memoization could not be preserved
|
||||
|
||||
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This value was memoized in source but not in compilation output.
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Logs
|
||||
|
||||
```
|
||||
{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":206},"end":{"line":16,"column":1,"index":433},"filename":"dynamic-gating-bailout-nopanic.ts"},"detail":{"options":{"category":"PreserveManualMemo","severity":"CannotPreserveMemoization","reason":"Compilation skipped because existing memoization could not be preserved","description":"React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `value`, but the source dependencies were []. Inferred dependency not present in source.","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":9,"column":31,"index":288},"end":{"line":9,"column":52,"index":309},"filename":"dynamic-gating-bailout-nopanic.ts"},"message":"Could not preserve existing manual memoization"}]}}}
|
||||
{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":206},"end":{"line":16,"column":1,"index":433},"filename":"dynamic-gating-bailout-nopanic.ts"},"detail":{"options":{"category":"PreserveManualMemo","severity":"CannotPreserveMemoization","reason":"Existing memoization could not be preserved","description":"React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `value`, but the source dependencies were []. Inferred dependency not present in source.","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":9,"column":31,"index":288},"end":{"line":9,"column":52,"index":309},"filename":"dynamic-gating-bailout-nopanic.ts"},"message":"Could not preserve existing manual memoization"}]}}}
|
||||
```
|
||||
|
||||
### Eval output
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @compilationMode:"infer" @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees
|
||||
import {useMemo} from 'react';
|
||||
import {makeObject_Primitives, ValidateMemoization} from 'shared-runtime';
|
||||
|
||||
function Component(props) {
|
||||
const result = useMemo(() => {
|
||||
return makeObject(props.value).value + 1;
|
||||
}, [props.value]);
|
||||
return <ValidateMemoization inputs={[props.value]} output={result} />;
|
||||
}
|
||||
|
||||
function makeObject(value) {
|
||||
console.log(value);
|
||||
return {value};
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{value: 42}],
|
||||
sequentialRenders: [
|
||||
{value: 42},
|
||||
{value: 42},
|
||||
{value: 3.14},
|
||||
{value: 3.14},
|
||||
{value: 42},
|
||||
{value: 3.14},
|
||||
{value: 42},
|
||||
{value: 3.14},
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees
|
||||
import { useMemo } from "react";
|
||||
import { makeObject_Primitives, ValidateMemoization } from "shared-runtime";
|
||||
|
||||
function Component(props) {
|
||||
const $ = _c(7);
|
||||
let t0;
|
||||
if ($[0] !== props.value) {
|
||||
t0 = makeObject(props.value);
|
||||
$[0] = props.value;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
}
|
||||
const result = t0.value + 1;
|
||||
let t1;
|
||||
if ($[2] !== props.value) {
|
||||
t1 = [props.value];
|
||||
$[2] = props.value;
|
||||
$[3] = t1;
|
||||
} else {
|
||||
t1 = $[3];
|
||||
}
|
||||
let t2;
|
||||
if ($[4] !== result || $[5] !== t1) {
|
||||
t2 = <ValidateMemoization inputs={t1} output={result} />;
|
||||
$[4] = result;
|
||||
$[5] = t1;
|
||||
$[6] = t2;
|
||||
} else {
|
||||
t2 = $[6];
|
||||
}
|
||||
return t2;
|
||||
}
|
||||
|
||||
function makeObject(value) {
|
||||
console.log(value);
|
||||
return { value };
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ value: 42 }],
|
||||
sequentialRenders: [
|
||||
{ value: 42 },
|
||||
{ value: 42 },
|
||||
{ value: 3.14 },
|
||||
{ value: 3.14 },
|
||||
{ value: 42 },
|
||||
{ value: 3.14 },
|
||||
{ value: 42 },
|
||||
{ value: 3.14 },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>{"inputs":[42],"output":43}</div>
|
||||
<div>{"inputs":[42],"output":43}</div>
|
||||
<div>{"inputs":[3.14],"output":4.140000000000001}</div>
|
||||
<div>{"inputs":[3.14],"output":4.140000000000001}</div>
|
||||
<div>{"inputs":[42],"output":43}</div>
|
||||
<div>{"inputs":[3.14],"output":4.140000000000001}</div>
|
||||
<div>{"inputs":[42],"output":43}</div>
|
||||
<div>{"inputs":[3.14],"output":4.140000000000001}</div>
|
||||
logs: [42,3.14,42,3.14,42,3.14]
|
||||
@@ -0,0 +1,30 @@
|
||||
// @compilationMode:"infer" @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees
|
||||
import {useMemo} from 'react';
|
||||
import {makeObject_Primitives, ValidateMemoization} from 'shared-runtime';
|
||||
|
||||
function Component(props) {
|
||||
const result = useMemo(() => {
|
||||
return makeObject(props.value).value + 1;
|
||||
}, [props.value]);
|
||||
return <ValidateMemoization inputs={[props.value]} output={result} />;
|
||||
}
|
||||
|
||||
function makeObject(value) {
|
||||
console.log(value);
|
||||
return {value};
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{value: 42}],
|
||||
sequentialRenders: [
|
||||
{value: 42},
|
||||
{value: 42},
|
||||
{value: 3.14},
|
||||
{value: 3.14},
|
||||
{value: 42},
|
||||
{value: 3.14},
|
||||
{value: 42},
|
||||
{value: 3.14},
|
||||
],
|
||||
};
|
||||
@@ -2,6 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @compilationMode:"infer"
|
||||
import {makeArray} from 'shared-runtime';
|
||||
|
||||
function Component() {
|
||||
@@ -30,7 +31,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer"
|
||||
import { makeArray } from "shared-runtime";
|
||||
|
||||
function Component() {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// @compilationMode:"infer"
|
||||
import {makeArray} from 'shared-runtime';
|
||||
|
||||
function Component() {
|
||||
|
||||
@@ -49,12 +49,12 @@ import {
|
||||
} from "shared-runtime";
|
||||
|
||||
function useFoo(t0) {
|
||||
const $ = _c(3);
|
||||
const $ = _c(4);
|
||||
const { data } = t0;
|
||||
let obj;
|
||||
let myDiv = null;
|
||||
bb0: if (data.cond) {
|
||||
if ($[0] !== data.cond1) {
|
||||
if ($[0] !== data.cond || $[1] !== data.cond1) {
|
||||
bb0: if (data.cond) {
|
||||
obj = makeObject_Primitives();
|
||||
if (data.cond1) {
|
||||
myDiv = <Stringify value={mutateAndReturn(obj)} />;
|
||||
@@ -62,13 +62,14 @@ function useFoo(t0) {
|
||||
}
|
||||
|
||||
mutate(obj);
|
||||
$[0] = data.cond1;
|
||||
$[1] = obj;
|
||||
$[2] = myDiv;
|
||||
} else {
|
||||
obj = $[1];
|
||||
myDiv = $[2];
|
||||
}
|
||||
$[0] = data.cond;
|
||||
$[1] = data.cond1;
|
||||
$[2] = obj;
|
||||
$[3] = myDiv;
|
||||
} else {
|
||||
obj = $[2];
|
||||
myDiv = $[3];
|
||||
}
|
||||
return myDiv;
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
```
|
||||
Found 2 errors:
|
||||
|
||||
Memoization: Compilation skipped because existing memoization could not be preserved
|
||||
Compilation Skipped: Existing memoization could not be preserved
|
||||
|
||||
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This dependency may be mutated later, which could cause the value to change unexpectedly.
|
||||
|
||||
@@ -44,7 +44,7 @@ error.invalid-useCallback-captures-reassigned-context.ts:11:37
|
||||
13 | x = makeArray();
|
||||
14 |
|
||||
|
||||
Memoization: Compilation skipped because existing memoization could not be preserved
|
||||
Compilation Skipped: Existing memoization could not be preserved
|
||||
|
||||
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This value was memoized in source but not in compilation output.
|
||||
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enablePreserveExistingMemoizationGuarantees
|
||||
import {fbt} from 'fbt';
|
||||
|
||||
function Component() {
|
||||
const buttonLabel = () => {
|
||||
if (!someCondition) {
|
||||
return <fbt desc="My label">{'Purchase as a gift'}</fbt>;
|
||||
} else if (
|
||||
!iconOnly &&
|
||||
showPrice &&
|
||||
item?.current_gift_offer?.price?.formatted != null
|
||||
) {
|
||||
return (
|
||||
<fbt desc="Gift button's label">
|
||||
{'Gift | '}
|
||||
<fbt:param name="price">
|
||||
{item?.current_gift_offer?.price?.formatted}
|
||||
</fbt:param>
|
||||
</fbt>
|
||||
);
|
||||
} else if (!iconOnly && !showPrice) {
|
||||
return <fbt desc="Gift button's label">{'Gift'}</fbt>;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<View>
|
||||
<Button text={buttonLabel()} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees
|
||||
import { fbt } from "fbt";
|
||||
|
||||
function Component() {
|
||||
const $ = _c(1);
|
||||
const buttonLabel = _temp;
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = (
|
||||
<View>
|
||||
<Button text={buttonLabel()} />
|
||||
</View>
|
||||
);
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
function _temp() {
|
||||
if (!someCondition) {
|
||||
return fbt._("Purchase as a gift", null, { hk: "1gHj4g" });
|
||||
} else {
|
||||
if (
|
||||
!iconOnly &&
|
||||
showPrice &&
|
||||
item?.current_gift_offer?.price?.formatted != null
|
||||
) {
|
||||
return fbt._(
|
||||
"Gift | {price}",
|
||||
[fbt._param("price", item?.current_gift_offer?.price?.formatted)],
|
||||
{ hk: "3GTnGE" },
|
||||
);
|
||||
} else {
|
||||
if (!iconOnly && !showPrice) {
|
||||
return fbt._("Gift", null, { hk: "3fqfrk" });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: exception) Fixture not implemented
|
||||
@@ -0,0 +1,31 @@
|
||||
// @enablePreserveExistingMemoizationGuarantees
|
||||
import {fbt} from 'fbt';
|
||||
|
||||
function Component() {
|
||||
const buttonLabel = () => {
|
||||
if (!someCondition) {
|
||||
return <fbt desc="My label">{'Purchase as a gift'}</fbt>;
|
||||
} else if (
|
||||
!iconOnly &&
|
||||
showPrice &&
|
||||
item?.current_gift_offer?.price?.formatted != null
|
||||
) {
|
||||
return (
|
||||
<fbt desc="Gift button's label">
|
||||
{'Gift | '}
|
||||
<fbt:param name="price">
|
||||
{item?.current_gift_offer?.price?.formatted}
|
||||
</fbt:param>
|
||||
</fbt>
|
||||
);
|
||||
} else if (!iconOnly && !showPrice) {
|
||||
return <fbt desc="Gift button's label">{'Gift'}</fbt>;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<View>
|
||||
<Button text={buttonLabel()} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -32,7 +32,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Memoization: Compilation skipped because existing memoization could not be preserved
|
||||
Compilation Skipped: Existing memoization could not be preserved
|
||||
|
||||
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This value was memoized in source but not in compilation output.
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Memoization: Compilation skipped because existing memoization could not be preserved
|
||||
Compilation Skipped: Existing memoization could not be preserved
|
||||
|
||||
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This dependency may be mutated later, which could cause the value to change unexpectedly.
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Memoization: Compilation skipped because existing memoization could not be preserved
|
||||
Compilation Skipped: Existing memoization could not be preserved
|
||||
|
||||
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This dependency may be mutated later, which could cause the value to change unexpectedly.
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Memoization: Compilation skipped because existing memoization could not be preserved
|
||||
Compilation Skipped: Existing memoization could not be preserved
|
||||
|
||||
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `propB`, but the source dependencies were [propA, propB.x.y]. Inferred less specific property than source.
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
```
|
||||
Found 2 errors:
|
||||
|
||||
Memoization: Compilation skipped because existing memoization could not be preserved
|
||||
Compilation Skipped: Existing memoization could not be preserved
|
||||
|
||||
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `propA`, but the source dependencies were [propA.a, propB.x.y]. Inferred less specific property than source.
|
||||
|
||||
@@ -60,7 +60,7 @@ error.hoist-useCallback-infer-conditional-value-block.ts:6:21
|
||||
16 |
|
||||
17 | export const FIXTURE_ENTRYPOINT = {
|
||||
|
||||
Memoization: Compilation skipped because existing memoization could not be preserved
|
||||
Compilation Skipped: Existing memoization could not be preserved
|
||||
|
||||
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `propB`, but the source dependencies were [propA.a, propB.x.y]. Inferred less specific property than source.
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
```
|
||||
Found 2 errors:
|
||||
|
||||
Memoization: Compilation skipped because existing memoization could not be preserved
|
||||
Compilation Skipped: Existing memoization could not be preserved
|
||||
|
||||
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This dependency may be mutated later, which could cause the value to change unexpectedly.
|
||||
|
||||
@@ -45,7 +45,7 @@ error.invalid-useCallback-captures-reassigned-context.ts:12:37
|
||||
14 | x = makeArray();
|
||||
15 |
|
||||
|
||||
Memoization: Compilation skipped because existing memoization could not be preserved
|
||||
Compilation Skipped: Existing memoization could not be preserved
|
||||
|
||||
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This value was memoized in source but not in compilation output.
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Memoization: Compilation skipped because existing memoization could not be preserved
|
||||
Compilation Skipped: Existing memoization could not be preserved
|
||||
|
||||
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `ref`, but the source dependencies were []. Inferred dependency not present in source.
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Memoization: Compilation skipped because existing memoization could not be preserved
|
||||
Compilation Skipped: Existing memoization could not be preserved
|
||||
|
||||
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This value was memoized in source but not in compilation output.
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ function useHook(x) {
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Memoization: Compilation skipped because existing memoization could not be preserved
|
||||
Compilation Skipped: Existing memoization could not be preserved
|
||||
|
||||
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `aliasedX`, but the source dependencies were [x, aliasedProp]. Inferred different dependency than source.
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Memoization: Compilation skipped because existing memoization could not be preserved
|
||||
Compilation Skipped: Existing memoization could not be preserved
|
||||
|
||||
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `propB?.x.y`, but the source dependencies were [propA, propB.x.y]. Inferred different dependency than source.
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ function Component({propA, propB}) {
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Memoization: Compilation skipped because existing memoization could not be preserved
|
||||
Compilation Skipped: Existing memoization could not be preserved
|
||||
|
||||
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `propB`, but the source dependencies were [propA?.a, propB.x.y]. Inferred less specific property than source.
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ function Component({propA}) {
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Memoization: Compilation skipped because existing memoization could not be preserved
|
||||
Compilation Skipped: Existing memoization could not be preserved
|
||||
|
||||
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `propA`, but the source dependencies were [propA.x]. Inferred less specific property than source.
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ function useHook(x) {
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Memoization: Compilation skipped because existing memoization could not be preserved
|
||||
Compilation Skipped: Existing memoization could not be preserved
|
||||
|
||||
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `x`, but the source dependencies were [aliasedX, aliasedProp]. Inferred different dependency than source.
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ function Component({propA, propB}) {
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Memoization: Compilation skipped because existing memoization could not be preserved
|
||||
Compilation Skipped: Existing memoization could not be preserved
|
||||
|
||||
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `propB`, but the source dependencies were [propA?.a, propB.x.y]. Inferred less specific property than source.
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ function Component({propA, propB}) {
|
||||
```
|
||||
Found 2 errors:
|
||||
|
||||
Memoization: Compilation skipped because existing memoization could not be preserved
|
||||
Compilation Skipped: Existing memoization could not be preserved
|
||||
|
||||
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `propA`, but the source dependencies were [propA.a, propB.x.y]. Inferred less specific property than source.
|
||||
|
||||
@@ -54,7 +54,7 @@ error.useMemo-infer-less-specific-conditional-value-block.ts:6:17
|
||||
15 | }
|
||||
16 |
|
||||
|
||||
Memoization: Compilation skipped because existing memoization could not be preserved
|
||||
Compilation Skipped: Existing memoization could not be preserved
|
||||
|
||||
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `propB`, but the source dependencies were [propA.a, propB.x.y]. Inferred less specific property than source.
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ function Component({propA}) {
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Memoization: Compilation skipped because existing memoization could not be preserved
|
||||
Compilation Skipped: Existing memoization could not be preserved
|
||||
|
||||
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `propA`, but the source dependencies were [propA.x]. Inferred less specific property than source.
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ function Component({propA}) {
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Memoization: Compilation skipped because existing memoization could not be preserved
|
||||
Compilation Skipped: Existing memoization could not be preserved
|
||||
|
||||
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `propA`, but the source dependencies were [propA.x]. Inferred less specific property than source.
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ function useFoo(input1) {
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Memoization: Compilation skipped because existing memoization could not be preserved
|
||||
Compilation Skipped: Existing memoization could not be preserved
|
||||
|
||||
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `input1`, but the source dependencies were [y]. Inferred different dependency than source.
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enableForest
|
||||
// @enablePreserveExistingMemoizationGuarantees
|
||||
function Component({base, start, increment, test}) {
|
||||
let value = base;
|
||||
for (let i = start; i < test; i += increment) {
|
||||
@@ -27,25 +27,23 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @enableForest
|
||||
import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees
|
||||
function Component(t0) {
|
||||
const $ = _c(5);
|
||||
const $ = _c(2);
|
||||
const { base, start, increment, test } = t0;
|
||||
let value;
|
||||
if ($[0] !== base || $[1] !== increment || $[2] !== start || $[3] !== test) {
|
||||
value = base;
|
||||
for (let i = start; i < test; i = i + increment, i) {
|
||||
value = value + i;
|
||||
}
|
||||
$[0] = base;
|
||||
$[1] = increment;
|
||||
$[2] = start;
|
||||
$[3] = test;
|
||||
$[4] = value;
|
||||
} else {
|
||||
value = $[4];
|
||||
let value = base;
|
||||
for (let i = start; i < test; i = i + increment, i) {
|
||||
value = value + i;
|
||||
}
|
||||
return <div>{value}</div>;
|
||||
let t1;
|
||||
if ($[0] !== value) {
|
||||
t1 = <div>{value}</div>;
|
||||
$[0] = value;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @enableForest
|
||||
// @enablePreserveExistingMemoizationGuarantees
|
||||
function Component({base, start, increment, test}) {
|
||||
let value = base;
|
||||
for (let i = start; i < test; i += increment) {
|
||||
|
||||
@@ -26,7 +26,7 @@ function Component(props) {
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Memoization: Compilation skipped because existing memoization could not be preserved
|
||||
Compilation Skipped: Existing memoization could not be preserved
|
||||
|
||||
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `props.items`, but the source dependencies were [props?.items, props.cond]. Inferred different dependency than source.
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ function Component(props) {
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Memoization: Compilation skipped because existing memoization could not be preserved
|
||||
Compilation Skipped: Existing memoization could not be preserved
|
||||
|
||||
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `props.items`, but the source dependencies were [props?.items, props.cond]. Inferred different dependency than source.
|
||||
|
||||
|
||||
@@ -34,17 +34,16 @@ import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR
|
||||
import { useMemo } from "react";
|
||||
|
||||
function Component(props) {
|
||||
const $ = _c(6);
|
||||
const $ = _c(5);
|
||||
let t0;
|
||||
bb0: {
|
||||
let y;
|
||||
if (
|
||||
$[0] !== props.a ||
|
||||
$[1] !== props.b ||
|
||||
$[2] !== props.cond ||
|
||||
$[3] !== props.cond2
|
||||
) {
|
||||
y = [];
|
||||
if (
|
||||
$[0] !== props.a ||
|
||||
$[1] !== props.b ||
|
||||
$[2] !== props.cond ||
|
||||
$[3] !== props.cond2
|
||||
) {
|
||||
bb0: {
|
||||
const y = [];
|
||||
if (props.cond) {
|
||||
y.push(props.a);
|
||||
}
|
||||
@@ -54,17 +53,15 @@ function Component(props) {
|
||||
}
|
||||
|
||||
y.push(props.b);
|
||||
$[0] = props.a;
|
||||
$[1] = props.b;
|
||||
$[2] = props.cond;
|
||||
$[3] = props.cond2;
|
||||
$[4] = y;
|
||||
$[5] = t0;
|
||||
} else {
|
||||
y = $[4];
|
||||
t0 = $[5];
|
||||
t0 = y;
|
||||
}
|
||||
t0 = y;
|
||||
$[0] = props.a;
|
||||
$[1] = props.b;
|
||||
$[2] = props.cond;
|
||||
$[3] = props.cond2;
|
||||
$[4] = t0;
|
||||
} else {
|
||||
t0 = $[4];
|
||||
}
|
||||
const x = t0;
|
||||
return x;
|
||||
|
||||
@@ -55,7 +55,7 @@ import { makeArray, Stringify, useIdentity } from "shared-runtime";
|
||||
*/
|
||||
function Foo(t0) {
|
||||
"use memo";
|
||||
const $ = _c(5);
|
||||
const $ = _c(3);
|
||||
const { b } = t0;
|
||||
|
||||
const fnFactory = () => () => {
|
||||
@@ -66,26 +66,18 @@ function Foo(t0) {
|
||||
useIdentity();
|
||||
|
||||
const fn = fnFactory();
|
||||
let t1;
|
||||
if ($[0] !== b) {
|
||||
t1 = makeArray(b);
|
||||
$[0] = b;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
const arr = t1;
|
||||
const arr = makeArray(b);
|
||||
fn(arr);
|
||||
let t2;
|
||||
if ($[2] !== arr || $[3] !== myVar) {
|
||||
t2 = <Stringify cb={myVar} value={arr} shouldInvokeFns={true} />;
|
||||
$[2] = arr;
|
||||
$[3] = myVar;
|
||||
$[4] = t2;
|
||||
let t1;
|
||||
if ($[0] !== arr || $[1] !== myVar) {
|
||||
t1 = <Stringify cb={myVar} value={arr} shouldInvokeFns={true} />;
|
||||
$[0] = arr;
|
||||
$[1] = myVar;
|
||||
$[2] = t1;
|
||||
} else {
|
||||
t2 = $[4];
|
||||
t1 = $[2];
|
||||
}
|
||||
return t2;
|
||||
return t1;
|
||||
}
|
||||
function _temp2() {
|
||||
return console.log("b");
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {useMemo} from 'react';
|
||||
import {
|
||||
makeObject_Primitives,
|
||||
mutate,
|
||||
Stringify,
|
||||
ValidateMemoization,
|
||||
} from 'shared-runtime';
|
||||
|
||||
function Component({cond}) {
|
||||
const memoized = useMemo(() => {
|
||||
const value = makeObject_Primitives();
|
||||
if (cond) {
|
||||
return value;
|
||||
} else {
|
||||
mutate(value);
|
||||
return value;
|
||||
}
|
||||
}, [cond]);
|
||||
return <ValidateMemoization inputs={[cond]} output={memoized} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{cond: false}],
|
||||
sequentialRenders: [
|
||||
{cond: false},
|
||||
{cond: false},
|
||||
{cond: true},
|
||||
{cond: true},
|
||||
{cond: false},
|
||||
{cond: true},
|
||||
{cond: false},
|
||||
{cond: true},
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { useMemo } from "react";
|
||||
import {
|
||||
makeObject_Primitives,
|
||||
mutate,
|
||||
Stringify,
|
||||
ValidateMemoization,
|
||||
} from "shared-runtime";
|
||||
|
||||
function Component(t0) {
|
||||
const $ = _c(7);
|
||||
const { cond } = t0;
|
||||
let t1;
|
||||
if ($[0] !== cond) {
|
||||
const value = makeObject_Primitives();
|
||||
if (cond) {
|
||||
t1 = value;
|
||||
} else {
|
||||
mutate(value);
|
||||
t1 = value;
|
||||
}
|
||||
$[0] = cond;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
const memoized = t1;
|
||||
let t2;
|
||||
if ($[2] !== cond) {
|
||||
t2 = [cond];
|
||||
$[2] = cond;
|
||||
$[3] = t2;
|
||||
} else {
|
||||
t2 = $[3];
|
||||
}
|
||||
let t3;
|
||||
if ($[4] !== memoized || $[5] !== t2) {
|
||||
t3 = <ValidateMemoization inputs={t2} output={memoized} />;
|
||||
$[4] = memoized;
|
||||
$[5] = t2;
|
||||
$[6] = t3;
|
||||
} else {
|
||||
t3 = $[6];
|
||||
}
|
||||
return t3;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ cond: false }],
|
||||
sequentialRenders: [
|
||||
{ cond: false },
|
||||
{ cond: false },
|
||||
{ cond: true },
|
||||
{ cond: true },
|
||||
{ cond: false },
|
||||
{ cond: true },
|
||||
{ cond: false },
|
||||
{ cond: true },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>{"inputs":[false],"output":{"a":0,"b":"value1","c":true,"wat0":"joe"}}</div>
|
||||
<div>{"inputs":[false],"output":{"a":0,"b":"value1","c":true,"wat0":"joe"}}</div>
|
||||
<div>{"inputs":[true],"output":{"a":0,"b":"value1","c":true}}</div>
|
||||
<div>{"inputs":[true],"output":{"a":0,"b":"value1","c":true}}</div>
|
||||
<div>{"inputs":[false],"output":{"a":0,"b":"value1","c":true,"wat0":"joe"}}</div>
|
||||
<div>{"inputs":[true],"output":{"a":0,"b":"value1","c":true}}</div>
|
||||
<div>{"inputs":[false],"output":{"a":0,"b":"value1","c":true,"wat0":"joe"}}</div>
|
||||
<div>{"inputs":[true],"output":{"a":0,"b":"value1","c":true}}</div>
|
||||
@@ -0,0 +1,35 @@
|
||||
import {useMemo} from 'react';
|
||||
import {
|
||||
makeObject_Primitives,
|
||||
mutate,
|
||||
Stringify,
|
||||
ValidateMemoization,
|
||||
} from 'shared-runtime';
|
||||
|
||||
function Component({cond}) {
|
||||
const memoized = useMemo(() => {
|
||||
const value = makeObject_Primitives();
|
||||
if (cond) {
|
||||
return value;
|
||||
} else {
|
||||
mutate(value);
|
||||
return value;
|
||||
}
|
||||
}, [cond]);
|
||||
return <ValidateMemoization inputs={[cond]} output={memoized} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{cond: false}],
|
||||
sequentialRenders: [
|
||||
{cond: false},
|
||||
{cond: false},
|
||||
{cond: true},
|
||||
{cond: true},
|
||||
{cond: false},
|
||||
{cond: true},
|
||||
{cond: false},
|
||||
{cond: true},
|
||||
],
|
||||
};
|
||||
@@ -33,17 +33,16 @@ import { c as _c } from "react/compiler-runtime";
|
||||
import { useMemo } from "react";
|
||||
|
||||
function Component(props) {
|
||||
const $ = _c(6);
|
||||
const $ = _c(5);
|
||||
let t0;
|
||||
bb0: {
|
||||
let y;
|
||||
if (
|
||||
$[0] !== props.a ||
|
||||
$[1] !== props.b ||
|
||||
$[2] !== props.cond ||
|
||||
$[3] !== props.cond2
|
||||
) {
|
||||
y = [];
|
||||
if (
|
||||
$[0] !== props.a ||
|
||||
$[1] !== props.b ||
|
||||
$[2] !== props.cond ||
|
||||
$[3] !== props.cond2
|
||||
) {
|
||||
bb0: {
|
||||
const y = [];
|
||||
if (props.cond) {
|
||||
y.push(props.a);
|
||||
}
|
||||
@@ -53,17 +52,15 @@ function Component(props) {
|
||||
}
|
||||
|
||||
y.push(props.b);
|
||||
$[0] = props.a;
|
||||
$[1] = props.b;
|
||||
$[2] = props.cond;
|
||||
$[3] = props.cond2;
|
||||
$[4] = y;
|
||||
$[5] = t0;
|
||||
} else {
|
||||
y = $[4];
|
||||
t0 = $[5];
|
||||
t0 = y;
|
||||
}
|
||||
t0 = y;
|
||||
$[0] = props.a;
|
||||
$[1] = props.b;
|
||||
$[2] = props.cond;
|
||||
$[3] = props.cond2;
|
||||
$[4] = t0;
|
||||
} else {
|
||||
t0 = $[4];
|
||||
}
|
||||
const x = t0;
|
||||
return x;
|
||||
|
||||
@@ -48,10 +48,7 @@ export {
|
||||
printReactiveFunction,
|
||||
printReactiveFunctionWithOutlined,
|
||||
} from './ReactiveScopes';
|
||||
export {
|
||||
parseConfigPragmaForTests,
|
||||
parseConfigPragmaAsString,
|
||||
} from './Utils/TestUtils';
|
||||
export {parseConfigPragmaForTests} from './Utils/TestUtils';
|
||||
declare global {
|
||||
let __DEV__: boolean | null | undefined;
|
||||
}
|
||||
|
||||
@@ -198,6 +198,51 @@ export function makeSharedRuntimeTypeProvider({
|
||||
},
|
||||
},
|
||||
};
|
||||
} else if (moduleName === 'ReactCompilerKnownIncompatibleTest') {
|
||||
/**
|
||||
* Fake module used for testing validation of known incompatible
|
||||
* API validation
|
||||
*/
|
||||
return {
|
||||
kind: 'object',
|
||||
properties: {
|
||||
useKnownIncompatible: {
|
||||
kind: 'hook',
|
||||
positionalParams: [],
|
||||
restParam: EffectEnum.Read,
|
||||
returnType: {kind: 'type', name: 'Any'},
|
||||
knownIncompatible: `useKnownIncompatible is known to be incompatible`,
|
||||
},
|
||||
useKnownIncompatibleIndirect: {
|
||||
kind: 'hook',
|
||||
positionalParams: [],
|
||||
restParam: EffectEnum.Read,
|
||||
returnType: {
|
||||
kind: 'object',
|
||||
properties: {
|
||||
incompatible: {
|
||||
kind: 'function',
|
||||
positionalParams: [],
|
||||
restParam: EffectEnum.Read,
|
||||
calleeEffect: EffectEnum.Read,
|
||||
returnType: {kind: 'type', name: 'Any'},
|
||||
returnValueKind: ValueKindEnum.Mutable,
|
||||
knownIncompatible: `useKnownIncompatibleIndirect returns an incompatible() function that is known incompatible`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
knownIncompatible: {
|
||||
kind: 'function',
|
||||
positionalParams: [],
|
||||
restParam: EffectEnum.Read,
|
||||
calleeEffect: EffectEnum.Read,
|
||||
returnType: {kind: 'type', name: 'Any'},
|
||||
returnValueKind: ValueKindEnum.Mutable,
|
||||
knownIncompatible: `useKnownIncompatible is known to be incompatible`,
|
||||
},
|
||||
},
|
||||
};
|
||||
} else if (moduleName === 'ReactCompilerTest') {
|
||||
/**
|
||||
* Fake module used for testing validation that type providers return hook
|
||||
|
||||
@@ -74,13 +74,7 @@ function getDebugChannel(req) {
|
||||
return activeDebugChannels.get(requestId);
|
||||
}
|
||||
|
||||
async function renderApp(
|
||||
res,
|
||||
returnValue,
|
||||
formState,
|
||||
noCache,
|
||||
promiseForDebugChannel
|
||||
) {
|
||||
async function renderApp(res, returnValue, formState, noCache, debugChannel) {
|
||||
const {renderToPipeableStream} = await import(
|
||||
'react-server-dom-webpack/server'
|
||||
);
|
||||
@@ -132,7 +126,7 @@ async function renderApp(
|
||||
// For client-invoked server actions we refresh the tree and return a return value.
|
||||
const payload = {root, returnValue, formState};
|
||||
const {pipe} = renderToPipeableStream(payload, moduleMap, {
|
||||
debugChannel: await promiseForDebugChannel,
|
||||
debugChannel,
|
||||
filterStackFrame,
|
||||
});
|
||||
pipe(res);
|
||||
@@ -385,23 +379,20 @@ app.on('error', function (error) {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
// Open a websocket server for Debug information
|
||||
const WebSocket = require('ws');
|
||||
const webSocketServer = new WebSocket.Server({noServer: true});
|
||||
|
||||
httpServer.on('upgrade', (request, socket, head) => {
|
||||
const DEBUG_CHANNEL_PATH = '/debug-channel?';
|
||||
if (request.url.startsWith(DEBUG_CHANNEL_PATH)) {
|
||||
const requestId = request.url.slice(DEBUG_CHANNEL_PATH.length);
|
||||
const promiseForWs = new Promise(resolve => {
|
||||
webSocketServer.handleUpgrade(request, socket, head, ws => {
|
||||
ws.on('close', () => {
|
||||
activeDebugChannels.delete(requestId);
|
||||
});
|
||||
resolve(ws);
|
||||
});
|
||||
});
|
||||
activeDebugChannels.set(requestId, promiseForWs);
|
||||
} else {
|
||||
socket.destroy();
|
||||
}
|
||||
const webSocketServer = new WebSocket.Server({
|
||||
server: httpServer,
|
||||
path: '/debug-channel',
|
||||
});
|
||||
|
||||
webSocketServer.on('connection', (ws, req) => {
|
||||
const url = new URL(req.url, `http://${req.headers.host}`);
|
||||
const requestId = url.searchParams.get('id');
|
||||
|
||||
activeDebugChannels.set(requestId, ws);
|
||||
|
||||
ws.on('close', (code, reason) => {
|
||||
activeDebugChannels.delete(requestId);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -14,18 +14,52 @@ function findSourceMapURL(fileName) {
|
||||
);
|
||||
}
|
||||
|
||||
async function createWebSocketStream(url) {
|
||||
const ws = new WebSocket(url);
|
||||
ws.binaryType = 'arraybuffer';
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
ws.addEventListener('open', resolve, {once: true});
|
||||
ws.addEventListener('error', reject, {once: true});
|
||||
});
|
||||
|
||||
const writable = new WritableStream({
|
||||
write(chunk) {
|
||||
ws.send(chunk);
|
||||
},
|
||||
close() {
|
||||
ws.close();
|
||||
},
|
||||
abort(reason) {
|
||||
ws.close(1000, reason && String(reason));
|
||||
},
|
||||
});
|
||||
|
||||
const readable = new ReadableStream({
|
||||
start(controller) {
|
||||
ws.addEventListener('message', event => {
|
||||
controller.enqueue(event.data);
|
||||
});
|
||||
ws.addEventListener('close', () => {
|
||||
controller.close();
|
||||
});
|
||||
ws.addEventListener('error', err => {
|
||||
controller.error(err);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return {readable, writable};
|
||||
}
|
||||
|
||||
let updateRoot;
|
||||
async function callServer(id, args) {
|
||||
let response;
|
||||
if (
|
||||
process.env.NODE_ENV === 'development' &&
|
||||
typeof WebSocketStream === 'function'
|
||||
) {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
const requestId = crypto.randomUUID();
|
||||
const wss = new WebSocketStream(
|
||||
'ws://localhost:3001/debug-channel?' + requestId
|
||||
const debugChannel = await createWebSocketStream(
|
||||
`ws://localhost:3001/debug-channel?id=${requestId}`
|
||||
);
|
||||
const debugChannel = await wss.opened;
|
||||
response = createFromFetch(
|
||||
fetch('/', {
|
||||
method: 'POST',
|
||||
@@ -74,15 +108,11 @@ function Shell({data}) {
|
||||
|
||||
async function hydrateApp() {
|
||||
let response;
|
||||
if (
|
||||
process.env.NODE_ENV === 'development' &&
|
||||
typeof WebSocketStream === 'function'
|
||||
) {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
const requestId = crypto.randomUUID();
|
||||
const wss = new WebSocketStream(
|
||||
'ws://localhost:3001/debug-channel?' + requestId
|
||||
const debugChannel = await createWebSocketStream(
|
||||
`ws://localhost:3001/debug-channel?id=${requestId}`
|
||||
);
|
||||
const debugChannel = await wss.opened;
|
||||
response = createFromFetch(
|
||||
fetch('/', {
|
||||
headers: {
|
||||
|
||||
@@ -22,15 +22,22 @@ yarn add eslint-plugin-react-hooks --dev
|
||||
|
||||
#### >= 6.0.0
|
||||
|
||||
For users of 6.0 and beyond, simply add the `recommended` config.
|
||||
For users of 6.0 and beyond, add the `recommended` config.
|
||||
|
||||
```js
|
||||
import * as reactHooks from 'eslint-plugin-react-hooks';
|
||||
// eslint.config.js
|
||||
import reactHooks from 'eslint-plugin-react-hooks';
|
||||
import { defineConfig } from 'eslint/config';
|
||||
|
||||
export default [
|
||||
// ...
|
||||
reactHooks.configs.recommended,
|
||||
];
|
||||
export default defineConfig([
|
||||
{
|
||||
files: ["src/**/*.{js,jsx,ts,tsx}"],
|
||||
plugins: {
|
||||
'react-hooks': reactHooks,
|
||||
},
|
||||
extends: ['react-hooks/recommended'],
|
||||
},
|
||||
]);
|
||||
```
|
||||
|
||||
#### 5.2.0
|
||||
@@ -38,12 +45,18 @@ export default [
|
||||
For users of 5.2.0 (the first version with flat config support), add the `recommended-latest` config.
|
||||
|
||||
```js
|
||||
import * as reactHooks from 'eslint-plugin-react-hooks';
|
||||
import reactHooks from 'eslint-plugin-react-hooks';
|
||||
import { defineConfig } from 'eslint/config';
|
||||
|
||||
export default [
|
||||
// ...
|
||||
reactHooks.configs['recommended-latest'],
|
||||
];
|
||||
export default defineConfig([
|
||||
{
|
||||
files: ["src/**/*.{js,jsx,ts,tsx}"],
|
||||
plugins: {
|
||||
'react-hooks': reactHooks,
|
||||
},
|
||||
extends: ['react-hooks/recommended-latest'],
|
||||
},
|
||||
]);
|
||||
```
|
||||
|
||||
### Legacy Config (.eslintrc)
|
||||
|
||||
52
packages/react-client/src/ReactFlightClient.js
vendored
52
packages/react-client/src/ReactFlightClient.js
vendored
@@ -1010,10 +1010,15 @@ export function reportGlobalError(
|
||||
if (__DEV__) {
|
||||
const debugChannel = response._debugChannel;
|
||||
if (debugChannel !== undefined) {
|
||||
// If we don't have any more ways of reading data, we don't have to send any
|
||||
// more neither. So we close the writable side.
|
||||
// If we don't have any more ways of reading data, we don't have to send
|
||||
// any more neither. So we close the writable side.
|
||||
closeDebugChannel(debugChannel);
|
||||
response._debugChannel = undefined;
|
||||
// Make sure the debug channel is not closed a second time when the
|
||||
// Response gets GC:ed.
|
||||
if (debugChannelRegistry !== null) {
|
||||
debugChannelRegistry.unregister(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1069,7 +1074,14 @@ function getTaskName(type: mixed): string {
|
||||
}
|
||||
}
|
||||
|
||||
function initializeElement(response: Response, element: any): void {
|
||||
function initializeElement(
|
||||
response: Response,
|
||||
element: any,
|
||||
lazyType: null | LazyComponent<
|
||||
React$Element<any>,
|
||||
SomeChunk<React$Element<any>>,
|
||||
>,
|
||||
): void {
|
||||
if (!__DEV__) {
|
||||
return;
|
||||
}
|
||||
@@ -1136,6 +1148,18 @@ function initializeElement(response: Response, element: any): void {
|
||||
if (owner !== null) {
|
||||
initializeFakeStack(response, owner);
|
||||
}
|
||||
|
||||
// In case the JSX runtime has validated the lazy type as a static child, we
|
||||
// need to transfer this information to the element.
|
||||
if (
|
||||
lazyType &&
|
||||
lazyType._store &&
|
||||
lazyType._store.validated &&
|
||||
!element._store.validated
|
||||
) {
|
||||
element._store.validated = lazyType._store.validated;
|
||||
}
|
||||
|
||||
// TODO: We should be freezing the element but currently, we might write into
|
||||
// _debugInfo later. We could move it into _store which remains mutable.
|
||||
Object.freeze(element.props);
|
||||
@@ -1148,7 +1172,7 @@ function createElement(
|
||||
props: mixed,
|
||||
owner: ?ReactComponentInfo, // DEV-only
|
||||
stack: ?ReactStackTrace, // DEV-only
|
||||
validated: number, // DEV-only
|
||||
validated: 0 | 1 | 2, // DEV-only
|
||||
):
|
||||
| React$Element<any>
|
||||
| LazyComponent<React$Element<any>, SomeChunk<React$Element<any>>> {
|
||||
@@ -1225,7 +1249,7 @@ function createElement(
|
||||
handler.reason,
|
||||
);
|
||||
if (__DEV__) {
|
||||
initializeElement(response, element);
|
||||
initializeElement(response, element, null);
|
||||
// Conceptually the error happened inside this Element but right before
|
||||
// it was rendered. We don't have a client side component to render but
|
||||
// we can add some DebugInfo to explain that this was conceptually a
|
||||
@@ -1244,7 +1268,7 @@ function createElement(
|
||||
}
|
||||
erroredChunk._debugInfo = [erroredComponent];
|
||||
}
|
||||
return createLazyChunkWrapper(erroredChunk);
|
||||
return createLazyChunkWrapper(erroredChunk, validated);
|
||||
}
|
||||
if (handler.deps > 0) {
|
||||
// We have blocked references inside this Element but we can turn this into
|
||||
@@ -1253,16 +1277,17 @@ function createElement(
|
||||
createBlockedChunk(response);
|
||||
handler.value = element;
|
||||
handler.chunk = blockedChunk;
|
||||
const lazyType = createLazyChunkWrapper(blockedChunk, validated);
|
||||
if (__DEV__) {
|
||||
/// After we have initialized any blocked references, initialize stack etc.
|
||||
const init = initializeElement.bind(null, response, element);
|
||||
// After we have initialized any blocked references, initialize stack etc.
|
||||
const init = initializeElement.bind(null, response, element, lazyType);
|
||||
blockedChunk.then(init, init);
|
||||
}
|
||||
return createLazyChunkWrapper(blockedChunk);
|
||||
return lazyType;
|
||||
}
|
||||
}
|
||||
if (__DEV__) {
|
||||
initializeElement(response, element);
|
||||
initializeElement(response, element, null);
|
||||
}
|
||||
|
||||
return element;
|
||||
@@ -1270,6 +1295,7 @@ function createElement(
|
||||
|
||||
function createLazyChunkWrapper<T>(
|
||||
chunk: SomeChunk<T>,
|
||||
validated: 0 | 1 | 2, // DEV-only
|
||||
): LazyComponent<T, SomeChunk<T>> {
|
||||
const lazyType: LazyComponent<T, SomeChunk<T>> = {
|
||||
$$typeof: REACT_LAZY_TYPE,
|
||||
@@ -1281,6 +1307,8 @@ function createLazyChunkWrapper<T>(
|
||||
const chunkDebugInfo: ReactDebugInfo =
|
||||
chunk._debugInfo || (chunk._debugInfo = ([]: ReactDebugInfo));
|
||||
lazyType._debugInfo = chunkDebugInfo;
|
||||
// Initialize a store for key validation by the JSX runtime.
|
||||
lazyType._store = {validated: validated};
|
||||
}
|
||||
return lazyType;
|
||||
}
|
||||
@@ -2085,7 +2113,7 @@ function parseModelString(
|
||||
}
|
||||
// We create a React.lazy wrapper around any lazy values.
|
||||
// When passed into React, we'll know how to suspend on this.
|
||||
return createLazyChunkWrapper(chunk);
|
||||
return createLazyChunkWrapper(chunk, 0);
|
||||
}
|
||||
case '@': {
|
||||
// Promise
|
||||
@@ -2434,7 +2462,7 @@ function ResponseInstance(
|
||||
// When a Response gets GC:ed because nobody is referring to any of the
|
||||
// objects that lazily load from the Response anymore, then we can close
|
||||
// the debug channel.
|
||||
debugChannelRegistry.register(this, debugChannel);
|
||||
debugChannelRegistry.register(this, debugChannel, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,8 +80,8 @@ export default function Element({data, index, style}: Props): React.Node {
|
||||
};
|
||||
|
||||
// $FlowFixMe[missing-local-annot]
|
||||
const handleClick = ({metaKey}) => {
|
||||
if (id !== null) {
|
||||
const handleClick = ({metaKey, button}) => {
|
||||
if (id !== null && button === 0) {
|
||||
logEvent({
|
||||
event_name: 'select-element',
|
||||
metadata: {source: 'click-element'},
|
||||
|
||||
@@ -16,14 +16,15 @@
|
||||
|
||||
.TreeWrapper {
|
||||
border-top: 1px solid var(--color-border);
|
||||
flex: 1 1 var(--horizontal-resize-tree-percentage);
|
||||
flex: 1 1 65%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.InspectedElementWrapper {
|
||||
flex: 1 1 35%;
|
||||
flex: 0 0 calc(100% - var(--horizontal-resize-tree-percentage));
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
@@ -59,12 +60,12 @@
|
||||
|
||||
.TreeWrapper {
|
||||
border-top: 1px solid var(--color-border);
|
||||
flex: 1 1 var(--vertical-resize-tree-percentage);
|
||||
flex: 1 1 50%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.InspectedElementWrapper {
|
||||
flex: 1 1 50%;
|
||||
flex: 0 0 calc(100% - var(--vertical-resize-tree-percentage));
|
||||
}
|
||||
|
||||
.TreeWrapper + .ResizeBarWrapper .ResizeBar {
|
||||
|
||||
@@ -2,13 +2,18 @@
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 0 0.25rem;
|
||||
padding: 0.25rem;
|
||||
}
|
||||
|
||||
.SuspenseTimelineInput {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
/*
|
||||
* `overflow: auto` will add scrollbars but the input will not actually grow beyond visible content.
|
||||
* `overflow: hidden` will constrain the input to its visible content.
|
||||
*/
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.SuspenseTimelineRootSwitcher {
|
||||
@@ -16,20 +21,6 @@
|
||||
max-width: 3rem;
|
||||
}
|
||||
|
||||
.SuspenseTimelineMarkers {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
.SuspenseTimelineProgressIndicator {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.SuspenseTimelineMarkers > * {
|
||||
flex: 1 1 0;
|
||||
overflow: visible;
|
||||
visibility: hidden;
|
||||
width: 0
|
||||
}
|
||||
|
||||
.SuspenseTimelineActiveMarker {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,14 +11,7 @@ import type {Element, SuspenseNode} from '../../../frontend/types';
|
||||
import type Store from '../../store';
|
||||
|
||||
import * as React from 'react';
|
||||
import {
|
||||
useContext,
|
||||
useId,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import {useContext, useLayoutEffect, useMemo, useRef, useState} from 'react';
|
||||
import {BridgeContext, StoreContext} from '../context';
|
||||
import {TreeDispatcherContext} from '../Components/TreeContext';
|
||||
import {useHighlightHostInstance} from '../hooks';
|
||||
@@ -112,30 +105,6 @@ function SuspenseTimelineInput({rootID}: {rootID: Element['id'] | void}) {
|
||||
setValue(max);
|
||||
}
|
||||
|
||||
const markersID = useId();
|
||||
const markers: React.Node[] = useMemo(() => {
|
||||
return timeline.map((suspense, index) => {
|
||||
const takesUpSpace =
|
||||
suspense.rects !== null &&
|
||||
suspense.rects.some(rect => {
|
||||
return rect.width > 0 && rect.height > 0;
|
||||
});
|
||||
|
||||
return takesUpSpace ? (
|
||||
<option
|
||||
key={suspense.id}
|
||||
className={
|
||||
index === value ? styles.SuspenseTimelineActiveMarker : undefined
|
||||
}
|
||||
value={index}>
|
||||
#{index + 1}
|
||||
</option>
|
||||
) : (
|
||||
<option key={suspense.id} />
|
||||
);
|
||||
});
|
||||
}, [timeline, value]);
|
||||
|
||||
if (rootID === undefined) {
|
||||
return <div className={styles.SuspenseTimelineInput}>Root not found.</div>;
|
||||
}
|
||||
@@ -219,25 +188,26 @@ function SuspenseTimelineInput({rootID}: {rootID: Element['id'] | void}) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.SuspenseTimelineInput}>
|
||||
<input
|
||||
className={styles.SuspenseTimelineSlider}
|
||||
type="range"
|
||||
min={min}
|
||||
max={max}
|
||||
list={markersID}
|
||||
value={value}
|
||||
onBlur={handleBlur}
|
||||
onChange={handleChange}
|
||||
onFocus={handleFocus}
|
||||
onPointerMove={handlePointerMove}
|
||||
onPointerUp={clearHighlightHostInstance}
|
||||
ref={inputRef}
|
||||
/>
|
||||
<datalist id={markersID} className={styles.SuspenseTimelineMarkers}>
|
||||
{markers}
|
||||
</datalist>
|
||||
</div>
|
||||
<>
|
||||
<div>
|
||||
{value}/{max}
|
||||
</div>
|
||||
<div className={styles.SuspenseTimelineInput}>
|
||||
<input
|
||||
className={styles.SuspenseTimelineSlider}
|
||||
type="range"
|
||||
min={min}
|
||||
max={max}
|
||||
value={value}
|
||||
onBlur={handleBlur}
|
||||
onChange={handleChange}
|
||||
onFocus={handleFocus}
|
||||
onPointerMove={handlePointerMove}
|
||||
onPointerUp={clearHighlightHostInstance}
|
||||
ref={inputRef}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,5 +10,5 @@
|
||||
import * as React from 'react';
|
||||
|
||||
export default function SuspenseTreeList(_: {}): React$Node {
|
||||
return <div>Activity slices</div>;
|
||||
return <div>Activity slices not implemented yet</div>;
|
||||
}
|
||||
|
||||
@@ -126,6 +126,7 @@ function wwwOnCaughtError(
|
||||
|
||||
defaultOnCaughtError(error, errorInfo);
|
||||
}
|
||||
const noopOnDefaultTransitionIndicator = noop;
|
||||
|
||||
export function createRoot(
|
||||
container: Element | Document | DocumentFragment,
|
||||
@@ -137,6 +138,7 @@ export function createRoot(
|
||||
({
|
||||
onUncaughtError: wwwOnUncaughtError,
|
||||
onCaughtError: wwwOnCaughtError,
|
||||
onDefaultTransitionIndicator: noopOnDefaultTransitionIndicator,
|
||||
}: any),
|
||||
options,
|
||||
),
|
||||
@@ -155,6 +157,7 @@ export function hydrateRoot(
|
||||
({
|
||||
onUncaughtError: wwwOnUncaughtError,
|
||||
onCaughtError: wwwOnCaughtError,
|
||||
onDefaultTransitionIndicator: noopOnDefaultTransitionIndicator,
|
||||
}: any),
|
||||
options,
|
||||
),
|
||||
@@ -211,7 +214,6 @@ function getReactRootElementInContainer(container: any) {
|
||||
// This isn't reachable because onRecoverableError isn't called in the
|
||||
// legacy API.
|
||||
const noopOnRecoverableError = noop;
|
||||
const noopOnDefaultTransitionIndicator = noop;
|
||||
|
||||
function legacyCreateRootFromDOMContainer(
|
||||
container: Container,
|
||||
|
||||
@@ -46,7 +46,10 @@ import {
|
||||
createPublicRootInstance,
|
||||
type PublicRootInstance,
|
||||
} from 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface';
|
||||
import {disableLegacyMode} from 'shared/ReactFeatureFlags';
|
||||
import {
|
||||
disableLegacyMode,
|
||||
enableDefaultTransitionIndicator,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
|
||||
if (typeof ReactFiberErrorDialog.showErrorDialog !== 'function') {
|
||||
throw new Error(
|
||||
@@ -132,6 +135,12 @@ function render(
|
||||
if (options && options.onRecoverableError !== undefined) {
|
||||
onRecoverableError = options.onRecoverableError;
|
||||
}
|
||||
let onDefaultTransitionIndicator = nativeOnDefaultTransitionIndicator;
|
||||
if (enableDefaultTransitionIndicator) {
|
||||
if (options && options.onDefaultTransitionIndicator !== undefined) {
|
||||
onDefaultTransitionIndicator = options.onDefaultTransitionIndicator;
|
||||
}
|
||||
}
|
||||
|
||||
const publicRootInstance = createPublicRootInstance(containerTag);
|
||||
const rootInstance = {
|
||||
@@ -151,7 +160,7 @@ function render(
|
||||
onUncaughtError,
|
||||
onCaughtError,
|
||||
onRecoverableError,
|
||||
nativeOnDefaultTransitionIndicator,
|
||||
onDefaultTransitionIndicator,
|
||||
null,
|
||||
);
|
||||
|
||||
|
||||
@@ -134,6 +134,7 @@ export type RenderRootOptions = {
|
||||
error: mixed,
|
||||
errorInfo: {+componentStack?: ?string},
|
||||
) => void,
|
||||
onDefaultTransitionIndicator?: () => void | (() => void),
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user