Compare commits
45 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5cf71b322d | ||
|
|
f807ce6492 | ||
|
|
7b38acca0b | ||
|
|
1d9c3927ea | ||
|
|
969a9790ad | ||
|
|
665de2ed28 | ||
|
|
eda778b8ae | ||
|
|
1836b46fff | ||
|
|
eec50b17b3 | ||
|
|
a9410fb487 | ||
|
|
6b70072c4f | ||
|
|
b2cff47472 | ||
|
|
8943025358 | ||
|
|
3d9d22cbdb | ||
|
|
d4374b3ae3 | ||
|
|
3f2a42a5de | ||
|
|
294c33f34d | ||
|
|
3fb190f729 | ||
|
|
f5e96b9740 | ||
|
|
78992521a8 | ||
|
|
80d7aa17ad | ||
|
|
474f25842a | ||
|
|
1fef581e1a | ||
|
|
60d9b9740d | ||
|
|
c4e2508dad | ||
|
|
de5a1b203e | ||
|
|
b9a045368b | ||
|
|
e2cc315a1b | ||
|
|
5a31758ed6 | ||
|
|
ba6590dd7c | ||
|
|
2710795a1e | ||
|
|
735e9ac54e | ||
|
|
5d64f74211 | ||
|
|
3302d1f791 | ||
|
|
7697a9f62e | ||
|
|
3168e08f83 | ||
|
|
2805f0ed9e | ||
|
|
ac3e705a18 | ||
|
|
8e60cb7ed5 | ||
|
|
6a58b80020 | ||
|
|
b1b0955f2b | ||
|
|
1549bda33f | ||
|
|
bb6f0c8d2f | ||
|
|
aad7c664ff | ||
|
|
3fe51c9e14 |
@@ -577,6 +577,7 @@ module.exports = {
|
||||
$AsyncIterator: 'readonly',
|
||||
Iterator: 'readonly',
|
||||
AsyncIterator: 'readonly',
|
||||
IntervalID: 'readonly',
|
||||
IteratorResult: 'readonly',
|
||||
JSONValue: 'readonly',
|
||||
JSResourceReference: 'readonly',
|
||||
|
||||
@@ -6,61 +6,111 @@
|
||||
*/
|
||||
|
||||
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 React, {useState, useCallback} from 'react';
|
||||
import {Resizable} from 're-resizable';
|
||||
import {useStore} from '../StoreContext';
|
||||
import {useSnackbar} from 'notistack';
|
||||
import {useStore, useStoreDispatch} from '../StoreContext';
|
||||
import {monacoOptions} from './monacoOptions';
|
||||
import {
|
||||
ConfigError,
|
||||
generateOverridePragmaFromConfig,
|
||||
updateSourceWithOverridePragma,
|
||||
} from '../../lib/configUtils';
|
||||
|
||||
// @ts-expect-error - webpack asset/source loader handles .d.ts files as strings
|
||||
import compilerTypeDefs from 'babel-plugin-react-compiler/dist/index.d.ts';
|
||||
|
||||
loader.config({monaco});
|
||||
|
||||
export default function ConfigEditor(): JSX.Element {
|
||||
const [, setMonaco] = useState<Monaco | null>(null);
|
||||
export default function ConfigEditor(): React.ReactElement {
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
const store = useStore();
|
||||
const dispatchStore = useStoreDispatch();
|
||||
const {enqueueSnackbar} = useSnackbar();
|
||||
|
||||
// Parse string-based override config from pragma comment and format it
|
||||
const [configJavaScript, setConfigJavaScript] = useState('');
|
||||
const toggleExpanded = useCallback(() => {
|
||||
setIsExpanded(prev => !prev);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const pragma = store.source.substring(0, store.source.indexOf('\n'));
|
||||
const configString = `(${parseConfigPragmaAsString(pragma)})`;
|
||||
const handleApplyConfig: () => Promise<void> = async () => {
|
||||
try {
|
||||
const config = store.config || '';
|
||||
|
||||
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
|
||||
if (!config.trim()) {
|
||||
enqueueSnackbar(
|
||||
'Config is empty. Please add configuration options first.',
|
||||
{
|
||||
variant: 'warning',
|
||||
},
|
||||
);
|
||||
return;
|
||||
}
|
||||
const newPragma = await generateOverridePragmaFromConfig(config);
|
||||
const updatedSource = updateSourceWithOverridePragma(
|
||||
store.source,
|
||||
newPragma,
|
||||
);
|
||||
|
||||
dispatchStore({
|
||||
type: 'updateFile',
|
||||
payload: {
|
||||
source: updatedSource,
|
||||
config: config,
|
||||
},
|
||||
});
|
||||
console.log('Config:', configString);
|
||||
}, [store.source]);
|
||||
} catch (error) {
|
||||
console.error('Failed to apply config:', error);
|
||||
|
||||
if (error instanceof ConfigError && error.message.trim()) {
|
||||
enqueueSnackbar(error.message, {
|
||||
variant: 'error',
|
||||
});
|
||||
} else {
|
||||
enqueueSnackbar('Unexpected error: failed to apply config.', {
|
||||
variant: 'error',
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleChange: (value: string | undefined) => void = value => {
|
||||
if (!value) return;
|
||||
if (value === undefined) return;
|
||||
|
||||
// TODO: Implement sync logic to update pragma comments in the source
|
||||
console.log('Config changed:', value);
|
||||
// Only update the config
|
||||
dispatchStore({
|
||||
type: 'updateFile',
|
||||
payload: {
|
||||
source: store.source,
|
||||
config: value,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleMount: (
|
||||
_: editor.IStandaloneCodeEditor,
|
||||
monaco: Monaco,
|
||||
) => void = (_, monaco) => {
|
||||
setMonaco(monaco);
|
||||
// Add the babel-plugin-react-compiler type definitions to Monaco
|
||||
monaco.languages.typescript.typescriptDefaults.addExtraLib(
|
||||
//@ts-expect-error - compilerTypeDefs is a string
|
||||
compilerTypeDefs,
|
||||
'file:///node_modules/babel-plugin-react-compiler/dist/index.d.ts',
|
||||
);
|
||||
monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
|
||||
target: monaco.languages.typescript.ScriptTarget.Latest,
|
||||
allowNonTsExtensions: true,
|
||||
moduleResolution: monaco.languages.typescript.ModuleResolutionKind.NodeJs,
|
||||
module: monaco.languages.typescript.ModuleKind.ESNext,
|
||||
noEmit: true,
|
||||
strict: false,
|
||||
esModuleInterop: true,
|
||||
allowSyntheticDefaultImports: true,
|
||||
jsx: monaco.languages.typescript.JsxEmit.React,
|
||||
});
|
||||
|
||||
const uri = monaco.Uri.parse(`file:///config.js`);
|
||||
const uri = monaco.Uri.parse(`file:///config.ts`);
|
||||
const model = monaco.editor.getModel(uri);
|
||||
if (model) {
|
||||
model.updateOptions({tabSize: 2});
|
||||
@@ -68,36 +118,66 @@ export default function ConfigEditor(): JSX.Element {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative flex flex-col flex-none border-r border-gray-200">
|
||||
<h2 className="p-4 duration-150 ease-in border-b cursor-default border-grey-200 font-light text-secondary">
|
||||
Config Overrides
|
||||
</h2>
|
||||
<Resizable
|
||||
minWidth={300}
|
||||
maxWidth={600}
|
||||
defaultSize={{width: 350, height: 'auto'}}
|
||||
enable={{right: true}}
|
||||
className="!h-[calc(100vh_-_3.5rem_-_4rem)]">
|
||||
<MonacoEditor
|
||||
path={'config.js'}
|
||||
language={'javascript'}
|
||||
value={configJavaScript}
|
||||
onMount={handleMount}
|
||||
onChange={handleChange}
|
||||
options={{
|
||||
...monacoOptions,
|
||||
readOnly: true,
|
||||
lineNumbers: 'off',
|
||||
folding: false,
|
||||
renderLineHighlight: 'none',
|
||||
scrollBeyondLastLine: false,
|
||||
hideCursorInOverviewRuler: true,
|
||||
overviewRulerBorder: false,
|
||||
overviewRulerLanes: 0,
|
||||
fontSize: 12,
|
||||
}}
|
||||
/>
|
||||
</Resizable>
|
||||
<div className="flex flex-row relative">
|
||||
{isExpanded ? (
|
||||
<>
|
||||
<Resizable
|
||||
className="border-r"
|
||||
minWidth={300}
|
||||
maxWidth={600}
|
||||
defaultSize={{width: 350, height: 'auto'}}
|
||||
enable={{right: true}}>
|
||||
<h2
|
||||
title="Minimize config editor"
|
||||
aria-label="Minimize config editor"
|
||||
onClick={toggleExpanded}
|
||||
className="p-4 duration-150 ease-in border-b cursor-pointer border-grey-200 font-light text-secondary hover:text-link">
|
||||
- Config Overrides
|
||||
</h2>
|
||||
<div className="h-[calc(100vh_-_3.5rem_-_4rem)]">
|
||||
<MonacoEditor
|
||||
path={'config.ts'}
|
||||
language={'typescript'}
|
||||
value={store.config}
|
||||
onMount={handleMount}
|
||||
onChange={handleChange}
|
||||
options={{
|
||||
...monacoOptions,
|
||||
lineNumbers: 'off',
|
||||
folding: false,
|
||||
renderLineHighlight: 'none',
|
||||
scrollBeyondLastLine: false,
|
||||
hideCursorInOverviewRuler: true,
|
||||
overviewRulerBorder: false,
|
||||
overviewRulerLanes: 0,
|
||||
fontSize: 12,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Resizable>
|
||||
<button
|
||||
onClick={handleApplyConfig}
|
||||
title="Apply config overrides to input"
|
||||
aria-label="Apply config overrides to input"
|
||||
className="absolute right-0 top-1/2 transform -translate-y-1/2 translate-x-1/2 z-10 w-8 h-8 bg-blue-500 hover:bg-blue-600 text-white rounded-full border-2 border-white shadow-lg flex items-center justify-center text-sm font-medium transition-colors duration-150">
|
||||
→
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<div className="relative items-center h-full px-1 py-6 align-middle border-r border-grey-200">
|
||||
<button
|
||||
title="Expand config editor"
|
||||
aria-label="Expand config editor"
|
||||
style={{
|
||||
transform: 'rotate(90deg) translate(-50%)',
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
onClick={toggleExpanded}
|
||||
className="flex-grow-0 w-5 transition-colors duration-150 ease-in font-light text-secondary hover:text-link">
|
||||
Config Overrides
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import BabelPluginReactCompiler, {
|
||||
CompilerErrorDetail,
|
||||
CompilerDiagnostic,
|
||||
Effect,
|
||||
ErrorSeverity,
|
||||
ErrorCategory,
|
||||
parseConfigPragmaForTests,
|
||||
ValueKind,
|
||||
type Hook,
|
||||
@@ -258,7 +258,7 @@ function compile(
|
||||
console.error(err);
|
||||
error.details.push(
|
||||
new CompilerErrorDetail({
|
||||
severity: ErrorSeverity.Invariant,
|
||||
category: ErrorCategory.Invariant,
|
||||
reason: `Unexpected failure when transforming input! ${err}`,
|
||||
loc: null,
|
||||
suggestions: null,
|
||||
@@ -315,9 +315,12 @@ export default function Editor(): JSX.Element {
|
||||
});
|
||||
mountStore = defaultStore;
|
||||
}
|
||||
|
||||
dispatchStore({
|
||||
type: 'setStore',
|
||||
payload: {store: mountStore},
|
||||
payload: {
|
||||
store: mountStore,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -64,12 +64,16 @@ type Props = {
|
||||
async function tabify(
|
||||
source: string,
|
||||
compilerOutput: CompilerOutput,
|
||||
showInternals: boolean,
|
||||
): Promise<Map<string, ReactNode>> {
|
||||
const tabs = new Map<string, React.ReactNode>();
|
||||
const reorderedTabs = new Map<string, React.ReactNode>();
|
||||
const concattedResults = new Map<string, string>();
|
||||
// Concat all top level function declaration results into a single tab for each pass
|
||||
for (const [passName, results] of compilerOutput.results) {
|
||||
if (!showInternals && passName !== 'Output' && passName !== 'SourceMap') {
|
||||
continue;
|
||||
}
|
||||
for (const result of results) {
|
||||
switch (result.kind) {
|
||||
case 'hir': {
|
||||
@@ -225,10 +229,10 @@ function Output({store, compilerOutput}: Props): JSX.Element {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
tabify(store.source, compilerOutput).then(tabs => {
|
||||
tabify(store.source, compilerOutput, store.showInternals).then(tabs => {
|
||||
setTabs(tabs);
|
||||
});
|
||||
}, [store.source, compilerOutput]);
|
||||
}, [store.source, compilerOutput, store.showInternals]);
|
||||
|
||||
const changedPasses: Set<string> = new Set(['Output', 'HIR']); // Initial and final passes should always be bold
|
||||
let lastResult: string = '';
|
||||
@@ -248,7 +252,7 @@ function Output({store, compilerOutput}: Props): JSX.Element {
|
||||
return (
|
||||
<>
|
||||
<TabbedWindow
|
||||
defaultTab="HIR"
|
||||
defaultTab={store.showInternals ? 'HIR' : 'Output'}
|
||||
setTabsOpen={setTabsOpen}
|
||||
tabsOpen={tabsOpen}
|
||||
tabs={tabs}
|
||||
|
||||
@@ -14,10 +14,11 @@ import {useState} from 'react';
|
||||
import {defaultStore} from '../lib/defaultStore';
|
||||
import {IconGitHub} from './Icons/IconGitHub';
|
||||
import Logo from './Logo';
|
||||
import {useStoreDispatch} from './StoreContext';
|
||||
import {useStore, useStoreDispatch} from './StoreContext';
|
||||
|
||||
export default function Header(): JSX.Element {
|
||||
const [showCheck, setShowCheck] = useState(false);
|
||||
const store = useStore();
|
||||
const dispatchStore = useStoreDispatch();
|
||||
const {enqueueSnackbar, closeSnackbar} = useSnackbar();
|
||||
|
||||
@@ -56,6 +57,27 @@ export default function Header(): JSX.Element {
|
||||
<p className="hidden select-none sm:block">React Compiler Playground</p>
|
||||
</div>
|
||||
<div className="flex items-center text-[15px] gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<label className="relative inline-block w-[34px] h-5">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={store.showInternals}
|
||||
onChange={() => dispatchStore({type: 'toggleInternals'})}
|
||||
className="absolute opacity-0 cursor-pointer h-full w-full m-0"
|
||||
/>
|
||||
<span
|
||||
className={clsx(
|
||||
'absolute inset-0 rounded-full cursor-pointer transition-all duration-250',
|
||||
"before:content-[''] before:absolute before:w-4 before:h-4 before:left-0.5 before:bottom-0.5",
|
||||
'before:bg-white before:rounded-full before:transition-transform before:duration-250',
|
||||
'focus-within:shadow-[0_0_1px_#2196F3]',
|
||||
store.showInternals
|
||||
? 'bg-blue-500 before:translate-x-3.5'
|
||||
: 'bg-gray-300',
|
||||
)}></span>
|
||||
</label>
|
||||
<span className="text-secondary">Show Internals</span>
|
||||
</div>
|
||||
<button
|
||||
title="Reset Playground"
|
||||
aria-label="Reset Playground"
|
||||
|
||||
@@ -56,7 +56,11 @@ type ReducerAction =
|
||||
type: 'updateFile';
|
||||
payload: {
|
||||
source: string;
|
||||
config: string;
|
||||
};
|
||||
}
|
||||
| {
|
||||
type: 'toggleInternals';
|
||||
};
|
||||
|
||||
function storeReducer(store: Store, action: ReducerAction): Store {
|
||||
@@ -66,10 +70,18 @@ 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;
|
||||
}
|
||||
case 'toggleInternals': {
|
||||
const newStore = {
|
||||
...store,
|
||||
showInternals: !store.showInternals,
|
||||
};
|
||||
return newStore;
|
||||
}
|
||||
|
||||
120
compiler/apps/playground/lib/configUtils.ts
Normal file
120
compiler/apps/playground/lib/configUtils.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
/**
|
||||
* 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 {parsePluginOptions} from 'babel-plugin-react-compiler';
|
||||
import {parseConfigPragmaAsString} from '../../../packages/babel-plugin-react-compiler/src/Utils/TestUtils';
|
||||
|
||||
export class ConfigError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = 'ConfigError';
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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 = `\
|
||||
import type { PluginOptions } from 'babel-plugin-react-compiler/dist';
|
||||
|
||||
(${configString} satisfies Partial<PluginOptions>)`;
|
||||
}
|
||||
|
||||
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('({') + 1;
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that a config string can be parsed as a valid PluginOptions object
|
||||
* Throws an error if validation fails.
|
||||
*/
|
||||
function validateConfigAsPluginOptions(configString: string): void {
|
||||
// Validate that config can be parse as JS obj
|
||||
let parsedConfig: unknown;
|
||||
try {
|
||||
parsedConfig = new Function(`return (${configString})`)();
|
||||
} catch (_) {
|
||||
throw new ConfigError('Config has invalid syntax.');
|
||||
}
|
||||
|
||||
// Validate against PluginOptions schema
|
||||
try {
|
||||
parsePluginOptions(parsedConfig);
|
||||
} catch (_) {
|
||||
throw new ConfigError('Config does not match the expected schema.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
validateConfigAsPluginOptions(cleanConfig);
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
@@ -13,10 +13,36 @@ export default function MyApp() {
|
||||
}
|
||||
`;
|
||||
|
||||
export const defaultConfig = `\
|
||||
import type { PluginOptions } from 'babel-plugin-react-compiler/dist';
|
||||
|
||||
({
|
||||
compilationMode: 'infer',
|
||||
panicThreshold: 'none',
|
||||
environment: {},
|
||||
logger: null,
|
||||
gating: null,
|
||||
noEmit: false,
|
||||
dynamicGating: null,
|
||||
eslintSuppressionRules: null,
|
||||
flowSuppressions: true,
|
||||
ignoreUseNoForget: false,
|
||||
sources: filename => {
|
||||
return filename.indexOf('node_modules') === -1;
|
||||
},
|
||||
enableReanimatedCheck: true,
|
||||
customOptOutDirectives: null,
|
||||
target: '19',
|
||||
} satisfies Partial<PluginOptions>);`;
|
||||
|
||||
export const defaultStore: Store = {
|
||||
source: index,
|
||||
config: defaultConfig,
|
||||
showInternals: false,
|
||||
};
|
||||
|
||||
export const emptyStore: Store = {
|
||||
source: '',
|
||||
config: '',
|
||||
showInternals: false,
|
||||
};
|
||||
|
||||
@@ -10,18 +10,20 @@ import {
|
||||
compressToEncodedURIComponent,
|
||||
decompressFromEncodedURIComponent,
|
||||
} from 'lz-string';
|
||||
import {defaultStore} from '../defaultStore';
|
||||
import {defaultStore, defaultConfig} from '../defaultStore';
|
||||
|
||||
/**
|
||||
* Global Store for Playground
|
||||
*/
|
||||
export interface Store {
|
||||
source: string;
|
||||
config: string;
|
||||
showInternals: boolean;
|
||||
}
|
||||
export function encodeStore(store: Store): string {
|
||||
return compressToEncodedURIComponent(JSON.stringify(store));
|
||||
}
|
||||
export function decodeStore(hash: string): Store {
|
||||
export function decodeStore(hash: string): any {
|
||||
return JSON.parse(decompressFromEncodedURIComponent(hash));
|
||||
}
|
||||
|
||||
@@ -62,8 +64,14 @@ export function initStoreFromUrlOrLocalStorage(): Store {
|
||||
*/
|
||||
if (!encodedSource) return defaultStore;
|
||||
|
||||
const raw = decodeStore(encodedSource);
|
||||
const raw: any = decodeStore(encodedSource);
|
||||
|
||||
invariant(isValidStore(raw), 'Invalid Store');
|
||||
return raw;
|
||||
|
||||
// Make sure all properties are populated
|
||||
return {
|
||||
source: raw.source,
|
||||
config: 'config' in raw ? raw.config : defaultConfig,
|
||||
showInternals: 'showInternals' in raw ? raw.showInternals : false,
|
||||
};
|
||||
}
|
||||
|
||||
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
@@ -14,52 +14,30 @@ import invariant from 'invariant';
|
||||
|
||||
export enum ErrorSeverity {
|
||||
/**
|
||||
* Invalid JS syntax, or valid syntax that is semantically invalid which may indicate some
|
||||
* misunderstanding on the user’s part.
|
||||
* An actionable error that the developer can fix. For example, product code errors should be
|
||||
* reported as such.
|
||||
*/
|
||||
InvalidJS = 'InvalidJS',
|
||||
Error = 'Error',
|
||||
/**
|
||||
* JS syntax that is not supported and which we do not plan to support. Developers should
|
||||
* rewrite to use supported forms.
|
||||
* An error that the developer may not necessarily be able to fix. For example, syntax not
|
||||
* supported by the compiler does not indicate any fault in the product code.
|
||||
*/
|
||||
UnsupportedJS = 'UnsupportedJS',
|
||||
Warning = 'Warning',
|
||||
/**
|
||||
* Code that breaks the rules of React.
|
||||
* Not an error. These will not be surfaced in ESLint, but may be surfaced in other ways
|
||||
* (eg Forgive) where informational hints can be shown.
|
||||
*/
|
||||
InvalidReact = 'InvalidReact',
|
||||
Hint = 'Hint',
|
||||
/**
|
||||
* Incorrect configuration of the compiler.
|
||||
* These errors will not be reported anywhere. Useful for work in progress validations.
|
||||
*/
|
||||
InvalidConfig = 'InvalidConfig',
|
||||
/**
|
||||
* Code that can reasonably occur and that doesn't break any rules, but is unsafe to preserve
|
||||
* 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.
|
||||
*/
|
||||
Todo = 'Todo',
|
||||
/**
|
||||
* An unexpected internal error in the compiler that indicates critical issues that can panic
|
||||
* the compiler.
|
||||
*/
|
||||
Invariant = 'Invariant',
|
||||
Off = 'Off',
|
||||
}
|
||||
|
||||
export type CompilerDiagnosticOptions = {
|
||||
category: ErrorCategory;
|
||||
severity: ErrorSeverity;
|
||||
reason: string;
|
||||
description: string;
|
||||
description: string | null;
|
||||
details: Array<CompilerDiagnosticDetail>;
|
||||
suggestions?: Array<CompilerSuggestion> | null | undefined;
|
||||
};
|
||||
@@ -71,7 +49,7 @@ export type CompilerDiagnosticDetail =
|
||||
| {
|
||||
kind: 'error';
|
||||
loc: SourceLocation | null;
|
||||
message: string;
|
||||
message: string | null;
|
||||
}
|
||||
| {
|
||||
kind: 'hint';
|
||||
@@ -100,9 +78,11 @@ export type CompilerSuggestion =
|
||||
description: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* @deprecated use {@link CompilerDiagnosticOptions} instead
|
||||
*/
|
||||
export type CompilerErrorDetailOptions = {
|
||||
category: ErrorCategory;
|
||||
severity: ErrorSeverity;
|
||||
reason: string;
|
||||
description?: string | null | undefined;
|
||||
loc: SourceLocation | null;
|
||||
@@ -136,8 +116,8 @@ export class CompilerDiagnostic {
|
||||
get description(): CompilerDiagnosticOptions['description'] {
|
||||
return this.options.description;
|
||||
}
|
||||
get severity(): CompilerDiagnosticOptions['severity'] {
|
||||
return this.options.severity;
|
||||
get severity(): ErrorSeverity {
|
||||
return getRuleForCategory(this.category).severity;
|
||||
}
|
||||
get suggestions(): CompilerDiagnosticOptions['suggestions'] {
|
||||
return this.options.suggestions;
|
||||
@@ -146,8 +126,8 @@ export class CompilerDiagnostic {
|
||||
return this.options.category;
|
||||
}
|
||||
|
||||
withDetail(detail: CompilerDiagnosticDetail): CompilerDiagnostic {
|
||||
this.options.details.push(detail);
|
||||
withDetails(...details: Array<CompilerDiagnosticDetail>): CompilerDiagnostic {
|
||||
this.options.details.push(...details);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -161,11 +141,10 @@ export class CompilerDiagnostic {
|
||||
}
|
||||
|
||||
printErrorMessage(source: string, options: PrintErrorMessageOptions): string {
|
||||
const buffer = [
|
||||
printErrorSummary(this.severity, this.reason),
|
||||
'\n\n',
|
||||
this.description,
|
||||
];
|
||||
const buffer = [printErrorSummary(this.category, this.reason)];
|
||||
if (this.description != null) {
|
||||
buffer.push('\n\n', `${this.description}.`);
|
||||
}
|
||||
for (const detail of this.options.details) {
|
||||
switch (detail.kind) {
|
||||
case 'error': {
|
||||
@@ -175,9 +154,9 @@ export class CompilerDiagnostic {
|
||||
}
|
||||
let codeFrame: string;
|
||||
try {
|
||||
codeFrame = printCodeFrame(source, loc, detail.message);
|
||||
codeFrame = printCodeFrame(source, loc, detail.message ?? '');
|
||||
} catch (e) {
|
||||
codeFrame = detail.message;
|
||||
codeFrame = detail.message ?? '';
|
||||
}
|
||||
buffer.push('\n\n');
|
||||
if (loc.filename != null) {
|
||||
@@ -207,7 +186,7 @@ export class CompilerDiagnostic {
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
const buffer = [printErrorSummary(this.severity, this.reason)];
|
||||
const buffer = [printErrorSummary(this.category, this.reason)];
|
||||
if (this.description != null) {
|
||||
buffer.push(`. ${this.description}.`);
|
||||
}
|
||||
@@ -219,9 +198,11 @@ export class CompilerDiagnostic {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
/**
|
||||
* Each bailout or invariant in HIR lowering creates an {@link CompilerErrorDetail}, which is then
|
||||
* aggregated into a single {@link CompilerError} later.
|
||||
*
|
||||
* @deprecated use {@link CompilerDiagnostic} instead
|
||||
*/
|
||||
export class CompilerErrorDetail {
|
||||
options: CompilerErrorDetailOptions;
|
||||
@@ -236,8 +217,8 @@ export class CompilerErrorDetail {
|
||||
get description(): CompilerErrorDetailOptions['description'] {
|
||||
return this.options.description;
|
||||
}
|
||||
get severity(): CompilerErrorDetailOptions['severity'] {
|
||||
return this.options.severity;
|
||||
get severity(): ErrorSeverity {
|
||||
return getRuleForCategory(this.category).severity;
|
||||
}
|
||||
get loc(): CompilerErrorDetailOptions['loc'] {
|
||||
return this.options.loc;
|
||||
@@ -254,7 +235,7 @@ export class CompilerErrorDetail {
|
||||
}
|
||||
|
||||
printErrorMessage(source: string, options: PrintErrorMessageOptions): string {
|
||||
const buffer = [printErrorSummary(this.severity, this.reason)];
|
||||
const buffer = [printErrorSummary(this.category, this.reason)];
|
||||
if (this.description != null) {
|
||||
buffer.push(`\n\n${this.description}.`);
|
||||
}
|
||||
@@ -279,7 +260,7 @@ export class CompilerErrorDetail {
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
const buffer = [printErrorSummary(this.severity, this.reason)];
|
||||
const buffer = [printErrorSummary(this.category, this.reason)];
|
||||
if (this.description != null) {
|
||||
buffer.push(`. ${this.description}.`);
|
||||
}
|
||||
@@ -291,22 +272,28 @@ export class CompilerErrorDetail {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An aggregate of {@link CompilerDiagnostic}. This allows us to aggregate all issues found by the
|
||||
* compiler into a single error before we throw. Where possible, prefer to push diagnostics into
|
||||
* the error aggregate instead of throwing immediately.
|
||||
*/
|
||||
export class CompilerError extends Error {
|
||||
details: Array<CompilerErrorDetail | CompilerDiagnostic> = [];
|
||||
disabledDetails: Array<CompilerErrorDetail | CompilerDiagnostic> = [];
|
||||
printedMessage: string | null = null;
|
||||
|
||||
static invariant(
|
||||
condition: unknown,
|
||||
options: Omit<CompilerErrorDetailOptions, 'severity' | 'category'>,
|
||||
options: Omit<CompilerDiagnosticOptions, 'category'>,
|
||||
): asserts condition {
|
||||
if (!condition) {
|
||||
const errors = new CompilerError();
|
||||
errors.pushErrorDetail(
|
||||
new CompilerErrorDetail({
|
||||
...options,
|
||||
errors.pushDiagnostic(
|
||||
CompilerDiagnostic.create({
|
||||
reason: options.reason,
|
||||
description: options.description,
|
||||
category: ErrorCategory.Invariant,
|
||||
severity: ErrorSeverity.Invariant,
|
||||
}),
|
||||
}).withDetails(...options.details),
|
||||
);
|
||||
throw errors;
|
||||
}
|
||||
@@ -319,13 +306,12 @@ export class CompilerError extends Error {
|
||||
}
|
||||
|
||||
static throwTodo(
|
||||
options: Omit<CompilerErrorDetailOptions, 'severity' | 'category'>,
|
||||
options: Omit<CompilerErrorDetailOptions, 'category'>,
|
||||
): never {
|
||||
const errors = new CompilerError();
|
||||
errors.pushErrorDetail(
|
||||
new CompilerErrorDetail({
|
||||
...options,
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.Todo,
|
||||
}),
|
||||
);
|
||||
@@ -333,40 +319,31 @@ export class CompilerError extends Error {
|
||||
}
|
||||
|
||||
static throwInvalidJS(
|
||||
options: Omit<CompilerErrorDetailOptions, 'severity' | 'category'>,
|
||||
options: Omit<CompilerErrorDetailOptions, 'category'>,
|
||||
): never {
|
||||
const errors = new CompilerError();
|
||||
errors.pushErrorDetail(
|
||||
new CompilerErrorDetail({
|
||||
...options,
|
||||
severity: ErrorSeverity.InvalidJS,
|
||||
category: ErrorCategory.Syntax,
|
||||
}),
|
||||
);
|
||||
throw errors;
|
||||
}
|
||||
|
||||
static throwInvalidReact(
|
||||
options: Omit<CompilerErrorDetailOptions, 'severity'>,
|
||||
): never {
|
||||
static throwInvalidReact(options: CompilerErrorDetailOptions): never {
|
||||
const errors = new CompilerError();
|
||||
errors.pushErrorDetail(
|
||||
new CompilerErrorDetail({
|
||||
...options,
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
}),
|
||||
);
|
||||
errors.pushErrorDetail(new CompilerErrorDetail(options));
|
||||
throw errors;
|
||||
}
|
||||
|
||||
static throwInvalidConfig(
|
||||
options: Omit<CompilerErrorDetailOptions, 'severity' | 'category'>,
|
||||
options: Omit<CompilerErrorDetailOptions, 'category'>,
|
||||
): never {
|
||||
const errors = new CompilerError();
|
||||
errors.pushErrorDetail(
|
||||
new CompilerErrorDetail({
|
||||
...options,
|
||||
severity: ErrorSeverity.InvalidConfig,
|
||||
category: ErrorCategory.Config,
|
||||
}),
|
||||
);
|
||||
@@ -383,6 +360,7 @@ export class CompilerError extends Error {
|
||||
super(...args);
|
||||
this.name = 'ReactCompilerError';
|
||||
this.details = [];
|
||||
this.disabledDetails = [];
|
||||
}
|
||||
|
||||
override get message(): string {
|
||||
@@ -423,62 +401,93 @@ export class CompilerError extends Error {
|
||||
|
||||
merge(other: CompilerError): void {
|
||||
this.details.push(...other.details);
|
||||
this.disabledDetails.push(...other.disabledDetails);
|
||||
}
|
||||
|
||||
pushDiagnostic(diagnostic: CompilerDiagnostic): void {
|
||||
this.details.push(diagnostic);
|
||||
if (diagnostic.severity === ErrorSeverity.Off) {
|
||||
this.disabledDetails.push(diagnostic);
|
||||
} else {
|
||||
this.details.push(diagnostic);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use {@link pushDiagnostic} instead
|
||||
*/
|
||||
push(options: CompilerErrorDetailOptions): CompilerErrorDetail {
|
||||
const detail = new CompilerErrorDetail({
|
||||
category: options.category,
|
||||
reason: options.reason,
|
||||
description: options.description ?? null,
|
||||
severity: options.severity,
|
||||
suggestions: options.suggestions,
|
||||
loc: typeof options.loc === 'symbol' ? null : options.loc,
|
||||
});
|
||||
return this.pushErrorDetail(detail);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use {@link pushDiagnostic} instead
|
||||
*/
|
||||
pushErrorDetail(detail: CompilerErrorDetail): CompilerErrorDetail {
|
||||
this.details.push(detail);
|
||||
if (detail.severity === ErrorSeverity.Off) {
|
||||
this.disabledDetails.push(detail);
|
||||
} else {
|
||||
this.details.push(detail);
|
||||
}
|
||||
return detail;
|
||||
}
|
||||
|
||||
hasErrors(): boolean {
|
||||
hasAnyErrors(): boolean {
|
||||
return this.details.length > 0;
|
||||
}
|
||||
|
||||
asResult(): Result<void, CompilerError> {
|
||||
return this.hasErrors() ? Err(this) : Ok(undefined);
|
||||
return this.hasAnyErrors() ? Err(this) : Ok(undefined);
|
||||
}
|
||||
|
||||
/*
|
||||
* An error is critical if it means the compiler has entered into a broken state and cannot
|
||||
* continue safely. Other expected errors such as Todos mean that we can skip over that component
|
||||
* but otherwise continue compiling the rest of the app.
|
||||
/**
|
||||
* Returns true if any of the error details are of severity Error.
|
||||
*/
|
||||
isCritical(): boolean {
|
||||
return this.details.some(detail => {
|
||||
switch (detail.severity) {
|
||||
case ErrorSeverity.Invariant:
|
||||
case ErrorSeverity.InvalidJS:
|
||||
case ErrorSeverity.InvalidReact:
|
||||
case ErrorSeverity.InvalidConfig:
|
||||
case ErrorSeverity.UnsupportedJS:
|
||||
case ErrorSeverity.IncompatibleLibrary: {
|
||||
return true;
|
||||
}
|
||||
case ErrorSeverity.CannotPreserveMemoization:
|
||||
case ErrorSeverity.Todo: {
|
||||
return false;
|
||||
}
|
||||
default: {
|
||||
assertExhaustive(detail.severity, 'Unhandled error severity');
|
||||
}
|
||||
hasErrors(): boolean {
|
||||
for (const detail of this.details) {
|
||||
if (detail.severity === ErrorSeverity.Error) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if there are no Errors and there is at least one Warning.
|
||||
*/
|
||||
hasWarning(): boolean {
|
||||
let res = false;
|
||||
for (const detail of this.details) {
|
||||
if (detail.severity === ErrorSeverity.Error) {
|
||||
return false;
|
||||
}
|
||||
if (detail.severity === ErrorSeverity.Warning) {
|
||||
res = true;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
hasHints(): boolean {
|
||||
let res = false;
|
||||
for (const detail of this.details) {
|
||||
if (detail.severity === ErrorSeverity.Error) {
|
||||
return false;
|
||||
}
|
||||
if (detail.severity === ErrorSeverity.Warning) {
|
||||
return false;
|
||||
}
|
||||
if (detail.severity === ErrorSeverity.Hint) {
|
||||
res = true;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -505,115 +514,158 @@ function printCodeFrame(
|
||||
);
|
||||
}
|
||||
|
||||
function printErrorSummary(severity: ErrorSeverity, message: string): string {
|
||||
let severityCategory: string;
|
||||
switch (severity) {
|
||||
case ErrorSeverity.InvalidConfig:
|
||||
case ErrorSeverity.InvalidJS:
|
||||
case ErrorSeverity.InvalidReact:
|
||||
case ErrorSeverity.UnsupportedJS: {
|
||||
severityCategory = 'Error';
|
||||
function printErrorSummary(category: ErrorCategory, message: string): string {
|
||||
let heading: string;
|
||||
switch (category) {
|
||||
case ErrorCategory.AutomaticEffectDependencies:
|
||||
case ErrorCategory.CapitalizedCalls:
|
||||
case ErrorCategory.Config:
|
||||
case ErrorCategory.EffectDerivationsOfState:
|
||||
case ErrorCategory.EffectSetState:
|
||||
case ErrorCategory.ErrorBoundaries:
|
||||
case ErrorCategory.Factories:
|
||||
case ErrorCategory.FBT:
|
||||
case ErrorCategory.Fire:
|
||||
case ErrorCategory.Gating:
|
||||
case ErrorCategory.Globals:
|
||||
case ErrorCategory.Hooks:
|
||||
case ErrorCategory.Immutability:
|
||||
case ErrorCategory.Purity:
|
||||
case ErrorCategory.Refs:
|
||||
case ErrorCategory.RenderSetState:
|
||||
case ErrorCategory.StaticComponents:
|
||||
case ErrorCategory.Suppression:
|
||||
case ErrorCategory.Syntax:
|
||||
case ErrorCategory.UseMemo: {
|
||||
heading = 'Error';
|
||||
break;
|
||||
}
|
||||
case ErrorSeverity.IncompatibleLibrary:
|
||||
case ErrorSeverity.CannotPreserveMemoization: {
|
||||
severityCategory = 'Compilation Skipped';
|
||||
case ErrorCategory.EffectDependencies:
|
||||
case ErrorCategory.IncompatibleLibrary:
|
||||
case ErrorCategory.PreserveManualMemo:
|
||||
case ErrorCategory.UnsupportedSyntax: {
|
||||
heading = 'Compilation Skipped';
|
||||
break;
|
||||
}
|
||||
case ErrorSeverity.Invariant: {
|
||||
severityCategory = 'Invariant';
|
||||
case ErrorCategory.Invariant: {
|
||||
heading = 'Invariant';
|
||||
break;
|
||||
}
|
||||
case ErrorSeverity.Todo: {
|
||||
severityCategory = 'Todo';
|
||||
case ErrorCategory.Todo: {
|
||||
heading = 'Todo';
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
assertExhaustive(severity, `Unexpected severity '${severity}'`);
|
||||
assertExhaustive(category, `Unhandled category '${category}'`);
|
||||
}
|
||||
}
|
||||
return `${severityCategory}: ${message}`;
|
||||
return `${heading}: ${message}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* See getRuleForCategory() for how these map to ESLint rules
|
||||
*/
|
||||
export enum ErrorCategory {
|
||||
// Checking for valid hooks usage (non conditional, non-first class, non reactive, etc)
|
||||
/**
|
||||
* Checking for valid hooks usage (non conditional, non-first class, non reactive, etc)
|
||||
*/
|
||||
Hooks = 'Hooks',
|
||||
|
||||
// Checking for no capitalized calls (not definitively an error, hence separating)
|
||||
/**
|
||||
* Checking for no capitalized calls (not definitively an error, hence separating)
|
||||
*/
|
||||
CapitalizedCalls = 'CapitalizedCalls',
|
||||
|
||||
// Checking for static components
|
||||
/**
|
||||
* Checking for static components
|
||||
*/
|
||||
StaticComponents = 'StaticComponents',
|
||||
|
||||
// Checking for valid usage of manual memoization
|
||||
/**
|
||||
* Checking for valid usage of manual memoization
|
||||
*/
|
||||
UseMemo = 'UseMemo',
|
||||
|
||||
// Checking for higher order functions acting as factories for components/hooks
|
||||
/**
|
||||
* Checking for higher order functions acting as factories for components/hooks
|
||||
*/
|
||||
Factories = 'Factories',
|
||||
|
||||
// Checks that manual memoization is preserved
|
||||
/**
|
||||
* Checks that manual memoization is preserved
|
||||
*/
|
||||
PreserveManualMemo = 'PreserveManualMemo',
|
||||
|
||||
// Checks for known incompatible libraries
|
||||
/**
|
||||
* Checks for known incompatible libraries
|
||||
*/
|
||||
IncompatibleLibrary = 'IncompatibleLibrary',
|
||||
|
||||
// Checking for no mutations of props, hook arguments, hook return values
|
||||
/**
|
||||
* Checking for no mutations of props, hook arguments, hook return values
|
||||
*/
|
||||
Immutability = 'Immutability',
|
||||
|
||||
// Checking for assignments to globals
|
||||
/**
|
||||
* Checking for assignments to globals
|
||||
*/
|
||||
Globals = 'Globals',
|
||||
|
||||
// Checking for valid usage of refs, ie no access during render
|
||||
/**
|
||||
* Checking for valid usage of refs, ie no access during render
|
||||
*/
|
||||
Refs = 'Refs',
|
||||
|
||||
// Checks for memoized effect deps
|
||||
/**
|
||||
* Checks for memoized effect deps
|
||||
*/
|
||||
EffectDependencies = 'EffectDependencies',
|
||||
|
||||
// Checks for no setState in effect bodies
|
||||
/**
|
||||
* Checks for no setState in effect bodies
|
||||
*/
|
||||
EffectSetState = 'EffectSetState',
|
||||
|
||||
EffectDerivationsOfState = 'EffectDerivationsOfState',
|
||||
|
||||
// Validates against try/catch in place of error boundaries
|
||||
/**
|
||||
* Validates against try/catch in place of error boundaries
|
||||
*/
|
||||
ErrorBoundaries = 'ErrorBoundaries',
|
||||
|
||||
// Checking for pure functions
|
||||
/**
|
||||
* Checking for pure functions
|
||||
*/
|
||||
Purity = 'Purity',
|
||||
|
||||
// Validates against setState in render
|
||||
/**
|
||||
* Validates against setState in render
|
||||
*/
|
||||
RenderSetState = 'RenderSetState',
|
||||
|
||||
// Internal invariants
|
||||
/**
|
||||
* Internal invariants
|
||||
*/
|
||||
Invariant = 'Invariant',
|
||||
|
||||
// Todos
|
||||
/**
|
||||
* Todos
|
||||
*/
|
||||
Todo = 'Todo',
|
||||
|
||||
// Syntax errors
|
||||
/**
|
||||
* Syntax errors
|
||||
*/
|
||||
Syntax = 'Syntax',
|
||||
|
||||
// Checks for use of unsupported syntax
|
||||
/**
|
||||
* Checks for use of unsupported syntax
|
||||
*/
|
||||
UnsupportedSyntax = 'UnsupportedSyntax',
|
||||
|
||||
// Config errors
|
||||
/**
|
||||
* Config errors
|
||||
*/
|
||||
Config = 'Config',
|
||||
|
||||
// Gating error
|
||||
/**
|
||||
* Gating error
|
||||
*/
|
||||
Gating = 'Gating',
|
||||
|
||||
// Suppressions
|
||||
/**
|
||||
* Suppressions
|
||||
*/
|
||||
Suppression = 'Suppression',
|
||||
|
||||
// Issues with auto deps
|
||||
/**
|
||||
* Issues with auto deps
|
||||
*/
|
||||
AutomaticEffectDependencies = 'AutomaticEffectDependencies',
|
||||
|
||||
// Issues with `fire`
|
||||
/**
|
||||
* Issues with `fire`
|
||||
*/
|
||||
Fire = 'Fire',
|
||||
|
||||
// fbt-specific issues
|
||||
/**
|
||||
* fbt-specific issues
|
||||
*/
|
||||
FBT = 'FBT',
|
||||
}
|
||||
|
||||
@@ -621,6 +673,9 @@ export type LintRule = {
|
||||
// Stores the category the rule corresponds to, used to filter errors when reporting
|
||||
category: ErrorCategory;
|
||||
|
||||
// Stores the severity of the error, which is used to map to lint levels such as error/warning.
|
||||
severity: ErrorSeverity;
|
||||
|
||||
/**
|
||||
* The "name" of the rule as it will be used by developers to enable/disable, eg
|
||||
* "eslint-disable-nest line <name>"
|
||||
@@ -661,6 +716,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
case ErrorCategory.AutomaticEffectDependencies: {
|
||||
return {
|
||||
category,
|
||||
severity: ErrorSeverity.Error,
|
||||
name: 'automatic-effect-dependencies',
|
||||
description:
|
||||
'Verifies that automatic effect dependencies are compiled if opted-in',
|
||||
@@ -670,6 +726,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
case ErrorCategory.CapitalizedCalls: {
|
||||
return {
|
||||
category,
|
||||
severity: ErrorSeverity.Error,
|
||||
name: 'capitalized-calls',
|
||||
description:
|
||||
'Validates against calling capitalized functions/methods instead of using JSX',
|
||||
@@ -679,6 +736,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
case ErrorCategory.Config: {
|
||||
return {
|
||||
category,
|
||||
severity: ErrorSeverity.Error,
|
||||
name: 'config',
|
||||
description: 'Validates the compiler configuration options',
|
||||
recommended: true,
|
||||
@@ -687,6 +745,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
case ErrorCategory.EffectDependencies: {
|
||||
return {
|
||||
category,
|
||||
severity: ErrorSeverity.Error,
|
||||
name: 'memoized-effect-dependencies',
|
||||
description: 'Validates that effect dependencies are memoized',
|
||||
recommended: false,
|
||||
@@ -695,6 +754,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
case ErrorCategory.EffectDerivationsOfState: {
|
||||
return {
|
||||
category,
|
||||
severity: ErrorSeverity.Error,
|
||||
name: 'no-deriving-state-in-effects',
|
||||
description:
|
||||
'Validates against deriving values from state in an effect',
|
||||
@@ -704,6 +764,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
case ErrorCategory.EffectSetState: {
|
||||
return {
|
||||
category,
|
||||
severity: ErrorSeverity.Error,
|
||||
name: 'set-state-in-effect',
|
||||
description:
|
||||
'Validates against calling setState synchronously in an effect, which can lead to re-renders that degrade performance',
|
||||
@@ -713,6 +774,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
case ErrorCategory.ErrorBoundaries: {
|
||||
return {
|
||||
category,
|
||||
severity: ErrorSeverity.Error,
|
||||
name: 'error-boundaries',
|
||||
description:
|
||||
'Validates usage of error boundaries instead of try/catch for errors in child components',
|
||||
@@ -722,6 +784,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
case ErrorCategory.Factories: {
|
||||
return {
|
||||
category,
|
||||
severity: ErrorSeverity.Error,
|
||||
name: 'component-hook-factories',
|
||||
description:
|
||||
'Validates against higher order functions defining nested components or hooks. ' +
|
||||
@@ -732,6 +795,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
case ErrorCategory.FBT: {
|
||||
return {
|
||||
category,
|
||||
severity: ErrorSeverity.Error,
|
||||
name: 'fbt',
|
||||
description: 'Validates usage of fbt',
|
||||
recommended: false,
|
||||
@@ -740,6 +804,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
case ErrorCategory.Fire: {
|
||||
return {
|
||||
category,
|
||||
severity: ErrorSeverity.Error,
|
||||
name: 'fire',
|
||||
description: 'Validates usage of `fire`',
|
||||
recommended: false,
|
||||
@@ -748,6 +813,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
case ErrorCategory.Gating: {
|
||||
return {
|
||||
category,
|
||||
severity: ErrorSeverity.Error,
|
||||
name: 'gating',
|
||||
description:
|
||||
'Validates configuration of [gating mode](https://react.dev/reference/react-compiler/gating)',
|
||||
@@ -757,6 +823,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
case ErrorCategory.Globals: {
|
||||
return {
|
||||
category,
|
||||
severity: ErrorSeverity.Error,
|
||||
name: 'globals',
|
||||
description:
|
||||
'Validates against assignment/mutation of globals during render, part of ensuring that ' +
|
||||
@@ -767,6 +834,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
case ErrorCategory.Hooks: {
|
||||
return {
|
||||
category,
|
||||
severity: ErrorSeverity.Error,
|
||||
name: 'hooks',
|
||||
description: 'Validates the rules of hooks',
|
||||
/**
|
||||
@@ -780,6 +848,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
case ErrorCategory.Immutability: {
|
||||
return {
|
||||
category,
|
||||
severity: ErrorSeverity.Error,
|
||||
name: 'immutability',
|
||||
description:
|
||||
'Validates against mutating props, state, and other values that [are immutable](https://react.dev/reference/rules/components-and-hooks-must-be-pure#props-and-state-are-immutable)',
|
||||
@@ -789,6 +858,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
case ErrorCategory.Invariant: {
|
||||
return {
|
||||
category,
|
||||
severity: ErrorSeverity.Error,
|
||||
name: 'invariant',
|
||||
description: 'Internal invariants',
|
||||
recommended: false,
|
||||
@@ -797,6 +867,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
case ErrorCategory.PreserveManualMemo: {
|
||||
return {
|
||||
category,
|
||||
severity: ErrorSeverity.Error,
|
||||
name: 'preserve-manual-memoization',
|
||||
description:
|
||||
'Validates that existing manual memoized is preserved by the compiler. ' +
|
||||
@@ -808,6 +879,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
case ErrorCategory.Purity: {
|
||||
return {
|
||||
category,
|
||||
severity: ErrorSeverity.Error,
|
||||
name: 'purity',
|
||||
description:
|
||||
'Validates that [components/hooks are pure](https://react.dev/reference/rules/components-and-hooks-must-be-pure) by checking that they do not call known-impure functions',
|
||||
@@ -817,6 +889,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
case ErrorCategory.Refs: {
|
||||
return {
|
||||
category,
|
||||
severity: ErrorSeverity.Error,
|
||||
name: 'refs',
|
||||
description:
|
||||
'Validates correct usage of refs, not reading/writing during render. See the "pitfalls" section in [`useRef()` usage](https://react.dev/reference/react/useRef#usage)',
|
||||
@@ -826,6 +899,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
case ErrorCategory.RenderSetState: {
|
||||
return {
|
||||
category,
|
||||
severity: ErrorSeverity.Error,
|
||||
name: 'set-state-in-render',
|
||||
description:
|
||||
'Validates against setting state during render, which can trigger additional renders and potential infinite render loops',
|
||||
@@ -835,6 +909,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
case ErrorCategory.StaticComponents: {
|
||||
return {
|
||||
category,
|
||||
severity: ErrorSeverity.Error,
|
||||
name: 'static-components',
|
||||
description:
|
||||
'Validates that components are static, not recreated every render. Components that are recreated dynamically can reset state and trigger excessive re-rendering',
|
||||
@@ -844,6 +919,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
case ErrorCategory.Suppression: {
|
||||
return {
|
||||
category,
|
||||
severity: ErrorSeverity.Error,
|
||||
name: 'rule-suppression',
|
||||
description: 'Validates against suppression of other rules',
|
||||
recommended: false,
|
||||
@@ -852,6 +928,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
case ErrorCategory.Syntax: {
|
||||
return {
|
||||
category,
|
||||
severity: ErrorSeverity.Error,
|
||||
name: 'syntax',
|
||||
description: 'Validates against invalid syntax',
|
||||
recommended: false,
|
||||
@@ -860,6 +937,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
case ErrorCategory.Todo: {
|
||||
return {
|
||||
category,
|
||||
severity: ErrorSeverity.Hint,
|
||||
name: 'todo',
|
||||
description: 'Unimplemented features',
|
||||
recommended: false,
|
||||
@@ -868,6 +946,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
case ErrorCategory.UnsupportedSyntax: {
|
||||
return {
|
||||
category,
|
||||
severity: ErrorSeverity.Warning,
|
||||
name: 'unsupported-syntax',
|
||||
description:
|
||||
'Validates against syntax that we do not plan to support in React Compiler',
|
||||
@@ -877,6 +956,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
case ErrorCategory.UseMemo: {
|
||||
return {
|
||||
category,
|
||||
severity: ErrorSeverity.Error,
|
||||
name: 'use-memo',
|
||||
description:
|
||||
'Validates usage of the useMemo() hook against common mistakes. See [`useMemo()` docs](https://react.dev/reference/react/useMemo) for more information.',
|
||||
@@ -886,6 +966,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
case ErrorCategory.IncompatibleLibrary: {
|
||||
return {
|
||||
category,
|
||||
severity: ErrorSeverity.Warning,
|
||||
name: 'incompatible-library',
|
||||
description:
|
||||
'Validates against usage of libraries which are incompatible with memoization (manual or automatic)',
|
||||
|
||||
@@ -51,12 +51,26 @@ function insertAdditionalFunctionDeclaration(
|
||||
CompilerError.invariant(originalFnName != null && compiled.id != null, {
|
||||
reason:
|
||||
'Expected function declarations that are referenced elsewhere to have a named identifier',
|
||||
loc: fnPath.node.loc ?? null,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: fnPath.node.loc ?? null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
CompilerError.invariant(originalFnParams.length === compiledParams.length, {
|
||||
reason:
|
||||
'Expected React Compiler optimized function declarations to have the same number of parameters as source',
|
||||
loc: fnPath.node.loc ?? null,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: fnPath.node.loc ?? null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const gatingCondition = t.identifier(
|
||||
@@ -140,7 +154,13 @@ export function insertGatedFunctionDeclaration(
|
||||
CompilerError.invariant(compiled.type === 'FunctionDeclaration', {
|
||||
reason: 'Expected compiled node type to match input type',
|
||||
description: `Got ${compiled.type} but expected FunctionDeclaration`,
|
||||
loc: fnPath.node.loc ?? null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: fnPath.node.loc ?? null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
insertAdditionalFunctionDeclaration(
|
||||
fnPath,
|
||||
|
||||
@@ -9,7 +9,7 @@ import {NodePath} from '@babel/core';
|
||||
import * as t from '@babel/types';
|
||||
import {Scope as BabelScope} from '@babel/traverse';
|
||||
|
||||
import {CompilerError, ErrorCategory, ErrorSeverity} from '../CompilerError';
|
||||
import {CompilerError, ErrorCategory} from '../CompilerError';
|
||||
import {
|
||||
EnvironmentConfig,
|
||||
GeneratedSource,
|
||||
@@ -39,7 +39,6 @@ export function validateRestrictedImports(
|
||||
if (restrictedImports.has(importDeclPath.node.source.value)) {
|
||||
error.push({
|
||||
category: ErrorCategory.Todo,
|
||||
severity: ErrorSeverity.Todo,
|
||||
reason: 'Bailing out due to blocklisted import',
|
||||
description: `Import from module ${importDeclPath.node.source.value}`,
|
||||
loc: importDeclPath.node.loc ?? null,
|
||||
@@ -47,7 +46,7 @@ export function validateRestrictedImports(
|
||||
}
|
||||
},
|
||||
});
|
||||
if (error.hasErrors()) {
|
||||
if (error.hasAnyErrors()) {
|
||||
return error;
|
||||
} else {
|
||||
return null;
|
||||
@@ -207,7 +206,6 @@ export class ProgramContext {
|
||||
const error = new CompilerError();
|
||||
error.push({
|
||||
category: ErrorCategory.Todo,
|
||||
severity: ErrorSeverity.Todo,
|
||||
reason: 'Encountered conflicting global in generated program',
|
||||
description: `Conflict from local binding ${name}`,
|
||||
loc: scope.getBinding(name)?.path.node.loc ?? null,
|
||||
@@ -258,8 +256,14 @@ export function addImportsToProgram(
|
||||
{
|
||||
reason:
|
||||
'Encountered conflicting import specifiers in generated program',
|
||||
description: `Conflict from import ${loweredImport.module}:(${loweredImport.imported} as ${loweredImport.name}).`,
|
||||
loc: GeneratedSource,
|
||||
description: `Conflict from import ${loweredImport.module}:(${loweredImport.imported} as ${loweredImport.name})`,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: GeneratedSource,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
},
|
||||
);
|
||||
@@ -270,7 +274,13 @@ export function addImportsToProgram(
|
||||
reason:
|
||||
'Found inconsistent import specifier. This is an internal bug.',
|
||||
description: `Expected import ${moduleName}:${specifierName} but found ${loweredImport.module}:${loweredImport.imported}`,
|
||||
loc: GeneratedSource,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: GeneratedSource,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -135,7 +135,12 @@ export type PluginOptions = {
|
||||
*/
|
||||
eslintSuppressionRules: Array<string> | null | undefined;
|
||||
|
||||
/**
|
||||
* Whether to report "suppression" errors for Flow suppressions. If false, suppression errors
|
||||
* are only emitted for ESLint suppressions
|
||||
*/
|
||||
flowSuppressions: boolean;
|
||||
|
||||
/*
|
||||
* Ignore 'use no forget' annotations. Helpful during testing but should not be used in production.
|
||||
*/
|
||||
|
||||
@@ -103,6 +103,7 @@ import {validateNoFreezingKnownMutableFunctions} from '../Validation/ValidateNoF
|
||||
import {inferMutationAliasingEffects} from '../Inference/InferMutationAliasingEffects';
|
||||
import {inferMutationAliasingRanges} from '../Inference/InferMutationAliasingRanges';
|
||||
import {validateNoDerivedComputationsInEffects} from '../Validation/ValidateNoDerivedComputationsInEffects';
|
||||
import {nameAnonymousFunctions} from '../Transform/NameAnonymousFunctions';
|
||||
|
||||
export type CompilerPipelineValue =
|
||||
| {kind: 'ast'; name: string; value: CodegenFunction}
|
||||
@@ -324,6 +325,15 @@ function runWithEnvironment(
|
||||
outlineJSX(hir);
|
||||
}
|
||||
|
||||
if (env.config.enableNameAnonymousFunctions) {
|
||||
nameAnonymousFunctions(hir);
|
||||
log({
|
||||
kind: 'hir',
|
||||
name: 'NameAnonymousFunctions',
|
||||
value: hir,
|
||||
});
|
||||
}
|
||||
|
||||
if (env.config.enableFunctionOutlining) {
|
||||
outlineFunctions(hir, fbtOperands);
|
||||
log({kind: 'hir', name: 'OutlineFunctions', value: hir});
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
CompilerError,
|
||||
CompilerErrorDetail,
|
||||
ErrorCategory,
|
||||
ErrorSeverity,
|
||||
} from '../CompilerError';
|
||||
import {ExternalFunction, ReactFunctionType} from '../HIR/Environment';
|
||||
import {CodegenFunction} from '../ReactiveScopes';
|
||||
@@ -105,7 +104,6 @@ function findDirectivesDynamicGating(
|
||||
errors.push({
|
||||
reason: `Dynamic gating directive is not a valid JavaScript identifier`,
|
||||
description: `Found '${directive.value.value}'`,
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
category: ErrorCategory.Gating,
|
||||
loc: directive.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -113,7 +111,7 @@ function findDirectivesDynamicGating(
|
||||
}
|
||||
}
|
||||
}
|
||||
if (errors.hasErrors()) {
|
||||
if (errors.hasAnyErrors()) {
|
||||
return Err(errors);
|
||||
} else if (result.length > 1) {
|
||||
const error = new CompilerError();
|
||||
@@ -122,7 +120,6 @@ function findDirectivesDynamicGating(
|
||||
description: `Expected a single directive but found [${result
|
||||
.map(r => r.directive.value.value)
|
||||
.join(', ')}]`,
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
category: ErrorCategory.Gating,
|
||||
loc: result[0].directive.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -141,15 +138,13 @@ function findDirectivesDynamicGating(
|
||||
}
|
||||
}
|
||||
|
||||
function isCriticalError(err: unknown): boolean {
|
||||
return !(err instanceof CompilerError) || err.isCritical();
|
||||
function isError(err: unknown): boolean {
|
||||
return !(err instanceof CompilerError) || err.hasErrors();
|
||||
}
|
||||
|
||||
function isConfigError(err: unknown): boolean {
|
||||
if (err instanceof CompilerError) {
|
||||
return err.details.some(
|
||||
detail => detail.severity === ErrorSeverity.InvalidConfig,
|
||||
);
|
||||
return err.details.some(detail => detail.category === ErrorCategory.Config);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -214,8 +209,7 @@ function handleError(
|
||||
logError(err, context, fnLoc);
|
||||
if (
|
||||
context.opts.panicThreshold === 'all_errors' ||
|
||||
(context.opts.panicThreshold === 'critical_errors' &&
|
||||
isCriticalError(err)) ||
|
||||
(context.opts.panicThreshold === 'critical_errors' && isError(err)) ||
|
||||
isConfigError(err) // Always throws regardless of panic threshold
|
||||
) {
|
||||
throw err;
|
||||
@@ -316,7 +310,13 @@ function insertNewOutlinedFunctionNode(
|
||||
CompilerError.invariant(insertedFuncDecl.isFunctionDeclaration(), {
|
||||
reason: 'Expected inserted function declaration',
|
||||
description: `Got: ${insertedFuncDecl}`,
|
||||
loc: insertedFuncDecl.node?.loc ?? null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: insertedFuncDecl.node?.loc ?? null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
return insertedFuncDecl;
|
||||
}
|
||||
@@ -425,7 +425,14 @@ export function compileProgram(
|
||||
for (const outlined of compiled.outlined) {
|
||||
CompilerError.invariant(outlined.fn.outlined.length === 0, {
|
||||
reason: 'Unexpected nested outlined functions',
|
||||
loc: outlined.fn.loc,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: outlined.fn.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
const fn = insertNewOutlinedFunctionNode(
|
||||
program,
|
||||
@@ -458,7 +465,6 @@ export function compileProgram(
|
||||
new CompilerErrorDetail({
|
||||
reason:
|
||||
'Unexpected compiled functions when module scope opt-out is present',
|
||||
severity: ErrorSeverity.Invariant,
|
||||
category: ErrorCategory.Invariant,
|
||||
loc: null,
|
||||
}),
|
||||
@@ -827,7 +833,6 @@ function shouldSkipCompilation(
|
||||
reason: `Expected a filename but found none.`,
|
||||
description:
|
||||
"When the 'sources' config options is specified, the React compiler will only compile files with a name",
|
||||
severity: ErrorSeverity.InvalidConfig,
|
||||
category: ErrorCategory.Config,
|
||||
loc: null,
|
||||
}),
|
||||
@@ -890,7 +895,6 @@ function validateNoDynamicallyCreatedComponentsOrHooks(
|
||||
if (nestedFnType === 'Component' || nestedFnType === 'Hook') {
|
||||
CompilerError.throwDiagnostic({
|
||||
category: ErrorCategory.Factories,
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
reason: `Components and hooks cannot be created dynamically`,
|
||||
description: `The function \`${nestedName}\` appears to be a React ${nestedFnType.toLowerCase()}, but it's defined inside \`${parentName}\`. Components and Hooks should always be declared at module scope`,
|
||||
details: [
|
||||
@@ -1416,7 +1420,13 @@ export function getReactCompilerRuntimeModule(
|
||||
{
|
||||
reason: 'Expected target to already be validated',
|
||||
description: null,
|
||||
loc: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
CompilerError,
|
||||
CompilerSuggestionOperation,
|
||||
ErrorCategory,
|
||||
ErrorSeverity,
|
||||
} from '../CompilerError';
|
||||
import {assertExhaustive} from '../Utils/utils';
|
||||
import {GeneratedSource} from '../HIR';
|
||||
@@ -87,12 +86,18 @@ export function findProgramSuppressions(
|
||||
let enableComment: t.Comment | null = null;
|
||||
let source: SuppressionSource | null = null;
|
||||
|
||||
const rulePattern = `(${ruleNames.join('|')})`;
|
||||
const disableNextLinePattern = new RegExp(
|
||||
`eslint-disable-next-line ${rulePattern}`,
|
||||
);
|
||||
const disablePattern = new RegExp(`eslint-disable ${rulePattern}`);
|
||||
const enablePattern = new RegExp(`eslint-enable ${rulePattern}`);
|
||||
let disableNextLinePattern: RegExp | null = null;
|
||||
let disablePattern: RegExp | null = null;
|
||||
let enablePattern: RegExp | null = null;
|
||||
if (ruleNames.length !== 0) {
|
||||
const rulePattern = `(${ruleNames.join('|')})`;
|
||||
disableNextLinePattern = new RegExp(
|
||||
`eslint-disable-next-line ${rulePattern}`,
|
||||
);
|
||||
disablePattern = new RegExp(`eslint-disable ${rulePattern}`);
|
||||
enablePattern = new RegExp(`eslint-enable ${rulePattern}`);
|
||||
}
|
||||
|
||||
const flowSuppressionPattern = new RegExp(
|
||||
'\\$(FlowFixMe\\w*|FlowExpectedError|FlowIssue)\\[react\\-rule',
|
||||
);
|
||||
@@ -108,6 +113,7 @@ export function findProgramSuppressions(
|
||||
* CommentLine within the block.
|
||||
*/
|
||||
disableComment == null &&
|
||||
disableNextLinePattern != null &&
|
||||
disableNextLinePattern.test(comment.value)
|
||||
) {
|
||||
disableComment = comment;
|
||||
@@ -125,12 +131,16 @@ export function findProgramSuppressions(
|
||||
source = 'Flow';
|
||||
}
|
||||
|
||||
if (disablePattern.test(comment.value)) {
|
||||
if (disablePattern != null && disablePattern.test(comment.value)) {
|
||||
disableComment = comment;
|
||||
source = 'Eslint';
|
||||
}
|
||||
|
||||
if (enablePattern.test(comment.value) && source === 'Eslint') {
|
||||
if (
|
||||
enablePattern != null &&
|
||||
enablePattern.test(comment.value) &&
|
||||
source === 'Eslint'
|
||||
) {
|
||||
enableComment = comment;
|
||||
}
|
||||
|
||||
@@ -153,7 +163,14 @@ export function suppressionsToCompilerError(
|
||||
): CompilerError {
|
||||
CompilerError.invariant(suppressionRanges.length !== 0, {
|
||||
reason: `Expected at least suppression comment source range`,
|
||||
loc: GeneratedSource,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: GeneratedSource,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
const error = new CompilerError();
|
||||
for (const suppressionRange of suppressionRanges) {
|
||||
@@ -186,7 +203,6 @@ export function suppressionsToCompilerError(
|
||||
CompilerDiagnostic.create({
|
||||
reason: reason,
|
||||
description: `React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression \`${suppressionRange.disableComment.value.trim()}\``,
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
category: ErrorCategory.Suppression,
|
||||
suggestions: [
|
||||
{
|
||||
@@ -198,7 +214,7 @@ export function suppressionsToCompilerError(
|
||||
op: CompilerSuggestionOperation.Remove,
|
||||
},
|
||||
],
|
||||
}).withDetail({
|
||||
}).withDetails({
|
||||
kind: 'error',
|
||||
loc: suppressionRange.disableComment.loc ?? null,
|
||||
message: 'Found React rule suppression',
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import {NodePath} from '@babel/core';
|
||||
import * as t from '@babel/types';
|
||||
|
||||
import {CompilerError, EnvironmentConfig, ErrorSeverity, Logger} from '..';
|
||||
import {CompilerError, EnvironmentConfig, Logger} from '..';
|
||||
import {getOrInsertWith} from '../Utils/utils';
|
||||
import {Environment, GeneratedSource} from '../HIR';
|
||||
import {DEFAULT_EXPORT} from '../HIR/Environment';
|
||||
@@ -20,19 +20,15 @@ import {
|
||||
} from '../CompilerError';
|
||||
|
||||
function throwInvalidReact(
|
||||
options: Omit<CompilerDiagnosticOptions, 'severity'>,
|
||||
options: CompilerDiagnosticOptions,
|
||||
{logger, filename}: TraversalState,
|
||||
): never {
|
||||
const detail: CompilerDiagnosticOptions = {
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
...options,
|
||||
};
|
||||
logger?.logEvent(filename, {
|
||||
kind: 'CompileError',
|
||||
fnLoc: null,
|
||||
detail: new CompilerDiagnostic(detail),
|
||||
detail: new CompilerDiagnostic(options),
|
||||
});
|
||||
CompilerError.throwDiagnostic(detail);
|
||||
CompilerError.throwDiagnostic(options);
|
||||
}
|
||||
|
||||
function isAutodepsSigil(
|
||||
@@ -100,7 +96,7 @@ function assertValidEffectImportReference(
|
||||
reason:
|
||||
'Cannot infer dependencies of this effect. This will break your build!',
|
||||
description:
|
||||
'To resolve, either pass a dependency array or fix reported compiler bailout diagnostics.' +
|
||||
'To resolve, either pass a dependency array or fix reported compiler bailout diagnostics' +
|
||||
(maybeErrorDiagnostic ? ` ${maybeErrorDiagnostic}` : ''),
|
||||
details: [
|
||||
{
|
||||
@@ -132,9 +128,7 @@ function assertValidFireImportReference(
|
||||
reason: '[Fire] Untransformed reference to compiler-required feature.',
|
||||
description:
|
||||
'Either remove this `fire` call or ensure it is successfully transformed by the compiler' +
|
||||
maybeErrorDiagnostic
|
||||
? ` ${maybeErrorDiagnostic}`
|
||||
: '',
|
||||
(maybeErrorDiagnostic != null ? ` ${maybeErrorDiagnostic}` : ''),
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
@@ -221,7 +215,14 @@ function validateImportSpecifier(
|
||||
const binding = local.scope.getBinding(local.node.name);
|
||||
CompilerError.invariant(binding != null, {
|
||||
reason: 'Expected binding to be found for import specifier',
|
||||
loc: local.node.loc ?? null,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: local.node.loc ?? null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
checkFn(binding.referencePaths, state);
|
||||
}
|
||||
@@ -241,7 +242,14 @@ function validateNamespacedImport(
|
||||
|
||||
CompilerError.invariant(binding != null, {
|
||||
reason: 'Expected binding to be found for import specifier',
|
||||
loc: local.node.loc ?? null,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: local.node.loc ?? null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
const filteredReferences = new Map<
|
||||
CheckInvalidReferenceFn,
|
||||
|
||||
@@ -46,7 +46,14 @@ export function raiseUnificationErrors(
|
||||
if (errs.length === 0) {
|
||||
CompilerError.invariant(false, {
|
||||
reason: 'Should not have array of zero errors',
|
||||
loc,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
} else if (errs.length === 1) {
|
||||
CompilerError.throwInvalidJS({
|
||||
|
||||
@@ -152,7 +152,13 @@ export function makeLinearId(id: number): LinearId {
|
||||
CompilerError.invariant(id >= 0 && Number.isInteger(id), {
|
||||
reason: 'Expected LinearId id to be a non-negative integer',
|
||||
description: null,
|
||||
loc: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
return id as LinearId;
|
||||
@@ -167,7 +173,13 @@ export function makeTypeParameterId(id: number): TypeParameterId {
|
||||
CompilerError.invariant(id >= 0 && Number.isInteger(id), {
|
||||
reason: 'Expected TypeParameterId to be a non-negative integer',
|
||||
description: null,
|
||||
loc: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
return id as TypeParameterId;
|
||||
@@ -191,7 +203,13 @@ export function makeVariableId(id: number): VariableId {
|
||||
CompilerError.invariant(id >= 0 && Number.isInteger(id), {
|
||||
reason: 'Expected VariableId id to be a non-negative integer',
|
||||
description: null,
|
||||
loc: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
return id as VariableId;
|
||||
@@ -399,7 +417,14 @@ function convertFlowType(flowType: FlowType, loc: string): ResolvedType {
|
||||
} else {
|
||||
CompilerError.invariant(false, {
|
||||
reason: `Unsupported property kind ${prop.kind}`,
|
||||
loc: GeneratedSource,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: GeneratedSource,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -468,7 +493,14 @@ function convertFlowType(flowType: FlowType, loc: string): ResolvedType {
|
||||
} else {
|
||||
CompilerError.invariant(false, {
|
||||
reason: `Unsupported property kind ${prop.kind}`,
|
||||
loc: GeneratedSource,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: GeneratedSource,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -487,7 +519,14 @@ function convertFlowType(flowType: FlowType, loc: string): ResolvedType {
|
||||
} else {
|
||||
CompilerError.invariant(false, {
|
||||
reason: `Unsupported property kind ${prop.kind}`,
|
||||
loc: GeneratedSource,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: GeneratedSource,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -500,7 +539,14 @@ function convertFlowType(flowType: FlowType, loc: string): ResolvedType {
|
||||
}
|
||||
CompilerError.invariant(false, {
|
||||
reason: `Unsupported class instance type ${flowType.def.type.kind}`,
|
||||
loc: GeneratedSource,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: GeneratedSource,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
case 'Fun':
|
||||
@@ -559,7 +605,14 @@ function convertFlowType(flowType: FlowType, loc: string): ResolvedType {
|
||||
} else {
|
||||
CompilerError.invariant(false, {
|
||||
reason: `Unsupported component props type ${propsType.type.kind}`,
|
||||
loc: GeneratedSource,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: GeneratedSource,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -712,7 +765,14 @@ export class FlowTypeEnv implements ITypeEnv {
|
||||
// TODO: use flow-js only for web environments (e.g. playground)
|
||||
CompilerError.invariant(env.config.flowTypeProvider != null, {
|
||||
reason: 'Expected flowDumpTypes to be defined in environment config',
|
||||
loc: GeneratedSource,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: GeneratedSource,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
let stdout: any;
|
||||
if (source === lastFlowSource) {
|
||||
|
||||
@@ -38,7 +38,13 @@ export function assertConsistentIdentifiers(fn: HIRFunction): void {
|
||||
CompilerError.invariant(instr.lvalue.identifier.name === null, {
|
||||
reason: `Expected all lvalues to be temporaries`,
|
||||
description: `Found named lvalue \`${instr.lvalue.identifier.name}\``,
|
||||
loc: instr.lvalue.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: instr.lvalue.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
CompilerError.invariant(!assignments.has(instr.lvalue.identifier.id), {
|
||||
@@ -46,7 +52,13 @@ export function assertConsistentIdentifiers(fn: HIRFunction): void {
|
||||
description: `Found duplicate assignment of '${printPlace(
|
||||
instr.lvalue,
|
||||
)}'`,
|
||||
loc: instr.lvalue.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: instr.lvalue.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
assignments.add(instr.lvalue.identifier.id);
|
||||
@@ -77,7 +89,13 @@ function validate(
|
||||
CompilerError.invariant(identifier === previous, {
|
||||
reason: `Duplicate identifier object`,
|
||||
description: `Found duplicate identifier object for id ${identifier.id}`,
|
||||
loc: loc ?? GeneratedSource,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: loc ?? GeneratedSource,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -18,7 +18,13 @@ export function assertTerminalSuccessorsExist(fn: HIRFunction): void {
|
||||
description: `Block bb${successor} does not exist for terminal '${printTerminal(
|
||||
block.terminal,
|
||||
)}'`,
|
||||
loc: (block.terminal as any).loc ?? GeneratedSource,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: (block.terminal as any).loc ?? GeneratedSource,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
return successor;
|
||||
@@ -33,14 +39,26 @@ export function assertTerminalPredsExist(fn: HIRFunction): void {
|
||||
CompilerError.invariant(predBlock != null, {
|
||||
reason: 'Expected predecessor block to exist',
|
||||
description: `Block ${block.id} references non-existent ${pred}`,
|
||||
loc: GeneratedSource,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: GeneratedSource,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
CompilerError.invariant(
|
||||
[...eachTerminalSuccessor(predBlock.terminal)].includes(block.id),
|
||||
{
|
||||
reason: 'Terminal successor does not reference correct predecessor',
|
||||
description: `Block bb${block.id} has bb${predBlock.id} as a predecessor, but bb${predBlock.id}'s successors do not include bb${block.id}`,
|
||||
loc: GeneratedSource,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: GeneratedSource,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -131,7 +131,13 @@ export function recursivelyTraverseItems<T, TContext>(
|
||||
CompilerError.invariant(disjoint || nested, {
|
||||
reason: 'Invalid nesting in program blocks or scopes',
|
||||
description: `Items overlap but are not nested: ${maybeParentRange.start}:${maybeParentRange.end}(${currRange.start}:${currRange.end})`,
|
||||
loc: GeneratedSource,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: GeneratedSource,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
if (disjoint) {
|
||||
exit(maybeParent, context);
|
||||
|
||||
@@ -57,7 +57,13 @@ function validateMutableRange(
|
||||
{
|
||||
reason: `Invalid mutable range: [${range.start}:${range.end}]`,
|
||||
description: `${printPlace(place)} in ${description}`,
|
||||
loc: place.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: place.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
CompilerError,
|
||||
CompilerSuggestionOperation,
|
||||
ErrorCategory,
|
||||
ErrorSeverity,
|
||||
} from '../CompilerError';
|
||||
import {Err, Ok, Result} from '../Utils/Result';
|
||||
import {assertExhaustive, hasNode} from '../Utils/utils';
|
||||
@@ -48,6 +47,7 @@ import {
|
||||
makePropertyLiteral,
|
||||
makeType,
|
||||
promoteTemporary,
|
||||
validateIdentifierName,
|
||||
} from './HIR';
|
||||
import HIRBuilder, {Bindings, createTemporaryPlace} from './HIRBuilder';
|
||||
import {BuiltInArrayId} from './ObjectShape';
|
||||
@@ -108,11 +108,10 @@ export function lower(
|
||||
if (binding.kind !== 'Identifier') {
|
||||
builder.errors.pushDiagnostic(
|
||||
CompilerDiagnostic.create({
|
||||
severity: ErrorSeverity.Invariant,
|
||||
category: ErrorCategory.Invariant,
|
||||
reason: 'Could not find binding',
|
||||
description: `[BuildHIR] Could not find binding for param \`${param.node.name}\`.`,
|
||||
}).withDetail({
|
||||
description: `[BuildHIR] Could not find binding for param \`${param.node.name}\``,
|
||||
}).withDetails({
|
||||
kind: 'error',
|
||||
loc: param.node.loc ?? null,
|
||||
message: 'Could not find binding',
|
||||
@@ -173,11 +172,10 @@ export function lower(
|
||||
} else {
|
||||
builder.errors.pushDiagnostic(
|
||||
CompilerDiagnostic.create({
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.Todo,
|
||||
reason: `Handle ${param.node.type} parameters`,
|
||||
description: `[BuildHIR] Add support for ${param.node.type} parameters.`,
|
||||
}).withDetail({
|
||||
description: `[BuildHIR] Add support for ${param.node.type} parameters`,
|
||||
}).withDetails({
|
||||
kind: 'error',
|
||||
loc: param.node.loc ?? null,
|
||||
message: 'Unsupported parameter type',
|
||||
@@ -205,11 +203,10 @@ export function lower(
|
||||
} else {
|
||||
builder.errors.pushDiagnostic(
|
||||
CompilerDiagnostic.create({
|
||||
severity: ErrorSeverity.InvalidJS,
|
||||
category: ErrorCategory.Syntax,
|
||||
reason: `Unexpected function body kind`,
|
||||
description: `Expected function body to be an expression or a block statement, got \`${body.type}\`.`,
|
||||
}).withDetail({
|
||||
description: `Expected function body to be an expression or a block statement, got \`${body.type}\``,
|
||||
}).withDetails({
|
||||
kind: 'error',
|
||||
loc: body.node.loc ?? null,
|
||||
message: 'Expected a block statement or expression',
|
||||
@@ -217,7 +214,17 @@ export function lower(
|
||||
);
|
||||
}
|
||||
|
||||
if (builder.errors.hasErrors()) {
|
||||
let validatedId: HIRFunction['id'] = null;
|
||||
if (id != null) {
|
||||
const idResult = validateIdentifierName(id);
|
||||
if (idResult.isErr()) {
|
||||
builder.errors.merge(idResult.unwrapErr());
|
||||
} else {
|
||||
validatedId = idResult.unwrap().value;
|
||||
}
|
||||
}
|
||||
|
||||
if (builder.errors.hasAnyErrors()) {
|
||||
return Err(builder.errors);
|
||||
}
|
||||
|
||||
@@ -238,7 +245,8 @@ export function lower(
|
||||
);
|
||||
|
||||
return Ok({
|
||||
id,
|
||||
id: validatedId,
|
||||
nameHint: null,
|
||||
params,
|
||||
fnType: bindings == null ? env.fnType : 'Other',
|
||||
returnTypeAnnotation: null, // TODO: extract the actual return type node if present
|
||||
@@ -276,7 +284,6 @@ function lowerStatement(
|
||||
builder.errors.push({
|
||||
reason:
|
||||
'(BuildHIR::lowerStatement) Support ThrowStatement inside of try/catch',
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.Todo,
|
||||
loc: stmt.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -444,7 +451,13 @@ function lowerStatement(
|
||||
reason: 'Expected to find binding for hoisted identifier',
|
||||
description: `Could not find a binding for ${id.node.name}`,
|
||||
suggestions: null,
|
||||
loc: id.node.loc ?? GeneratedSource,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: id.node.loc ?? GeneratedSource,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
if (builder.environment.isHoistedIdentifier(binding.identifier)) {
|
||||
// Already hoisted
|
||||
@@ -464,7 +477,6 @@ function lowerStatement(
|
||||
kind = InstructionKind.HoistedFunction;
|
||||
} else if (!binding.path.isVariableDeclarator()) {
|
||||
builder.errors.push({
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.Todo,
|
||||
reason: 'Unsupported declaration type for hoisting',
|
||||
description: `variable "${binding.identifier.name}" declared with ${binding.path.type}`,
|
||||
@@ -474,7 +486,6 @@ function lowerStatement(
|
||||
continue;
|
||||
} else {
|
||||
builder.errors.push({
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.Todo,
|
||||
reason: 'Handle non-const declarations for hoisting',
|
||||
description: `variable "${binding.identifier.name}" declared with ${binding.kind}`,
|
||||
@@ -488,7 +499,14 @@ function lowerStatement(
|
||||
CompilerError.invariant(identifier.kind === 'Identifier', {
|
||||
reason:
|
||||
'Expected hoisted binding to be a local identifier, not a global',
|
||||
loc: id.node.loc ?? GeneratedSource,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: id.node.loc ?? GeneratedSource,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
const place: Place = {
|
||||
effect: Effect.Unknown,
|
||||
@@ -555,7 +573,6 @@ function lowerStatement(
|
||||
builder.errors.push({
|
||||
reason:
|
||||
'(BuildHIR::lowerStatement) Handle non-variable initialization in ForStatement',
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.Todo,
|
||||
loc: stmt.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -628,7 +645,6 @@ function lowerStatement(
|
||||
if (test.node == null) {
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::lowerStatement) Handle empty test in ForStatement`,
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.Todo,
|
||||
loc: stmt.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -780,7 +796,6 @@ function lowerStatement(
|
||||
if (hasDefault) {
|
||||
builder.errors.push({
|
||||
reason: `Expected at most one \`default\` branch in a switch statement, this code should have failed to parse`,
|
||||
severity: ErrorSeverity.InvalidJS,
|
||||
category: ErrorCategory.Syntax,
|
||||
loc: case_.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -853,7 +868,6 @@ function lowerStatement(
|
||||
if (nodeKind === 'var') {
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::lowerStatement) Handle ${nodeKind} kinds in VariableDeclaration`,
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.Todo,
|
||||
loc: stmt.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -882,7 +896,6 @@ function lowerStatement(
|
||||
if (binding.kind !== 'Identifier') {
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::lowerAssignment) Could not find binding for declaration.`,
|
||||
severity: ErrorSeverity.Invariant,
|
||||
category: ErrorCategory.Invariant,
|
||||
loc: id.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -900,7 +913,6 @@ function lowerStatement(
|
||||
const declRangeStart = declaration.parentPath.node.start!;
|
||||
builder.errors.push({
|
||||
reason: `Expect \`const\` declaration not to be reassigned`,
|
||||
severity: ErrorSeverity.InvalidJS,
|
||||
category: ErrorCategory.Syntax,
|
||||
loc: id.node.loc ?? null,
|
||||
suggestions: [
|
||||
@@ -948,7 +960,6 @@ function lowerStatement(
|
||||
builder.errors.push({
|
||||
reason: `Expected variable declaration to be an identifier if no initializer was provided`,
|
||||
description: `Got a \`${id.type}\``,
|
||||
severity: ErrorSeverity.InvalidJS,
|
||||
category: ErrorCategory.Syntax,
|
||||
loc: stmt.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -1028,7 +1039,13 @@ function lowerStatement(
|
||||
CompilerError.invariant(stmt.get('id').type === 'Identifier', {
|
||||
reason: 'function declarations must have a name',
|
||||
description: null,
|
||||
loc: stmt.node.loc ?? null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: stmt.node.loc ?? null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
const id = stmt.get('id') as NodePath<t.Identifier>;
|
||||
@@ -1057,7 +1074,6 @@ function lowerStatement(
|
||||
if (stmt.node.await) {
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::lowerStatement) Handle for-await loops`,
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.Todo,
|
||||
loc: stmt.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -1129,7 +1145,13 @@ function lowerStatement(
|
||||
CompilerError.invariant(declarations.length === 1, {
|
||||
reason: `Expected only one declaration in the init of a ForOfStatement, got ${declarations.length}`,
|
||||
description: null,
|
||||
loc: left.node.loc ?? null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: left.node.loc ?? null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
const id = declarations[0].get('id');
|
||||
@@ -1144,8 +1166,15 @@ function lowerStatement(
|
||||
test = lowerValueToTemporary(builder, assign);
|
||||
} else {
|
||||
CompilerError.invariant(left.isLVal(), {
|
||||
loc: leftLoc,
|
||||
reason: 'Expected ForOf init to be a variable declaration or lval',
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: leftLoc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
const assign = lowerAssignment(
|
||||
builder,
|
||||
@@ -1222,7 +1251,13 @@ function lowerStatement(
|
||||
CompilerError.invariant(declarations.length === 1, {
|
||||
reason: `Expected only one declaration in the init of a ForInStatement, got ${declarations.length}`,
|
||||
description: null,
|
||||
loc: left.node.loc ?? null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: left.node.loc ?? null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
const id = declarations[0].get('id');
|
||||
@@ -1237,8 +1272,15 @@ function lowerStatement(
|
||||
test = lowerValueToTemporary(builder, assign);
|
||||
} else {
|
||||
CompilerError.invariant(left.isLVal(), {
|
||||
loc: leftLoc,
|
||||
reason: 'Expected ForIn init to be a variable declaration or lval',
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: leftLoc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
const assign = lowerAssignment(
|
||||
builder,
|
||||
@@ -1290,7 +1332,6 @@ function lowerStatement(
|
||||
if (!hasNode(handlerPath)) {
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::lowerStatement) Handle TryStatement without a catch clause`,
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.Todo,
|
||||
loc: stmt.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -1300,7 +1341,6 @@ function lowerStatement(
|
||||
if (hasNode(stmt.get('finalizer'))) {
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::lowerStatement) Handle TryStatement with a finalizer ('finally') clause`,
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.Todo,
|
||||
loc: stmt.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -1394,7 +1434,6 @@ function lowerStatement(
|
||||
builder.errors.push({
|
||||
reason: `JavaScript 'with' syntax is not supported`,
|
||||
description: `'with' syntax is considered deprecated and removed from JavaScript standards, consider alternatives`,
|
||||
severity: ErrorSeverity.UnsupportedJS,
|
||||
category: ErrorCategory.UnsupportedSyntax,
|
||||
loc: stmtPath.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -1415,7 +1454,6 @@ function lowerStatement(
|
||||
builder.errors.push({
|
||||
reason: 'Inline `class` declarations are not supported',
|
||||
description: `Move class declarations outside of components/hooks`,
|
||||
severity: ErrorSeverity.UnsupportedJS,
|
||||
category: ErrorCategory.UnsupportedSyntax,
|
||||
loc: stmtPath.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -1445,7 +1483,6 @@ function lowerStatement(
|
||||
builder.errors.push({
|
||||
reason:
|
||||
'JavaScript `import` and `export` statements may only appear at the top level of a module',
|
||||
severity: ErrorSeverity.InvalidJS,
|
||||
category: ErrorCategory.Syntax,
|
||||
loc: stmtPath.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -1461,7 +1498,6 @@ function lowerStatement(
|
||||
builder.errors.push({
|
||||
reason:
|
||||
'TypeScript `namespace` statements may only appear at the top level of a module',
|
||||
severity: ErrorSeverity.InvalidJS,
|
||||
category: ErrorCategory.Syntax,
|
||||
loc: stmtPath.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -1540,7 +1576,6 @@ function lowerObjectPropertyKey(
|
||||
*/
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::lowerExpression) Expected Identifier, got ${key.type} key in ObjectExpression`,
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.Todo,
|
||||
loc: key.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -1566,7 +1601,6 @@ function lowerObjectPropertyKey(
|
||||
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::lowerExpression) Expected Identifier, got ${key.type} key in ObjectExpression`,
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.Todo,
|
||||
loc: key.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -1624,7 +1658,6 @@ function lowerExpression(
|
||||
if (!valuePath.isExpression()) {
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::lowerExpression) Handle ${valuePath.type} values in ObjectExpression`,
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.Todo,
|
||||
loc: valuePath.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -1651,7 +1684,6 @@ function lowerExpression(
|
||||
if (propertyPath.node.kind !== 'method') {
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::lowerExpression) Handle ${propertyPath.node.kind} functions in ObjectExpression`,
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.Todo,
|
||||
loc: propertyPath.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -1673,7 +1705,6 @@ function lowerExpression(
|
||||
} else {
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::lowerExpression) Handle ${propertyPath.type} properties in ObjectExpression`,
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.Todo,
|
||||
loc: propertyPath.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -1707,7 +1738,6 @@ function lowerExpression(
|
||||
} else {
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::lowerExpression) Handle ${element.type} elements in ArrayExpression`,
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.Todo,
|
||||
loc: element.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -1728,7 +1758,6 @@ function lowerExpression(
|
||||
builder.errors.push({
|
||||
reason: `Expected an expression as the \`new\` expression receiver (v8 intrinsics are not supported)`,
|
||||
description: `Got a \`${calleePath.node.type}\``,
|
||||
severity: ErrorSeverity.InvalidJS,
|
||||
category: ErrorCategory.Syntax,
|
||||
loc: calleePath.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -1755,7 +1784,6 @@ function lowerExpression(
|
||||
if (!calleePath.isExpression()) {
|
||||
builder.errors.push({
|
||||
reason: `Expected Expression, got ${calleePath.type} in CallExpression (v8 intrinsics not supported). This error is likely caused by a bug in React Compiler. Please file an issue`,
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.Todo,
|
||||
loc: calleePath.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -1790,7 +1818,6 @@ function lowerExpression(
|
||||
if (!leftPath.isExpression()) {
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::lowerExpression) Expected Expression, got ${leftPath.type} lval in BinaryExpression`,
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.Todo,
|
||||
loc: leftPath.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -1803,7 +1830,6 @@ function lowerExpression(
|
||||
if (operator === '|>') {
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::lowerExpression) Pipe operator not supported`,
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.Todo,
|
||||
loc: leftPath.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -1833,7 +1859,6 @@ function lowerExpression(
|
||||
if (last === null) {
|
||||
builder.errors.push({
|
||||
reason: `Expected sequence expression to have at least one expression`,
|
||||
severity: ErrorSeverity.InvalidJS,
|
||||
category: ErrorCategory.Syntax,
|
||||
loc: expr.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -2046,7 +2071,6 @@ function lowerExpression(
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::lowerExpression) Unsupported syntax on the left side of an AssignmentExpression`,
|
||||
description: `Expected an LVal, got: ${left.type}`,
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.Todo,
|
||||
loc: left.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -2075,7 +2099,6 @@ function lowerExpression(
|
||||
if (binaryOperator == null) {
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::lowerExpression) Handle ${operator} operators in AssignmentExpression`,
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.Todo,
|
||||
loc: expr.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -2175,7 +2198,6 @@ function lowerExpression(
|
||||
default: {
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::lowerExpression) Expected Identifier or MemberExpression, got ${expr.type} lval in AssignmentExpression`,
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.Todo,
|
||||
loc: expr.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -2215,7 +2237,6 @@ function lowerExpression(
|
||||
if (!attribute.isJSXAttribute()) {
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::lowerExpression) Handle ${attribute.type} attributes in JSXElement`,
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.Todo,
|
||||
loc: attribute.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -2229,7 +2250,6 @@ function lowerExpression(
|
||||
if (propName.indexOf(':') !== -1) {
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::lowerExpression) Unexpected colon in attribute name \`${propName}\``,
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.Todo,
|
||||
loc: namePath.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -2239,7 +2259,13 @@ function lowerExpression(
|
||||
CompilerError.invariant(namePath.isJSXNamespacedName(), {
|
||||
reason: 'Refinement',
|
||||
description: null,
|
||||
loc: namePath.node.loc ?? null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: namePath.node.loc ?? null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
const namespace = namePath.node.namespace.name;
|
||||
@@ -2260,7 +2286,6 @@ function lowerExpression(
|
||||
if (!valueExpr.isJSXExpressionContainer()) {
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::lowerExpression) Handle ${valueExpr.type} attribute values in JSXElement`,
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.Todo,
|
||||
loc: valueExpr.node?.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -2271,7 +2296,6 @@ function lowerExpression(
|
||||
if (!expression.isExpression()) {
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::lowerExpression) Handle ${expression.type} expressions in JSXExpressionContainer within JSXElement`,
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.Todo,
|
||||
loc: valueExpr.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -2295,8 +2319,14 @@ function lowerExpression(
|
||||
// This is already checked in builder.resolveIdentifier
|
||||
CompilerError.invariant(tagIdentifier.kind !== 'Identifier', {
|
||||
reason: `<${tagName}> tags should be module-level imports`,
|
||||
loc: openingIdentifier.node.loc ?? GeneratedSource,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: openingIdentifier.node.loc ?? GeneratedSource,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
@@ -2329,8 +2359,7 @@ function lowerExpression(
|
||||
for (const [name, locations] of Object.entries(fbtLocations)) {
|
||||
if (locations.length > 1) {
|
||||
CompilerError.throwDiagnostic({
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.FBT,
|
||||
category: ErrorCategory.Todo,
|
||||
reason: 'Support duplicate fbt tags',
|
||||
description: `Support \`<${tagName}>\` tags with multiple \`<${tagName}:${name}>\` values`,
|
||||
details: locations.map(loc => {
|
||||
@@ -2391,7 +2420,6 @@ function lowerExpression(
|
||||
builder.errors.push({
|
||||
reason:
|
||||
'(BuildHIR::lowerExpression) Handle tagged template with interpolations',
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.Todo,
|
||||
loc: exprPath.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -2402,7 +2430,13 @@ function lowerExpression(
|
||||
reason:
|
||||
"there should be only one quasi as we don't support interpolations yet",
|
||||
description: null,
|
||||
loc: expr.node.loc ?? null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: expr.node.loc ?? null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
const value = expr.get('quasi').get('quasis').at(0)!.node.value;
|
||||
@@ -2410,7 +2444,6 @@ function lowerExpression(
|
||||
builder.errors.push({
|
||||
reason:
|
||||
'(BuildHIR::lowerExpression) Handle tagged template where cooked value is different from raw value',
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.Todo,
|
||||
loc: exprPath.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -2433,7 +2466,6 @@ function lowerExpression(
|
||||
if (subexprs.length !== quasis.length - 1) {
|
||||
builder.errors.push({
|
||||
reason: `Unexpected quasi and subexpression lengths in template literal`,
|
||||
severity: ErrorSeverity.InvalidJS,
|
||||
category: ErrorCategory.Syntax,
|
||||
loc: exprPath.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -2444,7 +2476,6 @@ function lowerExpression(
|
||||
if (subexprs.some(e => !e.isExpression())) {
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::lowerAssignment) Handle TSType in TemplateLiteral.`,
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.Todo,
|
||||
loc: exprPath.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -2487,7 +2518,6 @@ function lowerExpression(
|
||||
} else {
|
||||
builder.errors.push({
|
||||
reason: `Only object properties can be deleted`,
|
||||
severity: ErrorSeverity.InvalidJS,
|
||||
category: ErrorCategory.Syntax,
|
||||
loc: expr.node.loc ?? null,
|
||||
suggestions: [
|
||||
@@ -2503,7 +2533,6 @@ function lowerExpression(
|
||||
} else if (expr.node.operator === 'throw') {
|
||||
builder.errors.push({
|
||||
reason: `Throw expressions are not supported`,
|
||||
severity: ErrorSeverity.InvalidJS,
|
||||
category: ErrorCategory.Syntax,
|
||||
loc: expr.node.loc ?? null,
|
||||
suggestions: [
|
||||
@@ -2625,7 +2654,6 @@ function lowerExpression(
|
||||
if (!argument.isIdentifier()) {
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::lowerExpression) Handle UpdateExpression with ${argument.type} argument`,
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.Todo,
|
||||
loc: exprPath.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -2634,7 +2662,6 @@ function lowerExpression(
|
||||
} else if (builder.isContextIdentifier(argument)) {
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::lowerExpression) Handle UpdateExpression to variables captured within lambdas.`,
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.Todo,
|
||||
loc: exprPath.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -2652,10 +2679,9 @@ function lowerExpression(
|
||||
* lowerIdentifierForAssignment should have already reported an error if it returned null,
|
||||
* we check here just in case
|
||||
*/
|
||||
if (!builder.errors.hasErrors()) {
|
||||
if (!builder.errors.hasAnyErrors()) {
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::lowerExpression) Found an invalid UpdateExpression without a previously reported error`,
|
||||
severity: ErrorSeverity.Invariant,
|
||||
category: ErrorCategory.Invariant,
|
||||
loc: exprLoc,
|
||||
suggestions: null,
|
||||
@@ -2665,7 +2691,6 @@ function lowerExpression(
|
||||
} else if (lvalue.kind === 'Global') {
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::lowerExpression) Support UpdateExpression where argument is a global`,
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.Todo,
|
||||
loc: exprLoc,
|
||||
suggestions: null,
|
||||
@@ -2721,7 +2746,6 @@ function lowerExpression(
|
||||
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::lowerExpression) Handle MetaProperty expressions other than import.meta`,
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.Todo,
|
||||
loc: exprPath.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -2731,7 +2755,6 @@ function lowerExpression(
|
||||
default: {
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::lowerExpression) Handle ${exprPath.type} expressions`,
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.Todo,
|
||||
loc: exprPath.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -2811,7 +2834,13 @@ function lowerOptionalMemberExpression(
|
||||
CompilerError.invariant(object !== null, {
|
||||
reason: 'Satisfy type checker',
|
||||
description: null,
|
||||
loc: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
|
||||
@@ -3029,7 +3058,6 @@ function lowerReorderableExpression(
|
||||
if (!isReorderableExpression(builder, expr, true)) {
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::node.lowerReorderableExpression) Expression type \`${expr.type}\` cannot be safely reordered`,
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.Todo,
|
||||
loc: expr.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -3226,7 +3254,6 @@ function lowerArguments(
|
||||
} else {
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::lowerExpression) Handle ${argPath.type} arguments in CallExpression`,
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.Todo,
|
||||
loc: argPath.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -3262,7 +3289,6 @@ function lowerMemberExpression(
|
||||
} else {
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::lowerMemberExpression) Handle ${propertyNode.type} property`,
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.Todo,
|
||||
loc: propertyNode.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -3284,7 +3310,6 @@ function lowerMemberExpression(
|
||||
if (!propertyNode.isExpression()) {
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::lowerMemberExpression) Expected Expression, got ${propertyNode.type} property`,
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.Todo,
|
||||
loc: propertyNode.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -3344,7 +3369,6 @@ function lowerJsxElementName(
|
||||
builder.errors.push({
|
||||
reason: `Expected JSXNamespacedName to have no colons in the namespace or name`,
|
||||
description: `Got \`${namespace}\` : \`${name}\``,
|
||||
severity: ErrorSeverity.InvalidJS,
|
||||
category: ErrorCategory.Syntax,
|
||||
loc: exprPath.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -3359,7 +3383,6 @@ function lowerJsxElementName(
|
||||
} else {
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::lowerJsxElementName) Handle ${exprPath.type} tags`,
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.Todo,
|
||||
loc: exprPath.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -3385,7 +3408,13 @@ function lowerJsxMemberExpression(
|
||||
CompilerError.invariant(object.isJSXIdentifier(), {
|
||||
reason: `TypeScript refinement fail: expected 'JsxIdentifier', got \`${object.node.type}\``,
|
||||
description: null,
|
||||
loc: object.node.loc ?? null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: object.node.loc ?? null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
|
||||
@@ -3427,7 +3456,13 @@ function lowerJsxElement(
|
||||
CompilerError.invariant(expression.isExpression(), {
|
||||
reason: `(BuildHIR::lowerJsxElement) Expected Expression but found ${expression.type}!`,
|
||||
description: null,
|
||||
loc: expression.node.loc ?? null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: expression.node.loc ?? null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
return lowerExpressionToTemporary(builder, expression);
|
||||
@@ -3458,7 +3493,6 @@ function lowerJsxElement(
|
||||
} else {
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::lowerJsxElement) Unhandled JsxElement, got: ${exprPath.type}`,
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.Todo,
|
||||
loc: exprPath.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -3541,17 +3575,14 @@ function lowerFunctionToValue(
|
||||
): InstructionValue {
|
||||
const exprNode = expr.node;
|
||||
const exprLoc = exprNode.loc ?? GeneratedSource;
|
||||
let name: string | null = null;
|
||||
if (expr.isFunctionExpression()) {
|
||||
name = expr.get('id')?.node?.name ?? null;
|
||||
}
|
||||
const loweredFunc = lowerFunction(builder, expr);
|
||||
if (!loweredFunc) {
|
||||
return {kind: 'UnsupportedNode', node: exprNode, loc: exprLoc};
|
||||
}
|
||||
return {
|
||||
kind: 'FunctionExpression',
|
||||
name,
|
||||
name: loweredFunc.func.id,
|
||||
nameHint: null,
|
||||
type: expr.node.type,
|
||||
loc: exprLoc,
|
||||
loweredFunc,
|
||||
@@ -3646,7 +3677,6 @@ function lowerIdentifier(
|
||||
reason: `The 'eval' function is not supported`,
|
||||
description:
|
||||
'Eval is an anti-pattern in JavaScript, and the code executed cannot be evaluated by React Compiler',
|
||||
severity: ErrorSeverity.UnsupportedJS,
|
||||
category: ErrorCategory.UnsupportedSyntax,
|
||||
loc: exprPath.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -3703,7 +3733,6 @@ function lowerIdentifierForAssignment(
|
||||
// Else its an internal error bc we couldn't find the binding
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::lowerAssignment) Could not find binding for declaration.`,
|
||||
severity: ErrorSeverity.Invariant,
|
||||
category: ErrorCategory.Invariant,
|
||||
loc: path.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -3716,7 +3745,6 @@ function lowerIdentifierForAssignment(
|
||||
) {
|
||||
builder.errors.push({
|
||||
reason: `Cannot reassign a \`const\` variable`,
|
||||
severity: ErrorSeverity.InvalidJS,
|
||||
category: ErrorCategory.Syntax,
|
||||
loc: path.node.loc ?? null,
|
||||
description:
|
||||
@@ -3774,7 +3802,6 @@ function lowerAssignment(
|
||||
if (kind === InstructionKind.Const && !isHoistedIdentifier) {
|
||||
builder.errors.push({
|
||||
reason: `Expected \`const\` declaration not to be reassigned`,
|
||||
severity: ErrorSeverity.InvalidJS,
|
||||
category: ErrorCategory.Syntax,
|
||||
loc: lvalue.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -3789,7 +3816,6 @@ function lowerAssignment(
|
||||
) {
|
||||
builder.errors.push({
|
||||
reason: `Unexpected context variable kind`,
|
||||
severity: ErrorSeverity.InvalidJS,
|
||||
category: ErrorCategory.Syntax,
|
||||
loc: lvalue.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -3834,7 +3860,13 @@ function lowerAssignment(
|
||||
CompilerError.invariant(kind === InstructionKind.Reassign, {
|
||||
reason: 'MemberExpression may only appear in an assignment expression',
|
||||
description: null,
|
||||
loc: lvaluePath.node.loc ?? null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: lvaluePath.node.loc ?? null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
const lvalue = lvaluePath as NodePath<t.MemberExpression>;
|
||||
@@ -3861,7 +3893,6 @@ function lowerAssignment(
|
||||
} else {
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::lowerAssignment) Handle ${property.type} properties in MemberExpression`,
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.Todo,
|
||||
loc: property.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -3874,7 +3905,6 @@ function lowerAssignment(
|
||||
builder.errors.push({
|
||||
reason:
|
||||
'(BuildHIR::lowerAssignment) Expected private name to appear as a non-computed property',
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.Todo,
|
||||
loc: property.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -3940,7 +3970,6 @@ function lowerAssignment(
|
||||
continue;
|
||||
} else if (identifier.kind === 'Global') {
|
||||
builder.errors.push({
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.Todo,
|
||||
reason:
|
||||
'Expected reassignment of globals to enable forceTemporaries',
|
||||
@@ -3980,7 +4009,6 @@ function lowerAssignment(
|
||||
continue;
|
||||
} else if (identifier.kind === 'Global') {
|
||||
builder.errors.push({
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.Todo,
|
||||
reason:
|
||||
'Expected reassignment of globals to enable forceTemporaries',
|
||||
@@ -4054,7 +4082,6 @@ function lowerAssignment(
|
||||
if (!argument.isIdentifier()) {
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::lowerAssignment) Handle ${argument.node.type} rest element in ObjectPattern`,
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.Todo,
|
||||
loc: argument.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -4086,7 +4113,6 @@ function lowerAssignment(
|
||||
continue;
|
||||
} else if (identifier.kind === 'Global') {
|
||||
builder.errors.push({
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.Todo,
|
||||
reason:
|
||||
'Expected reassignment of globals to enable forceTemporaries',
|
||||
@@ -4104,7 +4130,6 @@ function lowerAssignment(
|
||||
if (!property.isObjectProperty()) {
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::lowerAssignment) Handle ${property.type} properties in ObjectPattern`,
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.Todo,
|
||||
loc: property.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -4114,7 +4139,6 @@ function lowerAssignment(
|
||||
if (property.node.computed) {
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::lowerAssignment) Handle computed properties in ObjectPattern`,
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.Todo,
|
||||
loc: property.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -4129,7 +4153,6 @@ function lowerAssignment(
|
||||
if (!element.isLVal()) {
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::lowerAssignment) Expected object property value to be an LVal, got: ${element.type}`,
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.Todo,
|
||||
loc: element.node.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -4152,7 +4175,6 @@ function lowerAssignment(
|
||||
continue;
|
||||
} else if (identifier.kind === 'Global') {
|
||||
builder.errors.push({
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.Todo,
|
||||
reason:
|
||||
'Expected reassignment of globals to enable forceTemporaries',
|
||||
@@ -4302,7 +4324,6 @@ function lowerAssignment(
|
||||
default: {
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::lowerAssignment) Handle ${lvaluePath.type} assignments`,
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.Todo,
|
||||
loc: lvaluePath.node.loc ?? null,
|
||||
suggestions: null,
|
||||
|
||||
@@ -234,7 +234,14 @@ function pushEndScopeTerminal(
|
||||
const fallthroughId = context.fallthroughs.get(scope.id);
|
||||
CompilerError.invariant(fallthroughId != null, {
|
||||
reason: 'Expected scope to exist',
|
||||
loc: GeneratedSource,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: GeneratedSource,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
context.rewrites.push({
|
||||
kind: 'EndScope',
|
||||
|
||||
@@ -269,7 +269,14 @@ class PropertyPathRegistry {
|
||||
CompilerError.invariant(reactive === rootNode.fullPath.reactive, {
|
||||
reason:
|
||||
'[HoistablePropertyLoads] Found inconsistencies in `reactive` flag when deduping identifier reads within the same scope',
|
||||
loc: identifier.loc,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: identifier.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
return rootNode;
|
||||
@@ -498,7 +505,14 @@ function propagateNonNull(
|
||||
if (node == null) {
|
||||
CompilerError.invariant(false, {
|
||||
reason: `Bad node ${nodeId}, kind: ${direction}`,
|
||||
loc: GeneratedSource,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: GeneratedSource,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
const neighbors = Array.from(
|
||||
@@ -570,7 +584,14 @@ function propagateNonNull(
|
||||
CompilerError.invariant(i++ < 100, {
|
||||
reason:
|
||||
'[CollectHoistablePropertyLoads] fixed point iteration did not terminate after 100 loops',
|
||||
loc: GeneratedSource,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: GeneratedSource,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
changed = false;
|
||||
@@ -602,7 +623,13 @@ export function assertNonNull<T extends NonNullable<U>, U>(
|
||||
CompilerError.invariant(value != null, {
|
||||
reason: 'Unexpected null',
|
||||
description: source != null ? `(from ${source})` : null,
|
||||
loc: GeneratedSource,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: GeneratedSource,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -186,7 +186,13 @@ function matchOptionalTestBlock(
|
||||
reason:
|
||||
'[OptionalChainDeps] Inconsistent optional chaining property load',
|
||||
description: `Test=${printIdentifier(terminal.test.identifier)} PropertyLoad base=${printIdentifier(propertyLoad.value.object.identifier)}`,
|
||||
loc: propertyLoad.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: propertyLoad.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
|
||||
@@ -194,7 +200,14 @@ function matchOptionalTestBlock(
|
||||
storeLocal.value.identifier.id === propertyLoad.lvalue.identifier.id,
|
||||
{
|
||||
reason: '[OptionalChainDeps] Unexpected storeLocal',
|
||||
loc: propertyLoad.loc,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: propertyLoad.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
if (
|
||||
@@ -211,7 +224,14 @@ function matchOptionalTestBlock(
|
||||
alternate.instructions[1].value.kind === 'StoreLocal',
|
||||
{
|
||||
reason: 'Unexpected alternate structure',
|
||||
loc: terminal.loc,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: terminal.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
|
||||
@@ -247,7 +267,14 @@ function traverseOptionalBlock(
|
||||
if (maybeTest.terminal.kind === 'branch') {
|
||||
CompilerError.invariant(optional.terminal.optional, {
|
||||
reason: '[OptionalChainDeps] Expect base case to be always optional',
|
||||
loc: optional.terminal.loc,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: optional.terminal.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
/**
|
||||
* Optional base expressions are currently within value blocks which cannot
|
||||
@@ -285,7 +312,14 @@ function traverseOptionalBlock(
|
||||
maybeTest.instructions.at(-1)!.lvalue.identifier.id,
|
||||
{
|
||||
reason: '[OptionalChainDeps] Unexpected test expression',
|
||||
loc: maybeTest.terminal.loc,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: maybeTest.terminal.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
baseObject = {
|
||||
@@ -374,7 +408,14 @@ function traverseOptionalBlock(
|
||||
reason:
|
||||
'[OptionalChainDeps] Unexpected instructions an inner optional block. ' +
|
||||
'This indicates that the compiler may be incorrectly concatenating two unrelated optional chains',
|
||||
loc: optional.terminal.loc,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: optional.terminal.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
const matchConsequentResult = matchOptionalTestBlock(test, context.blocks);
|
||||
@@ -387,7 +428,13 @@ function traverseOptionalBlock(
|
||||
{
|
||||
reason: '[OptionalChainDeps] Unexpected optional goto-fallthrough',
|
||||
description: `${matchConsequentResult.consequentGoto} != ${optional.terminal.fallthrough}`,
|
||||
loc: optional.terminal.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: optional.terminal.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
const load = {
|
||||
|
||||
@@ -24,7 +24,14 @@ export function computeUnconditionalBlocks(fn: HIRFunction): Set<BlockId> {
|
||||
CompilerError.invariant(!unconditionalBlocks.has(current), {
|
||||
reason:
|
||||
'Internal error: non-terminating loop in ComputeUnconditionalBlocks',
|
||||
loc: null,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
unconditionalBlocks.add(current);
|
||||
|
||||
@@ -54,7 +54,14 @@ export class ReactiveScopeDependencyTreeHIR {
|
||||
prevAccessType == null || prevAccessType === accessType,
|
||||
{
|
||||
reason: 'Conflicting access types',
|
||||
loc: GeneratedSource,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: GeneratedSource,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
let nextNode = currNode.properties.get(path[i].property);
|
||||
@@ -90,7 +97,13 @@ export class ReactiveScopeDependencyTreeHIR {
|
||||
CompilerError.invariant(reactive === rootNode.reactive, {
|
||||
reason: '[DeriveMinimalDependenciesHIR] Conflicting reactive root flag',
|
||||
description: `Identifier ${printIdentifier(identifier)}`,
|
||||
loc: GeneratedSource,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: GeneratedSource,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
return rootNode;
|
||||
|
||||
@@ -89,7 +89,13 @@ export class Dominator<T> {
|
||||
CompilerError.invariant(dominator !== undefined, {
|
||||
reason: 'Unknown node',
|
||||
description: null,
|
||||
loc: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
return dominator === id ? null : dominator;
|
||||
@@ -130,7 +136,13 @@ export class PostDominator<T> {
|
||||
CompilerError.invariant(dominator !== undefined, {
|
||||
reason: 'Unknown node',
|
||||
description: null,
|
||||
loc: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
return dominator === id ? null : dominator;
|
||||
@@ -175,7 +187,13 @@ function computeImmediateDominators<T>(graph: Graph<T>): Map<T, T> {
|
||||
CompilerError.invariant(newIdom !== null, {
|
||||
reason: `At least one predecessor must have been visited for block ${id}`,
|
||||
description: null,
|
||||
loc: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
|
||||
|
||||
@@ -261,6 +261,8 @@ export const EnvironmentConfigSchema = z.object({
|
||||
|
||||
enableFire: z.boolean().default(false),
|
||||
|
||||
enableNameAnonymousFunctions: z.boolean().default(false),
|
||||
|
||||
/**
|
||||
* Enables inference and auto-insertion of effect dependencies. Takes in an array of
|
||||
* configurable module and import pairs to allow for user-land experimentation. For example,
|
||||
@@ -750,7 +752,13 @@ export class Environment {
|
||||
CompilerError.invariant(!this.#globals.has(hookName), {
|
||||
reason: `[Globals] Found existing definition in global registry for custom hook ${hookName}`,
|
||||
description: null,
|
||||
loc: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
this.#globals.set(
|
||||
@@ -783,7 +791,14 @@ export class Environment {
|
||||
CompilerError.invariant(code != null, {
|
||||
reason:
|
||||
'Expected Environment to be initialized with source code when a Flow type provider is specified',
|
||||
loc: null,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
this.#flowTypeEnvironment.init(this, code);
|
||||
} else {
|
||||
@@ -794,7 +809,14 @@ export class Environment {
|
||||
get typeContext(): FlowTypeEnv {
|
||||
CompilerError.invariant(this.#flowTypeEnvironment != null, {
|
||||
reason: 'Flow type environment not initialized',
|
||||
loc: null,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
return this.#flowTypeEnvironment;
|
||||
}
|
||||
@@ -1044,7 +1066,13 @@ export class Environment {
|
||||
CompilerError.invariant(shape !== undefined, {
|
||||
reason: `[HIR] Forget internal error: cannot resolve shape ${shapeId}`,
|
||||
description: null,
|
||||
loc: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
return shape.properties.get('*') ?? null;
|
||||
@@ -1069,7 +1097,13 @@ export class Environment {
|
||||
CompilerError.invariant(shape !== undefined, {
|
||||
reason: `[HIR] Forget internal error: cannot resolve shape ${shapeId}`,
|
||||
description: null,
|
||||
loc: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
if (typeof property === 'string') {
|
||||
@@ -1094,7 +1128,13 @@ export class Environment {
|
||||
CompilerError.invariant(shape !== undefined, {
|
||||
reason: `[HIR] Forget internal error: cannot resolve shape ${shapeId}`,
|
||||
description: null,
|
||||
loc: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
return shape.functionType;
|
||||
|
||||
@@ -184,7 +184,13 @@ function handleAssignment(
|
||||
CompilerError.invariant(valuePath.isLVal(), {
|
||||
reason: `[FindContextIdentifiers] Expected object property value to be an LVal, got: ${valuePath.type}`,
|
||||
description: null,
|
||||
loc: valuePath.node.loc ?? GeneratedSource,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: valuePath.node.loc ?? GeneratedSource,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
handleAssignment(currentFn, identifiers, valuePath);
|
||||
@@ -192,7 +198,13 @@ function handleAssignment(
|
||||
CompilerError.invariant(property.isRestElement(), {
|
||||
reason: `[FindContextIdentifiers] Invalid assumptions for babel types.`,
|
||||
description: null,
|
||||
loc: property.node.loc ?? GeneratedSource,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: property.node.loc ?? GeneratedSource,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
handleAssignment(currentFn, identifiers, property);
|
||||
|
||||
@@ -7,7 +7,11 @@
|
||||
|
||||
import {BindingKind} from '@babel/traverse';
|
||||
import * as t from '@babel/types';
|
||||
import {CompilerError} from '../CompilerError';
|
||||
import {
|
||||
CompilerDiagnostic,
|
||||
CompilerError,
|
||||
ErrorCategory,
|
||||
} from '../CompilerError';
|
||||
import {assertExhaustive} from '../Utils/utils';
|
||||
import {Environment, ReactFunctionType} from './Environment';
|
||||
import type {HookKind} from './ObjectShape';
|
||||
@@ -15,6 +19,7 @@ import {Type, makeType} from './Types';
|
||||
import {z} from 'zod';
|
||||
import type {AliasingEffect} from '../Inference/AliasingEffects';
|
||||
import {isReservedWord} from '../Utils/Keyword';
|
||||
import {Err, Ok, Result} from '../Utils/Result';
|
||||
|
||||
/*
|
||||
* *******************************************************************************************
|
||||
@@ -53,7 +58,8 @@ export type SourceLocation = t.SourceLocation | typeof GeneratedSource;
|
||||
*/
|
||||
export type ReactiveFunction = {
|
||||
loc: SourceLocation;
|
||||
id: string | null;
|
||||
id: ValidIdentifierName | null;
|
||||
nameHint: string | null;
|
||||
params: Array<Place | SpreadPattern>;
|
||||
generator: boolean;
|
||||
async: boolean;
|
||||
@@ -275,7 +281,8 @@ export type ReactiveTryTerminal = {
|
||||
// A function lowered to HIR form, ie where its body is lowered to an HIR control-flow graph
|
||||
export type HIRFunction = {
|
||||
loc: SourceLocation;
|
||||
id: string | null;
|
||||
id: ValidIdentifierName | null;
|
||||
nameHint: string | null;
|
||||
fnType: ReactFunctionType;
|
||||
env: Environment;
|
||||
params: Array<Place | SpreadPattern>;
|
||||
@@ -1123,7 +1130,8 @@ export type JsxAttribute =
|
||||
|
||||
export type FunctionExpression = {
|
||||
kind: 'FunctionExpression';
|
||||
name: string | null;
|
||||
name: ValidIdentifierName | null;
|
||||
nameHint: string | null;
|
||||
loweredFunc: LoweredFunction;
|
||||
type:
|
||||
| 'ArrowFunctionExpression'
|
||||
@@ -1298,31 +1306,52 @@ export function forkTemporaryIdentifier(
|
||||
};
|
||||
}
|
||||
|
||||
export function validateIdentifierName(
|
||||
name: string,
|
||||
): Result<ValidatedIdentifier, CompilerError> {
|
||||
if (isReservedWord(name)) {
|
||||
const error = new CompilerError();
|
||||
error.pushDiagnostic(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.Syntax,
|
||||
reason: 'Expected a non-reserved identifier name',
|
||||
description: `\`${name}\` is a reserved word in JavaScript and cannot be used as an identifier name`,
|
||||
suggestions: null,
|
||||
}).withDetails({
|
||||
kind: 'error',
|
||||
loc: GeneratedSource,
|
||||
message: 'reserved word',
|
||||
}),
|
||||
);
|
||||
return Err(error);
|
||||
} else if (!t.isValidIdentifier(name)) {
|
||||
const error = new CompilerError();
|
||||
error.pushDiagnostic(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.Syntax,
|
||||
reason: `Expected a valid identifier name`,
|
||||
description: `\`${name}\` is not a valid JavaScript identifier`,
|
||||
suggestions: null,
|
||||
}).withDetails({
|
||||
kind: 'error',
|
||||
loc: GeneratedSource,
|
||||
message: 'reserved word',
|
||||
}),
|
||||
);
|
||||
}
|
||||
return Ok({
|
||||
kind: 'named',
|
||||
value: name as ValidIdentifierName,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a valid identifier name. This should *not* be used for synthesizing
|
||||
* identifier names: only call this method for identifier names that appear in the
|
||||
* original source code.
|
||||
*/
|
||||
export function makeIdentifierName(name: string): ValidatedIdentifier {
|
||||
if (isReservedWord(name)) {
|
||||
CompilerError.throwInvalidJS({
|
||||
reason: 'Expected a non-reserved identifier name',
|
||||
loc: GeneratedSource,
|
||||
description: `\`${name}\` is a reserved word in JavaScript and cannot be used as an identifier name`,
|
||||
suggestions: null,
|
||||
});
|
||||
} else {
|
||||
CompilerError.invariant(t.isValidIdentifier(name), {
|
||||
reason: `Expected a valid identifier name`,
|
||||
loc: GeneratedSource,
|
||||
description: `\`${name}\` is not a valid JavaScript identifier`,
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
return {
|
||||
kind: 'named',
|
||||
value: name as ValidIdentifierName,
|
||||
};
|
||||
return validateIdentifierName(name).unwrap();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1334,8 +1363,14 @@ export function makeIdentifierName(name: string): ValidatedIdentifier {
|
||||
export function promoteTemporary(identifier: Identifier): void {
|
||||
CompilerError.invariant(identifier.name === null, {
|
||||
reason: `Expected a temporary (unnamed) identifier`,
|
||||
loc: GeneratedSource,
|
||||
description: `Identifier already has a name, \`${identifier.name}\``,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: GeneratedSource,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
identifier.name = {
|
||||
@@ -1358,8 +1393,14 @@ export function isPromotedTemporary(name: string): boolean {
|
||||
export function promoteTemporaryJsxTag(identifier: Identifier): void {
|
||||
CompilerError.invariant(identifier.name === null, {
|
||||
reason: `Expected a temporary (unnamed) identifier`,
|
||||
loc: GeneratedSource,
|
||||
description: `Identifier already has a name, \`${identifier.name}\``,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: GeneratedSource,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
identifier.name = {
|
||||
@@ -1527,7 +1568,13 @@ export function isMutableEffect(
|
||||
CompilerError.invariant(false, {
|
||||
reason: 'Unexpected unknown effect',
|
||||
description: null,
|
||||
loc: location,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: location,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
@@ -1660,7 +1707,13 @@ export function makeBlockId(id: number): BlockId {
|
||||
CompilerError.invariant(id >= 0 && Number.isInteger(id), {
|
||||
reason: 'Expected block id to be a non-negative integer',
|
||||
description: null,
|
||||
loc: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
return id as BlockId;
|
||||
@@ -1677,7 +1730,13 @@ export function makeScopeId(id: number): ScopeId {
|
||||
CompilerError.invariant(id >= 0 && Number.isInteger(id), {
|
||||
reason: 'Expected block id to be a non-negative integer',
|
||||
description: null,
|
||||
loc: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
return id as ScopeId;
|
||||
@@ -1694,7 +1753,13 @@ export function makeIdentifierId(id: number): IdentifierId {
|
||||
CompilerError.invariant(id >= 0 && Number.isInteger(id), {
|
||||
reason: 'Expected identifier id to be a non-negative integer',
|
||||
description: null,
|
||||
loc: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
return id as IdentifierId;
|
||||
@@ -1711,7 +1776,13 @@ export function makeDeclarationId(id: number): DeclarationId {
|
||||
CompilerError.invariant(id >= 0 && Number.isInteger(id), {
|
||||
reason: 'Expected declaration id to be a non-negative integer',
|
||||
description: null,
|
||||
loc: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
return id as DeclarationId;
|
||||
@@ -1728,7 +1799,13 @@ export function makeInstructionId(id: number): InstructionId {
|
||||
CompilerError.invariant(id >= 0 && Number.isInteger(id), {
|
||||
reason: 'Expected instruction id to be a non-negative integer',
|
||||
description: null,
|
||||
loc: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
return id as InstructionId;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
import {Binding, NodePath} from '@babel/traverse';
|
||||
import * as t from '@babel/types';
|
||||
import {CompilerError, ErrorCategory, ErrorSeverity} from '../CompilerError';
|
||||
import {CompilerError, ErrorCategory} from '../CompilerError';
|
||||
import {Environment} from './Environment';
|
||||
import {
|
||||
BasicBlock,
|
||||
@@ -309,8 +309,7 @@ export default class HIRBuilder {
|
||||
resolveBinding(node: t.Identifier): Identifier {
|
||||
if (node.name === 'fbt') {
|
||||
CompilerError.throwDiagnostic({
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.FBT,
|
||||
category: ErrorCategory.Todo,
|
||||
reason: 'Support local variables named `fbt`',
|
||||
description:
|
||||
'Local variables named `fbt` may conflict with the fbt plugin and are not yet supported',
|
||||
@@ -325,7 +324,6 @@ export default class HIRBuilder {
|
||||
}
|
||||
if (node.name === 'this') {
|
||||
CompilerError.throwDiagnostic({
|
||||
severity: ErrorSeverity.UnsupportedJS,
|
||||
category: ErrorCategory.UnsupportedSyntax,
|
||||
reason: '`this` is not supported syntax',
|
||||
description:
|
||||
@@ -509,7 +507,13 @@ export default class HIRBuilder {
|
||||
{
|
||||
reason: 'Mismatched label',
|
||||
description: null,
|
||||
loc: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
},
|
||||
);
|
||||
@@ -532,7 +536,13 @@ export default class HIRBuilder {
|
||||
{
|
||||
reason: 'Mismatched label',
|
||||
description: null,
|
||||
loc: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
},
|
||||
);
|
||||
@@ -568,7 +578,13 @@ export default class HIRBuilder {
|
||||
{
|
||||
reason: 'Mismatched loops',
|
||||
description: null,
|
||||
loc: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
},
|
||||
);
|
||||
@@ -593,7 +609,13 @@ export default class HIRBuilder {
|
||||
CompilerError.invariant(false, {
|
||||
reason: 'Expected a loop or switch to be in scope',
|
||||
description: null,
|
||||
loc: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
@@ -614,7 +636,13 @@ export default class HIRBuilder {
|
||||
CompilerError.invariant(false, {
|
||||
reason: 'Continue may only refer to a labeled loop',
|
||||
description: null,
|
||||
loc: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
@@ -622,7 +650,13 @@ export default class HIRBuilder {
|
||||
CompilerError.invariant(false, {
|
||||
reason: 'Expected a loop to be in scope',
|
||||
description: null,
|
||||
loc: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
@@ -645,7 +679,13 @@ function _shrink(func: HIR): void {
|
||||
CompilerError.invariant(block != null, {
|
||||
reason: `expected block ${blockId} to exist`,
|
||||
description: null,
|
||||
loc: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
target = getTargetIfIndirection(block);
|
||||
@@ -777,7 +817,13 @@ function getReversePostorderedBlocks(func: HIR): HIR['blocks'] {
|
||||
CompilerError.invariant(block != null, {
|
||||
reason: '[HIRBuilder] Unexpected null block',
|
||||
description: `expected block ${blockId} to exist`,
|
||||
loc: GeneratedSource,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: GeneratedSource,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
const successors = [...eachTerminalSuccessor(block.terminal)].reverse();
|
||||
const fallthrough = terminalFallthrough(block.terminal);
|
||||
@@ -833,7 +879,13 @@ export function markInstructionIds(func: HIR): void {
|
||||
CompilerError.invariant(!visited.has(instr), {
|
||||
reason: `${printInstruction(instr)} already visited!`,
|
||||
description: null,
|
||||
loc: instr.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: instr.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
visited.add(instr);
|
||||
@@ -856,7 +908,13 @@ export function markPredecessors(func: HIR): void {
|
||||
CompilerError.invariant(block != null, {
|
||||
reason: 'unexpected missing block',
|
||||
description: `block ${blockId}`,
|
||||
loc: GeneratedSource,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: GeneratedSource,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
if (prevBlock) {
|
||||
block.preds.add(prevBlock.id);
|
||||
|
||||
@@ -61,7 +61,13 @@ export function mergeConsecutiveBlocks(fn: HIRFunction): void {
|
||||
CompilerError.invariant(predecessor !== undefined, {
|
||||
reason: `Expected predecessor ${predecessorId} to exist`,
|
||||
description: null,
|
||||
loc: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
if (predecessor.terminal.kind !== 'goto' || predecessor.kind !== 'block') {
|
||||
@@ -77,7 +83,13 @@ export function mergeConsecutiveBlocks(fn: HIRFunction): void {
|
||||
CompilerError.invariant(phi.operands.size === 1, {
|
||||
reason: `Found a block with a single predecessor but where a phi has multiple (${phi.operands.size}) operands`,
|
||||
description: null,
|
||||
loc: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
const operand = Array.from(phi.operands.values())[0]!;
|
||||
|
||||
@@ -119,7 +119,13 @@ function parseAliasingSignatureConfig(
|
||||
CompilerError.invariant(!lifetimes.has(temp), {
|
||||
reason: `Invalid type configuration for module`,
|
||||
description: `Expected aliasing signature to have unique names for receiver, params, rest, returns, and temporaries in module '${moduleName}'`,
|
||||
loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
const place = signatureArgument(lifetimes.size);
|
||||
lifetimes.set(temp, place);
|
||||
@@ -130,7 +136,13 @@ function parseAliasingSignatureConfig(
|
||||
CompilerError.invariant(place != null, {
|
||||
reason: `Invalid type configuration for module`,
|
||||
description: `Expected aliasing signature effects to reference known names from receiver/params/rest/returns/temporaries, but '${temp}' is not a known name in '${moduleName}'`,
|
||||
loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
return place;
|
||||
}
|
||||
@@ -265,7 +277,13 @@ function addShape(
|
||||
CompilerError.invariant(!registry.has(id), {
|
||||
reason: `[ObjectShape] Could not add shape to registry: name ${id} already exists.`,
|
||||
description: null,
|
||||
loc: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
registry.set(id, shape);
|
||||
|
||||
@@ -56,6 +56,9 @@ export function printFunction(fn: HIRFunction): string {
|
||||
} else {
|
||||
definition += '<<anonymous>>';
|
||||
}
|
||||
if (fn.nameHint != null) {
|
||||
definition += ` ${fn.nameHint}`;
|
||||
}
|
||||
if (fn.params.length !== 0) {
|
||||
definition +=
|
||||
'(' +
|
||||
@@ -596,7 +599,13 @@ export function printInstructionValue(instrValue: ReactiveValue): string {
|
||||
{
|
||||
reason: 'Bad assumption about quasi length.',
|
||||
description: null,
|
||||
loc: instrValue.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: instrValue.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
},
|
||||
);
|
||||
@@ -865,8 +874,15 @@ export function printManualMemoDependency(
|
||||
} else {
|
||||
CompilerError.invariant(val.root.value.identifier.name?.kind === 'named', {
|
||||
reason: 'DepsValidation: expected named local variable in depslist',
|
||||
description: null,
|
||||
suggestions: null,
|
||||
loc: val.root.value.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: val.root.value.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
rootStr = nameOnly
|
||||
? val.root.value.identifier.name.value
|
||||
|
||||
@@ -86,7 +86,14 @@ export function propagateScopeDependenciesHIR(fn: HIRFunction): void {
|
||||
const hoistables = hoistablePropertyLoads.get(scope.id);
|
||||
CompilerError.invariant(hoistables != null, {
|
||||
reason: '[PropagateScopeDependencies] Scope not found in tracked blocks',
|
||||
loc: GeneratedSource,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: GeneratedSource,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
/**
|
||||
* Step 2: Calculate hoistable dependencies.
|
||||
@@ -428,7 +435,14 @@ export class DependencyCollectionContext {
|
||||
const scopedDependencies = this.#dependencies.value;
|
||||
CompilerError.invariant(scopedDependencies != null, {
|
||||
reason: '[PropagateScopeDeps]: Unexpected scope mismatch',
|
||||
loc: scope.loc,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: scope.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// Restore context of previous scope
|
||||
|
||||
@@ -53,7 +53,14 @@ export function pruneUnusedLabelsHIR(fn: HIRFunction): void {
|
||||
next.phis.size === 0 && fallthrough.phis.size === 0,
|
||||
{
|
||||
reason: 'Unexpected phis when merging label blocks',
|
||||
loc: label.terminal.loc,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: label.terminal.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
|
||||
@@ -64,7 +71,14 @@ export function pruneUnusedLabelsHIR(fn: HIRFunction): void {
|
||||
fallthrough.preds.has(nextId),
|
||||
{
|
||||
reason: 'Unexpected block predecessors when merging label blocks',
|
||||
loc: label.terminal.loc,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: label.terminal.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -202,8 +202,14 @@ function writeOptionalDependency(
|
||||
CompilerError.invariant(firstOptional !== -1, {
|
||||
reason:
|
||||
'[ScopeDependencyUtils] Internal invariant broken: expected optional path',
|
||||
loc: dep.identifier.loc,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: dep.identifier.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
if (firstOptional === dep.path.length - 1) {
|
||||
@@ -239,7 +245,13 @@ function writeOptionalDependency(
|
||||
CompilerError.invariant(testIdentifier !== null, {
|
||||
reason: 'Satisfy type checker',
|
||||
description: null,
|
||||
loc: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
|
||||
|
||||
@@ -87,7 +87,13 @@ export function makeTypeId(id: number): TypeId {
|
||||
CompilerError.invariant(id >= 0 && Number.isInteger(id), {
|
||||
reason: 'Expected instruction id to be a non-negative integer',
|
||||
description: null,
|
||||
loc: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
return id as TypeId;
|
||||
|
||||
@@ -1233,7 +1233,14 @@ export class ScopeBlockTraversal {
|
||||
CompilerError.invariant(blockInfo.scope.id === top, {
|
||||
reason:
|
||||
'Expected traversed block fallthrough to match top-most active scope',
|
||||
loc: block.instructions[0]?.loc ?? block.terminal.id,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: block.instructions[0]?.loc ?? block.terminal.id,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
this.#activeScopes.pop();
|
||||
}
|
||||
@@ -1247,7 +1254,14 @@ export class ScopeBlockTraversal {
|
||||
!this.blockInfos.has(block.terminal.fallthrough),
|
||||
{
|
||||
reason: 'Expected unique scope blocks and fallthroughs',
|
||||
loc: block.terminal.loc,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: block.terminal.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
this.blockInfos.set(block.terminal.block, {
|
||||
|
||||
@@ -78,7 +78,14 @@ function lowerWithMutationAliasing(fn: HIRFunction): void {
|
||||
case 'Apply': {
|
||||
CompilerError.invariant(false, {
|
||||
reason: `[AnalyzeFunctions] Expected Apply effects to be replaced with more precise effects`,
|
||||
loc: effect.function.loc,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: effect.function.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
case 'Mutate':
|
||||
|
||||
@@ -5,12 +5,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {
|
||||
CompilerDiagnostic,
|
||||
CompilerError,
|
||||
ErrorSeverity,
|
||||
SourceLocation,
|
||||
} from '..';
|
||||
import {CompilerDiagnostic, CompilerError, SourceLocation} from '..';
|
||||
import {ErrorCategory} from '../CompilerError';
|
||||
import {
|
||||
CallExpression,
|
||||
@@ -302,11 +297,10 @@ function extractManualMemoizationArgs(
|
||||
errors.pushDiagnostic(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.UseMemo,
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
reason: `Expected a callback function to be passed to ${kind}`,
|
||||
description: `Expected a callback function to be passed to ${kind}`,
|
||||
suggestions: null,
|
||||
}).withDetail({
|
||||
}).withDetails({
|
||||
kind: 'error',
|
||||
loc: instr.value.loc,
|
||||
message: `Expected a callback function to be passed to ${kind}`,
|
||||
@@ -318,11 +312,10 @@ function extractManualMemoizationArgs(
|
||||
errors.pushDiagnostic(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.UseMemo,
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
reason: `Unexpected spread argument to ${kind}`,
|
||||
description: `Unexpected spread argument to ${kind}`,
|
||||
suggestions: null,
|
||||
}).withDetail({
|
||||
}).withDetails({
|
||||
kind: 'error',
|
||||
loc: instr.value.loc,
|
||||
message: `Unexpected spread argument to ${kind}`,
|
||||
@@ -339,11 +332,10 @@ function extractManualMemoizationArgs(
|
||||
errors.pushDiagnostic(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.UseMemo,
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
reason: `Expected the dependency list for ${kind} to be an array literal`,
|
||||
description: `Expected the dependency list for ${kind} to be an array literal`,
|
||||
suggestions: null,
|
||||
}).withDetail({
|
||||
}).withDetails({
|
||||
kind: 'error',
|
||||
loc: depsListPlace.loc,
|
||||
message: `Expected the dependency list for ${kind} to be an array literal`,
|
||||
@@ -358,11 +350,10 @@ function extractManualMemoizationArgs(
|
||||
errors.pushDiagnostic(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.UseMemo,
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
reason: `Expected the dependency list to be an array of simple expressions (e.g. \`x\`, \`x.y.z\`, \`x?.y?.z\`)`,
|
||||
description: `Expected the dependency list to be an array of simple expressions (e.g. \`x\`, \`x.y.z\`, \`x?.y?.z\`)`,
|
||||
suggestions: null,
|
||||
}).withDetail({
|
||||
}).withDetails({
|
||||
kind: 'error',
|
||||
loc: dep.loc,
|
||||
message: `Expected the dependency list to be an array of simple expressions (e.g. \`x\`, \`x.y.z\`, \`x?.y?.z\`)`,
|
||||
@@ -463,16 +454,15 @@ export function dropManualMemoization(
|
||||
if (!hasNonVoidReturn(funcToCheck.loweredFunc.func)) {
|
||||
errors.pushDiagnostic(
|
||||
CompilerDiagnostic.create({
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
category: ErrorCategory.UseMemo,
|
||||
reason: 'useMemo() callbacks must return a value',
|
||||
description: `This ${
|
||||
manualMemo.loadInstr.value.kind === 'PropertyLoad'
|
||||
? 'React.useMemo'
|
||||
: 'useMemo'
|
||||
} callback doesn't return a value. useMemo is for computing and caching values, not for arbitrary side effects.`,
|
||||
} callback doesn't return a value. useMemo is for computing and caching values, not for arbitrary side effects`,
|
||||
suggestions: null,
|
||||
}).withDetail({
|
||||
}).withDetails({
|
||||
kind: 'error',
|
||||
loc: instr.value.loc,
|
||||
message: 'useMemo() callbacks must return a value',
|
||||
@@ -505,11 +495,10 @@ export function dropManualMemoization(
|
||||
errors.pushDiagnostic(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.UseMemo,
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
reason: `Expected the first argument to be an inline function expression`,
|
||||
description: `Expected the first argument to be an inline function expression`,
|
||||
suggestions: [],
|
||||
}).withDetail({
|
||||
}).withDetails({
|
||||
kind: 'error',
|
||||
loc: fnPlace.loc,
|
||||
message: `Expected the first argument to be an inline function expression`,
|
||||
@@ -624,7 +613,14 @@ function findOptionalPlaces(fn: HIRFunction): Set<IdentifierId> {
|
||||
default: {
|
||||
CompilerError.invariant(false, {
|
||||
reason: `Unexpected terminal in optional`,
|
||||
loc: terminal.loc,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: terminal.loc,
|
||||
message: `Unexpected ${terminal.kind} in optional`,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -438,7 +438,14 @@ function rewriteSplices(
|
||||
{
|
||||
reason:
|
||||
'[InferEffectDependencies] Internal invariant broken: expected block instructions to be sorted',
|
||||
loc: originalInstrs[cursor].loc,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: originalInstrs[cursor].loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
currBlock.instructions.push(originalInstrs[cursor]);
|
||||
@@ -447,7 +454,14 @@ function rewriteSplices(
|
||||
CompilerError.invariant(originalInstrs[cursor].id === rewrite.location, {
|
||||
reason:
|
||||
'[InferEffectDependencies] Internal invariant broken: splice location not found',
|
||||
loc: originalInstrs[cursor].loc,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: originalInstrs[cursor].loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
if (rewrite.kind === 'instr') {
|
||||
@@ -467,7 +481,14 @@ function rewriteSplices(
|
||||
{
|
||||
reason:
|
||||
'[InferEffectDependencies] Internal invariant broken: expected entry block to have a fallthrough',
|
||||
loc: entryBlock.terminal.loc,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: entryBlock.terminal.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
const originalTerminal = currBlock.terminal;
|
||||
@@ -566,7 +587,14 @@ function inferMinimalDependencies(
|
||||
CompilerError.invariant(hoistableToFnEntry != null, {
|
||||
reason:
|
||||
'[InferEffectDependencies] Internal invariant broken: missing entry block',
|
||||
loc: fnInstr.loc,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: fnInstr.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const dependencies = inferDependencies(
|
||||
@@ -622,7 +650,14 @@ function inferDependencies(
|
||||
CompilerError.invariant(resultUnfiltered != null, {
|
||||
reason:
|
||||
'[InferEffectDependencies] Internal invariant broken: missing scope dependencies',
|
||||
loc: fn.loc,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: fn.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const fnContext = new Set(fn.context.map(dep => dep.identifier.id));
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
CompilerDiagnostic,
|
||||
CompilerError,
|
||||
Effect,
|
||||
ErrorSeverity,
|
||||
SourceLocation,
|
||||
ValueKind,
|
||||
} from '..';
|
||||
@@ -59,7 +58,6 @@ import {
|
||||
printInstruction,
|
||||
printInstructionValue,
|
||||
printPlace,
|
||||
printSourceLocation,
|
||||
} from '../HIR/PrintHIR';
|
||||
import {FunctionSignature} from '../HIR/ObjectShape';
|
||||
import prettyFormat from 'pretty-format';
|
||||
@@ -136,7 +134,13 @@ export function inferMutationAliasingEffects(
|
||||
reason:
|
||||
'Expected React component to have not more than two parameters: one for props and for ref',
|
||||
description: null,
|
||||
loc: fn.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: fn.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
const [props, ref] = fn.params;
|
||||
@@ -203,7 +207,13 @@ export function inferMutationAliasingEffects(
|
||||
CompilerError.invariant(false, {
|
||||
reason: `[InferMutationAliasingEffects] Potential infinite loop`,
|
||||
description: `A value, temporary place, or effect was not cached properly`,
|
||||
loc: fn.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: fn.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
for (const [blockId, block] of fn.body.blocks) {
|
||||
@@ -358,7 +368,14 @@ function inferBlock(
|
||||
CompilerError.invariant(state.kind(handlerParam) != null, {
|
||||
reason:
|
||||
'Expected catch binding to be intialized with a DeclareLocal Catch instruction',
|
||||
loc: terminal.loc,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: terminal.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
const effects: Array<AliasingEffect> = [];
|
||||
for (const instr of block.instructions) {
|
||||
@@ -455,10 +472,9 @@ function applySignature(
|
||||
: 'value';
|
||||
const diagnostic = CompilerDiagnostic.create({
|
||||
category: ErrorCategory.Immutability,
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
reason: 'This value cannot be modified',
|
||||
description: `${reason}.`,
|
||||
}).withDetail({
|
||||
description: reason,
|
||||
}).withDetails({
|
||||
kind: 'error',
|
||||
loc: effect.value.loc,
|
||||
message: `${variable} cannot be modified`,
|
||||
@@ -467,7 +483,7 @@ function applySignature(
|
||||
effect.kind === 'Mutate' &&
|
||||
effect.reason?.kind === 'AssignCurrentProperty'
|
||||
) {
|
||||
diagnostic.withDetail({
|
||||
diagnostic.withDetails({
|
||||
kind: 'hint',
|
||||
message: `Hint: If this value is a Ref (value returned by \`useRef()\`), rename the variable to end in "Ref".`,
|
||||
});
|
||||
@@ -509,7 +525,14 @@ function applySignature(
|
||||
) {
|
||||
CompilerError.invariant(false, {
|
||||
reason: `Expected instruction lvalue to be initialized`,
|
||||
loc: instruction.loc,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: instruction.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
return effects.length !== 0 ? effects : null;
|
||||
@@ -538,7 +561,13 @@ function applyEffect(
|
||||
CompilerError.invariant(!initialized.has(effect.into.identifier.id), {
|
||||
reason: `Cannot re-initialize variable within an instruction`,
|
||||
description: `Re-initialized ${printPlace(effect.into)} in ${printAliasingEffect(effect)}`,
|
||||
loc: effect.into.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: effect.into.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
initialized.add(effect.into.identifier.id);
|
||||
|
||||
@@ -577,7 +606,13 @@ function applyEffect(
|
||||
CompilerError.invariant(!initialized.has(effect.into.identifier.id), {
|
||||
reason: `Cannot re-initialize variable within an instruction`,
|
||||
description: `Re-initialized ${printPlace(effect.into)} in ${printAliasingEffect(effect)}`,
|
||||
loc: effect.into.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: effect.into.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
initialized.add(effect.into.identifier.id);
|
||||
|
||||
@@ -637,7 +672,13 @@ function applyEffect(
|
||||
CompilerError.invariant(!initialized.has(effect.into.identifier.id), {
|
||||
reason: `Cannot re-initialize variable within an instruction`,
|
||||
description: `Re-initialized ${printPlace(effect.into)} in ${printAliasingEffect(effect)}`,
|
||||
loc: effect.into.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: effect.into.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
initialized.add(effect.into.identifier.id);
|
||||
|
||||
@@ -711,7 +752,13 @@ function applyEffect(
|
||||
{
|
||||
reason: `Expected destination value to already be initialized within this instruction for Alias effect`,
|
||||
description: `Destination ${printPlace(effect.into)} is not initialized in this instruction`,
|
||||
loc: effect.into.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: effect.into.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
/*
|
||||
@@ -770,7 +817,13 @@ function applyEffect(
|
||||
CompilerError.invariant(!initialized.has(effect.into.identifier.id), {
|
||||
reason: `Cannot re-initialize variable within an instruction`,
|
||||
description: `Re-initialized ${printPlace(effect.into)} in ${printAliasingEffect(effect)}`,
|
||||
loc: effect.into.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: effect.into.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
initialized.add(effect.into.identifier.id);
|
||||
|
||||
@@ -1040,18 +1093,17 @@ function applyEffect(
|
||||
);
|
||||
const diagnostic = CompilerDiagnostic.create({
|
||||
category: ErrorCategory.Immutability,
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
reason: 'Cannot access variable before it is declared',
|
||||
description: `${variable ?? 'This variable'} is accessed before it is declared, which prevents the earlier access from updating when this value changes over time.`,
|
||||
description: `${variable ?? 'This variable'} is accessed before it is declared, which prevents the earlier access from updating when this value changes over time`,
|
||||
});
|
||||
if (hoistedAccess != null && hoistedAccess.loc != effect.value.loc) {
|
||||
diagnostic.withDetail({
|
||||
diagnostic.withDetails({
|
||||
kind: 'error',
|
||||
loc: hoistedAccess.loc,
|
||||
message: `${variable ?? 'variable'} accessed before it is declared`,
|
||||
});
|
||||
}
|
||||
diagnostic.withDetail({
|
||||
diagnostic.withDetails({
|
||||
kind: 'error',
|
||||
loc: effect.value.loc,
|
||||
message: `${variable ?? 'variable'} is declared here`,
|
||||
@@ -1080,10 +1132,9 @@ function applyEffect(
|
||||
: 'value';
|
||||
const diagnostic = CompilerDiagnostic.create({
|
||||
category: ErrorCategory.Immutability,
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
reason: 'This value cannot be modified',
|
||||
description: `${reason}.`,
|
||||
}).withDetail({
|
||||
description: reason,
|
||||
}).withDetails({
|
||||
kind: 'error',
|
||||
loc: effect.value.loc,
|
||||
message: `${variable} cannot be modified`,
|
||||
@@ -1092,7 +1143,7 @@ function applyEffect(
|
||||
effect.kind === 'Mutate' &&
|
||||
effect.reason?.kind === 'AssignCurrentProperty'
|
||||
) {
|
||||
diagnostic.withDetail({
|
||||
diagnostic.withDetails({
|
||||
kind: 'hint',
|
||||
message: `Hint: If this value is a Ref (value returned by \`useRef()\`), rename the variable to end in "Ref".`,
|
||||
});
|
||||
@@ -1173,7 +1224,13 @@ class InferenceState {
|
||||
reason:
|
||||
'[InferMutationAliasingEffects] Expected all top-level identifiers to be defined as variables, not values',
|
||||
description: null,
|
||||
loc: value.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: value.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
this.#values.set(value, kind);
|
||||
@@ -1184,7 +1241,13 @@ class InferenceState {
|
||||
CompilerError.invariant(values != null, {
|
||||
reason: `[InferMutationAliasingEffects] Expected value kind to be initialized`,
|
||||
description: `${printPlace(place)}`,
|
||||
loc: place.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: place.loc,
|
||||
message: 'this is uninitialized',
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
return Array.from(values);
|
||||
@@ -1196,7 +1259,13 @@ class InferenceState {
|
||||
CompilerError.invariant(values != null, {
|
||||
reason: `[InferMutationAliasingEffects] Expected value kind to be initialized`,
|
||||
description: `${printPlace(place)}`,
|
||||
loc: place.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: place.loc,
|
||||
message: 'this is uninitialized',
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
let mergedKind: AbstractValue | null = null;
|
||||
@@ -1208,7 +1277,13 @@ class InferenceState {
|
||||
CompilerError.invariant(mergedKind !== null, {
|
||||
reason: `[InferMutationAliasingEffects] Expected at least one value`,
|
||||
description: `No value found at \`${printPlace(place)}\``,
|
||||
loc: place.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: place.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
return mergedKind;
|
||||
@@ -1220,7 +1295,13 @@ class InferenceState {
|
||||
CompilerError.invariant(values != null, {
|
||||
reason: `[InferMutationAliasingEffects] Expected value for identifier to be initialized`,
|
||||
description: `${printIdentifier(value.identifier)}`,
|
||||
loc: value.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: value.loc,
|
||||
message: 'Expected value for identifier to be initialized',
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
this.#variables.set(place.identifier.id, new Set(values));
|
||||
@@ -1231,7 +1312,13 @@ class InferenceState {
|
||||
CompilerError.invariant(values != null, {
|
||||
reason: `[InferMutationAliasingEffects] Expected value for identifier to be initialized`,
|
||||
description: `${printIdentifier(value.identifier)}`,
|
||||
loc: value.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: value.loc,
|
||||
message: 'Expected value for identifier to be initialized',
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
const prevValues = this.values(place);
|
||||
@@ -1244,11 +1331,15 @@ class InferenceState {
|
||||
// Defines (initializing or updating) a variable with a specific kind of value.
|
||||
define(place: Place, value: InstructionValue): void {
|
||||
CompilerError.invariant(this.#values.has(value), {
|
||||
reason: `[InferMutationAliasingEffects] Expected value to be initialized at '${printSourceLocation(
|
||||
value.loc,
|
||||
)}'`,
|
||||
reason: `[InferMutationAliasingEffects] Expected value to be initialized`,
|
||||
description: printInstructionValue(value),
|
||||
loc: value.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: value.loc,
|
||||
message: 'Expected value for identifier to be initialized',
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
this.#variables.set(place.identifier.id, new Set([value]));
|
||||
@@ -2056,11 +2147,10 @@ function computeSignatureForInstruction(
|
||||
place: value.value,
|
||||
error: CompilerDiagnostic.create({
|
||||
category: ErrorCategory.Globals,
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
reason:
|
||||
'Cannot reassign variables declared outside of the component/hook',
|
||||
description: `Variable ${variable} is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render)`,
|
||||
}).withDetail({
|
||||
}).withDetails({
|
||||
kind: 'error',
|
||||
loc: instr.loc,
|
||||
message: `${variable} cannot be reassigned`,
|
||||
@@ -2089,7 +2179,7 @@ function computeSignatureForInstruction(
|
||||
effects.push({
|
||||
kind: 'Freeze',
|
||||
value: operand,
|
||||
reason: ValueReason.Other,
|
||||
reason: ValueReason.HookCaptured,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2156,14 +2246,13 @@ function computeEffectsForLegacySignature(
|
||||
place: receiver,
|
||||
error: CompilerDiagnostic.create({
|
||||
category: ErrorCategory.Purity,
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
reason: 'Cannot call impure function during render',
|
||||
description:
|
||||
(signature.canonicalName != null
|
||||
? `\`${signature.canonicalName}\` is an impure function. `
|
||||
: '') +
|
||||
'Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent)',
|
||||
}).withDetail({
|
||||
}).withDetails({
|
||||
kind: 'error',
|
||||
loc,
|
||||
message: 'Cannot call impure function',
|
||||
@@ -2175,15 +2264,14 @@ function computeEffectsForLegacySignature(
|
||||
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.',
|
||||
'memoized',
|
||||
].join(''),
|
||||
}).withDetail({
|
||||
}).withDetails({
|
||||
kind: 'error',
|
||||
loc: receiver.loc,
|
||||
message: signature.knownIncompatible,
|
||||
@@ -2683,7 +2771,13 @@ export function isKnownMutableEffect(effect: Effect): boolean {
|
||||
CompilerError.invariant(false, {
|
||||
reason: 'Unexpected unknown effect',
|
||||
description: null,
|
||||
loc: GeneratedSource,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: GeneratedSource,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
@@ -2792,7 +2886,13 @@ function mergeValueKinds(a: ValueKind, b: ValueKind): ValueKind {
|
||||
{
|
||||
reason: `Unexpected value kind in mergeValues()`,
|
||||
description: `Found kinds ${a} and ${b}`,
|
||||
loc: GeneratedSource,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: GeneratedSource,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
return ValueKind.Primitive;
|
||||
|
||||
@@ -229,7 +229,14 @@ export function inferMutationAliasingRanges(
|
||||
} else {
|
||||
CompilerError.invariant(effect.kind === 'Freeze', {
|
||||
reason: `Unexpected '${effect.kind}' effect for MaybeThrow terminal`,
|
||||
loc: block.terminal.loc,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: block.terminal.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -378,7 +385,14 @@ export function inferMutationAliasingRanges(
|
||||
case 'Apply': {
|
||||
CompilerError.invariant(false, {
|
||||
reason: `[AnalyzeFunctions] Expected Apply effects to be replaced with more precise effects`,
|
||||
loc: effect.function.loc,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: effect.function.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
case 'MutateTransitive':
|
||||
@@ -525,7 +539,14 @@ export function inferMutationAliasingRanges(
|
||||
const fromNode = state.nodes.get(from.identifier);
|
||||
CompilerError.invariant(fromNode != null, {
|
||||
reason: `Expected a node to exist for all parameters and context variables`,
|
||||
loc: into.loc,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: into.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
if (fromNode.lastMutated === mutationIndex) {
|
||||
if (into.identifier.id === fn.returns.identifier.id) {
|
||||
@@ -547,7 +568,7 @@ export function inferMutationAliasingRanges(
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.hasErrors() && !isFunctionExpression) {
|
||||
if (errors.hasAnyErrors() && !isFunctionExpression) {
|
||||
return Err(errors);
|
||||
}
|
||||
return Ok(functionEffects);
|
||||
|
||||
@@ -349,7 +349,13 @@ export function inferReactivePlaces(fn: HIRFunction): void {
|
||||
CompilerError.invariant(false, {
|
||||
reason: 'Unexpected unknown effect',
|
||||
description: null,
|
||||
loc: operand.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: operand.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -191,7 +191,14 @@ function evaluatePhi(phi: Phi, constants: Constants): Constant | null {
|
||||
case 'Primitive': {
|
||||
CompilerError.invariant(value.kind === 'Primitive', {
|
||||
reason: 'value kind expected to be Primitive',
|
||||
loc: null,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
|
||||
@@ -204,7 +211,14 @@ function evaluatePhi(phi: Phi, constants: Constants): Constant | null {
|
||||
case 'LoadGlobal': {
|
||||
CompilerError.invariant(value.kind === 'LoadGlobal', {
|
||||
reason: 'value kind expected to be LoadGlobal',
|
||||
loc: null,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
|
||||
|
||||
@@ -709,7 +709,14 @@ function createPropsProperties(
|
||||
const spreadProp = jsxSpreadAttributes[0];
|
||||
CompilerError.invariant(spreadProp.kind === 'JsxSpreadAttribute', {
|
||||
reason: 'Spread prop attribute must be of kind JSXSpreadAttribute',
|
||||
loc: instr.loc,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: instr.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
propsProperty = {
|
||||
kind: 'ObjectProperty',
|
||||
|
||||
@@ -78,10 +78,17 @@ export function instructionReordering(fn: HIRFunction): void {
|
||||
}
|
||||
CompilerError.invariant(shared.size === 0, {
|
||||
reason: `InstructionReordering: expected all reorderable nodes to have been emitted`,
|
||||
loc:
|
||||
[...shared.values()]
|
||||
.map(node => node.instruction?.loc)
|
||||
.filter(loc => loc != null)[0] ?? GeneratedSource,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc:
|
||||
[...shared.values()]
|
||||
.map(node => node.instruction?.loc)
|
||||
.filter(loc => loc != null)[0] ?? GeneratedSource,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
markInstructionIds(fn.body);
|
||||
}
|
||||
@@ -302,7 +309,13 @@ function reorderBlock(
|
||||
node.reorderability === Reorderability.Reorderable,
|
||||
{
|
||||
reason: `Expected all remaining instructions to be reorderable`,
|
||||
loc: node.instruction?.loc ?? block.terminal.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: node.instruction?.loc ?? block.terminal.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
description:
|
||||
node.instruction != null
|
||||
? `Instruction [${node.instruction.id}] was not emitted yet but is not reorderable`
|
||||
|
||||
@@ -249,6 +249,7 @@ function emitSelectorFn(env: Environment, keys: Array<string>): Instruction {
|
||||
const fn: HIRFunction = {
|
||||
loc: GeneratedSource,
|
||||
id: null,
|
||||
nameHint: null,
|
||||
fnType: 'Other',
|
||||
env,
|
||||
params: [obj],
|
||||
@@ -275,6 +276,7 @@ function emitSelectorFn(env: Environment, keys: Array<string>): Instruction {
|
||||
value: {
|
||||
kind: 'FunctionExpression',
|
||||
name: null,
|
||||
nameHint: null,
|
||||
loweredFunc: {
|
||||
func: fn,
|
||||
},
|
||||
|
||||
@@ -31,7 +31,9 @@ export function outlineFunctions(
|
||||
) {
|
||||
const loweredFunc = value.loweredFunc.func;
|
||||
|
||||
const id = fn.env.generateGloballyUniqueIdentifierName(loweredFunc.id);
|
||||
const id = fn.env.generateGloballyUniqueIdentifierName(
|
||||
loweredFunc.id ?? loweredFunc.nameHint,
|
||||
);
|
||||
loweredFunc.id = id.value;
|
||||
|
||||
fn.env.outlineFunction(loweredFunc, null);
|
||||
|
||||
@@ -364,6 +364,7 @@ function emitOutlinedFn(
|
||||
const fn: HIRFunction = {
|
||||
loc: GeneratedSource,
|
||||
id: null,
|
||||
nameHint: null,
|
||||
fnType: 'Other',
|
||||
env,
|
||||
params: [propsObj],
|
||||
|
||||
@@ -52,7 +52,13 @@ export function pruneMaybeThrows(fn: HIRFunction): void {
|
||||
const mappedTerminal = terminalMapping.get(predecessor);
|
||||
CompilerError.invariant(mappedTerminal != null, {
|
||||
reason: `Expected non-existing phi operand's predecessor to have been mapped to a new terminal`,
|
||||
loc: GeneratedSource,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: GeneratedSource,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
description: `Could not find mapping for predecessor bb${predecessor} in block bb${
|
||||
block.id
|
||||
} for phi ${printPlace(phi.place)}`,
|
||||
|
||||
@@ -41,8 +41,15 @@ function findScopesToMerge(fn: HIRFunction): DisjointSet<ReactiveScope> {
|
||||
{
|
||||
reason:
|
||||
'Internal error: Expected all ObjectExpressions and ObjectMethods to have non-null scope.',
|
||||
description: null,
|
||||
suggestions: null,
|
||||
loc: GeneratedSource,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: GeneratedSource,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
mergeScopesBuilder.union([operandScope, lvalueScope]);
|
||||
|
||||
@@ -170,7 +170,14 @@ export function alignReactiveScopesToBlockScopesHIR(fn: HIRFunction): void {
|
||||
|
||||
CompilerError.invariant(!valueBlockNodes.has(fallthrough), {
|
||||
reason: 'Expect hir blocks to have unique fallthroughs',
|
||||
loc: terminal.loc,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: terminal.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
if (node != null) {
|
||||
valueBlockNodes.set(fallthrough, node);
|
||||
@@ -252,7 +259,14 @@ export function alignReactiveScopesToBlockScopesHIR(fn: HIRFunction): void {
|
||||
// Transition from block->value block, derive the outer block range
|
||||
CompilerError.invariant(fallthrough !== null, {
|
||||
reason: `Expected a fallthrough for value block`,
|
||||
loc: terminal.loc,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: terminal.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
const fallthroughBlock = fn.body.blocks.get(fallthrough)!;
|
||||
const nextId =
|
||||
|
||||
@@ -81,10 +81,16 @@ class CheckInstructionsAgainstScopesVisitor extends ReactiveFunctionVisitor<
|
||||
!this.activeScopes.has(scope.id)
|
||||
) {
|
||||
CompilerError.invariant(false, {
|
||||
description: `Instruction [${id}] is part of scope @${scope.id}, but that scope has already completed.`,
|
||||
loc: place.loc,
|
||||
reason:
|
||||
'Encountered an instruction that should be part of a scope, but where that scope has already completed',
|
||||
description: `Instruction [${id}] is part of scope @${scope.id}, but that scope has already completed`,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: place.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -28,7 +28,14 @@ class Visitor extends ReactiveFunctionVisitor<Set<BlockId>> {
|
||||
if (terminal.kind === 'break' || terminal.kind === 'continue') {
|
||||
CompilerError.invariant(seenLabels.has(terminal.target), {
|
||||
reason: 'Unexpected break to invalid label',
|
||||
loc: stmt.terminal.loc,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: stmt.terminal.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ export function buildReactiveFunction(fn: HIRFunction): ReactiveFunction {
|
||||
return {
|
||||
loc: fn.loc,
|
||||
id: fn.id,
|
||||
nameHint: fn.nameHint,
|
||||
params: fn.params,
|
||||
generator: fn.generator,
|
||||
async: fn.async,
|
||||
@@ -70,7 +71,13 @@ class Driver {
|
||||
CompilerError.invariant(!this.cx.emitted.has(block.id), {
|
||||
reason: `Cannot emit the same block twice: bb${block.id}`,
|
||||
description: null,
|
||||
loc: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
this.cx.emitted.add(block.id);
|
||||
@@ -130,7 +137,14 @@ class Driver {
|
||||
if (this.cx.isScheduled(terminal.consequent)) {
|
||||
CompilerError.invariant(false, {
|
||||
reason: `Unexpected 'if' where the consequent is already scheduled`,
|
||||
loc: terminal.loc,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: terminal.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
} else {
|
||||
consequent = this.traverseBlock(
|
||||
@@ -143,7 +157,14 @@ class Driver {
|
||||
if (this.cx.isScheduled(alternateId)) {
|
||||
CompilerError.invariant(false, {
|
||||
reason: `Unexpected 'if' where the alternate is already scheduled`,
|
||||
loc: terminal.loc,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: terminal.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
} else {
|
||||
alternate = this.traverseBlock(this.cx.ir.blocks.get(alternateId)!);
|
||||
@@ -196,7 +217,14 @@ class Driver {
|
||||
if (this.cx.isScheduled(case_.block)) {
|
||||
CompilerError.invariant(case_.block === terminal.fallthrough, {
|
||||
reason: `Unexpected 'switch' where a case is already scheduled and block is not the fallthrough`,
|
||||
loc: terminal.loc,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: terminal.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
@@ -255,7 +283,14 @@ class Driver {
|
||||
} else {
|
||||
CompilerError.invariant(false, {
|
||||
reason: `Unexpected 'do-while' where the loop is already scheduled`,
|
||||
loc: terminal.loc,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: terminal.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -316,7 +351,14 @@ class Driver {
|
||||
} else {
|
||||
CompilerError.invariant(false, {
|
||||
reason: `Unexpected 'while' where the loop is already scheduled`,
|
||||
loc: terminal.loc,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: terminal.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -402,7 +444,14 @@ class Driver {
|
||||
} else {
|
||||
CompilerError.invariant(false, {
|
||||
reason: `Unexpected 'for' where the loop is already scheduled`,
|
||||
loc: terminal.loc,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: terminal.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -500,7 +549,14 @@ class Driver {
|
||||
} else {
|
||||
CompilerError.invariant(false, {
|
||||
reason: `Unexpected 'for-of' where the loop is already scheduled`,
|
||||
loc: terminal.loc,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: terminal.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -572,7 +628,14 @@ class Driver {
|
||||
} else {
|
||||
CompilerError.invariant(false, {
|
||||
reason: `Unexpected 'for-in' where the loop is already scheduled`,
|
||||
loc: terminal.loc,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: terminal.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -615,7 +678,14 @@ class Driver {
|
||||
if (this.cx.isScheduled(terminal.alternate)) {
|
||||
CompilerError.invariant(false, {
|
||||
reason: `Unexpected 'branch' where the alternate is already scheduled`,
|
||||
loc: terminal.loc,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: terminal.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
} else {
|
||||
alternate = this.traverseBlock(
|
||||
@@ -653,7 +723,14 @@ class Driver {
|
||||
if (this.cx.isScheduled(terminal.block)) {
|
||||
CompilerError.invariant(false, {
|
||||
reason: `Unexpected 'label' where the block is already scheduled`,
|
||||
loc: terminal.loc,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: terminal.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
} else {
|
||||
block = this.traverseBlock(this.cx.ir.blocks.get(terminal.block)!);
|
||||
@@ -811,7 +888,14 @@ class Driver {
|
||||
if (this.cx.isScheduled(terminal.block)) {
|
||||
CompilerError.invariant(false, {
|
||||
reason: `Unexpected 'scope' where the block is already scheduled`,
|
||||
loc: terminal.loc,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: terminal.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
} else {
|
||||
block = this.traverseBlock(this.cx.ir.blocks.get(terminal.block)!);
|
||||
@@ -837,7 +921,13 @@ class Driver {
|
||||
CompilerError.invariant(false, {
|
||||
reason: 'Unexpected unsupported terminal',
|
||||
description: null,
|
||||
loc: terminal.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: terminal.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
@@ -874,7 +964,13 @@ class Driver {
|
||||
reason:
|
||||
'Expected branch block to end in an instruction that sets the test value',
|
||||
description: null,
|
||||
loc: instr.lvalue.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: instr.lvalue.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
},
|
||||
);
|
||||
@@ -906,7 +1002,13 @@ class Driver {
|
||||
CompilerError.invariant(false, {
|
||||
reason: 'Expected goto value block to have at least one instruction',
|
||||
description: null,
|
||||
loc: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
} else if (defaultBlock.instructions.length === 1) {
|
||||
@@ -1191,14 +1293,27 @@ class Driver {
|
||||
CompilerError.invariant(false, {
|
||||
reason: 'Expected a break target',
|
||||
description: null,
|
||||
loc: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
if (this.cx.scopeFallthroughs.has(target.block)) {
|
||||
CompilerError.invariant(target.type === 'implicit', {
|
||||
reason: 'Expected reactive scope to implicitly break to fallthrough',
|
||||
loc,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
return null;
|
||||
}
|
||||
@@ -1224,7 +1339,13 @@ class Driver {
|
||||
CompilerError.invariant(target !== null, {
|
||||
reason: `Expected continue target to be scheduled for bb${block}`,
|
||||
description: null,
|
||||
loc: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
|
||||
@@ -1299,7 +1420,13 @@ class Context {
|
||||
CompilerError.invariant(!this.#scheduled.has(block), {
|
||||
reason: `Break block is already scheduled: bb${block}`,
|
||||
description: null,
|
||||
loc: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
this.#scheduled.add(block);
|
||||
@@ -1318,7 +1445,13 @@ class Context {
|
||||
CompilerError.invariant(!this.#scheduled.has(continueBlock), {
|
||||
reason: `Continue block is already scheduled: bb${continueBlock}`,
|
||||
description: null,
|
||||
loc: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
this.#scheduled.add(continueBlock);
|
||||
@@ -1346,7 +1479,13 @@ class Context {
|
||||
CompilerError.invariant(last !== undefined && last.id === scheduleId, {
|
||||
reason: 'Can only unschedule the last target',
|
||||
description: null,
|
||||
loc: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
if (last.type !== 'loop' || last.ownsBlock !== null) {
|
||||
@@ -1421,7 +1560,13 @@ class Context {
|
||||
CompilerError.invariant(false, {
|
||||
reason: 'Expected a break target',
|
||||
description: null,
|
||||
loc: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
pruneUnusedLabels,
|
||||
renameVariables,
|
||||
} from '.';
|
||||
import {CompilerError, ErrorCategory, ErrorSeverity} from '../CompilerError';
|
||||
import {CompilerError, ErrorCategory} from '../CompilerError';
|
||||
import {Environment, ExternalFunction} from '../HIR';
|
||||
import {
|
||||
ArrayPattern,
|
||||
@@ -61,6 +61,7 @@ export const EARLY_RETURN_SENTINEL = 'react.early_return_sentinel';
|
||||
export type CodegenFunction = {
|
||||
type: 'CodegenFunction';
|
||||
id: t.Identifier | null;
|
||||
nameHint: string | null;
|
||||
params: t.FunctionDeclaration['params'];
|
||||
body: t.BlockStatement;
|
||||
generator: boolean;
|
||||
@@ -296,7 +297,14 @@ export function codegenFunction(
|
||||
CompilerError.invariant(globalGating != null, {
|
||||
reason:
|
||||
'Bad config not caught! Expected at least one of gating or globalGating',
|
||||
loc: null,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
ifTest = globalGating;
|
||||
@@ -365,7 +373,7 @@ function codegenReactiveFunction(
|
||||
}
|
||||
}
|
||||
|
||||
if (cx.errors.hasErrors()) {
|
||||
if (cx.errors.hasAnyErrors()) {
|
||||
return Err(cx.errors);
|
||||
}
|
||||
|
||||
@@ -376,6 +384,7 @@ function codegenReactiveFunction(
|
||||
type: 'CodegenFunction',
|
||||
loc: fn.loc,
|
||||
id: fn.id !== null ? t.identifier(fn.id) : null,
|
||||
nameHint: fn.nameHint,
|
||||
params,
|
||||
body,
|
||||
generator: fn.generator,
|
||||
@@ -499,10 +508,16 @@ function codegenBlock(cx: Context, block: ReactiveBlock): t.BlockStatement {
|
||||
continue;
|
||||
}
|
||||
CompilerError.invariant(temp.get(key)! === value, {
|
||||
loc: null,
|
||||
reason: 'Expected temporary value to be unchanged',
|
||||
description: null,
|
||||
suggestions: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
cx.temp = temp;
|
||||
@@ -670,7 +685,13 @@ function codegenReactiveScope(
|
||||
description: `Declaration \`${printIdentifier(
|
||||
identifier,
|
||||
)}\` is unnamed in scope @${scope.id}`,
|
||||
loc: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
|
||||
@@ -707,7 +728,13 @@ function codegenReactiveScope(
|
||||
CompilerError.invariant(firstOutputIndex !== null, {
|
||||
reason: `Expected scope to have at least one declaration`,
|
||||
description: `Scope '@${scope.id}' has no declarations`,
|
||||
loc: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
testCondition = t.binaryExpression(
|
||||
@@ -730,7 +757,13 @@ function codegenReactiveScope(
|
||||
{
|
||||
reason: `Expected to not have both change detection enabled and memoization disabled`,
|
||||
description: `Incompatible config options`,
|
||||
loc: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
testCondition = t.logicalExpression(
|
||||
@@ -914,8 +947,14 @@ function codegenReactiveScope(
|
||||
earlyReturnValue.value.name.kind === 'named',
|
||||
{
|
||||
reason: `Expected early return value to be promoted to a named variable`,
|
||||
loc: earlyReturnValue.loc,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: earlyReturnValue.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
},
|
||||
);
|
||||
@@ -945,7 +984,8 @@ function codegenTerminal(
|
||||
if (terminal.targetKind === 'implicit') {
|
||||
return null;
|
||||
}
|
||||
return t.breakStatement(
|
||||
return createBreakStatement(
|
||||
terminal.loc,
|
||||
terminal.targetKind === 'labeled'
|
||||
? t.identifier(codegenLabel(terminal.target))
|
||||
: null,
|
||||
@@ -955,14 +995,16 @@ function codegenTerminal(
|
||||
if (terminal.targetKind === 'implicit') {
|
||||
return null;
|
||||
}
|
||||
return t.continueStatement(
|
||||
return createContinueStatement(
|
||||
terminal.loc,
|
||||
terminal.targetKind === 'labeled'
|
||||
? t.identifier(codegenLabel(terminal.target))
|
||||
: null,
|
||||
);
|
||||
}
|
||||
case 'for': {
|
||||
return t.forStatement(
|
||||
return createForStatement(
|
||||
terminal.loc,
|
||||
codegenForInit(cx, terminal.init),
|
||||
codegenInstructionValueToExpression(cx, terminal.test),
|
||||
terminal.update !== null
|
||||
@@ -975,7 +1017,13 @@ function codegenTerminal(
|
||||
CompilerError.invariant(terminal.init.kind === 'SequenceExpression', {
|
||||
reason: `Expected a sequence expression init for for..in`,
|
||||
description: `Got \`${terminal.init.kind}\` expression instead`,
|
||||
loc: terminal.init.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: terminal.init.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
if (terminal.init.instructions.length !== 2) {
|
||||
@@ -1010,7 +1058,13 @@ function codegenTerminal(
|
||||
CompilerError.invariant(false, {
|
||||
reason: `Expected a StoreLocal or Destructure to be assigned to the collection`,
|
||||
description: `Found ${iterableItem.value.kind}`,
|
||||
loc: iterableItem.value.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: iterableItem.value.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
@@ -1027,7 +1081,13 @@ function codegenTerminal(
|
||||
reason:
|
||||
'Destructure should never be Reassign as it would be an Object/ArrayPattern',
|
||||
description: null,
|
||||
loc: iterableItem.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: iterableItem.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
case InstructionKind.Catch:
|
||||
@@ -1038,7 +1098,13 @@ function codegenTerminal(
|
||||
CompilerError.invariant(false, {
|
||||
reason: `Unexpected ${iterableItem.value.lvalue.kind} variable in for..in collection`,
|
||||
description: null,
|
||||
loc: iterableItem.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: iterableItem.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
default:
|
||||
@@ -1047,7 +1113,8 @@ function codegenTerminal(
|
||||
`Unhandled lvalue kind: ${iterableItem.value.lvalue.kind}`,
|
||||
);
|
||||
}
|
||||
return t.forInStatement(
|
||||
return createForInStatement(
|
||||
terminal.loc,
|
||||
/*
|
||||
* Special handling here since we only want the VariableDeclarators without any inits
|
||||
* This needs to be updated when we handle non-trivial ForOf inits
|
||||
@@ -1067,7 +1134,13 @@ function codegenTerminal(
|
||||
{
|
||||
reason: `Expected a single-expression sequence expression init for for..of`,
|
||||
description: `Got \`${terminal.init.kind}\` expression instead`,
|
||||
loc: terminal.init.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: terminal.init.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
},
|
||||
);
|
||||
@@ -1076,7 +1149,13 @@ function codegenTerminal(
|
||||
CompilerError.invariant(terminal.test.kind === 'SequenceExpression', {
|
||||
reason: `Expected a sequence expression test for for..of`,
|
||||
description: `Got \`${terminal.init.kind}\` expression instead`,
|
||||
loc: terminal.test.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: terminal.test.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
if (terminal.test.instructions.length !== 2) {
|
||||
@@ -1110,7 +1189,13 @@ function codegenTerminal(
|
||||
CompilerError.invariant(false, {
|
||||
reason: `Expected a StoreLocal or Destructure to be assigned to the collection`,
|
||||
description: `Found ${iterableItem.value.kind}`,
|
||||
loc: iterableItem.value.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: iterableItem.value.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
@@ -1131,7 +1216,13 @@ function codegenTerminal(
|
||||
CompilerError.invariant(false, {
|
||||
reason: `Unexpected ${iterableItem.value.lvalue.kind} variable in for..of collection`,
|
||||
description: null,
|
||||
loc: iterableItem.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: iterableItem.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
default:
|
||||
@@ -1140,7 +1231,8 @@ function codegenTerminal(
|
||||
`Unhandled lvalue kind: ${iterableItem.value.lvalue.kind}`,
|
||||
);
|
||||
}
|
||||
return t.forOfStatement(
|
||||
return createForOfStatement(
|
||||
terminal.loc,
|
||||
/*
|
||||
* Special handling here since we only want the VariableDeclarators without any inits
|
||||
* This needs to be updated when we handle non-trivial ForOf inits
|
||||
@@ -1162,7 +1254,7 @@ function codegenTerminal(
|
||||
alternate = block;
|
||||
}
|
||||
}
|
||||
return t.ifStatement(test, consequent, alternate);
|
||||
return createIfStatement(terminal.loc, test, consequent, alternate);
|
||||
}
|
||||
case 'return': {
|
||||
const value = codegenPlaceToExpression(cx, terminal.value);
|
||||
@@ -1173,7 +1265,8 @@ function codegenTerminal(
|
||||
return t.returnStatement(value);
|
||||
}
|
||||
case 'switch': {
|
||||
return t.switchStatement(
|
||||
return createSwitchStatement(
|
||||
terminal.loc,
|
||||
codegenPlaceToExpression(cx, terminal.test),
|
||||
terminal.cases.map(case_ => {
|
||||
const test =
|
||||
@@ -1186,15 +1279,26 @@ function codegenTerminal(
|
||||
);
|
||||
}
|
||||
case 'throw': {
|
||||
return t.throwStatement(codegenPlaceToExpression(cx, terminal.value));
|
||||
return createThrowStatement(
|
||||
terminal.loc,
|
||||
codegenPlaceToExpression(cx, terminal.value),
|
||||
);
|
||||
}
|
||||
case 'do-while': {
|
||||
const test = codegenInstructionValueToExpression(cx, terminal.test);
|
||||
return t.doWhileStatement(test, codegenBlock(cx, terminal.loop));
|
||||
return createDoWhileStatement(
|
||||
terminal.loc,
|
||||
test,
|
||||
codegenBlock(cx, terminal.loop),
|
||||
);
|
||||
}
|
||||
case 'while': {
|
||||
const test = codegenInstructionValueToExpression(cx, terminal.test);
|
||||
return t.whileStatement(test, codegenBlock(cx, terminal.loop));
|
||||
return createWhileStatement(
|
||||
terminal.loc,
|
||||
test,
|
||||
codegenBlock(cx, terminal.loop),
|
||||
);
|
||||
}
|
||||
case 'label': {
|
||||
return codegenBlock(cx, terminal.block);
|
||||
@@ -1205,7 +1309,8 @@ function codegenTerminal(
|
||||
catchParam = convertIdentifier(terminal.handlerBinding.identifier);
|
||||
cx.temp.set(terminal.handlerBinding.identifier.declarationId, null);
|
||||
}
|
||||
return t.tryStatement(
|
||||
return createTryStatement(
|
||||
terminal.loc,
|
||||
codegenBlock(cx, terminal.block),
|
||||
t.catchClause(catchParam, codegenBlock(cx, terminal.handler)),
|
||||
);
|
||||
@@ -1272,7 +1377,13 @@ function codegenInstructionNullable(
|
||||
reason:
|
||||
'Encountered a destructuring operation where some identifiers are already declared (reassignments) but others are not (declarations)',
|
||||
description: null,
|
||||
loc: instr.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: instr.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
} else if (hasReassign) {
|
||||
@@ -1285,7 +1396,13 @@ function codegenInstructionNullable(
|
||||
CompilerError.invariant(instr.lvalue === null, {
|
||||
reason: `Const declaration cannot be referenced as an expression`,
|
||||
description: null,
|
||||
loc: instr.value.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: instr.value.loc,
|
||||
message: `this is ${kind}`,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
return createVariableDeclaration(instr.loc, 'const', [
|
||||
@@ -1296,20 +1413,38 @@ function codegenInstructionNullable(
|
||||
CompilerError.invariant(instr.lvalue === null, {
|
||||
reason: `Function declaration cannot be referenced as an expression`,
|
||||
description: null,
|
||||
loc: instr.value.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: instr.value.loc,
|
||||
message: `this is ${kind}`,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
const genLvalue = codegenLValue(cx, lvalue);
|
||||
CompilerError.invariant(genLvalue.type === 'Identifier', {
|
||||
reason: 'Expected an identifier as a function declaration lvalue',
|
||||
description: null,
|
||||
loc: instr.value.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: instr.value.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
CompilerError.invariant(value?.type === 'FunctionExpression', {
|
||||
reason: 'Expected a function as a function declaration value',
|
||||
description: `Got ${value == null ? String(value) : value.type} at ${printInstruction(instr)}`,
|
||||
loc: instr.value.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: instr.value.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
return createFunctionDeclaration(
|
||||
@@ -1325,7 +1460,13 @@ function codegenInstructionNullable(
|
||||
CompilerError.invariant(instr.lvalue === null, {
|
||||
reason: `Const declaration cannot be referenced as an expression`,
|
||||
description: null,
|
||||
loc: instr.value.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: instr.value.loc,
|
||||
message: 'this is const',
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
return createVariableDeclaration(instr.loc, 'let', [
|
||||
@@ -1336,7 +1477,13 @@ function codegenInstructionNullable(
|
||||
CompilerError.invariant(value !== null, {
|
||||
reason: 'Expected a value for reassignment',
|
||||
description: null,
|
||||
loc: instr.value.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: instr.value.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
const expr = t.assignmentExpression(
|
||||
@@ -1369,7 +1516,13 @@ function codegenInstructionNullable(
|
||||
CompilerError.invariant(false, {
|
||||
reason: `Expected ${kind} to have been pruned in PruneHoistedContexts`,
|
||||
description: null,
|
||||
loc: instr.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: instr.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
@@ -1387,7 +1540,14 @@ function codegenInstructionNullable(
|
||||
} else if (instr.value.kind === 'ObjectMethod') {
|
||||
CompilerError.invariant(instr.lvalue, {
|
||||
reason: 'Expected object methods to have a temp lvalue',
|
||||
loc: null,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
cx.objectMethods.set(instr.lvalue.identifier.id, instr.value);
|
||||
@@ -1434,7 +1594,13 @@ function codegenForInit(
|
||||
(instr.kind === 'let' || instr.kind === 'const'),
|
||||
{
|
||||
reason: 'Expected a variable declaration',
|
||||
loc: init.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: init.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
description: `Got ${instr.type}`,
|
||||
suggestions: null,
|
||||
},
|
||||
@@ -1447,7 +1613,13 @@ function codegenForInit(
|
||||
});
|
||||
CompilerError.invariant(declarators.length > 0, {
|
||||
reason: 'Expected a variable declaration',
|
||||
loc: init.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: init.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
description: null,
|
||||
suggestions: null,
|
||||
});
|
||||
@@ -1543,7 +1715,13 @@ const createExpressionStatement = withLoc(t.expressionStatement);
|
||||
const _createLabelledStatement = withLoc(t.labeledStatement);
|
||||
const createVariableDeclaration = withLoc(t.variableDeclaration);
|
||||
const createFunctionDeclaration = withLoc(t.functionDeclaration);
|
||||
const _createWhileStatement = withLoc(t.whileStatement);
|
||||
const createWhileStatement = withLoc(t.whileStatement);
|
||||
const createDoWhileStatement = withLoc(t.doWhileStatement);
|
||||
const createSwitchStatement = withLoc(t.switchStatement);
|
||||
const createIfStatement = withLoc(t.ifStatement);
|
||||
const createForStatement = withLoc(t.forStatement);
|
||||
const createForOfStatement = withLoc(t.forOfStatement);
|
||||
const createForInStatement = withLoc(t.forInStatement);
|
||||
const createTaggedTemplateExpression = withLoc(t.taggedTemplateExpression);
|
||||
const createLogicalExpression = withLoc(t.logicalExpression);
|
||||
const createSequenceExpression = withLoc(t.sequenceExpression);
|
||||
@@ -1558,6 +1736,10 @@ const createJsxText = withLoc(t.jsxText);
|
||||
const createJsxClosingElement = withLoc(t.jsxClosingElement);
|
||||
const createJsxOpeningElement = withLoc(t.jsxOpeningElement);
|
||||
const createStringLiteral = withLoc(t.stringLiteral);
|
||||
const createThrowStatement = withLoc(t.throwStatement);
|
||||
const createTryStatement = withLoc(t.tryStatement);
|
||||
const createBreakStatement = withLoc(t.breakStatement);
|
||||
const createContinueStatement = withLoc(t.continueStatement);
|
||||
|
||||
function createHookGuard(
|
||||
guard: ExternalFunction,
|
||||
@@ -1768,7 +1950,13 @@ function codegenInstructionValue(
|
||||
CompilerError.invariant(t.isExpression(optionalValue.callee), {
|
||||
reason: 'v8 intrinsics are validated during lowering',
|
||||
description: null,
|
||||
loc: optionalValue.callee.loc ?? null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: optionalValue.callee.loc ?? null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
value = t.optionalCallExpression(
|
||||
@@ -1784,7 +1972,13 @@ function codegenInstructionValue(
|
||||
CompilerError.invariant(t.isExpression(property), {
|
||||
reason: 'Private names are validated during lowering',
|
||||
description: null,
|
||||
loc: property.loc ?? null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: property.loc ?? null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
value = t.optionalMemberExpression(
|
||||
@@ -1800,7 +1994,13 @@ function codegenInstructionValue(
|
||||
reason:
|
||||
'Expected an optional value to resolve to a call expression or member expression',
|
||||
description: `Got a \`${optionalValue.type}\``,
|
||||
loc: instrValue.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: instrValue.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
@@ -1816,10 +2016,15 @@ function codegenInstructionValue(
|
||||
t.isOptionalMemberExpression(memberExpr),
|
||||
{
|
||||
reason:
|
||||
'[Codegen] Internal error: MethodCall::property must be an unpromoted + unmemoized MemberExpression. ' +
|
||||
`Got a \`${memberExpr.type}\``,
|
||||
'[Codegen] Internal error: MethodCall::property must be an unpromoted + unmemoized MemberExpression',
|
||||
description: null,
|
||||
loc: memberExpr.loc ?? null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: memberExpr.loc ?? null,
|
||||
message: `Got: '${memberExpr.type}'`,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
},
|
||||
);
|
||||
@@ -1833,7 +2038,13 @@ function codegenInstructionValue(
|
||||
'[Codegen] Internal error: Forget should always generate MethodCall::property ' +
|
||||
'as a MemberExpression of MethodCall::receiver',
|
||||
description: null,
|
||||
loc: memberExpr.loc ?? null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: memberExpr.loc ?? null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
},
|
||||
);
|
||||
@@ -1878,7 +2089,14 @@ function codegenInstructionValue(
|
||||
const method = cx.objectMethods.get(property.place.identifier.id);
|
||||
CompilerError.invariant(method, {
|
||||
reason: 'Expected ObjectMethod instruction',
|
||||
loc: null,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
const loweredFunc = method.loweredFunc;
|
||||
@@ -1949,7 +2167,13 @@ function codegenInstructionValue(
|
||||
CompilerError.invariant(tagValue.type === 'StringLiteral', {
|
||||
reason: `Expected JSX tag to be an identifier or string, got \`${tagValue.type}\``,
|
||||
description: null,
|
||||
loc: tagValue.loc ?? null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: tagValue.loc ?? null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
if (tagValue.value.indexOf(':') >= 0) {
|
||||
@@ -1969,7 +2193,13 @@ function codegenInstructionValue(
|
||||
SINGLE_CHILD_FBT_TAGS.has(tagValue.value)
|
||||
) {
|
||||
CompilerError.invariant(instrValue.children != null, {
|
||||
loc: instrValue.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: instrValue.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
reason: 'Expected fbt element to have children',
|
||||
suggestions: null,
|
||||
description: null,
|
||||
@@ -2098,6 +2328,7 @@ function codegenInstructionValue(
|
||||
),
|
||||
reactiveFunction,
|
||||
).unwrap();
|
||||
|
||||
if (instrValue.type === 'ArrowFunctionExpression') {
|
||||
let body: t.BlockStatement | t.Expression = fn.body;
|
||||
if (body.body.length === 1 && loweredFunc.directives.length == 0) {
|
||||
@@ -2109,14 +2340,26 @@ function codegenInstructionValue(
|
||||
value = t.arrowFunctionExpression(fn.params, body, fn.async);
|
||||
} else {
|
||||
value = t.functionExpression(
|
||||
fn.id ??
|
||||
(instrValue.name != null ? t.identifier(instrValue.name) : null),
|
||||
instrValue.name != null ? t.identifier(instrValue.name) : null,
|
||||
fn.params,
|
||||
fn.body,
|
||||
fn.generator,
|
||||
fn.async,
|
||||
);
|
||||
}
|
||||
if (
|
||||
cx.env.config.enableNameAnonymousFunctions &&
|
||||
instrValue.name == null &&
|
||||
instrValue.nameHint != null
|
||||
) {
|
||||
const name = instrValue.nameHint;
|
||||
value = t.memberExpression(
|
||||
t.objectExpression([t.objectProperty(t.stringLiteral(name), value)]),
|
||||
t.stringLiteral(name),
|
||||
true,
|
||||
false,
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'TaggedTemplateExpression': {
|
||||
@@ -2184,7 +2427,6 @@ function codegenInstructionValue(
|
||||
reason: `(CodegenReactiveFunction::codegenInstructionValue) Cannot declare variables in a value block, tried to declare '${
|
||||
(declarator.id as t.Identifier).name
|
||||
}'`,
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.Todo,
|
||||
loc: declarator.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -2193,7 +2435,6 @@ function codegenInstructionValue(
|
||||
} else {
|
||||
cx.errors.push({
|
||||
reason: `(CodegenReactiveFunction::codegenInstructionValue) Handle conversion of ${stmt.type} to expression`,
|
||||
severity: ErrorSeverity.Todo,
|
||||
category: ErrorCategory.Todo,
|
||||
loc: stmt.loc ?? null,
|
||||
suggestions: null,
|
||||
@@ -2273,7 +2514,13 @@ function codegenInstructionValue(
|
||||
{
|
||||
reason: `Unexpected StoreLocal in codegenInstructionValue`,
|
||||
description: null,
|
||||
loc: instrValue.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: instrValue.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
},
|
||||
);
|
||||
@@ -2303,7 +2550,13 @@ function codegenInstructionValue(
|
||||
CompilerError.invariant(false, {
|
||||
reason: `Unexpected ${instrValue.kind} in codegenInstructionValue`,
|
||||
description: null,
|
||||
loc: instrValue.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: instrValue.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
@@ -2314,6 +2567,9 @@ function codegenInstructionValue(
|
||||
);
|
||||
}
|
||||
}
|
||||
if (instrValue.loc != null && instrValue.loc != GeneratedSource) {
|
||||
value.loc = instrValue.loc;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -2449,7 +2705,13 @@ function convertMemberExpressionToJsx(
|
||||
CompilerError.invariant(expr.property.type === 'Identifier', {
|
||||
reason: 'Expected JSX member expression property to be a string',
|
||||
description: null,
|
||||
loc: expr.loc ?? null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: expr.loc ?? null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
const property = t.jsxIdentifier(expr.property.name);
|
||||
@@ -2460,7 +2722,13 @@ function convertMemberExpressionToJsx(
|
||||
reason:
|
||||
'Expected JSX member expression to be an identifier or nested member expression',
|
||||
description: null,
|
||||
loc: expr.object.loc ?? null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: expr.object.loc ?? null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
const object = convertMemberExpressionToJsx(expr.object);
|
||||
@@ -2484,7 +2752,13 @@ function codegenObjectPropertyKey(
|
||||
CompilerError.invariant(t.isExpression(expr), {
|
||||
reason: 'Expected object property key to be an expression',
|
||||
description: null,
|
||||
loc: key.name.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: key.name.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
return expr;
|
||||
@@ -2631,7 +2905,13 @@ function codegenPlace(cx: Context, place: Place): t.Expression | t.JSXText {
|
||||
description: `Value for '${printPlace(
|
||||
place,
|
||||
)}' was not set in the codegen context`,
|
||||
loc: place.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: place.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
const identifier = convertIdentifier(place.identifier);
|
||||
@@ -2644,7 +2924,13 @@ function convertIdentifier(identifier: Identifier): t.Identifier {
|
||||
identifier.name !== null && identifier.name.kind === 'named',
|
||||
{
|
||||
reason: `Expected temporaries to be promoted to named identifiers in an earlier pass`,
|
||||
loc: GeneratedSource,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: GeneratedSource,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
description: `identifier ${identifier.id} is unnamed`,
|
||||
suggestions: null,
|
||||
},
|
||||
@@ -2660,7 +2946,14 @@ function compareScopeDependency(
|
||||
a.identifier.name?.kind === 'named' && b.identifier.name?.kind === 'named',
|
||||
{
|
||||
reason: '[Codegen] Expected named identifier for dependency',
|
||||
loc: a.identifier.loc,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: a.identifier.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
const aName = [
|
||||
@@ -2684,7 +2977,14 @@ function compareScopeDeclaration(
|
||||
a.identifier.name?.kind === 'named' && b.identifier.name?.kind === 'named',
|
||||
{
|
||||
reason: '[Codegen] Expected named identifier for declaration',
|
||||
loc: a.identifier.loc,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: a.identifier.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
const aName = a.identifier.name.value;
|
||||
|
||||
@@ -75,7 +75,13 @@ export function flattenScopesWithHooksOrUseHIR(fn: HIRFunction): void {
|
||||
CompilerError.invariant(terminal.kind === 'scope', {
|
||||
reason: `Expected block to have a scope terminal`,
|
||||
description: `Expected block bb${block.id} to end in a scope terminal`,
|
||||
loc: terminal.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: terminal.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
const body = fn.body.blocks.get(terminal.block)!;
|
||||
if (
|
||||
|
||||
@@ -162,7 +162,13 @@ export function inferReactiveScopeVariables(fn: HIRFunction): void {
|
||||
});
|
||||
CompilerError.invariant(false, {
|
||||
reason: `Invalid mutable range for scope`,
|
||||
loc: GeneratedSource,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: GeneratedSource,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
description: `Scope @${scope.id} has range [${scope.range.start}:${
|
||||
scope.range.end
|
||||
}] but the valid range is [1:${maxInstruction + 1}]`,
|
||||
|
||||
@@ -159,11 +159,17 @@ class Transform extends ReactiveFunctionTransform<ReactiveScopeDependencies | nu
|
||||
const merged: Array<MergedScope> = [];
|
||||
function reset(): void {
|
||||
CompilerError.invariant(current !== null, {
|
||||
loc: null,
|
||||
reason:
|
||||
'MergeConsecutiveScopes: expected current scope to be non-null if reset()',
|
||||
suggestions: null,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
if (current.to > current.from + 1) {
|
||||
merged.push(current);
|
||||
@@ -375,10 +381,16 @@ class Transform extends ReactiveFunctionTransform<ReactiveScopeDependencies | nu
|
||||
}
|
||||
const mergedScope = block[entry.from]!;
|
||||
CompilerError.invariant(mergedScope.kind === 'scope', {
|
||||
loc: null,
|
||||
reason:
|
||||
'MergeConsecutiveScopes: Expected scope starting index to be a scope',
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
nextInstructions.push(mergedScope);
|
||||
|
||||
@@ -323,7 +323,13 @@ function writeTerminal(writer: Writer, terminal: ReactiveTerminal): void {
|
||||
CompilerError.invariant(block != null, {
|
||||
reason: 'Expected case to have a block',
|
||||
description: null,
|
||||
loc: case_.test?.loc ?? null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: case_.test?.loc ?? null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
writeReactiveInstructions(writer, block);
|
||||
|
||||
@@ -290,7 +290,14 @@ class PromoteInterposedTemporaries extends ReactiveFunctionVisitor<InterState> {
|
||||
CompilerError.invariant(lval.identifier.name != null, {
|
||||
reason:
|
||||
'PromoteInterposedTemporaries: Assignment targets not expected to be temporaries',
|
||||
loc: instruction.loc,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: instruction.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -454,7 +461,13 @@ function promoteIdentifier(identifier: Identifier, state: State): void {
|
||||
reason:
|
||||
'promoteTemporary: Expected to be called only for temporary variables',
|
||||
description: null,
|
||||
loc: GeneratedSource,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: GeneratedSource,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
if (state.tags.has(identifier.declarationId)) {
|
||||
|
||||
@@ -145,7 +145,14 @@ class Visitor extends ReactiveFunctionTransform<VisitorState> {
|
||||
if (maybeHoistedFn != null) {
|
||||
CompilerError.invariant(maybeHoistedFn.kind === 'func', {
|
||||
reason: '[PruneHoistedContexts] Unexpected hoisted function',
|
||||
loc: instruction.loc,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: instruction.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
maybeHoistedFn.definition = instruction.value.lvalue.place;
|
||||
/**
|
||||
|
||||
@@ -196,7 +196,14 @@ class Visitor extends ReactiveFunctionVisitor<CreateUpdate> {
|
||||
): void {
|
||||
CompilerError.invariant(state !== 'Create', {
|
||||
reason: "Visiting a terminal statement with state 'Create'",
|
||||
loc: stmt.terminal.loc,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: stmt.terminal.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
super.visitTerminal(stmt, state);
|
||||
}
|
||||
|
||||
@@ -264,7 +264,13 @@ class State {
|
||||
CompilerError.invariant(identifierNode !== undefined, {
|
||||
reason: 'Expected identifier to be initialized',
|
||||
description: `[${id}] operand=${printPlace(place)} for identifier declaration ${identifier}`,
|
||||
loc: place.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: place.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
identifierNode.scopes.add(scope.id);
|
||||
@@ -286,7 +292,13 @@ function computeMemoizedIdentifiers(state: State): Set<DeclarationId> {
|
||||
CompilerError.invariant(node !== undefined, {
|
||||
reason: `Expected a node for all identifiers, none found for \`${id}\``,
|
||||
description: null,
|
||||
loc: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
if (node.seen) {
|
||||
@@ -328,7 +340,13 @@ function computeMemoizedIdentifiers(state: State): Set<DeclarationId> {
|
||||
CompilerError.invariant(node !== undefined, {
|
||||
reason: 'Expected a node for all scopes',
|
||||
description: null,
|
||||
loc: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
if (node.seen) {
|
||||
@@ -411,7 +429,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 +554,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 +719,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}] : [],
|
||||
@@ -963,7 +995,13 @@ class CollectDependenciesVisitor extends ReactiveFunctionVisitor<
|
||||
CompilerError.invariant(identifierNode !== undefined, {
|
||||
reason: 'Expected identifier to be initialized',
|
||||
description: null,
|
||||
loc: stmt.terminal.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: stmt.terminal.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
for (const scope of scopes) {
|
||||
@@ -988,7 +1026,13 @@ class CollectDependenciesVisitor extends ReactiveFunctionVisitor<
|
||||
CompilerError.invariant(identifierNode !== undefined, {
|
||||
reason: 'Expected identifier to be initialized',
|
||||
description: null,
|
||||
loc: reassignment.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: reassignment.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
for (const scope of scopes) {
|
||||
|
||||
@@ -186,7 +186,13 @@ class Scopes {
|
||||
CompilerError.invariant(last === next, {
|
||||
reason: 'Mismatch push/pop calls',
|
||||
description: null,
|
||||
loc: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -97,7 +97,13 @@ export function eliminateRedundantPhi(
|
||||
CompilerError.invariant(same !== null, {
|
||||
reason: 'Expected phis to be non-empty',
|
||||
description: null,
|
||||
loc: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
rewrites.set(phi.place.identifier, same);
|
||||
@@ -149,12 +155,26 @@ export function eliminateRedundantPhi(
|
||||
for (const phi of block.phis) {
|
||||
CompilerError.invariant(!rewrites.has(phi.place.identifier), {
|
||||
reason: '[EliminateRedundantPhis]: rewrite not complete',
|
||||
loc: phi.place.loc,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: phi.place.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
for (const [, operand] of phi.operands) {
|
||||
CompilerError.invariant(!rewrites.has(operand.identifier), {
|
||||
reason: '[EliminateRedundantPhis]: rewrite not complete',
|
||||
loc: phi.place.loc,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: phi.place.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,13 @@ class SSABuilder {
|
||||
CompilerError.invariant(this.#current !== null, {
|
||||
reason: 'we need to be in a block to access state!',
|
||||
description: null,
|
||||
loc: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
return this.#states.get(this.#current)!;
|
||||
@@ -253,7 +259,13 @@ function enterSSAImpl(
|
||||
CompilerError.invariant(!visitedBlocks.has(block), {
|
||||
reason: `found a cycle! visiting bb${block.id} again`,
|
||||
description: null,
|
||||
loc: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
|
||||
@@ -266,7 +278,13 @@ function enterSSAImpl(
|
||||
CompilerError.invariant(func.context.length === 0, {
|
||||
reason: `Expected function context to be empty for outer function declarations`,
|
||||
description: null,
|
||||
loc: func.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: func.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
func.params = func.params.map(param => {
|
||||
@@ -295,7 +313,13 @@ function enterSSAImpl(
|
||||
reason:
|
||||
'Expected function expression entry block to have zero predecessors',
|
||||
description: null,
|
||||
loc: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
entry.preds.add(blockId);
|
||||
|
||||
@@ -59,7 +59,13 @@ export function rewriteInstructionKindsBasedOnReassignment(
|
||||
{
|
||||
reason: `Expected variable not to be defined prior to declaration`,
|
||||
description: `${printPlace(lvalue.place)} was already defined`,
|
||||
loc: lvalue.place.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: lvalue.place.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
declarations.set(lvalue.place.identifier.declarationId, lvalue);
|
||||
@@ -77,7 +83,13 @@ export function rewriteInstructionKindsBasedOnReassignment(
|
||||
{
|
||||
reason: `Expected variable not to be defined prior to declaration`,
|
||||
description: `${printPlace(lvalue.place)} was already defined`,
|
||||
loc: lvalue.place.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: lvalue.place.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
declarations.set(lvalue.place.identifier.declarationId, lvalue);
|
||||
@@ -101,7 +113,13 @@ export function rewriteInstructionKindsBasedOnReassignment(
|
||||
description: `other places were \`${kind}\` but '${printPlace(
|
||||
place,
|
||||
)}' is const`,
|
||||
loc: place.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: place.loc,
|
||||
message: 'Expected consistent kind for destructuring',
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
},
|
||||
);
|
||||
@@ -114,7 +132,13 @@ export function rewriteInstructionKindsBasedOnReassignment(
|
||||
CompilerError.invariant(block.kind !== 'value', {
|
||||
reason: `TODO: Handle reassignment in a value block where the original declaration was removed by dead code elimination (DCE)`,
|
||||
description: null,
|
||||
loc: place.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: place.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
declarations.set(place.identifier.declarationId, lvalue);
|
||||
@@ -125,7 +149,13 @@ export function rewriteInstructionKindsBasedOnReassignment(
|
||||
description: `Other places were \`${kind}\` but '${printPlace(
|
||||
place,
|
||||
)}' is const`,
|
||||
loc: place.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: place.loc,
|
||||
message: 'Expected consistent kind for destructuring',
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
},
|
||||
);
|
||||
@@ -138,7 +168,13 @@ export function rewriteInstructionKindsBasedOnReassignment(
|
||||
description: `Other places were \`${kind}\` but '${printPlace(
|
||||
place,
|
||||
)}' is reassigned`,
|
||||
loc: place.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: place.loc,
|
||||
message: 'Expected consistent kind for destructuring',
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
},
|
||||
);
|
||||
@@ -150,7 +186,13 @@ export function rewriteInstructionKindsBasedOnReassignment(
|
||||
CompilerError.invariant(kind !== null, {
|
||||
reason: 'Expected at least one operand',
|
||||
description: null,
|
||||
loc: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
lvalue.kind = kind;
|
||||
@@ -163,7 +205,13 @@ export function rewriteInstructionKindsBasedOnReassignment(
|
||||
CompilerError.invariant(declaration !== undefined, {
|
||||
reason: `Expected variable to have been defined`,
|
||||
description: `No declaration for ${printPlace(lvalue)}`,
|
||||
loc: lvalue.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: lvalue.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
declaration.kind = InstructionKind.Let;
|
||||
break;
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
/**
|
||||
* 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 {
|
||||
FunctionExpression,
|
||||
getHookKind,
|
||||
HIRFunction,
|
||||
IdentifierId,
|
||||
} from '../HIR';
|
||||
|
||||
export function nameAnonymousFunctions(fn: HIRFunction): void {
|
||||
if (fn.id == null) {
|
||||
return;
|
||||
}
|
||||
const parentName = fn.id;
|
||||
const functions = nameAnonymousFunctionsImpl(fn);
|
||||
function visit(node: Node, prefix: string): void {
|
||||
if (node.generatedName != null) {
|
||||
/**
|
||||
* Note that we don't generate a name for functions that already had one,
|
||||
* so we'll only add the prefix to anonymous functions regardless of
|
||||
* nesting depth.
|
||||
*/
|
||||
const name = `${prefix}${node.generatedName}]`;
|
||||
node.fn.nameHint = name;
|
||||
node.fn.loweredFunc.func.nameHint = name;
|
||||
}
|
||||
/**
|
||||
* Whether or not we generated a name for the function at this node,
|
||||
* traverse into its nested functions to assign them names
|
||||
*/
|
||||
const nextPrefix = `${prefix}${node.generatedName ?? node.fn.name ?? '<anonymous>'} > `;
|
||||
for (const inner of node.inner) {
|
||||
visit(inner, nextPrefix);
|
||||
}
|
||||
}
|
||||
for (const node of functions) {
|
||||
visit(node, `${parentName}[`);
|
||||
}
|
||||
}
|
||||
|
||||
type Node = {
|
||||
fn: FunctionExpression;
|
||||
generatedName: string | null;
|
||||
inner: Array<Node>;
|
||||
};
|
||||
|
||||
function nameAnonymousFunctionsImpl(fn: HIRFunction): Array<Node> {
|
||||
// Functions that we track to generate names for
|
||||
const functions: Map<IdentifierId, Node> = new Map();
|
||||
// Tracks temporaries that read from variables/globals/properties
|
||||
const names: Map<IdentifierId, string> = new Map();
|
||||
// Tracks all function nodes to bubble up for later renaming
|
||||
const nodes: Array<Node> = [];
|
||||
for (const block of fn.body.blocks.values()) {
|
||||
for (const instr of block.instructions) {
|
||||
const {lvalue, value} = instr;
|
||||
switch (value.kind) {
|
||||
case 'LoadGlobal': {
|
||||
names.set(lvalue.identifier.id, value.binding.name);
|
||||
break;
|
||||
}
|
||||
case 'LoadContext':
|
||||
case 'LoadLocal': {
|
||||
const name = value.place.identifier.name;
|
||||
if (name != null && name.kind === 'named') {
|
||||
names.set(lvalue.identifier.id, name.value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'PropertyLoad': {
|
||||
const objectName = names.get(value.object.identifier.id);
|
||||
if (objectName != null) {
|
||||
names.set(
|
||||
lvalue.identifier.id,
|
||||
`${objectName}.${String(value.property)}`,
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'FunctionExpression': {
|
||||
const inner = nameAnonymousFunctionsImpl(value.loweredFunc.func);
|
||||
const node: Node = {
|
||||
fn: value,
|
||||
generatedName: null,
|
||||
inner,
|
||||
};
|
||||
/**
|
||||
* Bubble-up all functions, even if they're named, so that we can
|
||||
* later generate names for any inner anonymous functions
|
||||
*/
|
||||
nodes.push(node);
|
||||
if (value.name == null) {
|
||||
// but only generate names for anonymous functions
|
||||
functions.set(lvalue.identifier.id, node);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'StoreContext':
|
||||
case 'StoreLocal': {
|
||||
const node = functions.get(value.value.identifier.id);
|
||||
const variableName = value.lvalue.place.identifier.name;
|
||||
if (
|
||||
node != null &&
|
||||
variableName != null &&
|
||||
variableName.kind === 'named'
|
||||
) {
|
||||
node.generatedName = variableName.value;
|
||||
functions.delete(value.value.identifier.id);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'CallExpression':
|
||||
case 'MethodCall': {
|
||||
const callee =
|
||||
value.kind === 'MethodCall' ? value.property : value.callee;
|
||||
const hookKind = getHookKind(fn.env, callee.identifier);
|
||||
let calleeName: string | null = null;
|
||||
if (hookKind != null && hookKind !== 'Custom') {
|
||||
calleeName = hookKind;
|
||||
} else {
|
||||
calleeName = names.get(callee.identifier.id) ?? '(anonymous)';
|
||||
}
|
||||
let fnArgCount = 0;
|
||||
for (const arg of value.args) {
|
||||
if (arg.kind === 'Identifier' && functions.has(arg.identifier.id)) {
|
||||
fnArgCount++;
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < value.args.length; i++) {
|
||||
const arg = value.args[i]!;
|
||||
if (arg.kind === 'Spread') {
|
||||
continue;
|
||||
}
|
||||
const node = functions.get(arg.identifier.id);
|
||||
if (node != null) {
|
||||
const generatedName =
|
||||
fnArgCount > 1 ? `${calleeName}(arg${i})` : `${calleeName}()`;
|
||||
node.generatedName = generatedName;
|
||||
functions.delete(arg.identifier.id);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'JsxExpression': {
|
||||
for (const attr of value.props) {
|
||||
if (attr.kind === 'JsxSpreadAttribute') {
|
||||
continue;
|
||||
}
|
||||
const node = functions.get(attr.place.identifier.id);
|
||||
if (node != null) {
|
||||
const elementName =
|
||||
value.tag.kind === 'BuiltinTag'
|
||||
? value.tag.name
|
||||
: (names.get(value.tag.identifier.id) ?? null);
|
||||
const propName =
|
||||
elementName == null
|
||||
? attr.name
|
||||
: `<${elementName}>.${attr.name}`;
|
||||
node.generatedName = `${propName}`;
|
||||
functions.delete(attr.place.identifier.id);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
@@ -5,12 +5,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {
|
||||
CompilerError,
|
||||
CompilerErrorDetailOptions,
|
||||
ErrorSeverity,
|
||||
SourceLocation,
|
||||
} from '..';
|
||||
import {CompilerError, CompilerErrorDetailOptions, SourceLocation} from '..';
|
||||
import {
|
||||
ArrayExpression,
|
||||
CallExpression,
|
||||
@@ -133,7 +128,6 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void {
|
||||
context.pushError({
|
||||
loc: value.loc,
|
||||
description: null,
|
||||
severity: ErrorSeverity.Invariant,
|
||||
category: ErrorCategory.Invariant,
|
||||
reason: '[InsertFire] No LoadGlobal found for useEffect call',
|
||||
suggestions: null,
|
||||
@@ -180,7 +174,6 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void {
|
||||
loc: value.args[1].loc,
|
||||
description:
|
||||
'You must use an array literal for an effect dependency array when that effect uses `fire()`',
|
||||
severity: ErrorSeverity.Invariant,
|
||||
category: ErrorCategory.Fire,
|
||||
reason: CANNOT_COMPILE_FIRE,
|
||||
suggestions: null,
|
||||
@@ -191,7 +184,6 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void {
|
||||
loc: value.args[1].place.loc,
|
||||
description:
|
||||
'You must use an array literal for an effect dependency array when that effect uses `fire()`',
|
||||
severity: ErrorSeverity.Invariant,
|
||||
category: ErrorCategory.Fire,
|
||||
reason: CANNOT_COMPILE_FIRE,
|
||||
suggestions: null,
|
||||
@@ -226,7 +218,6 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void {
|
||||
context.pushError({
|
||||
loc: value.loc,
|
||||
description: null,
|
||||
severity: ErrorSeverity.Invariant,
|
||||
category: ErrorCategory.Invariant,
|
||||
reason:
|
||||
'[InsertFire] No loadLocal found for fire call argument',
|
||||
@@ -250,7 +241,6 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void {
|
||||
loc: value.loc,
|
||||
description:
|
||||
'`fire()` can only receive a function call such as `fire(fn(a,b)). Method calls and other expressions are not allowed',
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
category: ErrorCategory.Fire,
|
||||
reason: CANNOT_COMPILE_FIRE,
|
||||
suggestions: null,
|
||||
@@ -269,7 +259,6 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void {
|
||||
context.pushError({
|
||||
loc: value.loc,
|
||||
description,
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
category: ErrorCategory.Fire,
|
||||
reason: CANNOT_COMPILE_FIRE,
|
||||
suggestions: null,
|
||||
@@ -401,7 +390,6 @@ function ensureNoRemainingCalleeCaptures(
|
||||
description: `All uses of ${calleeName} must be either used with a fire() call in \
|
||||
this effect or not used with a fire() call at all. ${calleeName} was used with fire() on line \
|
||||
${printSourceLocationLine(calleeInfo.fireLoc)} in this effect`,
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
category: ErrorCategory.Fire,
|
||||
reason: CANNOT_COMPILE_FIRE,
|
||||
suggestions: null,
|
||||
@@ -420,7 +408,6 @@ function ensureNoMoreFireUses(fn: HIRFunction, context: Context): void {
|
||||
loc: place.identifier.loc,
|
||||
description: 'Cannot use `fire` outside of a useEffect function',
|
||||
category: ErrorCategory.Fire,
|
||||
severity: ErrorSeverity.Invariant,
|
||||
reason: CANNOT_COMPILE_FIRE,
|
||||
suggestions: null,
|
||||
});
|
||||
@@ -711,7 +698,7 @@ class Context {
|
||||
}
|
||||
|
||||
hasErrors(): boolean {
|
||||
return this.#errors.hasErrors();
|
||||
return this.#errors.hasAnyErrors();
|
||||
}
|
||||
|
||||
throwIfErrorsFound(): void {
|
||||
|
||||
@@ -616,7 +616,13 @@ class Unifier {
|
||||
CompilerError.invariant(type.operands.length > 0, {
|
||||
reason: 'there should be at least one operand',
|
||||
description: null,
|
||||
loc: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
|
||||
|
||||
@@ -21,7 +21,13 @@ export default class DisjointSet<T> {
|
||||
CompilerError.invariant(first != null, {
|
||||
reason: 'Expected set to be non-empty',
|
||||
description: null,
|
||||
loc: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
/*
|
||||
|
||||
@@ -164,7 +164,13 @@ function parseConfigPragmaEnvironmentForTest(
|
||||
CompilerError.invariant(false, {
|
||||
reason: 'Internal error, could not parse config from pragma string',
|
||||
description: `${fromZodError(config.error)}`,
|
||||
loc: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
@@ -248,18 +254,27 @@ function parseConfigStringAsJS(
|
||||
CompilerError.invariant(false, {
|
||||
reason: 'Failed to parse config pragma as JavaScript object',
|
||||
description: `Could not parse: ${configString}. Error: ${error}`,
|
||||
loc: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
|
||||
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,35 +284,24 @@ 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',
|
||||
description: `${fromZodError(validatedEnvironment.error)}`,
|
||||
loc: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
|
||||
if (validatedEnvironment.data.enableResetCacheOnSourceFileChanges == null) {
|
||||
validatedEnvironment.data.enableResetCacheOnSourceFileChanges = false;
|
||||
}
|
||||
|
||||
options.environment = validatedEnvironment.data;
|
||||
}
|
||||
|
||||
@@ -308,9 +312,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',
|
||||
|
||||
@@ -106,12 +106,19 @@ function visit(
|
||||
}
|
||||
|
||||
CompilerError.invariant(false, {
|
||||
reason: `Expected all references to a variable to be consistently local or context references`,
|
||||
loc: place.loc,
|
||||
reason:
|
||||
'Expected all references to a variable to be consistently local or context references',
|
||||
description: `Identifier ${printPlace(
|
||||
place,
|
||||
)} is referenced as a ${kind} variable, but was previously referenced as a ${prev} variable`,
|
||||
)} is referenced as a ${kind} variable, but was previously referenced as a ${prev.kind} variable`,
|
||||
suggestions: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: place.loc,
|
||||
message: `this is ${prev.kind}`,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
CompilerError,
|
||||
CompilerErrorDetail,
|
||||
ErrorCategory,
|
||||
ErrorSeverity,
|
||||
} from '../CompilerError';
|
||||
import {computeUnconditionalBlocks} from '../HIR/ComputeUnconditionalBlocks';
|
||||
import {isHookName} from '../HIR/Environment';
|
||||
@@ -129,7 +128,6 @@ export function validateHooksUsage(
|
||||
description: null,
|
||||
reason,
|
||||
loc: place.loc,
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
suggestions: null,
|
||||
}),
|
||||
);
|
||||
@@ -147,7 +145,6 @@ export function validateHooksUsage(
|
||||
reason:
|
||||
'Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values',
|
||||
loc: place.loc,
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
suggestions: null,
|
||||
}),
|
||||
);
|
||||
@@ -165,7 +162,6 @@ export function validateHooksUsage(
|
||||
reason:
|
||||
'Hooks must be the same function on every render, but this value may change over time to a different function. See https://react.dev/reference/rules/react-calls-components-and-hooks#dont-dynamically-use-hooks',
|
||||
loc: place.loc,
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
suggestions: null,
|
||||
}),
|
||||
);
|
||||
@@ -453,7 +449,6 @@ function visitFunctionExpression(errors: CompilerError, fn: HIRFunction): void {
|
||||
errors.pushErrorDetail(
|
||||
new CompilerErrorDetail({
|
||||
category: ErrorCategory.Hooks,
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
reason:
|
||||
'Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)',
|
||||
loc: callee.loc,
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {CompilerDiagnostic, CompilerError, Effect, ErrorSeverity} from '..';
|
||||
import {CompilerDiagnostic, CompilerError, Effect} from '..';
|
||||
import {ErrorCategory} from '../CompilerError';
|
||||
import {HIRFunction, IdentifierId, Place} from '../HIR';
|
||||
import {
|
||||
@@ -38,10 +38,9 @@ export function validateLocalsNotReassignedAfterRender(fn: HIRFunction): void {
|
||||
errors.pushDiagnostic(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.Immutability,
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
reason: 'Cannot reassign variable after render completes',
|
||||
description: `Reassigning ${variable} after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead.`,
|
||||
}).withDetail({
|
||||
description: `Reassigning ${variable} after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead`,
|
||||
}).withDetails({
|
||||
kind: 'error',
|
||||
loc: reassignment.loc,
|
||||
message: `Cannot reassign ${variable} after render completes`,
|
||||
@@ -94,11 +93,10 @@ function getContextReassignment(
|
||||
errors.pushDiagnostic(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.Immutability,
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
reason: 'Cannot reassign variable in async function',
|
||||
description:
|
||||
'Reassigning a variable in an async function can cause inconsistent behavior on subsequent renders. Consider using state instead',
|
||||
}).withDetail({
|
||||
}).withDetails({
|
||||
kind: 'error',
|
||||
loc: reassignment.loc,
|
||||
message: `Cannot reassign ${variable}`,
|
||||
@@ -193,7 +191,14 @@ function getContextReassignment(
|
||||
for (const operand of operands) {
|
||||
CompilerError.invariant(operand.effect !== Effect.Unknown, {
|
||||
reason: `Expected effects to be inferred prior to ValidateLocalsNotReassignedAfterRender`,
|
||||
loc: operand.loc,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: operand.loc,
|
||||
message: '',
|
||||
},
|
||||
],
|
||||
});
|
||||
const reassignment = reassigningFunctions.get(
|
||||
operand.identifier.id,
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {CompilerError, ErrorSeverity} from '..';
|
||||
import {CompilerError} from '..';
|
||||
import {ErrorCategory} from '../CompilerError';
|
||||
import {
|
||||
Identifier,
|
||||
@@ -113,7 +113,6 @@ class Visitor extends ReactiveFunctionVisitor<CompilerError> {
|
||||
reason:
|
||||
'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',
|
||||
description: null,
|
||||
severity: ErrorSeverity.CannotPreserveMemoization,
|
||||
loc: typeof instruction.loc !== 'symbol' ? instruction.loc : null,
|
||||
suggestions: null,
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {CompilerError, EnvironmentConfig, ErrorSeverity} from '..';
|
||||
import {CompilerError, EnvironmentConfig} from '..';
|
||||
import {ErrorCategory} from '../CompilerError';
|
||||
import {HIRFunction, IdentifierId} from '../HIR';
|
||||
import {DEFAULT_GLOBALS} from '../HIR/Globals';
|
||||
@@ -59,7 +59,7 @@ export function validateNoCapitalizedCalls(
|
||||
CompilerError.throwInvalidReact({
|
||||
category: ErrorCategory.CapitalizedCalls,
|
||||
reason,
|
||||
description: `${calleeName} may be a component.`,
|
||||
description: `${calleeName} may be a component`,
|
||||
loc: value.loc,
|
||||
suggestions: null,
|
||||
});
|
||||
@@ -82,9 +82,8 @@ export function validateNoCapitalizedCalls(
|
||||
if (propertyName != null) {
|
||||
errors.push({
|
||||
category: ErrorCategory.CapitalizedCalls,
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
reason,
|
||||
description: `${propertyName} may be a component.`,
|
||||
description: `${propertyName} may be a component`,
|
||||
loc: value.loc,
|
||||
suggestions: null,
|
||||
});
|
||||
|
||||
@@ -5,21 +5,217 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {CompilerError, ErrorSeverity, SourceLocation} from '..';
|
||||
import {effect} from 'zod';
|
||||
import {CompilerError, Effect, ErrorSeverity, SourceLocation} from '..';
|
||||
import {ErrorCategory} from '../CompilerError';
|
||||
import {
|
||||
ArrayExpression,
|
||||
BasicBlock,
|
||||
BlockId,
|
||||
Identifier,
|
||||
FunctionExpression,
|
||||
HIRFunction,
|
||||
IdentifierId,
|
||||
Instruction,
|
||||
Place,
|
||||
isSetStateType,
|
||||
isUseEffectHookType,
|
||||
isUseStateType,
|
||||
IdentifierName,
|
||||
GeneratedSource,
|
||||
} from '../HIR';
|
||||
import {printInstruction} from '../HIR/PrintHIR';
|
||||
import {
|
||||
eachInstructionValueOperand,
|
||||
eachInstructionOperand,
|
||||
eachTerminalOperand,
|
||||
eachInstructionLValue,
|
||||
eachPatternOperand,
|
||||
} from '../HIR/visitors';
|
||||
import {isMutable} from '../ReactiveScopes/InferReactiveScopeVariables';
|
||||
import {assertExhaustive} from '../Utils/utils';
|
||||
|
||||
type SetStateCall = {
|
||||
loc: SourceLocation;
|
||||
invalidDeps: Map<Identifier, Place[]> | undefined;
|
||||
setStateId: IdentifierId;
|
||||
};
|
||||
type TypeOfValue = 'ignored' | 'fromProps' | 'fromState' | 'fromPropsOrState';
|
||||
|
||||
type DerivationMetadata = {
|
||||
typeOfValue: TypeOfValue;
|
||||
// TODO: Rename to place
|
||||
identifierPlace: Place;
|
||||
sources: Place[];
|
||||
};
|
||||
|
||||
// TODO: This needs refining
|
||||
type ErrorMetadata = {
|
||||
errorType: 'HoistState' | 'CalculateInRender';
|
||||
propInfo: string | undefined;
|
||||
loc: SourceLocation;
|
||||
setStateId: IdentifierId;
|
||||
};
|
||||
|
||||
function joinValue(
|
||||
lvalueType: TypeOfValue,
|
||||
valueType: TypeOfValue,
|
||||
): TypeOfValue {
|
||||
if (lvalueType === 'ignored') return valueType;
|
||||
if (valueType === 'ignored') return lvalueType;
|
||||
if (lvalueType === valueType) return lvalueType;
|
||||
return 'fromPropsOrState';
|
||||
}
|
||||
|
||||
function updateDerivationMetadata(
|
||||
target: Place,
|
||||
sources: DerivationMetadata[],
|
||||
typeOfValue: TypeOfValue,
|
||||
derivedTuple: Map<IdentifierId, DerivationMetadata>,
|
||||
): void {
|
||||
let newValue: DerivationMetadata = {
|
||||
identifierPlace: target,
|
||||
sources: [],
|
||||
typeOfValue: typeOfValue,
|
||||
};
|
||||
|
||||
for (const source of sources) {
|
||||
// If the identifier of the source is a promoted identifier, then
|
||||
// we should set the target as the source.
|
||||
if (source.identifierPlace.identifier.name?.kind === 'promoted') {
|
||||
newValue.sources.push(target);
|
||||
} else {
|
||||
newValue.sources.push(...source.sources);
|
||||
}
|
||||
}
|
||||
derivedTuple.set(target.identifier.id, newValue);
|
||||
}
|
||||
|
||||
function parseInstr(
|
||||
instr: Instruction,
|
||||
derivedTuple: Map<IdentifierId, DerivationMetadata>,
|
||||
setStateCalls: Map<string, Place>,
|
||||
) {
|
||||
// console.log(printInstruction(instr));
|
||||
// console.log(instr);
|
||||
let typeOfValue: TypeOfValue = 'ignored';
|
||||
|
||||
// If the instruction is destructuring a useState hook call
|
||||
if (
|
||||
instr.value.kind === 'Destructure' &&
|
||||
instr.value.lvalue.pattern.kind === 'ArrayPattern' &&
|
||||
isUseStateType(instr.value.value.identifier)
|
||||
) {
|
||||
const value = instr.value.lvalue.pattern.items[0];
|
||||
if (value.kind === 'Identifier') {
|
||||
derivedTuple.set(value.identifier.id, {
|
||||
identifierPlace: value,
|
||||
sources: [value],
|
||||
typeOfValue: 'fromState',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// If the instruction is calling a setState
|
||||
if (
|
||||
instr.value.kind === 'CallExpression' &&
|
||||
isSetStateType(instr.value.callee.identifier) &&
|
||||
instr.value.args.length === 1 &&
|
||||
instr.value.args[0].kind === 'Identifier' &&
|
||||
instr.value.callee.loc !== GeneratedSource &&
|
||||
instr.value.callee.loc.identifierName !== undefined &&
|
||||
instr.value.callee.loc.identifierName !== null
|
||||
) {
|
||||
setStateCalls.set(
|
||||
instr.value.callee.loc.identifierName,
|
||||
instr.value.callee,
|
||||
);
|
||||
}
|
||||
|
||||
let sources: DerivationMetadata[] = [];
|
||||
for (const operand of eachInstructionOperand(instr)) {
|
||||
const opSource = derivedTuple.get(operand.identifier.id);
|
||||
if (opSource === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
typeOfValue = joinValue(typeOfValue, opSource.typeOfValue);
|
||||
sources.push(opSource);
|
||||
}
|
||||
|
||||
if (typeOfValue !== 'ignored') {
|
||||
for (const lvalue of eachInstructionLValue(instr)) {
|
||||
updateDerivationMetadata(lvalue, sources, typeOfValue, derivedTuple);
|
||||
}
|
||||
|
||||
for (const operand of eachInstructionOperand(instr)) {
|
||||
switch (operand.effect) {
|
||||
case Effect.Capture:
|
||||
case Effect.Store:
|
||||
case Effect.ConditionallyMutate:
|
||||
case Effect.ConditionallyMutateIterator:
|
||||
case Effect.Mutate: {
|
||||
if (isMutable(instr, operand)) {
|
||||
updateDerivationMetadata(
|
||||
operand,
|
||||
sources,
|
||||
typeOfValue,
|
||||
derivedTuple,
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Effect.Freeze:
|
||||
case Effect.Read: {
|
||||
// no-op
|
||||
break;
|
||||
}
|
||||
case Effect.Unknown: {
|
||||
CompilerError.invariant(false, {
|
||||
reason: 'Unexpected unknown effect',
|
||||
description: null,
|
||||
loc: operand.loc,
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
default: {
|
||||
assertExhaustive(
|
||||
operand.effect,
|
||||
`Unexpected effect kind \`${operand.effect}\``,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function parseBlockPhi(
|
||||
block: BasicBlock,
|
||||
derivedTuple: Map<IdentifierId, DerivationMetadata>,
|
||||
) {
|
||||
for (const phi of block.phis) {
|
||||
for (const operand of phi.operands.values()) {
|
||||
const source = derivedTuple.get(operand.identifier.id);
|
||||
if (source !== undefined && source.typeOfValue === 'fromProps') {
|
||||
if (
|
||||
source.identifierPlace.identifier.name === null ||
|
||||
source.identifierPlace.identifier.name?.kind === 'promoted'
|
||||
) {
|
||||
derivedTuple.set(phi.place.identifier.id, {
|
||||
identifierPlace: phi.place,
|
||||
sources: [phi.place],
|
||||
typeOfValue: 'fromProps',
|
||||
});
|
||||
} else {
|
||||
derivedTuple.set(phi.place.identifier.id, {
|
||||
identifierPlace: phi.place,
|
||||
sources: source.sources,
|
||||
typeOfValue: 'fromProps',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that useEffect is not used for derived computations which could/should
|
||||
@@ -48,12 +244,58 @@ export function validateNoDerivedComputationsInEffects(fn: HIRFunction): void {
|
||||
const candidateDependencies: Map<IdentifierId, ArrayExpression> = new Map();
|
||||
const functions: Map<IdentifierId, FunctionExpression> = new Map();
|
||||
const locals: Map<IdentifierId, IdentifierId> = new Map();
|
||||
const derivedTuple: Map<IdentifierId, DerivationMetadata> = new Map();
|
||||
|
||||
const errors = new CompilerError();
|
||||
// Investigating
|
||||
const effectSetStates: Map<string, Place> = new Map();
|
||||
const setStateCalls: Map<string, Place> = new Map();
|
||||
|
||||
// let shouldCalculateInRender: boolean = true;
|
||||
|
||||
const errors: ErrorMetadata[] = [];
|
||||
|
||||
if (fn.fnType === 'Hook') {
|
||||
for (const param of fn.params) {
|
||||
if (param.kind === 'Identifier') {
|
||||
derivedTuple.set(param.identifier.id, {
|
||||
identifierPlace: param,
|
||||
sources: [param],
|
||||
typeOfValue: 'fromProps',
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (fn.fnType === 'Component') {
|
||||
const props = fn.params[0];
|
||||
if (props != null && props.kind === 'Identifier') {
|
||||
derivedTuple.set(props.identifier.id, {
|
||||
identifierPlace: props,
|
||||
sources: [props],
|
||||
typeOfValue: 'fromProps',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (const block of fn.body.blocks.values()) {
|
||||
parseBlockPhi(block, derivedTuple);
|
||||
|
||||
for (const instr of block.instructions) {
|
||||
const {lvalue, value} = instr;
|
||||
|
||||
parseInstr(instr, derivedTuple, setStateCalls);
|
||||
|
||||
/*
|
||||
* Special case for function expressions, we need to parse nested instructions
|
||||
* TODO: Can there be more recursive levels?
|
||||
*/
|
||||
if (value.kind === 'FunctionExpression') {
|
||||
for (const [, block] of value.loweredFunc.func.body.blocks) {
|
||||
for (const instr of block.instructions) {
|
||||
parseInstr(instr, derivedTuple, setStateCalls);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Maybe this should run for every instruction being parsed
|
||||
if (value.kind === 'LoadLocal') {
|
||||
locals.set(lvalue.identifier.id, value.place.identifier.id);
|
||||
} else if (value.kind === 'ArrayExpression') {
|
||||
@@ -66,6 +308,7 @@ export function validateNoDerivedComputationsInEffects(fn: HIRFunction): void {
|
||||
) {
|
||||
const callee =
|
||||
value.kind === 'CallExpression' ? value.callee : value.property;
|
||||
|
||||
if (
|
||||
isUseEffectHookType(callee.identifier) &&
|
||||
value.args.length === 2 &&
|
||||
@@ -83,13 +326,22 @@ export function validateNoDerivedComputationsInEffects(fn: HIRFunction): void {
|
||||
const dependencies: Array<IdentifierId> = deps.elements.map(dep => {
|
||||
CompilerError.invariant(dep.kind === 'Identifier', {
|
||||
reason: `Dependency is checked as a place above`,
|
||||
loc: value.loc,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: value.loc,
|
||||
message: 'this is checked as a place above',
|
||||
},
|
||||
],
|
||||
});
|
||||
return locals.get(dep.identifier.id) ?? dep.identifier.id;
|
||||
});
|
||||
validateEffect(
|
||||
effectFunction.loweredFunc.func,
|
||||
dependencies,
|
||||
derivedTuple,
|
||||
effectSetStates,
|
||||
errors,
|
||||
);
|
||||
}
|
||||
@@ -97,43 +349,82 @@ export function validateNoDerivedComputationsInEffects(fn: HIRFunction): void {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (errors.hasErrors()) {
|
||||
throw errors;
|
||||
|
||||
console.log('setStateCalls: ', setStateCalls);
|
||||
console.log('effectSetStates: ', effectSetStates);
|
||||
const throwableErrors = new CompilerError();
|
||||
for (const error of errors) {
|
||||
throwableErrors.push({
|
||||
reason: `You may not need an effect. Values derived from state should be calculated in render, not in an effect. `,
|
||||
description: `You are using a value derived from props${error.propInfo} to update local state in an effect.`,
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
loc: error.loc,
|
||||
});
|
||||
}
|
||||
|
||||
if (throwableErrors.hasAnyErrors()) {
|
||||
throw throwableErrors;
|
||||
}
|
||||
}
|
||||
|
||||
function validateEffect(
|
||||
effectFunction: HIRFunction,
|
||||
effectDeps: Array<IdentifierId>,
|
||||
errors: CompilerError,
|
||||
derivedTuple: Map<IdentifierId, DerivationMetadata>,
|
||||
effectSetStates: Map<string, Place>,
|
||||
errors: ErrorMetadata[],
|
||||
): void {
|
||||
/*
|
||||
* TODO: This makes it so we only capture single line useEffects.
|
||||
* We should be able to capture multiline as well
|
||||
*/
|
||||
for (const operand of effectFunction.context) {
|
||||
if (isSetStateType(operand.identifier)) {
|
||||
continue;
|
||||
} else if (effectDeps.find(dep => dep === operand.identifier.id) != null) {
|
||||
continue;
|
||||
} else if (derivedTuple.has(operand.identifier.id)) {
|
||||
continue;
|
||||
} else {
|
||||
// Captured something other than the effect dep or setState
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// This might be wrong gotta double check
|
||||
let hasInvalidDep = false;
|
||||
for (const dep of effectDeps) {
|
||||
const depMetadata = derivedTuple.get(dep);
|
||||
if (
|
||||
effectFunction.context.find(operand => operand.identifier.id === dep) ==
|
||||
null
|
||||
effectFunction.context.find(operand => operand.identifier.id === dep) !=
|
||||
null ||
|
||||
(depMetadata !== undefined && depMetadata.typeOfValue !== 'ignored')
|
||||
) {
|
||||
// effect dep wasn't actually used in the function
|
||||
return;
|
||||
hasInvalidDep = true;
|
||||
}
|
||||
}
|
||||
|
||||
const seenBlocks: Set<BlockId> = new Set();
|
||||
const values: Map<IdentifierId, Array<IdentifierId>> = new Map();
|
||||
for (const dep of effectDeps) {
|
||||
values.set(dep, [dep]);
|
||||
if (!hasInvalidDep) {
|
||||
console.log('early return 2');
|
||||
// effect dep wasn't actually used in the function
|
||||
return;
|
||||
}
|
||||
|
||||
const setStateLocations: Array<SourceLocation> = [];
|
||||
const seenBlocks: Set<BlockId> = new Set();
|
||||
// This variable is suspicious maybe we don't need it?
|
||||
const values: Map<IdentifierId, Array<IdentifierId>> = new Map();
|
||||
const effectInvalidlyDerived: Map<IdentifierId, DerivationMetadata> =
|
||||
new Map();
|
||||
|
||||
for (const dep of effectDeps) {
|
||||
values.set(dep, [dep]);
|
||||
const depMetadata = derivedTuple.get(dep);
|
||||
if (depMetadata !== undefined) {
|
||||
effectInvalidlyDerived.set(dep, depMetadata);
|
||||
}
|
||||
}
|
||||
|
||||
const setStateCallsInEffect: Array<SetStateCall> = [];
|
||||
for (const block of effectFunction.body.blocks.values()) {
|
||||
for (const pred of block.preds) {
|
||||
if (!seenBlocks.has(pred)) {
|
||||
@@ -141,21 +432,24 @@ function validateEffect(
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (const phi of block.phis) {
|
||||
const aggregateDeps: Set<IdentifierId> = new Set();
|
||||
for (const operand of phi.operands.values()) {
|
||||
const deps = values.get(operand.identifier.id);
|
||||
if (deps != null) {
|
||||
for (const dep of deps) {
|
||||
aggregateDeps.add(dep);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (aggregateDeps.size !== 0) {
|
||||
values.set(phi.place.identifier.id, Array.from(aggregateDeps));
|
||||
}
|
||||
}
|
||||
|
||||
parseBlockPhi(block, effectInvalidlyDerived);
|
||||
|
||||
for (const instr of block.instructions) {
|
||||
if (
|
||||
instr.value.kind === 'CallExpression' &&
|
||||
isSetStateType(instr.value.callee.identifier) &&
|
||||
instr.value.args.length === 1 &&
|
||||
instr.value.args[0].kind === 'Identifier' &&
|
||||
instr.value.callee.loc !== GeneratedSource &&
|
||||
instr.value.callee.loc.identifierName !== undefined &&
|
||||
instr.value.callee.loc.identifierName !== null
|
||||
) {
|
||||
effectSetStates.set(
|
||||
instr.value.callee.loc.identifierName,
|
||||
instr.value.callee,
|
||||
);
|
||||
}
|
||||
switch (instr.value.kind) {
|
||||
case 'Primitive':
|
||||
case 'JSXText':
|
||||
@@ -176,7 +470,7 @@ function validateEffect(
|
||||
case 'CallExpression':
|
||||
case 'MethodCall': {
|
||||
const aggregateDeps: Set<IdentifierId> = new Set();
|
||||
for (const operand of eachInstructionValueOperand(instr.value)) {
|
||||
for (const operand of eachInstructionOperand(instr)) {
|
||||
const deps = values.get(operand.identifier.id);
|
||||
if (deps != null) {
|
||||
for (const dep of deps) {
|
||||
@@ -194,39 +488,74 @@ function validateEffect(
|
||||
instr.value.args.length === 1 &&
|
||||
instr.value.args[0].kind === 'Identifier'
|
||||
) {
|
||||
const deps = values.get(instr.value.args[0].identifier.id);
|
||||
if (deps != null && new Set(deps).size === effectDeps.length) {
|
||||
setStateLocations.push(instr.value.callee.loc);
|
||||
const propSources = derivedTuple.get(
|
||||
instr.value.args[0].identifier.id,
|
||||
);
|
||||
|
||||
if (propSources !== undefined) {
|
||||
setStateCallsInEffect.push({
|
||||
loc: instr.value.callee.loc,
|
||||
setStateId: instr.value.callee.identifier.id,
|
||||
invalidDeps: new Map([
|
||||
[instr.value.args[0].identifier, propSources.sources],
|
||||
]),
|
||||
});
|
||||
} else {
|
||||
// doesn't depend on any deps
|
||||
return;
|
||||
setStateCallsInEffect.push({
|
||||
loc: instr.value.callee.loc,
|
||||
setStateId: instr.value.callee.identifier.id,
|
||||
invalidDeps: undefined,
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
console.log('early return 4');
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const operand of eachTerminalOperand(block.terminal)) {
|
||||
if (values.has(operand.identifier.id)) {
|
||||
//
|
||||
return;
|
||||
}
|
||||
}
|
||||
seenBlocks.add(block.id);
|
||||
}
|
||||
|
||||
for (const loc of setStateLocations) {
|
||||
errors.push({
|
||||
category: ErrorCategory.EffectDerivationsOfState,
|
||||
reason:
|
||||
'Values derived from props and state should be calculated during render, not in an effect. (https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state)',
|
||||
description: null,
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
loc,
|
||||
suggestions: null,
|
||||
});
|
||||
// need to track if the setState call has been used elsewhere
|
||||
// if it is then the solution should be to lift the state up to the parent component
|
||||
// if not the solution should be to calculate the value in rende
|
||||
//
|
||||
// If the same setState is used both inside and outside the effect
|
||||
|
||||
for (const call of setStateCallsInEffect) {
|
||||
if (call.invalidDeps != null) {
|
||||
let propNames = '';
|
||||
for (const [, places] of call.invalidDeps.entries()) {
|
||||
const placeNames = places
|
||||
.map(place => place.identifier.name?.value)
|
||||
.join(', ');
|
||||
propNames += `[${placeNames}], `;
|
||||
}
|
||||
propNames = propNames.slice(0, -2);
|
||||
const propInfo = propNames ? ` (from props '${propNames}')` : '';
|
||||
|
||||
errors.push({
|
||||
errorType: 'HoistState',
|
||||
propInfo: propInfo,
|
||||
loc: call.loc,
|
||||
setStateId: call.setStateId,
|
||||
});
|
||||
} else {
|
||||
errors.push({
|
||||
errorType: 'CalculateInRender',
|
||||
propInfo: undefined,
|
||||
loc: call.loc,
|
||||
setStateId: call.setStateId,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {CompilerDiagnostic, CompilerError, Effect, ErrorSeverity} from '..';
|
||||
import {CompilerDiagnostic, CompilerError, Effect} from '..';
|
||||
import {ErrorCategory} from '../CompilerError';
|
||||
import {
|
||||
HIRFunction,
|
||||
@@ -66,16 +66,15 @@ export function validateNoFreezingKnownMutableFunctions(
|
||||
errors.pushDiagnostic(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.Immutability,
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
reason: 'Cannot modify local variables after render completes',
|
||||
description: `This argument is a function which may reassign or mutate ${variable} after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.`,
|
||||
description: `This argument is a function which may reassign or mutate ${variable} after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead`,
|
||||
})
|
||||
.withDetail({
|
||||
.withDetails({
|
||||
kind: 'error',
|
||||
loc: operand.loc,
|
||||
message: `This function may (indirectly) reassign or modify ${variable} after render`,
|
||||
})
|
||||
.withDetail({
|
||||
.withDetails({
|
||||
kind: 'error',
|
||||
loc: effect.value.loc,
|
||||
message: `This modifies ${variable}`,
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {CompilerDiagnostic, CompilerError, ErrorSeverity} from '..';
|
||||
import {CompilerDiagnostic, CompilerError} from '..';
|
||||
import {ErrorCategory} from '../CompilerError';
|
||||
import {HIRFunction} from '../HIR';
|
||||
import {getFunctionCallSignature} from '../Inference/InferMutationAliasingEffects';
|
||||
@@ -44,9 +44,8 @@ export function validateNoImpureFunctionsInRender(
|
||||
? `\`${signature.canonicalName}\` is an impure function. `
|
||||
: '') +
|
||||
'Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent)',
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
suggestions: null,
|
||||
}).withDetail({
|
||||
}).withDetails({
|
||||
kind: 'error',
|
||||
loc: callee.loc,
|
||||
message: 'Cannot call impure function',
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {CompilerDiagnostic, CompilerError, ErrorSeverity} from '..';
|
||||
import {CompilerDiagnostic, CompilerError} from '..';
|
||||
import {ErrorCategory} from '../CompilerError';
|
||||
import {BlockId, HIRFunction} from '../HIR';
|
||||
import {Result} from '../Utils/Result';
|
||||
@@ -38,10 +38,9 @@ export function validateNoJSXInTryStatement(
|
||||
errors.pushDiagnostic(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.ErrorBoundaries,
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
reason: 'Avoid constructing JSX within try/catch',
|
||||
description: `React does not immediately render components when JSX is rendered, so any errors from this component will not be caught by the try/catch. To catch errors in rendering a given component, wrap that component in an error boundary. (https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary)`,
|
||||
}).withDetail({
|
||||
}).withDetails({
|
||||
kind: 'error',
|
||||
loc: value.loc,
|
||||
message: 'Avoid constructing JSX within try/catch',
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
CompilerDiagnostic,
|
||||
CompilerError,
|
||||
ErrorCategory,
|
||||
ErrorSeverity,
|
||||
} from '../CompilerError';
|
||||
import {
|
||||
BlockId,
|
||||
@@ -58,8 +57,14 @@ function makeRefId(id: number): RefId {
|
||||
CompilerError.invariant(id >= 0 && Number.isInteger(id), {
|
||||
reason: 'Expected identifier id to be a non-negative integer',
|
||||
description: null,
|
||||
loc: null,
|
||||
suggestions: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
return id as RefId;
|
||||
}
|
||||
@@ -192,19 +197,40 @@ function tyEqual(a: RefAccessType, b: RefAccessType): boolean {
|
||||
case 'Guard':
|
||||
CompilerError.invariant(b.kind === 'Guard', {
|
||||
reason: 'Expected ref value',
|
||||
loc: null,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
return a.refId === b.refId;
|
||||
case 'RefValue':
|
||||
CompilerError.invariant(b.kind === 'RefValue', {
|
||||
reason: 'Expected ref value',
|
||||
loc: null,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
return a.loc == b.loc;
|
||||
case 'Structure': {
|
||||
CompilerError.invariant(b.kind === 'Structure', {
|
||||
reason: 'Expected structure',
|
||||
loc: null,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
const fnTypesEqual =
|
||||
(a.fn === null && b.fn === null) ||
|
||||
@@ -243,7 +269,14 @@ function joinRefAccessTypes(...types: Array<RefAccessType>): RefAccessType {
|
||||
a.kind === 'Structure' && b.kind === 'Structure',
|
||||
{
|
||||
reason: 'Expected structure',
|
||||
loc: null,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
const fn =
|
||||
@@ -470,10 +503,9 @@ function validateNoRefAccessInRenderImpl(
|
||||
errors.pushDiagnostic(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.Refs,
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
reason: 'Cannot access refs during render',
|
||||
description: ERROR_DESCRIPTION,
|
||||
}).withDetail({
|
||||
}).withDetails({
|
||||
kind: 'error',
|
||||
loc: callee.loc,
|
||||
message: `This function accesses a ref value`,
|
||||
@@ -703,14 +735,21 @@ function validateNoRefAccessInRenderImpl(
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.hasErrors()) {
|
||||
if (errors.hasAnyErrors()) {
|
||||
return Err(errors);
|
||||
}
|
||||
}
|
||||
|
||||
CompilerError.invariant(!env.hasChanged(), {
|
||||
reason: 'Ref type environment did not converge',
|
||||
loc: null,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: null,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return Ok(
|
||||
@@ -734,10 +773,9 @@ function guardCheck(errors: CompilerError, operand: Place, env: Env): void {
|
||||
errors.pushDiagnostic(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.Refs,
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
reason: 'Cannot access refs during render',
|
||||
description: ERROR_DESCRIPTION,
|
||||
}).withDetail({
|
||||
}).withDetails({
|
||||
kind: 'error',
|
||||
loc: operand.loc,
|
||||
message: `Cannot access ref value during render`,
|
||||
@@ -759,10 +797,9 @@ function validateNoRefValueAccess(
|
||||
errors.pushDiagnostic(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.Refs,
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
reason: 'Cannot access refs during render',
|
||||
description: ERROR_DESCRIPTION,
|
||||
}).withDetail({
|
||||
}).withDetails({
|
||||
kind: 'error',
|
||||
loc: (type.kind === 'RefValue' && type.loc) || operand.loc,
|
||||
message: `Cannot access ref value during render`,
|
||||
@@ -786,10 +823,9 @@ function validateNoRefPassedToFunction(
|
||||
errors.pushDiagnostic(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.Refs,
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
reason: 'Cannot access refs during render',
|
||||
description: ERROR_DESCRIPTION,
|
||||
}).withDetail({
|
||||
}).withDetails({
|
||||
kind: 'error',
|
||||
loc: (type.kind === 'RefValue' && type.loc) || loc,
|
||||
message: `Passing a ref to a function may read its value during render`,
|
||||
@@ -809,10 +845,9 @@ function validateNoRefUpdate(
|
||||
errors.pushDiagnostic(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.Refs,
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
reason: 'Cannot access refs during render',
|
||||
description: ERROR_DESCRIPTION,
|
||||
}).withDetail({
|
||||
}).withDetails({
|
||||
kind: 'error',
|
||||
loc: (type.kind === 'RefValue' && type.loc) || loc,
|
||||
message: `Cannot update ref during render`,
|
||||
@@ -831,10 +866,9 @@ function validateNoDirectRefValueAccess(
|
||||
errors.pushDiagnostic(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.Refs,
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
reason: 'Cannot access refs during render',
|
||||
description: ERROR_DESCRIPTION,
|
||||
}).withDetail({
|
||||
}).withDetails({
|
||||
kind: 'error',
|
||||
loc: type.loc ?? operand.loc,
|
||||
message: `Cannot access ref value during render`,
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
CompilerDiagnostic,
|
||||
CompilerError,
|
||||
ErrorCategory,
|
||||
ErrorSeverity,
|
||||
} from '../CompilerError';
|
||||
import {
|
||||
HIRFunction,
|
||||
@@ -107,9 +106,8 @@ export function validateNoSetStateInEffects(
|
||||
'* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\n' +
|
||||
'Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. ' +
|
||||
'(https://react.dev/learn/you-might-not-need-an-effect)',
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
suggestions: null,
|
||||
}).withDetail({
|
||||
}).withDetails({
|
||||
kind: 'error',
|
||||
loc: setState.loc,
|
||||
message:
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
CompilerDiagnostic,
|
||||
CompilerError,
|
||||
ErrorCategory,
|
||||
ErrorSeverity,
|
||||
} from '../CompilerError';
|
||||
import {HIRFunction, IdentifierId, isSetStateType} from '../HIR';
|
||||
import {computeUnconditionalBlocks} from '../HIR/ComputeUnconditionalBlocks';
|
||||
@@ -103,7 +102,14 @@ function validateNoSetStateInRenderImpl(
|
||||
case 'StartMemoize': {
|
||||
CompilerError.invariant(activeManualMemoId === null, {
|
||||
reason: 'Unexpected nested StartMemoize instructions',
|
||||
loc: instr.value.loc,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: instr.value.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
activeManualMemoId = instr.value.manualMemoId;
|
||||
break;
|
||||
@@ -114,7 +120,14 @@ function validateNoSetStateInRenderImpl(
|
||||
{
|
||||
reason:
|
||||
'Expected FinishMemoize to align with previous StartMemoize instruction',
|
||||
loc: instr.value.loc,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: instr.value.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
activeManualMemoId = null;
|
||||
@@ -134,9 +147,8 @@ function validateNoSetStateInRenderImpl(
|
||||
'Calling setState from useMemo may trigger an infinite loop',
|
||||
description:
|
||||
'Each time the memo callback is evaluated it will change state. This can cause a memoization dependency to change, running the memo function again and causing an infinite loop. Instead of setting state in useMemo(), prefer deriving the value during render. (https://react.dev/reference/react/useState)',
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
suggestions: null,
|
||||
}).withDetail({
|
||||
}).withDetails({
|
||||
kind: 'error',
|
||||
loc: callee.loc,
|
||||
message: 'Found setState() within useMemo()',
|
||||
@@ -150,9 +162,8 @@ function validateNoSetStateInRenderImpl(
|
||||
'Calling setState during render may trigger an infinite loop',
|
||||
description:
|
||||
'Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState)',
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
suggestions: null,
|
||||
}).withDetail({
|
||||
}).withDetails({
|
||||
kind: 'error',
|
||||
loc: callee.loc,
|
||||
message: 'Found setState() in render',
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
CompilerDiagnostic,
|
||||
CompilerError,
|
||||
ErrorCategory,
|
||||
ErrorSeverity,
|
||||
} from '../CompilerError';
|
||||
import {
|
||||
DeclarationId,
|
||||
@@ -19,7 +18,6 @@ import {
|
||||
IdentifierId,
|
||||
InstructionValue,
|
||||
ManualMemoDependency,
|
||||
Place,
|
||||
PrunedReactiveScopeBlock,
|
||||
ReactiveFunction,
|
||||
ReactiveInstruction,
|
||||
@@ -30,7 +28,10 @@ import {
|
||||
SourceLocation,
|
||||
} from '../HIR';
|
||||
import {printIdentifier, printManualMemoDependency} from '../HIR/PrintHIR';
|
||||
import {eachInstructionValueOperand} from '../HIR/visitors';
|
||||
import {
|
||||
eachInstructionValueLValue,
|
||||
eachInstructionValueOperand,
|
||||
} from '../HIR/visitors';
|
||||
import {collectMaybeMemoDependencies} from '../Inference/DropManualMemoization';
|
||||
import {
|
||||
ReactiveFunctionVisitor,
|
||||
@@ -246,7 +247,14 @@ function validateInferredDep(
|
||||
CompilerError.invariant(dep.identifier.name?.kind === 'named', {
|
||||
reason:
|
||||
'ValidatePreservedManualMemoization: expected scope dependency to be named',
|
||||
loc: GeneratedSource,
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: GeneratedSource,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
normalizedDep = {
|
||||
@@ -283,7 +291,6 @@ function validateInferredDep(
|
||||
errorState.pushDiagnostic(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.PreserveManualMemo,
|
||||
severity: ErrorSeverity.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. ',
|
||||
@@ -299,13 +306,13 @@ function validateInferredDep(
|
||||
errorDiagnostic
|
||||
? getCompareDependencyResultDescription(errorDiagnostic)
|
||||
: 'Inferred dependency not present in source'
|
||||
}.`
|
||||
}`
|
||||
: '',
|
||||
]
|
||||
.join('')
|
||||
.trim(),
|
||||
suggestions: null,
|
||||
}).withDetail({
|
||||
}).withDetails({
|
||||
kind: 'error',
|
||||
loc: memoLocation,
|
||||
message: 'Could not preserve existing manual memoization',
|
||||
@@ -332,56 +339,53 @@ class Visitor extends ReactiveFunctionVisitor<VisitorState> {
|
||||
* @returns a @{ManualMemoDependency} representing the variable +
|
||||
* property reads represented by @value
|
||||
*/
|
||||
recordDepsInValue(
|
||||
value: ReactiveValue,
|
||||
state: VisitorState,
|
||||
): ManualMemoDependency | null {
|
||||
recordDepsInValue(value: ReactiveValue, state: VisitorState): void {
|
||||
switch (value.kind) {
|
||||
case 'SequenceExpression': {
|
||||
for (const instr of value.instructions) {
|
||||
this.visitInstruction(instr, state);
|
||||
}
|
||||
const result = this.recordDepsInValue(value.value, state);
|
||||
return result;
|
||||
this.recordDepsInValue(value.value, state);
|
||||
break;
|
||||
}
|
||||
case 'OptionalExpression': {
|
||||
return this.recordDepsInValue(value.value, state);
|
||||
this.recordDepsInValue(value.value, state);
|
||||
break;
|
||||
}
|
||||
case 'ConditionalExpression': {
|
||||
this.recordDepsInValue(value.test, state);
|
||||
this.recordDepsInValue(value.consequent, state);
|
||||
this.recordDepsInValue(value.alternate, state);
|
||||
return null;
|
||||
break;
|
||||
}
|
||||
case 'LogicalExpression': {
|
||||
this.recordDepsInValue(value.left, state);
|
||||
this.recordDepsInValue(value.right, state);
|
||||
return null;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
const dep = collectMaybeMemoDependencies(
|
||||
value,
|
||||
this.temporaries,
|
||||
false,
|
||||
);
|
||||
if (value.kind === 'StoreLocal' || value.kind === 'StoreContext') {
|
||||
const storeTarget = value.lvalue.place;
|
||||
state.manualMemoState?.decls.add(
|
||||
storeTarget.identifier.declarationId,
|
||||
);
|
||||
if (storeTarget.identifier.name?.kind === 'named' && dep == null) {
|
||||
const dep: ManualMemoDependency = {
|
||||
root: {
|
||||
kind: 'NamedLocal',
|
||||
value: storeTarget,
|
||||
},
|
||||
path: [],
|
||||
};
|
||||
this.temporaries.set(storeTarget.identifier.id, dep);
|
||||
return dep;
|
||||
collectMaybeMemoDependencies(value, this.temporaries, false);
|
||||
if (
|
||||
value.kind === 'StoreLocal' ||
|
||||
value.kind === 'StoreContext' ||
|
||||
value.kind === 'Destructure'
|
||||
) {
|
||||
for (const storeTarget of eachInstructionValueLValue(value)) {
|
||||
state.manualMemoState?.decls.add(
|
||||
storeTarget.identifier.declarationId,
|
||||
);
|
||||
if (storeTarget.identifier.name?.kind === 'named') {
|
||||
this.temporaries.set(storeTarget.identifier.id, {
|
||||
root: {
|
||||
kind: 'NamedLocal',
|
||||
value: storeTarget,
|
||||
},
|
||||
path: [],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return dep;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -398,19 +402,15 @@ class Visitor extends ReactiveFunctionVisitor<VisitorState> {
|
||||
state.manualMemoState.decls.add(lvalue.identifier.declarationId);
|
||||
}
|
||||
|
||||
const maybeDep = this.recordDepsInValue(value, state);
|
||||
if (lvalId != null) {
|
||||
if (maybeDep != null) {
|
||||
temporaries.set(lvalId, maybeDep);
|
||||
} else if (isNamedLocal) {
|
||||
temporaries.set(lvalId, {
|
||||
root: {
|
||||
kind: 'NamedLocal',
|
||||
value: {...(instr.lvalue as Place)},
|
||||
},
|
||||
path: [],
|
||||
});
|
||||
}
|
||||
this.recordDepsInValue(value, state);
|
||||
if (lvalue != null) {
|
||||
temporaries.set(lvalue.identifier.id, {
|
||||
root: {
|
||||
kind: 'NamedLocal',
|
||||
value: {...lvalue},
|
||||
},
|
||||
path: [],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -497,7 +497,13 @@ class Visitor extends ReactiveFunctionVisitor<VisitorState> {
|
||||
CompilerError.invariant(state.manualMemoState == null, {
|
||||
reason: 'Unexpected nested StartMemoize instructions',
|
||||
description: `Bad manual memoization ids: ${state.manualMemoState?.manualMemoId}, ${value.manualMemoId}`,
|
||||
loc: value.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: value.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
|
||||
@@ -537,13 +543,12 @@ class Visitor extends ReactiveFunctionVisitor<VisitorState> {
|
||||
state.errors.pushDiagnostic(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.PreserveManualMemo,
|
||||
severity: ErrorSeverity.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. ',
|
||||
'This dependency may be mutated later, which could cause the value to change unexpectedly.',
|
||||
'This dependency may be mutated later, which could cause the value to change unexpectedly',
|
||||
].join(''),
|
||||
}).withDetail({
|
||||
}).withDetails({
|
||||
kind: 'error',
|
||||
loc,
|
||||
message: 'This dependency may be modified later',
|
||||
@@ -559,7 +564,13 @@ class Visitor extends ReactiveFunctionVisitor<VisitorState> {
|
||||
{
|
||||
reason: 'Unexpected mismatch between StartMemoize and FinishMemoize',
|
||||
description: `Encountered StartMemoize id=${state.manualMemoState?.manualMemoId} followed by FinishMemoize id=${value.manualMemoId}`,
|
||||
loc: value.loc,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: value.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
},
|
||||
);
|
||||
@@ -585,17 +596,16 @@ class Visitor extends ReactiveFunctionVisitor<VisitorState> {
|
||||
state.errors.pushDiagnostic(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.PreserveManualMemo,
|
||||
severity: ErrorSeverity.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. This value was memoized in source but not in compilation output. ',
|
||||
'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
|
||||
? `${printIdentifier(identifier)} was not memoized.`
|
||||
: '',
|
||||
]
|
||||
.join('')
|
||||
.trim(),
|
||||
}).withDetail({
|
||||
}).withDetails({
|
||||
kind: 'error',
|
||||
loc,
|
||||
message: 'Could not preserve existing memoization',
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
CompilerDiagnostic,
|
||||
CompilerError,
|
||||
ErrorCategory,
|
||||
ErrorSeverity,
|
||||
} from '../CompilerError';
|
||||
import {HIRFunction, IdentifierId, SourceLocation} from '../HIR';
|
||||
import {Result} from '../Utils/Result';
|
||||
@@ -67,16 +66,15 @@ export function validateStaticComponents(
|
||||
error.pushDiagnostic(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.StaticComponents,
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
reason: 'Cannot create components during render',
|
||||
description: `Components created during render will reset their state each time they are created. Declare components outside of render. `,
|
||||
description: `Components created during render will reset their state each time they are created. Declare components outside of render`,
|
||||
})
|
||||
.withDetail({
|
||||
.withDetails({
|
||||
kind: 'error',
|
||||
loc: value.tag.loc,
|
||||
message: 'This component is created during render',
|
||||
})
|
||||
.withDetail({
|
||||
.withDetails({
|
||||
kind: 'error',
|
||||
loc: location,
|
||||
message: 'The component is created during render here',
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
CompilerDiagnostic,
|
||||
CompilerError,
|
||||
ErrorCategory,
|
||||
ErrorSeverity,
|
||||
} from '../CompilerError';
|
||||
import {FunctionExpression, HIRFunction, IdentifierId} from '../HIR';
|
||||
import {Result} from '../Utils/Result';
|
||||
@@ -76,12 +75,11 @@ export function validateUseMemo(fn: HIRFunction): Result<void, CompilerError> {
|
||||
errors.pushDiagnostic(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.UseMemo,
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
reason: 'useMemo() callbacks may not accept parameters',
|
||||
description:
|
||||
'useMemo() callbacks are called by React to cache calculations across re-renders. They should not take parameters. Instead, directly reference the props, state, or local variables needed for the computation.',
|
||||
'useMemo() callbacks are called by React to cache calculations across re-renders. They should not take parameters. Instead, directly reference the props, state, or local variables needed for the computation',
|
||||
suggestions: null,
|
||||
}).withDetail({
|
||||
}).withDetails({
|
||||
kind: 'error',
|
||||
loc,
|
||||
message: 'Callbacks with parameters are not supported',
|
||||
@@ -93,13 +91,12 @@ export function validateUseMemo(fn: HIRFunction): Result<void, CompilerError> {
|
||||
errors.pushDiagnostic(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.UseMemo,
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
reason:
|
||||
'useMemo() callbacks may not be async or generator functions',
|
||||
description:
|
||||
'useMemo() callbacks are called once and must synchronously return a value.',
|
||||
'useMemo() callbacks are called once and must synchronously return a value',
|
||||
suggestions: null,
|
||||
}).withDetail({
|
||||
}).withDetails({
|
||||
kind: 'error',
|
||||
loc: body.loc,
|
||||
message: 'Async and generator functions are not supported',
|
||||
|
||||
@@ -56,7 +56,7 @@ it('logs failed compilation', () => {
|
||||
expect(event.kind).toEqual('CompileError');
|
||||
invariant(event.kind === 'CompileError', 'typescript be smarter');
|
||||
|
||||
expect(event.detail.severity).toEqual('InvalidReact');
|
||||
expect(event.detail.severity).toEqual('Error');
|
||||
//@ts-ignore
|
||||
const {start, end, identifierName} =
|
||||
event.detail.primaryLocation() as t.SourceLocation;
|
||||
|
||||
@@ -129,6 +129,7 @@ function useFoo(t0) {
|
||||
t1 = null;
|
||||
break bb0;
|
||||
}
|
||||
|
||||
if (cond2) {
|
||||
mutate(s);
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ function useFoo(t0) {
|
||||
if ($[0] !== cond || $[1] !== value) {
|
||||
bb0: {
|
||||
items = [];
|
||||
|
||||
if (cond) {
|
||||
break bb0;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user