Compare commits

...

24 Commits

Author SHA1 Message Date
Joe Savona
5eb09a72c4 [compiler] More flexible/helpful lazy ref initialization
Two small QoL improvements inspired by feedback:
* `if (ref.current == undefined) { ref.current = ... }` is now allowed.
* `if (!ref.current) { ref.current = ... }` is still disallowed, but we emit an extra hint suggesting the `if (!ref.current == null)` pattern.

I was on the fence about the latter. We got feedback asking to allow `if (!ref.current)` but if your ref stores a boolean value then this would allow reading the ref in render. The unary form is also less precise in general due to sketchy truthiness conversions. I figured a hint is a good compromise.
2025-09-09 17:52:16 -07:00
Joe Savona
8219b34b60 [compiler] Fix false positive hook return mutation error
This was fun. We previously added the MaybeAlias effect in #33984 in order to describe the semantic that an unknown function call _may_ alias its return value in its result, but that we don't know this for sure. We record mutations through MaybeAlias edges when walking backward in the data flow graph, but downgrade them to conditional mutations. See the original PR for full context.

That change was sufficient for the original case like

```js
const frozen = useContext();
useEffect(() => {
  frozen.method().property = true;
}, [...]);
```

But it wasn't sufficient for cases where the aliasing occured between operands:

```js
const dispatch = useDispatch();
<div onClick={(e) => {
  dispatch(...e.target.value)
  e.target.value = ...;
}} />
```

Here we would record a `Capture dispatch <- e.target` effect. Then during processing of the `event.target.value = ...` assignment we'd eventually  _forward_ from `event` to `dispatch` (along a MaybeAlias edge). But in #33984 I missed that this forward walk also has to downgrade to conditional.

In addition to that change, we also have to be a bit more precise about which set of effects we create for alias/capture/maybe-alias. The new logic is a bit clearer, I think:

