Update base for Update on "[compiler][ez] Rename disableMemoizationForDebugging to just disableMemoization"

Summary: We don't really need to make positive claims about what a particular mode is for in the name

[ghstack-poisoned]
This commit is contained in:
Mike Vitousek
2024-07-18 09:18:10 -07:00
295 changed files with 7611 additions and 4075 deletions
+1 -197
View File
@@ -7,32 +7,6 @@ aliases:
- &environment
TZ: /usr/share/zoneinfo/America/Los_Angeles
- &restore_yarn_cache_fixtures_dom
restore_cache:
name: Restore yarn cache for fixtures/dom
keys:
- v2-yarn_cache-{{ arch }}-{{ checksum "yarn.lock" }}-fixtures/dom
- &yarn_install_fixtures_dom
run:
name: Install dependencies in fixtures/dom
working_directory: fixtures/dom
command: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
- &yarn_install_fixtures_dom_retry
run:
name: Install dependencies in fixtures/dom (retry)
when: on_fail
working_directory: fixtures/dom
command: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
- &save_yarn_cache_fixtures_dom
save_cache:
name: Save yarn cache for fixtures/dom
key: v2-yarn_cache-{{ arch }}-{{ checksum "yarn.lock" }}-fixtures/dom
paths:
- ~/.cache/yarn
- &TEST_PARALLELISM 20
- &attach_workspace
@@ -97,7 +71,7 @@ jobs:
steps:
- checkout
- setup_node_modules
- run: yarn build
- run: yarn build --ci=circleci
- persist_to_workspace:
root: .
paths:
@@ -222,52 +196,6 @@ jobs:
RELEASE_CHANNEL: experimental
command: ./scripts/circleci/run_devtools_e2e_tests.js
run_fixtures_flight_tests:
docker: *docker
environment: *environment
steps:
- checkout
- attach_workspace:
at: .
# Fixture copies some built packages from the workroot after install.
# That means dependencies of the built packages are not installed.
# We need to install dependencies of the workroot to fulfill all dependency constraints
- setup_node_modules
- restore_cache:
name: Restore yarn cache of fixture
keys:
- v2-yarn_cache_fixtures_flight-{{ arch }}-{{ checksum "yarn.lock" }}
- run:
name: Install fixture dependencies
working_directory: fixtures/flight
command: |
yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
if [ $? -ne 0 ]; then
yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
fi
- save_cache:
name: Save yarn cache of fixture
key: v2-yarn_cache_fixtures_flight-{{ arch }}-{{ checksum "yarn.lock" }}
paths:
- ~/.cache/yarn
- run:
working_directory: fixtures/flight
name: Playwright install deps
command: |
npx playwright install
sudo npx playwright install-deps
- run:
name: Run tests
working_directory: fixtures/flight
command: yarn test
environment:
# Otherwise the webserver is a blackbox
DEBUG: pw:webserver
- store_artifacts:
path: fixtures/flight/playwright-report
- store_artifacts:
path: fixtures/flight/test-results
run_devtools_tests_for_versions:
docker: *docker
environment: *environment
@@ -311,75 +239,6 @@ jobs:
- store_artifacts:
path: ./tmp/screenshots
yarn_lint_build:
docker: *docker
environment: *environment
steps:
- checkout
- attach_workspace:
at: .
- setup_node_modules
- run: yarn lint-build
yarn_check_release_dependencies:
docker: *docker
environment: *environment
steps:
- checkout
- attach_workspace:
at: .
- setup_node_modules
- run: yarn check-release-dependencies
check_error_codes:
docker: *docker
environment: *environment
steps:
- checkout
- attach_workspace: *attach_workspace
- setup_node_modules
- run:
name: Search build artifacts for unminified errors
command: |
yarn extract-errors
git diff --quiet || (echo "Found unminified errors. Either update the error codes map or disable error minification for the affected build, if appropriate." && false)
yarn_test_build:
docker: *docker
environment: *environment
parallelism: *TEST_PARALLELISM
parameters:
args:
type: string
steps:
- checkout
- attach_workspace:
at: .
- setup_node_modules
- run: yarn test --build <<parameters.args>> --ci=circleci
RELEASE_CHANNEL_stable_yarn_test_dom_fixtures:
docker: *docker
environment: *environment
steps:
- checkout
- attach_workspace:
at: .
- setup_node_modules
- *restore_yarn_cache_fixtures_dom
- *yarn_install_fixtures_dom
- *yarn_install_fixtures_dom_retry
- *save_yarn_cache_fixtures_dom
- run:
name: Run DOM fixture tests
environment:
RELEASE_CHANNEL: stable
working_directory: fixtures/dom
command: |
yarn predev
yarn test --maxWorkers=2
publish_prerelease:
parameters:
commit_sha:
@@ -421,40 +280,6 @@ workflows:
requires:
- scrape_warning_messages
- yarn_build
- yarn_test_build:
requires:
- yarn_build
matrix:
parameters:
args:
# Intentionally passing these as strings instead of creating a
# separate parameter per CLI argument, since it's easier to
# control/see which combinations we want to run.
- "-r=stable --env=development"
- "-r=stable --env=production"
- "-r=experimental --env=development"
- "-r=experimental --env=production"
# Dev Tools
- "--project=devtools -r=experimental"
# TODO: Update test config to support www build tests
# - "-r=www-classic --env=development --variant=false"
# - "-r=www-classic --env=production --variant=false"
# - "-r=www-classic --env=development --variant=true"
# - "-r=www-classic --env=production --variant=true"
# - "-r=www-modern --env=development --variant=false"
# - "-r=www-modern --env=production --variant=false"
# - "-r=www-modern --env=development --variant=true"
# - "-r=www-modern --env=production --variant=true"
# TODO: Update test config to support xplat build tests
# - "-r=xplat --env=development --variant=false"
# - "-r=xplat --env=development --variant=true"
# - "-r=xplat --env=production --variant=false"
# - "-r=xplat --env=production --variant=true"
# TODO: Test more persistent configurations?
- download_base_build_for_sizebot:
filters:
branches:
@@ -469,27 +294,6 @@ workflows:
requires:
- download_base_build_for_sizebot
- yarn_build
- yarn_lint_build:
requires:
- yarn_build
- yarn_check_release_dependencies:
requires:
- yarn_build
- check_error_codes:
requires:
- yarn_build
- RELEASE_CHANNEL_stable_yarn_test_dom_fixtures:
requires:
- yarn_build
- build_devtools_and_process_artifacts:
requires:
- yarn_build
- run_devtools_e2e_tests:
requires:
- build_devtools_and_process_artifacts
- run_fixtures_flight_tests:
requires:
- yarn_build
devtools_regression_tests:
unless: << pipeline.parameters.prerelease_commit_sha >>
+1
View File
@@ -490,6 +490,7 @@ module.exports = {
'packages/react-devtools-extensions/**/*.js',
'packages/react-devtools-shared/src/hook.js',
'packages/react-devtools-shared/src/backend/console.js',
'packages/react-devtools-shared/src/backend/DevToolsComponentStackFrame.js',
],
globals: {
__IS_CHROME__: 'readonly',
+6 -3
View File
@@ -5,9 +5,12 @@ on:
branches: [main]
pull_request:
paths:
- "compiler/**"
- compiler/**
- .github/workflows/compiler-playground.yml
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
defaults:
run:
working-directory: compiler
@@ -20,8 +23,8 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18.x
cache: "yarn"
node-version: 18.20.1
cache: yarn
cache-dependency-path: compiler/yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
+1
View File
@@ -18,6 +18,7 @@ on:
env:
CARGO_TERM_COLOR: always
RUSTFLAGS: -Dwarnings
TZ: /usr/share/zoneinfo/America/Los_Angeles
defaults:
run:
+10 -7
View File
@@ -5,9 +5,12 @@ on:
branches: [main]
pull_request:
paths:
- "compiler/**"
- compiler/**
- .github/workflows/compiler-typescript.yml
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
defaults:
run:
working-directory: compiler
@@ -31,8 +34,8 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18.x
cache: "yarn"
node-version: 18.20.1
cache: yarn
cache-dependency-path: compiler/yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
@@ -50,8 +53,8 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18.x
cache: "yarn"
node-version: 18.20.1
cache: yarn
cache-dependency-path: compiler/yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
@@ -74,8 +77,8 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18.x
cache: "yarn"
node-version: 18.20.1
cache: yarn
cache-dependency-path: compiler/yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
@@ -5,6 +5,9 @@ on:
issue_comment:
types: [created, edited]
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
jobs:
check-repro:
runs-on: ubuntu-latest
@@ -0,0 +1,517 @@
name: (Runtime) Build and Test
on:
push:
branches: [main]
pull_request:
paths-ignore:
- compiler/**
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
jobs:
# ----- FLOW -----
discover_flow_inline_configs:
name: Discover flow inline configs
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.result }}
steps:
- uses: actions/checkout@v4
- uses: actions/github-script@v7
id: set-matrix
with:
script: |
const inlinedHostConfigs = require('./scripts/shared/inlinedHostConfigs.js');
return inlinedHostConfigs.map(config => config.shortName);
flow:
name: Flow check ${{ matrix.flow_inline_config_shortname }}
needs: discover_flow_inline_configs
runs-on: ubuntu-latest
continue-on-error: true
strategy:
matrix:
flow_inline_config_shortname: ${{ fromJSON(needs.discover_flow_inline_configs.outputs.matrix) }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18.20.1
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
- run: node ./scripts/tasks/flow-ci ${{ matrix.flow_inline_config_shortname }}
# ----- FIZZ -----
check_generated_fizz_runtime:
name: Confirm generated inline Fizz runtime is up to date
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18.20.1
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
- run: |
yarn generate-inline-fizz-runtime
git diff --quiet || (echo "There was a change to the Fizz runtime. Run `yarn generate-inline-fizz-runtime` and check in the result." && false)
# ----- FEATURE FLAGS -----
flags:
name: Check flags
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18.20.1
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
- run: yarn flags
# ----- TESTS -----
test:
name: yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }})
runs-on: ubuntu-latest
strategy:
matrix:
params:
- "-r=stable --env=development"
- "-r=stable --env=production"
- "-r=experimental --env=development"
- "-r=experimental --env=production"
- "-r=www-classic --env=development --variant=false"
- "-r=www-classic --env=production --variant=false"
- "-r=www-classic --env=development --variant=true"
- "-r=www-classic --env=production --variant=true"
- "-r=www-modern --env=development --variant=false"
- "-r=www-modern --env=production --variant=false"
- "-r=www-modern --env=development --variant=true"
- "-r=www-modern --env=production --variant=true"
- "-r=xplat --env=development --variant=false"
- "-r=xplat --env=development --variant=true"
- "-r=xplat --env=production --variant=false"
- "-r=xplat --env=production --variant=true"
# TODO: Test more persistent configurations?
- "-r=stable --env=development --persistent"
- "-r=experimental --env=development --persistent"
shard:
- 1/5
- 2/5
- 3/5
- 4/5
- 5/5
continue-on-error: true
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18.20.1
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
- run: yarn test ${{ matrix.params }} --ci=github --shard=${{ matrix.shard }}
# ----- BUILD -----
build_and_lint:
name: yarn build and lint
runs-on: ubuntu-latest
strategy:
matrix:
# yml is dumb. update the --total arg to yarn build if you change the number of workers
worker_id: [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19]
release_channel: [stable, experimental]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18.20.1
cache: yarn
cache-dependency-path: yarn.lock
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 11.0.22
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
- run: yarn build --index=${{ matrix.worker_id }} --total=20 --r=${{ matrix.release_channel }} --ci=github
env:
CI: github
RELEASE_CHANNEL: ${{ matrix.release_channel }}
NODE_INDEX: ${{ matrix.worker_id }}
- name: Lint build
run: yarn lint-build
- name: Display structure of build
run: ls -R build
- name: Archive build
uses: actions/upload-artifact@v4
with:
name: build_${{ matrix.worker_id }}_${{ matrix.release_channel }}
path: |
build
test_build:
name: yarn test-build
needs: build_and_lint
strategy:
matrix:
test_params: [
# Intentionally passing these as strings instead of creating a
# separate parameter per CLI argument, since it's easier to
# control/see which combinations we want to run.
-r=stable --env=development,
-r=stable --env=production,
-r=experimental --env=development,
-r=experimental --env=production,
# Dev Tools
--project=devtools -r=experimental,
# TODO: Update test config to support www build tests
# - "-r=www-classic --env=development --variant=false"
# - "-r=www-classic --env=production --variant=false"
# - "-r=www-classic --env=development --variant=true"
# - "-r=www-classic --env=production --variant=true"
# - "-r=www-modern --env=development --variant=false"
# - "-r=www-modern --env=production --variant=false"
# - "-r=www-modern --env=development --variant=true"
# - "-r=www-modern --env=production --variant=true"
# TODO: Update test config to support xplat build tests
# - "-r=xplat --env=development --variant=false"
# - "-r=xplat --env=development --variant=true"
# - "-r=xplat --env=production --variant=false"
# - "-r=xplat --env=production --variant=true"
# TODO: Test more persistent configurations?
]
shard:
- 1/3
- 2/3
- 3/3
continue-on-error: true
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18.20.1
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
- name: Restore archived build
uses: actions/download-artifact@v4
with:
path: build
merge-multiple: true
- name: Display structure of build
run: ls -R build
- run: yarn test --build ${{ matrix.test_params }} --shard=${{ matrix.shard }} --ci=github
process_artifacts_combined:
name: Process artifacts combined
needs: build_and_lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18.20.1
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
- name: Restore archived build
uses: actions/download-artifact@v4
with:
path: build
merge-multiple: true
- name: Display structure of build
run: ls -R build
- run: echo ${{ github.sha }} >> build/COMMIT_SHA
- name: Scrape warning messages
run: |
mkdir -p ./build/__test_utils__
node ./scripts/print-warnings/print-warnings.js > build/__test_utils__/ReactAllWarnings.js
# Compress build directory into a single tarball for easy download
- run: tar -zcvf ./build.tgz ./build
# TODO: Migrate scripts to use `build` directory instead of `build2`
- run: cp ./build.tgz ./build2.tgz
- name: Archive build artifacts
uses: actions/upload-artifact@v4
with:
name: combined_artifacts_${{ github.sha }}
path: |
./build.tgz
./build2.tgz
check_error_codes:
name: Search build artifacts for unminified errors
needs: build_and_lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18.20.1
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
- name: Restore archived build
uses: actions/download-artifact@v4
with:
path: build
merge-multiple: true
- name: Display structure of build
run: ls -R build
- name: Search build artifacts for unminified errors
run: |
yarn extract-errors
git diff --quiet || (echo "Found unminified errors. Either update the error codes map or disable error minification for the affected build, if appropriate." && false)
check_release_dependencies:
name: Check release dependencies
needs: build_and_lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18.20.1
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
- name: Restore archived build
uses: actions/download-artifact@v4
with:
path: build
merge-multiple: true
- name: Display structure of build
run: ls -R build
- run: yarn check-release-dependencies
RELEASE_CHANNEL_stable_yarn_test_dom_fixtures:
name: Check fixtures DOM (stable)
needs: build_and_lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18.20.1
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: "**/node_modules"
key: v2-yarn_cache_fixtures_dom-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
- run: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
working-directory: fixtures/dom
- name: Restore archived build
uses: actions/download-artifact@v4
with:
path: build
merge-multiple: true
- name: Display structure of build
run: ls -R build
- name: Run DOM fixture tests
run: |
yarn predev
yarn test
working-directory: fixtures/dom
env:
RELEASE_CHANNEL: stable
# ----- FLIGHT -----
run_fixtures_flight_tests:
name: Run fixtures Flight tests
needs: build_and_lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18.20.1
cache: yarn
cache-dependency-path: yarn.lock
# Fixture copies some built packages from the workroot after install.
# That means dependencies of the built packages are not installed.
# We need to install dependencies of the workroot to fulfill all dependency constraints
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: "**/node_modules"
key: v2-yarn_cache_fixtures_flight-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
- name: Restore archived build
uses: actions/download-artifact@v4
with:
path: build
merge-multiple: true
- name: Display structure of build
run: ls -R build
- name: Install fixture dependencies
working-directory: fixtures/flight
run: |
yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
if [ $? -ne 0 ]; then
yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
fi
- name: Playwright install deps
working-directory: fixtures/flight
run: |
npx playwright install
sudo npx playwright install-deps
- name: Run tests
working-directory: fixtures/flight
run: yarn test
env:
# Otherwise the webserver is a blackbox
DEBUG: pw:webserver
- name: Archive Flight fixture artifacts
uses: actions/upload-artifact@v4
with:
name: flight-playwright-report
path: fixtures/flight/playwright-report
- name: Archive Flight fixture artifacts
uses: actions/upload-artifact@v4
with:
name: flight-test-results
path: fixtures/flight/test-results
# ----- DEVTOOLS -----
build_devtools_and_process_artifacts:
name: Build DevTools and process artifacts
needs: build_and_lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18.20.1
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
- name: Restore archived build
uses: actions/download-artifact@v4
with:
path: build
merge-multiple: true
- run: ./scripts/circleci/pack_and_store_devtools_artifacts.sh
env:
RELEASE_CHANNEL: experimental
- name: Display structure of build
run: ls -R build
- name: Archive devtools build
uses: actions/upload-artifact@v4
with:
name: react-devtools
path: build/devtools.tgz
# Simplifies getting the extension for local testing
- name: Archive chrome extension
uses: actions/upload-artifact@v4
with:
name: react-devtools-chrome-extension
path: build/devtools/chrome-extension.zip
- name: Archive firefox extension
uses: actions/upload-artifact@v4
with:
name: react-devtools-firefox-extension
path: build/devtools/firefox-extension.zip
run_devtools_e2e_tests:
name: Run DevTools e2e tests
needs: build_devtools_and_process_artifacts
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18.20.1
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
- name: Restore archived build
uses: actions/download-artifact@v4
with:
path: build
merge-multiple: true
- run: |
npx playwright install
sudo npx playwright install-deps
- run: ./scripts/circleci/run_devtools_e2e_tests.js
env:
RELEASE_CHANNEL: experimental
@@ -4,6 +4,9 @@ on:
push:
branches: [main, meta-www, meta-fbsource]
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
jobs:
download_artifacts:
runs-on: ubuntu-latest
-30
View File
@@ -1,30 +0,0 @@
name: (Runtime) Fizz
on:
push:
branches: [main]
pull_request:
paths-ignore:
- 'compiler/**'
jobs:
check_generated_fizz_runtime:
name: Confirm generated inline Fizz runtime is up to date
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18.x
cache: "yarn"
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
- run: |
yarn generate-inline-fizz-runtime
git diff --quiet || (echo "There was a change to the Fizz runtime. Run `yarn generate-inline-fizz-runtime` and check in the result." && false)
-28
View File
@@ -1,28 +0,0 @@
name: (Runtime) Flags
on:
push:
branches: [main]
pull_request:
paths-ignore:
- 'compiler/**'
jobs:
flags:
name: Check flags
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18.x
cache: "yarn"
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
- run: yarn flags
-47
View File
@@ -1,47 +0,0 @@
name: (Runtime) Flow
on:
push:
branches: [main]
pull_request:
paths-ignore:
- 'compiler/**'
jobs:
discover_flow_inline_configs:
name: Discover flow inline configs
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.result }}
steps:
- uses: actions/checkout@v4
- uses: actions/github-script@v7
id: set-matrix
with:
script: |
const inlinedHostConfigs = require('./scripts/shared/inlinedHostConfigs.js');
return inlinedHostConfigs.map(config => config.shortName);
flow:
name: Flow check ${{ matrix.flow_inline_config_shortname }}
needs: discover_flow_inline_configs
runs-on: ubuntu-latest
continue-on-error: true
strategy:
matrix:
flow_inline_config_shortname: ${{ fromJSON(needs.discover_flow_inline_configs.outputs.matrix) }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18.x
cache: "yarn"
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
- run: node ./scripts/tasks/flow-ci ${{ matrix.flow_inline_config_shortname }}
+4
View File
@@ -9,6 +9,10 @@ on:
inputs:
prerelease_commit_sha:
required: false
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
jobs:
test_fuzz:
if: inputs.prerelease_commit_sha == ''
-85
View File
@@ -1,85 +0,0 @@
name: (Runtime) Test
on:
push:
branches: [main]
pull_request:
paths-ignore:
- 'compiler/**'
env:
# Number of workers (one per shard) to spawn
SHARD_COUNT: 5
jobs:
# Define the various test parameters and parallelism for this workflow
build_test_params:
name: Build test params
runs-on: ubuntu-latest
outputs:
params: ${{ steps.define-params.outputs.result }}
shard_id: ${{ steps.define-shards.outputs.result }}
steps:
- uses: actions/github-script@v7
id: define-shards
with:
script: |
function range(from, to) {
const arr = [];
for (let n = from; n <= to; n++) {
arr.push(n);
}
return arr;
}
return range(1, process.env.SHARD_COUNT);
- uses: actions/github-script@v7
id: define-params
with:
script: |
return [
"-r=stable --env=development",
"-r=stable --env=production",
"-r=experimental --env=development",
"-r=experimental --env=production",
"-r=www-classic --env=development --variant=false",
"-r=www-classic --env=production --variant=false",
"-r=www-classic --env=development --variant=true",
"-r=www-classic --env=production --variant=true",
"-r=www-modern --env=development --variant=false",
"-r=www-modern --env=production --variant=false",
"-r=www-modern --env=development --variant=true",
"-r=www-modern --env=production --variant=true",
"-r=xplat --env=development --variant=false",
"-r=xplat --env=development --variant=true",
"-r=xplat --env=production --variant=false",
"-r=xplat --env=production --variant=true",
// TODO: Test more persistent configurations?
"-r=stable --env=development --persistent",
"-r=experimental --env=development --persistent"
];
# Spawn a job for each shard for a given set of test params
test:
name: yarn test ${{ matrix.params }} (Shard ${{ matrix.shard_id }})
runs-on: ubuntu-latest
needs: build_test_params
strategy:
matrix:
params: ${{ fromJSON(needs.build_test_params.outputs.params) }}
shard_id: ${{ fromJSON(needs.build_test_params.outputs.shard_id) }}
continue-on-error: true
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18.x
cache: "yarn"
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
- run: yarn test ${{ matrix.params }} --ci=github --shard=${{ matrix.shard_id }}/${{ env.SHARD_COUNT }}
+11 -8
View File
@@ -5,6 +5,9 @@ on:
branches: [main]
pull_request:
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
jobs:
prettier:
name: Run prettier
@@ -13,8 +16,8 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18.x
cache: "yarn"
node-version: 18.20.1
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
@@ -31,8 +34,8 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18.x
cache: "yarn"
node-version: 18.20.1
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
@@ -49,8 +52,8 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18.x
cache: "yarn"
node-version: 18.20.1
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
@@ -67,8 +70,8 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18.x
cache: "yarn"
node-version: 18.20.1
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
+3
View File
@@ -5,6 +5,9 @@ on:
# Run hourly
- cron: '0 * * * *'
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
jobs:
stale:
runs-on: ubuntu-latest
+1 -1
View File
@@ -1 +1 @@
v18.20.0
v18.20.1
+3 -3
View File
@@ -1,4 +1,4 @@
# [React](https://react.dev/) &middot; [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/facebook/react/blob/main/LICENSE) [![npm version](https://img.shields.io/npm/v/react.svg?style=flat)](https://www.npmjs.com/package/react) [![CircleCI Status](https://circleci.com/gh/facebook/react.svg?style=shield)](https://circleci.com/gh/facebook/react) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://legacy.reactjs.org/docs/how-to-contribute.html#your-first-pull-request)
# [React](https://react.dev/) &middot; [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/facebook/react/blob/main/LICENSE) [![npm version](https://img.shields.io/npm/v/react.svg?style=flat)](https://www.npmjs.com/package/react) [![CircleCI Status](https://circleci.com/gh/facebook/react.svg?style=shield)](https://circleci.com/gh/facebook/react) [![(Runtime) Build and Test](https://github.com/facebook/react/actions/workflows/runtime_build_and_test.yml/badge.svg)](https://github.com/facebook/react/actions/workflows/runtime_build_and_test.yml) [![(Compiler) TypeScript](https://github.com/facebook/react/actions/workflows/compiler_typescript.yml/badge.svg?branch=main)](https://github.com/facebook/react/actions/workflows/compiler_typescript.yml) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://legacy.reactjs.org/docs/how-to-contribute.html#your-first-pull-request)
React is a JavaScript library for building user interfaces.
@@ -18,7 +18,7 @@ React has been designed for gradual adoption from the start, and **you can use a
## Documentation
You can find the React documentation [on the website](https://react.dev/).
You can find the React documentation [on the website](https://react.dev/).
Check out the [Getting Started](https://react.dev/learn) page for a quick overview.
@@ -55,7 +55,7 @@ root.render(<HelloMessage name="Taylor" />);
This example will render "Hello Taylor" into a container on the page.
You'll notice that we used an HTML-like syntax; [we call it JSX](https://react.dev/learn#writing-markup-with-jsx). JSX is not required to use React, but it makes code more readable, and writing it feels like writing HTML.
You'll notice that we used an HTML-like syntax; [we call it JSX](https://react.dev/learn#writing-markup-with-jsx). JSX is not required to use React, but it makes code more readable, and writing it feels like writing HTML.
## Contributing
@@ -42,6 +42,8 @@ import {
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";
function parseInput(input: string, language: "flow" | "typescript") {
// Extract the first line to quickly check for custom test directives
@@ -242,7 +244,7 @@ function compile(source: string): [CompilerOutput, "flow" | "typescript"] {
kind: "hir",
fnName,
name: result.name,
value: printHIR(result.value.body),
value: printFunctionWithOutlined(result.value),
});
break;
}
@@ -251,7 +253,7 @@ function compile(source: string): [CompilerOutput, "flow" | "typescript"] {
kind: "reactive",
fnName,
name: result.name,
value: printReactiveFunction(result.value),
value: printReactiveFunctionWithOutlined(result.value),
});
break;
}
+2 -4
View File
@@ -12,6 +12,7 @@
},
"dependencies": {
"@babel/core": "^7.19.1",
"@babel/generator": "^7.19.1",
"@babel/parser": "^7.19.1",
"@babel/plugin-syntax-typescript": "^7.18.6",
"@babel/plugin-transform-block-scoping": "^7.18.9",
@@ -55,9 +56,6 @@
},
"resolutions": {
"./**/@babel/parser": "7.7.4",
"./**/@babel/types": "7.7.4",
"@babel/core": "7.2.0",
"@babel/traverse": "7.1.6",
"@babel/generator": "7.2.0"
"./**/@babel/types": "7.7.4"
}
}
@@ -28,6 +28,7 @@ import { basename } from "path";
const e2eTransformerCacheKey = 1;
const forgetOptions: EnvironmentConfig = validateEnvironmentConfig({
enableAssumeHooksFollowRulesOfReact: true,
enableFunctionOutlining: false,
});
const debugMode = process.env["DEBUG_FORGET_COMPILER"] != null;
@@ -97,7 +97,7 @@ import {
validateUseMemo,
} from "../Validation";
import { validateLocalsNotReassignedAfterRender } from "../Validation/ValidateLocalsNotReassignedAfterRender";
import { memoizeExistingUseMemos } from "../ReactiveScopes/MemoizeExistingUseMemos";
import { outlineFunctions } from "../Optimization/OutlineFunctions";
export type CompilerPipelineValue =
| { kind: "ast"; name: string; value: CodegenFunction }
@@ -118,6 +118,7 @@ export function* run(
): Generator<CompilerPipelineValue, CodegenFunction> {
const contextIdentifiers = findContextIdentifiers(func);
const env = new Environment(
func.scope,
fnType,
config,
contextIdentifiers,
@@ -154,7 +155,7 @@ function* runWithEnvironment(
validateContextVariableLValues(hir);
validateUseMemo(hir);
if (env.config.enablePreserveExistingManualUseMemo !== "hook") {
if (!env.preserveManualMemo()) {
dropManualMemoization(hir);
yield log({ kind: "hir", name: "DropManualMemoization", value: hir });
}
@@ -238,6 +239,11 @@ function* runWithEnvironment(
inferReactiveScopeVariables(hir);
yield log({ kind: "hir", name: "InferReactiveScopeVariables", value: hir });
if (env.config.enableFunctionOutlining) {
outlineFunctions(hir);
yield log({ kind: "hir", name: "OutlineFunctions", value: hir });
}
alignMethodCallScopes(hir);
yield log({
kind: "hir",
@@ -267,19 +273,6 @@ function* runWithEnvironment(
value: hir,
});
if (
env.config.enablePreserveExistingManualUseMemo === "scope" ||
env.config.enableChangeDetection != null ||
env.config.disableMemoizationForDebugging
) {
memoizeExistingUseMemos(hir);
yield log({
kind: "hir",
name: "MemoizeExistingUseMemos",
value: hir,
});
}
alignReactiveScopesToBlockScopesHIR(hir);
yield log({
kind: "hir",
@@ -390,12 +383,14 @@ function* runWithEnvironment(
value: reactiveFunction,
});
pruneNonReactiveDependencies(reactiveFunction);
yield log({
kind: "reactive",
name: "PruneNonReactiveDependencies",
value: reactiveFunction,
});
if (env.config.enableChangeDetection == null) {
pruneNonReactiveDependencies(reactiveFunction);
yield log({
kind: "reactive",
name: "PruneNonReactiveDependencies",
value: reactiveFunction,
});
}
pruneUnusedScopes(reactiveFunction);
yield log({
@@ -489,6 +484,9 @@ function* runWithEnvironment(
const ast = codegenFunction(reactiveFunction, uniqueIdentifiers).unwrap();
yield log({ kind: "ast", name: "Codegen", value: ast });
for (const outlined of ast.outlined) {
yield log({ kind: "ast", name: "Codegen (outlined)", value: outlined.fn });
}
/**
* This flag should be only set for unit / fixture tests to check
@@ -87,6 +87,12 @@ export type BabelFn =
| NodePath<t.ArrowFunctionExpression>;
export type CompileResult = {
/**
* Distinguishes existing functions that were compiled ('original') from
* functions which were outlined. Only original functions need to be gated
* if gating mode is enabled.
*/
kind: "original" | "outlined";
originalFn: BabelFn;
compiledFn: CodegenFunction;
};
@@ -265,6 +271,11 @@ export function compileProgram(
);
const lintError = suppressionsToCompilerError(suppressions);
let hasCriticalError = lintError != null;
const queue: Array<{
kind: "original" | "outlined";
fn: BabelFn;
fnType: ReactFunctionType;
}> = [];
const compiledFns: Array<CompileResult> = [];
const traverseFunction = (fn: BabelFn, pass: CompilerPass): void => {
@@ -281,6 +292,47 @@ export function compileProgram(
ALREADY_COMPILED.add(fn.node);
fn.skip();
queue.push({ kind: "original", fn, fnType });
};
// Main traversal to compile with Forget
program.traverse(
{
ClassDeclaration(node: NodePath<t.ClassDeclaration>) {
/*
* Don't visit functions defined inside classes, because they
* can reference `this` which is unsafe for compilation
*/
node.skip();
return;
},
ClassExpression(node: NodePath<t.ClassExpression>) {
/*
* Don't visit functions defined inside classes, because they
* can reference `this` which is unsafe for compilation
*/
node.skip();
return;
},
FunctionDeclaration: traverseFunction,
FunctionExpression: traverseFunction,
ArrowFunctionExpression: traverseFunction,
},
{
...pass,
opts: { ...pass.opts, ...pass.opts },
filename: pass.filename ?? null,
}
);
const processFn = (
fn: BabelFn,
fnType: ReactFunctionType
): null | CodegenFunction => {
if (lintError != null) {
/**
* Note that Babel does not attach comment nodes to nodes; they are dangling off of the
@@ -335,52 +387,59 @@ export function compileProgram(
} catch (err) {
hasCriticalError ||= isCriticalError(err);
handleError(err, pass, fn.node.loc ?? null);
return;
return null;
}
if (!pass.opts.noEmit && !hasCriticalError) {
compiledFns.push({ originalFn: fn, compiledFn });
return compiledFn;
}
return null;
};
// Main traversal to compile with Forget
program.traverse(
{
ClassDeclaration(node: NodePath<t.ClassDeclaration>) {
/*
* Don't visit functions defined inside classes, because they
* can reference `this` which is unsafe for compilation
*/
node.skip();
return;
},
ClassExpression(node: NodePath<t.ClassExpression>) {
/*
* Don't visit functions defined inside classes, because they
* can reference `this` which is unsafe for compilation
*/
node.skip();
return;
},
FunctionDeclaration: traverseFunction,
FunctionExpression: traverseFunction,
ArrowFunctionExpression: traverseFunction,
},
{
...pass,
opts: { ...pass.opts, ...pass.opts },
filename: pass.filename ?? null,
while (queue.length !== 0) {
const current = queue.shift()!;
const compiled = processFn(current.fn, current.fnType);
if (compiled === null) {
continue;
}
);
for (const outlined of compiled.outlined) {
CompilerError.invariant(outlined.fn.outlined.length === 0, {
reason: "Unexpected nested outlined functions",
loc: outlined.fn.loc,
});
const fn = current.fn.insertAfter(
createNewFunctionNode(current.fn, outlined.fn)
)[0]!;
fn.skip();
ALREADY_COMPILED.add(fn.node);
if (outlined.type !== null) {
CompilerError.throwTodo({
reason: `Implement support for outlining React functions (components/hooks)`,
loc: outlined.fn.loc,
});
/*
* Above should be as simple as the following, but needs testing:
* queue.push({
* kind: "outlined",
* fn,
* fnType: outlined.type,
* });
*/
}
}
compiledFns.push({
kind: current.kind,
compiledFn: compiled,
originalFn: current.fn,
});
}
if (pass.opts.gating != null) {
const error = checkFunctionReferencedBeforeDeclarationAtTopLevel(
program,
compiledFns.map(({ originalFn }) => originalFn)
compiledFns.map((result) => {
return result.originalFn;
})
);
if (error) {
handleError(error, pass, null);
@@ -455,10 +514,11 @@ export function compileProgram(
* Only insert Forget-ified functions if we have not encountered a critical
* error elsewhere in the file, regardless of bailout mode.
*/
for (const { originalFn, compiledFn } of compiledFns) {
for (const result of compiledFns) {
const { kind, originalFn, compiledFn } = result;
const transformedFn = createNewFunctionNode(originalFn, compiledFn);
if (gating != null) {
if (gating != null && kind === "original") {
insertGatedFunctionDeclaration(originalFn, transformedFn, gating);
} else {
originalFn.replaceWith(transformedFn);
@@ -129,6 +129,7 @@ export function lower(
reactive: false,
loc: param.node.loc ?? GeneratedSource,
};
promoteTemporary(place.identifier);
params.push(place);
lowerAssignment(
builder,
@@ -1332,6 +1333,7 @@ function lowerStatement(
return;
}
case "TypeAlias":
case "TSInterfaceDeclaration":
case "TSTypeAliasDeclaration": {
// We do not preserve type annotations/syntax through transformation
return;
@@ -1358,7 +1360,6 @@ function lowerStatement(
case "TSEnumDeclaration":
case "TSExportAssignment":
case "TSImportEqualsDeclaration":
case "TSInterfaceDeclaration":
case "TSModuleDeclaration":
case "TSNamespaceExportDeclaration":
case "WithStatement": {
@@ -23,14 +23,17 @@ import {
BuiltInType,
Effect,
FunctionType,
HIRFunction,
IdentifierId,
NonLocalBinding,
PolyType,
ScopeId,
Type,
ValidatedIdentifier,
ValueKind,
makeBlockId,
makeIdentifierId,
makeIdentifierName,
makeScopeId,
} from "./HIR";
import {
@@ -41,6 +44,7 @@ import {
ShapeRegistry,
addHook,
} from "./ObjectShape";
import { Scope as BabelScope } from "@babel/traverse";
export const ExternalFunctionSchema = z.object({
// Source for the imported module that exports the `importSpecifierName` functions
@@ -182,9 +186,7 @@ const EnvironmentConfigSchema = z.object({
* that the memoized values remain memoized, the compiler will simply not prune existing calls to
* useMemo/useCallback.
*/
enablePreserveExistingManualUseMemo: z
.nullable(z.enum(["hook", "scope"]))
.default(null),
enablePreserveExistingManualUseMemo: z.boolean().default(false),
// 🌲
enableForest: z.boolean().default(false),
@@ -285,6 +287,12 @@ const EnvironmentConfigSchema = z.object({
*/
enableInstructionReordering: z.boolean().default(false),
/**
* Enables function outlinining, where anonymous functions that do not close over
* local variables can be extracted into top-level helper functions.
*/
enableFunctionOutlining: z.boolean().default(true),
/*
* Enables instrumentation codegen. This emits a dev-mode only call to an
* instrumentation function, for components and hooks that Forget compiles.
@@ -479,31 +487,6 @@ export function parseConfigPragma(pragma: string): EnvironmentConfig {
continue;
}
if (
key === "enablePreserveExistingManualUseMemo" &&
(val === undefined || val === "true" || val === "scope")
) {
maybeConfig[key] = "scope";
continue;
}
if (key === "enablePreserveExistingManualUseMemo" && val === "hook") {
maybeConfig[key] = "hook";
continue;
}
if (
key === "enablePreserveExistingManualUseMemo" &&
!(val === "false" || val === "off")
) {
CompilerError.throwInvalidConfig({
reason: `Invalid setting '${val}' for 'enablePreserveExistingManualUseMemo'. Valid settings are 'hook', 'scope', or 'off'.`,
description: null,
loc: null,
suggestions: null,
});
}
if (typeof defaultConfig[key as keyof EnvironmentConfig] !== "boolean") {
// skip parsing non-boolean properties
continue;
@@ -552,6 +535,11 @@ export class Environment {
#nextIdentifer: number = 0;
#nextBlock: number = 0;
#nextScope: number = 0;
#scope: BabelScope;
#outlinedFunctions: Array<{
fn: HIRFunction;
type: ReactFunctionType | null;
}> = [];
logger: Logger | null;
filename: string | null;
code: string | null;
@@ -563,6 +551,7 @@ export class Environment {
#hoistedIdentifiers: Set<t.Identifier>;
constructor(
scope: BabelScope,
fnType: ReactFunctionType,
config: EnvironmentConfig,
contextIdentifiers: Set<t.Identifier>,
@@ -571,6 +560,7 @@ export class Environment {
code: string | null,
useMemoCacheIdentifier: string
) {
this.#scope = scope;
this.fnType = fnType;
this.config = config;
this.filename = filename;
@@ -631,6 +621,24 @@ export class Environment {
return this.#hoistedIdentifiers.has(node);
}
generateGloballyUniqueIdentifierName(
name: string | null
): ValidatedIdentifier {
const identifierNode = this.#scope.generateUidIdentifier(name ?? undefined);
return makeIdentifierName(identifierNode.name);
}
outlineFunction(fn: HIRFunction, type: ReactFunctionType | null): void {
this.#outlinedFunctions.push({ fn, type });
}
getOutlinedFunctions(): Array<{
fn: HIRFunction;
type: ReactFunctionType | null;
}> {
return this.#outlinedFunctions;
}
getGlobalDeclaration(binding: NonLocalBinding): Global | null {
if (this.config.hookPattern != null) {
const match = new RegExp(this.config.hookPattern).exec(binding.name);
@@ -766,6 +774,14 @@ export class Environment {
return DefaultMutatingHook;
}
}
preserveManualMemo(): boolean {
return (
this.config.enablePreserveExistingManualUseMemo ||
this.config.disableMemoizationForDebugging ||
this.config.enableChangeDetection != null
);
}
}
// From https://github.com/facebook/react/blob/main/packages/eslint-plugin-react-hooks/src/RulesOfHooks.js#LL18C1-L23C2
@@ -771,12 +771,7 @@ export type ManualMemoDependency = {
kind: "NamedLocal";
value: Place;
}
| {
kind: "InlinedGlobal";
value: Place;
name: string;
}
| { kind: "Global"; binding: LoadGlobal };
| { kind: "Global"; identifierName: string };
path: Array<string>;
};
@@ -1173,7 +1168,7 @@ export type NonLocalBinding =
imported: string;
}
// let, const, function, etc declared in the module but outside the current component/hook
| { kind: "ModuleLocal"; name: string }
| { kind: "ModuleLocal"; name: string; immutable: boolean }
// an unresolved binding
| { kind: "Global"; name: string };
@@ -1434,8 +1429,6 @@ export type ReactiveScope = {
merged: Set<ScopeId>;
loc: SourceLocation;
source: boolean;
};
export type ReactiveScopeDependencies = Set<ReactiveScopeDependency>;
@@ -271,9 +271,14 @@ export default class HIRBuilder {
module: importDeclaration.node.source.value,
};
} else {
const immutable =
(path.isVariableDeclaration() && path.node.kind === "const") ||
path.isClassDeclaration() ||
path.isClassExpression();
return {
kind: "ModuleLocal",
name: originalName,
immutable,
};
}
}
@@ -6,7 +6,7 @@ import {
makeInstructionId,
} from ".";
import { getPlaceScope } from "../ReactiveScopes/BuildReactiveBlocks";
import { isMutable } from "../ReactiveScopes/InferReactiveScopeVariables";
import { isMutableAtInstruction } from "../ReactiveScopes/InferReactiveScopeVariables";
import DisjointSet from "../Utils/DisjointSet";
import { getOrInsertDefault } from "../Utils/utils";
import {
@@ -254,7 +254,7 @@ function visitPlace(
* of the stack to the mutated outer scope.
*/
const placeScope = getPlaceScope(id, place);
if (placeScope != null && isMutable({ id } as any, place)) {
if (placeScope != null && isMutableAtInstruction({ id } as any, place)) {
const placeScopeIdx = activeScopes.indexOf(placeScope);
if (placeScopeIdx !== -1 && placeScopeIdx !== activeScopes.length - 1) {
joined.union([placeScope, ...activeScopes.slice(placeScopeIdx + 1)]);
@@ -41,6 +41,14 @@ export type Options = {
indent: number;
};
export function printFunctionWithOutlined(fn: HIRFunction): string {
const output = [printFunction(fn)];
for (const outlined of fn.env.getOutlinedFunctions()) {
output.push(`\nfunction ${outlined.fn.id}:\n${printHIR(outlined.fn.body)}`);
}
return output.join("\n");
}
export function printFunction(fn: HIRFunction): string {
const output = [];
let definition = "";
@@ -843,14 +851,7 @@ export function printManualMemoDependency(
): string {
let rootStr;
if (val.root.kind === "Global") {
rootStr = val.root.binding.binding.name;
} else if (val.root.kind === "InlinedGlobal") {
const nameStr = nameOnly
? val.root.value.identifier.name != null
? printName(val.root.value.identifier.name)
: String(val.root.value.identifier.id)
: printIdentifier(val.root.value.identifier);
rootStr = `G(${val.root.name}=${nameStr})`;
rootStr = val.root.identifierName;
} else {
CompilerError.invariant(val.root.value.identifier.name?.kind === "named", {
reason: "DepsValidation: expected named local variable in depslist",
@@ -229,10 +229,7 @@ export function* eachInstructionValueOperand(
case "StartMemoize": {
if (instrValue.deps != null) {
for (const dep of instrValue.deps) {
if (
dep.root.kind === "NamedLocal" ||
dep.root.kind === "InlinedGlobal"
) {
if (dep.root.kind === "NamedLocal") {
yield dep.root.value;
}
}
@@ -557,10 +554,7 @@ export function mapInstructionValueOperands(
case "StartMemoize": {
if (instrValue.deps != null) {
for (const dep of instrValue.deps) {
if (
dep.root.kind === "NamedLocal" ||
dep.root.kind === "InlinedGlobal"
) {
if (dep.root.kind === "NamedLocal") {
dep.root.value = fn(dep.root.value);
}
}
@@ -58,7 +58,7 @@ export function collectMaybeMemoDependencies(
return {
root: {
kind: "Global",
binding: value,
identifierName: value.binding.name,
},
path: [],
};
@@ -173,53 +173,24 @@ function makeManualMemoizationMarkers(
env: Environment,
depsList: Array<ManualMemoDependency> | null,
memoDecl: Place,
manualMemoId: number,
isManualUseMemoEnabled: boolean
): [
Array<TInstruction<StartMemoize> | TInstruction<LoadGlobal>>,
TInstruction<FinishMemoize>,
] {
let globals: Array<TInstruction<StartMemoize> | TInstruction<LoadGlobal>> =
[];
for (const dep of depsList ?? []) {
if (dep.root.kind === "Global" && isManualUseMemoEnabled) {
const place = createTemporaryPlace(env, dep.root.binding.loc);
globals.push({
id: makeInstructionId(0),
lvalue: place,
value: {
kind: "LoadGlobal",
binding: dep.root.binding.binding,
loc: dep.root.binding.loc,
},
loc: dep.root.binding.loc,
});
dep.root = {
kind: "InlinedGlobal",
value: place,
name: dep.root.binding.binding.name,
};
}
}
manualMemoId: number
): [TInstruction<StartMemoize>, TInstruction<FinishMemoize>] {
return [
[
...globals,
{
id: makeInstructionId(0),
lvalue: createTemporaryPlace(env, fnExpr.loc),
value: {
kind: "StartMemoize",
manualMemoId,
/*
* Use deps list from source instead of inferred deps
* as dependencies
*/
deps: depsList,
loc: fnExpr.loc,
},
{
id: makeInstructionId(0),
lvalue: createTemporaryPlace(env, fnExpr.loc),
value: {
kind: "StartMemoize",
manualMemoId,
/*
* Use deps list from source instead of inferred deps
* as dependencies
*/
deps: depsList,
loc: fnExpr.loc,
},
],
loc: fnExpr.loc,
},
{
id: makeInstructionId(0),
lvalue: createTemporaryPlace(env, fnExpr.loc),
@@ -362,14 +333,9 @@ function extractManualMemoizationArgs(
* eg `React.useMemo()`.
*/
export function dropManualMemoization(func: HIRFunction): void {
const isManualUseMemoEnabled =
func.env.config.enablePreserveExistingManualUseMemo === "scope" ||
func.env.config.enableChangeDetection != null ||
func.env.config.disableMemoizationForDebugging;
const isValidationEnabled =
func.env.config.validatePreserveExistingMemoizationGuarantees ||
func.env.config.enablePreserveExistingMemoizationGuarantees ||
isManualUseMemoEnabled;
func.env.config.enablePreserveExistingMemoizationGuarantees;
const sidemap: IdentifierSidemap = {
functions: new Map(),
manualMemos: new Map(),
@@ -390,11 +356,7 @@ export function dropManualMemoization(func: HIRFunction): void {
*/
const queuedInserts: Map<
InstructionId,
Array<
| TInstruction<StartMemoize>
| TInstruction<FinishMemoize>
| TInstruction<LoadGlobal>
>
TInstruction<StartMemoize> | TInstruction<FinishMemoize>
> = new Map();
for (const [_, block] of func.body.blocks) {
for (let i = 0; i < block.instructions.length; i++) {
@@ -457,8 +419,7 @@ export function dropManualMemoization(func: HIRFunction): void {
func.env,
depsList,
memoDecl,
nextManualMemoId++,
isManualUseMemoEnabled
nextManualMemoId++
);
/**
@@ -475,7 +436,7 @@ export function dropManualMemoization(func: HIRFunction): void {
* ```
*/
queuedInserts.set(manualMemo.loadInstr.id, startMarker);
queuedInserts.set(instr.id, [finishMarker]);
queuedInserts.set(instr.id, finishMarker);
}
}
} else {
@@ -496,16 +457,8 @@ export function dropManualMemoization(func: HIRFunction): void {
const insertInstr = queuedInserts.get(instr.id);
if (insertInstr != null) {
nextInstructions = nextInstructions ?? block.instructions.slice(0, i);
const postInstructions: Array<Instruction> = [];
insertInstr.forEach((instr) => {
if (instr.value.kind === "LoadGlobal") {
nextInstructions?.push(instr);
} else {
postInstructions.push(instr);
}
});
nextInstructions.push(instr);
postInstructions.forEach((instr) => nextInstructions?.push(instr));
nextInstructions.push(insertInstr);
} else if (nextInstructions != null) {
nextInstructions.push(instr);
}
@@ -26,7 +26,7 @@ import {
} from "../HIR/visitors";
import {
findDisjointMutableValues,
isMutable,
isMutableAtInstruction,
} from "../ReactiveScopes/InferReactiveScopeVariables";
import DisjointSet from "../Utils/DisjointSet";
import { assertExhaustive } from "../Utils/utils";
@@ -232,7 +232,7 @@ export function inferReactivePlaces(fn: HIRFunction): void {
case Effect.Store:
case Effect.ConditionallyMutate:
case Effect.Mutate: {
if (isMutable(instruction, operand)) {
if (isMutableAtInstruction(instruction, operand)) {
const resolvedId = identifierMapping.get(operand.identifier);
if (resolvedId !== undefined) {
reactiveIdentifiers.markReactiveIdentifier(resolvedId);
@@ -0,0 +1,47 @@
/**
* 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 } from "../HIR";
export function outlineFunctions(fn: HIRFunction): void {
for (const [, block] of fn.body.blocks) {
for (const instr of block.instructions) {
const { value } = instr;
if (
value.kind === "FunctionExpression" ||
value.kind === "ObjectMethod"
) {
// Recurse in case there are inner functions which can be outlined
outlineFunctions(value.loweredFunc.func);
}
if (
value.kind === "FunctionExpression" &&
value.loweredFunc.dependencies.length === 0 &&
value.loweredFunc.func.context.length === 0 &&
// TODO: handle outlining named functions
value.loweredFunc.func.id === null
) {
const loweredFunc = value.loweredFunc.func;
const id = fn.env.generateGloballyUniqueIdentifierName(loweredFunc.id);
loweredFunc.id = id.value;
fn.env.outlineFunction(loweredFunc, null);
instr.value = {
kind: "LoadGlobal",
binding: {
kind: "Global",
name: id.value,
},
loc: value.loc,
};
}
}
}
}
@@ -7,7 +7,12 @@
import * as t from "@babel/types";
import { createHmac } from "crypto";
import { pruneHoistedContexts, pruneUnusedLValues, pruneUnusedLabels } from ".";
import {
pruneHoistedContexts,
pruneUnusedLValues,
pruneUnusedLabels,
renameVariables,
} from ".";
import { CompilerError, ErrorSeverity } from "../CompilerError";
import { Environment, EnvironmentConfig, ExternalFunction } from "../HIR";
import {
@@ -45,6 +50,7 @@ import { assertExhaustive } from "../Utils/utils";
import { buildReactiveFunction } from "./BuildReactiveFunction";
import { SINGLE_CHILD_FBT_TAGS } from "./MemoizeFbtAndMacroOperandsInSameScope";
import { ReactiveFunctionVisitor, visitReactiveFunction } from "./visitors";
import { ReactFunctionType } from "../HIR/Environment";
export const MEMO_CACHE_SENTINEL = "react.memo_cache_sentinel";
export const EARLY_RETURN_SENTINEL = "react.early_return_sentinel";
@@ -85,6 +91,11 @@ export type CodegenFunction = {
* because they were part of a pruned memo block.
*/
prunedMemoValues: number;
outlined: Array<{
fn: CodegenFunction;
type: ReactFunctionType | null;
}>;
};
export function codegenFunction(
@@ -258,6 +269,29 @@ export function codegenFunction(
compiled.body.body.unshift(test);
}
const outlined: CodegenFunction["outlined"] = [];
for (const { fn: outlinedFunction, type } of cx.env.getOutlinedFunctions()) {
const reactiveFunction = buildReactiveFunction(outlinedFunction);
pruneUnusedLabels(reactiveFunction);
pruneUnusedLValues(reactiveFunction);
pruneHoistedContexts(reactiveFunction);
const identifiers = renameVariables(reactiveFunction);
const codegen = codegenReactiveFunction(
new Context(
cx.env,
reactiveFunction.id ?? "[[ anonymous ]]",
identifiers
),
reactiveFunction
);
if (codegen.isErr()) {
return codegen;
}
outlined.push({ fn: codegen.unwrap(), type });
}
compiled.outlined = outlined;
return compileResult;
}
@@ -306,6 +340,7 @@ function codegenReactiveFunction(
memoValues: countMemoBlockVisitor.memoValues,
prunedMemoBlocks: countMemoBlockVisitor.prunedMemoBlocks,
prunedMemoValues: countMemoBlockVisitor.prunedMemoValues,
outlined: [],
});
}
@@ -622,11 +657,7 @@ function codegenReactiveScope(
);
}
if (
cx.env.config.disableMemoizationForDebugging &&
!scope.source &&
cx.env.config.enableChangeDetection == null
) {
if (cx.env.config.disableMemoizationForDebugging) {
testCondition = t.logicalExpression(
"||",
testCondition,
@@ -636,10 +667,7 @@ function codegenReactiveScope(
let computationBlock = codegenBlock(cx, block);
let memoStatement;
if (
cx.env.config.enableChangeDetection != null &&
changeExpressions.length > 0
) {
if (cx.env.config.enableChangeDetection != null) {
const loc =
typeof scope.loc === "symbol"
? "unknown location"
@@ -647,9 +675,9 @@ function codegenReactiveScope(
const detectionFunction =
cx.env.config.enableChangeDetection.structuralCheck;
const cacheLoadOldValueStatements: Array<t.Statement> = [];
const restoreOldValueStatements: Array<t.Statement> = [];
const changeDetectionStatements: Array<t.Statement> = [];
const idempotenceDetectionStatements: Array<t.Statement> = [];
const restoreOldValueStatements: Array<t.Statement> = [];
for (const {
name: { name: nameStr },
@@ -691,7 +719,7 @@ function codegenReactiveScope(
t.variableDeclarator(t.identifier(loadNameStr), genSlot()),
])
);
if (scope.source || !cx.env.config.disableMemoizationForDebugging) {
if (!cx.env.config.disableMemoizationForDebugging) {
restoreOldValueStatements.push(
t.expressionStatement(
t.assignmentExpression("=", t.identifier(nameStr), restoredValue)
@@ -740,7 +768,6 @@ function codegenReactiveScope(
t.blockStatement([
...cacheLoadOldValueStatements,
...changeDetectionStatements,
...restoreOldValueStatements,
])
),
...cacheStoreStatements,
@@ -111,13 +111,16 @@ export function inferReactiveScopeVariables(fn: HIRFunction): void {
earlyReturnValue: null,
merged: new Set(),
loc: identifier.loc,
source: false,
};
scopes.set(groupIdentifier, scope);
} else {
scope.range.start = makeInstructionId(
Math.min(scope.range.start, identifier.mutableRange.start)
);
if (scope.range.start === 0) {
scope.range.start = identifier.mutableRange.start;
} else if (identifier.mutableRange.start !== 0) {
scope.range.start = makeInstructionId(
Math.min(scope.range.start, identifier.mutableRange.start)
);
}
scope.range.end = makeInstructionId(
Math.max(scope.range.end, identifier.mutableRange.end)
);
@@ -162,10 +165,7 @@ export function inferReactiveScopeVariables(fn: HIRFunction): void {
}
}
export function mergeLocation(
l: SourceLocation,
r: SourceLocation
): SourceLocation {
function mergeLocation(l: SourceLocation, r: SourceLocation): SourceLocation {
if (l === GeneratedSource) {
return r;
} else if (r === GeneratedSource) {
@@ -185,25 +185,23 @@ export function mergeLocation(
}
// Is the operand mutable at this given instruction
export function isMutable({ id }: Instruction, place: Place): boolean {
export function isMutableAtInstruction(
{ id }: Instruction,
place: Place
): boolean {
const range = place.identifier.mutableRange;
return id >= range.start && id < range.end;
}
export function mayAllocate(
env: Environment,
instruction: Instruction,
conservative: boolean
): boolean {
function mayAllocate(env: Environment, instruction: Instruction): boolean {
const { value } = instruction;
switch (value.kind) {
case "Destructure": {
return (
doesPatternContainSpreadElement(value.lvalue.pattern) || conservative
);
return doesPatternContainSpreadElement(value.lvalue.pattern);
}
case "PostfixUpdate":
case "PrefixUpdate":
case "Await":
case "DeclareLocal":
case "DeclareContext":
case "StoreLocal":
@@ -214,31 +212,26 @@ export function mayAllocate(
case "LoadContext":
case "StoreContext":
case "PropertyDelete":
case "ComputedLoad":
case "ComputedDelete":
case "JSXText":
case "TemplateLiteral":
case "Primitive":
case "GetIterator":
case "IteratorNext":
case "NextPropertyOf":
case "Debugger":
case "StartMemoize":
case "FinishMemoize":
case "UnaryExpression":
case "BinaryExpression":
case "PropertyLoad":
case "StoreGlobal": {
return false;
}
case "PropertyLoad":
case "NextPropertyOf":
case "ComputedLoad":
case "Await": {
return conservative;
}
case "CallExpression":
case "MethodCall": {
return (
conservative || instruction.lvalue.identifier.type.kind !== "Primitive"
);
return instruction.lvalue.identifier.type.kind !== "Primitive";
}
case "RegExpLiteral":
case "PropertyStore":
@@ -263,80 +256,85 @@ export function mayAllocate(
}
}
export function collectMutableOperands(
fn: HIRFunction,
instr: Instruction,
conservative: boolean
): Array<Identifier> {
const operands: Array<Identifier> = [];
const range = instr.lvalue.identifier.mutableRange;
if (range.end > range.start + 1 || mayAllocate(fn.env, instr, conservative)) {
operands.push(instr.lvalue!.identifier);
/*
* These instructions may pick up external changes due to rules of react violations.
* Instructions should be included here if they may change without their inputs changing.
* For example, PostfixUpdate is not included because it only has a changed lval if
* it has a changed argument, but LoadProperty is included because the argument can be
* mutated elsewhere.
*/
function mayHaveChanged(env: Environment, instruction: Instruction): boolean {
if (env.config.enableChangeDetection == null) {
return false;
}
if (
instr.value.kind === "StoreLocal" ||
instr.value.kind === "StoreContext"
) {
if (
instr.value.lvalue.place.identifier.mutableRange.end >
instr.value.lvalue.place.identifier.mutableRange.start + 1
) {
operands.push(instr.value.lvalue.place.identifier);
switch (instruction.value.kind) {
case "Await":
case "ComputedLoad":
case "Destructure":
case "GetIterator":
case "IteratorNext":
case "NextPropertyOf":
case "PropertyLoad":
case "CallExpression":
case "MethodCall":
case "NewExpression": {
return true;
}
if (
isMutable(instr, instr.value.value) &&
instr.value.value.identifier.mutableRange.start > 0
) {
operands.push(instr.value.value.identifier);
case "LoadGlobal": {
return (
instruction.value.binding.kind === "ModuleLocal" &&
!instruction.value.binding.immutable
);
}
} else if (instr.value.kind === "Destructure") {
for (const place of eachPatternOperand(instr.value.lvalue.pattern)) {
if (
place.identifier.mutableRange.end >
place.identifier.mutableRange.start + 1
) {
operands.push(place.identifier);
}
case "PostfixUpdate":
case "PrefixUpdate":
case "DeclareLocal":
case "DeclareContext":
case "StoreLocal":
case "MetaProperty":
case "TypeCastExpression":
case "LoadLocal":
case "LoadContext":
case "StoreContext":
case "PropertyDelete":
case "ComputedDelete":
case "JSXText":
case "TemplateLiteral":
case "Primitive":
case "Debugger":
case "StartMemoize":
case "FinishMemoize":
case "UnaryExpression":
case "BinaryExpression":
case "StoreGlobal":
case "RegExpLiteral":
case "PropertyStore":
case "ComputedStore":
case "ArrayExpression":
case "JsxExpression":
case "JsxFragment":
case "ObjectExpression":
case "UnsupportedNode":
case "ObjectMethod":
case "FunctionExpression":
case "TaggedTemplateExpression": {
return false;
}
if (
isMutable(instr, instr.value.value) &&
instr.value.value.identifier.mutableRange.start > 0
) {
operands.push(instr.value.value.identifier);
}
} else if (instr.value.kind === "MethodCall") {
for (const operand of eachInstructionOperand(instr)) {
if (
isMutable(instr, operand) &&
/*
* exclude global variables from being added to scopes, we can't recreate them!
* TODO: improve handling of module-scoped variables and globals
*/
operand.identifier.mutableRange.start > 0
) {
operands.push(operand.identifier);
}
}
/*
* Ensure that the ComputedLoad to resolve the method is in the same scope as the
* call itself
*/
operands.push(instr.value.property.identifier);
} else {
for (const operand of eachInstructionOperand(instr)) {
if (
isMutable(instr, operand) &&
/*
* exclude global variables from being added to scopes, we can't recreate them!
* TODO: improve handling of module-scoped variables and globals
*/
operand.identifier.mutableRange.start > 0
) {
operands.push(operand.identifier);
}
default: {
assertExhaustive(
instruction.value,
`Unexpected value kind \`${(instruction.value as any).kind}\``
);
}
}
return operands;
}
function isIdentifierMutable(id: Identifier): boolean {
return id.mutableRange.end > id.mutableRange.start + 1;
}
function identifierHasMutableRange(id: Identifier): boolean {
return id.mutableRange.start > 0;
}
export function findDisjointMutableValues(
@@ -351,7 +349,7 @@ export function findDisjointMutableValues(
for (const phi of block.phis) {
if (
// The phi was reset because it was not mutated after creation
phi.id.mutableRange.start + 1 !== phi.id.mutableRange.end &&
isIdentifierMutable(phi.id) &&
phi.id.mutableRange.end >
(block.instructions.at(0)?.id ?? block.terminal.id)
) {
@@ -366,11 +364,77 @@ export function findDisjointMutableValues(
}
for (const instr of block.instructions) {
const operands = collectMutableOperands(
fn,
instr,
fn.env.config.enableChangeDetection != null
);
const operands: Array<Identifier> = [];
if (
isIdentifierMutable(instr.lvalue.identifier) ||
mayAllocate(fn.env, instr) ||
mayHaveChanged(fn.env, instr)
) {
operands.push(instr.lvalue!.identifier);
}
if (
instr.value.kind === "StoreLocal" ||
instr.value.kind === "StoreContext"
) {
if (isIdentifierMutable(instr.value.lvalue.place.identifier)) {
operands.push(instr.value.lvalue.place.identifier);
}
if (
isMutableAtInstruction(instr, instr.value.value) &&
identifierHasMutableRange(instr.value.value.identifier)
) {
operands.push(instr.value.value.identifier);
}
} else if (instr.value.kind === "Destructure") {
for (const place of eachPatternOperand(instr.value.lvalue.pattern)) {
if (
isIdentifierMutable(place.identifier) ||
mayHaveChanged(fn.env, instr)
) {
operands.push(place.identifier);
}
}
if (
(isMutableAtInstruction(instr, instr.value.value) &&
identifierHasMutableRange(instr.value.value.identifier)) ||
mayHaveChanged(fn.env, instr)
) {
operands.push(instr.value.value.identifier);
}
} else if (instr.value.kind === "MethodCall") {
for (const operand of eachInstructionOperand(instr)) {
if (
(isMutableAtInstruction(instr, operand) &&
/*
* exclude global variables from being added to scopes, we can't recreate them!
* TODO: improve handling of module-scoped variables and globals
*/
identifierHasMutableRange(operand.identifier)) ||
mayHaveChanged(fn.env, instr)
) {
operands.push(operand.identifier);
}
}
/*
* Ensure that the ComputedLoad to resolve the method is in the same scope as the
* call itself
*/
operands.push(instr.value.property.identifier);
} else {
for (const operand of eachInstructionOperand(instr)) {
if (
(isMutableAtInstruction(instr, operand) &&
/*
* exclude global variables from being added to scopes, we can't recreate them!
* TODO: improve handling of module-scoped variables and globals
*/
identifierHasMutableRange(operand.identifier)) ||
mayHaveChanged(fn.env, instr)
) {
operands.push(operand.identifier);
}
}
}
if (operands.length !== 0) {
scopeIdentifiers.union(operands);
}
@@ -1,121 +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 { CompilerError } from "../CompilerError";
import {
BasicBlock,
HIRFunction,
Identifier,
makeInstructionId,
ReactiveScope,
ReactiveScopeDependencies,
} from "../HIR";
import { eachTerminalSuccessor } from "../HIR/visitors";
import {
collectMutableOperands,
mergeLocation,
} from "./InferReactiveScopeVariables";
export function memoizeExistingUseMemos(fn: HIRFunction): void {
visitBlock(fn, fn.body.blocks.get(fn.body.entry)!, null, new Map());
}
let ctr = 0;
function nextId(): number {
return ctr++;
}
type CurrentScope =
| null
| { kind: "pending"; deps: ReactiveScopeDependencies; id: number }
| { kind: "available"; scope: ReactiveScope; id: number };
function visitBlock(
fn: HIRFunction,
block: BasicBlock,
scope: CurrentScope,
seen: Map<number, CurrentScope>
): void {
const visited = seen.get(block.id);
if (visited === undefined) {
seen.set(block.id, scope);
} else {
CompilerError.invariant(
visited === null ? scope === null : visited.id === scope?.id,
{
reason:
"MemoizeExistingUseMemos: visiting the same block with different scopes",
loc: null,
suggestions: null,
}
);
return;
}
function extend(
currentScope: ReactiveScope,
operands: Iterable<Identifier>
): void {
for (const operand of operands) {
currentScope.range.start = makeInstructionId(
Math.min(currentScope.range.start, operand.mutableRange.start)
);
currentScope.range.end = makeInstructionId(
Math.max(currentScope.range.end, operand.mutableRange.end)
);
currentScope.loc = mergeLocation(currentScope.loc, operand.loc);
operand.scope = currentScope;
operand.mutableRange = currentScope.range;
}
}
let currentScope = scope;
for (const instruction of block.instructions) {
if (instruction.value.kind === "StartMemoize") {
const deps: ReactiveScopeDependencies = new Set();
for (const dep of instruction.value.deps ?? []) {
CompilerError.invariant(dep.root.kind !== "Global", {
reason:
"MemoizeExistingUseMemos: Globals should have been replaced with InlineGlobals",
loc: instruction.loc,
suggestions: null,
description: null,
});
deps.add({ identifier: dep.root.value.identifier, path: dep.path });
}
currentScope = { kind: "pending", id: nextId(), deps };
} else if (instruction.value.kind === "FinishMemoize") {
currentScope = null;
} else if (currentScope != null) {
const operands = collectMutableOperands(fn, instruction, true);
if (operands.length > 0) {
if (currentScope.kind === "pending") {
currentScope = {
kind: "available",
id: currentScope.id,
scope: {
id: fn.env.nextScopeId,
range: { start: instruction.id, end: instruction.id },
dependencies: currentScope.deps,
declarations: new Map(),
reassignments: new Set(),
earlyReturnValue: null,
merged: new Set(),
loc: instruction.loc,
source: true,
},
};
}
extend(currentScope.scope, operands);
}
}
}
for (const successor of eachTerminalSuccessor(block.terminal)) {
visitBlock(fn, fn.body.blocks.get(successor)!, currentScope, seen);
}
}
@@ -43,6 +43,7 @@ export function memoizeFbtAndMacroOperandsInSameScope(fn: HIRFunction): void {
const fbtMacroTags = new Set([
...FBT_TAGS,
...(fn.env.config.customMacros ?? []),
...(fn.env.preserveManualMemo() ? ["useMemo", "useCallback"] : []),
]);
const fbtValues: Set<IdentifierId> = new Set();
while (true) {
@@ -15,6 +15,7 @@ import {
ReactiveFunction,
ReactiveScope,
ReactiveScopeBlock,
ReactiveScopeDependencies,
ReactiveScopeDependency,
ReactiveStatement,
Type,
@@ -108,7 +109,7 @@ class FindLastUsageVisitor extends ReactiveFunctionVisitor<void> {
}
}
class Transform extends ReactiveFunctionTransform<ReactiveScope | null> {
class Transform extends ReactiveFunctionTransform<ReactiveScopeDependencies | null> {
lastUsage: Map<IdentifierId, InstructionId>;
constructor(lastUsage: Map<IdentifierId, InstructionId>) {
@@ -118,13 +119,12 @@ class Transform extends ReactiveFunctionTransform<ReactiveScope | null> {
override transformScope(
scopeBlock: ReactiveScopeBlock,
state: ReactiveScope | null
state: ReactiveScopeDependencies | null
): Transformed<ReactiveStatement> {
this.visitScope(scopeBlock, scopeBlock.scope);
this.visitScope(scopeBlock, scopeBlock.scope.dependencies);
if (
state !== null &&
areEqualDependencies(state.dependencies, scopeBlock.scope.dependencies) &&
state.source === scopeBlock.scope.source
areEqualDependencies(state, scopeBlock.scope.dependencies)
) {
return { kind: "replace-many", value: scopeBlock.instructions };
} else {
@@ -132,7 +132,10 @@ class Transform extends ReactiveFunctionTransform<ReactiveScope | null> {
}
}
override visitBlock(block: ReactiveBlock, state: ReactiveScope | null): void {
override visitBlock(
block: ReactiveBlock,
state: ReactiveScopeDependencies | null
): void {
// Pass 1: visit nested blocks to potentially merge their scopes
this.traverseBlock(block, state);
@@ -414,9 +417,6 @@ function canMergeScopes(
current: ReactiveScopeBlock,
next: ReactiveScopeBlock
): boolean {
if (current.scope.source !== next.scope.source) {
return false;
}
// Don't merge scopes with reassignments
if (
current.scope.reassignments.size !== 0 ||
@@ -17,6 +17,7 @@ import {
ReactiveValue,
} from "../HIR/HIR";
import {
printFunction,
printIdentifier,
printInstructionValue,
printPlace,
@@ -24,8 +25,24 @@ import {
} from "../HIR/PrintHIR";
import { assertExhaustive } from "../Utils/utils";
export function printReactiveFunctionWithOutlined(
fn: ReactiveFunction
): string {
const writer = new Writer();
writeReactiveFunction(fn, writer);
for (const outlined of fn.env.getOutlinedFunctions()) {
writer.writeLine("\nfunction " + printFunction(outlined.fn));
}
return writer.complete();
}
export function printReactiveFunction(fn: ReactiveFunction): string {
const writer = new Writer();
writeReactiveFunction(fn, writer);
return writer.complete();
}
function writeReactiveFunction(fn: ReactiveFunction, writer: Writer): void {
writer.writeLine(`function ${fn.id !== null ? fn.id : "<unknown>"}(`);
writer.indented(() => {
for (const param of fn.params) {
@@ -39,7 +56,6 @@ export function printReactiveFunction(fn: ReactiveFunction): string {
writer.writeLine(") {");
writeReactiveInstructions(writer, fn.body);
writer.writeLine("}");
return writer.complete();
}
export function printReactiveScopeSummary(scope: ReactiveScope): string {
@@ -24,8 +24,6 @@ import { EARLY_RETURN_SENTINEL } from "./CodegenReactiveFunction";
import { ReactiveFunctionTransform, Transformed } from "./visitors";
/**
* TODO: Actualy propagate early return information, for now we throw a Todo bailout.
*
* This pass ensures that reactive blocks honor the control flow behavior of the
* original code including early return semantics. Specifically, if a reactive
* scope early returned during the previous execution and the inputs to that block
@@ -135,6 +133,14 @@ class Transform extends ReactiveFunctionTransform<State> {
scopeBlock: ReactiveScopeBlock,
parentState: State
): void {
/**
* Exit early if an earlier pass has already created an early return,
* which may happen in alternate compiler configurations.
*/
if (scopeBlock.scope.earlyReturnValue !== null) {
return;
}
const innerState: State = {
withinReactiveScope: true,
earlyReturnValue: parentState.earlyReturnValue,
@@ -685,9 +685,7 @@ class PropagationVisitor extends ReactiveFunctionVisitor<Context> {
const scopeDependencies = context.enter(scope.scope, () => {
this.visitBlock(scope.instructions, context);
});
if (!scope.scope.source) {
scope.scope.dependencies = scopeDependencies;
}
scope.scope.dependencies = scopeDependencies;
}
override visitPrunedScope(
@@ -183,7 +183,7 @@ class Visitor extends ReactiveFunctionVisitor<CreateUpdate> {
ident.path.forEach((key) => {
target &&= this.paths.get(target)?.get(key);
});
if (target && this.map.get(target) === "Create" && !scope.scope.source) {
if (target && this.map.get(target) === "Create") {
scope.scope.dependencies.delete(ident);
}
});
@@ -934,10 +934,14 @@ class PruneScopesTransform extends ReactiveFunctionTransform<
* is early-returned from within the scope. For now we intentionaly keep
* these scopes, and let them get pruned later by PruneUnusedScopes
* _after_ handling the early-return case in PropagateEarlyReturns.
*
* Also keep the scope if an early return was created by some earlier pass,
* which may happen in alternate compiler configurations.
*/
if (
scopeBlock.scope.declarations.size === 0 &&
scopeBlock.scope.reassignments.size === 0
(scopeBlock.scope.declarations.size === 0 &&
scopeBlock.scope.reassignments.size === 0) ||
scopeBlock.scope.earlyReturnValue !== null
) {
return { kind: "keep" };
}
@@ -949,7 +953,7 @@ class PruneScopesTransform extends ReactiveFunctionTransform<
Array.from(scopeBlock.scope.reassignments).some((identifier) =>
state.has(identifier.id)
);
if (hasMemoizedOutput || scopeBlock.scope.source) {
if (hasMemoizedOutput) {
return { kind: "keep" };
} else {
this.prunedScopes.add(scopeBlock.scope.id);
@@ -97,7 +97,7 @@ class Visitor extends ReactiveFunctionVisitor<ReactiveIdentifiers> {
this.traverseScope(scopeBlock, state);
for (const dep of scopeBlock.scope.dependencies) {
const isReactive = state.has(dep.identifier.id);
if (!isReactive && !scopeBlock.scope.source) {
if (!isReactive) {
scopeBlock.scope.dependencies.delete(dep);
}
}
@@ -43,7 +43,6 @@ class Transform extends ReactiveFunctionTransform<State> {
this.visitScope(scopeBlock, scopeState);
if (
!scopeState.hasReturnStatement &&
!scopeBlock.scope.source &&
scopeBlock.scope.reassignments.size === 0 &&
(scopeBlock.scope.declarations.size === 0 ||
/*
@@ -124,11 +124,11 @@ class OkImpl<T> implements Result<T, never> {
return this;
}
isOk(): boolean {
isOk(): this is OkImpl<T> {
return true;
}
isErr(): boolean {
isErr(): this is ErrImpl<never> {
return false;
}
@@ -199,11 +199,11 @@ class ErrImpl<E> implements Result<never, E> {
return fn(this.val);
}
isOk(): boolean {
isOk(): this is OkImpl<never> {
return false;
}
isErr(): boolean {
isErr(): this is ErrImpl<E> {
return true;
}
@@ -9,8 +9,9 @@ import generate from "@babel/generator";
import * as t from "@babel/types";
import chalk from "chalk";
import { HIR, HIRFunction, ReactiveFunction } from "../HIR/HIR";
import { printFunction, printHIR } from "../HIR/PrintHIR";
import { CodegenFunction, printReactiveFunction } from "../ReactiveScopes";
import { printFunctionWithOutlined, printHIR } from "../HIR/PrintHIR";
import { CodegenFunction } from "../ReactiveScopes";
import { printReactiveFunctionWithOutlined } from "../ReactiveScopes/PrintReactiveFunction";
let ENABLED: boolean = false;
@@ -79,7 +80,7 @@ export function logCodegenFunction(step: string, fn: CodegenFunction): void {
export function logHIRFunction(step: string, fn: HIRFunction): void {
if (ENABLED) {
const printed = printFunction(fn);
const printed = printFunctionWithOutlined(fn);
if (printed !== lastLogged) {
lastLogged = printed;
process.stdout.write(`${chalk.green(step)}:\n${printed}\n\n`);
@@ -91,7 +92,7 @@ export function logHIRFunction(step: string, fn: HIRFunction): void {
export function logReactiveFunction(step: string, fn: ReactiveFunction): void {
if (ENABLED) {
const printed = printReactiveFunction(fn);
const printed = printReactiveFunctionWithOutlined(fn);
if (printed !== lastLogged) {
lastLogged = printed;
process.stdout.write(`${chalk.green(step)}:\n${printed}\n\n`);
@@ -17,7 +17,7 @@ import {
isUseInsertionEffectHookType,
isUseLayoutEffectHookType,
} from "../HIR";
import { isMutable } from "../ReactiveScopes/InferReactiveScopeVariables";
import { isMutableAtInstruction } from "../ReactiveScopes/InferReactiveScopeVariables";
import {
ReactiveFunctionVisitor,
visitReactiveFunction,
@@ -99,7 +99,7 @@ class Visitor extends ReactiveFunctionVisitor<CompilerError> {
const deps = instruction.value.args[1]!;
if (
deps.kind === "Identifier" &&
(isMutable(instruction as Instruction, deps) ||
(isMutableAtInstruction(instruction as Instruction, deps) ||
isUnmemoized(deps.identifier, this.scopes))
) {
state.push({
@@ -25,7 +25,7 @@ import {
import { printManualMemoDependency } from "../HIR/PrintHIR";
import { eachInstructionValueOperand } from "../HIR/visitors";
import { collectMaybeMemoDependencies } from "../Inference/DropManualMemoization";
import { isMutable } from "../ReactiveScopes/InferReactiveScopeVariables";
import { isMutableAtInstruction } from "../ReactiveScopes/InferReactiveScopeVariables";
import {
ReactiveFunctionVisitor,
visitReactiveFunction,
@@ -146,13 +146,9 @@ function compareDeps(
const rootsEqual =
(inferred.root.kind === "Global" &&
source.root.kind === "Global" &&
inferred.root.binding.binding.name ===
source.root.binding.binding.name) ||
((inferred.root.kind === "NamedLocal" ||
inferred.root.kind === "InlinedGlobal") &&
(source.root.kind === "NamedLocal" ||
source.root.kind === "InlinedGlobal") &&
source.root.kind === inferred.root.kind &&
inferred.root.identifierName === source.root.identifierName) ||
(inferred.root.kind === "NamedLocal" &&
source.root.kind === "NamedLocal" &&
inferred.root.value.identifier.id === source.root.value.identifier.id);
if (!rootsEqual) {
return CompareDependencyResult.RootDifference;
@@ -382,8 +378,7 @@ class Visitor extends ReactiveFunctionVisitor<VisitorState> {
if (
state.manualMemoState != null &&
state.manualMemoState.depsFromSource != null &&
!scopeBlock.scope.source
state.manualMemoState.depsFromSource != null
) {
for (const dep of scopeBlock.scope.dependencies) {
validateInferredDep(
@@ -469,7 +464,7 @@ class Visitor extends ReactiveFunctionVisitor<VisitorState> {
instruction.value as InstructionValue
)) {
if (
isMutable(instruction as Instruction, value) ||
isMutableAtInstruction(instruction as Instruction, value) ||
(isDecl && isUnmemoized(value.identifier, this.scopes))
) {
state.errors.push({
@@ -40,57 +40,52 @@ import { useCallback, useEffect, useState } from "react";
let someGlobal = {};
function Component() {
const $ = _c(7);
const $ = _c(6);
const [state, setState] = useState(someGlobal);
const setGlobal = _temp;
let t0;
let t1;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = () => {
someGlobal.value = true;
};
$[0] = t0;
} else {
t0 = $[0];
}
const setGlobal = t0;
let t1;
let t2;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t1 = () => {
setGlobal();
};
t2 = [];
t1 = [];
$[0] = t0;
$[1] = t1;
$[2] = t2;
} else {
t0 = $[0];
t1 = $[1];
t2 = $[2];
}
useEffect(t1, t2);
useEffect(t0, t1);
let t2;
let t3;
let t4;
if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
t3 = () => {
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
t2 = () => {
setState(someGlobal.value);
};
t4 = [someGlobal];
t3 = [someGlobal];
$[2] = t2;
$[3] = t3;
$[4] = t4;
} else {
t2 = $[2];
t3 = $[3];
t4 = $[4];
}
useEffect(t3, t4);
useEffect(t2, t3);
const t5 = String(state);
let t6;
if ($[5] !== t5) {
t6 = <div>{t5}</div>;
const t4 = String(state);
let t5;
if ($[4] !== t4) {
t5 = <div>{t4}</div>;
$[4] = t4;
$[5] = t5;
$[6] = t6;
} else {
t6 = $[6];
t5 = $[5];
}
return t6;
return t5;
}
function _temp() {
someGlobal.value = true;
}
export const FIXTURE_ENTRYPOINT = {
@@ -39,57 +39,52 @@ import { useEffect, useState } from "react";
let someGlobal = {};
function Component() {
const $ = _c(7);
const $ = _c(6);
const [state, setState] = useState(someGlobal);
const setGlobal = _temp;
let t0;
let t1;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = () => {
someGlobal.value = true;
};
$[0] = t0;
} else {
t0 = $[0];
}
const setGlobal = t0;
let t1;
let t2;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t1 = () => {
setGlobal();
};
t2 = [];
t1 = [];
$[0] = t0;
$[1] = t1;
$[2] = t2;
} else {
t0 = $[0];
t1 = $[1];
t2 = $[2];
}
useEffect(t1, t2);
useEffect(t0, t1);
let t2;
let t3;
let t4;
if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
t3 = () => {
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
t2 = () => {
setState(someGlobal.value);
};
t4 = [someGlobal];
t3 = [someGlobal];
$[2] = t2;
$[3] = t3;
$[4] = t4;
} else {
t2 = $[2];
t3 = $[3];
t4 = $[4];
}
useEffect(t3, t4);
useEffect(t2, t3);
const t5 = String(state);
let t6;
if ($[5] !== t5) {
t6 = <div>{t5}</div>;
const t4 = String(state);
let t5;
if ($[4] !== t4) {
t5 = <div>{t4}</div>;
$[4] = t4;
$[5] = t5;
$[6] = t6;
} else {
t6 = $[6];
t5 = $[5];
}
return t6;
return t5;
}
function _temp() {
someGlobal.value = true;
}
export const FIXTURE_ENTRYPOINT = {
@@ -39,57 +39,52 @@ import { useEffect, useState } from "react";
let someGlobal = false;
function Component() {
const $ = _c(7);
const $ = _c(6);
const [state, setState] = useState(someGlobal);
const setGlobal = _temp;
let t0;
let t1;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = () => {
someGlobal = true;
};
$[0] = t0;
} else {
t0 = $[0];
}
const setGlobal = t0;
let t1;
let t2;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t1 = () => {
setGlobal();
};
t2 = [];
t1 = [];
$[0] = t0;
$[1] = t1;
$[2] = t2;
} else {
t0 = $[0];
t1 = $[1];
t2 = $[2];
}
useEffect(t1, t2);
useEffect(t0, t1);
let t2;
let t3;
let t4;
if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
t3 = () => {
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
t2 = () => {
setState(someGlobal);
};
t4 = [someGlobal];
t3 = [someGlobal];
$[2] = t2;
$[3] = t3;
$[4] = t4;
} else {
t2 = $[2];
t3 = $[3];
t4 = $[4];
}
useEffect(t3, t4);
useEffect(t2, t3);
const t5 = String(state);
let t6;
if ($[5] !== t5) {
t6 = <div>{t5}</div>;
const t4 = String(state);
let t5;
if ($[4] !== t4) {
t5 = <div>{t4}</div>;
$[4] = t4;
$[5] = t5;
$[6] = t6;
} else {
t6 = $[6];
t5 = $[5];
}
return t6;
return t5;
}
function _temp() {
someGlobal = true;
}
export const FIXTURE_ENTRYPOINT = {
@@ -36,48 +36,44 @@ import { useEffect, useState } from "react";
let someGlobal = false;
function Component() {
const $ = _c(6);
const $ = _c(5);
const [state, setState] = useState(someGlobal);
let t0;
let t1;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = () => {
someGlobal = true;
};
t1 = [];
t0 = [];
$[0] = t0;
$[1] = t1;
} else {
t0 = $[0];
t1 = $[1];
}
useEffect(t0, t1);
useEffect(_temp, t0);
let t1;
let t2;
let t3;
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
t2 = () => {
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t1 = () => {
setState(someGlobal);
};
t3 = [someGlobal];
t2 = [someGlobal];
$[1] = t1;
$[2] = t2;
$[3] = t3;
} else {
t1 = $[1];
t2 = $[2];
t3 = $[3];
}
useEffect(t2, t3);
useEffect(t1, t2);
const t4 = String(state);
let t5;
if ($[4] !== t4) {
t5 = <div>{t4}</div>;
const t3 = String(state);
let t4;
if ($[3] !== t3) {
t4 = <div>{t3}</div>;
$[3] = t3;
$[4] = t4;
$[5] = t5;
} else {
t5 = $[5];
t4 = $[4];
}
return t5;
return t4;
}
function _temp() {
someGlobal = true;
}
export const FIXTURE_ENTRYPOINT = {
@@ -27,13 +27,9 @@ export const FIXTURE_ENTRYPOINT = {
import { c as _c } from "react/compiler-runtime";
function Component() {
const $ = _c(1);
const onClick = _temp;
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
const onClick = () => {
someUnknownGlobal = true;
moduleLocal = true;
};
t0 = <div onClick={onClick} />;
$[0] = t0;
} else {
@@ -41,6 +37,10 @@ function Component() {
}
return t0;
}
function _temp() {
someUnknownGlobal = true;
moduleLocal = true;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
@@ -16,7 +16,7 @@ function Component(props) {
```javascript
import { c as _c } from "react/compiler-runtime";
function Component(props) {
const $ = _c(8);
const $ = _c(7);
let t0;
let t1;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
@@ -37,25 +37,21 @@ function Component(props) {
t2 = $[3];
}
const x = t2;
let t3;
if ($[4] === Symbol.for("react.memo_cache_sentinel")) {
t3 = () => "this closure gets stringified, not called";
$[4] = t3;
} else {
t3 = $[4];
}
const y = x.join(t3);
const y = x.join(_temp);
foo(y);
let t4;
if ($[5] !== x || $[6] !== y) {
t4 = [x, y];
$[5] = x;
$[6] = y;
$[7] = t4;
let t3;
if ($[4] !== x || $[5] !== y) {
t3 = [x, y];
$[4] = x;
$[5] = y;
$[6] = t3;
} else {
t4 = $[7];
t3 = $[6];
}
return t4;
return t3;
}
function _temp() {
return "this closure gets stringified, not called";
}
```
@@ -44,7 +44,7 @@ function Component(props) {
const items = t1;
let t2;
if ($[4] !== items) {
t2 = items.map((item_0) => item_0);
t2 = items.map(_temp);
$[4] = items;
$[5] = t2;
} else {
@@ -53,6 +53,9 @@ function Component(props) {
const mapped = t2;
return mapped;
}
function _temp(item_0) {
return item_0;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
@@ -33,7 +33,7 @@ function Component(props) {
const x = t0;
let t1;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
const y = x.map((item) => item);
const y = x.map(_temp);
t1 = [x, y];
$[1] = t1;
} else {
@@ -41,6 +41,9 @@ function Component(props) {
}
return t1;
}
function _temp(item) {
return item;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
@@ -33,7 +33,7 @@ function Component(props) {
const x = t0;
let t1;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
const y = x.map((item) => item);
const y = x.map(_temp);
t1 = [x, y];
$[1] = t1;
} else {
@@ -41,6 +41,9 @@ function Component(props) {
}
return t1;
}
function _temp(item) {
return item;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
@@ -28,10 +28,7 @@ function Component(props) {
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
const x = [];
const y = x.map((item) => {
item.updated = true;
return item;
});
const y = x.map(_temp);
t0 = [x, y];
$[0] = t0;
} else {
@@ -39,6 +36,10 @@ function Component(props) {
}
return t0;
}
function _temp(item) {
item.updated = true;
return item;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
@@ -28,10 +28,7 @@ function Component(props) {
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
const x = [];
const y = x.map((item) => {
item.updated = true;
return item;
});
const y = x.map(_temp);
t0 = [x, y];
$[0] = t0;
} else {
@@ -39,6 +36,10 @@ function Component(props) {
}
return t0;
}
function _temp(item) {
item.updated = true;
return item;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
@@ -28,7 +28,7 @@ function Component(props) {
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
const x = [{}];
const y = x.map((item) => item);
const y = x.map(_temp);
y[0].flag = true;
t0 = [x, y];
$[0] = t0;
@@ -37,6 +37,9 @@ function Component(props) {
}
return t0;
}
function _temp(item) {
return item;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
@@ -21,33 +21,29 @@ export const FIXTURE_ENTRYPOINT = {
```javascript
import { c as _c } from "react/compiler-runtime";
function Component(props) {
const $ = _c(5);
const $ = _c(4);
const f = _temp;
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = (item) => item;
$[0] = t0;
if ($[0] !== props.items) {
t0 = [...props.items].map(f);
$[0] = props.items;
$[1] = t0;
} else {
t0 = $[0];
t0 = $[1];
}
const f = t0;
const x = t0;
let t1;
if ($[1] !== props.items) {
t1 = [...props.items].map(f);
$[1] = props.items;
$[2] = t1;
if ($[2] !== x) {
t1 = [x, f];
$[2] = x;
$[3] = t1;
} else {
t1 = $[2];
t1 = $[3];
}
const x = t1;
let t2;
if ($[3] !== x) {
t2 = [x, f];
$[3] = x;
$[4] = t2;
} else {
t2 = $[4];
}
return t2;
return t1;
}
function _temp(item) {
return item;
}
export const FIXTURE_ENTRYPOINT = {
@@ -21,22 +21,14 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
function useFoo() {
const $ = _c(1);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = () => {
"worklet";
return 1;
};
$[0] = t0;
} else {
t0 = $[0];
}
const update = t0;
const update = _temp;
return update;
}
function _temp() {
"worklet";
return 1;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
@@ -16,22 +16,14 @@ function component(a) {
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
function component(a) {
const $ = _c(1);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = function () {
let z_0;
mutate(z_0);
};
$[0] = t0;
} else {
t0 = $[0];
}
const x = t0;
const x = _temp;
return x;
}
function _temp() {
let z_0;
mutate(z_0);
}
```
@@ -30,7 +30,6 @@ function Component(props) {
if (!condition) {
let old$x = $[1];
$structuralCheck(old$x, x, "x", "Component", "cached", "(3:6)");
x = old$x;
}
$[0] = props.value;
$[1] = x;
@@ -30,7 +30,6 @@ function Component(props) {
if (!condition) {
let old$x = $[1];
$structuralCheck(old$x, x, "x", "Component", "cached", "(3:6)");
x = $restore(old$x);
}
$[0] = props.value;
$[1] = $store(x);
@@ -0,0 +1,126 @@
## Input
```javascript
// @enableChangeDetection
let glob = 1;
function Component(props) {
const a = props.x;
const { b, ...c } = props.y;
const d = glob;
return (
<div>
{a}
{b}
{c}
{d}
</div>
);
}
```
## Code
```javascript
import { $structuralCheck } from "react-compiler-runtime";
import { c as _c } from "react/compiler-runtime"; // @enableChangeDetection
let glob = 1;
function Component(props) {
const $ = _c(11);
let t0;
{
t0 = props.x;
let condition = $[0] !== props.x;
if (!condition) {
let old$t0 = $[1];
$structuralCheck(old$t0, t0, "t0", "Component", "cached", "(5:5)");
}
$[0] = props.x;
$[1] = t0;
if (condition) {
t0 = props.x;
$structuralCheck($[1], t0, "t0", "Component", "recomputed", "(5:5)");
t0 = $[1];
}
}
const a = t0;
let b;
let c;
{
({ b, ...c } = props.y);
let condition = $[2] !== props.y;
if (!condition) {
let old$b = $[3];
let old$c = $[4];
$structuralCheck(old$b, b, "b", "Component", "cached", "(6:6)");
$structuralCheck(old$c, c, "c", "Component", "cached", "(6:6)");
}
$[2] = props.y;
$[3] = b;
$[4] = c;
if (condition) {
({ b, ...c } = props.y);
$structuralCheck($[3], b, "b", "Component", "recomputed", "(6:6)");
b = $[3];
$structuralCheck($[4], c, "c", "Component", "recomputed", "(6:6)");
c = $[4];
}
}
let t1;
{
t1 = glob;
let condition = $[5] === Symbol.for("react.memo_cache_sentinel");
if (!condition) {
let old$t1 = $[5];
$structuralCheck(old$t1, t1, "t1", "Component", "cached", "(13:13)");
}
$[5] = t1;
if (condition) {
t1 = glob;
$structuralCheck($[5], t1, "t1", "Component", "recomputed", "(13:13)");
t1 = $[5];
}
}
let t2;
{
t2 = (
<div>
{a}
{b}
{c}
{t1}
</div>
);
let condition = $[6] !== a || $[7] !== b || $[8] !== c || $[9] !== t1;
if (!condition) {
let old$t2 = $[10];
$structuralCheck(old$t2, t2, "t2", "Component", "cached", "(9:14)");
}
$[6] = a;
$[7] = b;
$[8] = c;
$[9] = t1;
$[10] = t2;
if (condition) {
t2 = (
<div>
{a}
{b}
{c}
{t1}
</div>
);
$structuralCheck($[10], t2, "t2", "Component", "recomputed", "(9:14)");
t2 = $[10];
}
}
return t2;
}
```
### Eval output
(kind: exception) Fixture not implemented
@@ -0,0 +1,16 @@
// @enableChangeDetection
let glob = 1;
function Component(props) {
const a = props.x;
const { b, ...c } = props.y;
const d = glob;
return (
<div>
{a}
{b}
{c}
{d}
</div>
);
}
@@ -22,24 +22,20 @@ export const FIXTURE_ENTRYPOINT = {
import { c as _c } from "react/compiler-runtime";
import { Stringify } from "shared-runtime";
function Component(props) {
const $ = _c(3);
const $ = _c(2);
const cb = _temp;
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = (x, y, z) => x + y + z;
$[0] = t0;
if ($[0] !== props.id) {
t0 = <Stringify cb={cb} id={props.id} />;
$[0] = props.id;
$[1] = t0;
} else {
t0 = $[0];
t0 = $[1];
}
const cb = t0;
let t1;
if ($[1] !== props.id) {
t1 = <Stringify cb={cb} id={props.id} />;
$[1] = props.id;
$[2] = t1;
} else {
t1 = $[2];
}
return t1;
return t0;
}
function _temp(x, y, z) {
return x + y + z;
}
export const FIXTURE_ENTRYPOINT = {
@@ -30,30 +30,30 @@ export const FIXTURE_ENTRYPOINT = {
```javascript
import { c as _c } from "react/compiler-runtime"; // Should print A, B, arg, original
function Component() {
const $ = _c(2);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = (o) => {
o.f = () => console.log("new");
};
$[0] = t0;
} else {
t0 = $[0];
}
const changeF = t0;
const $ = _c(1);
const changeF = _temp2;
let x;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
x = { f: () => console.log("original") };
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
x = { f: _temp3 };
(console.log("A"), x)[(console.log("B"), "f")](
(changeF(x), console.log("arg"), 1),
);
$[1] = x;
$[0] = x;
} else {
x = $[1];
x = $[0];
}
return x;
}
function _temp3() {
return console.log("original");
}
function _temp2(o) {
o.f = _temp;
}
function _temp() {
return console.log("new");
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
@@ -27,11 +27,14 @@ export const FIXTURE_ENTRYPOINT = {
import { invoke } from "shared-runtime";
function Component() {
const fn = () => ({ x: "value" });
const fn = _temp;
invoke(fn);
return 3;
}
function _temp() {
return { x: "value" };
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
@@ -31,7 +31,7 @@ function Foo() {
let t1;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t1 = function a(t2) {
const x_0 = t2 === undefined ? () => {} : t2;
const x_0 = t2 === undefined ? _temp : t2;
return (function b(t3) {
const y_0 = t3 === undefined ? [] : t3;
return [x_0, y_0];
@@ -44,6 +44,7 @@ function Foo() {
t0 = t1;
return t0;
}
function _temp() {}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
@@ -25,7 +25,7 @@ function Component(t0) {
const $ = _c(2);
let t1;
if ($[0] !== t0) {
t1 = t0 === undefined ? identity([() => {}, true, 42, "hello"]) : t0;
t1 = t0 === undefined ? identity([_temp, true, 42, "hello"]) : t0;
$[0] = t0;
$[1] = t1;
} else {
@@ -34,6 +34,7 @@ function Component(t0) {
const x = t1;
return x;
}
function _temp() {}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
@@ -16,20 +16,11 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
function Component(t0) {
const $ = _c(2);
let t1;
if ($[0] !== t0) {
t1 = t0 === undefined ? () => {} : t0;
$[0] = t0;
$[1] = t1;
} else {
t1 = $[1];
}
const x = t1;
const x = t0 === undefined ? _temp : t0;
return x;
}
function _temp() {}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
@@ -16,20 +16,13 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
function Component(t0) {
const $ = _c(2);
let t1;
if ($[0] !== t0) {
t1 = t0 === undefined ? () => [-1, true, 42, "hello"] : t0;
$[0] = t0;
$[1] = t1;
} else {
t1 = $[1];
}
const x = t1;
const x = t0 === undefined ? _temp : t0;
return x;
}
function _temp() {
return [-1, true, 42, "hello"];
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
@@ -58,37 +58,31 @@ function unsafeUpdateConst() {
}
function Component() {
const $ = _c(3);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = () => {
unsafeResetConst();
};
$[0] = t0;
} else {
t0 = $[0];
}
useState(t0);
const $ = _c(2);
useState(_temp);
unsafeUpdateConst();
let t0;
let t1;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t1 = [{ pretendConst }];
$[0] = t1;
} else {
t1 = $[0];
}
t0 = t1;
const value = t0;
let t2;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t2 = [{ pretendConst }];
t2 = <ValidateMemoization inputs={[]} output={value} />;
$[1] = t2;
} else {
t2 = $[1];
}
t1 = t2;
const value = t1;
let t3;
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
t3 = <ValidateMemoization inputs={[]} output={value} />;
$[2] = t3;
} else {
t3 = $[2];
}
return t3;
return t2;
}
function _temp() {
unsafeResetConst();
}
export const FIXTURE_ENTRYPOINT = {
@@ -61,45 +61,39 @@ function unsafeUpdateConst() {
}
function Component() {
const $ = _c(4);
const $ = _c(3);
if (
$[0] !== "4bf230b116dd95f382060ad17350e116395e41ed757e51fd074ea0b4ed281272"
) {
for (let $i = 0; $i < 4; $i += 1) {
for (let $i = 0; $i < 3; $i += 1) {
$[$i] = Symbol.for("react.memo_cache_sentinel");
}
$[0] = "4bf230b116dd95f382060ad17350e116395e41ed757e51fd074ea0b4ed281272";
}
let t0;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t0 = () => {
unsafeResetConst();
};
$[1] = t0;
} else {
t0 = $[1];
}
useState(t0);
useState(_temp);
unsafeUpdateConst();
let t0;
let t1;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t1 = [{ pretendConst }];
$[1] = t1;
} else {
t1 = $[1];
}
t0 = t1;
const value = t0;
let t2;
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
t2 = [{ pretendConst }];
t2 = <ValidateMemoization inputs={[pretendConst]} output={value} />;
$[2] = t2;
} else {
t2 = $[2];
}
t1 = t2;
const value = t1;
let t3;
if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
t3 = <ValidateMemoization inputs={[pretendConst]} output={value} />;
$[3] = t3;
} else {
t3 = $[3];
}
return t3;
return t2;
}
function _temp() {
unsafeResetConst();
}
export const FIXTURE_ENTRYPOINT = {
@@ -42,47 +42,41 @@ import { c as _c } from "react/compiler-runtime";
import { fbt } from "fbt";
function Component() {
const $ = _c(2);
const $ = _c(1);
const buttonLabel = _temp;
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = () => {
if (!someCondition) {
return fbt._("Purchase as a gift", null, { hk: "1gHj4g" });
} else {
if (
!iconOnly &&
showPrice &&
item?.current_gift_offer?.price?.formatted != null
) {
return fbt._(
"Gift | {price}",
[fbt._param("price", item?.current_gift_offer?.price?.formatted)],
{ hk: "3GTnGE" },
);
} else {
if (!iconOnly && !showPrice) {
return fbt._("Gift", null, { hk: "3fqfrk" });
}
}
}
};
$[0] = t0;
} else {
t0 = $[0];
}
const buttonLabel = t0;
let t1;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t1 = (
t0 = (
<View>
<Button text={buttonLabel()} />
</View>
);
$[1] = t1;
$[0] = t0;
} else {
t1 = $[1];
t0 = $[0];
}
return t0;
}
function _temp() {
if (!someCondition) {
return fbt._("Purchase as a gift", null, { hk: "1gHj4g" });
} else {
if (
!iconOnly &&
showPrice &&
item?.current_gift_offer?.price?.formatted != null
) {
return fbt._(
"Gift | {price}",
[fbt._param("price", item?.current_gift_offer?.price?.formatted)],
{ hk: "3GTnGE" },
);
} else {
if (!iconOnly && !showPrice) {
return fbt._("Gift", null, { hk: "3fqfrk" });
}
}
}
return t1;
}
```
@@ -19,29 +19,23 @@ function Component(props) {
```javascript
import { c as _c } from "react/compiler-runtime";
function Component(props) {
const $ = _c(3);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = (object, key, value) => {
object.updated = true;
object[key] = value;
};
$[0] = t0;
} else {
t0 = $[0];
}
const mutate = t0;
const $ = _c(2);
const mutate = _temp;
let x;
if ($[1] !== props) {
if ($[0] !== props) {
x = makeObject(props);
mutate(x);
$[1] = props;
$[2] = x;
$[0] = props;
$[1] = x;
} else {
x = $[2];
x = $[1];
}
return x;
}
function _temp(object, key, value) {
object.updated = true;
object[key] = value;
}
```
@@ -36,7 +36,7 @@ function hoisting(cond) {
items.push(bar());
};
const bar = () => true;
const bar = _temp;
foo();
}
$[0] = cond;
@@ -46,6 +46,9 @@ function hoisting(cond) {
}
return items;
}
function _temp() {
return true;
}
export const FIXTURE_ENTRYPOINT = {
fn: hoisting,
@@ -36,7 +36,7 @@ function hoisting() {
},
};
const bar = () => 1;
const bar = _temp;
t0 = x.foo();
$[0] = t0;
@@ -45,6 +45,9 @@ function hoisting() {
}
return t0;
}
function _temp() {
return 1;
}
export const FIXTURE_ENTRYPOINT = {
fn: hoisting,
@@ -28,18 +28,9 @@ export const FIXTURE_ENTRYPOINT = {
import { c as _c } from "react/compiler-runtime";
function Foo(t0) {
const $ = _c(1);
const outer = _temp;
let t1;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
const outer = (val) => {
const fact = (x) => {
if (x <= 0) {
return 1;
}
return x * fact(x - 1);
};
return fact(val);
};
t1 = outer(3);
$[0] = t1;
} else {
@@ -47,6 +38,15 @@ function Foo(t0) {
}
return t1;
}
function _temp(val) {
const fact = (x) => {
if (x <= 0) {
return 1;
}
return x * fact(x - 1);
};
return fact(val);
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
@@ -31,7 +31,7 @@ function hoisting() {
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
const foo = () => bar();
const bar = () => 1;
const bar = _temp;
t0 = foo();
$[0] = t0;
@@ -40,6 +40,9 @@ function hoisting() {
}
return t0;
}
function _temp() {
return 1;
}
export const FIXTURE_ENTRYPOINT = {
fn: hoisting,
@@ -26,15 +26,9 @@ export const FIXTURE_ENTRYPOINT = {
import { c as _c } from "react/compiler-runtime";
function Component(t0) {
const $ = _c(1);
const outer = _temp;
let t1;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
const outer = () => {
const inner = () => x;
const x = 3;
return inner();
};
t1 = <div>{outer()}</div>;
$[0] = t1;
} else {
@@ -42,6 +36,11 @@ function Component(t0) {
}
return t1;
}
function _temp() {
const inner = () => x;
const x = 3;
return inner();
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
@@ -0,0 +1,35 @@
## Input
```javascript
function Foo() {
type X = number;
interface Bar {
baz: number;
}
return 0;
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [],
};
```
## Code
```javascript
function Foo() {
return 0;
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [],
};
```
### Eval output
(kind: ok) 0
@@ -0,0 +1,12 @@
function Foo() {
type X = number;
interface Bar {
baz: number;
}
return 0;
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [],
};
@@ -49,17 +49,7 @@ function Component(t0) {
let t1;
if ($[0] !== items) {
t1 =
items.length > 0 ? (
<Foo
value={
<Bar>
{items.map((item) => (
<Item key={item.id} item={item} />
))}
</Bar>
}
/>
) : null;
items.length > 0 ? <Foo value={<Bar>{items.map(_temp)}</Bar>} /> : null;
$[0] = items;
$[1] = t1;
} else {
@@ -67,6 +57,9 @@ function Component(t0) {
}
return t1;
}
function _temp(item) {
return <Item key={item.id} item={item} />;
}
function Foo(t0) {
const { value } = t0;
@@ -39,18 +39,7 @@ function Component(t0) {
const { items } = t0;
let t1;
if ($[0] !== items) {
t1 =
items.length > 0 ? (
<Foo
value={
<>
{items.map((item) => (
<Stringify key={item.id} item={item} />
))}
</>
}
/>
) : null;
t1 = items.length > 0 ? <Foo value={<>{items.map(_temp)}</>} /> : null;
$[0] = items;
$[1] = t1;
} else {
@@ -58,6 +47,9 @@ function Component(t0) {
}
return t1;
}
function _temp(item) {
return <Stringify key={item.id} item={item} />;
}
function Foo(t0) {
const $ = _c(2);
@@ -29,7 +29,7 @@ function Foo() {
let t1;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t1 = function a(t2) {
const x_0 = t2 === undefined ? () => {} : t2;
const x_0 = t2 === undefined ? _temp : t2;
return x_0;
};
$[0] = t1;
@@ -39,6 +39,7 @@ function Foo() {
t0 = t1;
return t0;
}
function _temp() {}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
@@ -33,17 +33,10 @@ export const FIXTURE_ENTRYPOINT = {
```javascript
import { c as _c } from "react/compiler-runtime";
function Component(props) {
const $ = _c(3);
const $ = _c(2);
let t0;
if ($[0] !== props.items) {
let t1;
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
t1 = (item) => item != null;
$[2] = t1;
} else {
t1 = $[2];
}
t0 = props.items.filter(t1);
t0 = props.items.filter(_temp);
$[0] = props.items;
$[1] = t0;
} else {
@@ -52,6 +45,9 @@ function Component(props) {
const filtered = t0;
return filtered;
}
function _temp(item) {
return item != null;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
@@ -32,88 +32,69 @@ import { c as _c } from "react/compiler-runtime"; // @disableMemoizationForDebug
import { useMemo } from "react";
function Component(props) {
const $ = _c(9);
const $ = _c(7);
const a = useMemo(() => <div>{props.a}</div>, [props]);
let t0;
{
t0 = props.b;
let condition = $[0] !== props.b || true;
if (!condition) {
let old$t0 = $[1];
$structuralCheck(old$t0, t0, "t0", "Component", "cached", "(6:6)");
}
$[0] = props.b;
$[1] = t0;
if (condition) {
t0 = props.b;
$structuralCheck($[1], t0, "t0", "Component", "recomputed", "(6:6)");
t0 = $[1];
}
}
let t1;
{
t1 = <div>{props.a}</div>;
let condition = $[0] !== props;
t1 = <div>{t0}</div>;
let condition = $[2] !== t0 || true;
if (!condition) {
let old$t1 = $[1];
$structuralCheck(old$t1, t1, "t1", "Component", "cached", "(5:5)");
t1 = old$t1;
let old$t1 = $[3];
$structuralCheck(old$t1, t1, "t1", "Component", "cached", "(6:6)");
}
$[0] = props;
$[1] = t1;
$[2] = t0;
$[3] = t1;
if (condition) {
t1 = <div>{props.a}</div>;
$structuralCheck($[1], t1, "t1", "Component", "recomputed", "(5:5)");
t1 = $[1];
t1 = <div>{t0}</div>;
$structuralCheck($[3], t1, "t1", "Component", "recomputed", "(6:6)");
t1 = $[3];
}
}
t0 = t1;
const a = t0;
const b = t1;
let t2;
{
t2 = props.b;
let condition = $[2] !== props.b;
if (!condition) {
let old$t2 = $[3];
$structuralCheck(old$t2, t2, "t2", "Component", "cached", "(6:6)");
}
$[2] = props.b;
$[3] = t2;
if (condition) {
t2 = props.b;
$structuralCheck($[3], t2, "t2", "Component", "recomputed", "(6:6)");
t2 = $[3];
}
}
let t3;
{
t3 = <div>{t2}</div>;
let condition = $[4] !== t2;
if (!condition) {
let old$t3 = $[5];
$structuralCheck(old$t3, t3, "t3", "Component", "cached", "(6:6)");
}
$[4] = t2;
$[5] = t3;
if (condition) {
t3 = <div>{t2}</div>;
$structuralCheck($[5], t3, "t3", "Component", "recomputed", "(6:6)");
t3 = $[5];
}
}
const b = t3;
let t4;
{
t4 = (
t2 = (
<div>
{a}
{b}
</div>
);
let condition = $[6] !== a || $[7] !== b;
let condition = $[4] !== a || $[5] !== b || true;
if (!condition) {
let old$t4 = $[8];
$structuralCheck(old$t4, t4, "t4", "Component", "cached", "(8:11)");
let old$t2 = $[6];
$structuralCheck(old$t2, t2, "t2", "Component", "cached", "(8:11)");
}
$[6] = a;
$[7] = b;
$[8] = t4;
$[4] = a;
$[5] = b;
$[6] = t2;
if (condition) {
t4 = (
t2 = (
<div>
{a}
{b}
</div>
);
$structuralCheck($[8], t4, "t4", "Component", "recomputed", "(8:11)");
t4 = $[8];
$structuralCheck($[6], t2, "t2", "Component", "recomputed", "(8:11)");
t2 = $[6];
}
}
return t4;
return t2;
}
export const FIXTURE_ENTRYPOINT = {
@@ -1,66 +0,0 @@
## Input
```javascript
// @disableMemoizationForDebugging
import { useMemo } from "react";
const w = 42;
function Component(a) {
let x = useMemo(() => a.x, [a, w]);
return <div>{x}</div>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ x: 42 }],
isComponent: true,
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @disableMemoizationForDebugging
import { useMemo } from "react";
const w = 42;
function Component(a) {
const $ = _c(5);
const t0 = w;
let t1;
let t2;
if ($[0] !== a || $[1] !== t0) {
t2 = a.x;
$[0] = a;
$[1] = t0;
$[2] = t2;
} else {
t2 = $[2];
}
t1 = t2;
const x = t1;
let t3;
if ($[3] !== x || true) {
t3 = <div>{x}</div>;
$[3] = x;
$[4] = t3;
} else {
t3 = $[4];
}
return t3;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ x: 42 }],
isComponent: true,
};
```
### Eval output
(kind: ok) <div>42</div>
@@ -1,15 +0,0 @@
// @disableMemoizationForDebugging
import { useMemo } from "react";
const w = 42;
function Component(a) {
let x = useMemo(() => a.x, [a, w]);
return <div>{x}</div>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ x: 42 }],
isComponent: true,
};
@@ -18,7 +18,7 @@ function Component(props) {
const item = useFragment(graphql`...`, props.item);
let t0;
if ($[0] !== item.items) {
t0 = item.items?.map((item_0) => renderItem(item_0)) ?? [];
t0 = item.items?.map(_temp) ?? [];
$[0] = item.items;
$[1] = t0;
} else {
@@ -26,6 +26,9 @@ function Component(props) {
}
return t0;
}
function _temp(item_0) {
return renderItem(item_0);
}
```
@@ -0,0 +1,65 @@
## Input
```javascript
import { Stringify } from "shared-runtime";
function Component(props) {
// test outlined functions with destructured parameters - the
// temporary for the destructured param must be promoted
return (
<>
{props.items.map(({ id, name }) => (
<Stringify key={id} name={name} />
))}
</>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ items: [{ id: 1, name: "one" }] }],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { Stringify } from "shared-runtime";
function Component(props) {
const $ = _c(4);
let t0;
if ($[0] !== props.items) {
t0 = props.items.map(_temp);
$[0] = props.items;
$[1] = t0;
} else {
t0 = $[1];
}
let t1;
if ($[2] !== t0) {
t1 = <>{t0}</>;
$[2] = t0;
$[3] = t1;
} else {
t1 = $[3];
}
return t1;
}
function _temp(t0) {
const { id, name } = t0;
return <Stringify key={id} name={name} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ items: [{ id: 1, name: "one" }] }],
};
```
### Eval output
(kind: ok) <div>{"name":"one"}</div>
@@ -0,0 +1,18 @@
import { Stringify } from "shared-runtime";
function Component(props) {
// test outlined functions with destructured parameters - the
// temporary for the destructured param must be promoted
return (
<>
{props.items.map(({ id, name }) => (
<Stringify key={id} name={name} />
))}
</>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ items: [{ id: 1, name: "one" }] }],
};
@@ -0,0 +1,62 @@
## Input
```javascript
import { Stringify } from "shared-runtime";
function Component(props) {
return (
<div>
{props.items.map((item) => (
<Stringify key={item.id} item={item.name} />
))}
</div>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ items: [{ id: 1, name: "one" }] }],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { Stringify } from "shared-runtime";
function Component(props) {
const $ = _c(4);
let t0;
if ($[0] !== props.items) {
t0 = props.items.map(_temp);
$[0] = props.items;
$[1] = t0;
} else {
t0 = $[1];
}
let t1;
if ($[2] !== t0) {
t1 = <div>{t0}</div>;
$[2] = t0;
$[3] = t1;
} else {
t1 = $[3];
}
return t1;
}
function _temp(item) {
return <Stringify key={item.id} item={item.name} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ items: [{ id: 1, name: "one" }] }],
};
```
### Eval output
(kind: ok) <div><div>{"item":"one"}</div></div>
@@ -0,0 +1,16 @@
import { Stringify } from "shared-runtime";
function Component(props) {
return (
<div>
{props.items.map((item) => (
<Stringify key={item.id} item={item.name} />
))}
</div>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ items: [{ id: 1, name: "one" }] }],
};
@@ -22,22 +22,17 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees
// @validatePreserveExistingMemoizationGuarantees
import { useCallback } from "react";
import { CONST_STRING0 } from "shared-runtime";
// It's correct to infer a useCallback block has no reactive dependencies
function useFoo() {
const $ = _c(1);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = () => [CONST_STRING0];
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
return _temp;
}
function _temp() {
return [CONST_STRING0];
}
export const FIXTURE_ENTRYPOINT = {
@@ -40,7 +40,7 @@ import { useCallback } from "react";
import { Stringify } from "shared-runtime";
function Foo(t0) {
const $ = _c(9);
const $ = _c(8);
const { arr1, arr2, foo } = t0;
let t1;
let getVal1;
@@ -49,14 +49,8 @@ function Foo(t0) {
let y;
y = [];
let t2;
if ($[5] === Symbol.for("react.memo_cache_sentinel")) {
t2 = () => ({ x: 2 });
$[5] = t2;
} else {
t2 = $[5];
}
getVal1 = t2;
getVal1 = _temp;
t1 = () => [y];
foo ? (y = x.concat(arr2)) : y;
@@ -71,16 +65,19 @@ function Foo(t0) {
}
const getVal2 = t1;
let t2;
if ($[6] !== getVal1 || $[7] !== getVal2) {
if ($[5] !== getVal1 || $[6] !== getVal2) {
t2 = <Stringify val1={getVal1} val2={getVal2} shouldInvokeFns={true} />;
$[6] = getVal1;
$[7] = getVal2;
$[8] = t2;
$[5] = getVal1;
$[6] = getVal2;
$[7] = t2;
} else {
t2 = $[8];
t2 = $[7];
}
return t2;
}
function _temp() {
return { x: 2 };
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
@@ -0,0 +1,68 @@
## Input
```javascript
// @enableForest
function Component({ base, start, increment, test }) {
let value = base;
for (let i = start; i < test; i += increment) {
value += i;
}
return <div>{value}</div>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ base: 0, start: 0, test: 10, increment: 1 }],
sequentialRenders: [
{ base: 0, start: 1, test: 10, increment: 1 },
{ base: 0, start: 0, test: 10, increment: 2 },
{ base: 2, start: 0, test: 10, increment: 2 },
{ base: 0, start: 0, test: 11, increment: 2 },
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @enableForest
function Component(t0) {
const $ = _c(5);
const { base, start, increment, test } = t0;
let value;
if ($[0] !== base || $[1] !== start || $[2] !== test || $[3] !== increment) {
value = base;
for (let i = start; i < test; i = i + increment, i) {
value = value + i;
}
$[0] = base;
$[1] = start;
$[2] = test;
$[3] = increment;
$[4] = value;
} else {
value = $[4];
}
return <div>{value}</div>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ base: 0, start: 0, test: 10, increment: 1 }],
sequentialRenders: [
{ base: 0, start: 1, test: 10, increment: 1 },
{ base: 0, start: 0, test: 10, increment: 2 },
{ base: 2, start: 0, test: 10, increment: 2 },
{ base: 0, start: 0, test: 11, increment: 2 },
],
};
```
### Eval output
(kind: ok) <div>45</div>
<div>20</div>
<div>22</div>
<div>30</div>

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