Compare commits
124 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1517c63a78 | ||
|
|
5fbe88ab40 | ||
|
|
4f34cc4a2e | ||
|
|
3d14fcf03f | ||
|
|
edac0dded9 | ||
|
|
3586a7f9e8 | ||
|
|
f6fb1a07a5 | ||
|
|
7513996f20 | ||
|
|
bb4418d647 | ||
|
|
074e92777c | ||
|
|
ac7da9d46d | ||
|
|
0dca9c2471 | ||
|
|
b9af1404ea | ||
|
|
e9638c33d7 | ||
|
|
28d4bc496b | ||
|
|
dffacc7b80 | ||
|
|
da7487b681 | ||
|
|
9fec565a9b | ||
|
|
996d0eb055 | ||
|
|
d85ec5f5bd | ||
|
|
fe813143e2 | ||
|
|
2f0e7e570d | ||
|
|
56d0ddae18 | ||
|
|
345ca24f13 | ||
|
|
97cdd5d3c3 | ||
|
|
eb7f8b42c9 | ||
|
|
eed2560762 | ||
|
|
463b808176 | ||
|
|
96c61b7f1f | ||
|
|
0bfa404bac | ||
|
|
81e1ee7476 | ||
|
|
4a3ff8eed6 | ||
|
|
ec4374c387 | ||
|
|
60b5271a9a | ||
|
|
033edca721 | ||
|
|
e6dc25daea | ||
|
|
150f022444 | ||
|
|
49ded1d12a | ||
|
|
3a43e72d66 | ||
|
|
8ba3501cd9 | ||
|
|
956d770adf | ||
|
|
d35fef9e21 | ||
|
|
a7a116577d | ||
|
|
777264b4ef | ||
|
|
befc1246b0 | ||
|
|
bbea677b77 | ||
|
|
f1ecf82bfb | ||
|
|
b44a99bf58 | ||
|
|
e4314a0a0f | ||
|
|
e43986f1f3 | ||
|
|
c932e45780 | ||
|
|
223f81d877 | ||
|
|
8a6c589be7 | ||
|
|
7cafeff340 | ||
|
|
0378b46e7e | ||
|
|
bb402876f7 | ||
|
|
9a645e1d10 | ||
|
|
2d7f0c4259 | ||
|
|
4aad5e45ba | ||
|
|
453a19a107 | ||
|
|
5d87cd2244 | ||
|
|
5f71eed2eb | ||
|
|
455424dbf3 | ||
|
|
9fd4c09d68 | ||
|
|
d45db667d4 | ||
|
|
3fc1bc6f28 | ||
|
|
ef8b6fa257 | ||
|
|
0b78161d7d | ||
|
|
dcf83f7c2d | ||
|
|
94fce500bc | ||
|
|
508f7aa78f | ||
|
|
e104795f63 | ||
|
|
c0d151ce7e | ||
|
|
fc41c24aa6 | ||
|
|
73aa744b70 | ||
|
|
602917c8cb | ||
|
|
91d097b2c5 | ||
|
|
7216c0f002 | ||
|
|
6a3d16ca74 | ||
|
|
65c4decb56 | ||
|
|
1e0d12b6f2 | ||
|
|
e9cab42ece | ||
|
|
3cfcdfb307 | ||
|
|
9c2a8dd5f8 | ||
|
|
811e203ed4 | ||
|
|
d92056efb3 | ||
|
|
58ac15cdc9 | ||
|
|
bfc8801e0f | ||
|
|
d2a288febf | ||
|
|
4db4b21c63 | ||
|
|
31d91651e0 | ||
|
|
9406162bc9 | ||
|
|
9b2a545b32 | ||
|
|
bb6c9d521e | ||
|
|
123ff13b19 | ||
|
|
e130c08b06 | ||
|
|
9894c488e0 | ||
|
|
cee7939b00 | ||
|
|
b42341ddc7 | ||
|
|
7a3ffef703 | ||
|
|
e67b4fe22e | ||
|
|
4a523489b7 | ||
|
|
94cf60bede | ||
|
|
bbc13fa17b | ||
|
|
12eaef7ef5 | ||
|
|
c80c69fa96 | ||
|
|
aab72cb1cb | ||
|
|
fa3feba672 | ||
|
|
2a911f27dd | ||
|
|
18ee505e77 | ||
|
|
1d1b26c701 | ||
|
|
fe3f0ec037 | ||
|
|
d70ee32b88 | ||
|
|
6c7b1a1d98 | ||
|
|
ed077194b5 | ||
|
|
643257ca52 | ||
|
|
06e89951be | ||
|
|
79d9aed7ed | ||
|
|
c8822e926b | ||
|
|
a947eba4f2 | ||
|
|
374dfe8edf | ||
|
|
2bee34867d | ||
|
|
d37faa041b | ||
|
|
3a2ff8b51b |
@@ -474,7 +474,7 @@ module.exports = {
|
||||
{
|
||||
files: ['packages/react-server-dom-turbopack/**/*.js'],
|
||||
globals: {
|
||||
__turbopack_load__: 'readonly',
|
||||
__turbopack_load_by_url__: 'readonly',
|
||||
__turbopack_require__: 'readonly',
|
||||
},
|
||||
},
|
||||
@@ -622,6 +622,7 @@ module.exports = {
|
||||
ScrollTimeline: 'readonly',
|
||||
EventListenerOptionsOrUseCapture: 'readonly',
|
||||
FocusOptions: 'readonly',
|
||||
OptionalEffectTiming: 'readonly',
|
||||
|
||||
spyOnDev: 'readonly',
|
||||
spyOnDevAndProd: 'readonly',
|
||||
|
||||
54
.github/workflows/runtime_build_and_test.yml
vendored
54
.github/workflows/runtime_build_and_test.yml
vendored
@@ -6,6 +6,12 @@ on:
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- compiler/**
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
commit_sha:
|
||||
required: false
|
||||
type: string
|
||||
default: ''
|
||||
|
||||
permissions: {}
|
||||
|
||||
@@ -28,7 +34,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
|
||||
- name: Check cache hit
|
||||
uses: actions/cache/restore@v4
|
||||
id: node_modules
|
||||
@@ -69,7 +75,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
|
||||
- name: Check cache hit
|
||||
uses: actions/cache/restore@v4
|
||||
id: node_modules
|
||||
@@ -117,7 +123,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/github-script@v7
|
||||
id: set-matrix
|
||||
with:
|
||||
@@ -136,7 +142,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
@@ -166,7 +172,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
@@ -198,7 +204,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
@@ -254,7 +260,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
@@ -301,10 +307,12 @@ jobs:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
|
||||
restore-keys: |
|
||||
runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
|
||||
runtime-and-compiler-node_modules-v6-
|
||||
- run: yarn install --frozen-lockfile
|
||||
- name: Install runtime dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
if: steps.node_modules.outputs.cache-hit != 'true'
|
||||
- name: Install compiler dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
working-directory: compiler
|
||||
if: steps.node_modules.outputs.cache-hit != 'true'
|
||||
- run: ./scripts/react-compiler/build-compiler.sh && ./scripts/react-compiler/link-compiler.sh
|
||||
- run: yarn workspace eslint-plugin-react-hooks test
|
||||
@@ -323,7 +331,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
@@ -418,7 +426,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
@@ -463,7 +471,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
@@ -491,7 +499,7 @@ jobs:
|
||||
merge-multiple: true
|
||||
- name: Display structure of build
|
||||
run: ls -R build
|
||||
- run: echo ${{ github.event.pull_request.head.sha || github.sha }} >> build/COMMIT_SHA
|
||||
- run: echo ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }} >> build/COMMIT_SHA
|
||||
- name: Scrape warning messages
|
||||
run: |
|
||||
mkdir -p ./build/__test_utils__
|
||||
@@ -528,7 +536,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
@@ -568,7 +576,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
@@ -605,7 +613,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
@@ -646,7 +654,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
@@ -720,7 +728,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
@@ -777,7 +785,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
@@ -822,7 +830,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
@@ -871,7 +879,7 @@ jobs:
|
||||
node ./scripts/print-warnings/print-warnings.js > build/__test_utils__/ReactAllWarnings.js
|
||||
- name: Display structure of build for PR
|
||||
run: ls -R build
|
||||
- run: echo ${{ github.event.pull_request.head.sha || github.sha }} >> build/COMMIT_SHA
|
||||
- run: echo ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }} >> build/COMMIT_SHA
|
||||
- run: node ./scripts/tasks/danger
|
||||
- name: Archive sizebot results
|
||||
uses: actions/upload-artifact@v4
|
||||
|
||||
8
.github/workflows/runtime_prereleases.yml
vendored
8
.github/workflows/runtime_prereleases.yml
vendored
@@ -85,7 +85,7 @@ jobs:
|
||||
--skipTests \
|
||||
--tags=${{ inputs.dist_tag }} \
|
||||
--onlyPackages=${{ inputs.only_packages }} ${{ (inputs.dry && '') || '\'}}
|
||||
${{ inputs.dry && '--dry'}}
|
||||
${{ inputs.dry && '--dry' || '' }}
|
||||
- if: '${{ inputs.skip_packages }}'
|
||||
name: 'Publish all packages EXCEPT ${{ inputs.skip_packages }}'
|
||||
run: |
|
||||
@@ -94,19 +94,19 @@ jobs:
|
||||
--skipTests \
|
||||
--tags=${{ inputs.dist_tag }} \
|
||||
--skipPackages=${{ inputs.skip_packages }} ${{ (inputs.dry && '') || '\'}}
|
||||
${{ inputs.dry && '--dry'}}
|
||||
${{ inputs.dry && '--dry' || '' }}
|
||||
- if: '${{ !(inputs.skip_packages && inputs.only_packages) }}'
|
||||
name: 'Publish all packages'
|
||||
run: |
|
||||
scripts/release/publish.js \
|
||||
--ci \
|
||||
--tags=${{ inputs.dist_tag }} ${{ (inputs.dry && '') || '\'}}
|
||||
${{ inputs.dry && '--dry'}}
|
||||
${{ inputs.dry && '--dry' || '' }}
|
||||
- name: Notify Discord on failure
|
||||
if: failure() && inputs.enableFailureNotification == true
|
||||
uses: tsickert/discord-webhook@86dc739f3f165f16dadc5666051c367efa1692f4
|
||||
with:
|
||||
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
|
||||
embed-author-name: "GitHub Actions"
|
||||
embed-title: 'Publish of $${{ inputs.release_channel }} release failed'
|
||||
embed-title: '[Runtime] Publish of ${{ inputs.release_channel }}@${{ inputs.dist_tag}} release failed'
|
||||
embed-url: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}/attempts/${{ github.run_attempt }}
|
||||
|
||||
@@ -110,7 +110,7 @@ jobs:
|
||||
--tags=${{ inputs.tags }} \
|
||||
--publishVersion=${{ inputs.version_to_publish }} \
|
||||
--onlyPackages=${{ inputs.only_packages }} ${{ (inputs.dry && '') || '\'}}
|
||||
${{ inputs.dry && '--dry'}}
|
||||
${{ inputs.dry && '--dry' || '' }}
|
||||
- if: '${{ inputs.skip_packages }}'
|
||||
name: 'Publish all packages EXCEPT ${{ inputs.skip_packages }}'
|
||||
run: |
|
||||
@@ -119,7 +119,7 @@ jobs:
|
||||
--tags=${{ inputs.tags }} \
|
||||
--publishVersion=${{ inputs.version_to_publish }} \
|
||||
--skipPackages=${{ inputs.skip_packages }} ${{ (inputs.dry && '') || '\'}}
|
||||
${{ inputs.dry && '--dry'}}
|
||||
${{ inputs.dry && '--dry' || '' }}
|
||||
- name: Archive released package for debugging
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
|
||||
@@ -19,11 +19,11 @@ An Owner Stack is a string representing the components that are directly respons
|
||||
* Updated `useId` to use valid CSS selectors, changing format from `:r123:` to `«r123»`. [#32001](https://github.com/facebook/react/pull/32001)
|
||||
* Added a dev-only warning for null/undefined created in useEffect, useInsertionEffect, and useLayoutEffect. [#32355](https://github.com/facebook/react/pull/32355)
|
||||
* Fixed a bug where dev-only methods were exported in production builds. React.act is no longer available in production builds. [#32200](https://github.com/facebook/react/pull/32200)
|
||||
* Improved consistency across prod and dev to improve compatibility with Google Closure Complier and bindings [#31808](https://github.com/facebook/react/pull/31808)
|
||||
* Improved consistency across prod and dev to improve compatibility with Google Closure Compiler and bindings [#31808](https://github.com/facebook/react/pull/31808)
|
||||
* Improve passive effect scheduling for consistent task yielding. [#31785](https://github.com/facebook/react/pull/31785)
|
||||
* Fixed asserts in React Native when passChildrenWhenCloningPersistedNodes is enabled for OffscreenComponent rendering. [#32528](https://github.com/facebook/react/pull/32528)
|
||||
* Fixed component name resolution for Portal [#32640](https://github.com/facebook/react/pull/32640)
|
||||
* Added support for beforetoggle and toggle events on the dialog element. #32479 [#32479](https://github.com/facebook/react/pull/32479)
|
||||
* Added support for beforetoggle and toggle events on the dialog element. [#32479](https://github.com/facebook/react/pull/32479)
|
||||
|
||||
### React DOM
|
||||
* Fixed double warning when the `href` attribute is an empty string [#31783](https://github.com/facebook/react/pull/31783)
|
||||
|
||||
@@ -44,6 +44,7 @@ import {
|
||||
PrintedCompilerPipelineValue,
|
||||
} from './Output';
|
||||
import {transformFromAstSync} from '@babel/core';
|
||||
import {LoggerEvent} from 'babel-plugin-react-compiler/dist/Entrypoint';
|
||||
|
||||
function parseInput(
|
||||
input: string,
|
||||
@@ -143,6 +144,7 @@ const COMMON_HOOKS: Array<[string, Hook]> = [
|
||||
function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] {
|
||||
const results = new Map<string, Array<PrintedCompilerPipelineValue>>();
|
||||
const error = new CompilerError();
|
||||
const otherErrors: Array<CompilerErrorDetail> = [];
|
||||
const upsert: (result: PrintedCompilerPipelineValue) => void = result => {
|
||||
const entry = results.get(result.name);
|
||||
if (Array.isArray(entry)) {
|
||||
@@ -210,7 +212,11 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] {
|
||||
},
|
||||
logger: {
|
||||
debugLogIRs: logIR,
|
||||
logEvent: () => {},
|
||||
logEvent: (_filename: string | null, event: LoggerEvent) => {
|
||||
if (event.kind === 'CompileError') {
|
||||
otherErrors.push(new CompilerErrorDetail(event.detail));
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
transformOutput = invokeCompiler(source, language, opts);
|
||||
@@ -237,6 +243,10 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] {
|
||||
);
|
||||
}
|
||||
}
|
||||
// Only include logger errors if there weren't other errors
|
||||
if (!error.hasErrors() && otherErrors.length !== 0) {
|
||||
otherErrors.forEach(e => error.push(e));
|
||||
}
|
||||
if (error.hasErrors()) {
|
||||
return [{kind: 'err', results, error: error}, language];
|
||||
}
|
||||
|
||||
@@ -15,6 +15,11 @@ export enum ErrorSeverity {
|
||||
* misunderstanding on the user’s part.
|
||||
*/
|
||||
InvalidJS = 'InvalidJS',
|
||||
/**
|
||||
* JS syntax that is not supported and which we do not plan to support. Developers should
|
||||
* rewrite to use supported forms.
|
||||
*/
|
||||
UnsupportedJS = 'UnsupportedJS',
|
||||
/**
|
||||
* Code that breaks the rules of React.
|
||||
*/
|
||||
@@ -241,12 +246,16 @@ export class CompilerError extends Error {
|
||||
case ErrorSeverity.InvalidJS:
|
||||
case ErrorSeverity.InvalidReact:
|
||||
case ErrorSeverity.InvalidConfig:
|
||||
case ErrorSeverity.UnsupportedJS: {
|
||||
return true;
|
||||
}
|
||||
case ErrorSeverity.CannotPreserveMemoization:
|
||||
case ErrorSeverity.Todo:
|
||||
case ErrorSeverity.Todo: {
|
||||
return false;
|
||||
default:
|
||||
}
|
||||
default: {
|
||||
assertExhaustive(detail.severity, 'Unhandled error severity');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ import {validateLocalsNotReassignedAfterRender} from '../Validation/ValidateLoca
|
||||
import {outlineFunctions} from '../Optimization/OutlineFunctions';
|
||||
import {propagatePhiTypes} from '../TypeInference/PropagatePhiTypes';
|
||||
import {lowerContextAccess} from '../Optimization/LowerContextAccess';
|
||||
import {validateNoSetStateInPassiveEffects} from '../Validation/ValidateNoSetStateInPassiveEffects';
|
||||
import {validateNoSetStateInEffects} from '../Validation/ValidateNoSetStateInEffects';
|
||||
import {validateNoJSXInTryStatement} from '../Validation/ValidateNoJSXInTryStatement';
|
||||
import {propagateScopeDependenciesHIR} from '../HIR/PropagateScopeDependenciesHIR';
|
||||
import {outlineJSX} from '../Optimization/OutlineJsx';
|
||||
@@ -292,8 +292,8 @@ function runWithEnvironment(
|
||||
validateNoSetStateInRender(hir).unwrap();
|
||||
}
|
||||
|
||||
if (env.config.validateNoSetStateInPassiveEffects) {
|
||||
env.logErrors(validateNoSetStateInPassiveEffects(hir));
|
||||
if (env.config.validateNoSetStateInEffects) {
|
||||
env.logErrors(validateNoSetStateInEffects(hir));
|
||||
}
|
||||
|
||||
if (env.config.validateNoJSXInTryStatements) {
|
||||
|
||||
@@ -35,8 +35,41 @@ function throwInvalidReact(
|
||||
});
|
||||
CompilerError.throw(detail);
|
||||
}
|
||||
|
||||
function isAutodepsSigil(
|
||||
arg: NodePath<t.ArgumentPlaceholder | t.SpreadElement | t.Expression>,
|
||||
): boolean {
|
||||
// Check for AUTODEPS identifier imported from React
|
||||
if (arg.isIdentifier() && arg.node.name === 'AUTODEPS') {
|
||||
const binding = arg.scope.getBinding(arg.node.name);
|
||||
if (binding && binding.path.isImportSpecifier()) {
|
||||
const importSpecifier = binding.path.node as t.ImportSpecifier;
|
||||
if (importSpecifier.imported.type === 'Identifier') {
|
||||
return (importSpecifier.imported as t.Identifier).name === 'AUTODEPS';
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for React.AUTODEPS member expression
|
||||
if (arg.isMemberExpression() && !arg.node.computed) {
|
||||
const object = arg.get('object');
|
||||
const property = arg.get('property');
|
||||
|
||||
if (
|
||||
object.isIdentifier() &&
|
||||
object.node.name === 'React' &&
|
||||
property.isIdentifier() &&
|
||||
property.node.name === 'AUTODEPS'
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
function assertValidEffectImportReference(
|
||||
numArgs: number,
|
||||
autodepsIndex: number,
|
||||
paths: Array<NodePath<t.Node>>,
|
||||
context: TraversalState,
|
||||
): void {
|
||||
@@ -49,11 +82,10 @@ function assertValidEffectImportReference(
|
||||
maybeCalleeLoc != null &&
|
||||
context.inferredEffectLocations.has(maybeCalleeLoc);
|
||||
/**
|
||||
* Only error on untransformed references of the form `useMyEffect(...)`
|
||||
* or `moduleNamespace.useMyEffect(...)`, with matching argument counts.
|
||||
* TODO: do we also want a mode to also hard error on non-call references?
|
||||
* Error on effect calls that still have AUTODEPS in their args
|
||||
*/
|
||||
if (args.length === numArgs && !hasInferredEffect) {
|
||||
const hasAutodepsArg = args.some(isAutodepsSigil);
|
||||
if (hasAutodepsArg && !hasInferredEffect) {
|
||||
const maybeErrorDiagnostic = matchCompilerDiagnostic(
|
||||
path,
|
||||
context.transformErrors,
|
||||
@@ -128,12 +160,12 @@ export default function validateNoUntransformedReferences(
|
||||
if (env.inferEffectDependencies) {
|
||||
for (const {
|
||||
function: {source, importSpecifierName},
|
||||
numRequiredArgs,
|
||||
autodepsIndex,
|
||||
} of env.inferEffectDependencies) {
|
||||
const module = getOrInsertWith(moduleLoadChecks, source, () => new Map());
|
||||
module.set(
|
||||
importSpecifierName,
|
||||
assertValidEffectImportReference.bind(null, numRequiredArgs),
|
||||
assertValidEffectImportReference.bind(null, autodepsIndex),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,7 +221,6 @@ export function lower(
|
||||
params,
|
||||
fnType: bindings == null ? env.fnType : 'Other',
|
||||
returnTypeAnnotation: null, // TODO: extract the actual return type node if present
|
||||
returnType: makeType(),
|
||||
returns: createTemporaryPlace(env, func.node.loc ?? GeneratedSource),
|
||||
body: builder.build(),
|
||||
context,
|
||||
@@ -1356,13 +1355,85 @@ function lowerStatement(
|
||||
|
||||
return;
|
||||
}
|
||||
case 'TypeAlias':
|
||||
case 'TSInterfaceDeclaration':
|
||||
case 'TSTypeAliasDeclaration': {
|
||||
// We do not preserve type annotations/syntax through transformation
|
||||
case 'WithStatement': {
|
||||
builder.errors.push({
|
||||
reason: `JavaScript 'with' syntax is not supported`,
|
||||
description: `'with' syntax is considered deprecated and removed from JavaScript standards, consider alternatives`,
|
||||
severity: ErrorSeverity.UnsupportedJS,
|
||||
loc: stmtPath.node.loc ?? null,
|
||||
suggestions: null,
|
||||
});
|
||||
lowerValueToTemporary(builder, {
|
||||
kind: 'UnsupportedNode',
|
||||
loc: stmtPath.node.loc ?? GeneratedSource,
|
||||
node: stmtPath.node,
|
||||
});
|
||||
return;
|
||||
}
|
||||
case 'ClassDeclaration': {
|
||||
/**
|
||||
* In theory we could support inline class declarations, but this is rare enough in practice
|
||||
* and complex enough to support that we don't anticipate supporting anytime soon. Developers
|
||||
* are encouraged to lift classes out of component/hook declarations.
|
||||
*/
|
||||
builder.errors.push({
|
||||
reason: 'Inline `class` declarations are not supported',
|
||||
description: `Move class declarations outside of components/hooks`,
|
||||
severity: ErrorSeverity.UnsupportedJS,
|
||||
loc: stmtPath.node.loc ?? null,
|
||||
suggestions: null,
|
||||
});
|
||||
lowerValueToTemporary(builder, {
|
||||
kind: 'UnsupportedNode',
|
||||
loc: stmtPath.node.loc ?? GeneratedSource,
|
||||
node: stmtPath.node,
|
||||
});
|
||||
return;
|
||||
}
|
||||
case 'EnumDeclaration':
|
||||
case 'TSEnumDeclaration': {
|
||||
lowerValueToTemporary(builder, {
|
||||
kind: 'UnsupportedNode',
|
||||
loc: stmtPath.node.loc ?? GeneratedSource,
|
||||
node: stmtPath.node,
|
||||
});
|
||||
return;
|
||||
}
|
||||
case 'ExportAllDeclaration':
|
||||
case 'ExportDefaultDeclaration':
|
||||
case 'ExportNamedDeclaration':
|
||||
case 'ImportDeclaration':
|
||||
case 'TSExportAssignment':
|
||||
case 'TSImportEqualsDeclaration': {
|
||||
builder.errors.push({
|
||||
reason:
|
||||
'JavaScript `import` and `export` statements may only appear at the top level of a module',
|
||||
severity: ErrorSeverity.InvalidJS,
|
||||
loc: stmtPath.node.loc ?? null,
|
||||
suggestions: null,
|
||||
});
|
||||
lowerValueToTemporary(builder, {
|
||||
kind: 'UnsupportedNode',
|
||||
loc: stmtPath.node.loc ?? GeneratedSource,
|
||||
node: stmtPath.node,
|
||||
});
|
||||
return;
|
||||
}
|
||||
case 'TSNamespaceExportDeclaration': {
|
||||
builder.errors.push({
|
||||
reason:
|
||||
'TypeScript `namespace` statements may only appear at the top level of a module',
|
||||
severity: ErrorSeverity.InvalidJS,
|
||||
loc: stmtPath.node.loc ?? null,
|
||||
suggestions: null,
|
||||
});
|
||||
lowerValueToTemporary(builder, {
|
||||
kind: 'UnsupportedNode',
|
||||
loc: stmtPath.node.loc ?? GeneratedSource,
|
||||
node: stmtPath.node,
|
||||
});
|
||||
return;
|
||||
}
|
||||
case 'ClassDeclaration':
|
||||
case 'DeclareClass':
|
||||
case 'DeclareExportAllDeclaration':
|
||||
case 'DeclareExportDeclaration':
|
||||
@@ -1373,31 +1444,14 @@ function lowerStatement(
|
||||
case 'DeclareOpaqueType':
|
||||
case 'DeclareTypeAlias':
|
||||
case 'DeclareVariable':
|
||||
case 'EnumDeclaration':
|
||||
case 'ExportAllDeclaration':
|
||||
case 'ExportDefaultDeclaration':
|
||||
case 'ExportNamedDeclaration':
|
||||
case 'ImportDeclaration':
|
||||
case 'InterfaceDeclaration':
|
||||
case 'OpaqueType':
|
||||
case 'TSDeclareFunction':
|
||||
case 'TSEnumDeclaration':
|
||||
case 'TSExportAssignment':
|
||||
case 'TSImportEqualsDeclaration':
|
||||
case 'TSInterfaceDeclaration':
|
||||
case 'TSModuleDeclaration':
|
||||
case 'TSNamespaceExportDeclaration':
|
||||
case 'WithStatement': {
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::lowerStatement) Handle ${stmtPath.type} statements`,
|
||||
severity: ErrorSeverity.Todo,
|
||||
loc: stmtPath.node.loc ?? null,
|
||||
suggestions: null,
|
||||
});
|
||||
lowerValueToTemporary(builder, {
|
||||
kind: 'UnsupportedNode',
|
||||
loc: stmtPath.node.loc ?? GeneratedSource,
|
||||
node: stmtPath.node,
|
||||
});
|
||||
case 'TSTypeAliasDeclaration':
|
||||
case 'TypeAlias': {
|
||||
// We do not preserve type annotations/syntax through transformation
|
||||
return;
|
||||
}
|
||||
default: {
|
||||
@@ -2947,6 +3001,8 @@ function isReorderableExpression(
|
||||
}
|
||||
}
|
||||
}
|
||||
case 'TSAsExpression':
|
||||
case 'TSNonNullExpression':
|
||||
case 'TypeCastExpression': {
|
||||
return isReorderableExpression(
|
||||
builder,
|
||||
@@ -3503,6 +3559,16 @@ function lowerIdentifier(
|
||||
return place;
|
||||
}
|
||||
default: {
|
||||
if (binding.kind === 'Global' && binding.name === 'eval') {
|
||||
builder.errors.push({
|
||||
reason: `The 'eval' function is not supported`,
|
||||
description:
|
||||
'Eval is an anti-pattern in JavaScript, and the code executed cannot be evaluated by React Compiler',
|
||||
severity: ErrorSeverity.UnsupportedJS,
|
||||
loc: exprPath.node.loc ?? null,
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
return lowerValueToTemporary(builder, {
|
||||
kind: 'LoadGlobal',
|
||||
binding,
|
||||
|
||||
@@ -265,21 +265,19 @@ export const EnvironmentConfigSchema = z.object({
|
||||
* {
|
||||
* module: 'react',
|
||||
* imported: 'useEffect',
|
||||
* numRequiredArgs: 1,
|
||||
* autodepsIndex: 1,
|
||||
* },{
|
||||
* module: 'MyExperimentalEffectHooks',
|
||||
* imported: 'useExperimentalEffect',
|
||||
* numRequiredArgs: 2,
|
||||
* autodepsIndex: 2,
|
||||
* },
|
||||
* ]
|
||||
* would insert dependencies for calls of `useEffect` imported from `react` and calls of
|
||||
* useExperimentalEffect` from `MyExperimentalEffectHooks`.
|
||||
*
|
||||
* `numRequiredArgs` tells the compiler the amount of arguments required to append a dependency
|
||||
* array to the end of the call. With the configuration above, we'd insert dependencies for
|
||||
* `useEffect` if it is only given a single argument and it would be appended to the argument list.
|
||||
*
|
||||
* numRequiredArgs must always be greater than 0, otherwise there is no function to analyze for dependencies
|
||||
* `autodepsIndex` tells the compiler which index we expect the AUTODEPS to appear in.
|
||||
* With the configuration above, we'd insert dependencies for `useEffect` if it has two
|
||||
* arguments, and the second is AUTODEPS.
|
||||
*
|
||||
* Still experimental.
|
||||
*/
|
||||
@@ -288,7 +286,7 @@ export const EnvironmentConfigSchema = z.object({
|
||||
z.array(
|
||||
z.object({
|
||||
function: ExternalFunctionSchema,
|
||||
numRequiredArgs: z.number().min(1, 'numRequiredArgs must be > 0'),
|
||||
autodepsIndex: z.number().min(1, 'autodepsIndex must be > 0'),
|
||||
}),
|
||||
),
|
||||
)
|
||||
@@ -320,10 +318,10 @@ export const EnvironmentConfigSchema = z.object({
|
||||
validateNoSetStateInRender: z.boolean().default(true),
|
||||
|
||||
/**
|
||||
* Validates that setState is not called directly within a passive effect (useEffect).
|
||||
* Validates that setState is not called synchronously within an effect (useEffect and friends).
|
||||
* Scheduling a setState (with an event listener, subscription, etc) is valid.
|
||||
*/
|
||||
validateNoSetStateInPassiveEffects: z.boolean().default(false),
|
||||
validateNoSetStateInEffects: z.boolean().default(false),
|
||||
|
||||
/**
|
||||
* Validates against creating JSX within a try block and recommends using an error boundary
|
||||
|
||||
@@ -9,6 +9,7 @@ import {Effect, ValueKind, ValueReason} from './HIR';
|
||||
import {
|
||||
BUILTIN_SHAPES,
|
||||
BuiltInArrayId,
|
||||
BuiltInAutodepsId,
|
||||
BuiltInFireFunctionId,
|
||||
BuiltInFireId,
|
||||
BuiltInMapId,
|
||||
@@ -780,6 +781,7 @@ const REACT_APIS: Array<[string, BuiltInType]> = [
|
||||
BuiltInUseEffectEventId,
|
||||
),
|
||||
],
|
||||
['AUTODEPS', addObject(DEFAULT_SHAPES, BuiltInAutodepsId, [])],
|
||||
];
|
||||
|
||||
TYPED_GLOBALS.push(
|
||||
|
||||
@@ -279,7 +279,6 @@ export type HIRFunction = {
|
||||
env: Environment;
|
||||
params: Array<Place | SpreadPattern>;
|
||||
returnTypeAnnotation: t.FlowType | t.TSType | null;
|
||||
returnType: Type;
|
||||
returns: Place;
|
||||
context: Array<Place>;
|
||||
effects: Array<FunctionEffect> | null;
|
||||
@@ -1770,6 +1769,10 @@ export function isUseStateType(id: Identifier): boolean {
|
||||
return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInUseState';
|
||||
}
|
||||
|
||||
export function isJsxType(type: Type): boolean {
|
||||
return type.kind === 'Object' && type.shapeId === 'BuiltInJsx';
|
||||
}
|
||||
|
||||
export function isRefOrRefValue(id: Identifier): boolean {
|
||||
return isUseRefType(id) || isRefValueType(id);
|
||||
}
|
||||
|
||||
@@ -107,6 +107,17 @@ export function mergeConsecutiveBlocks(fn: HIRFunction): void {
|
||||
merged.merge(block.id, predecessorId);
|
||||
fn.body.blocks.delete(block.id);
|
||||
}
|
||||
for (const [, block] of fn.body.blocks) {
|
||||
for (const phi of block.phis) {
|
||||
for (const [predecessorId, operand] of phi.operands) {
|
||||
const mapped = merged.get(predecessorId);
|
||||
if (mapped !== predecessorId) {
|
||||
phi.operands.delete(predecessorId);
|
||||
phi.operands.set(mapped, operand);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
markPredecessors(fn.body);
|
||||
for (const [, {terminal}] of fn.body.blocks) {
|
||||
if (terminalHasFallthrough(terminal)) {
|
||||
|
||||
@@ -384,6 +384,7 @@ export const BuiltInFireId = 'BuiltInFire';
|
||||
export const BuiltInFireFunctionId = 'BuiltInFireFunction';
|
||||
export const BuiltInUseEffectEventId = 'BuiltInUseEffectEvent';
|
||||
export const BuiltinEffectEventId = 'BuiltInEffectEventFunction';
|
||||
export const BuiltInAutodepsId = 'BuiltInAutoDepsId';
|
||||
|
||||
// See getReanimatedModuleType() in Globals.ts — this is part of supporting Reanimated's ref-like types
|
||||
export const ReanimatedSharedValueId = 'ReanimatedSharedValueId';
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import generate from '@babel/generator';
|
||||
import {CompilerError} from '../CompilerError';
|
||||
import {printReactiveScopeSummary} from '../ReactiveScopes/PrintReactiveFunction';
|
||||
import DisjointSet from '../Utils/DisjointSet';
|
||||
@@ -54,6 +53,8 @@ export function printFunction(fn: HIRFunction): string {
|
||||
let definition = '';
|
||||
if (fn.id !== null) {
|
||||
definition += fn.id;
|
||||
} else {
|
||||
definition += '<<anonymous>>';
|
||||
}
|
||||
if (fn.params.length !== 0) {
|
||||
definition +=
|
||||
@@ -71,10 +72,8 @@ export function printFunction(fn: HIRFunction): string {
|
||||
} else {
|
||||
definition += '()';
|
||||
}
|
||||
if (definition.length !== 0) {
|
||||
output.push(definition);
|
||||
}
|
||||
output.push(`: ${printType(fn.returnType)} @ ${printPlace(fn.returns)}`);
|
||||
definition += `: ${printPlace(fn.returns)}`;
|
||||
output.push(definition);
|
||||
output.push(...fn.directives);
|
||||
output.push(printHIR(fn.body));
|
||||
return output.join('\n');
|
||||
@@ -466,7 +465,7 @@ export function printInstructionValue(instrValue: ReactiveValue): string {
|
||||
break;
|
||||
}
|
||||
case 'UnsupportedNode': {
|
||||
value = `UnsupportedNode(${generate(instrValue.node).code})`;
|
||||
value = `UnsupportedNode ${instrValue.node.type}`;
|
||||
break;
|
||||
}
|
||||
case 'LoadLocal': {
|
||||
@@ -715,7 +714,7 @@ export function printInstructionValue(instrValue: ReactiveValue): string {
|
||||
break;
|
||||
}
|
||||
case 'FinishMemoize': {
|
||||
value = `FinishMemoize decl=${printPlace(instrValue.decl)}`;
|
||||
value = `FinishMemoize decl=${printPlace(instrValue.decl)}${instrValue.pruned ? ' pruned' : ''}`;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
|
||||
@@ -20,11 +20,9 @@ import {inferReactiveScopeVariables} from '../ReactiveScopes';
|
||||
import {rewriteInstructionKindsBasedOnReassignment} from '../SSA';
|
||||
import {inferMutableRanges} from './InferMutableRanges';
|
||||
import inferReferenceEffects from './InferReferenceEffects';
|
||||
import {assertExhaustive, retainWhere} from '../Utils/utils';
|
||||
import {assertExhaustive} from '../Utils/utils';
|
||||
import {inferMutationAliasingEffects} from './InferMutationAliasingEffects';
|
||||
import {inferFunctionExpressionAliasingEffectsSignature} from './InferFunctionExpressionAliasingEffectsSignature';
|
||||
import {inferMutationAliasingRanges} from './InferMutationAliasingRanges';
|
||||
import {hashEffect} from './AliasingEffects';
|
||||
|
||||
export default function analyseFunctions(func: HIRFunction): void {
|
||||
for (const [_, block] of func.body.blocks) {
|
||||
@@ -69,30 +67,12 @@ function lowerWithMutationAliasing(fn: HIRFunction): void {
|
||||
analyseFunctions(fn);
|
||||
inferMutationAliasingEffects(fn, {isFunctionExpression: true});
|
||||
deadCodeElimination(fn);
|
||||
inferMutationAliasingRanges(fn, {isFunctionExpression: true});
|
||||
const functionEffects = inferMutationAliasingRanges(fn, {
|
||||
isFunctionExpression: true,
|
||||
}).unwrap();
|
||||
rewriteInstructionKindsBasedOnReassignment(fn);
|
||||
inferReactiveScopeVariables(fn);
|
||||
const effects = inferFunctionExpressionAliasingEffectsSignature(fn);
|
||||
fn.env.logger?.debugLogIRs?.({
|
||||
kind: 'hir',
|
||||
name: 'AnalyseFunction (inner)',
|
||||
value: fn,
|
||||
});
|
||||
if (effects != null) {
|
||||
fn.aliasingEffects ??= [];
|
||||
fn.aliasingEffects?.push(...effects);
|
||||
}
|
||||
if (fn.aliasingEffects != null) {
|
||||
const seen = new Set<string>();
|
||||
retainWhere(fn.aliasingEffects, effect => {
|
||||
const hash = hashEffect(effect);
|
||||
if (seen.has(hash)) {
|
||||
return false;
|
||||
}
|
||||
seen.add(hash);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
fn.aliasingEffects = functionEffects;
|
||||
|
||||
/**
|
||||
* Phase 2: populate the Effect of each context variable to use in inferring
|
||||
@@ -100,7 +80,7 @@ function lowerWithMutationAliasing(fn: HIRFunction): void {
|
||||
* effects to decide if the function may be mutable or not.
|
||||
*/
|
||||
const capturedOrMutated = new Set<IdentifierId>();
|
||||
for (const effect of effects ?? []) {
|
||||
for (const effect of functionEffects) {
|
||||
switch (effect.kind) {
|
||||
case 'Assign':
|
||||
case 'Alias':
|
||||
@@ -152,6 +132,12 @@ function lowerWithMutationAliasing(fn: HIRFunction): void {
|
||||
operand.effect = Effect.Read;
|
||||
}
|
||||
}
|
||||
|
||||
fn.env.logger?.debugLogIRs?.({
|
||||
kind: 'hir',
|
||||
name: 'AnalyseFunction (inner)',
|
||||
value: fn,
|
||||
});
|
||||
}
|
||||
|
||||
function lower(func: HIRFunction): void {
|
||||
|
||||
@@ -57,6 +57,8 @@ import {
|
||||
} from '../HIR/visitors';
|
||||
import {empty} from '../Utils/Stack';
|
||||
import {getOrInsertWith} from '../Utils/utils';
|
||||
import {deadCodeElimination} from '../Optimization';
|
||||
import {BuiltInAutodepsId} from '../HIR/ObjectShape';
|
||||
|
||||
/**
|
||||
* Infers reactive dependencies captured by useEffect lambdas and adds them as
|
||||
@@ -77,7 +79,7 @@ export function inferEffectDependencies(fn: HIRFunction): void {
|
||||
);
|
||||
moduleTargets.set(
|
||||
effectTarget.function.importSpecifierName,
|
||||
effectTarget.numRequiredArgs,
|
||||
effectTarget.autodepsIndex,
|
||||
);
|
||||
}
|
||||
const autodepFnLoads = new Map<IdentifierId, number>();
|
||||
@@ -135,7 +137,6 @@ export function inferEffectDependencies(fn: HIRFunction): void {
|
||||
}
|
||||
} else if (value.kind === 'LoadGlobal') {
|
||||
loadGlobals.add(lvalue.identifier.id);
|
||||
|
||||
/*
|
||||
* TODO: Handle properties on default exports, like
|
||||
* import React from 'react';
|
||||
@@ -169,8 +170,22 @@ export function inferEffectDependencies(fn: HIRFunction): void {
|
||||
) {
|
||||
const callee =
|
||||
value.kind === 'CallExpression' ? value.callee : value.property;
|
||||
|
||||
const autodepsArgIndex = value.args.findIndex(
|
||||
arg =>
|
||||
arg.kind === 'Identifier' &&
|
||||
arg.identifier.type.kind === 'Object' &&
|
||||
arg.identifier.type.shapeId === BuiltInAutodepsId,
|
||||
);
|
||||
const autodepsArgExpectedIndex = autodepFnLoads.get(
|
||||
callee.identifier.id,
|
||||
);
|
||||
|
||||
if (
|
||||
value.args.length === autodepFnLoads.get(callee.identifier.id) &&
|
||||
value.args.length > 0 &&
|
||||
autodepsArgExpectedIndex != null &&
|
||||
autodepsArgIndex === autodepsArgExpectedIndex &&
|
||||
autodepFnLoads.has(callee.identifier.id) &&
|
||||
value.args[0].kind === 'Identifier'
|
||||
) {
|
||||
// We have a useEffect call with no deps array, so we need to infer the deps
|
||||
@@ -260,7 +275,10 @@ export function inferEffectDependencies(fn: HIRFunction): void {
|
||||
effects: null,
|
||||
},
|
||||
});
|
||||
value.args.push({...depsPlace, effect: Effect.Freeze});
|
||||
value.args[autodepsArgIndex] = {
|
||||
...depsPlace,
|
||||
effect: Effect.Freeze,
|
||||
};
|
||||
fn.env.inferredEffectLocations.add(callee.loc);
|
||||
} else if (loadGlobals.has(value.args[0].identifier.id)) {
|
||||
// Global functions have no reactive dependencies, so we can insert an empty array
|
||||
@@ -275,7 +293,10 @@ export function inferEffectDependencies(fn: HIRFunction): void {
|
||||
effects: null,
|
||||
},
|
||||
});
|
||||
value.args.push({...depsPlace, effect: Effect.Freeze});
|
||||
value.args[autodepsArgIndex] = {
|
||||
...depsPlace,
|
||||
effect: Effect.Freeze,
|
||||
};
|
||||
fn.env.inferredEffectLocations.add(callee.loc);
|
||||
}
|
||||
} else if (
|
||||
@@ -323,6 +344,7 @@ export function inferEffectDependencies(fn: HIRFunction): void {
|
||||
// Renumber instructions and fix scope ranges
|
||||
markInstructionIds(fn.body);
|
||||
fixScopeAndIdentifierRanges(fn.body);
|
||||
deadCodeElimination(fn);
|
||||
|
||||
fn.env.hasInferredEffect = true;
|
||||
}
|
||||
@@ -408,6 +430,7 @@ function rewriteSplices(
|
||||
rewriteBlocks.push(currBlock);
|
||||
|
||||
let cursor = 0;
|
||||
|
||||
for (const rewrite of splices) {
|
||||
while (originalInstrs[cursor].id < rewrite.location) {
|
||||
CompilerError.invariant(
|
||||
@@ -429,7 +452,7 @@ function rewriteSplices(
|
||||
|
||||
if (rewrite.kind === 'instr') {
|
||||
currBlock.instructions.push(rewrite.value);
|
||||
} else {
|
||||
} else if (rewrite.kind === 'block') {
|
||||
const {entry, blocks} = rewrite.value;
|
||||
const entryBlock = blocks.get(entry)!;
|
||||
// splice in all instructions from the entry block
|
||||
|
||||
@@ -1,206 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {HIRFunction, IdentifierId, Place, ValueKind, ValueReason} from '../HIR';
|
||||
import {getOrInsertDefault} from '../Utils/utils';
|
||||
import {AliasingEffect} from './AliasingEffects';
|
||||
|
||||
/**
|
||||
* This function tracks data flow within an inner function expression in order to
|
||||
* compute a set of data-flow aliasing effects describing data flow between the function's
|
||||
* params, context variables, and return value.
|
||||
*
|
||||
* For example, consider the following function expression:
|
||||
*
|
||||
* ```
|
||||
* (x) => { return [x, y] }
|
||||
* ```
|
||||
*
|
||||
* This function captures both param `x` and context variable `y` into the return value.
|
||||
* Unlike our previous inference which counted this as a mutation of x and y, we want to
|
||||
* build a signature for the function that describes the data flow. We would infer
|
||||
* `Capture x -> return, Capture y -> return` effects for this function.
|
||||
*
|
||||
* This function *also* propagates more ambient-style effects (MutateFrozen, MutateGlobal, Impure, Render)
|
||||
* from instructions within the function up to the function itself.
|
||||
*/
|
||||
export function inferFunctionExpressionAliasingEffectsSignature(
|
||||
fn: HIRFunction,
|
||||
): Array<AliasingEffect> | null {
|
||||
const effects: Array<AliasingEffect> = [];
|
||||
|
||||
/**
|
||||
* Map used to identify tracked variables: params, context vars, return value
|
||||
* This is used to detect mutation/capturing/aliasing of params/context vars
|
||||
*/
|
||||
const tracked = new Map<IdentifierId, Place>();
|
||||
tracked.set(fn.returns.identifier.id, fn.returns);
|
||||
for (const operand of [...fn.context, ...fn.params]) {
|
||||
const place = operand.kind === 'Identifier' ? operand : operand.place;
|
||||
tracked.set(place.identifier.id, place);
|
||||
}
|
||||
|
||||
/**
|
||||
* Track capturing/aliasing of context vars and params into each other and into the return.
|
||||
* We don't need to track locals and intermediate values, since we're only concerned with effects
|
||||
* as they relate to arguments visible outside the function.
|
||||
*
|
||||
* For each aliased identifier we track capture/alias/createfrom and then merge this with how
|
||||
* the value is used. Eg capturing an alias => capture. See joinEffects() helper.
|
||||
*/
|
||||
type AliasedIdentifier = {
|
||||
kind: AliasingKind;
|
||||
place: Place;
|
||||
};
|
||||
const dataFlow = new Map<IdentifierId, Array<AliasedIdentifier>>();
|
||||
|
||||
/*
|
||||
* Check for aliasing of tracked values. Also joins the effects of how the value is
|
||||
* used (@param kind) with the aliasing type of each value
|
||||
*/
|
||||
function lookup(
|
||||
place: Place,
|
||||
kind: AliasedIdentifier['kind'],
|
||||
): Array<AliasedIdentifier> | null {
|
||||
if (tracked.has(place.identifier.id)) {
|
||||
return [{kind, place}];
|
||||
}
|
||||
return (
|
||||
dataFlow.get(place.identifier.id)?.map(aliased => ({
|
||||
kind: joinEffects(aliased.kind, kind),
|
||||
place: aliased.place,
|
||||
})) ?? null
|
||||
);
|
||||
}
|
||||
|
||||
// todo: fixpoint
|
||||
for (const block of fn.body.blocks.values()) {
|
||||
for (const phi of block.phis) {
|
||||
const operands: Array<AliasedIdentifier> = [];
|
||||
for (const operand of phi.operands.values()) {
|
||||
const inputs = lookup(operand, 'Alias');
|
||||
if (inputs != null) {
|
||||
operands.push(...inputs);
|
||||
}
|
||||
}
|
||||
if (operands.length !== 0) {
|
||||
dataFlow.set(phi.place.identifier.id, operands);
|
||||
}
|
||||
}
|
||||
for (const instr of block.instructions) {
|
||||
if (instr.effects == null) continue;
|
||||
for (const effect of instr.effects) {
|
||||
if (
|
||||
effect.kind === 'Assign' ||
|
||||
effect.kind === 'Capture' ||
|
||||
effect.kind === 'Alias' ||
|
||||
effect.kind === 'CreateFrom'
|
||||
) {
|
||||
const from = lookup(effect.from, effect.kind);
|
||||
if (from == null) {
|
||||
continue;
|
||||
}
|
||||
const into = lookup(effect.into, 'Alias');
|
||||
if (into == null) {
|
||||
getOrInsertDefault(dataFlow, effect.into.identifier.id, []).push(
|
||||
...from,
|
||||
);
|
||||
} else {
|
||||
for (const aliased of into) {
|
||||
getOrInsertDefault(
|
||||
dataFlow,
|
||||
aliased.place.identifier.id,
|
||||
[],
|
||||
).push(...from);
|
||||
}
|
||||
}
|
||||
} else if (
|
||||
effect.kind === 'Create' ||
|
||||
effect.kind === 'CreateFunction'
|
||||
) {
|
||||
getOrInsertDefault(dataFlow, effect.into.identifier.id, [
|
||||
{kind: 'Alias', place: effect.into},
|
||||
]);
|
||||
} else if (
|
||||
effect.kind === 'MutateFrozen' ||
|
||||
effect.kind === 'MutateGlobal' ||
|
||||
effect.kind === 'Impure' ||
|
||||
effect.kind === 'Render'
|
||||
) {
|
||||
effects.push(effect);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (block.terminal.kind === 'return') {
|
||||
const from = lookup(block.terminal.value, 'Alias');
|
||||
if (from != null) {
|
||||
getOrInsertDefault(dataFlow, fn.returns.identifier.id, []).push(
|
||||
...from,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create aliasing effects based on observed data flow
|
||||
let hasReturn = false;
|
||||
for (const [into, from] of dataFlow) {
|
||||
const input = tracked.get(into);
|
||||
if (input == null) {
|
||||
continue;
|
||||
}
|
||||
for (const aliased of from) {
|
||||
if (
|
||||
aliased.place.identifier.id === input.identifier.id ||
|
||||
!tracked.has(aliased.place.identifier.id)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
const effect = {kind: aliased.kind, from: aliased.place, into: input};
|
||||
effects.push(effect);
|
||||
if (
|
||||
into === fn.returns.identifier.id &&
|
||||
(aliased.kind === 'Assign' || aliased.kind === 'CreateFrom')
|
||||
) {
|
||||
hasReturn = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: more precise return effect inference
|
||||
if (!hasReturn) {
|
||||
effects.unshift({
|
||||
kind: 'Create',
|
||||
into: fn.returns,
|
||||
value:
|
||||
fn.returnType.kind === 'Primitive'
|
||||
? ValueKind.Primitive
|
||||
: ValueKind.Mutable,
|
||||
reason: ValueReason.KnownReturnSignature,
|
||||
});
|
||||
}
|
||||
|
||||
return effects;
|
||||
}
|
||||
|
||||
export enum MutationKind {
|
||||
None = 0,
|
||||
Conditional = 1,
|
||||
Definite = 2,
|
||||
}
|
||||
|
||||
type AliasingKind = 'Alias' | 'Capture' | 'CreateFrom' | 'Assign';
|
||||
function joinEffects(
|
||||
effect1: AliasingKind,
|
||||
effect2: AliasingKind,
|
||||
): AliasingKind {
|
||||
if (effect1 === 'Capture' || effect2 === 'Capture') {
|
||||
return 'Capture';
|
||||
} else if (effect1 === 'Assign' || effect2 === 'Assign') {
|
||||
return 'Assign';
|
||||
} else {
|
||||
return 'Alias';
|
||||
}
|
||||
}
|
||||
@@ -57,7 +57,6 @@ import {
|
||||
import {
|
||||
printAliasingEffect,
|
||||
printAliasingSignature,
|
||||
printFunction,
|
||||
printIdentifier,
|
||||
printInstruction,
|
||||
printInstructionValue,
|
||||
@@ -194,19 +193,15 @@ export function inferMutationAliasingEffects(
|
||||
hoistedContextDeclarations,
|
||||
);
|
||||
|
||||
let count = 0;
|
||||
let iterationCount = 0;
|
||||
while (queuedStates.size !== 0) {
|
||||
count++;
|
||||
if (count > 100) {
|
||||
console.log(
|
||||
'oops infinite loop',
|
||||
fn.id,
|
||||
typeof fn.loc !== 'symbol' ? fn.loc?.filename : null,
|
||||
);
|
||||
if (DEBUG) {
|
||||
console.log(printFunction(fn));
|
||||
}
|
||||
throw new Error('infinite loop');
|
||||
iterationCount++;
|
||||
if (iterationCount > 100) {
|
||||
CompilerError.invariant(false, {
|
||||
reason: `[InferMutationAliasingEffects] Potential infinite loop`,
|
||||
description: `A value, temporary place, or effect was not cached properly`,
|
||||
loc: fn.loc,
|
||||
});
|
||||
}
|
||||
for (const [blockId, block] of fn.body.blocks) {
|
||||
const incomingState = queuedStates.get(blockId);
|
||||
@@ -217,11 +212,6 @@ export function inferMutationAliasingEffects(
|
||||
|
||||
statesByBlock.set(blockId, incomingState);
|
||||
const state = incomingState.clone();
|
||||
if (DEBUG) {
|
||||
console.log('*************');
|
||||
console.log(`bb${block.id}`);
|
||||
console.log('*************');
|
||||
}
|
||||
inferBlock(context, state, block);
|
||||
|
||||
for (const nextBlockId of eachTerminalSuccessor(block.terminal)) {
|
||||
@@ -832,7 +822,8 @@ function applyEffect(
|
||||
const functionValues = state.values(effect.function);
|
||||
if (
|
||||
functionValues.length === 1 &&
|
||||
functionValues[0].kind === 'FunctionExpression'
|
||||
functionValues[0].kind === 'FunctionExpression' &&
|
||||
functionValues[0].loweredFunc.func.aliasingEffects != null
|
||||
) {
|
||||
/*
|
||||
* We're calling a locally declared function, we already know it's effects!
|
||||
@@ -867,9 +858,6 @@ function applyEffect(
|
||||
),
|
||||
);
|
||||
if (signatureEffects != null) {
|
||||
if (DEBUG) {
|
||||
console.log('apply function expression effects');
|
||||
}
|
||||
applyEffect(
|
||||
context,
|
||||
state,
|
||||
@@ -902,16 +890,10 @@ function applyEffect(
|
||||
);
|
||||
}
|
||||
if (signatureEffects != null) {
|
||||
if (DEBUG) {
|
||||
console.log('apply aliasing signature effects');
|
||||
}
|
||||
for (const signatureEffect of signatureEffects) {
|
||||
applyEffect(context, state, signatureEffect, initialized, effects);
|
||||
}
|
||||
} else if (effect.signature != null) {
|
||||
if (DEBUG) {
|
||||
console.log('apply legacy signature effects');
|
||||
}
|
||||
const legacyEffects = computeEffectsForLegacySignature(
|
||||
state,
|
||||
effect.signature,
|
||||
@@ -924,9 +906,6 @@ function applyEffect(
|
||||
applyEffect(context, state, legacyEffect, initialized, effects);
|
||||
}
|
||||
} else {
|
||||
if (DEBUG) {
|
||||
console.log('default effects');
|
||||
}
|
||||
applyEffect(
|
||||
context,
|
||||
state,
|
||||
@@ -1292,9 +1271,6 @@ class InferenceState {
|
||||
kind: ValueKind.Frozen,
|
||||
reason: new Set([reason]),
|
||||
});
|
||||
if (DEBUG) {
|
||||
console.log(`freeze value: ${printInstructionValue(value)} ${reason}`);
|
||||
}
|
||||
if (
|
||||
value.kind === 'FunctionExpression' &&
|
||||
(this.env.config.enablePreserveExistingMemoizationGuarantees ||
|
||||
@@ -2151,8 +2127,6 @@ function computeEffectsForLegacySignature(
|
||||
const mutateIterator = conditionallyMutateIterator(place);
|
||||
if (mutateIterator != null) {
|
||||
effects.push(mutateIterator);
|
||||
// TODO: should we always push to captures?
|
||||
captures.push(place);
|
||||
}
|
||||
effects.push({
|
||||
kind: 'Capture',
|
||||
@@ -2334,17 +2308,6 @@ function computeEffectsForSignature(
|
||||
// Too many args and there is no rest param to hold them
|
||||
(args.length > signature.params.length && signature.rest == null)
|
||||
) {
|
||||
if (DEBUG) {
|
||||
if (signature.params.length > args.length) {
|
||||
console.log(
|
||||
`not enough args: ${args.length} args for ${signature.params.length} params`,
|
||||
);
|
||||
} else {
|
||||
console.log(
|
||||
`too many args: ${args.length} args for ${signature.params.length} params, with no rest param`,
|
||||
);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
// Build substitutions
|
||||
@@ -2359,9 +2322,6 @@ function computeEffectsForSignature(
|
||||
continue;
|
||||
} else if (params == null || i >= params.length || arg.kind === 'Spread') {
|
||||
if (signature.rest == null) {
|
||||
if (DEBUG) {
|
||||
console.log(`no rest value to hold param`);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
const place = arg.kind === 'Identifier' ? arg : arg.place;
|
||||
@@ -2469,23 +2429,14 @@ function computeEffectsForSignature(
|
||||
case 'Apply': {
|
||||
const applyReceiver = substitutions.get(effect.receiver.identifier.id);
|
||||
if (applyReceiver == null || applyReceiver.length !== 1) {
|
||||
if (DEBUG) {
|
||||
console.log(`too many substitutions for receiver`);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
const applyFunction = substitutions.get(effect.function.identifier.id);
|
||||
if (applyFunction == null || applyFunction.length !== 1) {
|
||||
if (DEBUG) {
|
||||
console.log(`too many substitutions for function`);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
const applyInto = substitutions.get(effect.into.identifier.id);
|
||||
if (applyInto == null || applyInto.length !== 1) {
|
||||
if (DEBUG) {
|
||||
console.log(`too many substitutions for into`);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
const applyArgs: Array<Place | SpreadPattern | Hole> = [];
|
||||
@@ -2495,18 +2446,12 @@ function computeEffectsForSignature(
|
||||
} else if (arg.kind === 'Identifier') {
|
||||
const applyArg = substitutions.get(arg.identifier.id);
|
||||
if (applyArg == null || applyArg.length !== 1) {
|
||||
if (DEBUG) {
|
||||
console.log(`too many substitutions for arg`);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
applyArgs.push(applyArg[0]);
|
||||
} else {
|
||||
const applyArg = substitutions.get(arg.place.identifier.id);
|
||||
if (applyArg == null || applyArg.length !== 1) {
|
||||
if (DEBUG) {
|
||||
console.log(`too many substitutions for arg`);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
applyArgs.push({kind: 'Spread', place: applyArg[0]});
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import prettyFormat from 'pretty-format';
|
||||
import {CompilerError, SourceLocation} from '..';
|
||||
import {
|
||||
BlockId,
|
||||
@@ -14,8 +13,12 @@ import {
|
||||
Identifier,
|
||||
IdentifierId,
|
||||
InstructionId,
|
||||
isJsxType,
|
||||
makeInstructionId,
|
||||
ValueKind,
|
||||
ValueReason,
|
||||
Place,
|
||||
isPrimitiveType,
|
||||
} from '../HIR/HIR';
|
||||
import {
|
||||
eachInstructionLValue,
|
||||
@@ -23,43 +26,58 @@ import {
|
||||
eachTerminalOperand,
|
||||
} from '../HIR/visitors';
|
||||
import {assertExhaustive, getOrInsertWith} from '../Utils/utils';
|
||||
import {printFunction} from '../HIR';
|
||||
import {printIdentifier, printPlace} from '../HIR/PrintHIR';
|
||||
import {MutationKind} from './InferFunctionExpressionAliasingEffectsSignature';
|
||||
import {Result} from '../Utils/Result';
|
||||
|
||||
const DEBUG = false;
|
||||
const VERBOSE = false;
|
||||
import {Err, Ok, Result} from '../Utils/Result';
|
||||
import {AliasingEffect} from './AliasingEffects';
|
||||
|
||||
/**
|
||||
* Infers mutable ranges for all values in the program, using previously inferred
|
||||
* mutation/aliasing effects. This pass builds a data flow graph using the effects,
|
||||
* tracking an abstract notion of "when" each effect occurs relative to the others.
|
||||
* It then walks each mutation effect against the graph, updating the range of each
|
||||
* node that would be reachable at the "time" that the effect occurred.
|
||||
* This pass builds an abstract model of the heap and interprets the effects of the
|
||||
* given function in order to determine the following:
|
||||
* - The mutable ranges of all identifiers in the function
|
||||
* - The externally-visible effects of the function, such as mutations of params and
|
||||
* context-vars, aliasing between params/context-vars/return-value, and impure side
|
||||
* effects.
|
||||
* - The legacy `Effect` to store on each Place.
|
||||
*
|
||||
* This pass builds a data flow graph using the effects, tracking an abstract notion
|
||||
* of "when" each effect occurs relative to the others. It then walks each mutation
|
||||
* effect against the graph, updating the range of each node that would be reachable
|
||||
* at the "time" that the effect occurred.
|
||||
*
|
||||
* This pass also validates against invalid effects: any function that is reachable
|
||||
* by being called, or via a Render effect, is validated against mutating globals
|
||||
* or calling impure code.
|
||||
*
|
||||
* Note that this function also populates the outer function's aliasing effects with
|
||||
* any mutations that apply to its params or context variables. For example, a
|
||||
* function expression such as the following:
|
||||
* any mutations that apply to its params or context variables.
|
||||
*
|
||||
* ## Example
|
||||
* A function expression such as the following:
|
||||
*
|
||||
* ```
|
||||
* (x) => { x.y = true }
|
||||
* ```
|
||||
*
|
||||
* Would populate a `Mutate x` aliasing effect on the outer function.
|
||||
*
|
||||
* ## Returned Function Effects
|
||||
*
|
||||
* The function returns (if successful) a list of externally-visible effects.
|
||||
* This is determined by simulating a conditional, transitive mutation against
|
||||
* each param, context variable, and return value in turn, and seeing which other
|
||||
* such values are affected. If they're affected, they must be captured, so we
|
||||
* record a Capture.
|
||||
*
|
||||
* The only tricky bit is the return value, which could _alias_ (or even assign)
|
||||
* one or more of the params/context-vars rather than just capturing. So we have
|
||||
* to do a bit more tracking for returns.
|
||||
*/
|
||||
export function inferMutationAliasingRanges(
|
||||
fn: HIRFunction,
|
||||
{isFunctionExpression}: {isFunctionExpression: boolean},
|
||||
): Result<void, CompilerError> {
|
||||
if (VERBOSE) {
|
||||
console.log();
|
||||
console.log(printFunction(fn));
|
||||
}
|
||||
): Result<Array<AliasingEffect>, CompilerError> {
|
||||
// The set of externally-visible effects
|
||||
const functionEffects: Array<AliasingEffect> = [];
|
||||
|
||||
/**
|
||||
* Part 1: Infer mutable ranges for values. We build an abstract model of
|
||||
* values, the alias/capture edges between them, and the set of mutations.
|
||||
@@ -115,20 +133,6 @@ export function inferMutationAliasingRanges(
|
||||
seenBlocks.add(block.id);
|
||||
|
||||
for (const instr of block.instructions) {
|
||||
if (
|
||||
instr.value.kind === 'FunctionExpression' ||
|
||||
instr.value.kind === 'ObjectMethod'
|
||||
) {
|
||||
state.create(instr.lvalue, {
|
||||
kind: 'Function',
|
||||
function: instr.value.loweredFunc.func,
|
||||
});
|
||||
} else {
|
||||
for (const lvalue of eachInstructionLValue(instr)) {
|
||||
state.create(lvalue, {kind: 'Object'});
|
||||
}
|
||||
}
|
||||
|
||||
if (instr.effects == null) continue;
|
||||
for (const effect of instr.effects) {
|
||||
if (effect.kind === 'Create') {
|
||||
@@ -141,6 +145,15 @@ export function inferMutationAliasingRanges(
|
||||
} else if (effect.kind === 'CreateFrom') {
|
||||
state.createFrom(index++, effect.from, effect.into);
|
||||
} else if (effect.kind === 'Assign') {
|
||||
/**
|
||||
* TODO: Invariant that the node is not initialized yet
|
||||
*
|
||||
* InferFunctionExpressionAliasingEffectSignatures currently infers
|
||||
* Assign effects in some places that should be Alias, leading to
|
||||
* Assign effects that reinitialize a value. The end result appears to
|
||||
* be fine, but we should fix that inference pass so that we add the
|
||||
* invariant here.
|
||||
*/
|
||||
if (!state.nodes.has(effect.into.identifier)) {
|
||||
state.create(effect.into, {kind: 'Object'});
|
||||
}
|
||||
@@ -183,8 +196,10 @@ export function inferMutationAliasingRanges(
|
||||
effect.kind === 'Impure'
|
||||
) {
|
||||
errors.push(effect.error);
|
||||
functionEffects.push(effect);
|
||||
} else if (effect.kind === 'Render') {
|
||||
renders.push({index: index++, place: effect.place});
|
||||
functionEffects.push(effect);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -216,10 +231,6 @@ export function inferMutationAliasingRanges(
|
||||
}
|
||||
}
|
||||
|
||||
if (VERBOSE) {
|
||||
console.log(state.debug());
|
||||
console.log(pretty(mutations));
|
||||
}
|
||||
for (const mutation of mutations) {
|
||||
state.mutate(
|
||||
mutation.index,
|
||||
@@ -234,10 +245,6 @@ export function inferMutationAliasingRanges(
|
||||
for (const render of renders) {
|
||||
state.render(render.index, render.place.identifier, errors);
|
||||
}
|
||||
if (DEBUG) {
|
||||
console.log(pretty([...state.nodes.keys()]));
|
||||
}
|
||||
fn.aliasingEffects ??= [];
|
||||
for (const param of [...fn.context, ...fn.params]) {
|
||||
const place = param.kind === 'Identifier' ? param : param.place;
|
||||
const node = state.nodes.get(place.identifier);
|
||||
@@ -248,13 +255,13 @@ export function inferMutationAliasingRanges(
|
||||
if (node.local != null) {
|
||||
if (node.local.kind === MutationKind.Conditional) {
|
||||
mutated = true;
|
||||
fn.aliasingEffects.push({
|
||||
functionEffects.push({
|
||||
kind: 'MutateConditionally',
|
||||
value: {...place, loc: node.local.loc},
|
||||
});
|
||||
} else if (node.local.kind === MutationKind.Definite) {
|
||||
mutated = true;
|
||||
fn.aliasingEffects.push({
|
||||
functionEffects.push({
|
||||
kind: 'Mutate',
|
||||
value: {...place, loc: node.local.loc},
|
||||
});
|
||||
@@ -263,13 +270,13 @@ export function inferMutationAliasingRanges(
|
||||
if (node.transitive != null) {
|
||||
if (node.transitive.kind === MutationKind.Conditional) {
|
||||
mutated = true;
|
||||
fn.aliasingEffects.push({
|
||||
functionEffects.push({
|
||||
kind: 'MutateTransitiveConditionally',
|
||||
value: {...place, loc: node.transitive.loc},
|
||||
});
|
||||
} else if (node.transitive.kind === MutationKind.Definite) {
|
||||
mutated = true;
|
||||
fn.aliasingEffects.push({
|
||||
functionEffects.push({
|
||||
kind: 'MutateTransitive',
|
||||
value: {...place, loc: node.transitive.loc},
|
||||
});
|
||||
@@ -458,10 +465,82 @@ export function inferMutationAliasingRanges(
|
||||
}
|
||||
}
|
||||
|
||||
if (VERBOSE) {
|
||||
console.log(printFunction(fn));
|
||||
/**
|
||||
* Part 3
|
||||
* Finish populating the externally visible effects. Above we bubble-up the side effects
|
||||
* (MutateFrozen/MutableGlobal/Impure/Render) as well as mutations of context variables.
|
||||
* Here we populate an effect to create the return value as well as populating alias/capture
|
||||
* effects for how data flows between the params, context vars, and return.
|
||||
*/
|
||||
const returns = fn.returns.identifier;
|
||||
functionEffects.push({
|
||||
kind: 'Create',
|
||||
into: fn.returns,
|
||||
value: isPrimitiveType(returns)
|
||||
? ValueKind.Primitive
|
||||
: isJsxType(returns.type)
|
||||
? ValueKind.Frozen
|
||||
: ValueKind.Mutable,
|
||||
reason: ValueReason.KnownReturnSignature,
|
||||
});
|
||||
/**
|
||||
* Determine precise data-flow effects by simulating transitive mutations of the params/
|
||||
* captures and seeing what other params/context variables are affected. Anything that
|
||||
* would be transitively mutated needs a capture relationship.
|
||||
*/
|
||||
const tracked: Array<Place> = [];
|
||||
const ignoredErrors = new CompilerError();
|
||||
for (const param of [...fn.params, ...fn.context, fn.returns]) {
|
||||
const place = param.kind === 'Identifier' ? param : param.place;
|
||||
tracked.push(place);
|
||||
}
|
||||
return errors.asResult();
|
||||
for (const into of tracked) {
|
||||
const mutationIndex = index++;
|
||||
state.mutate(
|
||||
mutationIndex,
|
||||
into.identifier,
|
||||
null,
|
||||
true,
|
||||
MutationKind.Conditional,
|
||||
into.loc,
|
||||
ignoredErrors,
|
||||
);
|
||||
for (const from of tracked) {
|
||||
if (
|
||||
from.identifier.id === into.identifier.id ||
|
||||
from.identifier.id === fn.returns.identifier.id
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
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,
|
||||
});
|
||||
if (fromNode.lastMutated === mutationIndex) {
|
||||
if (into.identifier.id === fn.returns.identifier.id) {
|
||||
// The return value could be any of the params/context variables
|
||||
functionEffects.push({
|
||||
kind: 'Alias',
|
||||
from,
|
||||
into,
|
||||
});
|
||||
} else {
|
||||
// Otherwise params/context-vars can only capture each other
|
||||
functionEffects.push({
|
||||
kind: 'Capture',
|
||||
from,
|
||||
into,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.hasErrors() && !isFunctionExpression) {
|
||||
return Err(errors);
|
||||
}
|
||||
return Ok(functionEffects);
|
||||
}
|
||||
|
||||
function appendFunctionErrors(errors: CompilerError, fn: HIRFunction): void {
|
||||
@@ -477,6 +556,12 @@ function appendFunctionErrors(errors: CompilerError, fn: HIRFunction): void {
|
||||
}
|
||||
}
|
||||
|
||||
export enum MutationKind {
|
||||
None = 0,
|
||||
Conditional = 1,
|
||||
Definite = 2,
|
||||
}
|
||||
|
||||
type Node = {
|
||||
id: Identifier;
|
||||
createdFrom: Map<Identifier, number>;
|
||||
@@ -485,6 +570,7 @@ type Node = {
|
||||
edges: Array<{index: number; node: Identifier; kind: 'capture' | 'alias'}>;
|
||||
transitive: {kind: MutationKind; loc: SourceLocation} | null;
|
||||
local: {kind: MutationKind; loc: SourceLocation} | null;
|
||||
lastMutated: number;
|
||||
value:
|
||||
| {kind: 'Object'}
|
||||
| {kind: 'Phi'}
|
||||
@@ -502,6 +588,7 @@ class AliasingState {
|
||||
edges: [],
|
||||
transitive: null,
|
||||
local: null,
|
||||
lastMutated: 0,
|
||||
value,
|
||||
});
|
||||
}
|
||||
@@ -511,11 +598,6 @@ class AliasingState {
|
||||
const fromNode = this.nodes.get(from.identifier);
|
||||
const toNode = this.nodes.get(into.identifier);
|
||||
if (fromNode == null || toNode == null) {
|
||||
if (VERBOSE) {
|
||||
console.log(
|
||||
`skip: createFrom ${printPlace(from)}${!!fromNode} -> ${printPlace(into)}${!!toNode}`,
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
fromNode.edges.push({index, node: into.identifier, kind: 'alias'});
|
||||
@@ -528,11 +610,6 @@ class AliasingState {
|
||||
const fromNode = this.nodes.get(from.identifier);
|
||||
const toNode = this.nodes.get(into.identifier);
|
||||
if (fromNode == null || toNode == null) {
|
||||
if (VERBOSE) {
|
||||
console.log(
|
||||
`skip: capture ${printPlace(from)}${!!fromNode} -> ${printPlace(into)}${!!toNode}`,
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
fromNode.edges.push({index, node: into.identifier, kind: 'capture'});
|
||||
@@ -545,11 +622,6 @@ class AliasingState {
|
||||
const fromNode = this.nodes.get(from.identifier);
|
||||
const toNode = this.nodes.get(into.identifier);
|
||||
if (fromNode == null || toNode == null) {
|
||||
if (VERBOSE) {
|
||||
console.log(
|
||||
`skip: assign ${printPlace(from)}${!!fromNode} -> ${printPlace(into)}${!!toNode}`,
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
fromNode.edges.push({index, node: into.identifier, kind: 'alias'});
|
||||
@@ -598,17 +670,13 @@ class AliasingState {
|
||||
mutate(
|
||||
index: number,
|
||||
start: Identifier,
|
||||
end: InstructionId,
|
||||
// Null is used for simulated mutations
|
||||
end: InstructionId | null,
|
||||
transitive: boolean,
|
||||
kind: MutationKind,
|
||||
loc: SourceLocation,
|
||||
errors: CompilerError,
|
||||
): void {
|
||||
if (DEBUG) {
|
||||
console.log(
|
||||
`mutate ix=${index} start=$${start.id} end=[${end}]${transitive ? ' transitive' : ''} kind=${kind}`,
|
||||
);
|
||||
}
|
||||
const seen = new Set<Identifier>();
|
||||
const queue: Array<{
|
||||
place: Identifier;
|
||||
@@ -623,21 +691,14 @@ class AliasingState {
|
||||
seen.add(current);
|
||||
const node = this.nodes.get(current);
|
||||
if (node == null) {
|
||||
if (DEBUG) {
|
||||
console.log(
|
||||
`no node! ${printIdentifier(start)} for identifier ${printIdentifier(current)}`,
|
||||
);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (DEBUG) {
|
||||
console.log(
|
||||
` mutate $${node.id.id} transitive=${transitive} direction=${direction}`,
|
||||
node.lastMutated = Math.max(node.lastMutated, index);
|
||||
if (end != null) {
|
||||
node.id.mutableRange.end = makeInstructionId(
|
||||
Math.max(node.id.mutableRange.end, end),
|
||||
);
|
||||
}
|
||||
node.id.mutableRange.end = makeInstructionId(
|
||||
Math.max(node.id.mutableRange.end, end),
|
||||
);
|
||||
if (
|
||||
node.value.kind === 'Function' &&
|
||||
node.transitive == null &&
|
||||
@@ -701,37 +762,5 @@ class AliasingState {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (DEBUG) {
|
||||
const nodes = new Map();
|
||||
for (const id of seen) {
|
||||
const node = this.nodes.get(id);
|
||||
nodes.set(id.id, node);
|
||||
}
|
||||
console.log(pretty(nodes));
|
||||
}
|
||||
}
|
||||
|
||||
debug(): string {
|
||||
return pretty(this.nodes);
|
||||
}
|
||||
}
|
||||
|
||||
export function pretty(v: any): string {
|
||||
return prettyFormat(v, {
|
||||
plugins: [
|
||||
{
|
||||
test: v =>
|
||||
v !== null && typeof v === 'object' && v.kind === 'Identifier',
|
||||
serialize: v => printPlace(v),
|
||||
},
|
||||
{
|
||||
test: v =>
|
||||
v !== null &&
|
||||
typeof v === 'object' &&
|
||||
typeof v.declarationId === 'number',
|
||||
serialize: v =>
|
||||
`${printIdentifier(v)}:${v.mutableRange.start}:${v.mutableRange.end}`,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
Environment,
|
||||
FunctionExpression,
|
||||
GeneratedSource,
|
||||
GotoTerminal,
|
||||
GotoVariant,
|
||||
HIRFunction,
|
||||
IdentifierId,
|
||||
@@ -19,6 +20,7 @@ import {
|
||||
Place,
|
||||
isStatementBlockKind,
|
||||
makeInstructionId,
|
||||
mergeConsecutiveBlocks,
|
||||
promoteTemporary,
|
||||
reversePostorderBlocks,
|
||||
} from '../HIR';
|
||||
@@ -73,6 +75,10 @@ import {retainWhere} from '../Utils/utils';
|
||||
* - All return statements in the original function expression are replaced with a
|
||||
* StoreLocal to the temporary we allocated before plus a Goto to the fallthrough
|
||||
* block (code following the CallExpression).
|
||||
*
|
||||
* Note that if the inliined function has only one return, we avoid the labeled block
|
||||
* and fully inline the code. The original return is replaced with an assignmen to the
|
||||
* IIFE's call expression lvalue.
|
||||
*/
|
||||
export function inlineImmediatelyInvokedFunctionExpressions(
|
||||
fn: HIRFunction,
|
||||
@@ -146,37 +152,75 @@ export function inlineImmediatelyInvokedFunctionExpressions(
|
||||
*/
|
||||
block.instructions.length = ii;
|
||||
|
||||
/*
|
||||
* To account for complex control flow within the lambda, we treat the lambda
|
||||
* as if it were a single labeled statement, and replace all returns with gotos
|
||||
* to the label fallthrough.
|
||||
*/
|
||||
const newTerminal: LabelTerminal = {
|
||||
block: body.loweredFunc.func.body.entry,
|
||||
id: makeInstructionId(0),
|
||||
kind: 'label',
|
||||
fallthrough: continuationBlockId,
|
||||
loc: block.terminal.loc,
|
||||
};
|
||||
block.terminal = newTerminal;
|
||||
if (hasSingleExitReturnTerminal(body.loweredFunc.func)) {
|
||||
block.terminal = {
|
||||
kind: 'goto',
|
||||
block: body.loweredFunc.func.body.entry,
|
||||
id: block.terminal.id,
|
||||
loc: block.terminal.loc,
|
||||
variant: GotoVariant.Break,
|
||||
} as GotoTerminal;
|
||||
for (const block of body.loweredFunc.func.body.blocks.values()) {
|
||||
if (block.terminal.kind === 'return') {
|
||||
block.instructions.push({
|
||||
id: makeInstructionId(0),
|
||||
loc: block.terminal.loc,
|
||||
lvalue: instr.lvalue,
|
||||
value: {
|
||||
kind: 'LoadLocal',
|
||||
loc: block.terminal.loc,
|
||||
place: block.terminal.value,
|
||||
},
|
||||
effects: null,
|
||||
});
|
||||
block.terminal = {
|
||||
kind: 'goto',
|
||||
block: continuationBlockId,
|
||||
id: block.terminal.id,
|
||||
loc: block.terminal.loc,
|
||||
variant: GotoVariant.Break,
|
||||
} as GotoTerminal;
|
||||
}
|
||||
}
|
||||
for (const [id, block] of body.loweredFunc.func.body.blocks) {
|
||||
block.preds.clear();
|
||||
fn.body.blocks.set(id, block);
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* To account for multiple returns within the lambda, we treat the lambda
|
||||
* as if it were a single labeled statement, and replace all returns with gotos
|
||||
* to the label fallthrough.
|
||||
*/
|
||||
const newTerminal: LabelTerminal = {
|
||||
block: body.loweredFunc.func.body.entry,
|
||||
id: makeInstructionId(0),
|
||||
kind: 'label',
|
||||
fallthrough: continuationBlockId,
|
||||
loc: block.terminal.loc,
|
||||
};
|
||||
block.terminal = newTerminal;
|
||||
|
||||
// We store the result in the IIFE temporary
|
||||
const result = instr.lvalue;
|
||||
// We store the result in the IIFE temporary
|
||||
const result = instr.lvalue;
|
||||
|
||||
// Declare the IIFE temporary
|
||||
declareTemporary(fn.env, block, result);
|
||||
// Declare the IIFE temporary
|
||||
declareTemporary(fn.env, block, result);
|
||||
|
||||
// Promote the temporary with a name as we require this to persist
|
||||
promoteTemporary(result.identifier);
|
||||
// Promote the temporary with a name as we require this to persist
|
||||
if (result.identifier.name == null) {
|
||||
promoteTemporary(result.identifier);
|
||||
}
|
||||
|
||||
/*
|
||||
* Rewrite blocks from the lambda to replace any `return` with a
|
||||
* store to the result and `goto` the continuation block
|
||||
*/
|
||||
for (const [id, block] of body.loweredFunc.func.body.blocks) {
|
||||
block.preds.clear();
|
||||
rewriteBlock(fn.env, block, continuationBlockId, result);
|
||||
fn.body.blocks.set(id, block);
|
||||
/*
|
||||
* Rewrite blocks from the lambda to replace any `return` with a
|
||||
* store to the result and `goto` the continuation block
|
||||
*/
|
||||
for (const [id, block] of body.loweredFunc.func.body.blocks) {
|
||||
block.preds.clear();
|
||||
rewriteBlock(fn.env, block, continuationBlockId, result);
|
||||
fn.body.blocks.set(id, block);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -199,7 +243,7 @@ export function inlineImmediatelyInvokedFunctionExpressions(
|
||||
|
||||
if (inlinedFunctions.size !== 0) {
|
||||
// Remove instructions that define lambdas which we inlined
|
||||
for (const [, block] of fn.body.blocks) {
|
||||
for (const block of fn.body.blocks.values()) {
|
||||
retainWhere(
|
||||
block.instructions,
|
||||
instr => !inlinedFunctions.has(instr.lvalue.identifier.id),
|
||||
@@ -213,9 +257,25 @@ export function inlineImmediatelyInvokedFunctionExpressions(
|
||||
reversePostorderBlocks(fn.body);
|
||||
markInstructionIds(fn.body);
|
||||
markPredecessors(fn.body);
|
||||
mergeConsecutiveBlocks(fn);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the function has a single exit terminal (throw/return) which is a return
|
||||
*/
|
||||
function hasSingleExitReturnTerminal(fn: HIRFunction): boolean {
|
||||
let hasReturn = false;
|
||||
let exitCount = 0;
|
||||
for (const [, block] of fn.body.blocks) {
|
||||
if (block.terminal.kind === 'return' || block.terminal.kind === 'throw') {
|
||||
hasReturn ||= block.terminal.kind === 'return';
|
||||
exitCount++;
|
||||
}
|
||||
}
|
||||
return exitCount === 1 && hasReturn;
|
||||
}
|
||||
|
||||
/*
|
||||
* Rewrites the block so that all `return` terminals are replaced:
|
||||
* * Add a StoreLocal <returnValue> = <terminal.value>
|
||||
|
||||
@@ -514,9 +514,9 @@ Intuition: these effects are inverses of each other (capturing into an object, e
|
||||
Capture then CreatFrom is equivalent to Alias: we have to assume that the result _is_ the original value and that a local mutation of the result could mutate the original.
|
||||
|
||||
```js
|
||||
const y = [x]; // capture
|
||||
const z = y[0]; // createfrom
|
||||
mutate(z); // this clearly can mutate x, so the result must be one of Assign/Alias/CreateFrom
|
||||
const b = [a]; // capture
|
||||
const c = b[0]; // createfrom
|
||||
mutate(c); // this clearly can mutate a, so the result must be one of Assign/Alias/CreateFrom
|
||||
```
|
||||
|
||||
We use Alias as the return type because the mutability kind of the result is not derived from the source value (there's a fresh object in between due to the capture), so the full set of effects in practice would be a Create+Alias.
|
||||
@@ -528,17 +528,17 @@ CreateFrom c <- b
|
||||
Alias c <- a
|
||||
```
|
||||
|
||||
Meanwhile the opposite direction preservers the capture, because the result is not the same as the source:
|
||||
Meanwhile the opposite direction preserves the capture, because the result is not the same as the source:
|
||||
|
||||
```js
|
||||
const y = x[0]; // createfrom
|
||||
const z = [y]; // capture
|
||||
mutate(z); // does not mutate x, so the result must be Capture
|
||||
const b = a[0]; // createfrom
|
||||
const c = [b]; // capture
|
||||
mutate(c); // does not mutate a, so the result must be Capture
|
||||
```
|
||||
|
||||
```
|
||||
Capture b <- a
|
||||
CreateFrom c <- b
|
||||
CreateFrom b <- a
|
||||
Capture c <- b
|
||||
=>
|
||||
Capture b <- a
|
||||
Capture c <- a
|
||||
```
|
||||
@@ -25,7 +25,6 @@ import {
|
||||
makeBlockId,
|
||||
makeInstructionId,
|
||||
makePropertyLiteral,
|
||||
makeType,
|
||||
markInstructionIds,
|
||||
promoteTemporary,
|
||||
reversePostorderBlocks,
|
||||
@@ -253,7 +252,6 @@ function emitSelectorFn(env: Environment, keys: Array<string>): Instruction {
|
||||
env,
|
||||
params: [obj],
|
||||
returnTypeAnnotation: null,
|
||||
returnType: makeType(),
|
||||
returns: createTemporaryPlace(env, GeneratedSource),
|
||||
context: [],
|
||||
effects: null,
|
||||
|
||||
@@ -21,7 +21,6 @@ import {
|
||||
makeBlockId,
|
||||
makeIdentifierName,
|
||||
makeInstructionId,
|
||||
makeType,
|
||||
ObjectProperty,
|
||||
Place,
|
||||
promoteTemporary,
|
||||
@@ -368,7 +367,6 @@ function emitOutlinedFn(
|
||||
env,
|
||||
params: [propsObj],
|
||||
returnTypeAnnotation: null,
|
||||
returnType: makeType(),
|
||||
returns: createTemporaryPlace(env, GeneratedSource),
|
||||
context: [],
|
||||
effects: null,
|
||||
|
||||
@@ -349,11 +349,9 @@ function codegenReactiveFunction(
|
||||
fn: ReactiveFunction,
|
||||
): Result<CodegenFunction, CompilerError> {
|
||||
for (const param of fn.params) {
|
||||
if (param.kind === 'Identifier') {
|
||||
cx.temp.set(param.identifier.declarationId, null);
|
||||
} else {
|
||||
cx.temp.set(param.place.identifier.declarationId, null);
|
||||
}
|
||||
const place = param.kind === 'Identifier' ? param : param.place;
|
||||
cx.temp.set(place.identifier.declarationId, null);
|
||||
cx.declare(place.identifier);
|
||||
}
|
||||
|
||||
const params = fn.params.map(param => convertParameter(param));
|
||||
@@ -1183,7 +1181,7 @@ function codegenTerminal(
|
||||
? codegenPlaceToExpression(cx, case_.test)
|
||||
: null;
|
||||
const block = codegenBlock(cx, case_.block!);
|
||||
return t.switchCase(test, [block]);
|
||||
return t.switchCase(test, block.body.length === 0 ? [] : [block]);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -79,6 +79,10 @@ export function extractScopeDeclarationsFromDestructuring(
|
||||
fn: ReactiveFunction,
|
||||
): void {
|
||||
const state = new State(fn.env);
|
||||
for (const param of fn.params) {
|
||||
const place = param.kind === 'Identifier' ? param : param.place;
|
||||
state.declared.add(place.identifier.declarationId);
|
||||
}
|
||||
visitReactiveFunction(fn, new Visitor(), state);
|
||||
}
|
||||
|
||||
|
||||
@@ -829,12 +829,14 @@ class CollectDependenciesVisitor extends ReactiveFunctionVisitor<
|
||||
};
|
||||
}
|
||||
case 'UnsupportedNode': {
|
||||
CompilerError.invariant(false, {
|
||||
reason: `Unexpected unsupported node`,
|
||||
description: null,
|
||||
loc: value.loc,
|
||||
suggestions: null,
|
||||
});
|
||||
const lvalues = [];
|
||||
if (lvalue !== null) {
|
||||
lvalues.push({place: lvalue, level: MemoizationLevel.Never});
|
||||
}
|
||||
return {
|
||||
lvalues,
|
||||
rvalues: [],
|
||||
};
|
||||
}
|
||||
default: {
|
||||
assertExhaustive(
|
||||
@@ -1064,12 +1066,29 @@ class PruneScopesTransform extends ReactiveFunctionTransform<
|
||||
|
||||
const value = instruction.value;
|
||||
if (value.kind === 'StoreLocal' && value.lvalue.kind === 'Reassign') {
|
||||
// Complex cases of useMemo inlining result in a temporary that is reassigned
|
||||
const ids = getOrInsertDefault(
|
||||
this.reassignments,
|
||||
value.lvalue.place.identifier.declarationId,
|
||||
new Set(),
|
||||
);
|
||||
ids.add(value.value.identifier);
|
||||
} else if (
|
||||
value.kind === 'LoadLocal' &&
|
||||
value.place.identifier.scope != null &&
|
||||
instruction.lvalue != null &&
|
||||
instruction.lvalue.identifier.scope == null
|
||||
) {
|
||||
/*
|
||||
* Simpler cases result in a direct assignment to the original lvalue, with a
|
||||
* LoadLocal
|
||||
*/
|
||||
const ids = getOrInsertDefault(
|
||||
this.reassignments,
|
||||
instruction.lvalue.identifier.declarationId,
|
||||
new Set(),
|
||||
);
|
||||
ids.add(value.place.identifier);
|
||||
} else if (value.kind === 'FinishMemoize') {
|
||||
let decls;
|
||||
if (value.decl.identifier.scope == null) {
|
||||
|
||||
@@ -90,7 +90,8 @@ function apply(func: HIRFunction, unifier: Unifier): void {
|
||||
}
|
||||
}
|
||||
}
|
||||
func.returnType = unifier.get(func.returnType);
|
||||
const returns = func.returns.identifier;
|
||||
returns.type = unifier.get(returns.type);
|
||||
}
|
||||
|
||||
type TypeEquation = {
|
||||
@@ -143,12 +144,12 @@ function* generate(
|
||||
}
|
||||
}
|
||||
if (returnTypes.length > 1) {
|
||||
yield equation(func.returnType, {
|
||||
yield equation(func.returns.identifier.type, {
|
||||
kind: 'Phi',
|
||||
operands: returnTypes,
|
||||
});
|
||||
} else if (returnTypes.length === 1) {
|
||||
yield equation(func.returnType, returnTypes[0]!);
|
||||
yield equation(func.returns.identifier.type, returnTypes[0]!);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -407,7 +408,7 @@ function* generateInstructionTypes(
|
||||
yield equation(left, {
|
||||
kind: 'Function',
|
||||
shapeId: BuiltInFunctionId,
|
||||
return: value.loweredFunc.func.returnType,
|
||||
return: value.loweredFunc.func.returns.identifier.type,
|
||||
isConstructor: false,
|
||||
});
|
||||
break;
|
||||
|
||||
@@ -75,21 +75,21 @@ const testComplexConfigDefaults: PartialEnvironmentConfig = {
|
||||
source: 'react',
|
||||
importSpecifierName: 'useEffect',
|
||||
},
|
||||
numRequiredArgs: 1,
|
||||
autodepsIndex: 1,
|
||||
},
|
||||
{
|
||||
function: {
|
||||
source: 'shared-runtime',
|
||||
importSpecifierName: 'useSpecialEffect',
|
||||
},
|
||||
numRequiredArgs: 2,
|
||||
autodepsIndex: 2,
|
||||
},
|
||||
{
|
||||
function: {
|
||||
source: 'useEffectWrapper',
|
||||
importSpecifierName: 'default',
|
||||
},
|
||||
numRequiredArgs: 1,
|
||||
autodepsIndex: 1,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -11,20 +11,22 @@ import {
|
||||
IdentifierId,
|
||||
isSetStateType,
|
||||
isUseEffectHookType,
|
||||
isUseInsertionEffectHookType,
|
||||
isUseLayoutEffectHookType,
|
||||
Place,
|
||||
} from '../HIR';
|
||||
import {eachInstructionValueOperand} from '../HIR/visitors';
|
||||
import {Result} from '../Utils/Result';
|
||||
|
||||
/**
|
||||
* Validates against calling setState in the body of a *passive* effect (useEffect),
|
||||
* Validates against calling setState in the body of an effect (useEffect and friends),
|
||||
* while allowing calling setState in callbacks scheduled by the effect.
|
||||
*
|
||||
* Calling setState during execution of a useEffect triggers a re-render, which is
|
||||
* often bad for performance and frequently has more efficient and straightforward
|
||||
* alternatives. See https://react.dev/learn/you-might-not-need-an-effect for examples.
|
||||
*/
|
||||
export function validateNoSetStateInPassiveEffects(
|
||||
export function validateNoSetStateInEffects(
|
||||
fn: HIRFunction,
|
||||
): Result<void, CompilerError> {
|
||||
const setStateFunctions: Map<IdentifierId, Place> = new Map();
|
||||
@@ -79,7 +81,11 @@ export function validateNoSetStateInPassiveEffects(
|
||||
instr.value.kind === 'MethodCall'
|
||||
? instr.value.receiver
|
||||
: instr.value.callee;
|
||||
if (isUseEffectHookType(callee.identifier)) {
|
||||
if (
|
||||
isUseEffectHookType(callee.identifier) ||
|
||||
isUseLayoutEffectHookType(callee.identifier) ||
|
||||
isUseInsertionEffectHookType(callee.identifier)
|
||||
) {
|
||||
const arg = instr.value.args[0];
|
||||
if (arg !== undefined && arg.kind === 'Identifier') {
|
||||
const setState = setStateFunctions.get(arg.identifier.id);
|
||||
@@ -445,11 +445,13 @@ class Visitor extends ReactiveFunctionVisitor<VisitorState> {
|
||||
*/
|
||||
this.recordTemporaries(instruction, state);
|
||||
const value = instruction.value;
|
||||
// Track reassignments from inlining of manual memo
|
||||
if (
|
||||
value.kind === 'StoreLocal' &&
|
||||
value.lvalue.kind === 'Reassign' &&
|
||||
state.manualMemoState != null
|
||||
) {
|
||||
// Complex cases of inlining end up with a temporary that is reassigned
|
||||
const ids = getOrInsertDefault(
|
||||
state.manualMemoState.reassignments,
|
||||
value.lvalue.place.identifier.declarationId,
|
||||
@@ -457,6 +459,21 @@ class Visitor extends ReactiveFunctionVisitor<VisitorState> {
|
||||
);
|
||||
ids.add(value.value.identifier);
|
||||
}
|
||||
if (
|
||||
value.kind === 'LoadLocal' &&
|
||||
value.place.identifier.scope != null &&
|
||||
instruction.lvalue != null &&
|
||||
instruction.lvalue.identifier.scope == null &&
|
||||
state.manualMemoState != null
|
||||
) {
|
||||
// Simpler cases of inlining assign to the original IIFE lvalue
|
||||
const ids = getOrInsertDefault(
|
||||
state.manualMemoState.reassignments,
|
||||
instruction.lvalue.identifier.declarationId,
|
||||
new Set(),
|
||||
);
|
||||
ids.add(value.place.identifier);
|
||||
}
|
||||
if (value.kind === 'StartMemoize') {
|
||||
let depsFromSource: Array<ManualMemoDependency> | null = null;
|
||||
if (value.deps != null) {
|
||||
|
||||
@@ -33,12 +33,12 @@ describe('parseConfigPragma()', () => {
|
||||
source: 'react',
|
||||
importSpecifierName: 'useEffect',
|
||||
},
|
||||
numRequiredArgs: 0,
|
||||
autodepsIndex: 0,
|
||||
},
|
||||
],
|
||||
} as any);
|
||||
}).toThrowErrorMatchingInlineSnapshot(
|
||||
`"InvalidConfig: Could not validate environment config. Update React Compiler config to fix the error. Validation error: numRequiredArgs must be > 0 at "inferEffectDependencies[0].numRequiredArgs""`,
|
||||
`"InvalidConfig: Could not validate environment config. Update React Compiler config to fix the error. Validation error: autodepsIndex must be > 0 at "inferEffectDependencies[0].autodepsIndex""`,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -26,20 +26,16 @@ import { c as _c } from "react/compiler-runtime";
|
||||
import { getNull } from "shared-runtime";
|
||||
|
||||
function Component(props) {
|
||||
const $ = _c(3);
|
||||
let t0;
|
||||
const $ = _c(2);
|
||||
let items;
|
||||
if ($[0] !== props.a) {
|
||||
t0 = getNull() ?? [];
|
||||
items = t0;
|
||||
items = getNull() ?? [];
|
||||
|
||||
items.push(props.a);
|
||||
$[0] = props.a;
|
||||
$[1] = items;
|
||||
$[2] = t0;
|
||||
} else {
|
||||
items = $[1];
|
||||
t0 = $[2];
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
@@ -52,15 +52,13 @@ function Component(t0) {
|
||||
}
|
||||
const onClick = t1;
|
||||
let t2;
|
||||
let t3;
|
||||
if ($[2] !== onClick) {
|
||||
t3 = <div onClick={onClick}>{someGlobal.value}</div>;
|
||||
t2 = <div onClick={onClick}>{someGlobal.value}</div>;
|
||||
$[2] = onClick;
|
||||
$[3] = t3;
|
||||
$[3] = t2;
|
||||
} else {
|
||||
t3 = $[3];
|
||||
t2 = $[3];
|
||||
}
|
||||
t2 = t3;
|
||||
return t2;
|
||||
}
|
||||
|
||||
|
||||
@@ -30,50 +30,46 @@ function Component(props) {
|
||||
const $ = _c(4);
|
||||
const [x] = useState(0);
|
||||
let t0;
|
||||
let t1;
|
||||
if ($[0] !== x) {
|
||||
t1 = calculateExpensiveNumber(x);
|
||||
t0 = calculateExpensiveNumber(x);
|
||||
$[0] = x;
|
||||
$[1] = t1;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
t0 = $[1];
|
||||
}
|
||||
t0 = t1;
|
||||
const expensiveNumber = t0;
|
||||
let t2;
|
||||
let t1;
|
||||
if ($[2] !== expensiveNumber) {
|
||||
t2 = <div>{expensiveNumber}</div>;
|
||||
t1 = <div>{expensiveNumber}</div>;
|
||||
$[2] = expensiveNumber;
|
||||
$[3] = t2;
|
||||
$[3] = t1;
|
||||
} else {
|
||||
t2 = $[3];
|
||||
t1 = $[3];
|
||||
}
|
||||
return t2;
|
||||
return t1;
|
||||
}
|
||||
|
||||
function Component2(props) {
|
||||
const $ = _c(4);
|
||||
const [x] = useState(0);
|
||||
let t0;
|
||||
let t1;
|
||||
if ($[0] !== x) {
|
||||
t1 = calculateExpensiveNumber(x);
|
||||
t0 = calculateExpensiveNumber(x);
|
||||
$[0] = x;
|
||||
$[1] = t1;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
t0 = $[1];
|
||||
}
|
||||
t0 = t1;
|
||||
const expensiveNumber = t0;
|
||||
let t2;
|
||||
let t1;
|
||||
if ($[2] !== expensiveNumber) {
|
||||
t2 = <div>{expensiveNumber}</div>;
|
||||
t1 = <div>{expensiveNumber}</div>;
|
||||
$[2] = expensiveNumber;
|
||||
$[3] = t2;
|
||||
$[3] = t1;
|
||||
} else {
|
||||
t2 = $[3];
|
||||
t1 = $[3];
|
||||
}
|
||||
return t2;
|
||||
return t1;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
@@ -32,50 +32,46 @@ function Component(props) {
|
||||
const $ = _c(4);
|
||||
const [x] = useState(0);
|
||||
let t0;
|
||||
let t1;
|
||||
if ($[0] !== x) {
|
||||
t1 = calculateExpensiveNumber(x);
|
||||
t0 = calculateExpensiveNumber(x);
|
||||
$[0] = x;
|
||||
$[1] = t1;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
t0 = $[1];
|
||||
}
|
||||
t0 = t1;
|
||||
const expensiveNumber = t0;
|
||||
let t2;
|
||||
let t1;
|
||||
if ($[2] !== expensiveNumber) {
|
||||
t2 = <div>{expensiveNumber}</div>;
|
||||
t1 = <div>{expensiveNumber}</div>;
|
||||
$[2] = expensiveNumber;
|
||||
$[3] = t2;
|
||||
$[3] = t1;
|
||||
} else {
|
||||
t2 = $[3];
|
||||
t1 = $[3];
|
||||
}
|
||||
return t2;
|
||||
return t1;
|
||||
}
|
||||
|
||||
function Component2(props) {
|
||||
const $ = _c(4);
|
||||
const [x] = useState(0);
|
||||
let t0;
|
||||
let t1;
|
||||
if ($[0] !== x) {
|
||||
t1 = calculateExpensiveNumber(x);
|
||||
t0 = calculateExpensiveNumber(x);
|
||||
$[0] = x;
|
||||
$[1] = t1;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
t0 = $[1];
|
||||
}
|
||||
t0 = t1;
|
||||
const expensiveNumber = t0;
|
||||
let t2;
|
||||
let t1;
|
||||
if ($[2] !== expensiveNumber) {
|
||||
t2 = <div>{expensiveNumber}</div>;
|
||||
t1 = <div>{expensiveNumber}</div>;
|
||||
$[2] = expensiveNumber;
|
||||
$[3] = t2;
|
||||
$[3] = t1;
|
||||
} else {
|
||||
t2 = $[3];
|
||||
t1 = $[3];
|
||||
}
|
||||
return t2;
|
||||
return t1;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
@@ -30,25 +30,23 @@ function Component(props) {
|
||||
const $ = _c(4);
|
||||
const [x] = React.useState(0);
|
||||
let t0;
|
||||
let t1;
|
||||
if ($[0] !== x) {
|
||||
t1 = calculateExpensiveNumber(x);
|
||||
t0 = calculateExpensiveNumber(x);
|
||||
$[0] = x;
|
||||
$[1] = t1;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
t0 = $[1];
|
||||
}
|
||||
t0 = t1;
|
||||
const expensiveNumber = t0;
|
||||
let t2;
|
||||
let t1;
|
||||
if ($[2] !== expensiveNumber) {
|
||||
t2 = <div>{expensiveNumber}</div>;
|
||||
t1 = <div>{expensiveNumber}</div>;
|
||||
$[2] = expensiveNumber;
|
||||
$[3] = t2;
|
||||
$[3] = t1;
|
||||
} else {
|
||||
t2 = $[3];
|
||||
t1 = $[3];
|
||||
}
|
||||
return t2;
|
||||
return t1;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
|
||||
@@ -36,30 +36,28 @@ function Component(props) {
|
||||
const $ = _c(4);
|
||||
const [x] = React.useState(0);
|
||||
let t0;
|
||||
let t1;
|
||||
if ($[0] !== x) {
|
||||
t1 = calculateExpensiveNumber(x);
|
||||
t0 = calculateExpensiveNumber(x);
|
||||
$[0] = x;
|
||||
$[1] = t1;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
t0 = $[1];
|
||||
}
|
||||
t0 = t1;
|
||||
const expensiveNumber = t0;
|
||||
let t2;
|
||||
let t1;
|
||||
if ($[2] !== expensiveNumber) {
|
||||
t2 = (
|
||||
t1 = (
|
||||
<div>
|
||||
{expensiveNumber}
|
||||
{`${someImport}`}
|
||||
</div>
|
||||
);
|
||||
$[2] = expensiveNumber;
|
||||
$[3] = t2;
|
||||
$[3] = t1;
|
||||
} else {
|
||||
t2 = $[3];
|
||||
t1 = $[3];
|
||||
}
|
||||
return t2;
|
||||
return t1;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
|
||||
@@ -36,26 +36,22 @@ import { useMemo } from "react";
|
||||
function Component(props) {
|
||||
const $ = _c(2);
|
||||
let t0;
|
||||
let t1;
|
||||
if ($[0] !== props.value) {
|
||||
t1 = { value: props.value };
|
||||
t0 = { value: props.value };
|
||||
$[0] = props.value;
|
||||
$[1] = t1;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
t0 = $[1];
|
||||
}
|
||||
const handlers = t1;
|
||||
const handlers = t0;
|
||||
bb0: switch (props.test) {
|
||||
case true: {
|
||||
console.log(handlers.value);
|
||||
break bb0;
|
||||
}
|
||||
default: {
|
||||
}
|
||||
default:
|
||||
}
|
||||
|
||||
t0 = handlers;
|
||||
const outerHandlers = t0;
|
||||
const outerHandlers = handlers;
|
||||
return outerHandlers;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,11 +37,9 @@ function useTest() {
|
||||
|
||||
const t1 = (w = 42);
|
||||
const t2 = w;
|
||||
let t3;
|
||||
|
||||
w = 999;
|
||||
t3 = 2;
|
||||
t0 = makeArray(t1, t2, t3);
|
||||
t0 = makeArray(t1, t2, 2);
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
|
||||
@@ -37,11 +37,9 @@ function useTest() {
|
||||
|
||||
const t1 = (w.x = 42);
|
||||
const t2 = w.x;
|
||||
let t3;
|
||||
|
||||
w.x = 999;
|
||||
t3 = 2;
|
||||
t0 = makeArray(t1, t2, t3);
|
||||
t0 = makeArray(t1, t2, 2);
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
|
||||
@@ -32,11 +32,9 @@ function useTest() {
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
const t1 = print(1);
|
||||
let t2;
|
||||
|
||||
print(2);
|
||||
t2 = 2;
|
||||
t0 = makeArray(t1, t2);
|
||||
t0 = makeArray(t1, 2);
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
|
||||
@@ -29,37 +29,33 @@ function useHook(t0) {
|
||||
const $ = _c(7);
|
||||
const { a, b } = t0;
|
||||
let t1;
|
||||
let t2;
|
||||
if ($[0] !== a) {
|
||||
t2 = identity({ a });
|
||||
t1 = identity({ a });
|
||||
$[0] = a;
|
||||
$[1] = t2;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t2 = $[1];
|
||||
t1 = $[1];
|
||||
}
|
||||
t1 = t2;
|
||||
const valA = t1;
|
||||
let t3;
|
||||
let t4;
|
||||
let t2;
|
||||
if ($[2] !== b) {
|
||||
t4 = identity([b]);
|
||||
t2 = identity([b]);
|
||||
$[2] = b;
|
||||
$[3] = t4;
|
||||
$[3] = t2;
|
||||
} else {
|
||||
t4 = $[3];
|
||||
t2 = $[3];
|
||||
}
|
||||
t3 = t4;
|
||||
const valB = t3;
|
||||
let t5;
|
||||
const valB = t2;
|
||||
let t3;
|
||||
if ($[4] !== valA || $[5] !== valB) {
|
||||
t5 = [valA, valB];
|
||||
t3 = [valA, valB];
|
||||
$[4] = valA;
|
||||
$[5] = valB;
|
||||
$[6] = t5;
|
||||
$[6] = t3;
|
||||
} else {
|
||||
t5 = $[6];
|
||||
t3 = $[6];
|
||||
}
|
||||
return t5;
|
||||
return t3;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
|
||||
@@ -34,10 +34,8 @@ function Component(props) {
|
||||
let Component;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
Component = Stringify;
|
||||
let t0;
|
||||
|
||||
t0 = Component;
|
||||
Component = t0;
|
||||
Component = Component;
|
||||
$[0] = Component;
|
||||
} else {
|
||||
Component = $[0];
|
||||
|
||||
@@ -28,20 +28,18 @@ import { c as _c } from "react/compiler-runtime";
|
||||
function Foo() {
|
||||
const $ = _c(1);
|
||||
let t0;
|
||||
let t1;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t1 = function a(t2) {
|
||||
const x_0 = t2 === undefined ? _temp : t2;
|
||||
return (function b(t3) {
|
||||
const y_0 = t3 === undefined ? [] : t3;
|
||||
t0 = function a(t1) {
|
||||
const x_0 = t1 === undefined ? _temp : t1;
|
||||
return (function b(t2) {
|
||||
const y_0 = t2 === undefined ? [] : t2;
|
||||
return [x_0, y_0];
|
||||
})();
|
||||
};
|
||||
$[0] = t1;
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t1 = $[0];
|
||||
t0 = $[0];
|
||||
}
|
||||
t0 = t1;
|
||||
return t0;
|
||||
}
|
||||
function _temp() {}
|
||||
|
||||
@@ -67,8 +67,7 @@ function Component(props) {
|
||||
case "b": {
|
||||
break bb1;
|
||||
}
|
||||
case "c": {
|
||||
}
|
||||
case "c":
|
||||
default: {
|
||||
x = 6;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@ import * as React from "react";
|
||||
|
||||
function Component(props) {
|
||||
const $ = _c(2);
|
||||
let t0;
|
||||
let x;
|
||||
if ($[0] !== props.value) {
|
||||
x = [];
|
||||
@@ -38,8 +37,7 @@ function Component(props) {
|
||||
} else {
|
||||
x = $[1];
|
||||
}
|
||||
t0 = x;
|
||||
const x_0 = t0;
|
||||
const x_0 = x;
|
||||
return x_0;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
function Component(props) {
|
||||
eval('props.x = true');
|
||||
return <div />;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
1 | function Component(props) {
|
||||
> 2 | eval('props.x = true');
|
||||
| ^^^^ UnsupportedJS: The 'eval' function is not supported. Eval is an anti-pattern in JavaScript, and the code executed cannot be evaluated by React Compiler (2:2)
|
||||
3 | return <div />;
|
||||
4 | }
|
||||
5 |
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
function Component(props) {
|
||||
eval('props.x = true');
|
||||
return <div />;
|
||||
}
|
||||
@@ -84,7 +84,7 @@ let moduleLocal = false;
|
||||
> 3 | var x = [];
|
||||
| ^^^^^^^^^^^ Todo: (BuildHIR::lowerStatement) Handle var kinds in VariableDeclaration (3:3)
|
||||
|
||||
Todo: (BuildHIR::lowerStatement) Handle ClassDeclaration statements (5:10)
|
||||
UnsupportedJS: Inline `class` declarations are not supported. Move class declarations outside of components/hooks (5:10)
|
||||
|
||||
Todo: (BuildHIR::lowerStatement) Handle non-variable initialization in ForStatement (20:22)
|
||||
|
||||
|
||||
@@ -42,34 +42,32 @@ function Component(props) {
|
||||
const c1 = __c;
|
||||
const $c = c1;
|
||||
let t0;
|
||||
let t1;
|
||||
if ($[0] !== $c) {
|
||||
t1 = [$c];
|
||||
t0 = [$c];
|
||||
$[0] = $c;
|
||||
$[1] = t1;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
t0 = $[1];
|
||||
}
|
||||
t0 = t1;
|
||||
const array = t0;
|
||||
let t2;
|
||||
let t1;
|
||||
if ($[2] !== state) {
|
||||
t2 = [state];
|
||||
t1 = [state];
|
||||
$[2] = state;
|
||||
$[3] = t2;
|
||||
$[3] = t1;
|
||||
} else {
|
||||
t2 = $[3];
|
||||
t1 = $[3];
|
||||
}
|
||||
let t3;
|
||||
if ($[4] !== array || $[5] !== t2) {
|
||||
t3 = <ValidateMemoization inputs={t2} output={array} />;
|
||||
let t2;
|
||||
if ($[4] !== array || $[5] !== t1) {
|
||||
t2 = <ValidateMemoization inputs={t1} output={array} />;
|
||||
$[4] = array;
|
||||
$[5] = t2;
|
||||
$[6] = t3;
|
||||
$[5] = t1;
|
||||
$[6] = t2;
|
||||
} else {
|
||||
t3 = $[6];
|
||||
t2 = $[6];
|
||||
}
|
||||
return t3;
|
||||
return t2;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
|
||||
@@ -63,23 +63,21 @@ function Component() {
|
||||
|
||||
unsafeUpdateConst();
|
||||
let t0;
|
||||
let t1;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t1 = [{ pretendConst }];
|
||||
$[0] = t1;
|
||||
t0 = [{ pretendConst }];
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t1 = $[0];
|
||||
t0 = $[0];
|
||||
}
|
||||
t0 = t1;
|
||||
const value = t0;
|
||||
let t2;
|
||||
let t1;
|
||||
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t2 = <ValidateMemoization inputs={[]} output={value} />;
|
||||
$[1] = t2;
|
||||
t1 = <ValidateMemoization inputs={[]} output={value} />;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t2 = $[1];
|
||||
t1 = $[1];
|
||||
}
|
||||
return t2;
|
||||
return t1;
|
||||
}
|
||||
function _temp() {
|
||||
unsafeResetConst();
|
||||
|
||||
@@ -74,23 +74,21 @@ function Component() {
|
||||
|
||||
unsafeUpdateConst();
|
||||
let t0;
|
||||
let t1;
|
||||
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t1 = [{ pretendConst }];
|
||||
$[1] = t1;
|
||||
t0 = [{ pretendConst }];
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
t0 = $[1];
|
||||
}
|
||||
t0 = t1;
|
||||
const value = t0;
|
||||
let t2;
|
||||
let t1;
|
||||
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t2 = <ValidateMemoization inputs={[pretendConst]} output={value} />;
|
||||
$[2] = t2;
|
||||
t1 = <ValidateMemoization inputs={[pretendConst]} output={value} />;
|
||||
$[2] = t1;
|
||||
} else {
|
||||
t2 = $[2];
|
||||
t1 = $[2];
|
||||
}
|
||||
return t2;
|
||||
return t1;
|
||||
}
|
||||
function _temp() {
|
||||
unsafeResetConst();
|
||||
|
||||
@@ -38,36 +38,34 @@ function Component(props) {
|
||||
$[0] = "20945b0193e529df490847c66111b38d7b02485d5b53d0829ff3b23af87b105c";
|
||||
}
|
||||
const [state] = useState(0);
|
||||
let t0;
|
||||
const t1 = state * 2;
|
||||
const t0 = state * 2;
|
||||
let t1;
|
||||
if ($[1] !== t0) {
|
||||
t1 = [t0];
|
||||
$[1] = t0;
|
||||
$[2] = t1;
|
||||
} else {
|
||||
t1 = $[2];
|
||||
}
|
||||
const doubled = t1;
|
||||
let t2;
|
||||
if ($[1] !== t1) {
|
||||
t2 = [t1];
|
||||
$[1] = t1;
|
||||
$[2] = t2;
|
||||
} else {
|
||||
t2 = $[2];
|
||||
}
|
||||
t0 = t2;
|
||||
const doubled = t0;
|
||||
let t3;
|
||||
if ($[3] !== state) {
|
||||
t3 = [state];
|
||||
t2 = [state];
|
||||
$[3] = state;
|
||||
$[4] = t3;
|
||||
$[4] = t2;
|
||||
} else {
|
||||
t3 = $[4];
|
||||
t2 = $[4];
|
||||
}
|
||||
let t4;
|
||||
if ($[5] !== doubled || $[6] !== t3) {
|
||||
t4 = <ValidateMemoization inputs={t3} output={doubled} />;
|
||||
let t3;
|
||||
if ($[5] !== doubled || $[6] !== t2) {
|
||||
t3 = <ValidateMemoization inputs={t2} output={doubled} />;
|
||||
$[5] = doubled;
|
||||
$[6] = t3;
|
||||
$[7] = t4;
|
||||
$[6] = t2;
|
||||
$[7] = t3;
|
||||
} else {
|
||||
t4 = $[7];
|
||||
t3 = $[7];
|
||||
}
|
||||
return t4;
|
||||
return t3;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
|
||||
@@ -40,36 +40,34 @@ function Component(t0) {
|
||||
const $ = _c(7);
|
||||
const { data } = t0;
|
||||
let t1;
|
||||
let t2;
|
||||
if ($[0] !== data.name) {
|
||||
t2 = fbt._("{name}", [fbt._param("name", data.name ?? "")], {
|
||||
t1 = fbt._("{name}", [fbt._param("name", data.name ?? "")], {
|
||||
hk: "csQUH",
|
||||
});
|
||||
$[0] = data.name;
|
||||
$[1] = t2;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t2 = $[1];
|
||||
t1 = $[1];
|
||||
}
|
||||
t1 = t2;
|
||||
const el = t1;
|
||||
let t3;
|
||||
let t2;
|
||||
if ($[2] !== data.name) {
|
||||
t3 = [data.name];
|
||||
t2 = [data.name];
|
||||
$[2] = data.name;
|
||||
$[3] = t3;
|
||||
$[3] = t2;
|
||||
} else {
|
||||
t3 = $[3];
|
||||
t2 = $[3];
|
||||
}
|
||||
let t4;
|
||||
if ($[4] !== el || $[5] !== t3) {
|
||||
t4 = <ValidateMemoization inputs={t3} output={el} />;
|
||||
let t3;
|
||||
if ($[4] !== el || $[5] !== t2) {
|
||||
t3 = <ValidateMemoization inputs={t2} output={el} />;
|
||||
$[4] = el;
|
||||
$[5] = t3;
|
||||
$[6] = t4;
|
||||
$[5] = t2;
|
||||
$[6] = t3;
|
||||
} else {
|
||||
t4 = $[6];
|
||||
t3 = $[6];
|
||||
}
|
||||
return t4;
|
||||
return t3;
|
||||
}
|
||||
|
||||
const props1 = { data: { name: "Mike" } };
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @flow
|
||||
function Component(props) {
|
||||
enum Bool {
|
||||
True = 'true',
|
||||
False = 'false',
|
||||
}
|
||||
|
||||
let bool: Bool = Bool.False;
|
||||
if (props.value) {
|
||||
bool = Bool.True;
|
||||
}
|
||||
return <div>{bool}</div>;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{value: true}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
function Component(props) {
|
||||
const $ = _c(2);
|
||||
enum Bool {
|
||||
True = "true",
|
||||
False = "false",
|
||||
}
|
||||
|
||||
let bool = Bool.False;
|
||||
if (props.value) {
|
||||
bool = Bool.True;
|
||||
}
|
||||
let t0;
|
||||
if ($[0] !== bool) {
|
||||
t0 = <div>{bool}</div>;
|
||||
$[0] = bool;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ value: true }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: exception) Bool is not defined
|
||||
@@ -0,0 +1,18 @@
|
||||
// @flow
|
||||
function Component(props) {
|
||||
enum Bool {
|
||||
True = 'true',
|
||||
False = 'false',
|
||||
}
|
||||
|
||||
let bool: Bool = Bool.False;
|
||||
if (props.value) {
|
||||
bool = Bool.True;
|
||||
}
|
||||
return <div>{bool}</div>;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{value: true}],
|
||||
};
|
||||
@@ -47,17 +47,14 @@ function Component(t0) {
|
||||
const $ = _c(19);
|
||||
const { a, b } = t0;
|
||||
let t1;
|
||||
let t2;
|
||||
if ($[0] !== a) {
|
||||
t2 = [a];
|
||||
t1 = [a];
|
||||
$[0] = a;
|
||||
$[1] = t2;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t2 = $[1];
|
||||
t1 = $[1];
|
||||
}
|
||||
t1 = t2;
|
||||
const x = t1;
|
||||
let t3;
|
||||
let items;
|
||||
if ($[2] !== b || $[3] !== x) {
|
||||
items = [b];
|
||||
@@ -70,59 +67,57 @@ function Component(t0) {
|
||||
} else {
|
||||
items = $[4];
|
||||
}
|
||||
|
||||
t3 = items;
|
||||
const y = t3;
|
||||
let t4;
|
||||
const y = items;
|
||||
let t2;
|
||||
if ($[5] !== a) {
|
||||
t4 = [a];
|
||||
t2 = [a];
|
||||
$[5] = a;
|
||||
$[6] = t4;
|
||||
$[6] = t2;
|
||||
} else {
|
||||
t4 = $[6];
|
||||
t2 = $[6];
|
||||
}
|
||||
let t5;
|
||||
if ($[7] !== t4 || $[8] !== x) {
|
||||
t5 = <ValidateMemoization inputs={t4} output={x} />;
|
||||
$[7] = t4;
|
||||
let t3;
|
||||
if ($[7] !== t2 || $[8] !== x) {
|
||||
t3 = <ValidateMemoization inputs={t2} output={x} />;
|
||||
$[7] = t2;
|
||||
$[8] = x;
|
||||
$[9] = t5;
|
||||
$[9] = t3;
|
||||
} else {
|
||||
t5 = $[9];
|
||||
t3 = $[9];
|
||||
}
|
||||
let t6;
|
||||
let t4;
|
||||
if ($[10] !== b || $[11] !== x) {
|
||||
t6 = [x, b];
|
||||
t4 = [x, b];
|
||||
$[10] = b;
|
||||
$[11] = x;
|
||||
$[12] = t6;
|
||||
$[12] = t4;
|
||||
} else {
|
||||
t6 = $[12];
|
||||
t4 = $[12];
|
||||
}
|
||||
let t7;
|
||||
if ($[13] !== t6 || $[14] !== y) {
|
||||
t7 = <ValidateMemoization inputs={t6} output={y} />;
|
||||
$[13] = t6;
|
||||
let t5;
|
||||
if ($[13] !== t4 || $[14] !== y) {
|
||||
t5 = <ValidateMemoization inputs={t4} output={y} />;
|
||||
$[13] = t4;
|
||||
$[14] = y;
|
||||
$[15] = t7;
|
||||
$[15] = t5;
|
||||
} else {
|
||||
t7 = $[15];
|
||||
t5 = $[15];
|
||||
}
|
||||
let t8;
|
||||
if ($[16] !== t5 || $[17] !== t7) {
|
||||
t8 = (
|
||||
let t6;
|
||||
if ($[16] !== t3 || $[17] !== t5) {
|
||||
t6 = (
|
||||
<>
|
||||
{t3}
|
||||
{t5}
|
||||
{t7}
|
||||
</>
|
||||
);
|
||||
$[16] = t5;
|
||||
$[17] = t7;
|
||||
$[18] = t8;
|
||||
$[16] = t3;
|
||||
$[17] = t5;
|
||||
$[18] = t6;
|
||||
} else {
|
||||
t8 = $[18];
|
||||
t6 = $[18];
|
||||
}
|
||||
return t8;
|
||||
return t6;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
|
||||
@@ -34,7 +34,6 @@ import { ValidateMemoization } from "shared-runtime";
|
||||
|
||||
function Component(props) {
|
||||
const $ = _c(7);
|
||||
let t0;
|
||||
let a;
|
||||
if ($[0] !== props.name) {
|
||||
a = [];
|
||||
@@ -48,26 +47,25 @@ function Component(props) {
|
||||
} else {
|
||||
a = $[1];
|
||||
}
|
||||
t0 = a;
|
||||
const a_0 = t0;
|
||||
let t1;
|
||||
const a_0 = a;
|
||||
let t0;
|
||||
if ($[2] !== props.name) {
|
||||
t1 = [props.name];
|
||||
t0 = [props.name];
|
||||
$[2] = props.name;
|
||||
$[3] = t1;
|
||||
$[3] = t0;
|
||||
} else {
|
||||
t1 = $[3];
|
||||
t0 = $[3];
|
||||
}
|
||||
let t2;
|
||||
if ($[4] !== a_0 || $[5] !== t1) {
|
||||
t2 = <ValidateMemoization inputs={t1} output={a_0} />;
|
||||
let t1;
|
||||
if ($[4] !== a_0 || $[5] !== t0) {
|
||||
t1 = <ValidateMemoization inputs={t0} output={a_0} />;
|
||||
$[4] = a_0;
|
||||
$[5] = t1;
|
||||
$[6] = t2;
|
||||
$[5] = t0;
|
||||
$[6] = t1;
|
||||
} else {
|
||||
t2 = $[6];
|
||||
t1 = $[6];
|
||||
}
|
||||
return t2;
|
||||
return t1;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
|
||||
```javascript
|
||||
// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @inferEffectDependencies
|
||||
import {useEffect} from 'react';
|
||||
import {useEffect, AUTODEPS} from 'react';
|
||||
import {print} from 'shared-runtime';
|
||||
|
||||
function ReactiveVariable({propVal}) {
|
||||
'use memo if(invalid identifier)';
|
||||
const arr = [propVal];
|
||||
useEffect(() => print(arr));
|
||||
useEffect(() => print(arr), AUTODEPS);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
@@ -25,8 +25,8 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
```
|
||||
6 | 'use memo if(invalid identifier)';
|
||||
7 | const arr = [propVal];
|
||||
> 8 | useEffect(() => print(arr));
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (8:8)
|
||||
> 8 | useEffect(() => print(arr), AUTODEPS);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (8:8)
|
||||
9 | }
|
||||
10 |
|
||||
11 | export const FIXTURE_ENTRYPOINT = {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @inferEffectDependencies
|
||||
import {useEffect} from 'react';
|
||||
import {useEffect, AUTODEPS} from 'react';
|
||||
import {print} from 'shared-runtime';
|
||||
|
||||
function ReactiveVariable({propVal}) {
|
||||
'use memo if(invalid identifier)';
|
||||
const arr = [propVal];
|
||||
useEffect(() => print(arr));
|
||||
useEffect(() => print(arr), AUTODEPS);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
|
||||
@@ -46,7 +46,7 @@ const React$useMemo = React.useMemo;
|
||||
const Internal$Reassigned$useHook = useHook;
|
||||
|
||||
function Component() {
|
||||
const $ = _c(8);
|
||||
const $ = _c(7);
|
||||
const [state] = React$useState(0);
|
||||
const object = Internal$Reassigned$useHook();
|
||||
let t0;
|
||||
@@ -59,34 +59,30 @@ function Component() {
|
||||
}
|
||||
const json = t0;
|
||||
let t1;
|
||||
let t2;
|
||||
if ($[2] !== state) {
|
||||
t1 = makeArray(state);
|
||||
const doubledArray = t1;
|
||||
const doubledArray = makeArray(state);
|
||||
|
||||
t2 = doubledArray.join("");
|
||||
t1 = doubledArray.join("");
|
||||
$[2] = state;
|
||||
$[3] = t2;
|
||||
$[4] = t1;
|
||||
$[3] = t1;
|
||||
} else {
|
||||
t2 = $[3];
|
||||
t1 = $[4];
|
||||
t1 = $[3];
|
||||
}
|
||||
let t3;
|
||||
if ($[5] !== json || $[6] !== t2) {
|
||||
t3 = (
|
||||
let t2;
|
||||
if ($[4] !== json || $[5] !== t1) {
|
||||
t2 = (
|
||||
<div>
|
||||
{t2}
|
||||
{t1}
|
||||
{json}
|
||||
</div>
|
||||
);
|
||||
$[5] = json;
|
||||
$[4] = json;
|
||||
$[5] = t1;
|
||||
$[6] = t2;
|
||||
$[7] = t3;
|
||||
} else {
|
||||
t3 = $[7];
|
||||
t2 = $[6];
|
||||
}
|
||||
return t3;
|
||||
return t2;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
|
||||
@@ -22,20 +22,16 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
function Component(props) {
|
||||
const $ = _c(3);
|
||||
let t0;
|
||||
const $ = _c(2);
|
||||
let items;
|
||||
if ($[0] !== props.a) {
|
||||
t0 = [];
|
||||
items = t0;
|
||||
items = [];
|
||||
|
||||
items.push(props.a);
|
||||
$[0] = props.a;
|
||||
$[1] = items;
|
||||
$[2] = t0;
|
||||
} else {
|
||||
items = $[1];
|
||||
t0 = $[2];
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
@@ -4,9 +4,10 @@
|
||||
```javascript
|
||||
// @inferEffectDependencies @compilationMode:"infer" @panicThreshold:"none"
|
||||
import useMyEffect from 'useEffectWrapper';
|
||||
import {AUTODEPS} from 'react';
|
||||
|
||||
function nonReactFn(arg) {
|
||||
useMyEffect(() => [1, 2, arg]);
|
||||
useMyEffect(() => [1, 2, arg], AUTODEPS);
|
||||
}
|
||||
|
||||
```
|
||||
@@ -15,12 +16,12 @@ function nonReactFn(arg) {
|
||||
## Error
|
||||
|
||||
```
|
||||
3 |
|
||||
4 | function nonReactFn(arg) {
|
||||
> 5 | useMyEffect(() => [1, 2, arg]);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (5:5)
|
||||
6 | }
|
||||
7 |
|
||||
4 |
|
||||
5 | function nonReactFn(arg) {
|
||||
> 6 | useMyEffect(() => [1, 2, arg], AUTODEPS);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (6:6)
|
||||
7 | }
|
||||
8 |
|
||||
```
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// @inferEffectDependencies @compilationMode:"infer" @panicThreshold:"none"
|
||||
import useMyEffect from 'useEffectWrapper';
|
||||
import {AUTODEPS} from 'react';
|
||||
|
||||
function nonReactFn(arg) {
|
||||
useMyEffect(() => [1, 2, arg]);
|
||||
useMyEffect(() => [1, 2, arg], AUTODEPS);
|
||||
}
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
|
||||
```javascript
|
||||
// @inferEffectDependencies @compilationMode:"infer" @panicThreshold:"none"
|
||||
import {useEffect} from 'react';
|
||||
import {useEffect, AUTODEPS} from 'react';
|
||||
|
||||
function nonReactFn(arg) {
|
||||
useEffect(() => [1, 2, arg]);
|
||||
useEffect(() => [1, 2, arg], AUTODEPS);
|
||||
}
|
||||
|
||||
```
|
||||
@@ -17,8 +17,8 @@ function nonReactFn(arg) {
|
||||
```
|
||||
3 |
|
||||
4 | function nonReactFn(arg) {
|
||||
> 5 | useEffect(() => [1, 2, arg]);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (5:5)
|
||||
> 5 | useEffect(() => [1, 2, arg], AUTODEPS);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (5:5)
|
||||
6 | }
|
||||
7 |
|
||||
```
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// @inferEffectDependencies @compilationMode:"infer" @panicThreshold:"none"
|
||||
import {useEffect} from 'react';
|
||||
import {useEffect, AUTODEPS} from 'react';
|
||||
|
||||
function nonReactFn(arg) {
|
||||
useEffect(() => [1, 2, arg]);
|
||||
useEffect(() => [1, 2, arg], AUTODEPS);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
```javascript
|
||||
// @inferEffectDependencies @panicThreshold:"none"
|
||||
import {useEffect} from 'react';
|
||||
import {useEffect, AUTODEPS} from 'react';
|
||||
|
||||
/**
|
||||
* Error on non-inlined effect functions:
|
||||
@@ -21,7 +21,7 @@ function Component({foo}) {
|
||||
}
|
||||
|
||||
// No inferred dep array, the argument is not a lambda
|
||||
useEffect(f);
|
||||
useEffect(f, AUTODEPS);
|
||||
}
|
||||
|
||||
```
|
||||
@@ -32,8 +32,8 @@ function Component({foo}) {
|
||||
```
|
||||
18 |
|
||||
19 | // No inferred dep array, the argument is not a lambda
|
||||
> 20 | useEffect(f);
|
||||
| ^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (20:20)
|
||||
> 20 | useEffect(f, AUTODEPS);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (20:20)
|
||||
21 | }
|
||||
22 |
|
||||
```
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// @inferEffectDependencies @panicThreshold:"none"
|
||||
import {useEffect} from 'react';
|
||||
import {useEffect, AUTODEPS} from 'react';
|
||||
|
||||
/**
|
||||
* Error on non-inlined effect functions:
|
||||
@@ -17,5 +17,5 @@ function Component({foo}) {
|
||||
}
|
||||
|
||||
// No inferred dep array, the argument is not a lambda
|
||||
useEffect(f);
|
||||
useEffect(f, AUTODEPS);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
// @dynamicGating:{"source":"shared-runtime"} @inferEffectDependencies @panicThreshold:"none"
|
||||
|
||||
import useEffectWrapper from 'useEffectWrapper';
|
||||
import {AUTODEPS} from 'react';
|
||||
|
||||
/**
|
||||
* TODO: run the non-forget enabled version through the effect inference
|
||||
@@ -13,7 +14,7 @@ import useEffectWrapper from 'useEffectWrapper';
|
||||
function Component({foo}) {
|
||||
'use memo if(getTrue)';
|
||||
const arr = [];
|
||||
useEffectWrapper(() => arr.push(foo));
|
||||
useEffectWrapper(() => arr.push(foo), AUTODEPS);
|
||||
arr.push(2);
|
||||
return arr;
|
||||
}
|
||||
@@ -30,13 +31,13 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Error
|
||||
|
||||
```
|
||||
10 | 'use memo if(getTrue)';
|
||||
11 | const arr = [];
|
||||
> 12 | useEffectWrapper(() => arr.push(foo));
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (12:12)
|
||||
13 | arr.push(2);
|
||||
14 | return arr;
|
||||
15 | }
|
||||
11 | 'use memo if(getTrue)';
|
||||
12 | const arr = [];
|
||||
> 13 | useEffectWrapper(() => arr.push(foo), AUTODEPS);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (13:13)
|
||||
14 | arr.push(2);
|
||||
15 | return arr;
|
||||
16 | }
|
||||
```
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// @dynamicGating:{"source":"shared-runtime"} @inferEffectDependencies @panicThreshold:"none"
|
||||
|
||||
import useEffectWrapper from 'useEffectWrapper';
|
||||
import {AUTODEPS} from 'react';
|
||||
|
||||
/**
|
||||
* TODO: run the non-forget enabled version through the effect inference
|
||||
@@ -9,7 +10,7 @@ import useEffectWrapper from 'useEffectWrapper';
|
||||
function Component({foo}) {
|
||||
'use memo if(getTrue)';
|
||||
const arr = [];
|
||||
useEffectWrapper(() => arr.push(foo));
|
||||
useEffectWrapper(() => arr.push(foo), AUTODEPS);
|
||||
arr.push(2);
|
||||
return arr;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
```javascript
|
||||
// @gating @inferEffectDependencies @panicThreshold:"none"
|
||||
import useEffectWrapper from 'useEffectWrapper';
|
||||
import {AUTODEPS} from 'react';
|
||||
|
||||
/**
|
||||
* TODO: run the non-forget enabled version through the effect inference
|
||||
@@ -11,7 +12,7 @@ import useEffectWrapper from 'useEffectWrapper';
|
||||
*/
|
||||
function Component({foo}) {
|
||||
const arr = [];
|
||||
useEffectWrapper(() => arr.push(foo));
|
||||
useEffectWrapper(() => arr.push(foo), AUTODEPS);
|
||||
arr.push(2);
|
||||
return arr;
|
||||
}
|
||||
@@ -28,13 +29,13 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Error
|
||||
|
||||
```
|
||||
8 | function Component({foo}) {
|
||||
9 | const arr = [];
|
||||
> 10 | useEffectWrapper(() => arr.push(foo));
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (10:10)
|
||||
11 | arr.push(2);
|
||||
12 | return arr;
|
||||
13 | }
|
||||
9 | function Component({foo}) {
|
||||
10 | const arr = [];
|
||||
> 11 | useEffectWrapper(() => arr.push(foo), AUTODEPS);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (11:11)
|
||||
12 | arr.push(2);
|
||||
13 | return arr;
|
||||
14 | }
|
||||
```
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// @gating @inferEffectDependencies @panicThreshold:"none"
|
||||
import useEffectWrapper from 'useEffectWrapper';
|
||||
import {AUTODEPS} from 'react';
|
||||
|
||||
/**
|
||||
* TODO: run the non-forget enabled version through the effect inference
|
||||
@@ -7,7 +8,7 @@ import useEffectWrapper from 'useEffectWrapper';
|
||||
*/
|
||||
function Component({foo}) {
|
||||
const arr = [];
|
||||
useEffectWrapper(() => arr.push(foo));
|
||||
useEffectWrapper(() => arr.push(foo), AUTODEPS);
|
||||
arr.push(2);
|
||||
return arr;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import React from 'react';
|
||||
|
||||
function NonReactiveDepInEffect() {
|
||||
const obj = makeObject_Primitives();
|
||||
React.useEffect(() => print(obj));
|
||||
React.useEffect(() => print(obj), React.AUTODEPS);
|
||||
}
|
||||
|
||||
```
|
||||
@@ -18,8 +18,8 @@ function NonReactiveDepInEffect() {
|
||||
```
|
||||
4 | function NonReactiveDepInEffect() {
|
||||
5 | const obj = makeObject_Primitives();
|
||||
> 6 | React.useEffect(() => print(obj));
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (6:6)
|
||||
> 6 | React.useEffect(() => print(obj), React.AUTODEPS);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (6:6)
|
||||
7 | }
|
||||
8 |
|
||||
```
|
||||
|
||||
@@ -3,5 +3,5 @@ import React from 'react';
|
||||
|
||||
function NonReactiveDepInEffect() {
|
||||
const obj = makeObject_Primitives();
|
||||
React.useEffect(() => print(obj));
|
||||
React.useEffect(() => print(obj), React.AUTODEPS);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
```javascript
|
||||
// @inferEffectDependencies @panicThreshold:"none"
|
||||
import {useSpecialEffect} from 'shared-runtime';
|
||||
import {AUTODEPS} from 'react';
|
||||
|
||||
/**
|
||||
* Note that a react compiler-based transform still has limitations on JS syntax.
|
||||
@@ -11,13 +12,17 @@ import {useSpecialEffect} from 'shared-runtime';
|
||||
*/
|
||||
function Component({prop1}) {
|
||||
'use memo';
|
||||
useSpecialEffect(() => {
|
||||
try {
|
||||
console.log(prop1);
|
||||
} finally {
|
||||
console.log('exiting');
|
||||
}
|
||||
}, [prop1]);
|
||||
useSpecialEffect(
|
||||
() => {
|
||||
try {
|
||||
console.log(prop1);
|
||||
} finally {
|
||||
console.log('exiting');
|
||||
}
|
||||
},
|
||||
[prop1],
|
||||
AUTODEPS
|
||||
);
|
||||
return <div>{prop1}</div>;
|
||||
}
|
||||
|
||||
@@ -27,25 +32,33 @@ function Component({prop1}) {
|
||||
## Error
|
||||
|
||||
```
|
||||
8 | function Component({prop1}) {
|
||||
9 | 'use memo';
|
||||
> 10 | useSpecialEffect(() => {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
> 11 | try {
|
||||
| ^^^^^^^^^
|
||||
> 12 | console.log(prop1);
|
||||
| ^^^^^^^^^
|
||||
> 13 | } finally {
|
||||
| ^^^^^^^^^
|
||||
> 14 | console.log('exiting');
|
||||
| ^^^^^^^^^
|
||||
> 15 | }
|
||||
| ^^^^^^^^^
|
||||
> 16 | }, [prop1]);
|
||||
| ^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics.. (Bailout reason: Todo: (BuildHIR::lowerStatement) Handle TryStatement without a catch clause (11:15)) (10:16)
|
||||
17 | return <div>{prop1}</div>;
|
||||
18 | }
|
||||
19 |
|
||||
9 | function Component({prop1}) {
|
||||
10 | 'use memo';
|
||||
> 11 | useSpecialEffect(
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
> 12 | () => {
|
||||
| ^^^^^^^^^^^
|
||||
> 13 | try {
|
||||
| ^^^^^^^^^^^
|
||||
> 14 | console.log(prop1);
|
||||
| ^^^^^^^^^^^
|
||||
> 15 | } finally {
|
||||
| ^^^^^^^^^^^
|
||||
> 16 | console.log('exiting');
|
||||
| ^^^^^^^^^^^
|
||||
> 17 | }
|
||||
| ^^^^^^^^^^^
|
||||
> 18 | },
|
||||
| ^^^^^^^^^^^
|
||||
> 19 | [prop1],
|
||||
| ^^^^^^^^^^^
|
||||
> 20 | AUTODEPS
|
||||
| ^^^^^^^^^^^
|
||||
> 21 | );
|
||||
| ^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics.. (Bailout reason: Todo: (BuildHIR::lowerStatement) Handle TryStatement without a catch clause (13:17)) (11:21)
|
||||
22 | return <div>{prop1}</div>;
|
||||
23 | }
|
||||
24 |
|
||||
```
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// @inferEffectDependencies @panicThreshold:"none"
|
||||
import {useSpecialEffect} from 'shared-runtime';
|
||||
import {AUTODEPS} from 'react';
|
||||
|
||||
/**
|
||||
* Note that a react compiler-based transform still has limitations on JS syntax.
|
||||
@@ -7,12 +8,16 @@ import {useSpecialEffect} from 'shared-runtime';
|
||||
*/
|
||||
function Component({prop1}) {
|
||||
'use memo';
|
||||
useSpecialEffect(() => {
|
||||
try {
|
||||
console.log(prop1);
|
||||
} finally {
|
||||
console.log('exiting');
|
||||
}
|
||||
}, [prop1]);
|
||||
useSpecialEffect(
|
||||
() => {
|
||||
try {
|
||||
console.log(prop1);
|
||||
} finally {
|
||||
console.log('exiting');
|
||||
}
|
||||
},
|
||||
[prop1],
|
||||
AUTODEPS
|
||||
);
|
||||
return <div>{prop1}</div>;
|
||||
}
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
|
||||
```javascript
|
||||
// @inferEffectDependencies @panicThreshold:"none"
|
||||
import {useEffect} from 'react';
|
||||
import {useEffect, AUTODEPS} from 'react';
|
||||
|
||||
function Component({propVal}) {
|
||||
'use no memo';
|
||||
useEffect(() => [propVal]);
|
||||
useEffect(() => [propVal], AUTODEPS);
|
||||
}
|
||||
|
||||
```
|
||||
@@ -18,8 +18,8 @@ function Component({propVal}) {
|
||||
```
|
||||
4 | function Component({propVal}) {
|
||||
5 | 'use no memo';
|
||||
> 6 | useEffect(() => [propVal]);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (6:6)
|
||||
> 6 | useEffect(() => [propVal], AUTODEPS);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (6:6)
|
||||
7 | }
|
||||
8 |
|
||||
```
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// @inferEffectDependencies @panicThreshold:"none"
|
||||
import {useEffect} from 'react';
|
||||
import {useEffect, AUTODEPS} from 'react';
|
||||
|
||||
function Component({propVal}) {
|
||||
'use no memo';
|
||||
useEffect(() => [propVal]);
|
||||
useEffect(() => [propVal], AUTODEPS);
|
||||
}
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
|
||||
```javascript
|
||||
// @inferEffectDependencies @panicThreshold:"none"
|
||||
import {useEffect} from 'react';
|
||||
import {useEffect, AUTODEPS} from 'react';
|
||||
import {print} from 'shared-runtime';
|
||||
|
||||
function Component({foo}) {
|
||||
const arr = [];
|
||||
// Taking either arr[0].value or arr as a dependency is reasonable
|
||||
// as long as developers know what to expect.
|
||||
useEffect(() => print(arr[0].value));
|
||||
useEffect(() => print(arr[0].value), AUTODEPS);
|
||||
arr.push({value: foo});
|
||||
return arr;
|
||||
}
|
||||
@@ -21,7 +21,7 @@ function Component({foo}) {
|
||||
|
||||
```javascript
|
||||
// @inferEffectDependencies @panicThreshold:"none"
|
||||
import { useEffect } from "react";
|
||||
import { useEffect, AUTODEPS } from "react";
|
||||
import { print } from "shared-runtime";
|
||||
|
||||
function Component(t0) {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
// @inferEffectDependencies @panicThreshold:"none"
|
||||
import {useEffect} from 'react';
|
||||
import {useEffect, AUTODEPS} from 'react';
|
||||
import {print} from 'shared-runtime';
|
||||
|
||||
function Component({foo}) {
|
||||
const arr = [];
|
||||
// Taking either arr[0].value or arr as a dependency is reasonable
|
||||
// as long as developers know what to expect.
|
||||
useEffect(() => print(arr[0].value));
|
||||
useEffect(() => print(arr[0].value), AUTODEPS);
|
||||
arr.push({value: foo});
|
||||
return arr;
|
||||
}
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
|
||||
```javascript
|
||||
// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly
|
||||
import {useEffect} from 'react';
|
||||
import {useEffect, AUTODEPS} from 'react';
|
||||
import {print} from 'shared-runtime';
|
||||
|
||||
function Component({foo}) {
|
||||
const arr = [];
|
||||
// Taking either arr[0].value or arr as a dependency is reasonable
|
||||
// as long as developers know what to expect.
|
||||
useEffect(() => print(arr[0]?.value));
|
||||
useEffect(() => print(arr[0]?.value), AUTODEPS);
|
||||
arr.push({value: foo});
|
||||
return arr;
|
||||
}
|
||||
@@ -26,7 +26,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
|
||||
```javascript
|
||||
// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly
|
||||
import { useEffect } from "react";
|
||||
import { useEffect, AUTODEPS } from "react";
|
||||
import { print } from "shared-runtime";
|
||||
|
||||
function Component(t0) {
|
||||
@@ -48,9 +48,9 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Logs
|
||||
|
||||
```
|
||||
{"kind":"CompileError","fnLoc":{"start":{"line":5,"column":0,"index":139},"end":{"line":12,"column":1,"index":384},"filename":"mutate-after-useeffect-optional-chain.ts"},"detail":{"reason":"Updating a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the mutation before calling useEffect()","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":10,"column":2,"index":345},"end":{"line":10,"column":5,"index":348},"filename":"mutate-after-useeffect-optional-chain.ts","identifierName":"arr"}}}
|
||||
{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":9,"column":2,"index":304},"end":{"line":9,"column":39,"index":341},"filename":"mutate-after-useeffect-optional-chain.ts"},"decorations":[{"start":{"line":9,"column":24,"index":326},"end":{"line":9,"column":27,"index":329},"filename":"mutate-after-useeffect-optional-chain.ts","identifierName":"arr"}]}
|
||||
{"kind":"CompileSuccess","fnLoc":{"start":{"line":5,"column":0,"index":139},"end":{"line":12,"column":1,"index":384},"filename":"mutate-after-useeffect-optional-chain.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0}
|
||||
{"kind":"CompileError","fnLoc":{"start":{"line":5,"column":0,"index":149},"end":{"line":12,"column":1,"index":404},"filename":"mutate-after-useeffect-optional-chain.ts"},"detail":{"reason":"Updating a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the mutation before calling useEffect()","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":10,"column":2,"index":365},"end":{"line":10,"column":5,"index":368},"filename":"mutate-after-useeffect-optional-chain.ts","identifierName":"arr"}}}
|
||||
{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":9,"column":2,"index":314},"end":{"line":9,"column":49,"index":361},"filename":"mutate-after-useeffect-optional-chain.ts"},"decorations":[{"start":{"line":9,"column":24,"index":336},"end":{"line":9,"column":27,"index":339},"filename":"mutate-after-useeffect-optional-chain.ts","identifierName":"arr"}]}
|
||||
{"kind":"CompileSuccess","fnLoc":{"start":{"line":5,"column":0,"index":149},"end":{"line":12,"column":1,"index":404},"filename":"mutate-after-useeffect-optional-chain.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0}
|
||||
```
|
||||
|
||||
### Eval output
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly
|
||||
import {useEffect} from 'react';
|
||||
import {useEffect, AUTODEPS} from 'react';
|
||||
import {print} from 'shared-runtime';
|
||||
|
||||
function Component({foo}) {
|
||||
const arr = [];
|
||||
// Taking either arr[0].value or arr as a dependency is reasonable
|
||||
// as long as developers know what to expect.
|
||||
useEffect(() => print(arr[0]?.value));
|
||||
useEffect(() => print(arr[0]?.value), AUTODEPS);
|
||||
arr.push({value: foo});
|
||||
return arr;
|
||||
}
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
```javascript
|
||||
// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly
|
||||
|
||||
import {useEffect, useRef} from 'react';
|
||||
import {useEffect, useRef, AUTODEPS} from 'react';
|
||||
import {print} from 'shared-runtime';
|
||||
|
||||
function Component({arrRef}) {
|
||||
// Avoid taking arr.current as a dependency
|
||||
useEffect(() => print(arrRef.current));
|
||||
useEffect(() => print(arrRef.current), AUTODEPS);
|
||||
arrRef.current.val = 2;
|
||||
return arrRef;
|
||||
}
|
||||
@@ -26,7 +26,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
```javascript
|
||||
// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly
|
||||
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useEffect, useRef, AUTODEPS } from "react";
|
||||
import { print } from "shared-runtime";
|
||||
|
||||
function Component(t0) {
|
||||
@@ -47,9 +47,9 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Logs
|
||||
|
||||
```
|
||||
{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":148},"end":{"line":11,"column":1,"index":311},"filename":"mutate-after-useeffect-ref-access.ts"},"detail":{"reason":"Mutating component props or hook arguments is not allowed. Consider using a local variable instead","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":9,"column":2,"index":269},"end":{"line":9,"column":16,"index":283},"filename":"mutate-after-useeffect-ref-access.ts"}}}
|
||||
{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":8,"column":2,"index":227},"end":{"line":8,"column":40,"index":265},"filename":"mutate-after-useeffect-ref-access.ts"},"decorations":[{"start":{"line":8,"column":24,"index":249},"end":{"line":8,"column":30,"index":255},"filename":"mutate-after-useeffect-ref-access.ts","identifierName":"arrRef"}]}
|
||||
{"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":148},"end":{"line":11,"column":1,"index":311},"filename":"mutate-after-useeffect-ref-access.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0}
|
||||
{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":158},"end":{"line":11,"column":1,"index":331},"filename":"mutate-after-useeffect-ref-access.ts"},"detail":{"reason":"Mutating component props or hook arguments is not allowed. Consider using a local variable instead","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":9,"column":2,"index":289},"end":{"line":9,"column":16,"index":303},"filename":"mutate-after-useeffect-ref-access.ts"}}}
|
||||
{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":8,"column":2,"index":237},"end":{"line":8,"column":50,"index":285},"filename":"mutate-after-useeffect-ref-access.ts"},"decorations":[{"start":{"line":8,"column":24,"index":259},"end":{"line":8,"column":30,"index":265},"filename":"mutate-after-useeffect-ref-access.ts","identifierName":"arrRef"}]}
|
||||
{"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":158},"end":{"line":11,"column":1,"index":331},"filename":"mutate-after-useeffect-ref-access.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0}
|
||||
```
|
||||
|
||||
### Eval output
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly
|
||||
|
||||
import {useEffect, useRef} from 'react';
|
||||
import {useEffect, useRef, AUTODEPS} from 'react';
|
||||
import {print} from 'shared-runtime';
|
||||
|
||||
function Component({arrRef}) {
|
||||
// Avoid taking arr.current as a dependency
|
||||
useEffect(() => print(arrRef.current));
|
||||
useEffect(() => print(arrRef.current), AUTODEPS);
|
||||
arrRef.current.val = 2;
|
||||
return arrRef;
|
||||
}
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
|
||||
```javascript
|
||||
// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly
|
||||
import {useEffect} from 'react';
|
||||
import {useEffect, AUTODEPS} from 'react';
|
||||
|
||||
function Component({foo}) {
|
||||
const arr = [];
|
||||
useEffect(() => {
|
||||
arr.push(foo);
|
||||
});
|
||||
}, AUTODEPS);
|
||||
arr.push(2);
|
||||
return arr;
|
||||
}
|
||||
@@ -25,7 +25,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
|
||||
```javascript
|
||||
// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly
|
||||
import { useEffect } from "react";
|
||||
import { useEffect, AUTODEPS } from "react";
|
||||
|
||||
function Component(t0) {
|
||||
const { foo } = t0;
|
||||
@@ -47,9 +47,9 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Logs
|
||||
|
||||
```
|
||||
{"kind":"CompileError","fnLoc":{"start":{"line":4,"column":0,"index":101},"end":{"line":11,"column":1,"index":222},"filename":"mutate-after-useeffect.ts"},"detail":{"reason":"Updating a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the mutation before calling useEffect()","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":9,"column":2,"index":194},"end":{"line":9,"column":5,"index":197},"filename":"mutate-after-useeffect.ts","identifierName":"arr"}}}
|
||||
{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":6,"column":2,"index":149},"end":{"line":8,"column":4,"index":190},"filename":"mutate-after-useeffect.ts"},"decorations":[{"start":{"line":7,"column":4,"index":171},"end":{"line":7,"column":7,"index":174},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},{"start":{"line":7,"column":4,"index":171},"end":{"line":7,"column":7,"index":174},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},{"start":{"line":7,"column":13,"index":180},"end":{"line":7,"column":16,"index":183},"filename":"mutate-after-useeffect.ts","identifierName":"foo"}]}
|
||||
{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":101},"end":{"line":11,"column":1,"index":222},"filename":"mutate-after-useeffect.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0}
|
||||
{"kind":"CompileError","fnLoc":{"start":{"line":4,"column":0,"index":111},"end":{"line":11,"column":1,"index":242},"filename":"mutate-after-useeffect.ts"},"detail":{"reason":"Updating a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the mutation before calling useEffect()","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":9,"column":2,"index":214},"end":{"line":9,"column":5,"index":217},"filename":"mutate-after-useeffect.ts","identifierName":"arr"}}}
|
||||
{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":6,"column":2,"index":159},"end":{"line":8,"column":14,"index":210},"filename":"mutate-after-useeffect.ts"},"decorations":[{"start":{"line":7,"column":4,"index":181},"end":{"line":7,"column":7,"index":184},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},{"start":{"line":7,"column":4,"index":181},"end":{"line":7,"column":7,"index":184},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},{"start":{"line":7,"column":13,"index":190},"end":{"line":7,"column":16,"index":193},"filename":"mutate-after-useeffect.ts","identifierName":"foo"}]}
|
||||
{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":111},"end":{"line":11,"column":1,"index":242},"filename":"mutate-after-useeffect.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0}
|
||||
```
|
||||
|
||||
### Eval output
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly
|
||||
import {useEffect} from 'react';
|
||||
import {useEffect, AUTODEPS} from 'react';
|
||||
|
||||
function Component({foo}) {
|
||||
const arr = [];
|
||||
useEffect(() => {
|
||||
arr.push(foo);
|
||||
});
|
||||
}, AUTODEPS);
|
||||
arr.push(2);
|
||||
return arr;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @inferEffectDependencies
|
||||
import {useEffect, AUTODEPS} from 'react';
|
||||
|
||||
function Component({foo}) {
|
||||
useEffect(AUTODEPS);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
3 |
|
||||
4 | function Component({foo}) {
|
||||
> 5 | useEffect(AUTODEPS);
|
||||
| ^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (5:5)
|
||||
6 | }
|
||||
7 |
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
// @inferEffectDependencies
|
||||
import {useEffect, AUTODEPS} from 'react';
|
||||
|
||||
function Component({foo}) {
|
||||
useEffect(AUTODEPS);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @inferEffectDependencies
|
||||
import {AUTODEPS} from 'react';
|
||||
import useEffectWrapper from 'useEffectWrapper';
|
||||
|
||||
function Component({foo}) {
|
||||
useEffectWrapper(
|
||||
() => {
|
||||
console.log(foo);
|
||||
},
|
||||
[foo],
|
||||
AUTODEPS
|
||||
);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
4 |
|
||||
5 | function Component({foo}) {
|
||||
> 6 | useEffectWrapper(
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
> 7 | () => {
|
||||
| ^^^^^^^^^^^
|
||||
> 8 | console.log(foo);
|
||||
| ^^^^^^^^^^^
|
||||
> 9 | },
|
||||
| ^^^^^^^^^^^
|
||||
> 10 | [foo],
|
||||
| ^^^^^^^^^^^
|
||||
> 11 | AUTODEPS
|
||||
| ^^^^^^^^^^^
|
||||
> 12 | );
|
||||
| ^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (6:12)
|
||||
13 | }
|
||||
14 |
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
// @inferEffectDependencies
|
||||
import {AUTODEPS} from 'react';
|
||||
import useEffectWrapper from 'useEffectWrapper';
|
||||
|
||||
function Component({foo}) {
|
||||
useEffectWrapper(
|
||||
() => {
|
||||
console.log(foo);
|
||||
},
|
||||
[foo],
|
||||
AUTODEPS
|
||||
);
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
```javascript
|
||||
// @inferEffectDependencies
|
||||
import {useEffect, useRef} from 'react';
|
||||
import {useEffect, useRef, AUTODEPS} from 'react';
|
||||
function useCustomRef() {
|
||||
const ref = useRef();
|
||||
return ref;
|
||||
@@ -12,7 +12,7 @@ function NonReactiveWrapper() {
|
||||
const ref = useCustomRef();
|
||||
useEffect(() => {
|
||||
print(ref);
|
||||
});
|
||||
}, AUTODEPS);
|
||||
}
|
||||
|
||||
```
|
||||
@@ -21,7 +21,7 @@ function NonReactiveWrapper() {
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useEffect, useRef, AUTODEPS } from "react";
|
||||
function useCustomRef() {
|
||||
const ref = useRef();
|
||||
return ref;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// @inferEffectDependencies
|
||||
import {useEffect, useRef} from 'react';
|
||||
import {useEffect, useRef, AUTODEPS} from 'react';
|
||||
function useCustomRef() {
|
||||
const ref = useRef();
|
||||
return ref;
|
||||
@@ -8,5 +8,5 @@ function NonReactiveWrapper() {
|
||||
const ref = useCustomRef();
|
||||
useEffect(() => {
|
||||
print(ref);
|
||||
});
|
||||
}, AUTODEPS);
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@ import * as SharedRuntime from 'shared-runtime';
|
||||
|
||||
function NonReactiveDepInEffect() {
|
||||
const obj = makeObject_Primitives();
|
||||
React.useEffect(() => print(obj));
|
||||
SharedRuntime.useSpecialEffect(() => print(obj), [obj]);
|
||||
React.useEffect(() => print(obj), React.AUTODEPS);
|
||||
SharedRuntime.useSpecialEffect(() => print(obj), [obj], React.AUTODEPS);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
@@ -4,6 +4,6 @@ import * as SharedRuntime from 'shared-runtime';
|
||||
|
||||
function NonReactiveDepInEffect() {
|
||||
const obj = makeObject_Primitives();
|
||||
React.useEffect(() => print(obj));
|
||||
SharedRuntime.useSpecialEffect(() => print(obj), [obj]);
|
||||
React.useEffect(() => print(obj), React.AUTODEPS);
|
||||
SharedRuntime.useSpecialEffect(() => print(obj), [obj], React.AUTODEPS);
|
||||
}
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
```javascript
|
||||
// @inferEffectDependencies
|
||||
import {print, useSpecialEffect} from 'shared-runtime';
|
||||
import {AUTODEPS} from 'react';
|
||||
|
||||
function CustomConfig({propVal}) {
|
||||
// Insertion
|
||||
useSpecialEffect(() => print(propVal), [propVal]);
|
||||
useSpecialEffect(() => print(propVal), [propVal], AUTODEPS);
|
||||
// No insertion
|
||||
useSpecialEffect(() => print(propVal), [propVal], [propVal]);
|
||||
}
|
||||
@@ -19,6 +20,7 @@ function CustomConfig({propVal}) {
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies
|
||||
import { print, useSpecialEffect } from "shared-runtime";
|
||||
import { AUTODEPS } from "react";
|
||||
|
||||
function CustomConfig(t0) {
|
||||
const $ = _c(7);
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
// @inferEffectDependencies
|
||||
import {print, useSpecialEffect} from 'shared-runtime';
|
||||
import {AUTODEPS} from 'react';
|
||||
|
||||
function CustomConfig({propVal}) {
|
||||
// Insertion
|
||||
useSpecialEffect(() => print(propVal), [propVal]);
|
||||
useSpecialEffect(() => print(propVal), [propVal], AUTODEPS);
|
||||
// No insertion
|
||||
useSpecialEffect(() => print(propVal), [propVal], [propVal]);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
```javascript
|
||||
// @inferEffectDependencies
|
||||
import {useEffect, useRef} from 'react';
|
||||
import {useEffect, useRef, AUTODEPS} from 'react';
|
||||
import useEffectWrapper from 'useEffectWrapper';
|
||||
|
||||
const moduleNonReactive = 0;
|
||||
@@ -24,7 +24,7 @@ function Component({foo, bar}) {
|
||||
console.log(ref.current);
|
||||
console.log(localNonPrimitiveReactive);
|
||||
console.log(localNonPrimitiveNonreactive);
|
||||
});
|
||||
}, AUTODEPS);
|
||||
|
||||
// Optional chains and property accesses
|
||||
// TODO: we may be able to save bytes by omitting property accesses if the
|
||||
@@ -32,11 +32,11 @@ function Component({foo, bar}) {
|
||||
useEffect(() => {
|
||||
console.log(bar?.baz);
|
||||
console.log(bar.qux);
|
||||
});
|
||||
}, AUTODEPS);
|
||||
|
||||
useEffectWrapper(() => {
|
||||
console.log(foo);
|
||||
});
|
||||
}, AUTODEPS);
|
||||
}
|
||||
|
||||
```
|
||||
@@ -45,7 +45,7 @@ function Component({foo, bar}) {
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useEffect, useRef, AUTODEPS } from "react";
|
||||
import useEffectWrapper from "useEffectWrapper";
|
||||
|
||||
const moduleNonReactive = 0;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// @inferEffectDependencies
|
||||
import {useEffect, useRef} from 'react';
|
||||
import {useEffect, useRef, AUTODEPS} from 'react';
|
||||
import useEffectWrapper from 'useEffectWrapper';
|
||||
|
||||
const moduleNonReactive = 0;
|
||||
@@ -20,7 +20,7 @@ function Component({foo, bar}) {
|
||||
console.log(ref.current);
|
||||
console.log(localNonPrimitiveReactive);
|
||||
console.log(localNonPrimitiveNonreactive);
|
||||
});
|
||||
}, AUTODEPS);
|
||||
|
||||
// Optional chains and property accesses
|
||||
// TODO: we may be able to save bytes by omitting property accesses if the
|
||||
@@ -28,9 +28,9 @@ function Component({foo, bar}) {
|
||||
useEffect(() => {
|
||||
console.log(bar?.baz);
|
||||
console.log(bar.qux);
|
||||
});
|
||||
}, AUTODEPS);
|
||||
|
||||
useEffectWrapper(() => {
|
||||
console.log(foo);
|
||||
});
|
||||
}, AUTODEPS);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user