* If the value is frozen, it's an ImmutableCapture edge
* If the values are mutable, it's a Capture
* If it's a context->context, context->mutable, or mutable->context, count it as MaybeAlias.
2025-09-09 13:18:05 -07:00
Joseph Savona
a9410fb487 [compiler] Option to infer names for anonymous functions (#34410)
Adds a `@enableNameAnonymousFunctions` feature to infer helpful names
for anonymous functions within components and hooks. The logic is
inspired by a custom Next.js transform, flagged to us by @eps1lon, that
does something similar. Implementing this transform within React
Compiler means that all React (Compiler) users can benefit from more
helpful names when debugging.

The idea builds on the fact that JS engines try to infer helpful names
for anonymous functions (in stack traces) when those functions are
accessed through an object property lookup:

```js
({'a[xyz]': () => {
  throw new Error('hello!')
} }['a[xyz]'])()

// Stack trace:
Uncaught Error: hello!
    at a[xyz] (<anonymous>:1:26) // <-- note the name here
    at <anonymous>:1:60
```

The new NameAnonymousFunctions transform is gated by the above flag,
which is off by default. It attemps to infer names for functions as
follows:

First, determine a "local" name:
* Assigning a function to a named variable uses the variable name.
`const f = () => {}` gets the name "f".
* Passing the function as an argument to a function gets the name of the
function, ie `foo(() => ...)` get the name "foo()", `foo.bar(() => ...)`
gets the name "foo.bar()". Note the parenthesis to help understand that
it was part of a call.
* Passing the function to a known hook uses the name of the hook,
`useEffect(() => ...)` uses "useEffect()".
* Passing the function as a JSX prop uses the element and attr name, eg
`<div onClick={() => ...}` uses "<div>.onClick".

Second, the local name is combined with the name of the outer
component/hook, so the final names will be strings like `Component[f]`
or `useMyHook[useEffect()]`.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34410).
* #34434
* __->__ #34410
2025-09-09 10:22:19 -07:00
Sebastian "Sebbie" Silbermann
6b70072c4f [DevTools] Finalize heuristic for naming unnamed <Suspense> (#34428) 2025-09-09 17:56:26 +02:00
Ruslan Lesiutin
b2cff47472 [DevTools] feat: propagate fetchFileWithCaching from initialization options for Fusebox (#34429)
Each integrator: browser extension, Chrome DevTools Frontend fork,
Electron shell must define and provide `fetchFileWithCaching` in order
for DevTools to be able to fetch application resources, such as scripts
or source maps.

More specifically, if this is available, React DevTools will be able to
symbolicate source locations for component frames, owner stacks,
"suspended by" Promises call frames.

This will be available with the next release of React DevTools.
2025-09-09 13:00:53 +01:00
Sebastian "Sebbie" Silbermann
8943025358 [DevTools] Fix handling of host roots on mount (#34400) 2025-09-08 22:53:02 +02:00
Eugene Choi
3d9d22cbdb [playground] Fix CompilerError mismatch (#34420)
The compiler playground was crashing at any small syntax errors in the
`Input` panel due to updating the `CompilerErrorDetailOptions` type in
#34401. Updated the option to take in a `ErrorCategory` instead.

---------

Co-authored-by: lauren <poteto@users.noreply.github.com>
2025-09-08 15:06:54 -04:00
Eugene Choi
d4374b3ae3 [compiler] [playground] Show internals toggle (#34399)
<!--
  Thanks for submitting a pull request!
We appreciate you spending the time to work on these changes. Please
provide enough information so that others can review your pull request.
The three fields below are mandatory.

Before submitting a pull request, please make sure the following is
done:

1. Fork [the repository](https://github.com/facebook/react) and create
your branch from `main`.
  2. Run `yarn` in the repository root.
3. If you've fixed a bug or added code that should be tested, add tests!
4. Ensure the test suite passes (`yarn test`). Tip: `yarn test --watch
TestName` is helpful in development.
5. Run `yarn test --prod` to test in the production environment. It
supports the same options as `yarn test`.
6. If you need a debugger, run `yarn test --debug --watch TestName`,
open `chrome://inspect`, and press "Inspect".
7. Format your code with
[prettier](https://github.com/prettier/prettier) (`yarn prettier`).
8. Make sure your code lints (`yarn lint`). Tip: `yarn linc` to only
check changed files.
  9. Run the [Flow](https://flowtype.org/) type checks (`yarn flow`).
  10. If you haven't already, complete the CLA.

Learn more about contributing:
https://reactjs.org/docs/how-to-contribute.html
-->

## Summary

Added a "Show Internals" toggle switch to either show only the Config,
Input, Output, and Source Map tabs, or these tabs + all the additional
compiler options. The open/close state of these tabs will be preserved
(unless on page refresh, which is the same as the currently
functionality).


<!--
Explain the **motivation** for making this change. What existing problem
does the pull request solve?
-->

## How did you test this change?

<!--
Demonstrate the code is solid. Example: The exact commands you ran and
their output, screenshots / videos if the pull request changes the user
interface.
How exactly did you verify that your PR solves the issue you wanted to
solve?
  If you leave this empty, your PR will very likely be closed.
-->



https://github.com/user-attachments/assets/8eb0f69e-360c-4e9b-9155-7aa185a0c018
2025-09-08 14:21:03 -04:00
Joseph Savona
3f2a42a5de [compiler] Handle empty list of eslint suppression rules (#34323)
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34323).
* #34276
* __->__ #34323
2025-09-08 10:33:10 -07:00
Sebastian Markbåge
294c33f34d [Flight] Always initialize a debug info array for each Chunk (#34419)
I'm about to add info for pretty much all of these anyway since they all
depend on the data stream itself.
2025-09-08 12:28:14 -04:00
Sebastian "Sebbie" Silbermann
3fb190f729 [DevTools] Avoid renders of stale Suspense store (#34396) 2025-09-08 11:42:03 +02:00
Joseph Savona
f5e96b9740 [compiler] Add missing source locations to statements, expressions (#34406)
Adds missing locations to all the statement kinds that we produce in
codegenInstruction(), and adds generic handling of source locations for
the nodes produced by codegenInstructionValue(). There are definitely
some places where we are still missing a location, but this should
address some of the known issues we've seen such as missing location on
`throw`.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34406).
* #34394
* __->__ #34406
* #34346
2025-09-06 11:14:30 -07:00
lauren
78992521a8 [compiler] Filter out disabled errors from being reported (#34409)
This PR stops error details of severity `ErrorSeverity.Off` from being
reported.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34409).
* __->__ #34409
* #34404
2025-09-06 13:07:23 -04:00
lauren
80d7aa17ad [compiler] Fix error description inconsistency (#34404)
Small fix to make all descriptions consistently printed with a single
period at the end.

Ran `grep -rn "description:" packages/babel-plugin-react-compiler/src
--include="*.ts" --exclude-dir="__tests__" | grep '\.\s*["\`]'` to find
all descriptions ending in a period and manually fixed them.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34404).
* #34409
* __->__ #34404
2025-09-06 13:07:02 -04:00
lauren
474f25842a [compiler] Migrate CompilerError.invariant to new CompilerDiagnostic infra (#34403)
Mechanical PR to migrate existing invariants to use the new
CompilerDiagnostic infra @josephsavona added. Will tackle the others at
a later time.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34403).
* #34409
* #34404
* __->__ #34403
2025-09-06 12:58:08 -04:00
lauren
1fef581e1a [compiler] Deprecate CompilerErrorDetail (#34402)
Now that we have a new CompilerDiagnostic type (which the CompilerError
aggregate can hold), the old CompilerErrorDetail type can be marked as
deprecated. Eventually we should migrate everything to the new
CompilerDiagnostic type.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34402).
* #34409
* #34404
* #34403
* __->__ #34402
* #34401
2025-09-06 12:41:54 -04:00
lauren
60d9b9740d [compiler] Derive ErrorSeverity from ErrorCategory (#34401)
With #34176 we now have granular lint rules created for each compiler
ErrorCategory. However, we had remnants of our old error severities
still in use which makes reporting errors quite clunky. Previously you
would need to specify both a category and severity which often ended up
being the same.

This PR moves severity definition into our rules which are generated
from our categories. For now I decided to defer "upgrading" categories
from a simple string to a sum type since we are only using severities to
map errors to eslint severity.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34401).
* #34409
* #34404
* #34403
* #34402
* __->__ #34401
2025-09-06 12:41:29 -04:00
KimCookieYa
c4e2508dad [react-devtools-shared] Fix URL construction when base URL is invalid (#34407)
### Problem
- Users encounter “Failed to construct 'URL': Invalid base URL” when
clicking the “View source” action in DevTools if the underlying base URL
is invalid.
- This exception originates from `new URL(relative, base)` and bubbles
up, interrupting the DevTools UI.
- Fixes GitHub issue
[#34317](https://github.com/facebook/react/issues/34317)

### Solution
- Wrap URL construction to:
  - First try `new URL(sourceMapAt, sourceURL)`.
  - If that fails, try `new URL(sourceMapAt)` as an absolute URL.
  - If both fail, return `null` (no symbolication) rather than throwing.
- This preserves normal behavior for valid bases and absolute URLs,
while avoiding crashes for invalid bases.

### Implementation details
- Updated `symbolicateSource` in
`packages/react-devtools-shared/src/symbolicateSource.js` to handle
invalid base URL scenarios without throwing.
- Added/verified tests in
`packages/react-devtools-shared/src/__tests__/utils-test.js`:
- “should not throw for invalid base URL with relative source map” →
resolves to `null`.
- “should resolve absolute source map even if base URL is invalid” →
still resolves correctly.

### Test plan
- Lint/format:
  - `yarn prettier-check`
  - `yarn linc`
- Type checking:
  - `yarn flow dom-node`
- Unit tests:
  - `yarn test --watchAll=false utils-test`
  - Optionally: `yarn test --watchAll=false utils-test inspectedElement`
- All of the above pass locally for experimental channel.

### Risks and rollout
- Risk: Low. Only affects cases where the base URL is invalid.
- Normal cases (valid base or absolute `sourceMappingURL`) are
unchanged.
- No user-facing API changes; DevTools UX becomes more resilient.

### Affected packages
- `react-devtools-shared`

### Related
- Fixes GitHub issue
[#34317](https://github.com/facebook/react/issues/34317)

### Checklist
- [x] Ran `yarn prettier-check`
- [x] Ran `yarn linc`
- [x] Ran `yarn flow dom-node`
- [x] Relevant unit tests passing
- [x] Linked issue and added a concise summary


<!--
  Thanks for submitting a pull request!
We appreciate you spending the time to work on these changes. Please
provide enough information so that others can review your pull request.
The three fields below are mandatory.

Before submitting a pull request, please make sure the following is
done:

1. Fork [the repository](https://github.com/facebook/react) and create
your branch from `main`.
  2. Run `yarn` in the repository root.
3. If you've fixed a bug or added code that should be tested, add tests!
4. Ensure the test suite passes (`yarn test`). Tip: `yarn test --watch
TestName` is helpful in development.
5. Run `yarn test --prod` to test in the production environment. It
supports the same options as `yarn test`.
6. If you need a debugger, run `yarn test --debug --watch TestName`,
open `chrome://inspect`, and press "Inspect".
7. Format your code with
[prettier](https://github.com/prettier/prettier) (`yarn prettier`).
8. Make sure your code lints (`yarn lint`). Tip: `yarn linc` to only
check changed files.
  9. Run the [Flow](https://flowtype.org/) type checks (`yarn flow`).
  10. If you haven't already, complete the CLA.

Learn more about contributing:
https://reactjs.org/docs/how-to-contribute.html
-->

## Summary

<!--
Explain the **motivation** for making this change. What existing problem
does the pull request solve?
-->

## How did you test this change?

<!--
Demonstrate the code is solid. Example: The exact commands you ran and
their output, screenshots / videos if the pull request changes the user
interface.
How exactly did you verify that your PR solves the issue you wanted to
solve?
  If you leave this empty, your PR will very likely be closed.
-->
2025-09-06 14:00:45 +01:00
Eugene Choi
de5a1b203e [compiler][playground] (3/N) Config override panel (#34371)
<!--
  Thanks for submitting a pull request!
We appreciate you spending the time to work on these changes. Please
provide enough information so that others can review your pull request.
The three fields below are mandatory.

Before submitting a pull request, please make sure the following is
done:

1. Fork [the repository](https://github.com/facebook/react) and create
your branch from `main`.
  2. Run `yarn` in the repository root.
3. If you've fixed a bug or added code that should be tested, add tests!
4. Ensure the test suite passes (`yarn test`). Tip: `yarn test --watch
TestName` is helpful in development.
5. Run `yarn test --prod` to test in the production environment. It
supports the same options as `yarn test`.
6. If you need a debugger, run `yarn test --debug --watch TestName`,
open `chrome://inspect`, and press "Inspect".
7. Format your code with
[prettier](https://github.com/prettier/prettier) (`yarn prettier`).
8. Make sure your code lints (`yarn lint`). Tip: `yarn linc` to only
check changed files.
  9. Run the [Flow](https://flowtype.org/) type checks (`yarn flow`).
  10. If you haven't already, complete the CLA.

Learn more about contributing:
https://reactjs.org/docs/how-to-contribute.html
-->

## Summary

Part 3 of adding a "Config Override" panel to the React compiler
playground. Added a button to apply config changes to the Input panel,
as well as making the tab collapsible. Added validation for the the
PluginOptions type (although comes with a bit more boilerplate) to make
it very obvious what the possible config errors could be. Added some
toasts for trying to apply broken configs.

<!--
Explain the **motivation** for making this change. What existing problem
does the pull request solve?
-->

## How did you test this change?


https://github.com/user-attachments/assets/63ab8636-396f-45ba-aaa5-4136e62ccccc


<!--
Demonstrate the code is solid. Example: The exact commands you ran and
their output, screenshots / videos if the pull request changes the user
interface.
How exactly did you verify that your PR solves the issue you wanted to
solve?
  If you leave this empty, your PR will very likely be closed.
-->
2025-09-05 10:12:01 -04:00
Sebastian "Sebbie" Silbermann
b9a045368b [DevTools] Allow inspecting root when navigating Suspense timeline (#34380) 2025-09-04 16:42:25 +02:00
Sebastian "Sebbie" Silbermann
e2cc315a1b [DevTools] Don't suspend shell while retrieving original source for "open-in-editor" (#34381) 2025-09-04 16:39:07 +02:00
Sebastian "Sebbie" Silbermann
5a31758ed6 [DevTools] Allow inspection before streaming has finished in Chrome (#34360) 2025-09-04 12:21:06 +02:00
Sebastian "Sebbie" Silbermann
ba6590dd7c [DevTools] Rerender boundaries when they unsuspend when advancing the timeline (#34359) 2025-09-04 10:49:16 +02:00
Joseph Savona
2710795a1e [compiler] Cleanup for @enablePreserveExistingMemoizationGuarantees (#34346)
I tried turning on `@enablePreserveExistingMemoizationGuarantees` by
default and cleaned up a couple small things:

* We emit freeze calls for StartMemoize deps but these had
ValueReason.Other so the message wasn't great. We now treat these like
other hook arguments.
* PruneNonEscapingScopes was being too aggressive in this mode and
memoizing even loads of globals. Switching to
MemoizationLevel.Conditional ensures we build a graph that connects
through to primitive-returning function calls, but doesn't unnecessarily
force memoization otherwise.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34346).
* #34347
* __->__ #34346
2025-09-03 21:30:52 -07:00
347 changed files with 4527 additions and 1352 deletions

View File

@@ -577,6 +577,7 @@ module.exports = {
$AsyncIterator: 'readonly',
Iterator: 'readonly',
AsyncIterator: 'readonly',
IntervalID: 'readonly',
IteratorResult: 'readonly',
JSONValue: 'readonly',
JSResourceReference: 'readonly',

View File

@@ -8,58 +8,109 @@
import MonacoEditor, {loader, type Monaco} from '@monaco-editor/react';
import type {editor} from 'monaco-editor';
import * as monaco from 'monaco-editor';
import {useState} from 'react';
import React, {useState, useCallback} from 'react';
import {Resizable} from 're-resizable';
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();
const handleChange: (value: string | undefined) => void = async value => {
if (value === undefined) return;
const toggleExpanded = useCallback(() => {
setIsExpanded(prev => !prev);
}, []);
const handleApplyConfig: () => Promise<void> = async () => {
try {
const newPragma = await generateOverridePragmaFromConfig(value);
const config = store.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,
);
// Update the store with both the new config and updated source
dispatchStore({
type: 'updateFile',
payload: {
source: updatedSource,
config: value,
},
});
} catch (_) {
dispatchStore({
type: 'updateFile',
payload: {
source: store.source,
config: value,
config: config,
},
});
} 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 === undefined) return;
// 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});
@@ -67,35 +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={store.config}
onMount={handleMount}
onChange={handleChange}
options={{
...monacoOptions,
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>
);
}

View File

@@ -13,7 +13,7 @@ import BabelPluginReactCompiler, {
CompilerErrorDetail,
CompilerDiagnostic,
Effect,
ErrorSeverity,
ErrorCategory,
parseConfigPragmaForTests,
ValueKind,
type Hook,
@@ -48,7 +48,6 @@ import {
import {transformFromAstSync} from '@babel/core';
import {LoggerEvent} from 'babel-plugin-react-compiler/dist/Entrypoint';
import {useSearchParams} from 'next/navigation';
import {parseAndFormatConfig} from '../../lib/configUtils';
function parseInput(
input: string,
@@ -259,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,
@@ -317,16 +316,11 @@ export default function Editor(): JSX.Element {
mountStore = defaultStore;
}
parseAndFormatConfig(mountStore.source).then(config => {
dispatchStore({
type: 'setStore',
payload: {
store: {
...mountStore,
config,
},
},
});
dispatchStore({
type: 'setStore',
payload: {
store: mountStore,
},
});
});

View File

@@ -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}

View File

@@ -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"

View File

@@ -56,8 +56,11 @@ type ReducerAction =
type: 'updateFile';
payload: {
source: string;
config?: string;
config: string;
};
}
| {
type: 'toggleInternals';
};
function storeReducer(store: Store, action: ReducerAction): Store {
@@ -75,5 +78,12 @@ function storeReducer(store: Store, action: ReducerAction): Store {
};
return newStore;
}
case 'toggleInternals': {
const newStore = {
...store,
showInternals: !store.showInternals,
};
return newStore;
}
}
}

View File

@@ -8,8 +8,15 @@
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
*/
@@ -17,7 +24,10 @@ export async function parseAndFormatConfig(source: string): Promise<string> {
const pragma = source.substring(0, source.indexOf('\n'));
let configString = parseConfigPragmaAsString(pragma);
if (configString !== '') {
configString = `(${configString})`;
configString = `\
import type { PluginOptions } from 'babel-plugin-react-compiler/dist';
(${configString} satisfies Partial<PluginOptions>)`;
}
try {
@@ -34,10 +44,10 @@ export async function parseAndFormatConfig(source: string): Promise<string> {
}
function extractCurlyBracesContent(input: string): string {
const startIndex = input.indexOf('{');
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');
throw new Error('No outer curly braces found in input.');
}
return input.slice(startIndex, endIndex + 1);
}
@@ -49,6 +59,27 @@ function cleanContent(content: string): string {
.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
*/
@@ -58,6 +89,8 @@ export async function generateOverridePragmaFromConfig(
const content = extractCurlyBracesContent(formattedConfigString);
const cleanConfig = cleanContent(content);
validateConfigAsPluginOptions(cleanConfig);
// Format the config to ensure it's valid
await prettier.format(`(${cleanConfig})`, {
semi: false,

View File

@@ -13,12 +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: '',
config: defaultConfig,
showInternals: false,
};
export const emptyStore: Store = {
source: '',
config: '',
showInternals: false,
};

View File

@@ -10,19 +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;
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));
}
@@ -63,17 +64,14 @@ export function initStoreFromUrlOrLocalStorage(): Store {
*/
if (!encodedSource) return defaultStore;
const raw = decodeStore(encodedSource);
const raw: any = decodeStore(encodedSource);
invariant(isValidStore(raw), 'Invalid Store');
// Add config property if missing for backwards compatibility
if (!('config' in raw)) {
return {
...raw,
config: '',
};
}
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,
};
}

View File

@@ -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 users 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)',

View File

@@ -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,

View File

@@ -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,
},
],
},
);
}

View File

@@ -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.
*/

View File

@@ -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}
@@ -414,6 +415,15 @@ function runWithEnvironment(
});
}
if (env.config.enableNameAnonymousFunctions) {
nameAnonymousFunctions(hir);
log({
kind: 'hir',
name: 'NameAnonymougFunctions',
value: hir,
});
}
const reactiveFunction = buildReactiveFunction(hir);
log({
kind: 'reactive',

View File

@@ -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,
},
);

View File

@@ -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',

View File

@@ -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,

View File

@@ -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({

View File

@@ -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) {

View File

@@ -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,
});
}

View File

@@ -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,
},
],
},
);
}

View File

@@ -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);

View File

@@ -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,
},
],
},
);
}

View File

@@ -13,7 +13,6 @@ import {
CompilerError,
CompilerSuggestionOperation,
ErrorCategory,
ErrorSeverity,
} from '../CompilerError';
import {Err, Ok, Result} from '../Utils/Result';
import {assertExhaustive, hasNode} from '../Utils/utils';
@@ -108,11 +107,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 +171,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 +202,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 +213,7 @@ export function lower(
);
}
if (builder.errors.hasErrors()) {
if (builder.errors.hasAnyErrors()) {
return Err(builder.errors);
}
@@ -276,7 +272,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 +439,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 +465,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 +474,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 +487,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 +561,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 +633,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 +784,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 +856,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 +884,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 +901,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 +948,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 +1027,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 +1062,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 +1133,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 +1154,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 +1239,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 +1260,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 +1320,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 +1329,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 +1422,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 +1442,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 +1471,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 +1486,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 +1564,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 +1589,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 +1646,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 +1672,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 +1693,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 +1726,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 +1746,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 +1772,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 +1806,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 +1818,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 +1847,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 +2059,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 +2087,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 +2186,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 +2225,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 +2238,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 +2247,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 +2274,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 +2284,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 +2307,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 +2347,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 +2408,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 +2418,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 +2432,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 +2454,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 +2464,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 +2506,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 +2521,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 +2642,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 +2650,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 +2667,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 +2679,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 +2734,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 +2743,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 +2822,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 +3046,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 +3242,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 +3277,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 +3298,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 +3357,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 +3371,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 +3396,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 +3444,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 +3481,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,
@@ -3544,6 +3566,8 @@ function lowerFunctionToValue(
let name: string | null = null;
if (expr.isFunctionExpression()) {
name = expr.get('id')?.node?.name ?? null;
} else if (expr.isFunctionDeclaration()) {
name = expr.get('id')?.node?.name ?? null;
}
const loweredFunc = lowerFunction(builder, expr);
if (!loweredFunc) {
@@ -3646,7 +3670,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 +3726,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 +3738,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 +3795,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 +3809,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 +3853,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 +3886,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 +3898,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 +3963,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 +4002,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 +4075,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 +4106,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 +4123,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 +4132,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 +4146,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 +4168,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 +4317,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,

View File

@@ -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',

View File

@@ -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;
}

View File

@@ -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 = {

View File

@@ -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);

View File

@@ -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;

View File

@@ -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,
});

View File

@@ -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;

View File

@@ -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);

