Compare commits
142 Commits
sync-nextj
...
pr32012
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b817b78bc | ||
|
|
8932ca32f4 | ||
|
|
c4595ca4c8 | ||
|
|
74ea0c73a2 | ||
|
|
1506685f0e | ||
|
|
fd9cfa416f | ||
|
|
800c9db22e | ||
|
|
98418e8902 | ||
|
|
38127b2815 | ||
|
|
3a5496b3f5 | ||
|
|
a4d122f2d1 | ||
|
|
e30c6693e4 | ||
|
|
379089d288 | ||
|
|
426872679d | ||
|
|
a160102f3a | ||
|
|
f892dabd8c | ||
|
|
6efbc0897f | ||
|
|
7b402084af | ||
|
|
3314162535 | ||
|
|
e0c893f51d | ||
|
|
6b865330f4 | ||
|
|
83be48b9de | ||
|
|
defffdbba4 | ||
|
|
3ce77d55a2 | ||
|
|
11df5224e6 | ||
|
|
9627d71c50 | ||
|
|
301a18a6af | ||
|
|
03e4ec2d0f | ||
|
|
bf883bebbc | ||
|
|
f42f8c0635 | ||
|
|
3b009b4cd5 | ||
|
|
220dece92b | ||
|
|
c784273bcc | ||
|
|
dc7578290f | ||
|
|
7c11aad374 | ||
|
|
d8a08f8e39 | ||
|
|
a7c898d83a | ||
|
|
c81312e3a7 | ||
|
|
d8b903f49e | ||
|
|
6ca7fbe884 | ||
|
|
0de1233fd1 | ||
|
|
1e9eb95db5 | ||
|
|
fe21c947c8 | ||
|
|
c8c89fab5b | ||
|
|
62208bee5a | ||
|
|
694d3e1aae | ||
|
|
c01b8058e6 | ||
|
|
50f00fd876 | ||
|
|
d4ac7689f9 | ||
|
|
4309bde2b4 | ||
|
|
fc8a898dd1 | ||
|
|
97d794958f | ||
|
|
94867f33be | ||
|
|
6907aa2a30 | ||
|
|
45a720f7c7 | ||
|
|
ab27231dc5 | ||
|
|
03297e048d | ||
|
|
99471c02dd | ||
|
|
26297f5383 | ||
|
|
6a3d6a4382 | ||
|
|
de82912e62 | ||
|
|
518d06d26a | ||
|
|
36d15d5862 | ||
|
|
c70ab3f4b0 | ||
|
|
9f540fcc51 | ||
|
|
8f92ea467e | ||
|
|
bd76ce54d9 | ||
|
|
9463d51e51 | ||
|
|
a9bbe34622 | ||
|
|
17520b6381 | ||
|
|
7de040ccfa | ||
|
|
74e39ce2a1 | ||
|
|
a34aa05e69 | ||
|
|
faf6c4dfdc | ||
|
|
ef979d4703 | ||
|
|
95465dc491 | ||
|
|
74dd2da9ac | ||
|
|
e1d843f4d8 | ||
|
|
1e9ef39a87 | ||
|
|
7eb8234f7c | ||
|
|
2bd1c756c6 | ||
|
|
6a4b46cd70 | ||
|
|
facec3ee71 | ||
|
|
f5077bcc92 | ||
|
|
34ee3919c3 | ||
|
|
d428725882 | ||
|
|
975cea2d3d | ||
|
|
49b1a956a9 | ||
|
|
8dab5920e0 | ||
|
|
8a7b30669a | ||
|
|
a78bbf9dbc | ||
|
|
c869063f0d | ||
|
|
308be6e8dc | ||
|
|
d325f872de | ||
|
|
ac17270652 | ||
|
|
80b81fe563 | ||
|
|
e30872a4e0 | ||
|
|
54e86bd0d0 | ||
|
|
bdf187174d | ||
|
|
07facb52d3 | ||
|
|
909ed63e0a | ||
|
|
031230d2e0 | ||
|
|
f7b1273da2 | ||
|
|
e06c72fcf4 | ||
|
|
2d320563f3 | ||
|
|
c80b336d23 | ||
|
|
c32780eeb4 | ||
|
|
d1dd7feabc | ||
|
|
9e2c233139 | ||
|
|
0d67cc0651 | ||
|
|
2e25ee373d | ||
|
|
a1b3bd0da0 | ||
|
|
152080276c | ||
|
|
4996a8fa5c | ||
|
|
3ad17ecd31 | ||
|
|
982cf95c8b | ||
|
|
08dfd0b805 | ||
|
|
ef63718a27 | ||
|
|
fb12845d77 | ||
|
|
17ca4b157f | ||
|
|
4dff0e62b2 | ||
|
|
a7b829524b | ||
|
|
56ae4b8d22 | ||
|
|
d5e8f79cf4 | ||
|
|
7130d0c6cf | ||
|
|
130095f76b | ||
|
|
e854ce3b15 | ||
|
|
c86542b240 | ||
|
|
6928bf2f7c | ||
|
|
ca587425fe | ||
|
|
a4964987dc | ||
|
|
92b62f500c | ||
|
|
79ddf5b574 | ||
|
|
7c4a7c9ddf | ||
|
|
16367ceb02 | ||
|
|
7cb356e862 | ||
|
|
4a8fc0f92e | ||
|
|
3b597c0576 | ||
|
|
372ec00c03 | ||
|
|
3d2ab01a55 | ||
|
|
76d603a72a | ||
|
|
226b85926a |
@@ -303,7 +303,6 @@ module.exports = {
|
||||
ERROR,
|
||||
{isProductionUserAppCode: true},
|
||||
],
|
||||
'react-internal/no-to-warn-dev-within-to-throw': ERROR,
|
||||
'react-internal/warning-args': ERROR,
|
||||
'react-internal/no-production-logging': ERROR,
|
||||
},
|
||||
@@ -330,6 +329,7 @@ module.exports = {
|
||||
'packages/react-server-dom-esm/**/*.js',
|
||||
'packages/react-server-dom-webpack/**/*.js',
|
||||
'packages/react-server-dom-turbopack/**/*.js',
|
||||
'packages/react-server-dom-parcel/**/*.js',
|
||||
'packages/react-server-dom-fb/**/*.js',
|
||||
'packages/react-test-renderer/**/*.js',
|
||||
'packages/react-debug-tools/**/*.js',
|
||||
@@ -481,6 +481,12 @@ module.exports = {
|
||||
__turbopack_require__: 'readonly',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['packages/react-server-dom-parcel/**/*.js'],
|
||||
globals: {
|
||||
parcelRequire: 'readonly',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['packages/scheduler/**/*.js'],
|
||||
globals: {
|
||||
|
||||
8
.github/workflows/compiler_playground.yml
vendored
8
.github/workflows/compiler_playground.yml
vendored
@@ -9,7 +9,7 @@ on:
|
||||
- .github/workflows/compiler_playground.yml
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
|
||||
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
@@ -38,11 +38,7 @@ jobs:
|
||||
with:
|
||||
path: "**/node_modules"
|
||||
key: compiler-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
|
||||
- name: yarn install compiler
|
||||
run: yarn install --frozen-lockfile
|
||||
working-directory: compiler
|
||||
- name: yarn install playground
|
||||
run: yarn install --frozen-lockfile
|
||||
- run: yarn install --frozen-lockfile
|
||||
- run: npx playwright install --with-deps chromium
|
||||
- run: CI=true yarn test
|
||||
- run: ls -R test-results
|
||||
|
||||
2
.github/workflows/compiler_rust.yml
vendored
2
.github/workflows/compiler_rust.yml
vendored
@@ -16,7 +16,7 @@ on:
|
||||
- compiler/*.toml
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
|
||||
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
|
||||
2
.github/workflows/compiler_typescript.yml
vendored
2
.github/workflows/compiler_typescript.yml
vendored
@@ -9,7 +9,7 @@ on:
|
||||
- .github/workflows/compiler_typescript.yml
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
|
||||
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
|
||||
34
.github/workflows/runtime_build_and_test.yml
vendored
34
.github/workflows/runtime_build_and_test.yml
vendored
@@ -8,7 +8,7 @@ on:
|
||||
- compiler/**
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
|
||||
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
@@ -25,6 +25,8 @@ jobs:
|
||||
matrix: ${{ steps.set-matrix.outputs.result }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/github-script@v7
|
||||
id: set-matrix
|
||||
with:
|
||||
@@ -42,6 +44,8 @@ jobs:
|
||||
flow_inline_config_shortname: ${{ fromJSON(needs.discover_flow_inline_configs.outputs.matrix) }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
@@ -64,6 +68,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
@@ -88,6 +94,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
@@ -139,6 +147,8 @@ jobs:
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
@@ -166,6 +176,8 @@ jobs:
|
||||
release_channel: [stable, experimental]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
@@ -242,6 +254,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
@@ -272,6 +286,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
@@ -317,6 +333,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
@@ -350,6 +368,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
@@ -380,6 +400,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
@@ -419,6 +441,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
@@ -480,6 +504,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
@@ -528,6 +554,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
@@ -562,6 +590,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
@@ -603,6 +633,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
|
||||
21
.github/workflows/shared_discord_notify.yml
vendored
Normal file
21
.github/workflows/shared_discord_notify.yml
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
name: (Shared) Discord Notify
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [labeled]
|
||||
|
||||
jobs:
|
||||
notify:
|
||||
if: ${{ github.event.label.name == 'React Core Team' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Discord Webhook Action
|
||||
uses: tsickert/discord-webhook@v6.0.0
|
||||
with:
|
||||
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
|
||||
embed-author-name: ${{ github.event.pull_request.user.login }}
|
||||
embed-author-url: ${{ github.event.pull_request.user.html_url }}
|
||||
embed-author-icon-url: ${{ github.event.pull_request.user.avatar_url }}
|
||||
embed-title: '#${{ github.event.number }} (+${{github.event.pull_request.additions}} -${{github.event.pull_request.deletions}}): ${{ github.event.pull_request.title }}'
|
||||
embed-description: ${{ github.event.pull_request.body }}
|
||||
embed-url: ${{ github.event.pull_request.html_url }}
|
||||
2
.github/workflows/shared_lint.yml
vendored
2
.github/workflows/shared_lint.yml
vendored
@@ -6,7 +6,7 @@ on:
|
||||
pull_request:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
|
||||
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
|
||||
@@ -7,18 +7,18 @@
|
||||
//
|
||||
// The @latest channel uses the version as-is, e.g.:
|
||||
//
|
||||
// 19.0.0
|
||||
// 19.1.0
|
||||
//
|
||||
// The @canary channel appends additional information, with the scheme
|
||||
// <version>-<label>-<commit_sha>, e.g.:
|
||||
//
|
||||
// 19.0.0-canary-a1c2d3e4
|
||||
// 19.1.0-canary-a1c2d3e4
|
||||
//
|
||||
// The @experimental channel doesn't include a version, only a date and a sha, e.g.:
|
||||
//
|
||||
// 0.0.0-experimental-241c4467e-20200129
|
||||
|
||||
const ReactVersion = '19.0.0';
|
||||
const ReactVersion = '19.1.0';
|
||||
|
||||
// The label used by the @canary channel. Represents the upcoming release's
|
||||
// stability. Most of the time, this will be "canary", but we may temporarily
|
||||
@@ -26,27 +26,28 @@ const ReactVersion = '19.0.0';
|
||||
//
|
||||
// It only affects the label used in the version string. To customize the
|
||||
// npm dist tags used during publish, refer to .github/workflows/runtime_prereleases_*.yml.
|
||||
const canaryChannelLabel = 'rc';
|
||||
const canaryChannelLabel = 'canary';
|
||||
|
||||
// If the canaryChannelLabel is "rc", the build pipeline will use this to build
|
||||
// an RC version of the packages.
|
||||
const rcNumber = 1;
|
||||
const rcNumber = 0;
|
||||
|
||||
const stablePackages = {
|
||||
'eslint-plugin-react-hooks': '5.1.0',
|
||||
'jest-react': '0.16.0',
|
||||
'eslint-plugin-react-hooks': '5.2.0',
|
||||
'jest-react': '0.17.0',
|
||||
react: ReactVersion,
|
||||
'react-art': ReactVersion,
|
||||
'react-dom': ReactVersion,
|
||||
'react-server-dom-webpack': ReactVersion,
|
||||
'react-server-dom-turbopack': ReactVersion,
|
||||
'react-server-dom-parcel': ReactVersion,
|
||||
'react-is': ReactVersion,
|
||||
'react-reconciler': '0.31.0',
|
||||
'react-refresh': '0.16.0',
|
||||
'react-reconciler': '0.32.0',
|
||||
'react-refresh': '0.17.0',
|
||||
'react-test-renderer': ReactVersion,
|
||||
'use-subscription': '1.10.0',
|
||||
'use-sync-external-store': '1.4.0',
|
||||
scheduler: '0.25.0',
|
||||
'use-subscription': '1.11.0',
|
||||
'use-sync-external-store': '1.5.0',
|
||||
scheduler: '0.26.0',
|
||||
};
|
||||
|
||||
// These packages do not exist in the @canary or @latest channel, only
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
function TestComponent(t0) {
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
export default function TestComponent(t0) {
|
||||
const $ = _c(2);
|
||||
const { x } = t0;
|
||||
let t1;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
function MyApp() {
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
export default function MyApp() {
|
||||
const $ = _c(1);
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { c as _c } from "react/compiler-runtime"; //
|
||||
@compilationMode(all)
|
||||
function nonReactFn() {
|
||||
const $ = _c(1);
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = {};
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// @compilationMode(infer)
|
||||
function nonReactFn() {
|
||||
return {};
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
function TestComponent(t0) {
|
||||
"use memo";
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
export default function TestComponent(t0) {
|
||||
const $ = _c(2);
|
||||
const { x } = t0;
|
||||
let t1;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
function TestComponent({ x }) {
|
||||
"use no memo";
|
||||
export default function TestComponent({ x }) {
|
||||
return <Button>{x}</Button>;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
function useFoo(propVal) {
|
||||
const $ = _c(2);
|
||||
const t0 = (propVal.baz: number);
|
||||
let t1;
|
||||
if ($[0] !== t0) {
|
||||
t1 = <div>{t0}</div>;
|
||||
$[0] = t0;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
function Foo() {
|
||||
const $ = _c(2);
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = foo();
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
const x = t0 as number;
|
||||
let t1;
|
||||
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t1 = <div>{x}</div>;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
"use no memo";
|
||||
function TestComponent({ x }) {
|
||||
"use memo";
|
||||
return <Button>{x}</Button>;
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
function TestComponent(t0) {
|
||||
"use memo";
|
||||
const $ = _c(2);
|
||||
@@ -12,7 +13,7 @@ function TestComponent(t0) {
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
function anonymous_1(t0) {
|
||||
const TestComponent2 = (t0) => {
|
||||
"use memo";
|
||||
const $ = _c(2);
|
||||
const { x } = t0;
|
||||
@@ -25,4 +26,4 @@ function anonymous_1(t0) {
|
||||
t1 = $[1];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
function anonymous_1() {
|
||||
const TestComponent = function () {
|
||||
"use no memo";
|
||||
return <Button>{x}</Button>;
|
||||
}
|
||||
function anonymous_3({ x }) {
|
||||
};
|
||||
const TestComponent2 = ({ x }) => {
|
||||
"use no memo";
|
||||
return <Button>{x}</Button>;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -9,11 +9,11 @@ import {expect, test} from '@playwright/test';
|
||||
import {encodeStore, type Store} from '../../lib/stores';
|
||||
import {format} from 'prettier';
|
||||
|
||||
function print(data: Array<string>): Promise<string> {
|
||||
function formatPrint(data: Array<string>): Promise<string> {
|
||||
return format(data.join(''), {parser: 'babel'});
|
||||
}
|
||||
|
||||
const DIRECTIVE_TEST_CASES = [
|
||||
const TEST_CASE_INPUTS = [
|
||||
{
|
||||
name: 'module-scope-use-memo',
|
||||
input: `
|
||||
@@ -55,7 +55,7 @@ const TestComponent2 = ({ x }) => {
|
||||
};`,
|
||||
},
|
||||
{
|
||||
name: 'function-scope-beats-module-scope',
|
||||
name: 'todo-function-scope-does-not-beat-module-scope',
|
||||
input: `
|
||||
'use no memo';
|
||||
function TestComponent({ x }) {
|
||||
@@ -63,6 +63,44 @@ function TestComponent({ x }) {
|
||||
return <Button>{x}</Button>;
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: 'parse-typescript',
|
||||
input: `
|
||||
function Foo() {
|
||||
const x = foo() as number;
|
||||
return <div>{x}</div>;
|
||||
}
|
||||
`,
|
||||
noFormat: true,
|
||||
},
|
||||
{
|
||||
name: 'parse-flow',
|
||||
input: `
|
||||
// @flow
|
||||
function useFoo(propVal: {+baz: number}) {
|
||||
return <div>{(propVal.baz as number)}</div>;
|
||||
}
|
||||
`,
|
||||
noFormat: true,
|
||||
},
|
||||
{
|
||||
name: 'compilationMode-infer',
|
||||
input: `// @compilationMode(infer)
|
||||
function nonReactFn() {
|
||||
return {};
|
||||
}
|
||||
`,
|
||||
noFormat: true,
|
||||
},
|
||||
{
|
||||
name: 'compilationMode-all',
|
||||
input: `// @compilationMode(all)
|
||||
function nonReactFn() {
|
||||
return {};
|
||||
}
|
||||
`,
|
||||
noFormat: true,
|
||||
},
|
||||
];
|
||||
|
||||
test('editor should open successfully', async ({page}) => {
|
||||
@@ -90,7 +128,7 @@ test('editor should compile from hash successfully', async ({page}) => {
|
||||
});
|
||||
const text =
|
||||
(await page.locator('.monaco-editor').nth(1).allInnerTexts()) ?? [];
|
||||
const output = await print(text);
|
||||
const output = await formatPrint(text);
|
||||
|
||||
expect(output).not.toEqual('');
|
||||
expect(output).toMatchSnapshot('01-user-output.txt');
|
||||
@@ -115,14 +153,14 @@ test('reset button works', async ({page}) => {
|
||||
});
|
||||
const text =
|
||||
(await page.locator('.monaco-editor').nth(1).allInnerTexts()) ?? [];
|
||||
const output = await print(text);
|
||||
const output = await formatPrint(text);
|
||||
|
||||
expect(output).not.toEqual('');
|
||||
expect(output).toMatchSnapshot('02-default-output.txt');
|
||||
});
|
||||
|
||||
DIRECTIVE_TEST_CASES.forEach((t, idx) =>
|
||||
test(`directives work: ${t.name}`, async ({page}) => {
|
||||
TEST_CASE_INPUTS.forEach((t, idx) =>
|
||||
test(`playground compiles: ${t.name}`, async ({page}) => {
|
||||
const store: Store = {
|
||||
source: t.input,
|
||||
};
|
||||
@@ -135,7 +173,12 @@ DIRECTIVE_TEST_CASES.forEach((t, idx) =>
|
||||
|
||||
const text =
|
||||
(await page.locator('.monaco-editor').nth(1).allInnerTexts()) ?? [];
|
||||
const output = await print(text);
|
||||
let output: string;
|
||||
if (t.noFormat) {
|
||||
output = text.join('');
|
||||
} else {
|
||||
output = await formatPrint(text);
|
||||
}
|
||||
|
||||
expect(output).not.toEqual('');
|
||||
expect(output).toMatchSnapshot(`${t.name}-output.txt`);
|
||||
|
||||
@@ -5,23 +5,21 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {parse as babelParse} from '@babel/parser';
|
||||
import {parse as babelParse, ParseResult} from '@babel/parser';
|
||||
import * as HermesParser from 'hermes-parser';
|
||||
import traverse, {NodePath} from '@babel/traverse';
|
||||
import * as t from '@babel/types';
|
||||
import {
|
||||
import BabelPluginReactCompiler, {
|
||||
CompilerError,
|
||||
CompilerErrorDetail,
|
||||
Effect,
|
||||
ErrorSeverity,
|
||||
parseConfigPragmaForTests,
|
||||
ValueKind,
|
||||
runPlayground,
|
||||
type Hook,
|
||||
findDirectiveDisablingMemoization,
|
||||
findDirectiveEnablingMemoization,
|
||||
PluginOptions,
|
||||
CompilerPipelineValue,
|
||||
parsePluginOptions,
|
||||
} from 'babel-plugin-react-compiler/src';
|
||||
import {type ReactFunctionType} from 'babel-plugin-react-compiler/src/HIR/Environment';
|
||||
import clsx from 'clsx';
|
||||
import invariant from 'invariant';
|
||||
import {useSnackbar} from 'notistack';
|
||||
@@ -39,32 +37,18 @@ import {useStore, useStoreDispatch} from '../StoreContext';
|
||||
import Input from './Input';
|
||||
import {
|
||||
CompilerOutput,
|
||||
CompilerTransformOutput,
|
||||
default as Output,
|
||||
PrintedCompilerPipelineValue,
|
||||
} from './Output';
|
||||
import {printFunctionWithOutlined} from 'babel-plugin-react-compiler/src/HIR/PrintHIR';
|
||||
import {printReactiveFunctionWithOutlined} from 'babel-plugin-react-compiler/src/ReactiveScopes/PrintReactiveFunction';
|
||||
import {transformFromAstSync} from '@babel/core';
|
||||
|
||||
type FunctionLike =
|
||||
| NodePath<t.FunctionDeclaration>
|
||||
| NodePath<t.ArrowFunctionExpression>
|
||||
| NodePath<t.FunctionExpression>;
|
||||
enum MemoizeDirectiveState {
|
||||
Enabled = 'Enabled',
|
||||
Disabled = 'Disabled',
|
||||
Undefined = 'Undefined',
|
||||
}
|
||||
|
||||
const MEMOIZE_ENABLED_OR_UNDEFINED_STATES = new Set([
|
||||
MemoizeDirectiveState.Enabled,
|
||||
MemoizeDirectiveState.Undefined,
|
||||
]);
|
||||
|
||||
const MEMOIZE_ENABLED_OR_DISABLED_STATES = new Set([
|
||||
MemoizeDirectiveState.Enabled,
|
||||
MemoizeDirectiveState.Disabled,
|
||||
]);
|
||||
function parseInput(input: string, language: 'flow' | 'typescript'): any {
|
||||
function parseInput(
|
||||
input: string,
|
||||
language: 'flow' | 'typescript',
|
||||
): ParseResult<t.File> {
|
||||
// Extract the first line to quickly check for custom test directives
|
||||
if (language === 'flow') {
|
||||
return HermesParser.parse(input, {
|
||||
@@ -77,95 +61,35 @@ function parseInput(input: string, language: 'flow' | 'typescript'): any {
|
||||
return babelParse(input, {
|
||||
plugins: ['typescript', 'jsx'],
|
||||
sourceType: 'module',
|
||||
});
|
||||
}) as ParseResult<t.File>;
|
||||
}
|
||||
}
|
||||
|
||||
function parseFunctions(
|
||||
function invokeCompiler(
|
||||
source: string,
|
||||
language: 'flow' | 'typescript',
|
||||
): Array<{
|
||||
compilationEnabled: boolean;
|
||||
fn: FunctionLike;
|
||||
}> {
|
||||
const items: Array<{
|
||||
compilationEnabled: boolean;
|
||||
fn: FunctionLike;
|
||||
}> = [];
|
||||
try {
|
||||
const ast = parseInput(source, language);
|
||||
traverse(ast, {
|
||||
FunctionDeclaration(nodePath) {
|
||||
items.push({
|
||||
compilationEnabled: shouldCompile(nodePath),
|
||||
fn: nodePath,
|
||||
});
|
||||
nodePath.skip();
|
||||
},
|
||||
ArrowFunctionExpression(nodePath) {
|
||||
items.push({
|
||||
compilationEnabled: shouldCompile(nodePath),
|
||||
fn: nodePath,
|
||||
});
|
||||
nodePath.skip();
|
||||
},
|
||||
FunctionExpression(nodePath) {
|
||||
items.push({
|
||||
compilationEnabled: shouldCompile(nodePath),
|
||||
fn: nodePath,
|
||||
});
|
||||
nodePath.skip();
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
CompilerError.throwInvalidJS({
|
||||
reason: String(e),
|
||||
description: null,
|
||||
loc: null,
|
||||
suggestions: null,
|
||||
});
|
||||
options: PluginOptions,
|
||||
): CompilerTransformOutput {
|
||||
const ast = parseInput(source, language);
|
||||
let result = transformFromAstSync(ast, source, {
|
||||
filename: '_playgroundFile.js',
|
||||
highlightCode: false,
|
||||
retainLines: true,
|
||||
plugins: [[BabelPluginReactCompiler, options]],
|
||||
ast: true,
|
||||
sourceType: 'module',
|
||||
configFile: false,
|
||||
sourceMaps: true,
|
||||
babelrc: false,
|
||||
});
|
||||
if (result?.ast == null || result?.code == null || result?.map == null) {
|
||||
throw new Error('Expected successful compilation');
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
function shouldCompile(fn: FunctionLike): boolean {
|
||||
const {body} = fn.node;
|
||||
if (t.isBlockStatement(body)) {
|
||||
const selfCheck = checkExplicitMemoizeDirectives(body.directives);
|
||||
if (selfCheck === MemoizeDirectiveState.Enabled) return true;
|
||||
if (selfCheck === MemoizeDirectiveState.Disabled) return false;
|
||||
|
||||
const parentWithDirective = fn.findParent(parentPath => {
|
||||
if (parentPath.isBlockStatement() || parentPath.isProgram()) {
|
||||
const directiveCheck = checkExplicitMemoizeDirectives(
|
||||
parentPath.node.directives,
|
||||
);
|
||||
return MEMOIZE_ENABLED_OR_DISABLED_STATES.has(directiveCheck);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if (!parentWithDirective) return true;
|
||||
const parentDirectiveCheck = checkExplicitMemoizeDirectives(
|
||||
(parentWithDirective.node as t.Program | t.BlockStatement).directives,
|
||||
);
|
||||
return MEMOIZE_ENABLED_OR_UNDEFINED_STATES.has(parentDirectiveCheck);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function checkExplicitMemoizeDirectives(
|
||||
directives: Array<t.Directive>,
|
||||
): MemoizeDirectiveState {
|
||||
if (findDirectiveEnablingMemoization(directives).length) {
|
||||
return MemoizeDirectiveState.Enabled;
|
||||
}
|
||||
if (findDirectiveDisablingMemoization(directives).length) {
|
||||
return MemoizeDirectiveState.Disabled;
|
||||
}
|
||||
return MemoizeDirectiveState.Undefined;
|
||||
return {
|
||||
code: result.code,
|
||||
sourceMaps: result.map,
|
||||
language,
|
||||
};
|
||||
}
|
||||
|
||||
const COMMON_HOOKS: Array<[string, Hook]> = [
|
||||
@@ -216,37 +140,6 @@ const COMMON_HOOKS: Array<[string, Hook]> = [
|
||||
],
|
||||
];
|
||||
|
||||
function isHookName(s: string): boolean {
|
||||
return /^use[A-Z0-9]/.test(s);
|
||||
}
|
||||
|
||||
function getReactFunctionType(id: t.Identifier | null): ReactFunctionType {
|
||||
if (id != null) {
|
||||
if (isHookName(id.name)) {
|
||||
return 'Hook';
|
||||
}
|
||||
|
||||
const isPascalCaseNameSpace = /^[A-Z].*/;
|
||||
if (isPascalCaseNameSpace.test(id.name)) {
|
||||
return 'Component';
|
||||
}
|
||||
}
|
||||
return 'Other';
|
||||
}
|
||||
|
||||
function getFunctionIdentifier(
|
||||
fn:
|
||||
| NodePath<t.FunctionDeclaration>
|
||||
| NodePath<t.ArrowFunctionExpression>
|
||||
| NodePath<t.FunctionExpression>,
|
||||
): t.Identifier | null {
|
||||
if (fn.isArrowFunctionExpression()) {
|
||||
return null;
|
||||
}
|
||||
const id = fn.get('id');
|
||||
return Array.isArray(id) === false && id.isIdentifier() ? id.node : null;
|
||||
}
|
||||
|
||||
function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] {
|
||||
const results = new Map<string, Array<PrintedCompilerPipelineValue>>();
|
||||
const error = new CompilerError();
|
||||
@@ -264,101 +157,63 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] {
|
||||
} else {
|
||||
language = 'typescript';
|
||||
}
|
||||
let count = 0;
|
||||
const withIdentifier = (id: t.Identifier | null): t.Identifier => {
|
||||
if (id != null && id.name != null) {
|
||||
return id;
|
||||
} else {
|
||||
return t.identifier(`anonymous_${count++}`);
|
||||
}
|
||||
};
|
||||
let transformOutput;
|
||||
try {
|
||||
// Extract the first line to quickly check for custom test directives
|
||||
const pragma = source.substring(0, source.indexOf('\n'));
|
||||
const config = parseConfigPragmaForTests(pragma);
|
||||
const parsedFunctions = parseFunctions(source, language);
|
||||
for (const func of parsedFunctions) {
|
||||
const id = withIdentifier(getFunctionIdentifier(func.fn));
|
||||
const fnName = id.name;
|
||||
if (!func.compilationEnabled) {
|
||||
upsert({
|
||||
kind: 'ast',
|
||||
fnName,
|
||||
name: 'CodeGen',
|
||||
value: {
|
||||
type: 'FunctionDeclaration',
|
||||
id:
|
||||
func.fn.isArrowFunctionExpression() ||
|
||||
func.fn.isFunctionExpression()
|
||||
? withIdentifier(null)
|
||||
: func.fn.node.id,
|
||||
async: func.fn.node.async,
|
||||
generator: !!func.fn.node.generator,
|
||||
body: func.fn.node.body as t.BlockStatement,
|
||||
params: func.fn.node.params,
|
||||
},
|
||||
});
|
||||
continue;
|
||||
}
|
||||
for (const result of runPlayground(
|
||||
func.fn,
|
||||
{
|
||||
...config,
|
||||
customHooks: new Map([...COMMON_HOOKS]),
|
||||
},
|
||||
getReactFunctionType(id),
|
||||
)) {
|
||||
switch (result.kind) {
|
||||
case 'ast': {
|
||||
upsert({
|
||||
kind: 'ast',
|
||||
fnName,
|
||||
name: result.name,
|
||||
value: {
|
||||
type: 'FunctionDeclaration',
|
||||
id: withIdentifier(result.value.id),
|
||||
async: result.value.async,
|
||||
generator: result.value.generator,
|
||||
body: result.value.body,
|
||||
params: result.value.params,
|
||||
},
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'hir': {
|
||||
upsert({
|
||||
kind: 'hir',
|
||||
fnName,
|
||||
name: result.name,
|
||||
value: printFunctionWithOutlined(result.value),
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'reactive': {
|
||||
upsert({
|
||||
kind: 'reactive',
|
||||
fnName,
|
||||
name: result.name,
|
||||
value: printReactiveFunctionWithOutlined(result.value),
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'debug': {
|
||||
upsert({
|
||||
kind: 'debug',
|
||||
fnName,
|
||||
name: result.name,
|
||||
value: result.value,
|
||||
});
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
const _: never = result;
|
||||
throw new Error(`Unhandled result ${result}`);
|
||||
}
|
||||
const logIR = (result: CompilerPipelineValue): void => {
|
||||
switch (result.kind) {
|
||||
case 'ast': {
|
||||
break;
|
||||
}
|
||||
case 'hir': {
|
||||
upsert({
|
||||
kind: 'hir',
|
||||
fnName: result.value.id,
|
||||
name: result.name,
|
||||
value: printFunctionWithOutlined(result.value),
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'reactive': {
|
||||
upsert({
|
||||
kind: 'reactive',
|
||||
fnName: result.value.id,
|
||||
name: result.name,
|
||||
value: printReactiveFunctionWithOutlined(result.value),
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'debug': {
|
||||
upsert({
|
||||
kind: 'debug',
|
||||
fnName: null,
|
||||
name: result.name,
|
||||
value: result.value,
|
||||
});
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
const _: never = result;
|
||||
throw new Error(`Unhandled result ${result}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
const parsedOptions = parseConfigPragmaForTests(pragma, {
|
||||
compilationMode: 'infer',
|
||||
});
|
||||
const opts: PluginOptions = parsePluginOptions({
|
||||
...parsedOptions,
|
||||
environment: {
|
||||
...parsedOptions.environment,
|
||||
customHooks: new Map([...COMMON_HOOKS]),
|
||||
},
|
||||
logger: {
|
||||
debugLogIRs: logIR,
|
||||
logEvent: () => {},
|
||||
},
|
||||
});
|
||||
transformOutput = invokeCompiler(source, language, opts);
|
||||
} catch (err) {
|
||||
/**
|
||||
* error might be an invariant violation or other runtime error
|
||||
@@ -385,7 +240,7 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] {
|
||||
if (error.hasErrors()) {
|
||||
return [{kind: 'err', results, error: error}, language];
|
||||
}
|
||||
return [{kind: 'ok', results}, language];
|
||||
return [{kind: 'ok', results, transformOutput}, language];
|
||||
}
|
||||
|
||||
export default function Editor(): JSX.Element {
|
||||
@@ -405,7 +260,7 @@ export default function Editor(): JSX.Element {
|
||||
} catch (e) {
|
||||
invariant(e instanceof Error, 'Only Error may be caught.');
|
||||
enqueueSnackbar(e.message, {
|
||||
variant: 'message',
|
||||
variant: 'warning',
|
||||
...createMessage(
|
||||
'Bad URL - fell back to the default Playground.',
|
||||
MessageLevel.Info,
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import generate from '@babel/generator';
|
||||
import * as t from '@babel/types';
|
||||
import {
|
||||
CodeIcon,
|
||||
DocumentAddIcon,
|
||||
@@ -21,17 +19,12 @@ import {memo, ReactNode, useEffect, useState} from 'react';
|
||||
import {type Store} from '../../lib/stores';
|
||||
import TabbedWindow from '../TabbedWindow';
|
||||
import {monacoOptions} from './monacoOptions';
|
||||
import {BabelFileResult} from '@babel/core';
|
||||
const MemoizedOutput = memo(Output);
|
||||
|
||||
export default MemoizedOutput;
|
||||
|
||||
export type PrintedCompilerPipelineValue =
|
||||
| {
|
||||
kind: 'ast';
|
||||
name: string;
|
||||
fnName: string | null;
|
||||
value: t.FunctionDeclaration;
|
||||
}
|
||||
| {
|
||||
kind: 'hir';
|
||||
name: string;
|
||||
@@ -41,8 +34,17 @@ export type PrintedCompilerPipelineValue =
|
||||
| {kind: 'reactive'; name: string; fnName: string | null; value: string}
|
||||
| {kind: 'debug'; name: string; fnName: string | null; value: string};
|
||||
|
||||
export type CompilerTransformOutput = {
|
||||
code: string;
|
||||
sourceMaps: BabelFileResult['map'];
|
||||
language: 'flow' | 'typescript';
|
||||
};
|
||||
export type CompilerOutput =
|
||||
| {kind: 'ok'; results: Map<string, Array<PrintedCompilerPipelineValue>>}
|
||||
| {
|
||||
kind: 'ok';
|
||||
transformOutput: CompilerTransformOutput;
|
||||
results: Map<string, Array<PrintedCompilerPipelineValue>>;
|
||||
}
|
||||
| {
|
||||
kind: 'err';
|
||||
results: Map<string, Array<PrintedCompilerPipelineValue>>;
|
||||
@@ -61,7 +63,6 @@ async function tabify(
|
||||
const tabs = new Map<string, React.ReactNode>();
|
||||
const reorderedTabs = new Map<string, React.ReactNode>();
|
||||
const concattedResults = new Map<string, string>();
|
||||
let topLevelFnDecls: Array<t.FunctionDeclaration> = [];
|
||||
// Concat all top level function declaration results into a single tab for each pass
|
||||
for (const [passName, results] of compilerOutput.results) {
|
||||
for (const result of results) {
|
||||
@@ -87,9 +88,6 @@ async function tabify(
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'ast':
|
||||
topLevelFnDecls.push(result.value);
|
||||
break;
|
||||
case 'debug': {
|
||||
concattedResults.set(passName, result.value);
|
||||
break;
|
||||
@@ -114,13 +112,17 @@ async function tabify(
|
||||
lastPassOutput = text;
|
||||
}
|
||||
// Ensure that JS and the JS source map come first
|
||||
if (topLevelFnDecls.length > 0) {
|
||||
/**
|
||||
* Make a synthetic Program so we can have a single AST with all the top level
|
||||
* FunctionDeclarations
|
||||
*/
|
||||
const ast = t.program(topLevelFnDecls);
|
||||
const {code, sourceMapUrl} = await codegen(ast, source);
|
||||
if (compilerOutput.kind === 'ok') {
|
||||
const {transformOutput} = compilerOutput;
|
||||
const sourceMapUrl = getSourceMapUrl(
|
||||
transformOutput.code,
|
||||
JSON.stringify(transformOutput.sourceMaps),
|
||||
);
|
||||
const code = await prettier.format(transformOutput.code, {
|
||||
semi: true,
|
||||
parser: transformOutput.language === 'flow' ? 'babel-flow' : 'babel-ts',
|
||||
plugins: [parserBabel, prettierPluginEstree],
|
||||
});
|
||||
reorderedTabs.set(
|
||||
'JS',
|
||||
<TextTabContent
|
||||
@@ -147,27 +149,6 @@ async function tabify(
|
||||
return reorderedTabs;
|
||||
}
|
||||
|
||||
async function codegen(
|
||||
ast: t.Program,
|
||||
source: string,
|
||||
): Promise<{code: any; sourceMapUrl: string | null}> {
|
||||
const generated = generate(
|
||||
ast,
|
||||
{sourceMaps: true, sourceFileName: 'input.js'},
|
||||
source,
|
||||
);
|
||||
const sourceMapUrl = getSourceMapUrl(
|
||||
generated.code,
|
||||
JSON.stringify(generated.map),
|
||||
);
|
||||
const codegenOutput = await prettier.format(generated.code, {
|
||||
semi: true,
|
||||
parser: 'babel',
|
||||
plugins: [parserBabel, prettierPluginEstree],
|
||||
});
|
||||
return {code: codegenOutput, sourceMapUrl};
|
||||
}
|
||||
|
||||
function utf16ToUTF8(s: string): string {
|
||||
return unescape(encodeURIComponent(s));
|
||||
}
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "cd ../.. && concurrently --kill-others -n compiler,runtime,playground \"yarn workspace babel-plugin-react-compiler run build --watch\" \"yarn workspace react-compiler-runtime run build --watch\" \"wait-on packages/babel-plugin-react-compiler/dist/index.js && cd apps/playground && NODE_ENV=development next dev\"",
|
||||
"dev": "cd ../.. && concurrently --kill-others -n compiler,runtime,playground \"yarn workspace babel-plugin-react-compiler run watch\" \"yarn workspace react-compiler-runtime run watch\" \"wait-on packages/babel-plugin-react-compiler/dist/index.js && cd apps/playground && NODE_ENV=development next dev\"",
|
||||
"build:compiler": "cd ../.. && concurrently -n compiler,runtime \"yarn workspace babel-plugin-react-compiler run build\" \"yarn workspace react-compiler-runtime run build\"",
|
||||
"build": "yarn build:compiler && next build",
|
||||
"postbuild": "node ./scripts/downloadFonts.js",
|
||||
"preinstall": "cd ../.. && yarn install --frozen-lockfile",
|
||||
"postinstall": "./scripts/link-compiler.sh",
|
||||
"vercel-build": "yarn build",
|
||||
"start": "next start",
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"start": "yarn workspace playground run start",
|
||||
"next": "yarn workspace playground run dev",
|
||||
"build": "yarn workspaces run build",
|
||||
"dev": "echo 'DEPRECATED: use `cd apps/playground && yarn dev` instead!' && sleep 5 && cd apps/playground && yarn dev",
|
||||
"dev": "cd apps/playground && yarn dev",
|
||||
"test": "yarn workspaces run test",
|
||||
"snap": "yarn workspace babel-plugin-react-compiler run snap",
|
||||
"snap:build": "yarn workspace snap run build",
|
||||
@@ -26,25 +26,23 @@
|
||||
"react-is": "0.0.0-experimental-4beb1fd8-20241118"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^25.0.7",
|
||||
"@rollup/plugin-json": "^6.1.0",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@rollup/plugin-terser": "^0.4.4",
|
||||
"@rollup/plugin-typescript": "^11.1.6",
|
||||
"@tsconfig/strictest": "^2.0.5",
|
||||
"concurrently": "^7.4.0",
|
||||
"esbuild": "^0.24.2",
|
||||
"folder-hash": "^4.0.4",
|
||||
"npm-dts": "^1.3.13",
|
||||
"object-assign": "^4.1.1",
|
||||
"ora": "5.4.1",
|
||||
"prettier": "^3.3.3",
|
||||
"prettier-plugin-hermes-parser": "^0.25.1",
|
||||
"prettier-plugin-hermes-parser": "^0.26.0",
|
||||
"prompt-promise": "^1.0.3",
|
||||
"rollup": "^4.22.4",
|
||||
"rollup-plugin-banner2": "^1.2.3",
|
||||
"rollup-plugin-prettier": "^4.1.1",
|
||||
"rimraf": "^5.0.10",
|
||||
"typescript": "^5.4.3",
|
||||
"wait-on": "^7.2.0",
|
||||
"yargs": "^17.7.2"
|
||||
},
|
||||
"resolutions": {
|
||||
"rimraf": "5.0.10"
|
||||
},
|
||||
"packageManager": "yarn@1.22.22"
|
||||
}
|
||||
|
||||
@@ -9,14 +9,15 @@
|
||||
"!*.tsbuildinfo"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "rimraf dist && rollup --config --bundleConfigAsCjs",
|
||||
"build": "rimraf dist && scripts/build.js",
|
||||
"test": "./scripts/link-react-compiler-runtime.sh && yarn snap:ci",
|
||||
"jest": "yarn build && ts-node node_modules/.bin/jest",
|
||||
"snap": "node ../snap/dist/main.js",
|
||||
"snap:build": "yarn workspace snap run build",
|
||||
"snap:ci": "yarn snap:build && yarn snap",
|
||||
"ts:analyze-trace": "scripts/ts-analyze-trace.sh",
|
||||
"lint": "yarn eslint src"
|
||||
"lint": "yarn eslint src",
|
||||
"watch": "scripts/build.js --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.19.0"
|
||||
@@ -42,16 +43,13 @@
|
||||
"babel-jest": "^29.0.3",
|
||||
"babel-plugin-fbt": "^1.0.0",
|
||||
"babel-plugin-fbt-runtime": "^1.0.0",
|
||||
"chalk": "4",
|
||||
"eslint": "^8.57.1",
|
||||
"glob": "^7.1.6",
|
||||
"invariant": "^2.2.4",
|
||||
"jest": "^29.0.3",
|
||||
"jest-environment-jsdom": "^29.0.3",
|
||||
"pretty-format": "^24",
|
||||
"react": "0.0.0-experimental-4beb1fd8-20241118",
|
||||
"react-dom": "0.0.0-experimental-4beb1fd8-20241118",
|
||||
"rimraf": "^3.0.2",
|
||||
"ts-jest": "^29.1.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"zod": "^3.22.4",
|
||||
|
||||
@@ -1,71 +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 typescript from '@rollup/plugin-typescript';
|
||||
import {nodeResolve} from '@rollup/plugin-node-resolve';
|
||||
import commonjs from '@rollup/plugin-commonjs';
|
||||
import json from '@rollup/plugin-json';
|
||||
import path from 'path';
|
||||
import process from 'process';
|
||||
import terser from '@rollup/plugin-terser';
|
||||
import prettier from 'rollup-plugin-prettier';
|
||||
import banner2 from 'rollup-plugin-banner2';
|
||||
|
||||
const NO_INLINE = new Set(['@babel/types']);
|
||||
|
||||
const DEV_ROLLUP_CONFIG = {
|
||||
input: 'src/index.ts',
|
||||
output: {
|
||||
file: 'dist/index.js',
|
||||
format: 'cjs',
|
||||
sourcemap: false,
|
||||
exports: 'named',
|
||||
},
|
||||
plugins: [
|
||||
typescript({
|
||||
tsconfig: './tsconfig.json',
|
||||
outputToFilesystem: true,
|
||||
compilerOptions: {
|
||||
noEmit: true,
|
||||
},
|
||||
}),
|
||||
json(),
|
||||
nodeResolve({
|
||||
preferBuiltins: true,
|
||||
resolveOnly: module => NO_INLINE.has(module) === false,
|
||||
rootDir: path.join(process.cwd(), '..'),
|
||||
}),
|
||||
commonjs(),
|
||||
terser({
|
||||
format: {
|
||||
comments: false,
|
||||
},
|
||||
compress: false,
|
||||
mangle: false,
|
||||
}),
|
||||
prettier(),
|
||||
banner2(
|
||||
() => `/**
|
||||
* 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.
|
||||
*
|
||||
* @lightSyntaxTransform
|
||||
* @noflow
|
||||
* @nolint
|
||||
* @preventMunge
|
||||
* @preserve-invariant-messages
|
||||
*/
|
||||
|
||||
"use no memo";
|
||||
`
|
||||
),
|
||||
],
|
||||
};
|
||||
|
||||
export default DEV_ROLLUP_CONFIG;
|
||||
61
compiler/packages/babel-plugin-react-compiler/scripts/build.js
Executable file
61
compiler/packages/babel-plugin-react-compiler/scripts/build.js
Executable file
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const esbuild = require('esbuild');
|
||||
const yargs = require('yargs');
|
||||
const path = require('path');
|
||||
|
||||
const argv = yargs(process.argv.slice(2))
|
||||
.options('w', {
|
||||
alias: 'watch',
|
||||
default: false,
|
||||
type: 'boolean',
|
||||
})
|
||||
.parse();
|
||||
|
||||
const config = {
|
||||
entryPoints: [path.join(__dirname, '../src/index.ts')],
|
||||
outfile: path.join(__dirname, '../dist/index.js'),
|
||||
bundle: true,
|
||||
external: ['@babel/types'],
|
||||
format: 'cjs',
|
||||
platform: 'node',
|
||||
banner: {
|
||||
js: `/**
|
||||
* 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.
|
||||
*
|
||||
* @lightSyntaxTransform
|
||||
* @noflow
|
||||
* @nolint
|
||||
* @preventMunge
|
||||
* @preserve-invariant-messages
|
||||
*/
|
||||
|
||||
"use no memo";`,
|
||||
},
|
||||
};
|
||||
|
||||
async function main() {
|
||||
if (argv.w) {
|
||||
const ctx = await esbuild.context(config);
|
||||
await ctx.watch();
|
||||
console.log('watching for changes...');
|
||||
} else {
|
||||
await esbuild.build({
|
||||
sourcemap: true,
|
||||
minify: false,
|
||||
...config,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -39,7 +39,10 @@ export default function BabelPluginReactCompiler(
|
||||
) {
|
||||
opts = injectReanimatedFlag(opts);
|
||||
}
|
||||
if (isDev) {
|
||||
if (
|
||||
opts.environment.enableResetCacheOnSourceFileChanges !== false &&
|
||||
isDev
|
||||
) {
|
||||
opts = {
|
||||
...opts,
|
||||
environment: {
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
} from '../HIR/Environment';
|
||||
import {hasOwnProperty} from '../Utils/utils';
|
||||
import {fromZodError} from 'zod-validation-error';
|
||||
import {CompilerPipelineValue} from './Pipeline';
|
||||
|
||||
const PanicThresholdOptionsSchema = z.enum([
|
||||
/*
|
||||
@@ -209,6 +210,7 @@ export type LoggerEvent =
|
||||
|
||||
export type Logger = {
|
||||
logEvent: (filename: string | null, event: LoggerEvent) => void;
|
||||
debugLogIRs?: (value: CompilerPipelineValue) => void;
|
||||
};
|
||||
|
||||
export const defaultOptions: PluginOptions = {
|
||||
|
||||
@@ -79,13 +79,6 @@ import {
|
||||
rewriteInstructionKindsBasedOnReassignment,
|
||||
} from '../SSA';
|
||||
import {inferTypes} from '../TypeInference';
|
||||
import {
|
||||
logCodegenFunction,
|
||||
logDebug,
|
||||
logHIRFunction,
|
||||
logReactiveFunction,
|
||||
} from '../Utils/logger';
|
||||
import {assertExhaustive} from '../Utils/utils';
|
||||
import {
|
||||
validateContextVariableLValues,
|
||||
validateHooksUsage,
|
||||
@@ -104,6 +97,8 @@ import {validateNoSetStateInPassiveEffects} from '../Validation/ValidateNoSetSta
|
||||
import {validateNoJSXInTryStatement} from '../Validation/ValidateNoJSXInTryStatement';
|
||||
import {propagateScopeDependenciesHIR} from '../HIR/PropagateScopeDependenciesHIR';
|
||||
import {outlineJSX} from '../Optimization/OutlineJsx';
|
||||
import {optimizePropsMethodCalls} from '../Optimization/OptimizePropsMethodCalls';
|
||||
import {transformFire} from '../Transform';
|
||||
|
||||
export type CompilerPipelineValue =
|
||||
| {kind: 'ast'; name: string; value: CodegenFunction}
|
||||
@@ -111,7 +106,7 @@ export type CompilerPipelineValue =
|
||||
| {kind: 'reactive'; name: string; value: ReactiveFunction}
|
||||
| {kind: 'debug'; name: string; value: string};
|
||||
|
||||
export function* run(
|
||||
function run(
|
||||
func: NodePath<
|
||||
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
|
||||
>,
|
||||
@@ -121,7 +116,7 @@ export function* run(
|
||||
logger: Logger | null,
|
||||
filename: string | null,
|
||||
code: string | null,
|
||||
): Generator<CompilerPipelineValue, CodegenFunction> {
|
||||
): CodegenFunction {
|
||||
const contextIdentifiers = findContextIdentifiers(func);
|
||||
const env = new Environment(
|
||||
func.scope,
|
||||
@@ -133,30 +128,32 @@ export function* run(
|
||||
code,
|
||||
useMemoCacheIdentifier,
|
||||
);
|
||||
yield log({
|
||||
env.logger?.debugLogIRs?.({
|
||||
kind: 'debug',
|
||||
name: 'EnvironmentConfig',
|
||||
value: prettyFormat(env.config),
|
||||
});
|
||||
const ast = yield* runWithEnvironment(func, env);
|
||||
return ast;
|
||||
return runWithEnvironment(func, env);
|
||||
}
|
||||
|
||||
/*
|
||||
* Note: this is split from run() to make `config` out of scope, so that all
|
||||
* access to feature flags has to be through the Environment for consistency.
|
||||
*/
|
||||
function* runWithEnvironment(
|
||||
function runWithEnvironment(
|
||||
func: NodePath<
|
||||
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
|
||||
>,
|
||||
env: Environment,
|
||||
): Generator<CompilerPipelineValue, CodegenFunction> {
|
||||
): CodegenFunction {
|
||||
const log = (value: CompilerPipelineValue): void => {
|
||||
env.logger?.debugLogIRs?.(value);
|
||||
};
|
||||
const hir = lower(func, env).unwrap();
|
||||
yield log({kind: 'hir', name: 'HIR', value: hir});
|
||||
log({kind: 'hir', name: 'HIR', value: hir});
|
||||
|
||||
pruneMaybeThrows(hir);
|
||||
yield log({kind: 'hir', name: 'PruneMaybeThrows', value: hir});
|
||||
log({kind: 'hir', name: 'PruneMaybeThrows', value: hir});
|
||||
|
||||
validateContextVariableLValues(hir);
|
||||
validateUseMemo(hir);
|
||||
@@ -167,40 +164,45 @@ function* runWithEnvironment(
|
||||
!env.config.enableChangeDetectionForDebugging
|
||||
) {
|
||||
dropManualMemoization(hir);
|
||||
yield log({kind: 'hir', name: 'DropManualMemoization', value: hir});
|
||||
log({kind: 'hir', name: 'DropManualMemoization', value: hir});
|
||||
}
|
||||
|
||||
inlineImmediatelyInvokedFunctionExpressions(hir);
|
||||
yield log({
|
||||
log({
|
||||
kind: 'hir',
|
||||
name: 'InlineImmediatelyInvokedFunctionExpressions',
|
||||
value: hir,
|
||||
});
|
||||
|
||||
mergeConsecutiveBlocks(hir);
|
||||
yield log({kind: 'hir', name: 'MergeConsecutiveBlocks', value: hir});
|
||||
log({kind: 'hir', name: 'MergeConsecutiveBlocks', value: hir});
|
||||
|
||||
assertConsistentIdentifiers(hir);
|
||||
assertTerminalSuccessorsExist(hir);
|
||||
|
||||
enterSSA(hir);
|
||||
yield log({kind: 'hir', name: 'SSA', value: hir});
|
||||
log({kind: 'hir', name: 'SSA', value: hir});
|
||||
|
||||
eliminateRedundantPhi(hir);
|
||||
yield log({kind: 'hir', name: 'EliminateRedundantPhi', value: hir});
|
||||
log({kind: 'hir', name: 'EliminateRedundantPhi', value: hir});
|
||||
|
||||
assertConsistentIdentifiers(hir);
|
||||
|
||||
constantPropagation(hir);
|
||||
yield log({kind: 'hir', name: 'ConstantPropagation', value: hir});
|
||||
log({kind: 'hir', name: 'ConstantPropagation', value: hir});
|
||||
|
||||
inferTypes(hir);
|
||||
yield log({kind: 'hir', name: 'InferTypes', value: hir});
|
||||
log({kind: 'hir', name: 'InferTypes', value: hir});
|
||||
|
||||
if (env.config.validateHooksUsage) {
|
||||
validateHooksUsage(hir);
|
||||
}
|
||||
|
||||
if (env.config.enableFire) {
|
||||
transformFire(hir);
|
||||
log({kind: 'hir', name: 'TransformFire', value: hir});
|
||||
}
|
||||
|
||||
if (env.config.validateNoCapitalizedCalls) {
|
||||
validateNoCapitalizedCalls(hir);
|
||||
}
|
||||
@@ -209,28 +211,31 @@ function* runWithEnvironment(
|
||||
lowerContextAccess(hir, env.config.lowerContextAccess);
|
||||
}
|
||||
|
||||
optimizePropsMethodCalls(hir);
|
||||
log({kind: 'hir', name: 'OptimizePropsMethodCalls', value: hir});
|
||||
|
||||
analyseFunctions(hir);
|
||||
yield log({kind: 'hir', name: 'AnalyseFunctions', value: hir});
|
||||
log({kind: 'hir', name: 'AnalyseFunctions', value: hir});
|
||||
|
||||
inferReferenceEffects(hir);
|
||||
yield log({kind: 'hir', name: 'InferReferenceEffects', value: hir});
|
||||
log({kind: 'hir', name: 'InferReferenceEffects', value: hir});
|
||||
|
||||
validateLocalsNotReassignedAfterRender(hir);
|
||||
|
||||
// Note: Has to come after infer reference effects because "dead" code may still affect inference
|
||||
deadCodeElimination(hir);
|
||||
yield log({kind: 'hir', name: 'DeadCodeElimination', value: hir});
|
||||
log({kind: 'hir', name: 'DeadCodeElimination', value: hir});
|
||||
|
||||
if (env.config.enableInstructionReordering) {
|
||||
instructionReordering(hir);
|
||||
yield log({kind: 'hir', name: 'InstructionReordering', value: hir});
|
||||
log({kind: 'hir', name: 'InstructionReordering', value: hir});
|
||||
}
|
||||
|
||||
pruneMaybeThrows(hir);
|
||||
yield log({kind: 'hir', name: 'PruneMaybeThrows', value: hir});
|
||||
log({kind: 'hir', name: 'PruneMaybeThrows', value: hir});
|
||||
|
||||
inferMutableRanges(hir);
|
||||
yield log({kind: 'hir', name: 'InferMutableRanges', value: hir});
|
||||
log({kind: 'hir', name: 'InferMutableRanges', value: hir});
|
||||
|
||||
if (env.config.assertValidMutableRanges) {
|
||||
assertValidMutableRanges(hir);
|
||||
@@ -253,27 +258,27 @@ function* runWithEnvironment(
|
||||
}
|
||||
|
||||
inferReactivePlaces(hir);
|
||||
yield log({kind: 'hir', name: 'InferReactivePlaces', value: hir});
|
||||
log({kind: 'hir', name: 'InferReactivePlaces', value: hir});
|
||||
|
||||
rewriteInstructionKindsBasedOnReassignment(hir);
|
||||
yield log({
|
||||
log({
|
||||
kind: 'hir',
|
||||
name: 'RewriteInstructionKindsBasedOnReassignment',
|
||||
value: hir,
|
||||
});
|
||||
|
||||
propagatePhiTypes(hir);
|
||||
yield log({
|
||||
log({
|
||||
kind: 'hir',
|
||||
name: 'PropagatePhiTypes',
|
||||
value: hir,
|
||||
});
|
||||
|
||||
inferReactiveScopeVariables(hir);
|
||||
yield log({kind: 'hir', name: 'InferReactiveScopeVariables', value: hir});
|
||||
log({kind: 'hir', name: 'InferReactiveScopeVariables', value: hir});
|
||||
|
||||
const fbtOperands = memoizeFbtAndMacroOperandsInSameScope(hir);
|
||||
yield log({
|
||||
log({
|
||||
kind: 'hir',
|
||||
name: 'MemoizeFbtAndMacroOperandsInSameScope',
|
||||
value: hir,
|
||||
@@ -285,39 +290,39 @@ function* runWithEnvironment(
|
||||
|
||||
if (env.config.enableFunctionOutlining) {
|
||||
outlineFunctions(hir, fbtOperands);
|
||||
yield log({kind: 'hir', name: 'OutlineFunctions', value: hir});
|
||||
log({kind: 'hir', name: 'OutlineFunctions', value: hir});
|
||||
}
|
||||
|
||||
alignMethodCallScopes(hir);
|
||||
yield log({
|
||||
log({
|
||||
kind: 'hir',
|
||||
name: 'AlignMethodCallScopes',
|
||||
value: hir,
|
||||
});
|
||||
|
||||
alignObjectMethodScopes(hir);
|
||||
yield log({
|
||||
log({
|
||||
kind: 'hir',
|
||||
name: 'AlignObjectMethodScopes',
|
||||
value: hir,
|
||||
});
|
||||
|
||||
pruneUnusedLabelsHIR(hir);
|
||||
yield log({
|
||||
log({
|
||||
kind: 'hir',
|
||||
name: 'PruneUnusedLabelsHIR',
|
||||
value: hir,
|
||||
});
|
||||
|
||||
alignReactiveScopesToBlockScopesHIR(hir);
|
||||
yield log({
|
||||
log({
|
||||
kind: 'hir',
|
||||
name: 'AlignReactiveScopesToBlockScopesHIR',
|
||||
value: hir,
|
||||
});
|
||||
|
||||
mergeOverlappingReactiveScopesHIR(hir);
|
||||
yield log({
|
||||
log({
|
||||
kind: 'hir',
|
||||
name: 'MergeOverlappingReactiveScopesHIR',
|
||||
value: hir,
|
||||
@@ -325,7 +330,7 @@ function* runWithEnvironment(
|
||||
assertValidBlockNesting(hir);
|
||||
|
||||
buildReactiveScopeTerminalsHIR(hir);
|
||||
yield log({
|
||||
log({
|
||||
kind: 'hir',
|
||||
name: 'BuildReactiveScopeTerminalsHIR',
|
||||
value: hir,
|
||||
@@ -334,14 +339,14 @@ function* runWithEnvironment(
|
||||
assertValidBlockNesting(hir);
|
||||
|
||||
flattenReactiveLoopsHIR(hir);
|
||||
yield log({
|
||||
log({
|
||||
kind: 'hir',
|
||||
name: 'FlattenReactiveLoopsHIR',
|
||||
value: hir,
|
||||
});
|
||||
|
||||
flattenScopesWithHooksOrUseHIR(hir);
|
||||
yield log({
|
||||
log({
|
||||
kind: 'hir',
|
||||
name: 'FlattenScopesWithHooksOrUseHIR',
|
||||
value: hir,
|
||||
@@ -349,7 +354,7 @@ function* runWithEnvironment(
|
||||
assertTerminalSuccessorsExist(hir);
|
||||
assertTerminalPredsExist(hir);
|
||||
propagateScopeDependenciesHIR(hir);
|
||||
yield log({
|
||||
log({
|
||||
kind: 'hir',
|
||||
name: 'PropagateScopeDependenciesHIR',
|
||||
value: hir,
|
||||
@@ -361,7 +366,7 @@ function* runWithEnvironment(
|
||||
|
||||
if (env.config.inlineJsxTransform) {
|
||||
inlineJsxTransform(hir, env.config.inlineJsxTransform);
|
||||
yield log({
|
||||
log({
|
||||
kind: 'hir',
|
||||
name: 'inlineJsxTransform',
|
||||
value: hir,
|
||||
@@ -369,7 +374,7 @@ function* runWithEnvironment(
|
||||
}
|
||||
|
||||
const reactiveFunction = buildReactiveFunction(hir);
|
||||
yield log({
|
||||
log({
|
||||
kind: 'reactive',
|
||||
name: 'BuildReactiveFunction',
|
||||
value: reactiveFunction,
|
||||
@@ -378,7 +383,7 @@ function* runWithEnvironment(
|
||||
assertWellFormedBreakTargets(reactiveFunction);
|
||||
|
||||
pruneUnusedLabels(reactiveFunction);
|
||||
yield log({
|
||||
log({
|
||||
kind: 'reactive',
|
||||
name: 'PruneUnusedLabels',
|
||||
value: reactiveFunction,
|
||||
@@ -386,35 +391,35 @@ function* runWithEnvironment(
|
||||
assertScopeInstructionsWithinScopes(reactiveFunction);
|
||||
|
||||
pruneNonEscapingScopes(reactiveFunction);
|
||||
yield log({
|
||||
log({
|
||||
kind: 'reactive',
|
||||
name: 'PruneNonEscapingScopes',
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
pruneNonReactiveDependencies(reactiveFunction);
|
||||
yield log({
|
||||
log({
|
||||
kind: 'reactive',
|
||||
name: 'PruneNonReactiveDependencies',
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
pruneUnusedScopes(reactiveFunction);
|
||||
yield log({
|
||||
log({
|
||||
kind: 'reactive',
|
||||
name: 'PruneUnusedScopes',
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
mergeReactiveScopesThatInvalidateTogether(reactiveFunction);
|
||||
yield log({
|
||||
log({
|
||||
kind: 'reactive',
|
||||
name: 'MergeReactiveScopesThatInvalidateTogether',
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
pruneAlwaysInvalidatingScopes(reactiveFunction);
|
||||
yield log({
|
||||
log({
|
||||
kind: 'reactive',
|
||||
name: 'PruneAlwaysInvalidatingScopes',
|
||||
value: reactiveFunction,
|
||||
@@ -422,7 +427,7 @@ function* runWithEnvironment(
|
||||
|
||||
if (env.config.enableChangeDetectionForDebugging != null) {
|
||||
pruneInitializationDependencies(reactiveFunction);
|
||||
yield log({
|
||||
log({
|
||||
kind: 'reactive',
|
||||
name: 'PruneInitializationDependencies',
|
||||
value: reactiveFunction,
|
||||
@@ -430,49 +435,49 @@ function* runWithEnvironment(
|
||||
}
|
||||
|
||||
propagateEarlyReturns(reactiveFunction);
|
||||
yield log({
|
||||
log({
|
||||
kind: 'reactive',
|
||||
name: 'PropagateEarlyReturns',
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
pruneUnusedLValues(reactiveFunction);
|
||||
yield log({
|
||||
log({
|
||||
kind: 'reactive',
|
||||
name: 'PruneUnusedLValues',
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
promoteUsedTemporaries(reactiveFunction);
|
||||
yield log({
|
||||
log({
|
||||
kind: 'reactive',
|
||||
name: 'PromoteUsedTemporaries',
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
extractScopeDeclarationsFromDestructuring(reactiveFunction);
|
||||
yield log({
|
||||
log({
|
||||
kind: 'reactive',
|
||||
name: 'ExtractScopeDeclarationsFromDestructuring',
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
stabilizeBlockIds(reactiveFunction);
|
||||
yield log({
|
||||
log({
|
||||
kind: 'reactive',
|
||||
name: 'StabilizeBlockIds',
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
const uniqueIdentifiers = renameVariables(reactiveFunction);
|
||||
yield log({
|
||||
log({
|
||||
kind: 'reactive',
|
||||
name: 'RenameVariables',
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
pruneHoistedContexts(reactiveFunction);
|
||||
yield log({
|
||||
log({
|
||||
kind: 'reactive',
|
||||
name: 'PruneHoistedContexts',
|
||||
value: reactiveFunction,
|
||||
@@ -493,9 +498,9 @@ function* runWithEnvironment(
|
||||
uniqueIdentifiers,
|
||||
fbtOperands,
|
||||
}).unwrap();
|
||||
yield log({kind: 'ast', name: 'Codegen', value: ast});
|
||||
log({kind: 'ast', name: 'Codegen', value: ast});
|
||||
for (const outlined of ast.outlined) {
|
||||
yield log({kind: 'ast', name: 'Codegen (outlined)', value: outlined.fn});
|
||||
log({kind: 'ast', name: 'Codegen (outlined)', value: outlined.fn});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -521,7 +526,7 @@ export function compileFn(
|
||||
filename: string | null,
|
||||
code: string | null,
|
||||
): CodegenFunction {
|
||||
let generator = run(
|
||||
return run(
|
||||
func,
|
||||
config,
|
||||
fnType,
|
||||
@@ -530,46 +535,4 @@ export function compileFn(
|
||||
filename,
|
||||
code,
|
||||
);
|
||||
while (true) {
|
||||
const next = generator.next();
|
||||
if (next.done) {
|
||||
return next.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function log(value: CompilerPipelineValue): CompilerPipelineValue {
|
||||
switch (value.kind) {
|
||||
case 'ast': {
|
||||
logCodegenFunction(value.name, value.value);
|
||||
break;
|
||||
}
|
||||
case 'hir': {
|
||||
logHIRFunction(value.name, value.value);
|
||||
break;
|
||||
}
|
||||
case 'reactive': {
|
||||
logReactiveFunction(value.name, value.value);
|
||||
break;
|
||||
}
|
||||
case 'debug': {
|
||||
logDebug(value.name, value.value);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
assertExhaustive(value, 'Unexpected compilation kind');
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export function* runPlayground(
|
||||
func: NodePath<
|
||||
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
|
||||
>,
|
||||
config: EnvironmentConfig,
|
||||
fnType: ReactFunctionType,
|
||||
): Generator<CompilerPipelineValue, CodegenFunction> {
|
||||
const ast = yield* run(func, config, fnType, '_c', null, null, null);
|
||||
return ast;
|
||||
}
|
||||
|
||||
@@ -564,6 +564,14 @@ export function compileProgram(
|
||||
if (environment.enableChangeDetectionForDebugging != null) {
|
||||
externalFunctions.push(environment.enableChangeDetectionForDebugging);
|
||||
}
|
||||
|
||||
const hasFireRewrite = compiledFns.some(c => c.compiledFn.hasFireRewrite);
|
||||
if (environment.enableFire && hasFireRewrite) {
|
||||
externalFunctions.push({
|
||||
source: getReactCompilerRuntimeModule(pass.opts),
|
||||
importSpecifierName: 'useFire',
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
handleError(err, pass, null);
|
||||
return;
|
||||
|
||||
@@ -1078,6 +1078,12 @@ function lowerStatement(
|
||||
const left = stmt.get('left');
|
||||
const leftLoc = left.node.loc ?? GeneratedSource;
|
||||
let test: Place;
|
||||
const advanceIterator = lowerValueToTemporary(builder, {
|
||||
kind: 'IteratorNext',
|
||||
loc: leftLoc,
|
||||
iterator: {...iterator},
|
||||
collection: {...value},
|
||||
});
|
||||
if (left.isVariableDeclaration()) {
|
||||
const declarations = left.get('declarations');
|
||||
CompilerError.invariant(declarations.length === 1, {
|
||||
@@ -1087,12 +1093,6 @@ function lowerStatement(
|
||||
suggestions: null,
|
||||
});
|
||||
const id = declarations[0].get('id');
|
||||
const advanceIterator = lowerValueToTemporary(builder, {
|
||||
kind: 'IteratorNext',
|
||||
loc: leftLoc,
|
||||
iterator: {...iterator},
|
||||
collection: {...value},
|
||||
});
|
||||
const assign = lowerAssignment(
|
||||
builder,
|
||||
leftLoc,
|
||||
@@ -1103,13 +1103,19 @@ function lowerStatement(
|
||||
);
|
||||
test = lowerValueToTemporary(builder, assign);
|
||||
} else {
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::lowerStatement) Handle ${left.type} inits in ForOfStatement`,
|
||||
severity: ErrorSeverity.Todo,
|
||||
loc: left.node.loc ?? null,
|
||||
suggestions: null,
|
||||
CompilerError.invariant(left.isLVal(), {
|
||||
loc: leftLoc,
|
||||
reason: 'Expected ForOf init to be a variable declaration or lval',
|
||||
});
|
||||
return;
|
||||
const assign = lowerAssignment(
|
||||
builder,
|
||||
leftLoc,
|
||||
InstructionKind.Reassign,
|
||||
left,
|
||||
advanceIterator,
|
||||
'Assignment',
|
||||
);
|
||||
test = lowerValueToTemporary(builder, assign);
|
||||
}
|
||||
builder.terminateWithContinuation(
|
||||
{
|
||||
@@ -1166,6 +1172,11 @@ function lowerStatement(
|
||||
const left = stmt.get('left');
|
||||
const leftLoc = left.node.loc ?? GeneratedSource;
|
||||
let test: Place;
|
||||
const nextPropertyTemp = lowerValueToTemporary(builder, {
|
||||
kind: 'NextPropertyOf',
|
||||
loc: leftLoc,
|
||||
value,
|
||||
});
|
||||
if (left.isVariableDeclaration()) {
|
||||
const declarations = left.get('declarations');
|
||||
CompilerError.invariant(declarations.length === 1, {
|
||||
@@ -1175,11 +1186,6 @@ function lowerStatement(
|
||||
suggestions: null,
|
||||
});
|
||||
const id = declarations[0].get('id');
|
||||
const nextPropertyTemp = lowerValueToTemporary(builder, {
|
||||
kind: 'NextPropertyOf',
|
||||
loc: leftLoc,
|
||||
value,
|
||||
});
|
||||
const assign = lowerAssignment(
|
||||
builder,
|
||||
leftLoc,
|
||||
@@ -1190,13 +1196,19 @@ function lowerStatement(
|
||||
);
|
||||
test = lowerValueToTemporary(builder, assign);
|
||||
} else {
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::lowerStatement) Handle ${left.type} inits in ForInStatement`,
|
||||
severity: ErrorSeverity.Todo,
|
||||
loc: left.node.loc ?? null,
|
||||
suggestions: null,
|
||||
CompilerError.invariant(left.isLVal(), {
|
||||
loc: leftLoc,
|
||||
reason: 'Expected ForIn init to be a variable declaration or lval',
|
||||
});
|
||||
return;
|
||||
const assign = lowerAssignment(
|
||||
builder,
|
||||
leftLoc,
|
||||
InstructionKind.Reassign,
|
||||
left,
|
||||
nextPropertyTemp,
|
||||
'Assignment',
|
||||
);
|
||||
test = lowerValueToTemporary(builder, assign);
|
||||
}
|
||||
builder.terminateWithContinuation(
|
||||
{
|
||||
|
||||
@@ -9,7 +9,13 @@ import * as t from '@babel/types';
|
||||
import {ZodError, z} from 'zod';
|
||||
import {fromZodError} from 'zod-validation-error';
|
||||
import {CompilerError} from '../CompilerError';
|
||||
import {Logger} from '../Entrypoint';
|
||||
import {
|
||||
CompilationMode,
|
||||
Logger,
|
||||
PanicThresholdOptions,
|
||||
parsePluginOptions,
|
||||
PluginOptions,
|
||||
} from '../Entrypoint';
|
||||
import {Err, Ok, Result} from '../Utils/Result';
|
||||
import {
|
||||
DEFAULT_GLOBALS,
|
||||
@@ -168,11 +174,19 @@ const EnvironmentConfigSchema = z.object({
|
||||
customMacros: z.nullable(z.array(MacroSchema)).default(null),
|
||||
|
||||
/**
|
||||
* Enable a check that resets the memoization cache when the source code of the file changes.
|
||||
* This is intended to support hot module reloading (HMR), where the same runtime component
|
||||
* instance will be reused across different versions of the component source.
|
||||
* Enable a check that resets the memoization cache when the source code of
|
||||
* the file changes. This is intended to support hot module reloading (HMR),
|
||||
* where the same runtime component instance will be reused across different
|
||||
* versions of the component source.
|
||||
*
|
||||
* When set to
|
||||
* - true: code for HMR support is always generated, regardless of NODE_ENV
|
||||
* or `globalThis.__DEV__`
|
||||
* - false: code for HMR support is not generated
|
||||
* - null: (default) code for HMR support is conditionally generated dependent
|
||||
* on `NODE_ENV` and `globalThis.__DEV__` at the time of compilation.
|
||||
*/
|
||||
enableResetCacheOnSourceFileChanges: z.boolean().default(false),
|
||||
enableResetCacheOnSourceFileChanges: z.nullable(z.boolean()).default(null),
|
||||
|
||||
/**
|
||||
* Enable using information from existing useMemo/useCallback to understand when a value is done
|
||||
@@ -241,6 +255,8 @@ const EnvironmentConfigSchema = z.object({
|
||||
*/
|
||||
enableOptionalDependencies: z.boolean().default(true),
|
||||
|
||||
enableFire: z.boolean().default(false),
|
||||
|
||||
/**
|
||||
* Enables inference and auto-insertion of effect dependencies. Takes in an array of
|
||||
* configurable module and import pairs to allow for user-land experimentation. For example,
|
||||
@@ -673,7 +689,9 @@ const testComplexConfigDefaults: PartialEnvironmentConfig = {
|
||||
/**
|
||||
* For snap test fixtures and playground only.
|
||||
*/
|
||||
export function parseConfigPragmaForTests(pragma: string): EnvironmentConfig {
|
||||
function parseConfigPragmaEnvironmentForTest(
|
||||
pragma: string,
|
||||
): EnvironmentConfig {
|
||||
const maybeConfig: any = {};
|
||||
// Get the defaults to programmatically check for boolean properties
|
||||
const defaultConfig = EnvironmentConfigSchema.parse({});
|
||||
@@ -708,7 +726,10 @@ export function parseConfigPragmaForTests(pragma: string): EnvironmentConfig {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (typeof defaultConfig[key as keyof EnvironmentConfig] !== 'boolean') {
|
||||
if (
|
||||
key !== 'enableResetCacheOnSourceFileChanges' &&
|
||||
typeof defaultConfig[key as keyof EnvironmentConfig] !== 'boolean'
|
||||
) {
|
||||
// skip parsing non-boolean properties
|
||||
continue;
|
||||
}
|
||||
@@ -718,9 +739,15 @@ export function parseConfigPragmaForTests(pragma: string): EnvironmentConfig {
|
||||
maybeConfig[key] = false;
|
||||
}
|
||||
}
|
||||
|
||||
const config = EnvironmentConfigSchema.safeParse(maybeConfig);
|
||||
if (config.success) {
|
||||
/**
|
||||
* Unless explicitly enabled, do not insert HMR handling code
|
||||
* in test fixtures or playground to reduce visual noise.
|
||||
*/
|
||||
if (config.data.enableResetCacheOnSourceFileChanges == null) {
|
||||
config.data.enableResetCacheOnSourceFileChanges = false;
|
||||
}
|
||||
return config.data;
|
||||
}
|
||||
CompilerError.invariant(false, {
|
||||
@@ -730,6 +757,48 @@ export function parseConfigPragmaForTests(pragma: string): EnvironmentConfig {
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
export function parseConfigPragmaForTests(
|
||||
pragma: string,
|
||||
defaults: {
|
||||
compilationMode: CompilationMode;
|
||||
},
|
||||
): PluginOptions {
|
||||
const environment = parseConfigPragmaEnvironmentForTest(pragma);
|
||||
let compilationMode: CompilationMode = defaults.compilationMode;
|
||||
let panicThreshold: PanicThresholdOptions = 'all_errors';
|
||||
for (const token of pragma.split(' ')) {
|
||||
if (!token.startsWith('@')) {
|
||||
continue;
|
||||
}
|
||||
switch (token) {
|
||||
case '@compilationMode(annotation)': {
|
||||
compilationMode = 'annotation';
|
||||
break;
|
||||
}
|
||||
case '@compilationMode(infer)': {
|
||||
compilationMode = 'infer';
|
||||
break;
|
||||
}
|
||||
case '@compilationMode(all)': {
|
||||
compilationMode = 'all';
|
||||
break;
|
||||
}
|
||||
case '@compilationMode(syntax)': {
|
||||
compilationMode = 'syntax';
|
||||
break;
|
||||
}
|
||||
case '@panicThreshold(none)': {
|
||||
panicThreshold = 'none';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return parsePluginOptions({
|
||||
environment,
|
||||
compilationMode,
|
||||
panicThreshold,
|
||||
});
|
||||
}
|
||||
|
||||
export type PartialEnvironmentConfig = Partial<EnvironmentConfig>;
|
||||
|
||||
@@ -768,6 +837,7 @@ export class Environment {
|
||||
fnType: ReactFunctionType;
|
||||
useMemoCacheIdentifier: string;
|
||||
hasLoweredContextAccess: boolean;
|
||||
hasFireRewrite: boolean;
|
||||
|
||||
#contextIdentifiers: Set<t.Identifier>;
|
||||
#hoistedIdentifiers: Set<t.Identifier>;
|
||||
@@ -792,6 +862,7 @@ export class Environment {
|
||||
this.#shapes = new Map(DEFAULT_SHAPES);
|
||||
this.#globals = new Map(DEFAULT_GLOBALS);
|
||||
this.hasLoweredContextAccess = false;
|
||||
this.hasFireRewrite = false;
|
||||
|
||||
if (
|
||||
config.disableMemoizationForDebugging &&
|
||||
|
||||
@@ -9,6 +9,7 @@ import {Effect, ValueKind, ValueReason} from './HIR';
|
||||
import {
|
||||
BUILTIN_SHAPES,
|
||||
BuiltInArrayId,
|
||||
BuiltInFireId,
|
||||
BuiltInMixedReadonlyId,
|
||||
BuiltInUseActionStateId,
|
||||
BuiltInUseContextHookId,
|
||||
@@ -87,6 +88,21 @@ const UNTYPED_GLOBALS: Set<string> = new Set([
|
||||
]);
|
||||
|
||||
const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
|
||||
[
|
||||
'Object',
|
||||
addObject(DEFAULT_SHAPES, 'Object', [
|
||||
[
|
||||
'keys',
|
||||
addFunction(DEFAULT_SHAPES, [], {
|
||||
positionalParams: [Effect.Read],
|
||||
restParam: null,
|
||||
returnType: {kind: 'Object', shapeId: BuiltInArrayId},
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Mutable,
|
||||
}),
|
||||
],
|
||||
]),
|
||||
],
|
||||
[
|
||||
'Array',
|
||||
addObject(DEFAULT_SHAPES, 'Array', [
|
||||
@@ -468,6 +484,21 @@ const REACT_APIS: Array<[string, BuiltInType]> = [
|
||||
BuiltInUseOperatorId,
|
||||
),
|
||||
],
|
||||
[
|
||||
'fire',
|
||||
addFunction(
|
||||
DEFAULT_SHAPES,
|
||||
[],
|
||||
{
|
||||
positionalParams: [],
|
||||
restParam: null,
|
||||
returnType: {kind: 'Primitive'},
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
},
|
||||
BuiltInFireId,
|
||||
),
|
||||
],
|
||||
];
|
||||
|
||||
TYPED_GLOBALS.push(
|
||||
|
||||
@@ -840,6 +840,11 @@ export type LoadLocal = {
|
||||
place: Place;
|
||||
loc: SourceLocation;
|
||||
};
|
||||
export type LoadContext = {
|
||||
kind: 'LoadContext';
|
||||
place: Place;
|
||||
loc: SourceLocation;
|
||||
};
|
||||
|
||||
/*
|
||||
* The value of a given instruction. Note that values are not recursive: complex
|
||||
@@ -852,11 +857,7 @@ export type LoadLocal = {
|
||||
|
||||
export type InstructionValue =
|
||||
| LoadLocal
|
||||
| {
|
||||
kind: 'LoadContext';
|
||||
place: Place;
|
||||
loc: SourceLocation;
|
||||
}
|
||||
| LoadContext
|
||||
| {
|
||||
kind: 'DeclareLocal';
|
||||
lvalue: LValue;
|
||||
@@ -1644,6 +1645,10 @@ export function isArrayType(id: Identifier): boolean {
|
||||
return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInArray';
|
||||
}
|
||||
|
||||
export function isPropsType(id: Identifier): boolean {
|
||||
return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInProps';
|
||||
}
|
||||
|
||||
export function isRefValueType(id: Identifier): boolean {
|
||||
return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInRefValue';
|
||||
}
|
||||
|
||||
@@ -213,6 +213,7 @@ export const BuiltInDispatchId = 'BuiltInDispatch';
|
||||
export const BuiltInUseContextHookId = 'BuiltInUseContextHook';
|
||||
export const BuiltInUseTransitionId = 'BuiltInUseTransition';
|
||||
export const BuiltInStartTransitionId = 'BuiltInStartTransition';
|
||||
export const BuiltInFireId = 'BuiltInFire';
|
||||
|
||||
// ShapeRegistry with default definitions for built-ins.
|
||||
export const BUILTIN_SHAPES: ShapeRegistry = new Map();
|
||||
|
||||
@@ -897,6 +897,14 @@ export function printSourceLocation(loc: SourceLocation): string {
|
||||
}
|
||||
}
|
||||
|
||||
export function printSourceLocationLine(loc: SourceLocation): string {
|
||||
if (typeof loc === 'symbol') {
|
||||
return 'generated';
|
||||
} else {
|
||||
return `${loc.start.line}:${loc.end.line}`;
|
||||
}
|
||||
}
|
||||
|
||||
export function printAliases(aliases: DisjointSet<Identifier>): string {
|
||||
const aliasSets = aliases.buildSets();
|
||||
|
||||
|
||||
@@ -17,6 +17,11 @@ import {
|
||||
areEqualPaths,
|
||||
IdentifierId,
|
||||
Terminal,
|
||||
InstructionValue,
|
||||
LoadContext,
|
||||
TInstruction,
|
||||
FunctionExpression,
|
||||
ObjectMethod,
|
||||
} from './HIR';
|
||||
import {
|
||||
collectHoistablePropertyLoads,
|
||||
@@ -223,11 +228,25 @@ export function collectTemporariesSidemap(
|
||||
fn,
|
||||
usedOutsideDeclaringScope,
|
||||
temporaries,
|
||||
false,
|
||||
null,
|
||||
);
|
||||
return temporaries;
|
||||
}
|
||||
|
||||
function isLoadContextMutable(
|
||||
instrValue: InstructionValue,
|
||||
id: InstructionId,
|
||||
): instrValue is LoadContext {
|
||||
if (instrValue.kind === 'LoadContext') {
|
||||
CompilerError.invariant(instrValue.place.identifier.scope != null, {
|
||||
reason:
|
||||
'[PropagateScopeDependencies] Expected all context variables to be assigned a scope',
|
||||
loc: instrValue.loc,
|
||||
});
|
||||
return id >= instrValue.place.identifier.scope.range.end;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* Recursive collect a sidemap of all `LoadLocal` and `PropertyLoads` with a
|
||||
* function and all nested functions.
|
||||
@@ -239,17 +258,21 @@ function collectTemporariesSidemapImpl(
|
||||
fn: HIRFunction,
|
||||
usedOutsideDeclaringScope: ReadonlySet<DeclarationId>,
|
||||
temporaries: Map<IdentifierId, ReactiveScopeDependency>,
|
||||
isInnerFn: boolean,
|
||||
innerFnContext: {instrId: InstructionId} | null,
|
||||
): void {
|
||||
for (const [_, block] of fn.body.blocks) {
|
||||
for (const instr of block.instructions) {
|
||||
const {value, lvalue} = instr;
|
||||
for (const {value, lvalue, id: origInstrId} of block.instructions) {
|
||||
const instrId =
|
||||
innerFnContext != null ? innerFnContext.instrId : origInstrId;
|
||||
const usedOutside = usedOutsideDeclaringScope.has(
|
||||
lvalue.identifier.declarationId,
|
||||
);
|
||||
|
||||
if (value.kind === 'PropertyLoad' && !usedOutside) {
|
||||
if (!isInnerFn || temporaries.has(value.object.identifier.id)) {
|
||||
if (
|
||||
innerFnContext == null ||
|
||||
temporaries.has(value.object.identifier.id)
|
||||
) {
|
||||
/**
|
||||
* All dependencies of a inner / nested function must have a base
|
||||
* identifier from the outermost component / hook. This is because the
|
||||
@@ -265,13 +288,13 @@ function collectTemporariesSidemapImpl(
|
||||
temporaries.set(lvalue.identifier.id, property);
|
||||
}
|
||||
} else if (
|
||||
value.kind === 'LoadLocal' &&
|
||||
(value.kind === 'LoadLocal' || isLoadContextMutable(value, instrId)) &&
|
||||
lvalue.identifier.name == null &&
|
||||
value.place.identifier.name !== null &&
|
||||
!usedOutside
|
||||
) {
|
||||
if (
|
||||
!isInnerFn ||
|
||||
innerFnContext == null ||
|
||||
fn.context.some(
|
||||
context => context.identifier.id === value.place.identifier.id,
|
||||
)
|
||||
@@ -289,7 +312,7 @@ function collectTemporariesSidemapImpl(
|
||||
value.loweredFunc.func,
|
||||
usedOutsideDeclaringScope,
|
||||
temporaries,
|
||||
true,
|
||||
innerFnContext ?? {instrId},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -358,19 +381,22 @@ class Context {
|
||||
|
||||
#temporaries: ReadonlyMap<IdentifierId, ReactiveScopeDependency>;
|
||||
#temporariesUsedOutsideScope: ReadonlySet<DeclarationId>;
|
||||
#processedInstrsInOptional: ReadonlySet<Instruction | Terminal>;
|
||||
|
||||
/**
|
||||
* Tracks the traversal state. See Context.declare for explanation of why this
|
||||
* is needed.
|
||||
*/
|
||||
inInnerFn: boolean = false;
|
||||
#innerFnContext: {outerInstrId: InstructionId} | null = null;
|
||||
|
||||
constructor(
|
||||
temporariesUsedOutsideScope: ReadonlySet<DeclarationId>,
|
||||
temporaries: ReadonlyMap<IdentifierId, ReactiveScopeDependency>,
|
||||
processedInstrsInOptional: ReadonlySet<Instruction | Terminal>,
|
||||
) {
|
||||
this.#temporariesUsedOutsideScope = temporariesUsedOutsideScope;
|
||||
this.#temporaries = temporaries;
|
||||
this.#processedInstrsInOptional = processedInstrsInOptional;
|
||||
}
|
||||
|
||||
enterScope(scope: ReactiveScope): void {
|
||||
@@ -431,7 +457,7 @@ class Context {
|
||||
* by root identifier mutable ranges).
|
||||
*/
|
||||
declare(identifier: Identifier, decl: Decl): void {
|
||||
if (this.inInnerFn) return;
|
||||
if (this.#innerFnContext != null) return;
|
||||
if (!this.#declarations.has(identifier.declarationId)) {
|
||||
this.#declarations.set(identifier.declarationId, decl);
|
||||
}
|
||||
@@ -574,22 +600,52 @@ class Context {
|
||||
currentScope.reassignments.add(place.identifier);
|
||||
}
|
||||
}
|
||||
enterInnerFn<T>(
|
||||
innerFn: TInstruction<FunctionExpression> | TInstruction<ObjectMethod>,
|
||||
cb: () => T,
|
||||
): T {
|
||||
const prevContext = this.#innerFnContext;
|
||||
this.#innerFnContext = this.#innerFnContext ?? {outerInstrId: innerFn.id};
|
||||
const result = cb();
|
||||
this.#innerFnContext = prevContext;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip dependencies that are subexpressions of other dependencies. e.g. if a
|
||||
* dependency is tracked in the temporaries sidemap, it can be added at
|
||||
* site-of-use
|
||||
*/
|
||||
isDeferredDependency(
|
||||
instr:
|
||||
| {kind: HIRValue.Instruction; value: Instruction}
|
||||
| {kind: HIRValue.Terminal; value: Terminal},
|
||||
): boolean {
|
||||
return (
|
||||
this.#processedInstrsInOptional.has(instr.value) ||
|
||||
(instr.kind === HIRValue.Instruction &&
|
||||
this.#temporaries.has(instr.value.lvalue.identifier.id))
|
||||
);
|
||||
}
|
||||
}
|
||||
enum HIRValue {
|
||||
Instruction = 1,
|
||||
Terminal,
|
||||
}
|
||||
|
||||
function handleInstruction(instr: Instruction, context: Context): void {
|
||||
const {id, value, lvalue} = instr;
|
||||
if (value.kind === 'LoadLocal') {
|
||||
if (
|
||||
value.place.identifier.name === null ||
|
||||
lvalue.identifier.name !== null ||
|
||||
context.isUsedOutsideDeclaringScope(lvalue)
|
||||
) {
|
||||
context.visitOperand(value.place);
|
||||
}
|
||||
} else if (value.kind === 'PropertyLoad') {
|
||||
if (context.isUsedOutsideDeclaringScope(lvalue)) {
|
||||
context.visitProperty(value.object, value.property, false);
|
||||
}
|
||||
context.declare(lvalue.identifier, {
|
||||
id,
|
||||
scope: context.currentScope,
|
||||
});
|
||||
if (
|
||||
context.isDeferredDependency({kind: HIRValue.Instruction, value: instr})
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (value.kind === 'PropertyLoad') {
|
||||
context.visitProperty(value.object, value.property, false);
|
||||
} else if (value.kind === 'StoreLocal') {
|
||||
context.visitOperand(value.value);
|
||||
if (value.lvalue.kind === InstructionKind.Reassign) {
|
||||
@@ -632,11 +688,6 @@ function handleInstruction(instr: Instruction, context: Context): void {
|
||||
context.visitOperand(operand);
|
||||
}
|
||||
}
|
||||
|
||||
context.declare(lvalue.identifier, {
|
||||
id,
|
||||
scope: context.currentScope,
|
||||
});
|
||||
}
|
||||
|
||||
function collectDependencies(
|
||||
@@ -645,7 +696,11 @@ function collectDependencies(
|
||||
temporaries: ReadonlyMap<IdentifierId, ReactiveScopeDependency>,
|
||||
processedInstrsInOptional: ReadonlySet<Instruction | Terminal>,
|
||||
): Map<ReactiveScope, Array<ReactiveScopeDependency>> {
|
||||
const context = new Context(usedOutsideDeclaringScope, temporaries);
|
||||
const context = new Context(
|
||||
usedOutsideDeclaringScope,
|
||||
temporaries,
|
||||
processedInstrsInOptional,
|
||||
);
|
||||
|
||||
for (const param of fn.params) {
|
||||
if (param.kind === 'Identifier') {
|
||||
@@ -694,16 +749,26 @@ function collectDependencies(
|
||||
/**
|
||||
* Recursively visit the inner function to extract dependencies there
|
||||
*/
|
||||
const wasInInnerFn = context.inInnerFn;
|
||||
context.inInnerFn = true;
|
||||
handleFunction(instr.value.loweredFunc.func);
|
||||
context.inInnerFn = wasInInnerFn;
|
||||
} else if (!processedInstrsInOptional.has(instr)) {
|
||||
const innerFn = instr.value.loweredFunc.func;
|
||||
context.enterInnerFn(
|
||||
instr as
|
||||
| TInstruction<FunctionExpression>
|
||||
| TInstruction<ObjectMethod>,
|
||||
() => {
|
||||
handleFunction(innerFn);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
handleInstruction(instr, context);
|
||||
}
|
||||
}
|
||||
|
||||
if (!processedInstrsInOptional.has(block.terminal)) {
|
||||
if (
|
||||
!context.isDeferredDependency({
|
||||
kind: HIRValue.Terminal,
|
||||
value: block.terminal,
|
||||
})
|
||||
) {
|
||||
for (const place of eachTerminalOperand(block.terminal)) {
|
||||
context.visitOperand(place);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ import {
|
||||
import {deadCodeElimination} from '../Optimization';
|
||||
import {inferReactiveScopeVariables} from '../ReactiveScopes';
|
||||
import {rewriteInstructionKindsBasedOnReassignment} from '../SSA';
|
||||
import {logHIRFunction} from '../Utils/logger';
|
||||
import {inferMutableContextVariables} from './InferMutableContextVariables';
|
||||
import {inferMutableRanges} from './InferMutableRanges';
|
||||
import inferReferenceEffects from './InferReferenceEffects';
|
||||
@@ -112,7 +111,11 @@ function lower(func: HIRFunction): void {
|
||||
rewriteInstructionKindsBasedOnReassignment(func);
|
||||
inferReactiveScopeVariables(func);
|
||||
inferMutableContextVariables(func);
|
||||
logHIRFunction('AnalyseFunction (inner)', func);
|
||||
func.env.logger?.debugLogIRs?.({
|
||||
kind: 'hir',
|
||||
name: 'AnalyseFunction (inner)',
|
||||
value: func,
|
||||
});
|
||||
}
|
||||
|
||||
function infer(
|
||||
|
||||
@@ -546,16 +546,14 @@ function createPropsProperties(
|
||||
let refProperty: ObjectProperty | undefined;
|
||||
let keyProperty: ObjectProperty | undefined;
|
||||
const props: Array<ObjectProperty | SpreadPattern> = [];
|
||||
const jsxAttributesWithoutKeyAndRef = propAttributes.filter(
|
||||
p => p.kind === 'JsxAttribute' && p.name !== 'key' && p.name !== 'ref',
|
||||
const jsxAttributesWithoutKey = propAttributes.filter(
|
||||
p => p.kind === 'JsxAttribute' && p.name !== 'key',
|
||||
);
|
||||
const jsxSpreadAttributes = propAttributes.filter(
|
||||
p => p.kind === 'JsxSpreadAttribute',
|
||||
);
|
||||
const spreadPropsOnly =
|
||||
jsxAttributesWithoutKeyAndRef.length === 0 &&
|
||||
jsxSpreadAttributes.length === 1;
|
||||
|
||||
jsxAttributesWithoutKey.length === 0 && jsxSpreadAttributes.length === 1;
|
||||
propAttributes.forEach(prop => {
|
||||
switch (prop.kind) {
|
||||
case 'JsxAttribute': {
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* 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, isPropsType} from '../HIR';
|
||||
|
||||
/**
|
||||
* Converts method calls into regular calls where the receiver is the props object:
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* // INPUT
|
||||
* props.foo();
|
||||
*
|
||||
* // OUTPUT
|
||||
* const t0 = props.foo;
|
||||
* t0();
|
||||
* ```
|
||||
*
|
||||
* Counter example:
|
||||
*
|
||||
* Here the receiver is `props.foo`, not the props object, so we don't rewrite it:
|
||||
*
|
||||
* // INPUT
|
||||
* props.foo.bar();
|
||||
*
|
||||
* // OUTPUT
|
||||
* props.foo.bar();
|
||||
* ```
|
||||
*/
|
||||
export function optimizePropsMethodCalls(fn: HIRFunction): void {
|
||||
for (const [, block] of fn.body.blocks) {
|
||||
for (let i = 0; i < block.instructions.length; i++) {
|
||||
const instr = block.instructions[i]!;
|
||||
if (
|
||||
instr.value.kind === 'MethodCall' &&
|
||||
isPropsType(instr.value.receiver.identifier)
|
||||
) {
|
||||
instr.value = {
|
||||
kind: 'CallExpression',
|
||||
callee: instr.value.property,
|
||||
args: instr.value.args,
|
||||
loc: instr.value.loc,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -103,6 +103,11 @@ export type CodegenFunction = {
|
||||
* This is true if the compiler has the lowered useContext calls.
|
||||
*/
|
||||
hasLoweredContextAccess: boolean;
|
||||
|
||||
/**
|
||||
* This is true if the compiler has compiled a fire to a useFire call
|
||||
*/
|
||||
hasFireRewrite: boolean;
|
||||
};
|
||||
|
||||
export function codegenFunction(
|
||||
@@ -355,6 +360,7 @@ function codegenReactiveFunction(
|
||||
prunedMemoValues: countMemoBlockVisitor.prunedMemoValues,
|
||||
outlined: [],
|
||||
hasLoweredContextAccess: fn.env.hasLoweredContextAccess,
|
||||
hasFireRewrite: fn.env.hasFireRewrite,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1354,20 +1360,6 @@ function codegenForInit(
|
||||
init: ReactiveValue,
|
||||
): t.Expression | t.VariableDeclaration | null {
|
||||
if (init.kind === 'SequenceExpression') {
|
||||
for (const instr of init.instructions) {
|
||||
if (instr.value.kind === 'DeclareContext') {
|
||||
CompilerError.throwTodo({
|
||||
reason: `Support for loops where the index variable is a context variable`,
|
||||
loc: instr.loc,
|
||||
description:
|
||||
instr.value.lvalue.place.identifier.name != null
|
||||
? `\`${instr.value.lvalue.place.identifier.name.value}\` is a context variable`
|
||||
: null,
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const body = codegenBlock(
|
||||
cx,
|
||||
init.instructions.map(instruction => ({
|
||||
@@ -1378,20 +1370,33 @@ function codegenForInit(
|
||||
const declarators: Array<t.VariableDeclarator> = [];
|
||||
let kind: 'let' | 'const' = 'const';
|
||||
body.forEach(instr => {
|
||||
CompilerError.invariant(
|
||||
instr.type === 'VariableDeclaration' &&
|
||||
(instr.kind === 'let' || instr.kind === 'const'),
|
||||
{
|
||||
reason: 'Expected a variable declaration',
|
||||
loc: init.loc,
|
||||
description: `Got ${instr.type}`,
|
||||
suggestions: null,
|
||||
},
|
||||
);
|
||||
if (instr.kind === 'let') {
|
||||
kind = 'let';
|
||||
let top: undefined | t.VariableDeclarator = undefined;
|
||||
if (
|
||||
instr.type === 'ExpressionStatement' &&
|
||||
instr.expression.type === 'AssignmentExpression' &&
|
||||
instr.expression.operator === '=' &&
|
||||
instr.expression.left.type === 'Identifier' &&
|
||||
(top = declarators.at(-1))?.id.type === 'Identifier' &&
|
||||
top?.id.name === instr.expression.left.name &&
|
||||
top?.init == null
|
||||
) {
|
||||
top.init = instr.expression.right;
|
||||
} else {
|
||||
CompilerError.invariant(
|
||||
instr.type === 'VariableDeclaration' &&
|
||||
(instr.kind === 'let' || instr.kind === 'const'),
|
||||
{
|
||||
reason: 'Expected a variable declaration',
|
||||
loc: init.loc,
|
||||
description: `Got ${instr.type}`,
|
||||
suggestions: null,
|
||||
},
|
||||
);
|
||||
if (instr.kind === 'let') {
|
||||
kind = 'let';
|
||||
}
|
||||
declarators.push(...instr.declarations);
|
||||
}
|
||||
declarators.push(...instr.declarations);
|
||||
});
|
||||
CompilerError.invariant(declarators.length > 0, {
|
||||
reason: 'Expected a variable declaration',
|
||||
|
||||
@@ -25,7 +25,6 @@ import {
|
||||
eachPatternOperand,
|
||||
} from '../HIR/visitors';
|
||||
import DisjointSet from '../Utils/DisjointSet';
|
||||
import {logHIRFunction} from '../Utils/logger';
|
||||
import {assertExhaustive} from '../Utils/utils';
|
||||
|
||||
/*
|
||||
@@ -156,7 +155,11 @@ export function inferReactiveScopeVariables(fn: HIRFunction): void {
|
||||
scope.range.end > maxInstruction + 1
|
||||
) {
|
||||
// Make it easier to debug why the error occurred
|
||||
logHIRFunction('InferReactiveScopeVariables (invalid scope)', fn);
|
||||
fn.env.logger?.debugLogIRs?.({
|
||||
kind: 'hir',
|
||||
name: 'InferReactiveScopeVariables (invalid scope)',
|
||||
value: fn,
|
||||
});
|
||||
CompilerError.invariant(false, {
|
||||
reason: `Invalid mutable range for scope`,
|
||||
loc: GeneratedSource,
|
||||
|
||||
@@ -0,0 +1,760 @@
|
||||
/**
|
||||
* 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 {
|
||||
CompilerError,
|
||||
CompilerErrorDetailOptions,
|
||||
ErrorSeverity,
|
||||
SourceLocation,
|
||||
} from '..';
|
||||
import {
|
||||
ArrayExpression,
|
||||
CallExpression,
|
||||
Effect,
|
||||
Environment,
|
||||
FunctionExpression,
|
||||
GeneratedSource,
|
||||
HIRFunction,
|
||||
Identifier,
|
||||
IdentifierId,
|
||||
Instruction,
|
||||
InstructionId,
|
||||
InstructionKind,
|
||||
InstructionValue,
|
||||
isUseEffectHookType,
|
||||
LoadLocal,
|
||||
makeInstructionId,
|
||||
Place,
|
||||
promoteTemporary,
|
||||
} from '../HIR';
|
||||
import {createTemporaryPlace, markInstructionIds} from '../HIR/HIRBuilder';
|
||||
import {getOrInsertWith} from '../Utils/utils';
|
||||
import {BuiltInFireId, DefaultNonmutatingHook} from '../HIR/ObjectShape';
|
||||
import {eachInstructionOperand} from '../HIR/visitors';
|
||||
import {printSourceLocationLine} from '../HIR/PrintHIR';
|
||||
|
||||
/*
|
||||
* TODO(jmbrown):
|
||||
* - traverse object methods
|
||||
* - method calls
|
||||
* - React.useEffect calls
|
||||
*/
|
||||
|
||||
const CANNOT_COMPILE_FIRE = 'Cannot compile `fire`';
|
||||
|
||||
export function transformFire(fn: HIRFunction): void {
|
||||
const context = new Context(fn.env);
|
||||
replaceFireFunctions(fn, context);
|
||||
if (!context.hasErrors()) {
|
||||
ensureNoMoreFireUses(fn, context);
|
||||
}
|
||||
context.throwIfErrorsFound();
|
||||
}
|
||||
|
||||
function replaceFireFunctions(fn: HIRFunction, context: Context): void {
|
||||
let hasRewrite = false;
|
||||
for (const [, block] of fn.body.blocks) {
|
||||
const rewriteInstrs = new Map<InstructionId, Array<Instruction>>();
|
||||
const deleteInstrs = new Set<InstructionId>();
|
||||
for (const instr of block.instructions) {
|
||||
const {value, lvalue} = instr;
|
||||
if (
|
||||
value.kind === 'CallExpression' &&
|
||||
isUseEffectHookType(value.callee.identifier) &&
|
||||
value.args.length > 0 &&
|
||||
value.args[0].kind === 'Identifier'
|
||||
) {
|
||||
const lambda = context.getFunctionExpression(
|
||||
value.args[0].identifier.id,
|
||||
);
|
||||
if (lambda != null) {
|
||||
const capturedCallees =
|
||||
visitFunctionExpressionAndPropagateFireDependencies(
|
||||
lambda,
|
||||
context,
|
||||
true,
|
||||
);
|
||||
|
||||
// Add useFire calls for all fire calls in found in the lambda
|
||||
const newInstrs = [];
|
||||
for (const [
|
||||
fireCalleePlace,
|
||||
fireCalleeInfo,
|
||||
] of capturedCallees.entries()) {
|
||||
if (!context.hasCalleeWithInsertedFire(fireCalleePlace)) {
|
||||
context.addCalleeWithInsertedFire(fireCalleePlace);
|
||||
const loadUseFireInstr = makeLoadUseFireInstruction(fn.env);
|
||||
const loadFireCalleeInstr = makeLoadFireCalleeInstruction(
|
||||
fn.env,
|
||||
fireCalleeInfo.capturedCalleeIdentifier,
|
||||
);
|
||||
const callUseFireInstr = makeCallUseFireInstruction(
|
||||
fn.env,
|
||||
loadUseFireInstr.lvalue,
|
||||
loadFireCalleeInstr.lvalue,
|
||||
);
|
||||
const storeUseFireInstr = makeStoreUseFireInstruction(
|
||||
fn.env,
|
||||
callUseFireInstr.lvalue,
|
||||
fireCalleeInfo.fireFunctionBinding,
|
||||
);
|
||||
newInstrs.push(
|
||||
loadUseFireInstr,
|
||||
loadFireCalleeInstr,
|
||||
callUseFireInstr,
|
||||
storeUseFireInstr,
|
||||
);
|
||||
|
||||
// We insert all of these instructions before the useEffect is loaded
|
||||
const loadUseEffectInstrId = context.getLoadGlobalInstrId(
|
||||
value.callee.identifier.id,
|
||||
);
|
||||
if (loadUseEffectInstrId == null) {
|
||||
context.pushError({
|
||||
loc: value.loc,
|
||||
description: null,
|
||||
severity: ErrorSeverity.Invariant,
|
||||
reason: '[InsertFire] No LoadGlobal found for useEffect call',
|
||||
suggestions: null,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
rewriteInstrs.set(loadUseEffectInstrId, newInstrs);
|
||||
}
|
||||
}
|
||||
ensureNoRemainingCalleeCaptures(
|
||||
lambda.loweredFunc.func,
|
||||
context,
|
||||
capturedCallees,
|
||||
);
|
||||
|
||||
if (
|
||||
value.args.length > 1 &&
|
||||
value.args[1] != null &&
|
||||
value.args[1].kind === 'Identifier'
|
||||
) {
|
||||
const depArray = value.args[1];
|
||||
const depArrayExpression = context.getArrayExpression(
|
||||
depArray.identifier.id,
|
||||
);
|
||||
if (depArrayExpression != null) {
|
||||
for (const dependency of depArrayExpression.elements) {
|
||||
if (dependency.kind === 'Identifier') {
|
||||
const loadOfDependency = context.getLoadLocalInstr(
|
||||
dependency.identifier.id,
|
||||
);
|
||||
if (loadOfDependency != null) {
|
||||
const replacedDepArrayItem = capturedCallees.get(
|
||||
loadOfDependency.place.identifier.id,
|
||||
);
|
||||
if (replacedDepArrayItem != null) {
|
||||
loadOfDependency.place =
|
||||
replacedDepArrayItem.fireFunctionBinding;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
context.pushError({
|
||||
loc: value.args[1].loc,
|
||||
description:
|
||||
'You must use an array literal for an effect dependency array when that effect uses `fire()`',
|
||||
severity: ErrorSeverity.Invariant,
|
||||
reason: CANNOT_COMPILE_FIRE,
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
} else if (value.args.length > 1 && value.args[1].kind === 'Spread') {
|
||||
context.pushError({
|
||||
loc: value.args[1].place.loc,
|
||||
description:
|
||||
'You must use an array literal for an effect dependency array when that effect uses `fire()`',
|
||||
severity: ErrorSeverity.Invariant,
|
||||
reason: CANNOT_COMPILE_FIRE,
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (
|
||||
value.kind === 'CallExpression' &&
|
||||
value.callee.identifier.type.kind === 'Function' &&
|
||||
value.callee.identifier.type.shapeId === BuiltInFireId &&
|
||||
context.inUseEffectLambda()
|
||||
) {
|
||||
/*
|
||||
* We found a fire(callExpr()) call. We remove the `fire()` call and replace the callExpr()
|
||||
* with a freshly generated fire function binding. We'll insert the useFire call before the
|
||||
* useEffect call, which happens in the CallExpression (useEffect) case above.
|
||||
*/
|
||||
|
||||
/*
|
||||
* We only allow fire to be called with a CallExpression: `fire(f())`
|
||||
* TODO: add support for method calls: `fire(this.method())`
|
||||
*/
|
||||
if (value.args.length === 1 && value.args[0].kind === 'Identifier') {
|
||||
const callExpr = context.getCallExpression(
|
||||
value.args[0].identifier.id,
|
||||
);
|
||||
|
||||
if (callExpr != null) {
|
||||
const calleeId = callExpr.callee.identifier.id;
|
||||
const loadLocal = context.getLoadLocalInstr(calleeId);
|
||||
if (loadLocal == null) {
|
||||
context.pushError({
|
||||
loc: value.loc,
|
||||
description: null,
|
||||
severity: ErrorSeverity.Invariant,
|
||||
reason:
|
||||
'[InsertFire] No loadLocal found for fire call argument',
|
||||
suggestions: null,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
const fireFunctionBinding =
|
||||
context.getOrGenerateFireFunctionBinding(
|
||||
loadLocal.place,
|
||||
value.loc,
|
||||
);
|
||||
|
||||
loadLocal.place = {...fireFunctionBinding};
|
||||
|
||||
// Delete the fire call expression
|
||||
deleteInstrs.add(instr.id);
|
||||
} else {
|
||||
context.pushError({
|
||||
loc: value.loc,
|
||||
description:
|
||||
'`fire()` can only receive a function call such as `fire(fn(a,b)). Method calls and other expressions are not allowed',
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
reason: CANNOT_COMPILE_FIRE,
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
let description: string =
|
||||
'fire() can only take in a single call expression as an argument';
|
||||
if (value.args.length === 0) {
|
||||
description += ' but received none';
|
||||
} else if (value.args.length > 1) {
|
||||
description += ' but received multiple arguments';
|
||||
} else if (value.args[0].kind === 'Spread') {
|
||||
description += ' but received a spread argument';
|
||||
}
|
||||
context.pushError({
|
||||
loc: value.loc,
|
||||
description,
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
reason: CANNOT_COMPILE_FIRE,
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
} else if (value.kind === 'CallExpression') {
|
||||
context.addCallExpression(lvalue.identifier.id, value);
|
||||
} else if (
|
||||
value.kind === 'FunctionExpression' &&
|
||||
context.inUseEffectLambda()
|
||||
) {
|
||||
visitFunctionExpressionAndPropagateFireDependencies(
|
||||
value,
|
||||
context,
|
||||
false,
|
||||
);
|
||||
} else if (value.kind === 'FunctionExpression') {
|
||||
context.addFunctionExpression(lvalue.identifier.id, value);
|
||||
} else if (value.kind === 'LoadLocal') {
|
||||
context.addLoadLocalInstr(lvalue.identifier.id, value);
|
||||
} else if (
|
||||
value.kind === 'LoadGlobal' &&
|
||||
value.binding.kind === 'ImportSpecifier' &&
|
||||
value.binding.module === 'react' &&
|
||||
value.binding.imported === 'fire' &&
|
||||
context.inUseEffectLambda()
|
||||
) {
|
||||
deleteInstrs.add(instr.id);
|
||||
} else if (value.kind === 'LoadGlobal') {
|
||||
context.addLoadGlobalInstrId(lvalue.identifier.id, instr.id);
|
||||
} else if (value.kind === 'ArrayExpression') {
|
||||
context.addArrayExpression(lvalue.identifier.id, value);
|
||||
}
|
||||
}
|
||||
block.instructions = rewriteInstructions(rewriteInstrs, block.instructions);
|
||||
block.instructions = deleteInstructions(deleteInstrs, block.instructions);
|
||||
|
||||
if (rewriteInstrs.size > 0 || deleteInstrs.size > 0) {
|
||||
hasRewrite = true;
|
||||
fn.env.hasFireRewrite = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasRewrite) {
|
||||
markInstructionIds(fn.body);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverses a function expression to find fire calls fire(foo()) and replaces them with
|
||||
* fireFoo().
|
||||
*
|
||||
* When a function captures a fire call we need to update its context to reflect the newly created
|
||||
* fire function bindings and update the LoadLocals referenced by the function's dependencies.
|
||||
*
|
||||
* @param isUseEffect is necessary so we can keep track of when we should additionally insert
|
||||
* useFire hooks calls.
|
||||
*/
|
||||
function visitFunctionExpressionAndPropagateFireDependencies(
|
||||
fnExpr: FunctionExpression,
|
||||
context: Context,
|
||||
enteringUseEffect: boolean,
|
||||
): FireCalleesToFireFunctionBinding {
|
||||
let withScope = enteringUseEffect
|
||||
? context.withUseEffectLambdaScope.bind(context)
|
||||
: context.withFunctionScope.bind(context);
|
||||
|
||||
const calleesCapturedByFnExpression = withScope(() =>
|
||||
replaceFireFunctions(fnExpr.loweredFunc.func, context),
|
||||
);
|
||||
|
||||
/*
|
||||
* Make a mapping from each dependency to the corresponding LoadLocal for it so that
|
||||
* we can replace the loaded place with the generated fire function binding
|
||||
*/
|
||||
const loadLocalsToDepLoads = new Map<IdentifierId, LoadLocal>();
|
||||
for (const dep of fnExpr.loweredFunc.dependencies) {
|
||||
const loadLocal = context.getLoadLocalInstr(dep.identifier.id);
|
||||
if (loadLocal != null) {
|
||||
loadLocalsToDepLoads.set(loadLocal.place.identifier.id, loadLocal);
|
||||
}
|
||||
}
|
||||
|
||||
const replacedCallees = new Map<IdentifierId, Place>();
|
||||
for (const [
|
||||
calleeIdentifierId,
|
||||
loadedFireFunctionBindingPlace,
|
||||
] of calleesCapturedByFnExpression.entries()) {
|
||||
/*
|
||||
* Given the ids of captured fire callees, look at the deps for loads of those identifiers
|
||||
* and replace them with the new fire function binding
|
||||
*/
|
||||
const loadLocal = loadLocalsToDepLoads.get(calleeIdentifierId);
|
||||
if (loadLocal == null) {
|
||||
context.pushError({
|
||||
loc: fnExpr.loc,
|
||||
description: null,
|
||||
severity: ErrorSeverity.Invariant,
|
||||
reason:
|
||||
'[InsertFire] No loadLocal found for fire call argument for lambda',
|
||||
suggestions: null,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
const oldPlaceId = loadLocal.place.identifier.id;
|
||||
loadLocal.place = {
|
||||
...loadedFireFunctionBindingPlace.fireFunctionBinding,
|
||||
};
|
||||
|
||||
replacedCallees.set(
|
||||
oldPlaceId,
|
||||
loadedFireFunctionBindingPlace.fireFunctionBinding,
|
||||
);
|
||||
}
|
||||
|
||||
// For each replaced callee, update the context of the function expression to track it
|
||||
for (
|
||||
let contextIdx = 0;
|
||||
contextIdx < fnExpr.loweredFunc.func.context.length;
|
||||
contextIdx++
|
||||
) {
|
||||
const contextItem = fnExpr.loweredFunc.func.context[contextIdx];
|
||||
const replacedCallee = replacedCallees.get(contextItem.identifier.id);
|
||||
if (replacedCallee != null) {
|
||||
fnExpr.loweredFunc.func.context[contextIdx] = replacedCallee;
|
||||
}
|
||||
}
|
||||
|
||||
context.mergeCalleesFromInnerScope(calleesCapturedByFnExpression);
|
||||
|
||||
return calleesCapturedByFnExpression;
|
||||
}
|
||||
|
||||
/*
|
||||
* eachInstructionOperand is not sufficient for our cases because:
|
||||
* 1. fire is a global, which will not appear
|
||||
* 2. The HIR may be malformed, so can't rely on function deps and must
|
||||
* traverse the whole function.
|
||||
*/
|
||||
function* eachReachablePlace(fn: HIRFunction): Iterable<Place> {
|
||||
for (const [, block] of fn.body.blocks) {
|
||||
for (const instr of block.instructions) {
|
||||
if (
|
||||
instr.value.kind === 'FunctionExpression' ||
|
||||
instr.value.kind === 'ObjectMethod'
|
||||
) {
|
||||
yield* eachReachablePlace(instr.value.loweredFunc.func);
|
||||
} else {
|
||||
yield* eachInstructionOperand(instr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function ensureNoRemainingCalleeCaptures(
|
||||
fn: HIRFunction,
|
||||
context: Context,
|
||||
capturedCallees: FireCalleesToFireFunctionBinding,
|
||||
): void {
|
||||
for (const place of eachReachablePlace(fn)) {
|
||||
const calleeInfo = capturedCallees.get(place.identifier.id);
|
||||
if (calleeInfo != null) {
|
||||
const calleeName =
|
||||
calleeInfo.capturedCalleeIdentifier.name?.kind === 'named'
|
||||
? calleeInfo.capturedCalleeIdentifier.name.value
|
||||
: '<unknown>';
|
||||
context.pushError({
|
||||
loc: place.loc,
|
||||
description: `All uses of ${calleeName} must be either used with a fire() call in \
|
||||
this effect or not used with a fire() call at all. ${calleeName} was used with fire() on line \
|
||||
${printSourceLocationLine(calleeInfo.fireLoc)} in this effect`,
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
reason: CANNOT_COMPILE_FIRE,
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function ensureNoMoreFireUses(fn: HIRFunction, context: Context): void {
|
||||
for (const place of eachReachablePlace(fn)) {
|
||||
if (
|
||||
place.identifier.type.kind === 'Function' &&
|
||||
place.identifier.type.shapeId === BuiltInFireId
|
||||
) {
|
||||
context.pushError({
|
||||
loc: place.identifier.loc,
|
||||
description: 'Cannot use `fire` outside of a useEffect function',
|
||||
severity: ErrorSeverity.Invariant,
|
||||
reason: CANNOT_COMPILE_FIRE,
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function makeLoadUseFireInstruction(env: Environment): Instruction {
|
||||
const useFirePlace = createTemporaryPlace(env, GeneratedSource);
|
||||
useFirePlace.effect = Effect.Read;
|
||||
useFirePlace.identifier.type = DefaultNonmutatingHook;
|
||||
const instrValue: InstructionValue = {
|
||||
kind: 'LoadGlobal',
|
||||
binding: {
|
||||
kind: 'ImportSpecifier',
|
||||
name: 'useFire',
|
||||
module: 'react',
|
||||
imported: 'useFire',
|
||||
},
|
||||
loc: GeneratedSource,
|
||||
};
|
||||
return {
|
||||
id: makeInstructionId(0),
|
||||
value: instrValue,
|
||||
lvalue: {...useFirePlace},
|
||||
loc: GeneratedSource,
|
||||
};
|
||||
}
|
||||
|
||||
function makeLoadFireCalleeInstruction(
|
||||
env: Environment,
|
||||
fireCalleeIdentifier: Identifier,
|
||||
): Instruction {
|
||||
const loadedFireCallee = createTemporaryPlace(env, GeneratedSource);
|
||||
const fireCallee: Place = {
|
||||
kind: 'Identifier',
|
||||
identifier: fireCalleeIdentifier,
|
||||
reactive: false,
|
||||
effect: Effect.Unknown,
|
||||
loc: fireCalleeIdentifier.loc,
|
||||
};
|
||||
return {
|
||||
id: makeInstructionId(0),
|
||||
value: {
|
||||
kind: 'LoadLocal',
|
||||
loc: GeneratedSource,
|
||||
place: {...fireCallee},
|
||||
},
|
||||
lvalue: {...loadedFireCallee},
|
||||
loc: GeneratedSource,
|
||||
};
|
||||
}
|
||||
|
||||
function makeCallUseFireInstruction(
|
||||
env: Environment,
|
||||
useFirePlace: Place,
|
||||
argPlace: Place,
|
||||
): Instruction {
|
||||
const useFireCallResultPlace = createTemporaryPlace(env, GeneratedSource);
|
||||
useFireCallResultPlace.effect = Effect.Read;
|
||||
|
||||
const useFireCall: CallExpression = {
|
||||
kind: 'CallExpression',
|
||||
callee: {...useFirePlace},
|
||||
args: [argPlace],
|
||||
loc: GeneratedSource,
|
||||
};
|
||||
|
||||
return {
|
||||
id: makeInstructionId(0),
|
||||
value: useFireCall,
|
||||
lvalue: {...useFireCallResultPlace},
|
||||
loc: GeneratedSource,
|
||||
};
|
||||
}
|
||||
|
||||
function makeStoreUseFireInstruction(
|
||||
env: Environment,
|
||||
useFireCallResultPlace: Place,
|
||||
fireFunctionBindingPlace: Place,
|
||||
): Instruction {
|
||||
promoteTemporary(fireFunctionBindingPlace.identifier);
|
||||
|
||||
const fireFunctionBindingLValuePlace = createTemporaryPlace(
|
||||
env,
|
||||
GeneratedSource,
|
||||
);
|
||||
return {
|
||||
id: makeInstructionId(0),
|
||||
value: {
|
||||
kind: 'StoreLocal',
|
||||
lvalue: {
|
||||
kind: InstructionKind.Const,
|
||||
place: {...fireFunctionBindingPlace},
|
||||
},
|
||||
value: {...useFireCallResultPlace},
|
||||
type: null,
|
||||
loc: GeneratedSource,
|
||||
},
|
||||
lvalue: fireFunctionBindingLValuePlace,
|
||||
loc: GeneratedSource,
|
||||
};
|
||||
}
|
||||
|
||||
type FireCalleesToFireFunctionBinding = Map<
|
||||
IdentifierId,
|
||||
{
|
||||
fireFunctionBinding: Place;
|
||||
capturedCalleeIdentifier: Identifier;
|
||||
fireLoc: SourceLocation;
|
||||
}
|
||||
>;
|
||||
|
||||
class Context {
|
||||
#env: Environment;
|
||||
|
||||
#errors: CompilerError = new CompilerError();
|
||||
|
||||
/*
|
||||
* Used to look up the call expression passed to a `fire(callExpr())`. Gives back
|
||||
* the `callExpr()`.
|
||||
*/
|
||||
#callExpressions = new Map<IdentifierId, CallExpression>();
|
||||
|
||||
/*
|
||||
* We keep track of function expressions so that we can traverse them when
|
||||
* we encounter a lambda passed to a useEffect call
|
||||
*/
|
||||
#functionExpressions = new Map<IdentifierId, FunctionExpression>();
|
||||
|
||||
/*
|
||||
* Mapping from lvalue ids to the LoadLocal for it. Allows us to replace dependency LoadLocals.
|
||||
*/
|
||||
#loadLocals = new Map<IdentifierId, LoadLocal>();
|
||||
|
||||
/*
|
||||
* Maps all of the fire callees found in a component/hook to the generated fire function places
|
||||
* we create for them. Allows us to reuse already-inserted useFire results
|
||||
*/
|
||||
#fireCalleesToFireFunctions: Map<IdentifierId, Place> = new Map();
|
||||
|
||||
/*
|
||||
* The callees for which we have already created fire bindings. Used to skip inserting a new
|
||||
* useFire call for a fire callee if one has already been created.
|
||||
*/
|
||||
#calleesWithInsertedFire = new Set<IdentifierId>();
|
||||
|
||||
/*
|
||||
* A mapping from fire callees to the created fire function bindings that are reachable from this
|
||||
* scope.
|
||||
*
|
||||
* We additionally keep track of the captured callee identifier so that we can properly reference
|
||||
* it in the place where we LoadLocal the callee as an argument to useFire.
|
||||
*/
|
||||
#capturedCalleeIdentifierIds: FireCalleesToFireFunctionBinding = new Map();
|
||||
|
||||
/*
|
||||
* We only transform fire calls if we're syntactically within a useEffect lambda (for now)
|
||||
*/
|
||||
#inUseEffectLambda = false;
|
||||
|
||||
/*
|
||||
* Mapping from useEffect callee identifier ids to the instruction id of the
|
||||
* load global instruction for the useEffect call. We use this to insert the
|
||||
* useFire calls before the useEffect call
|
||||
*/
|
||||
#loadGlobalInstructionIds = new Map<IdentifierId, InstructionId>();
|
||||
|
||||
constructor(env: Environment) {
|
||||
this.#env = env;
|
||||
}
|
||||
|
||||
/*
|
||||
* We keep track of array expressions so we can rewrite dependency arrays passed to useEffect
|
||||
* to use the fire functions
|
||||
*/
|
||||
#arrayExpressions = new Map<IdentifierId, ArrayExpression>();
|
||||
|
||||
pushError(error: CompilerErrorDetailOptions): void {
|
||||
this.#errors.push(error);
|
||||
}
|
||||
|
||||
withFunctionScope(fn: () => void): FireCalleesToFireFunctionBinding {
|
||||
fn();
|
||||
return this.#capturedCalleeIdentifierIds;
|
||||
}
|
||||
|
||||
withUseEffectLambdaScope(fn: () => void): FireCalleesToFireFunctionBinding {
|
||||
const capturedCalleeIdentifierIds = this.#capturedCalleeIdentifierIds;
|
||||
const inUseEffectLambda = this.#inUseEffectLambda;
|
||||
|
||||
this.#capturedCalleeIdentifierIds = new Map();
|
||||
this.#inUseEffectLambda = true;
|
||||
|
||||
const resultCapturedCalleeIdentifierIds = this.withFunctionScope(fn);
|
||||
|
||||
this.#capturedCalleeIdentifierIds = capturedCalleeIdentifierIds;
|
||||
this.#inUseEffectLambda = inUseEffectLambda;
|
||||
|
||||
return resultCapturedCalleeIdentifierIds;
|
||||
}
|
||||
|
||||
addCallExpression(id: IdentifierId, callExpr: CallExpression): void {
|
||||
this.#callExpressions.set(id, callExpr);
|
||||
}
|
||||
|
||||
getCallExpression(id: IdentifierId): CallExpression | undefined {
|
||||
return this.#callExpressions.get(id);
|
||||
}
|
||||
|
||||
addLoadLocalInstr(id: IdentifierId, loadLocal: LoadLocal): void {
|
||||
this.#loadLocals.set(id, loadLocal);
|
||||
}
|
||||
|
||||
getLoadLocalInstr(id: IdentifierId): LoadLocal | undefined {
|
||||
return this.#loadLocals.get(id);
|
||||
}
|
||||
getOrGenerateFireFunctionBinding(
|
||||
callee: Place,
|
||||
fireLoc: SourceLocation,
|
||||
): Place {
|
||||
const fireFunctionBinding = getOrInsertWith(
|
||||
this.#fireCalleesToFireFunctions,
|
||||
callee.identifier.id,
|
||||
() => createTemporaryPlace(this.#env, GeneratedSource),
|
||||
);
|
||||
|
||||
this.#capturedCalleeIdentifierIds.set(callee.identifier.id, {
|
||||
fireFunctionBinding,
|
||||
capturedCalleeIdentifier: callee.identifier,
|
||||
fireLoc,
|
||||
});
|
||||
|
||||
return fireFunctionBinding;
|
||||
}
|
||||
|
||||
mergeCalleesFromInnerScope(
|
||||
innerCallees: FireCalleesToFireFunctionBinding,
|
||||
): void {
|
||||
for (const [id, calleeInfo] of innerCallees.entries()) {
|
||||
this.#capturedCalleeIdentifierIds.set(id, calleeInfo);
|
||||
}
|
||||
}
|
||||
|
||||
addCalleeWithInsertedFire(id: IdentifierId): void {
|
||||
this.#calleesWithInsertedFire.add(id);
|
||||
}
|
||||
|
||||
hasCalleeWithInsertedFire(id: IdentifierId): boolean {
|
||||
return this.#calleesWithInsertedFire.has(id);
|
||||
}
|
||||
|
||||
inUseEffectLambda(): boolean {
|
||||
return this.#inUseEffectLambda;
|
||||
}
|
||||
|
||||
addFunctionExpression(id: IdentifierId, fn: FunctionExpression): void {
|
||||
this.#functionExpressions.set(id, fn);
|
||||
}
|
||||
|
||||
getFunctionExpression(id: IdentifierId): FunctionExpression | undefined {
|
||||
return this.#functionExpressions.get(id);
|
||||
}
|
||||
|
||||
addLoadGlobalInstrId(id: IdentifierId, instrId: InstructionId): void {
|
||||
this.#loadGlobalInstructionIds.set(id, instrId);
|
||||
}
|
||||
|
||||
getLoadGlobalInstrId(id: IdentifierId): InstructionId | undefined {
|
||||
return this.#loadGlobalInstructionIds.get(id);
|
||||
}
|
||||
|
||||
addArrayExpression(id: IdentifierId, array: ArrayExpression): void {
|
||||
this.#arrayExpressions.set(id, array);
|
||||
}
|
||||
|
||||
getArrayExpression(id: IdentifierId): ArrayExpression | undefined {
|
||||
return this.#arrayExpressions.get(id);
|
||||
}
|
||||
|
||||
hasErrors(): boolean {
|
||||
return this.#errors.hasErrors();
|
||||
}
|
||||
|
||||
throwIfErrorsFound(): void {
|
||||
if (this.hasErrors()) throw this.#errors;
|
||||
}
|
||||
}
|
||||
|
||||
function deleteInstructions(
|
||||
deleteInstrs: Set<InstructionId>,
|
||||
instructions: Array<Instruction>,
|
||||
): Array<Instruction> {
|
||||
if (deleteInstrs.size > 0) {
|
||||
const newInstrs = instructions.filter(instr => !deleteInstrs.has(instr.id));
|
||||
return newInstrs;
|
||||
}
|
||||
return instructions;
|
||||
}
|
||||
|
||||
function rewriteInstructions(
|
||||
rewriteInstrs: Map<InstructionId, Array<Instruction>>,
|
||||
instructions: Array<Instruction>,
|
||||
): Array<Instruction> {
|
||||
if (rewriteInstrs.size > 0) {
|
||||
const newInstrs = [];
|
||||
for (const instr of instructions) {
|
||||
const newInstrsAtId = rewriteInstrs.get(instr.id);
|
||||
if (newInstrsAtId != null) {
|
||||
newInstrs.push(...newInstrsAtId, instr);
|
||||
} else {
|
||||
newInstrs.push(instr);
|
||||
}
|
||||
}
|
||||
|
||||
return newInstrs;
|
||||
}
|
||||
|
||||
return instructions;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
export {transformFire} from './TransformFire';
|
||||
@@ -1,110 +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 generate from '@babel/generator';
|
||||
import * as t from '@babel/types';
|
||||
import chalk from 'chalk';
|
||||
import {HIR, HIRFunction, ReactiveFunction} from '../HIR/HIR';
|
||||
import {printFunctionWithOutlined, printHIR} from '../HIR/PrintHIR';
|
||||
import {CodegenFunction} from '../ReactiveScopes';
|
||||
import {printReactiveFunctionWithOutlined} from '../ReactiveScopes/PrintReactiveFunction';
|
||||
|
||||
let ENABLED: boolean = false;
|
||||
|
||||
let lastLogged: string;
|
||||
|
||||
export function toggleLogging(enabled: boolean): void {
|
||||
ENABLED = enabled;
|
||||
}
|
||||
|
||||
export function logDebug(step: string, value: string): void {
|
||||
if (ENABLED) {
|
||||
process.stdout.write(`${chalk.green(step)}:\n${value}\n\n`);
|
||||
}
|
||||
}
|
||||
|
||||
export function logHIR(step: string, ir: HIR): void {
|
||||
if (ENABLED) {
|
||||
const printed = printHIR(ir);
|
||||
if (printed !== lastLogged) {
|
||||
lastLogged = printed;
|
||||
process.stdout.write(`${chalk.green(step)}:\n${printed}\n\n`);
|
||||
} else {
|
||||
process.stdout.write(`${chalk.blue(step)}: (no change)\n\n`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function logCodegenFunction(step: string, fn: CodegenFunction): void {
|
||||
if (ENABLED) {
|
||||
let printed: string | null = null;
|
||||
try {
|
||||
const node = t.functionDeclaration(
|
||||
fn.id,
|
||||
fn.params,
|
||||
fn.body,
|
||||
fn.generator,
|
||||
fn.async,
|
||||
);
|
||||
const ast = generate(node);
|
||||
printed = ast.code;
|
||||
} catch (e) {
|
||||
let errMsg: string;
|
||||
if (
|
||||
typeof e === 'object' &&
|
||||
e != null &&
|
||||
'message' in e &&
|
||||
typeof e.message === 'string'
|
||||
) {
|
||||
errMsg = e.message.toString();
|
||||
} else {
|
||||
errMsg = '[empty]';
|
||||
}
|
||||
console.log('Error formatting AST: ' + errMsg);
|
||||
}
|
||||
if (printed === null) {
|
||||
return;
|
||||
}
|
||||
if (printed !== lastLogged) {
|
||||
lastLogged = printed;
|
||||
process.stdout.write(`${chalk.green(step)}:\n${printed}\n\n`);
|
||||
} else {
|
||||
process.stdout.write(`${chalk.blue(step)}: (no change)\n\n`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function logHIRFunction(step: string, fn: HIRFunction): void {
|
||||
if (ENABLED) {
|
||||
const printed = printFunctionWithOutlined(fn);
|
||||
if (printed !== lastLogged) {
|
||||
lastLogged = printed;
|
||||
process.stdout.write(`${chalk.green(step)}:\n${printed}\n\n`);
|
||||
} else {
|
||||
process.stdout.write(`${chalk.blue(step)}: (no change)\n\n`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function logReactiveFunction(step: string, fn: ReactiveFunction): void {
|
||||
if (ENABLED) {
|
||||
const printed = printReactiveFunctionWithOutlined(fn);
|
||||
if (printed !== lastLogged) {
|
||||
lastLogged = printed;
|
||||
process.stdout.write(`${chalk.green(step)}:\n${printed}\n\n`);
|
||||
} else {
|
||||
process.stdout.write(`${chalk.blue(step)}: (no change)\n\n`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function log(fn: () => string): void {
|
||||
if (ENABLED) {
|
||||
const message = fn();
|
||||
process.stdout.write(message.trim() + '\n\n');
|
||||
}
|
||||
}
|
||||
@@ -305,6 +305,14 @@ function validateNoRefAccessInRenderImpl(
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'TypeCastExpression': {
|
||||
env.set(
|
||||
instr.lvalue.identifier.id,
|
||||
env.get(instr.value.value.identifier.id) ??
|
||||
refTypeOfType(instr.lvalue),
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'LoadContext':
|
||||
case 'LoadLocal': {
|
||||
env.set(
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {useRef} from 'react';
|
||||
|
||||
function useArrayOfRef() {
|
||||
const ref = useRef(null);
|
||||
const callback = value => {
|
||||
ref.current = value;
|
||||
};
|
||||
return [callback] as const;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: () => {
|
||||
useArrayOfRef();
|
||||
return 'ok';
|
||||
},
|
||||
params: [{}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { useRef } from "react";
|
||||
|
||||
function useArrayOfRef() {
|
||||
const $ = _c(1);
|
||||
const ref = useRef(null);
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
const callback = (value) => {
|
||||
ref.current = value;
|
||||
};
|
||||
|
||||
t0 = [callback];
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
return t0 as const;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: () => {
|
||||
useArrayOfRef();
|
||||
return "ok";
|
||||
},
|
||||
|
||||
params: [{}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) "ok"
|
||||
@@ -0,0 +1,17 @@
|
||||
import {useRef} from 'react';
|
||||
|
||||
function useArrayOfRef() {
|
||||
const ref = useRef(null);
|
||||
const callback = value => {
|
||||
ref.current = value;
|
||||
};
|
||||
return [callback] as const;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: () => {
|
||||
useArrayOfRef();
|
||||
return 'ok';
|
||||
},
|
||||
params: [{}],
|
||||
};
|
||||
@@ -0,0 +1,129 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {makeArray, mutate} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Bug repro:
|
||||
* Found differences in evaluator results
|
||||
* Non-forget (expected):
|
||||
* (kind: ok)
|
||||
* {"bar":4,"x":{"foo":3,"wat0":"joe"}}
|
||||
* {"bar":5,"x":{"foo":3,"wat0":"joe"}}
|
||||
* Forget:
|
||||
* (kind: ok)
|
||||
* {"bar":4,"x":{"foo":3,"wat0":"joe"}}
|
||||
* {"bar":5,"x":{"foo":3,"wat0":"joe","wat1":"joe"}}
|
||||
*
|
||||
* Fork of `capturing-func-alias-captured-mutate`, but instead of directly
|
||||
* aliasing `y` via `[y]`, we make an opaque call.
|
||||
*
|
||||
* Note that the bug here is that we don't infer that `a = makeArray(y)`
|
||||
* potentially captures a context variable into a local variable. As a result,
|
||||
* we don't understand that `a[0].x = b` captures `x` into `y` -- instead, we're
|
||||
* currently inferring that this lambda captures `y` (for a potential later
|
||||
* mutation) and simply reads `x`.
|
||||
*
|
||||
* Concretely `InferReferenceEffects.hasContextRefOperand` is incorrectly not
|
||||
* used when we analyze CallExpressions.
|
||||
*/
|
||||
function Component({foo, bar}: {foo: number; bar: number}) {
|
||||
let x = {foo};
|
||||
let y: {bar: number; x?: {foo: number}} = {bar};
|
||||
const f0 = function () {
|
||||
let a = makeArray(y); // a = [y]
|
||||
let b = x;
|
||||
// this writes y.x = x
|
||||
a[0].x = b;
|
||||
};
|
||||
f0();
|
||||
mutate(y.x);
|
||||
return y;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{foo: 3, bar: 4}],
|
||||
sequentialRenders: [
|
||||
{foo: 3, bar: 4},
|
||||
{foo: 3, bar: 5},
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { makeArray, mutate } from "shared-runtime";
|
||||
|
||||
/**
|
||||
* Bug repro:
|
||||
* Found differences in evaluator results
|
||||
* Non-forget (expected):
|
||||
* (kind: ok)
|
||||
* {"bar":4,"x":{"foo":3,"wat0":"joe"}}
|
||||
* {"bar":5,"x":{"foo":3,"wat0":"joe"}}
|
||||
* Forget:
|
||||
* (kind: ok)
|
||||
* {"bar":4,"x":{"foo":3,"wat0":"joe"}}
|
||||
* {"bar":5,"x":{"foo":3,"wat0":"joe","wat1":"joe"}}
|
||||
*
|
||||
* Fork of `capturing-func-alias-captured-mutate`, but instead of directly
|
||||
* aliasing `y` via `[y]`, we make an opaque call.
|
||||
*
|
||||
* Note that the bug here is that we don't infer that `a = makeArray(y)`
|
||||
* potentially captures a context variable into a local variable. As a result,
|
||||
* we don't understand that `a[0].x = b` captures `x` into `y` -- instead, we're
|
||||
* currently inferring that this lambda captures `y` (for a potential later
|
||||
* mutation) and simply reads `x`.
|
||||
*
|
||||
* Concretely `InferReferenceEffects.hasContextRefOperand` is incorrectly not
|
||||
* used when we analyze CallExpressions.
|
||||
*/
|
||||
function Component(t0) {
|
||||
const $ = _c(5);
|
||||
const { foo, bar } = t0;
|
||||
let t1;
|
||||
if ($[0] !== foo) {
|
||||
t1 = { foo };
|
||||
$[0] = foo;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
const x = t1;
|
||||
let y;
|
||||
if ($[2] !== bar || $[3] !== x) {
|
||||
y = { bar };
|
||||
const f0 = function () {
|
||||
const a = makeArray(y);
|
||||
const b = x;
|
||||
|
||||
a[0].x = b;
|
||||
};
|
||||
|
||||
f0();
|
||||
mutate(y.x);
|
||||
$[2] = bar;
|
||||
$[3] = x;
|
||||
$[4] = y;
|
||||
} else {
|
||||
y = $[4];
|
||||
}
|
||||
return y;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ foo: 3, bar: 4 }],
|
||||
sequentialRenders: [
|
||||
{ foo: 3, bar: 4 },
|
||||
{ foo: 3, bar: 5 },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import {makeArray, mutate} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Bug repro:
|
||||
* Found differences in evaluator results
|
||||
* Non-forget (expected):
|
||||
* (kind: ok)
|
||||
* {"bar":4,"x":{"foo":3,"wat0":"joe"}}
|
||||
* {"bar":5,"x":{"foo":3,"wat0":"joe"}}
|
||||
* Forget:
|
||||
* (kind: ok)
|
||||
* {"bar":4,"x":{"foo":3,"wat0":"joe"}}
|
||||
* {"bar":5,"x":{"foo":3,"wat0":"joe","wat1":"joe"}}
|
||||
*
|
||||
* Fork of `capturing-func-alias-captured-mutate`, but instead of directly
|
||||
* aliasing `y` via `[y]`, we make an opaque call.
|
||||
*
|
||||
* Note that the bug here is that we don't infer that `a = makeArray(y)`
|
||||
* potentially captures a context variable into a local variable. As a result,
|
||||
* we don't understand that `a[0].x = b` captures `x` into `y` -- instead, we're
|
||||
* currently inferring that this lambda captures `y` (for a potential later
|
||||
* mutation) and simply reads `x`.
|
||||
*
|
||||
* Concretely `InferReferenceEffects.hasContextRefOperand` is incorrectly not
|
||||
* used when we analyze CallExpressions.
|
||||
*/
|
||||
function Component({foo, bar}: {foo: number; bar: number}) {
|
||||
let x = {foo};
|
||||
let y: {bar: number; x?: {foo: number}} = {bar};
|
||||
const f0 = function () {
|
||||
let a = makeArray(y); // a = [y]
|
||||
let b = x;
|
||||
// this writes y.x = x
|
||||
a[0].x = b;
|
||||
};
|
||||
f0();
|
||||
mutate(y.x);
|
||||
return y;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{foo: 3, bar: 4}],
|
||||
sequentialRenders: [
|
||||
{foo: 3, bar: 4},
|
||||
{foo: 3, bar: 5},
|
||||
],
|
||||
};
|
||||
@@ -58,18 +58,16 @@ function Foo(t0) {
|
||||
bar = $[1];
|
||||
result = $[2];
|
||||
}
|
||||
|
||||
const t1 = bar;
|
||||
let t2;
|
||||
if ($[3] !== result || $[4] !== t1) {
|
||||
t2 = <Stringify result={result} fn={t1} shouldInvokeFns={true} />;
|
||||
$[3] = result;
|
||||
$[4] = t1;
|
||||
$[5] = t2;
|
||||
let t1;
|
||||
if ($[3] !== bar || $[4] !== result) {
|
||||
t1 = <Stringify result={result} fn={bar} shouldInvokeFns={true} />;
|
||||
$[3] = bar;
|
||||
$[4] = result;
|
||||
$[5] = t1;
|
||||
} else {
|
||||
t2 = $[5];
|
||||
t1 = $[5];
|
||||
}
|
||||
return t2;
|
||||
return t1;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
|
||||
@@ -43,16 +43,15 @@ function Component(props) {
|
||||
} else {
|
||||
x = $[1];
|
||||
}
|
||||
const t0 = x;
|
||||
let t1;
|
||||
if ($[2] !== t0) {
|
||||
t1 = { x: t0 };
|
||||
$[2] = t0;
|
||||
$[3] = t1;
|
||||
let t0;
|
||||
if ($[2] !== x) {
|
||||
t0 = { x };
|
||||
$[2] = x;
|
||||
$[3] = t0;
|
||||
} else {
|
||||
t1 = $[3];
|
||||
t0 = $[3];
|
||||
}
|
||||
return t1;
|
||||
return t0;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
|
||||
@@ -42,16 +42,15 @@ function Component(props) {
|
||||
} else {
|
||||
x = $[1];
|
||||
}
|
||||
const t0 = x;
|
||||
let t1;
|
||||
if ($[2] !== t0) {
|
||||
t1 = <div>{t0}</div>;
|
||||
$[2] = t0;
|
||||
$[3] = t1;
|
||||
let t0;
|
||||
if ($[2] !== x) {
|
||||
t0 = <div>{x}</div>;
|
||||
$[2] = x;
|
||||
$[3] = t0;
|
||||
} else {
|
||||
t1 = $[3];
|
||||
t0 = $[3];
|
||||
}
|
||||
return t1;
|
||||
return t0;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
|
||||
@@ -43,16 +43,15 @@ function Component(props) {
|
||||
} else {
|
||||
x = $[1];
|
||||
}
|
||||
const t0 = x;
|
||||
let t1;
|
||||
if ($[2] !== t0) {
|
||||
t1 = { x: t0 };
|
||||
$[2] = t0;
|
||||
$[3] = t1;
|
||||
let t0;
|
||||
if ($[2] !== x) {
|
||||
t0 = { x };
|
||||
$[2] = x;
|
||||
$[3] = t0;
|
||||
} else {
|
||||
t1 = $[3];
|
||||
t0 = $[3];
|
||||
}
|
||||
return t1;
|
||||
return t0;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
|
||||
@@ -42,16 +42,15 @@ function Component(props) {
|
||||
} else {
|
||||
x = $[1];
|
||||
}
|
||||
const t0 = x;
|
||||
let t1;
|
||||
if ($[2] !== t0) {
|
||||
t1 = { x: t0 };
|
||||
$[2] = t0;
|
||||
$[3] = t1;
|
||||
let t0;
|
||||
if ($[2] !== x) {
|
||||
t0 = { x };
|
||||
$[2] = x;
|
||||
$[3] = t0;
|
||||
} else {
|
||||
t1 = $[3];
|
||||
t0 = $[3];
|
||||
}
|
||||
return t1;
|
||||
return t0;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
function Component() {
|
||||
const data = useData();
|
||||
const items = [];
|
||||
// NOTE: `i` is a context variable because it's reassigned and also referenced
|
||||
// within a closure, the `onClick` handler of each item
|
||||
for (let i = MIN; i <= MAX; i += INCREMENT) {
|
||||
items.push(<Stringify key={i} onClick={() => data.set(i)} />);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
4 | // NOTE: `i` is a context variable because it's reassigned and also referenced
|
||||
5 | // within a closure, the `onClick` handler of each item
|
||||
> 6 | for (let i = MIN; i <= MAX; i += INCREMENT) {
|
||||
| ^^^^^^^^^^^ Todo: Support for loops where the index variable is a context variable. `i` is a context variable (6:6)
|
||||
7 | items.push(<Stringify key={i} onClick={() => data.set(i)} />);
|
||||
8 | }
|
||||
9 | return items;
|
||||
```
|
||||
|
||||
|
||||
@@ -98,12 +98,6 @@ Todo: (BuildHIR::lowerExpression) Handle tagged template with interpolations (30
|
||||
|
||||
Todo: (BuildHIR::lowerExpression) Handle tagged template where cooked value is different from raw value (34:34)
|
||||
|
||||
Todo: (BuildHIR::lowerStatement) Handle Identifier inits in ForOfStatement (36:36)
|
||||
|
||||
Todo: (BuildHIR::lowerStatement) Handle ArrayPattern inits in ForOfStatement (38:38)
|
||||
|
||||
Todo: (BuildHIR::lowerStatement) Handle ObjectPattern inits in ForOfStatement (40:40)
|
||||
|
||||
Todo: (BuildHIR::node.lowerReorderableExpression) Expression type `MemberExpression` cannot be safely reordered (57:57)
|
||||
|
||||
Todo: (BuildHIR::node.lowerReorderableExpression) Expression type `BinaryExpression` cannot be safely reordered (53:53)
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
function Component() {
|
||||
const data = useData();
|
||||
const items = [];
|
||||
// NOTE: `i` is a context variable because it's reassigned and also referenced
|
||||
// within a closure, the `onClick` handler of each item
|
||||
for (let i = MIN; i <= MAX; i += INCREMENT) {
|
||||
items.push(<div key={i} onClick={() => data.set(i)} />);
|
||||
}
|
||||
return <>{items}</>;
|
||||
}
|
||||
|
||||
const MIN = 0;
|
||||
const MAX = 3;
|
||||
const INCREMENT = 1;
|
||||
|
||||
function useData() {
|
||||
return new Map();
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
params: [],
|
||||
fn: Component,
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
function Component() {
|
||||
const $ = _c(2);
|
||||
const data = useData();
|
||||
let t0;
|
||||
if ($[0] !== data) {
|
||||
const items = [];
|
||||
for (let i = MIN; i <= MAX; i = i + INCREMENT, i) {
|
||||
items.push(<div key={i} onClick={() => data.set(i)} />);
|
||||
}
|
||||
|
||||
t0 = <>{items}</>;
|
||||
$[0] = data;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
|
||||
const MIN = 0;
|
||||
const MAX = 3;
|
||||
const INCREMENT = 1;
|
||||
|
||||
function useData() {
|
||||
const $ = _c(1);
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = new Map();
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
params: [],
|
||||
fn: Component,
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div></div><div></div><div></div><div></div>
|
||||
@@ -4,7 +4,20 @@ function Component() {
|
||||
// NOTE: `i` is a context variable because it's reassigned and also referenced
|
||||
// within a closure, the `onClick` handler of each item
|
||||
for (let i = MIN; i <= MAX; i += INCREMENT) {
|
||||
items.push(<Stringify key={i} onClick={() => data.set(i)} />);
|
||||
items.push(<div key={i} onClick={() => data.set(i)} />);
|
||||
}
|
||||
return items;
|
||||
return <>{items}</>;
|
||||
}
|
||||
|
||||
const MIN = 0;
|
||||
const MAX = 3;
|
||||
const INCREMENT = 1;
|
||||
|
||||
function useData() {
|
||||
return new Map();
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
params: [],
|
||||
fn: Component,
|
||||
};
|
||||
@@ -60,6 +60,10 @@ function ConditionalJsx({shouldWrap}) {
|
||||
return content;
|
||||
}
|
||||
|
||||
function ComponentWithSpreadPropsAndRef({ref, ...other}) {
|
||||
return <Foo ref={ref} {...other} />;
|
||||
}
|
||||
|
||||
// TODO: Support value blocks
|
||||
function TernaryJsx({cond}) {
|
||||
return cond ? <div /> : null;
|
||||
@@ -409,6 +413,41 @@ function ConditionalJsx(t0) {
|
||||
return content;
|
||||
}
|
||||
|
||||
function ComponentWithSpreadPropsAndRef(t0) {
|
||||
const $ = _c2(6);
|
||||
let other;
|
||||
let ref;
|
||||
if ($[0] !== t0) {
|
||||
({ ref, ...other } = t0);
|
||||
$[0] = t0;
|
||||
$[1] = other;
|
||||
$[2] = ref;
|
||||
} else {
|
||||
other = $[1];
|
||||
ref = $[2];
|
||||
}
|
||||
let t1;
|
||||
if ($[3] !== other || $[4] !== ref) {
|
||||
if (DEV) {
|
||||
t1 = <Foo ref={ref} {...other} />;
|
||||
} else {
|
||||
t1 = {
|
||||
$$typeof: Symbol.for("react.transitional.element"),
|
||||
type: Foo,
|
||||
ref: ref,
|
||||
key: null,
|
||||
props: { ref: ref, ...other },
|
||||
};
|
||||
}
|
||||
$[3] = other;
|
||||
$[4] = ref;
|
||||
$[5] = t1;
|
||||
} else {
|
||||
t1 = $[5];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
|
||||
// TODO: Support value blocks
|
||||
function TernaryJsx(t0) {
|
||||
const $ = _c2(2);
|
||||
|
||||
@@ -56,6 +56,10 @@ function ConditionalJsx({shouldWrap}) {
|
||||
return content;
|
||||
}
|
||||
|
||||
function ComponentWithSpreadPropsAndRef({ref, ...other}) {
|
||||
return <Foo ref={ref} {...other} />;
|
||||
}
|
||||
|
||||
// TODO: Support value blocks
|
||||
function TernaryJsx({cond}) {
|
||||
return cond ? <div /> : null;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
```javascript
|
||||
// @enableJsxOutlining
|
||||
function Component(arr) {
|
||||
function Component({arr}) {
|
||||
const x = useX();
|
||||
return arr.map(i => {
|
||||
<>
|
||||
@@ -49,12 +49,13 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @enableJsxOutlining
|
||||
function Component(arr) {
|
||||
function Component(t0) {
|
||||
const $ = _c(3);
|
||||
const { arr } = t0;
|
||||
const x = useX();
|
||||
let t0;
|
||||
let t1;
|
||||
if ($[0] !== arr || $[1] !== x) {
|
||||
t0 = arr.map((i) => {
|
||||
t1 = arr.map((i) => {
|
||||
arr.map((i_0, id) => {
|
||||
const T0 = _temp;
|
||||
const child = <T0 i={i_0} x={x} />;
|
||||
@@ -65,11 +66,11 @@ function Component(arr) {
|
||||
});
|
||||
$[0] = arr;
|
||||
$[1] = x;
|
||||
$[2] = t0;
|
||||
$[2] = t1;
|
||||
} else {
|
||||
t0 = $[2];
|
||||
t1 = $[2];
|
||||
}
|
||||
return t0;
|
||||
return t1;
|
||||
}
|
||||
function _temp(t0) {
|
||||
const $ = _c(5);
|
||||
@@ -140,4 +141,4 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: exception) arr.map is not a function
|
||||
(kind: ok) [null,null]
|
||||
@@ -1,5 +1,5 @@
|
||||
// @enableJsxOutlining
|
||||
function Component(arr) {
|
||||
function Component({arr}) {
|
||||
const x = useX();
|
||||
return arr.map(i => {
|
||||
<>
|
||||
|
||||
@@ -33,17 +33,15 @@ function f(a) {
|
||||
} else {
|
||||
x = $[1];
|
||||
}
|
||||
|
||||
const t0 = x;
|
||||
let t1;
|
||||
if ($[2] !== t0) {
|
||||
t1 = <div x={t0} />;
|
||||
$[2] = t0;
|
||||
$[3] = t1;
|
||||
let t0;
|
||||
if ($[2] !== x) {
|
||||
t0 = <div x={x} />;
|
||||
$[2] = x;
|
||||
$[3] = t0;
|
||||
} else {
|
||||
t1 = $[3];
|
||||
t0 = $[3];
|
||||
}
|
||||
return t1;
|
||||
return t0;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import {useCallback} from 'react';
|
||||
import {Stringify} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* TODO: we're currently bailing out because `contextVar` is a context variable
|
||||
* and not recorded into the PropagateScopeDeps LoadLocal / PropertyLoad
|
||||
* sidemap. Previously, we were able to avoid this as `BuildHIR` hoisted
|
||||
* `LoadContext` and `PropertyLoad` instructions into the outer function, which
|
||||
* we took as eligible dependencies.
|
||||
*
|
||||
* One solution is to simply record `LoadContext` identifiers into the
|
||||
* temporaries sidemap when the instruction occurs *after* the context
|
||||
* variable's mutable range.
|
||||
*/
|
||||
function Foo(props) {
|
||||
let contextVar;
|
||||
if (props.cond) {
|
||||
contextVar = {val: 2};
|
||||
} else {
|
||||
contextVar = {};
|
||||
}
|
||||
|
||||
const cb = useCallback(() => [contextVar.val], [contextVar.val]);
|
||||
|
||||
return <Stringify cb={cb} shouldInvokeFns={true} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{cond: true}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
22 | }
|
||||
23 |
|
||||
> 24 | const cb = useCallback(() => [contextVar.val], [contextVar.val]);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (24:24)
|
||||
25 |
|
||||
26 | return <Stringify cb={cb} shouldInvokeFns={true} />;
|
||||
27 | }
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import {useCallback} from 'react';
|
||||
import {Stringify} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* TODO: we're currently bailing out because `contextVar` is a context variable
|
||||
* and not recorded into the PropagateScopeDeps LoadLocal / PropertyLoad
|
||||
* sidemap. Previously, we were able to avoid this as `BuildHIR` hoisted
|
||||
* `LoadContext` and `PropertyLoad` instructions into the outer function, which
|
||||
* we took as eligible dependencies.
|
||||
*
|
||||
* One solution is to simply record `LoadContext` identifiers into the
|
||||
* temporaries sidemap when the instruction occurs *after* the context
|
||||
* variable's mutable range.
|
||||
*/
|
||||
function Foo(props) {
|
||||
let contextVar;
|
||||
if (props.cond) {
|
||||
contextVar = {val: 2};
|
||||
} else {
|
||||
contextVar = {};
|
||||
}
|
||||
|
||||
const cb = useCallback(() => [contextVar.val], [contextVar.val]);
|
||||
|
||||
return <Stringify cb={cb} shouldInvokeFns={true} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{cond: true}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees
|
||||
import { useCallback } from "react";
|
||||
import { Stringify } from "shared-runtime";
|
||||
|
||||
/**
|
||||
* TODO: we're currently bailing out because `contextVar` is a context variable
|
||||
* and not recorded into the PropagateScopeDeps LoadLocal / PropertyLoad
|
||||
* sidemap. Previously, we were able to avoid this as `BuildHIR` hoisted
|
||||
* `LoadContext` and `PropertyLoad` instructions into the outer function, which
|
||||
* we took as eligible dependencies.
|
||||
*
|
||||
* One solution is to simply record `LoadContext` identifiers into the
|
||||
* temporaries sidemap when the instruction occurs *after* the context
|
||||
* variable's mutable range.
|
||||
*/
|
||||
function Foo(props) {
|
||||
const $ = _c(6);
|
||||
let contextVar;
|
||||
if ($[0] !== props.cond) {
|
||||
if (props.cond) {
|
||||
contextVar = { val: 2 };
|
||||
} else {
|
||||
contextVar = {};
|
||||
}
|
||||
$[0] = props.cond;
|
||||
$[1] = contextVar;
|
||||
} else {
|
||||
contextVar = $[1];
|
||||
}
|
||||
let t0;
|
||||
if ($[2] !== contextVar.val) {
|
||||
t0 = () => [contextVar.val];
|
||||
$[2] = contextVar.val;
|
||||
$[3] = t0;
|
||||
} else {
|
||||
t0 = $[3];
|
||||
}
|
||||
contextVar;
|
||||
const cb = t0;
|
||||
let t1;
|
||||
if ($[4] !== cb) {
|
||||
t1 = <Stringify cb={cb} shouldInvokeFns={true} />;
|
||||
$[4] = cb;
|
||||
$[5] = t1;
|
||||
} else {
|
||||
t1 = $[5];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{ cond: true }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>{"cb":{"kind":"Function","result":[2]},"shouldInvokeFns":true}</div>
|
||||
@@ -44,16 +44,15 @@ function useFoo(arr1, arr2) {
|
||||
y = $[2];
|
||||
}
|
||||
let t0;
|
||||
const t1 = y;
|
||||
let t2;
|
||||
if ($[3] !== t1) {
|
||||
t2 = { y: t1 };
|
||||
$[3] = t1;
|
||||
$[4] = t2;
|
||||
let t1;
|
||||
if ($[3] !== y) {
|
||||
t1 = { y };
|
||||
$[3] = y;
|
||||
$[4] = t1;
|
||||
} else {
|
||||
t2 = $[4];
|
||||
t1 = $[4];
|
||||
}
|
||||
t0 = t2;
|
||||
t0 = t1;
|
||||
return t0;
|
||||
}
|
||||
|
||||
|
||||
@@ -36,17 +36,15 @@ function HomeDiscoStoreItemTileRating(props) {
|
||||
} else {
|
||||
count = $[1];
|
||||
}
|
||||
|
||||
const t0 = count;
|
||||
let t1;
|
||||
if ($[2] !== t0) {
|
||||
t1 = <Text>{t0}</Text>;
|
||||
$[2] = t0;
|
||||
$[3] = t1;
|
||||
let t0;
|
||||
if ($[2] !== count) {
|
||||
t0 = <Text>{count}</Text>;
|
||||
$[2] = count;
|
||||
$[3] = t0;
|
||||
} else {
|
||||
t1 = $[3];
|
||||
t0 = $[3];
|
||||
}
|
||||
return t1;
|
||||
return t0;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @compilationMode(infer)
|
||||
import {useMemo} from 'react';
|
||||
import {ValidateMemoization} from 'shared-runtime';
|
||||
|
||||
function Component(props) {
|
||||
const x = useMemo(() => props.x(), [props.x]);
|
||||
return <ValidateMemoization inputs={[props.x]} output={x} />;
|
||||
}
|
||||
|
||||
const f = () => ['React'];
|
||||
const g = () => ['Compiler'];
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{x: () => ['React']}],
|
||||
sequentialRenders: [{x: f}, {x: g}, {x: g}, {x: f}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer)
|
||||
import { useMemo } from "react";
|
||||
import { ValidateMemoization } from "shared-runtime";
|
||||
|
||||
function Component(props) {
|
||||
const $ = _c(7);
|
||||
let t0;
|
||||
let t1;
|
||||
if ($[0] !== props.x) {
|
||||
t1 = props.x();
|
||||
$[0] = props.x;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
t0 = t1;
|
||||
const x = t0;
|
||||
let t2;
|
||||
if ($[2] !== props.x) {
|
||||
t2 = [props.x];
|
||||
$[2] = props.x;
|
||||
$[3] = t2;
|
||||
} else {
|
||||
t2 = $[3];
|
||||
}
|
||||
let t3;
|
||||
if ($[4] !== t2 || $[5] !== x) {
|
||||
t3 = <ValidateMemoization inputs={t2} output={x} />;
|
||||
$[4] = t2;
|
||||
$[5] = x;
|
||||
$[6] = t3;
|
||||
} else {
|
||||
t3 = $[6];
|
||||
}
|
||||
return t3;
|
||||
}
|
||||
|
||||
const f = () => ["React"];
|
||||
const g = () => ["Compiler"];
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ x: () => ["React"] }],
|
||||
sequentialRenders: [{ x: f }, { x: g }, { x: g }, { x: f }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>{"inputs":["[[ function params=0 ]]"],"output":["React"]}</div>
|
||||
<div>{"inputs":["[[ function params=0 ]]"],"output":["Compiler"]}</div>
|
||||
<div>{"inputs":["[[ function params=0 ]]"],"output":["Compiler"]}</div>
|
||||
<div>{"inputs":["[[ function params=0 ]]"],"output":["React"]}</div>
|
||||
@@ -0,0 +1,16 @@
|
||||
// @compilationMode(infer)
|
||||
import {useMemo} from 'react';
|
||||
import {ValidateMemoization} from 'shared-runtime';
|
||||
|
||||
function Component(props) {
|
||||
const x = useMemo(() => props.x(), [props.x]);
|
||||
return <ValidateMemoization inputs={[props.x]} output={x} />;
|
||||
}
|
||||
|
||||
const f = () => ['React'];
|
||||
const g = () => ['Compiler'];
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{x: () => ['React']}],
|
||||
sequentialRenders: [{x: f}, {x: g}, {x: g}, {x: f}],
|
||||
};
|
||||
@@ -67,17 +67,15 @@ function Component(props) {
|
||||
} else {
|
||||
x = $[1];
|
||||
}
|
||||
|
||||
const t0 = x;
|
||||
let t1;
|
||||
if ($[2] !== t0) {
|
||||
t1 = [t0];
|
||||
$[2] = t0;
|
||||
$[3] = t1;
|
||||
let t0;
|
||||
if ($[2] !== x) {
|
||||
t0 = [x];
|
||||
$[2] = x;
|
||||
$[3] = t0;
|
||||
} else {
|
||||
t1 = $[3];
|
||||
t0 = $[3];
|
||||
}
|
||||
return t1;
|
||||
return t0;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {throwErrorWithMessage, ValidateMemoization} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Context variables are local variables that (1) have at least one reassignment
|
||||
* and (2) are captured into a function expression. These have a known mutable
|
||||
* range: from first declaration / assignment to the last direct or aliased,
|
||||
* mutable reference.
|
||||
*
|
||||
* This fixture validates that forget can take granular dependencies on context
|
||||
* variables when the reference to a context var happens *after* the end of its
|
||||
* mutable range.
|
||||
*/
|
||||
function Component({cond, a}) {
|
||||
let contextVar;
|
||||
if (cond) {
|
||||
contextVar = {val: a};
|
||||
} else {
|
||||
contextVar = {};
|
||||
throwErrorWithMessage('');
|
||||
}
|
||||
const cb = {cb: () => contextVar.val * 4};
|
||||
|
||||
/**
|
||||
* manually specify input to avoid adding a `PropertyLoad` from contextVar,
|
||||
* which might affect hoistable-objects analysis.
|
||||
*/
|
||||
return (
|
||||
<ValidateMemoization
|
||||
inputs={[cond ? a : undefined]}
|
||||
output={cb}
|
||||
onlyCheckCompiled={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{cond: false, a: undefined}],
|
||||
sequentialRenders: [
|
||||
{cond: true, a: 2},
|
||||
{cond: true, a: 2},
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { throwErrorWithMessage, ValidateMemoization } from "shared-runtime";
|
||||
|
||||
/**
|
||||
* Context variables are local variables that (1) have at least one reassignment
|
||||
* and (2) are captured into a function expression. These have a known mutable
|
||||
* range: from first declaration / assignment to the last direct or aliased,
|
||||
* mutable reference.
|
||||
*
|
||||
* This fixture validates that forget can take granular dependencies on context
|
||||
* variables when the reference to a context var happens *after* the end of its
|
||||
* mutable range.
|
||||
*/
|
||||
function Component(t0) {
|
||||
const $ = _c(10);
|
||||
const { cond, a } = t0;
|
||||
let contextVar;
|
||||
if ($[0] !== a || $[1] !== cond) {
|
||||
if (cond) {
|
||||
contextVar = { val: a };
|
||||
} else {
|
||||
contextVar = {};
|
||||
throwErrorWithMessage("");
|
||||
}
|
||||
$[0] = a;
|
||||
$[1] = cond;
|
||||
$[2] = contextVar;
|
||||
} else {
|
||||
contextVar = $[2];
|
||||
}
|
||||
let t1;
|
||||
if ($[3] !== contextVar.val) {
|
||||
t1 = { cb: () => contextVar.val * 4 };
|
||||
$[3] = contextVar.val;
|
||||
$[4] = t1;
|
||||
} else {
|
||||
t1 = $[4];
|
||||
}
|
||||
const cb = t1;
|
||||
|
||||
const t2 = cond ? a : undefined;
|
||||
let t3;
|
||||
if ($[5] !== t2) {
|
||||
t3 = [t2];
|
||||
$[5] = t2;
|
||||
$[6] = t3;
|
||||
} else {
|
||||
t3 = $[6];
|
||||
}
|
||||
let t4;
|
||||
if ($[7] !== cb || $[8] !== t3) {
|
||||
t4 = (
|
||||
<ValidateMemoization inputs={t3} output={cb} onlyCheckCompiled={true} />
|
||||
);
|
||||
$[7] = cb;
|
||||
$[8] = t3;
|
||||
$[9] = t4;
|
||||
} else {
|
||||
t4 = $[9];
|
||||
}
|
||||
return t4;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ cond: false, a: undefined }],
|
||||
sequentialRenders: [
|
||||
{ cond: true, a: 2 },
|
||||
{ cond: true, a: 2 },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>{"inputs":[2],"output":{"cb":"[[ function params=0 ]]"}}</div>
|
||||
<div>{"inputs":[2],"output":{"cb":"[[ function params=0 ]]"}}</div>
|
||||
@@ -0,0 +1,43 @@
|
||||
import {throwErrorWithMessage, ValidateMemoization} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Context variables are local variables that (1) have at least one reassignment
|
||||
* and (2) are captured into a function expression. These have a known mutable
|
||||
* range: from first declaration / assignment to the last direct or aliased,
|
||||
* mutable reference.
|
||||
*
|
||||
* This fixture validates that forget can take granular dependencies on context
|
||||
* variables when the reference to a context var happens *after* the end of its
|
||||
* mutable range.
|
||||
*/
|
||||
function Component({cond, a}) {
|
||||
let contextVar;
|
||||
if (cond) {
|
||||
contextVar = {val: a};
|
||||
} else {
|
||||
contextVar = {};
|
||||
throwErrorWithMessage('');
|
||||
}
|
||||
const cb = {cb: () => contextVar.val * 4};
|
||||
|
||||
/**
|
||||
* manually specify input to avoid adding a `PropertyLoad` from contextVar,
|
||||
* which might affect hoistable-objects analysis.
|
||||
*/
|
||||
return (
|
||||
<ValidateMemoization
|
||||
inputs={[cond ? a : undefined]}
|
||||
output={cb}
|
||||
onlyCheckCompiled={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{cond: false, a: undefined}],
|
||||
sequentialRenders: [
|
||||
{cond: true, a: 2},
|
||||
{cond: true, a: 2},
|
||||
],
|
||||
};
|
||||
@@ -35,17 +35,15 @@ function HomeDiscoStoreItemTileRating(props) {
|
||||
} else {
|
||||
count = $[1];
|
||||
}
|
||||
|
||||
const t0 = count;
|
||||
let t1;
|
||||
if ($[2] !== t0) {
|
||||
t1 = <Text>{t0}</Text>;
|
||||
$[2] = t0;
|
||||
$[3] = t1;
|
||||
let t0;
|
||||
if ($[2] !== count) {
|
||||
t0 = <Text>{count}</Text>;
|
||||
$[2] = count;
|
||||
$[3] = t0;
|
||||
} else {
|
||||
t1 = $[3];
|
||||
t0 = $[3];
|
||||
}
|
||||
return t1;
|
||||
return t0;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {arrayPush} from 'shared-runtime';
|
||||
|
||||
function useFoo({a, b}) {
|
||||
const obj = {a};
|
||||
arrayPush(Object.keys(obj), b);
|
||||
return obj;
|
||||
}
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{a: 2, b: 3}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { arrayPush } from "shared-runtime";
|
||||
|
||||
function useFoo(t0) {
|
||||
const $ = _c(2);
|
||||
const { a, b } = t0;
|
||||
let t1;
|
||||
if ($[0] !== a) {
|
||||
t1 = { a };
|
||||
$[0] = a;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
const obj = t1;
|
||||
arrayPush(Object.keys(obj), b);
|
||||
return obj;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{ a: 2, b: 3 }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) {"a":2}
|
||||
@@ -0,0 +1,11 @@
|
||||
import {arrayPush} from 'shared-runtime';
|
||||
|
||||
function useFoo({a, b}) {
|
||||
const obj = {a};
|
||||
arrayPush(Object.keys(obj), b);
|
||||
return obj;
|
||||
}
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{a: 2, b: 3}],
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enableFire
|
||||
import {fire} from 'react';
|
||||
|
||||
function Component(props) {
|
||||
const foo = props => {
|
||||
console.log(props);
|
||||
};
|
||||
useEffect(() => {
|
||||
fire(foo(props));
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { useFire } from "react/compiler-runtime";
|
||||
import { c as _c } from "react/compiler-runtime"; // @enableFire
|
||||
import { fire } from "react";
|
||||
|
||||
function Component(props) {
|
||||
const $ = _c(3);
|
||||
const foo = _temp;
|
||||
const t0 = useFire(foo);
|
||||
let t1;
|
||||
if ($[0] !== props || $[1] !== t0) {
|
||||
t1 = () => {
|
||||
t0(props);
|
||||
};
|
||||
$[0] = props;
|
||||
$[1] = t0;
|
||||
$[2] = t1;
|
||||
} else {
|
||||
t1 = $[2];
|
||||
}
|
||||
useEffect(t1);
|
||||
return null;
|
||||
}
|
||||
function _temp(props_0) {
|
||||
console.log(props_0);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: exception) Fixture not implemented
|
||||
@@ -0,0 +1,13 @@
|
||||
// @enableFire
|
||||
import {fire} from 'react';
|
||||
|
||||
function Component(props) {
|
||||
const foo = props => {
|
||||
console.log(props);
|
||||
};
|
||||
useEffect(() => {
|
||||
fire(foo(props));
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enableFire
|
||||
import {fire} from 'react';
|
||||
|
||||
function Component(props) {
|
||||
const foo = props => {
|
||||
console.log(props);
|
||||
};
|
||||
useEffect(() => {
|
||||
function nested() {
|
||||
function nestedAgain() {
|
||||
function nestedThrice() {
|
||||
fire(foo(props));
|
||||
}
|
||||
nestedThrice();
|
||||
}
|
||||
nestedAgain();
|
||||
}
|
||||
nested();
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { useFire } from "react/compiler-runtime";
|
||||
import { c as _c } from "react/compiler-runtime"; // @enableFire
|
||||
import { fire } from "react";
|
||||
|
||||
function Component(props) {
|
||||
const $ = _c(3);
|
||||
const foo = _temp;
|
||||
const t0 = useFire(foo);
|
||||
let t1;
|
||||
if ($[0] !== props || $[1] !== t0) {
|
||||
t1 = () => {
|
||||
const nested = function nested() {
|
||||
const nestedAgain = function nestedAgain() {
|
||||
const nestedThrice = function nestedThrice() {
|
||||
t0(props);
|
||||
};
|
||||
|
||||
nestedThrice();
|
||||
};
|
||||
|
||||
nestedAgain();
|
||||
};
|
||||
|
||||
nested();
|
||||
};
|
||||
$[0] = props;
|
||||
$[1] = t0;
|
||||
$[2] = t1;
|
||||
} else {
|
||||
t1 = $[2];
|
||||
}
|
||||
useEffect(t1);
|
||||
return null;
|
||||
}
|
||||
function _temp(props_0) {
|
||||
console.log(props_0);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: exception) Fixture not implemented
|
||||
@@ -0,0 +1,22 @@
|
||||
// @enableFire
|
||||
import {fire} from 'react';
|
||||
|
||||
function Component(props) {
|
||||
const foo = props => {
|
||||
console.log(props);
|
||||
};
|
||||
useEffect(() => {
|
||||
function nested() {
|
||||
function nestedAgain() {
|
||||
function nestedThrice() {
|
||||
fire(foo(props));
|
||||
}
|
||||
nestedThrice();
|
||||
}
|
||||
nestedAgain();
|
||||
}
|
||||
nested();
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enableFire
|
||||
import {fire, useEffect} from 'react';
|
||||
|
||||
function Component(props) {
|
||||
const foo = props => {
|
||||
console.log(props);
|
||||
};
|
||||
|
||||
if (props.cond) {
|
||||
useEffect(() => {
|
||||
fire(foo(props));
|
||||
});
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
8 |
|
||||
9 | if (props.cond) {
|
||||
> 10 | useEffect(() => {
|
||||
| ^^^^^^^^^ InvalidReact: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (10:10)
|
||||
11 | fire(foo(props));
|
||||
12 | });
|
||||
13 | }
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
// @enableFire
|
||||
import {fire, useEffect} from 'react';
|
||||
|
||||
function Component(props) {
|
||||
const foo = props => {
|
||||
console.log(props);
|
||||
};
|
||||
|
||||
if (props.cond) {
|
||||
useEffect(() => {
|
||||
fire(foo(props));
|
||||
});
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enableFire
|
||||
import {fire} from 'react';
|
||||
|
||||
function Component(props) {
|
||||
const foo = props => {
|
||||
console.log(props);
|
||||
};
|
||||
useEffect(() => {
|
||||
function nested() {
|
||||
fire(foo(props));
|
||||
foo(props);
|
||||
}
|
||||
|
||||
nested();
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
9 | function nested() {
|
||||
10 | fire(foo(props));
|
||||
> 11 | foo(props);
|
||||
| ^^^ InvalidReact: Cannot compile `fire`. All uses of foo must be either used with a fire() call in this effect or not used with a fire() call at all. foo was used with fire() on line 10:10 in this effect (11:11)
|
||||
12 | }
|
||||
13 |
|
||||
14 | nested();
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
// @enableFire
|
||||
import {fire} from 'react';
|
||||
|
||||
function Component(props) {
|
||||
const foo = props => {
|
||||
console.log(props);
|
||||
};
|
||||
useEffect(() => {
|
||||
function nested() {
|
||||
fire(foo(props));
|
||||
foo(props);
|
||||
}
|
||||
|
||||
nested();
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enableFire
|
||||
import {fire} from 'react';
|
||||
|
||||
function Component({bar, baz}) {
|
||||
const foo = () => {
|
||||
console.log(bar, baz);
|
||||
};
|
||||
useEffect(() => {
|
||||
fire(foo(bar), baz);
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
7 | };
|
||||
8 | useEffect(() => {
|
||||
> 9 | fire(foo(bar), baz);
|
||||
| ^^^^^^^^^^^^^^^^^^^ InvalidReact: Cannot compile `fire`. fire() can only take in a single call expression as an argument but received multiple arguments (9:9)
|
||||
10 | });
|
||||
11 |
|
||||
12 | return null;
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
// @enableFire
|
||||
import {fire} from 'react';
|
||||
|
||||
function Component({bar, baz}) {
|
||||
const foo = () => {
|
||||
console.log(bar, baz);
|
||||
};
|
||||
useEffect(() => {
|
||||
fire(foo(bar), baz);
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enable
|
||||
import {fire} from 'react';
|
||||
|
||||
function Component(props) {
|
||||
const foo = props => {
|
||||
console.log(props);
|
||||
};
|
||||
useEffect(() => {
|
||||
useEffect(() => {
|
||||
function nested() {
|
||||
fire(foo(props));
|
||||
}
|
||||
|
||||
nested();
|
||||
});
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
7 | };
|
||||
8 | useEffect(() => {
|
||||
> 9 | useEffect(() => {
|
||||
| ^^^^^^^^^ InvalidReact: Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning). Cannot call useEffect within a function component (9:9)
|
||||
10 | function nested() {
|
||||
11 | fire(foo(props));
|
||||
12 | }
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
// @enable
|
||||
import {fire} from 'react';
|
||||
|
||||
function Component(props) {
|
||||
const foo = props => {
|
||||
console.log(props);
|
||||
};
|
||||
useEffect(() => {
|
||||
useEffect(() => {
|
||||
function nested() {
|
||||
fire(foo(props));
|
||||
}
|
||||
|
||||
nested();
|
||||
});
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enableFire
|
||||
import {fire} from 'react';
|
||||
|
||||
function Component(props) {
|
||||
const foo = () => {
|
||||
console.log(props);
|
||||
};
|
||||
useEffect(() => {
|
||||
fire(props);
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
7 | };
|
||||
8 | useEffect(() => {
|
||||
> 9 | fire(props);
|
||||
| ^^^^^^^^^^^ InvalidReact: Cannot compile `fire`. `fire()` can only receive a function call such as `fire(fn(a,b)). Method calls and other expressions are not allowed (9:9)
|
||||
10 | });
|
||||
11 |
|
||||
12 | return null;
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
// @enableFire
|
||||
import {fire} from 'react';
|
||||
|
||||
function Component(props) {
|
||||
const foo = () => {
|
||||
console.log(props);
|
||||
};
|
||||
useEffect(() => {
|
||||
fire(props);
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enableFire
|
||||
import {fire, useCallback} from 'react';
|
||||
|
||||
function Component({props, bar}) {
|
||||
const foo = () => {
|
||||
console.log(props);
|
||||
};
|
||||
fire(foo(props));
|
||||
|
||||
useCallback(() => {
|
||||
fire(foo(props));
|
||||
}, [foo, props]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
6 | console.log(props);
|
||||
7 | };
|
||||
> 8 | fire(foo(props));
|
||||
| ^^^^ Invariant: Cannot compile `fire`. Cannot use `fire` outside of a useEffect function (8:8)
|
||||
|
||||
Invariant: Cannot compile `fire`. Cannot use `fire` outside of a useEffect function (11:11)
|
||||
9 |
|
||||
10 | useCallback(() => {
|
||||
11 | fire(foo(props));
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
// @enableFire
|
||||
import {fire, useCallback} from 'react';
|
||||
|
||||
function Component({props, bar}) {
|
||||
const foo = () => {
|
||||
console.log(props);
|
||||
};
|
||||
fire(foo(props));
|
||||
|
||||
useCallback(() => {
|
||||
fire(foo(props));
|
||||
}, [foo, props]);
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enableFire
|
||||
import {fire} from 'react';
|
||||
|
||||
function Component(props) {
|
||||
const foo = props => {
|
||||
console.log(props);
|
||||
};
|
||||
|
||||
const deps = [foo, props];
|
||||
|
||||
useEffect(() => {
|
||||
fire(foo(props));
|
||||
}, deps);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
11 | useEffect(() => {
|
||||
12 | fire(foo(props));
|
||||
> 13 | }, deps);
|
||||
| ^^^^ Invariant: Cannot compile `fire`. You must use an array literal for an effect dependency array when that effect uses `fire()` (13:13)
|
||||
14 |
|
||||
15 | return null;
|
||||
16 | }
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
// @enableFire
|
||||
import {fire} from 'react';
|
||||
|
||||
function Component(props) {
|
||||
const foo = props => {
|
||||
console.log(props);
|
||||
};
|
||||
|
||||
const deps = [foo, props];
|
||||
|
||||
useEffect(() => {
|
||||
fire(foo(props));
|
||||
}, deps);
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enableFire
|
||||
import {fire} from 'react';
|
||||
|
||||
function Component(props) {
|
||||
const foo = props => {
|
||||
console.log(props);
|
||||
};
|
||||
|
||||
const deps = [foo, props];
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
fire(foo(props));
|
||||
},
|
||||
...deps
|
||||
);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
13 | fire(foo(props));
|
||||
14 | },
|
||||
> 15 | ...deps
|
||||
| ^^^^ Invariant: Cannot compile `fire`. You must use an array literal for an effect dependency array when that effect uses `fire()` (15:15)
|
||||
16 | );
|
||||
17 |
|
||||
18 | return null;
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
// @enableFire
|
||||
import {fire} from 'react';
|
||||
|
||||
function Component(props) {
|
||||
const foo = props => {
|
||||
console.log(props);
|
||||
};
|
||||
|
||||
const deps = [foo, props];
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
fire(foo(props));
|
||||
},
|
||||
...deps
|
||||
);
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enableFire
|
||||
import {fire} from 'react';
|
||||
|
||||
function Component(props) {
|
||||
const foo = () => {
|
||||
console.log(props);
|
||||
};
|
||||
useEffect(() => {
|
||||
fire(...foo);
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
7 | };
|
||||
8 | useEffect(() => {
|
||||
> 9 | fire(...foo);
|
||||
| ^^^^^^^^^^^^ InvalidReact: Cannot compile `fire`. fire() can only take in a single call expression as an argument but received a spread argument (9:9)
|
||||
10 | });
|
||||
11 |
|
||||
12 | return null;
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
// @enableFire
|
||||
import {fire} from 'react';
|
||||
|
||||
function Component(props) {
|
||||
const foo = () => {
|
||||
console.log(props);
|
||||
};
|
||||
useEffect(() => {
|
||||
fire(...foo);
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user