View File

@@ -15,6 +15,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';
/*
* *******************************************************************************************
@@ -1298,6 +1299,15 @@ export function forkTemporaryIdentifier(
};
}
export function validateIdentifierName(
name: string,
): Result<ValidIdentifierName, null> {
if (isReservedWord(name) || !t.isValidIdentifier(name)) {
return Err(null);
}
return Ok(makeIdentifierName(name).value);
}
/**
* 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
@@ -1314,8 +1324,14 @@ export function makeIdentifierName(name: string): ValidatedIdentifier {
} else {
CompilerError.invariant(t.isValidIdentifier(name), {
reason: `Expected a valid identifier name`,
loc: GeneratedSource,
description: `\`${name}\` is not a valid JavaScript identifier`,
details: [
{
kind: 'error',
loc: GeneratedSource,
message: null,
},
],
suggestions: null,
});
}
@@ -1334,8 +1350,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 +1380,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 +1555,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 +1694,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 +1717,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 +1740,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 +1763,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 +1786,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;

View File

@@ -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);

View File

@@ -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]!;

View File

@@ -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);

View File

@@ -596,7 +596,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 +871,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

View File

@@ -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

View File

@@ -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,
},
],
},
);

View File

@@ -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,
});

View File

@@ -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;

View File

@@ -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, {

View File

@@ -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':

View File

@@ -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`,
},
],
});
}
}

View File

@@ -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));

View File

@@ -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);
@@ -707,11 +748,21 @@ function applyEffect(
case 'Alias':
case 'Capture': {
CompilerError.invariant(
effect.kind === 'Capture' || initialized.has(effect.into.identifier.id),
effect.kind === 'Capture' ||
effect.kind === 'MaybeAlias' ||
initialized.has(effect.into.identifier.id),
{
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,
reason: `Expected destination to already be initialized within this instruction`,
description:
`Destination ${printPlace(effect.into)} is not initialized in this ` +
`instruction for effect ${printAliasingEffect(effect)}`,
details: [
{
kind: 'error',
loc: effect.into.loc,
message: null,
},
],
},
);
/*
@@ -720,49 +771,67 @@ function applyEffect(
* copy-on-write semantics, then we can prune the effect
*/
const intoKind = state.kind(effect.into).kind;
let isMutableDesination: boolean;
let destinationType: 'context' | 'mutable' | null = null;
switch (intoKind) {
case ValueKind.Context:
case ValueKind.Mutable:
case ValueKind.MaybeFrozen: {
isMutableDesination = true;
case ValueKind.Context: {
destinationType = 'context';
break;
}
default: {
isMutableDesination = false;
case ValueKind.Mutable:
case ValueKind.MaybeFrozen: {
destinationType = 'mutable';
break;
}
}
const fromKind = state.kind(effect.from).kind;
let isMutableReferenceType: boolean;
let sourceType: 'context' | 'mutable' | 'frozen' | null = null;
switch (fromKind) {
case ValueKind.Context: {
sourceType = 'context';
break;
}
case ValueKind.Global:
case ValueKind.Primitive: {
isMutableReferenceType = false;
break;
}
case ValueKind.Frozen: {
isMutableReferenceType = false;
applyEffect(
context,
state,
{
kind: 'ImmutableCapture',
from: effect.from,
into: effect.into,
},
initialized,
effects,
);
sourceType = 'frozen';
break;
}
default: {
isMutableReferenceType = true;
sourceType = 'mutable';
break;
}
}
if (isMutableDesination && isMutableReferenceType) {
if (sourceType === 'frozen') {
applyEffect(
context,
state,
{
kind: 'ImmutableCapture',
from: effect.from,
into: effect.into,
},
initialized,
effects,
);
} else if (
(sourceType === 'mutable' && destinationType === 'mutable') ||
effect.kind === 'MaybeAlias'
) {
effects.push(effect);
} else if (
(sourceType === 'context' && destinationType != null) ||
(sourceType === 'mutable' && destinationType === 'context')
) {
applyEffect(
context,
state,
{kind: 'MaybeAlias', from: effect.from, into: effect.into},
initialized,
effects,
);
}
break;
}
@@ -770,7 +839,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 +1115,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 +1154,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 +1165,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 +1246,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 +1263,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 +1281,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 +1299,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 +1317,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 +1334,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 +1353,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 +2169,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 +2201,7 @@ function computeSignatureForInstruction(
effects.push({
kind: 'Freeze',
value: operand,
reason: ValueReason.Other,
reason: ValueReason.HookCaptured,
});
}
}
@@ -2156,14 +2268,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 +2286,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 +2793,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 +2908,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;

View File

@@ -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);
@@ -758,7 +779,13 @@ class AliasingState {
if (edge.index >= index) {
break;
}
queue.push({place: edge.node, transitive, direction: 'forwards', kind});
queue.push({
place: edge.node,
transitive,
direction: 'forwards',
// Traversing a maybeAlias edge always downgrades to conditional mutation
kind: edge.kind === 'maybeAlias' ? MutationKind.Conditional : kind,
});
}
for (const [alias, when] of node.createdFrom) {
if (when >= index) {
@@ -786,7 +813,12 @@ class AliasingState {
if (when >= index) {
continue;
}
queue.push({place: alias, transitive, direction: 'backwards', kind});
queue.push({
place: alias,
transitive,
direction: 'backwards',
kind,
});
}
/**
* MaybeAlias indicates potential data flow from unknown function calls,

View File

@@ -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,
});
}

View File

@@ -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,
});

View File

@@ -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',

View File

@@ -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`

View File

@@ -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)}`,

View File

@@ -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]);

View File

@@ -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 =

View File

@@ -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,
});
}

View File

@@ -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,
},
],
});
}
}

View File

@@ -70,7 +70,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 +136,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 +156,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 +216,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 +282,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 +350,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 +443,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 +548,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 +627,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 +677,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 +722,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 +887,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 +920,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 +963,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 +1001,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 +1292,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 +1338,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 +1419,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 +1444,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 +1478,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 +1559,13 @@ class Context {
CompilerError.invariant(false, {
reason: 'Expected a break target',
description: null,
loc: null,
details: [
{
kind: 'error',
loc: null,
message: null,
},
],
suggestions: null,
});
}

View File

@@ -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,
@@ -43,6 +43,7 @@ import {
ValidIdentifierName,
getHookKind,
makeIdentifierName,
validateIdentifierName,
} from '../HIR/HIR';
import {printIdentifier, printInstruction, printPlace} from '../HIR/PrintHIR';
import {eachPatternOperand} from '../HIR/visitors';
@@ -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);
}
@@ -499,10 +507,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 +684,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 +727,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 +756,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 +946,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 +983,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 +994,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 +1016,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 +1057,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 +1080,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 +1097,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 +1112,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 +1133,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 +1148,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 +1188,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 +1215,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 +1230,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 +1253,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 +1264,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 +1278,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 +1308,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 +1376,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 +1395,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 +1412,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 +1459,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 +1476,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 +1515,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 +1539,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 +1593,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 +1612,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 +1714,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 +1735,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 +1949,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 +1971,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 +1993,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 +2015,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 +2037,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 +2088,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 +2166,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 +2192,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 +2327,11 @@ function codegenInstructionValue(
),
reactiveFunction,
).unwrap();
const validatedName =
instrValue.name != null
? validateIdentifierName(instrValue.name)
: Err(null);
if (instrValue.type === 'ArrowFunctionExpression') {
let body: t.BlockStatement | t.Expression = fn.body;
if (body.body.length === 1 && loweredFunc.directives.length == 0) {
@@ -2109,14 +2343,28 @@ function codegenInstructionValue(
value = t.arrowFunctionExpression(fn.params, body, fn.async);
} else {
value = t.functionExpression(
fn.id ??
(instrValue.name != null ? t.identifier(instrValue.name) : null),
validatedName
.map<t.Identifier | null>(name => t.identifier(name))
.unwrapOr(null),
fn.params,
fn.body,
fn.generator,
fn.async,
);
}
if (
cx.env.config.enableNameAnonymousFunctions &&
validatedName.isErr() &&
instrValue.name != null
) {
const name = instrValue.name;
value = t.memberExpression(
t.objectExpression([t.objectProperty(t.stringLiteral(name), value)]),
t.stringLiteral(name),
true,
false,
);
}
break;
}
case 'TaggedTemplateExpression': {
@@ -2184,7 +2432,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 +2440,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 +2519,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 +2555,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 +2572,9 @@ function codegenInstructionValue(
);
}
}
if (instrValue.loc != null && instrValue.loc != GeneratedSource) {
value.loc = instrValue.loc;
}
return value;
}
@@ -2449,7 +2710,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 +2727,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 +2757,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 +2910,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 +2929,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 +2951,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 +2982,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;

View File

@@ -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 (

View File

@@ -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}]`,

View File

@@ -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);

View File

@@ -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);

View File

@@ -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)) {

View File

@@ -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;
/**

View File

@@ -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);
}

View File

@@ -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) {
@@ -546,7 +564,7 @@ class CollectDependenciesVisitor extends ReactiveFunctionVisitor<
* memoization. Note: we may still prune primitive-producing scopes if
* they don't ultimately escape at all.
*/
const level = MemoizationLevel.Memoized;
const level = MemoizationLevel.Conditional;
return {
lvalues: lvalue !== null ? [{place: lvalue, level}] : [],
rvalues: [...eachReactiveValueOperand(value)],
@@ -701,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}] : [],
@@ -979,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) {
@@ -1004,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) {

View File

@@ -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,
});
}

View File

@@ -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,
},
],
});
}
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -0,0 +1,173 @@
/**
* 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.name = 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;
}

View File

@@ -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 {

View File

@@ -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,
});

View File

@@ -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,
});
/*

View File

@@ -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,13 +254,17 @@ 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 ?? {},
@@ -281,7 +291,13 @@ function parseConfigStringAsJS(
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,
});
}

View File

@@ -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}`,
},
],
});
}
}

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,
});

View File

@@ -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,
});

View File

@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import {CompilerError, ErrorSeverity, SourceLocation} from '..';
import {CompilerError, SourceLocation} from '..';
import {ErrorCategory} from '../CompilerError';
import {
ArrayExpression,
@@ -83,7 +83,14 @@ 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;
});
@@ -97,7 +104,7 @@ export function validateNoDerivedComputationsInEffects(fn: HIRFunction): void {
}
}
}
if (errors.hasErrors()) {
if (errors.hasAnyErrors()) {
throw errors;
}
}
@@ -224,7 +231,6 @@ function validateEffect(
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,
});

View File

@@ -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}`,

View File

@@ -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',

View File

@@ -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',

View File

@@ -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`,
@@ -607,12 +639,55 @@ function validateNoRefAccessInRenderImpl(
case 'StartMemoize':
case 'FinishMemoize':
break;
case 'LoadGlobal': {
if (instr.value.binding.name === 'undefined') {
env.set(instr.lvalue.identifier.id, {kind: 'Nullable'});
}
break;
}
case 'Primitive': {
if (instr.value.value == null) {
env.set(instr.lvalue.identifier.id, {kind: 'Nullable'});
}
break;
}
case 'UnaryExpression': {
if (instr.value.operator === '!') {
const value = env.get(instr.value.value.identifier.id);
const refId =
value?.kind === 'RefValue' && value.refId != null
? value.refId
: null;
if (refId !== null) {
/*
* Record an error suggesting the `if (ref.current == null)` pattern,
* but also record the lvalue as a guard so that we don't emit a second
* error for the write to the ref
*/
env.set(instr.lvalue.identifier.id, {kind: 'Guard', refId});
errors.pushDiagnostic(
CompilerDiagnostic.create({
category: ErrorCategory.Refs,
reason: 'Cannot access refs during render',
description: ERROR_DESCRIPTION,
})
.withDetails({
kind: 'error',
loc: instr.value.value.loc,
message: `Cannot access ref value during render`,
})
.withDetails({
kind: 'hint',
message:
'To initialize a ref only once, check that the ref is null with the pattern `if (ref.current == null) { ref.current = ... }`',
}),
);
break;
}
}
validateNoRefValueAccess(errors, env, instr.value.value);
break;
}
case 'BinaryExpression': {
const left = env.get(instr.value.left.identifier.id);
const right = env.get(instr.value.right.identifier.id);
@@ -703,14 +778,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 +816,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 +840,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 +866,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 +888,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 +909,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`,

View File

@@ -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:

View File

@@ -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',

View File

@@ -9,7 +9,6 @@ import {
CompilerDiagnostic,
CompilerError,
ErrorCategory,
ErrorSeverity,
} from '../CompilerError';
import {
DeclarationId,
@@ -246,7 +245,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 +289,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 +304,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',
@@ -497,7 +502,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 +548,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 +569,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 +601,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',

View File

@@ -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',

View File

@@ -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',

View File

@@ -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;

View File

@@ -129,6 +129,7 @@ function useFoo(t0) {
t1 = null;
break bb0;
}
if (cond2) {
mutate(s);
}

View File

@@ -43,6 +43,7 @@ function useFoo(t0) {
if ($[0] !== cond || $[1] !== value) {
bb0: {
items = [];
if (cond) {
break bb0;
}

View File

@@ -39,6 +39,7 @@ function Foo() {
if (cond) {
thing = makeObject_Primitives();
}
if (CONST_TRUE) {
mutate(thing);
}

View File

@@ -0,0 +1,42 @@
## Input
```javascript
//@flow
import {useRef} from 'react';
component C() {
const r = useRef(null);
if (r.current == undefined) {
r.current = 1;
}
}
export const FIXTURE_ENTRYPOINT = {
fn: C,
params: [{}],
};
```
## Code
```javascript
import { useRef } from "react";
function C() {
const r = useRef(null);
if (r.current == undefined) {
r.current = 1;
}
}
export const FIXTURE_ENTRYPOINT = {
fn: C,
params: [{}],
};
```
### Eval output
(kind: ok)

View File

@@ -0,0 +1,14 @@
//@flow
import {useRef} from 'react';
component C() {
const r = useRef(null);
if (r.current == undefined) {
r.current = 1;
}
}
export const FIXTURE_ENTRYPOINT = {
fn: C,
params: [{}],
};

View File

@@ -23,6 +23,7 @@ import { c as _c } from "react/compiler-runtime";
function useBar(props) {
const $ = _c(1);
let z;
if (props.a) {
if (props.b) {
let t0;

View File

@@ -67,6 +67,7 @@ function getNativeLogFunction(level) {
) {
logLevel = LOG_LEVELS.warn;
}
if (global.__inspectorLog) {
global.__inspectorLog(
INSPECTOR_LEVELS[logLevel],
@@ -75,6 +76,7 @@ function getNativeLogFunction(level) {
INSPECTOR_FRAMES_TO_SKIP,
);
}
if (groupStack.length) {
str = groupFormat("", str);
}

View File

@@ -47,9 +47,7 @@ function useKeyCommand() {
};
const moveLeft = { handler: handleKey("left") };
const moveRight = { handler: handleKey("right") };
t0 = [moveLeft, moveRight];
$[0] = t0;
} else {

View File

@@ -33,7 +33,6 @@ function Component() {
let y;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
y = x = {};
const foo = () => {
x = makeArray();
};

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