Compare commits

..

14 Commits

Author SHA1 Message Date
Sebastian Sebbie Silbermann
ede3840137 Version 19.1.2 2025-12-03 15:33:20 +01:00
Sebastian Markbage
0e2ae74309 Bring ReactFlightClient fixes to FlightReplyServer
We did a bunch of refactors to ReactFlightClient in PRs like #29823 and #33664.

This brings a bunch of those related refactors to the equivalent FlightReplyServer. Such as deep resolution of cycles and deferred error handling.
2025-12-03 15:32:26 +01:00
Sebastian "Sebbie" Silbermann
6e79ecf60b [release] Allow building single release channel with processed versions (#35270) 2025-12-03 01:38:04 +01:00
Jack Pope
02ef495809 Reset packages we are not releasing to currently published versions 2025-07-24 16:35:10 -04:00
Riccardo Cipolleschi
3f178f55fc [Release] Update build script to properly set React Native's renderers version (#33972) 2025-07-24 16:49:56 +01:00
Jack Pope
87e33ca2b7 Set release versions to 19.1.1 2025-07-16 13:05:36 -04:00
lauren
52cf381c72 [eprh] Bump stable version (#32978)
https://www.npmjs.com/package/eslint-plugin-react-hooks/v/6.0.0 was just
released, so we can bump this now.
2025-07-16 12:55:41 -04:00
lauren
b793948e15 Bump next prerelease version numbers (#32782)
Updates the version numbers in the prerelease channels.
2025-07-16 12:55:24 -04:00
Jack Pope
73e4ba42cd Allow runtime_build_and_test action to trigger manually (#33796) 2025-07-16 12:52:44 -04:00
Ruslan Lesiutin
5a1eb6f61a fix: rename bottom stack frame (#33680)
`react-stack-bottom-frame` -> `react_stack_bottom_frame`.

This survives `@babel/plugin-transform-function-name`, but now frames
will be displayed as `at Object.react_stack_bottom_frame (...)` in V8.
Checks that were relying on exact function name match were updated to
use either `.indexOf()` or `.includes()`

For backwards compatibility, both React DevTools and Flight Client will
look for both options. I am not so sure about the latter and if React
version is locked.
2025-07-16 11:53:56 -04:00
Sebastian Markbåge
01eae200bf [DevTools] Get source location from structured callsites in prepareStackTrace (#33143)
When we get the source location for "View source for this element" we
should be using the enclosing function of the callsite of the child. So
that we don't just point to some random line within the component.

This is similar to the technique in #33136.

This technique is now really better than the fake throw technique, when
available. So I now favor the owner technique. The only problem it's
only available in DEV and only if it has a child that's owned (and not
filtered).

We could implement this same technique for the error that's thrown in
the fake throwing solution. However, we really shouldn't need that at
all because for client components we should be able to call
`inspect(fn)` at least in Chrome which is even better.
2025-07-16 11:11:51 -04:00
Timothy Yung
0e6781a06b Enable the enableEagerAlternateStateNodeCleanup Feature Flag (#33447)
Enables the `enableEagerAlternateStateNodeCleanup` feature flag for all
variants, while maintaining the `__VARIANT__` for the internal React
Native flavor for backtesting reasons.

```
$ yarn test
```
2025-07-15 15:29:02 -04:00
Samuel Susla
2cd3c424ea Add eager alternate.stateNode cleanup (#33161)
This is a fix for a problem where React retains shadow nodes longer than
it needs to. The behaviour is shown in React Native test:
https://github.com/facebook/react-native/blob/main/packages/react-native/src/private/__tests__/utilities/__tests__/ShadowNodeReferenceCounter-itest.js#L169

When React commits a new shadow tree, old shadow nodes are stored inside
`fiber.alternate.stateNode`. This is not cleared up until React clones
the node again. This may be problematic if mutation deletes a subtree,
in that case `fiber.alternate.stateNode` will retain entire subtree
until next update. In case of image nodes, this means retaining entire
images.

So when React goes from revision A: `<View><View /></View>` to revision
B: `<View />`, `fiber.alternate.stateNode` will be pointing to Shadow
Node that represents revision A..

![image](https://github.com/user-attachments/assets/076b677e-d152-4763-8c9d-4f923212b424)

To fix this, this PR adds a new feature flag
`enableEagerAlternateStateNodeCleanup`. When enabled,
`alternate.stateNode` is proactively pointed towards finishedWork's
stateNode, releasing resources sooner.

I have verified this fixes the issue [demonstrated by React Native
tests](https://github.com/facebook/react-native/blob/main/packages/react-native/src/private/__tests__/utilities/__tests__/ShadowNodeReferenceCounter-itest.js#L169).
All existing React tests pass when the flag is enabled.
2025-07-15 15:24:50 -04:00
Jack Pope
a24654e65b Ship enableFabricCompleteRootInCommitPhase (#33064)
This was shipped internally. Cleaning up the flag.
2025-07-15 15:16:15 -04:00
238 changed files with 3650 additions and 5000 deletions

View File

@@ -1,7 +1,7 @@
name: (Compiler) Discord Notify
on:
pull_request_target:
pull_request:
types: [opened, ready_for_review]
paths:
- compiler/**

View File

@@ -40,12 +40,8 @@ jobs:
with:
path: |
**/node_modules
key: compiler-and-playground-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
key: compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
working-directory: compiler
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Check Playwright version
id: playwright_version
run: echo "playwright_version=$(npm ls @playwright/test | grep @playwright | sed 's/.*@//' | head -1)" >> "$GITHUB_OUTPUT"

View File

@@ -49,9 +49,8 @@ jobs:
with:
path: |
**/node_modules
key: compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/yarn.lock') }}
key: compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Publish packages to npm
run: |
cp ./scripts/release/ci-npmrc ~/.npmrc

View File

@@ -47,13 +47,11 @@ jobs:
cache-dependency-path: compiler/yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/yarn.lock') }}
key: compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn workspace babel-plugin-react-compiler lint
# Hardcoded to improve parallelism
@@ -73,9 +71,8 @@ jobs:
with:
path: |
**/node_modules
key: compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/yarn.lock') }}
key: compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn workspace babel-plugin-react-compiler jest
test:
@@ -99,9 +96,8 @@ jobs:
with:
path: |
**/node_modules
key: compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/yarn.lock') }}
key: compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: xvfb-run -a yarn workspace ${{ matrix.workspace_name }} test
if: runner.os == 'Linux' && matrix.workspace_name == 'react-forgive'
- run: yarn workspace ${{ matrix.workspace_name }} test

View File

@@ -40,9 +40,7 @@ jobs:
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn --cwd scripts/release install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Download react-devtools artifacts for base revision
run: |
git fetch origin main
@@ -77,7 +75,6 @@ jobs:
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Restore archived build
uses: actions/download-artifact@v4
with:
@@ -137,7 +134,6 @@ jobs:
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Restore all archived build artifacts
uses: actions/download-artifact@v4
- name: Display structure of build
@@ -173,24 +169,14 @@ jobs:
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Restore all archived build artifacts
uses: actions/download-artifact@v4
- name: Display structure of build
run: ls -R build
- name: Check Playwright version
id: playwright_version
run: echo "playwright_version=$(npm ls @playwright/test | grep @playwright | sed 's/.*@//' | head -1)" >> "$GITHUB_OUTPUT"
- name: Cache Playwright Browsers for version ${{ steps.playwright_version.outputs.playwright_version }}
id: cache_playwright_browsers
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: playwright-browsers-v6-${{ runner.arch }}-${{ runner.os }}-${{ steps.playwright_version.outputs.playwright_version }}
- run: npx playwright install --with-deps
if: steps.cache_playwright_browsers.outputs.cache-hit != 'true'
- run: npx playwright install-deps
if: steps.cache_playwright_browsers.outputs.cache-hit == 'true'
- name: Playwright install deps
run: |
npx playwright install
sudo npx playwright install-deps
- run: ./scripts/ci/download_devtools_regression_build.js ${{ matrix.version }}
- run: ls -R build-regression
- run: ./scripts/ci/run_devtools_e2e_tests.js ${{ matrix.version }}

View File

@@ -6,6 +6,12 @@ on:
pull_request:
paths-ignore:
- compiler/**
workflow_dispatch:
inputs:
commit_sha:
required: false
type: string
default: ''
permissions: {}
@@ -28,7 +34,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
- name: Check cache hit
uses: actions/cache/restore@v4
id: node_modules
@@ -69,7 +75,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
- name: Check cache hit
uses: actions/cache/restore@v4
id: node_modules
@@ -117,7 +123,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
- uses: actions/github-script@v7
id: set-matrix
with:
@@ -136,7 +142,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -166,7 +172,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -198,7 +204,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -254,7 +260,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -294,7 +300,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -389,7 +395,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -426,15 +432,11 @@ jobs:
process_artifacts_combined:
name: Process artifacts combined
needs: [build_and_lint, runtime_node_modules_cache]
permissions:
# https://github.com/actions/attest-build-provenance
id-token: write
attestations: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -462,7 +464,7 @@ jobs:
merge-multiple: true
- name: Display structure of build
run: ls -R build
- run: echo ${{ github.event.pull_request.head.sha || github.sha }} >> build/COMMIT_SHA
- run: echo ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }} >> build/COMMIT_SHA
- name: Scrape warning messages
run: |
mkdir -p ./build/__test_utils__
@@ -472,7 +474,6 @@ jobs:
# TODO: Migrate scripts to use `build` directory instead of `build2`
- run: cp ./build.tgz ./build2.tgz
- name: Archive build artifacts
id: upload_artifacts_combined
uses: actions/upload-artifact@v4
with:
name: artifacts_combined
@@ -480,17 +481,6 @@ jobs:
./build.tgz
./build2.tgz
if-no-files-found: error
- uses: actions/attest-build-provenance@v2
# We don't verify builds generated from pull requests not originating from facebook/react.
# However, if the PR lands, the run on `main` will generate the attestation which can then
# be used to download a build via scripts/release/download-experimental-build.js.
#
# Note that this means that scripts/release/download-experimental-build.js must be run with
# --no-verify when downloading a build from a fork.
if: github.event_name == 'push' && github.ref_name == 'main' || github.event.pull_request.head.repo.full_name == github.repository
with:
subject-name: artifacts_combined.zip
subject-digest: sha256:${{ steps.upload_artifacts_combined.outputs.artifact-digest }}
check_error_codes:
name: Search build artifacts for unminified errors
@@ -499,7 +489,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -539,7 +529,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -576,7 +566,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -617,7 +607,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -691,7 +681,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -748,7 +738,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -793,7 +783,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -813,18 +803,14 @@ jobs:
- run: yarn --cwd scripts/release install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Download artifacts for base revision
# The build could have been generated from a fork, so we must download the build without
# any verification. This is safe since we only use this for sizebot calculation and the
# unverified artifact is not used. Additionally this workflow runs in the pull_request
# trigger so only restricted permissions are available.
run: |
GH_TOKEN=${{ github.token }} scripts/release/download-experimental-build.js --commit=$(git rev-parse ${{ github.event.pull_request.base.sha }}) ${{ (github.event.pull_request.head.repo.full_name != github.repository && '--noVerify') || ''}}
GH_TOKEN=${{ github.token }} scripts/release/download-experimental-build.js --commit=$(git rev-parse ${{ github.event.pull_request.base.sha }})
mv ./build ./base-build
# TODO: The `download-experimental-build` script copies the npm
# packages into the `node_modules` directory. This is a historical
# quirk of how the release script works. Let's pretend they
# don't exist.
- name: Delete extraneous files
# TODO: The `download-experimental-build` script copies the npm
# packages into the `node_modules` directory. This is a historical
# quirk of how the release script works. Let's pretend they
# don't exist.
run: rm -rf ./base-build/node_modules
- name: Display structure of base-build from origin/main
run: ls -R base-build
@@ -842,7 +828,7 @@ jobs:
node ./scripts/print-warnings/print-warnings.js > build/__test_utils__/ReactAllWarnings.js
- name: Display structure of build for PR
run: ls -R build
- run: echo ${{ github.event.pull_request.head.sha || github.sha }} >> build/COMMIT_SHA
- run: echo ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || github.event.pull_request.head.sha || github.sha }} >> build/COMMIT_SHA
- run: node ./scripts/tasks/danger
- name: Archive sizebot results
uses: actions/upload-artifact@v4

View File

@@ -1,7 +1,7 @@
name: (Runtime) Discord Notify
on:
pull_request_target:
pull_request:
types: [opened, ready_for_review]
paths-ignore:
- compiler/**

View File

@@ -46,20 +46,17 @@ jobs:
with:
path: |
**/node_modules
key: runtime-and-compiler-eslint_e2e-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock', 'fixtures/eslint-v*/yarn.lock') }}
key: runtime-and-compiler-eslint_e2e-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn --cwd compiler install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Install fixture dependencies
working-directory: ./fixtures/eslint-v${{ matrix.eslint_major }}
run: yarn --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Build plugin
working-directory: fixtures/eslint-v${{ matrix.eslint_major }}
run: node build.mjs
- name: Install fixture dependencies
working-directory: ./fixtures/eslint-v${{ matrix.eslint_major }}
run: yarn --frozen-lockfile
- name: Run lint test
working-directory: ./fixtures/eslint-v${{ matrix.eslint_major }}
run: yarn lint

View File

@@ -14,8 +14,6 @@ on:
required: true
type: string
secrets:
GH_TOKEN:
required: true
NPM_TOKEN:
required: true
@@ -31,9 +29,6 @@ jobs:
publish_prerelease:
name: Publish prelease (${{ inputs.release_channel }}) ${{ inputs.commit_sha }} @${{ inputs.dist_tag }}
runs-on: ubuntu-latest
permissions:
# We use github.token to download the build artifact from a previous runtime_build_and_test.yml run
actions: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
@@ -51,10 +46,8 @@ jobs:
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn --cwd scripts/release install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: |
GH_TOKEN=${{ secrets.GH_TOKEN }} scripts/release/prepare-release-from-ci.js --skipTests -r ${{ inputs.release_channel }} --commit=${{ inputs.commit_sha }}
scripts/release/prepare-release-from-ci.js --skipTests -r ${{ inputs.release_channel }} --commit=${{ inputs.commit_sha }}
cp ./scripts/release/ci-npmrc ~/.npmrc
scripts/release/publish.js --ci --tags ${{ inputs.dist_tag }}

View File

@@ -16,9 +16,6 @@ jobs:
publish_prerelease_canary:
name: Publish to Canary channel
uses: facebook/react/.github/workflows/runtime_prereleases.yml@main
permissions:
# We use github.token to download the build artifact from a previous runtime_build_and_test.yml run
actions: read
with:
commit_sha: ${{ inputs.prerelease_commit_sha }}
release_channel: stable
@@ -35,14 +32,10 @@ jobs:
dist_tag: canary,next
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
publish_prerelease_experimental:
name: Publish to Experimental channel
uses: facebook/react/.github/workflows/runtime_prereleases.yml@main
permissions:
# We use github.token to download the build artifact from a previous runtime_build_and_test.yml run
actions: read
# NOTE: Intentionally running these jobs sequentially because npm
# will sometimes fail if you try to concurrently publish two
# different versions of the same package, even if they use different
@@ -54,4 +47,3 @@ jobs:
dist_tag: experimental
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -14,23 +14,16 @@ jobs:
publish_prerelease_canary:
name: Publish to Canary channel
uses: facebook/react/.github/workflows/runtime_prereleases.yml@main
permissions:
# We use github.token to download the build artifact from a previous runtime_build_and_test.yml run
actions: read
with:
commit_sha: ${{ github.sha }}
release_channel: stable
dist_tag: canary,next
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
publish_prerelease_experimental:
name: Publish to Experimental channel
uses: facebook/react/.github/workflows/runtime_prereleases.yml@main
permissions:
# We use github.token to download the build artifact from a previous runtime_build_and_test.yml run
actions: read
# NOTE: Intentionally running these jobs sequentially because npm
# will sometimes fail if you try to concurrently publish two
# different versions of the same package, even if they use different
@@ -42,4 +35,3 @@ jobs:
dist_tag: experimental
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -78,9 +78,7 @@ jobs:
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn --cwd scripts/release install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: cp ./scripts/release/ci-npmrc ~/.npmrc
- if: '${{ inputs.only_packages }}'
name: 'Prepare ${{ inputs.only_packages }} from NPM'

View File

@@ -6,6 +6,10 @@ on:
actor:
required: true
type: string
is_remote:
required: false
type: boolean
default: false
outputs:
is_core_team:
value: ${{ jobs.check_maintainer.outputs.is_core_team }}
@@ -26,6 +30,7 @@ jobs:
outputs:
is_core_team: ${{ steps.check_if_actor_is_maintainer.outputs.result }}
steps:
- uses: actions/checkout@v4
- name: Check if actor is maintainer
id: check_if_actor_is_maintainer
uses: actions/github-script@v7
@@ -33,20 +38,33 @@ jobs:
script: |
const fs = require('fs');
const actor = '${{ inputs.actor }}';
const res = await github.rest.repos.getContent({
owner: 'facebook',
repo: 'react',
path: 'MAINTAINERS',
ref: 'main',
headers: { Accept: 'application/vnd.github+json' }
});
if (res.status !== 200) {
console.error(res);
throw new Error('Unable to fetch MAINTAINERS file');
let isRemote = ${{ inputs.is_remote }};
if (typeof isRemote === 'string') {
isRemote = isRemote === 'true';
}
content = Buffer.from(res.data.content, 'base64').toString();
if (content == null || typeof content !== 'string') {
throw new Error('Unable to retrieve MAINTAINERS file');
if (typeof isRemote !== 'boolean') {
throw new Error(`Invalid \`isRemote\` input. Expected a boolean, got: ${isRemote}`);
}
let content = null;
if (isRemote === true) {
const res = await github.rest.repos.getContent({
owner: 'facebook',
repo: 'react',
path: 'MAINTAINERS',
ref: 'main',
headers: { Accept: 'application/vnd.github+json' }
});
if (res.status !== 200) {
console.error(res);
throw new Error('Unable to fetch MAINTAINERS file');
}
content = Buffer.from(res.data.content, 'base64').toString();
} else {
content = await fs.readFileSync('./MAINTAINERS', { encoding: 'utf8' });
}
if (content === null) {
throw new Error('Unable to retrieve local or http MAINTAINERS file');
}
const maintainers = new Set(content.split('\n'));

View File

@@ -3,8 +3,7 @@
name: (Shared) Cleanup Stale Branch Caches
on:
schedule:
# Every 6 hours
- cron: 0 */6 * * *
- cron: 0 0 * * *
workflow_dispatch:
permissions: {}

View File

@@ -1,7 +1,7 @@
name: (Shared) Label Core Team PRs
on:
pull_request_target:
pull_request:
permissions: {}

View File

@@ -29,7 +29,6 @@ jobs:
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
@@ -37,7 +36,6 @@ jobs:
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn prettier-check
eslint:
@@ -52,7 +50,6 @@ jobs:
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
@@ -60,7 +57,6 @@ jobs:
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: node ./scripts/tasks/eslint
check_license:
@@ -75,7 +71,6 @@ jobs:
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
@@ -83,7 +78,6 @@ jobs:
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: ./scripts/ci/check_license.sh
test_print_warnings:
@@ -98,7 +92,6 @@ jobs:
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
@@ -106,5 +99,4 @@ jobs:
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: ./scripts/ci/test_print_warnings.sh

View File

@@ -7,18 +7,18 @@
//
// The @latest channel uses the version as-is, e.g.:
//
// 19.1.0
// 19.1.2
//
// The @canary channel appends additional information, with the scheme
// <version>-<label>-<commit_sha>, e.g.:
//
// 19.1.0-canary-a1c2d3e4
// 19.1.2-canary-a1c2d3e4
//
// The @experimental channel doesn't include a version, only a date and a sha, e.g.:
//
// 0.0.0-experimental-241c4467e-20200129
const ReactVersion = '19.1.0';
const ReactVersion = '19.1.2';
// The label used by the @canary channel. Represents the upcoming release's
// stability. Most of the time, this will be "canary", but we may temporarily
@@ -34,7 +34,7 @@ const rcNumber = 0;
const stablePackages = {
'eslint-plugin-react-hooks': '5.2.0',
'jest-react': '0.17.0',
'jest-react': '0.16.0',
react: ReactVersion,
'react-art': ReactVersion,
'react-dom': ReactVersion,

View File

@@ -19,9 +19,7 @@ import BabelPluginReactCompiler, {
PluginOptions,
CompilerPipelineValue,
parsePluginOptions,
printReactiveFunctionWithOutlined,
printFunctionWithOutlined,
} from 'babel-plugin-react-compiler';
} from 'babel-plugin-react-compiler/src';
import clsx from 'clsx';
import invariant from 'invariant';
import {useSnackbar} from 'notistack';
@@ -43,6 +41,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';
import {transformFromAstSync} from '@babel/core';
function parseInput(

View File

@@ -6,7 +6,7 @@
*/
import MonacoEditor, {loader, type Monaco} from '@monaco-editor/react';
import {CompilerErrorDetail} from 'babel-plugin-react-compiler';
import {CompilerErrorDetail} from 'babel-plugin-react-compiler/src';
import invariant from 'invariant';
import type {editor} from 'monaco-editor';
import * as monaco from 'monaco-editor';

View File

@@ -11,7 +11,7 @@ import {
InformationCircleIcon,
} from '@heroicons/react/outline';
import MonacoEditor, {DiffEditor} from '@monaco-editor/react';
import {type CompilerError} from 'babel-plugin-react-compiler';
import {type CompilerError} from 'babel-plugin-react-compiler/src';
import parserBabel from 'prettier/plugins/babel';
import * as prettierPluginEstree from 'prettier/plugins/estree';
import * as prettier from 'prettier/standalone';

View File

@@ -6,7 +6,10 @@
*/
import {Monaco} from '@monaco-editor/react';
import {CompilerErrorDetail, ErrorSeverity} from 'babel-plugin-react-compiler';
import {
CompilerErrorDetail,
ErrorSeverity,
} from 'babel-plugin-react-compiler/src';
import {MarkerSeverity, type editor} from 'monaco-editor';
function mapReactCompilerSeverityToMonaco(
@@ -51,7 +54,7 @@ export function renderReactCompilerMarkers({
model,
details,
}: ReactCompilerMarkerConfig): void {
const markers: Array<editor.IMarkerData> = [];
let markers = [];
for (const detail of details) {
const marker = mapReactCompilerDiagnosticToMonacoMarker(detail, monaco);
if (marker == null) {

View File

@@ -4,7 +4,7 @@
"private": true,
"scripts": {
"dev": "cd ../.. && concurrently --kill-others -n compiler,runtime,playground \"yarn workspace babel-plugin-react-compiler run watch\" \"yarn workspace react-compiler-runtime run watch\" \"wait-on packages/babel-plugin-react-compiler/dist/index.js && cd apps/playground && NODE_ENV=development next dev\"",
"build:compiler": "cd ../.. && concurrently -n compiler,runtime \"yarn workspace babel-plugin-react-compiler run build --dts\" \"yarn workspace react-compiler-runtime run build\"",
"build:compiler": "cd ../.. && concurrently -n compiler,runtime \"yarn workspace babel-plugin-react-compiler run build\" \"yarn workspace react-compiler-runtime run build\"",
"build": "yarn build:compiler && next build",
"postbuild": "node ./scripts/downloadFonts.js",
"preinstall": "cd ../.. && yarn install --frozen-lockfile",

View File

@@ -12,12 +12,12 @@
"build": "rimraf dist && tsup",
"test": "./scripts/link-react-compiler-runtime.sh && yarn snap:ci",
"jest": "yarn build && ts-node node_modules/.bin/jest",
"snap": "yarn workspace snap run snap",
"snap": "node ../snap/dist/main.js",
"snap:build": "yarn workspace snap run build",
"snap:ci": "yarn snap:build && yarn snap",
"ts:analyze-trace": "scripts/ts-analyze-trace.sh",
"lint": "yarn eslint src",
"watch": "yarn build --dts --watch"
"watch": "yarn build --watch"
},
"dependencies": {
"@babel/types": "^7.26.0"

View File

@@ -73,7 +73,7 @@ export default function BabelPluginReactCompiler(
pass.filename ?? null,
opts.logger,
opts.environment,
result,
result?.retryErrors ?? [],
);
if (ENABLE_REACT_COMPILER_TIMINGS === true) {
performance.mark(`${filename}:end`, {

View File

@@ -7,9 +7,8 @@
import {NodePath} from '@babel/core';
import * as t from '@babel/types';
import {PluginOptions} from './Options';
import {CompilerError} from '../CompilerError';
import {ProgramContext} from './Imports';
import {ExternalFunction} from '..';
/**
* Gating rewrite for function declarations which are referenced before their
@@ -35,8 +34,7 @@ import {ExternalFunction} from '..';
function insertAdditionalFunctionDeclaration(
fnPath: NodePath<t.FunctionDeclaration>,
compiled: t.FunctionDeclaration,
programContext: ProgramContext,
gatingFunctionIdentifierName: string,
gating: NonNullable<PluginOptions['gating']>,
): void {
const originalFnName = fnPath.node.id;
const originalFnParams = fnPath.node.params;
@@ -59,14 +57,14 @@ function insertAdditionalFunctionDeclaration(
loc: fnPath.node.loc ?? null,
});
const gatingCondition = t.identifier(
programContext.newUid(`${gatingFunctionIdentifierName}_result`),
const gatingCondition = fnPath.scope.generateUidIdentifier(
`${gating.importSpecifierName}_result`,
);
const unoptimizedFnName = t.identifier(
programContext.newUid(`${originalFnName.name}_unoptimized`),
const unoptimizedFnName = fnPath.scope.generateUidIdentifier(
`${originalFnName.name}_unoptimized`,
);
const optimizedFnName = t.identifier(
programContext.newUid(`${originalFnName.name}_optimized`),
const optimizedFnName = fnPath.scope.generateUidIdentifier(
`${originalFnName.name}_optimized`,
);
/**
* Step 1: rename existing functions
@@ -117,7 +115,7 @@ function insertAdditionalFunctionDeclaration(
t.variableDeclaration('const', [
t.variableDeclarator(
gatingCondition,
t.callExpression(t.identifier(gatingFunctionIdentifierName), []),
t.callExpression(t.identifier(gating.importSpecifierName), []),
),
]),
);
@@ -131,26 +129,19 @@ export function insertGatedFunctionDeclaration(
| t.FunctionDeclaration
| t.ArrowFunctionExpression
| t.FunctionExpression,
programContext: ProgramContext,
gating: ExternalFunction,
gating: NonNullable<PluginOptions['gating']>,
referencedBeforeDeclaration: boolean,
): void {
const gatingImportedName = programContext.addImportSpecifier(gating).name;
if (referencedBeforeDeclaration && fnPath.isFunctionDeclaration()) {
CompilerError.invariant(compiled.type === 'FunctionDeclaration', {
reason: 'Expected compiled node type to match input type',
description: `Got ${compiled.type} but expected FunctionDeclaration`,
loc: fnPath.node.loc ?? null,
});
insertAdditionalFunctionDeclaration(
fnPath,
compiled,
programContext,
gatingImportedName,
);
insertAdditionalFunctionDeclaration(fnPath, compiled, gating);
} else {
const gatingExpression = t.conditionalExpression(
t.callExpression(t.identifier(gatingImportedName), []),
t.callExpression(t.identifier(gating.importSpecifierName), []),
buildFunctionExpression(compiled),
buildFunctionExpression(fnPath.node),
);

View File

@@ -7,19 +7,9 @@
import {NodePath} from '@babel/core';
import * as t from '@babel/types';
import {Scope as BabelScope} from '@babel/traverse';
import {CompilerError, ErrorSeverity} from '../CompilerError';
import {
EnvironmentConfig,
GeneratedSource,
NonLocalImportSpecifier,
} from '../HIR';
import {getOrInsertWith} from '../Utils/utils';
import {ExternalFunction, isHookName} from '../HIR/Environment';
import {Err, Ok, Result} from '../Utils/Result';
import {CompilerReactTarget} from './Options';
import {getReactCompilerRuntimeModule} from './Program';
import {EnvironmentConfig, ExternalFunction, GeneratedSource} from '../HIR';
import {getOrInsertDefault} from '../Utils/utils';
export function validateRestrictedImports(
path: NodePath<t.Program>,
@@ -52,209 +42,50 @@ export function validateRestrictedImports(
}
}
export class ProgramContext {
/* Program and environment context */
scope: BabelScope;
reactRuntimeModule: string;
hookPattern: string | null;
// known generated or referenced identifiers in the program
knownReferencedNames: Set<string> = new Set();
// generated imports
imports: Map<string, Map<string, NonLocalImportSpecifier>> = new Map();
constructor(
program: NodePath<t.Program>,
reactRuntimeModule: CompilerReactTarget,
hookPattern: string | null,
) {
this.hookPattern = hookPattern;
this.scope = program.scope;
this.reactRuntimeModule = getReactCompilerRuntimeModule(reactRuntimeModule);
}
isHookName(name: string): boolean {
if (this.hookPattern == null) {
return isHookName(name);
} else {
const match = new RegExp(this.hookPattern).exec(name);
return (
match != null && typeof match[1] === 'string' && isHookName(match[1])
);
}
}
hasReference(name: string): boolean {
return (
this.knownReferencedNames.has(name) ||
this.scope.hasBinding(name) ||
this.scope.hasGlobal(name) ||
this.scope.hasReference(name)
);
}
newUid(name: string): string {
/**
* Don't call babel's generateUid for known hook imports, as
* InferTypes might eventually type `HookKind` based on callee naming
* convention and `_useFoo` is not named as a hook.
*
* Local uid generation is susceptible to check-before-use bugs since we're
* checking for naming conflicts / references long before we actually insert
* the import. (see similar logic in HIRBuilder:resolveBinding)
*/
let uid;
if (this.isHookName(name)) {
uid = name;
let i = 0;
while (this.hasReference(uid)) {
this.knownReferencedNames.add(uid);
uid = `${name}_${i++}`;
}
} else if (!this.hasReference(name)) {
uid = name;
} else {
uid = this.scope.generateUid(name);
}
this.knownReferencedNames.add(uid);
return uid;
}
addMemoCacheImport(): NonLocalImportSpecifier {
return this.addImportSpecifier(
{
source: this.reactRuntimeModule,
importSpecifierName: 'c',
},
'_c',
);
}
/**
*
* @param externalFunction
* @param nameHint if defined, will be used as the name of the import specifier
* @returns
*/
addImportSpecifier(
{source: module, importSpecifierName: specifier}: ExternalFunction,
nameHint?: string,
): NonLocalImportSpecifier {
const maybeBinding = this.imports.get(module)?.get(specifier);
if (maybeBinding != null) {
return {...maybeBinding};
}
const binding: NonLocalImportSpecifier = {
kind: 'ImportSpecifier',
name: this.newUid(nameHint ?? specifier),
module,
imported: specifier,
};
getOrInsertWith(this.imports, module, () => new Map()).set(specifier, {
...binding,
});
return binding;
}
addNewReference(name: string): void {
this.knownReferencedNames.add(name);
}
assertGlobalBinding(
name: string,
localScope?: BabelScope,
): Result<void, CompilerError> {
const scope = localScope ?? this.scope;
if (!scope.hasReference(name) && !scope.hasBinding(name)) {
return Ok(undefined);
}
const error = new CompilerError();
error.push({
severity: ErrorSeverity.Todo,
reason: 'Encountered conflicting global in generated program',
description: `Conflict from local binding ${name}`,
loc: scope.getBinding(name)?.path.node.loc ?? null,
suggestions: null,
});
return Err(error);
}
}
function getExistingImports(
program: NodePath<t.Program>,
): Map<string, NodePath<t.ImportDeclaration>> {
const existingImports = new Map<string, NodePath<t.ImportDeclaration>>();
program.traverse({
ImportDeclaration(path) {
if (isNonNamespacedImport(path)) {
existingImports.set(path.node.source.value, path);
}
},
});
return existingImports;
}
export function addImportsToProgram(
path: NodePath<t.Program>,
programContext: ProgramContext,
importList: Array<ExternalFunction>,
): void {
const existingImports = getExistingImports(path);
const identifiers: Set<string> = new Set();
const sortedImports: Map<string, Array<string>> = new Map();
for (const {importSpecifierName, source} of importList) {
/*
* Codegen currently does not rename import specifiers, so we do additional
* validation here
*/
CompilerError.invariant(identifiers.has(importSpecifierName) === false, {
reason: `Encountered conflicting import specifier for ${importSpecifierName} in Forget config.`,
description: null,
loc: GeneratedSource,
suggestions: null,
});
CompilerError.invariant(
path.scope.hasBinding(importSpecifierName) === false,
{
reason: `Encountered conflicting import specifiers for ${importSpecifierName} in generated program.`,
description: null,
loc: GeneratedSource,
suggestions: null,
},
);
identifiers.add(importSpecifierName);
const importSpecifierNameList = getOrInsertDefault(
sortedImports,
source,
[],
);
importSpecifierNameList.push(importSpecifierName);
}
const stmts: Array<t.ImportDeclaration> = [];
const sortedModules = [...programContext.imports.entries()].sort(([a], [b]) =>
a.localeCompare(b),
);
for (const [moduleName, importsMap] of sortedModules) {
for (const [specifierName, loweredImport] of importsMap) {
/**
* Assert that the import identifier hasn't already be declared in the program.
* Note: we use getBinding here since `Scope.hasBinding` pessimistically returns true
* for all allocated uids (from `Scope.getUid`)
*/
CompilerError.invariant(
path.scope.getBinding(loweredImport.name) == null,
{
reason:
'Encountered conflicting import specifiers in generated program',
description: `Conflict from import ${loweredImport.module}:(${loweredImport.imported} as ${loweredImport.name}).`,
loc: GeneratedSource,
suggestions: null,
},
);
CompilerError.invariant(
loweredImport.module === moduleName &&
loweredImport.imported === specifierName,
{
reason:
'Found inconsistent import specifier. This is an internal bug.',
description: `Expected import ${moduleName}:${specifierName} but found ${loweredImport.module}:${loweredImport.imported}`,
loc: GeneratedSource,
},
);
}
const sortedImport: Array<NonLocalImportSpecifier> = [
...importsMap.values(),
].sort(({imported: a}, {imported: b}) => a.localeCompare(b));
const importSpecifiers = sortedImport.map(specifier => {
return t.importSpecifier(
t.identifier(specifier.name),
t.identifier(specifier.imported),
);
for (const [source, importSpecifierNameList] of sortedImports) {
const importSpecifiers = importSpecifierNameList.map(name => {
const id = t.identifier(name);
return t.importSpecifier(id, id);
});
/**
* If an existing import of this module exists (ie `import { ... } from
* '<moduleName>'`), inject new imported specifiers into the list of
* destructured variables.
*/
const maybeExistingImports = existingImports.get(moduleName);
if (maybeExistingImports != null) {
maybeExistingImports.pushContainer('specifiers', importSpecifiers);
} else {
stmts.push(
t.importDeclaration(importSpecifiers, t.stringLiteral(moduleName)),
);
}
stmts.push(t.importDeclaration(importSpecifiers, t.stringLiteral(source)));
}
path.unshiftContainer('body', stmts);
}
@@ -262,12 +93,13 @@ export function addImportsToProgram(
/*
* Matches `import { ... } from <moduleName>;`
* but not `import * as React from <moduleName>;`
* `import type { Foo } from <moduleName>;`
*/
function isNonNamespacedImport(
importDeclPath: NodePath<t.ImportDeclaration>,
moduleName: string,
): boolean {
return (
importDeclPath.get('source').node.value === moduleName &&
importDeclPath
.get('specifiers')
.every(specifier => specifier.isImportSpecifier()) &&
@@ -275,3 +107,94 @@ function isNonNamespacedImport(
importDeclPath.node.importKind !== 'typeof'
);
}
function hasExistingNonNamespacedImportOfModule(
program: NodePath<t.Program>,
moduleName: string,
): boolean {
let hasExistingImport = false;
program.traverse({
ImportDeclaration(importDeclPath) {
if (isNonNamespacedImport(importDeclPath, moduleName)) {
hasExistingImport = true;
}
},
});
return hasExistingImport;
}
/*
* If an existing import of React exists (ie `import { ... } from '<moduleName>'`), inject useMemoCache
* into the list of destructured variables.
*/
function addMemoCacheFunctionSpecifierToExistingImport(
program: NodePath<t.Program>,
moduleName: string,
identifierName: string,
): boolean {
let didInsertUseMemoCache = false;
program.traverse({
ImportDeclaration(importDeclPath) {
if (
!didInsertUseMemoCache &&
isNonNamespacedImport(importDeclPath, moduleName)
) {
importDeclPath.pushContainer(
'specifiers',
t.importSpecifier(t.identifier(identifierName), t.identifier('c')),
);
didInsertUseMemoCache = true;
}
},
});
return didInsertUseMemoCache;
}
export function updateMemoCacheFunctionImport(
program: NodePath<t.Program>,
moduleName: string,
useMemoCacheIdentifier: string,
): void {
/*
* If there isn't already an import of * as React, insert it so useMemoCache doesn't
* throw
*/
const hasExistingImport = hasExistingNonNamespacedImportOfModule(
program,
moduleName,
);
if (hasExistingImport) {
const didUpdateImport = addMemoCacheFunctionSpecifierToExistingImport(
program,
moduleName,
useMemoCacheIdentifier,
);
if (!didUpdateImport) {
throw new Error(
`Expected an ImportDeclaration of \`${moduleName}\` in order to update ImportSpecifiers with useMemoCache`,
);
}
} else {
addMemoCacheFunctionImportDeclaration(
program,
moduleName,
useMemoCacheIdentifier,
);
}
}
function addMemoCacheFunctionImportDeclaration(
program: NodePath<t.Program>,
moduleName: string,
localName: string,
): void {
program.unshiftContainer(
'body',
t.importDeclaration(
[t.importSpecifier(t.identifier(localName), t.identifier('c'))],
t.stringLiteral(moduleName),
),
);
}

View File

@@ -8,7 +8,7 @@
import {NodePath} from '@babel/traverse';
import * as t from '@babel/types';
import prettyFormat from 'pretty-format';
import {Logger, ProgramContext} from '.';
import {Logger} from '.';
import {
HIRFunction,
ReactiveFunction,
@@ -117,7 +117,7 @@ function run(
config: EnvironmentConfig,
fnType: ReactFunctionType,
mode: CompilerMode,
programContext: ProgramContext,
useMemoCacheIdentifier: string,
logger: Logger | null,
filename: string | null,
code: string | null,
@@ -132,7 +132,7 @@ function run(
logger,
filename,
code,
programContext,
useMemoCacheIdentifier,
);
env.logger?.debugLogIRs?.({
kind: 'debug',
@@ -552,7 +552,7 @@ export function compileFn(
config: EnvironmentConfig,
fnType: ReactFunctionType,
mode: CompilerMode,
programContext: ProgramContext,
useMemoCacheIdentifier: string,
logger: Logger | null,
filename: string | null,
code: string | null,
@@ -562,7 +562,7 @@ export function compileFn(
config,
fnType,
mode,
programContext,
useMemoCacheIdentifier,
logger,
filename,
code,

View File

@@ -12,7 +12,11 @@ import {
CompilerErrorDetail,
ErrorSeverity,
} from '../CompilerError';
import {EnvironmentConfig, ReactFunctionType} from '../HIR/Environment';
import {
EnvironmentConfig,
ExternalFunction,
ReactFunctionType,
} from '../HIR/Environment';
import {CodegenFunction} from '../ReactiveScopes';
import {isComponentDeclaration} from '../Utils/ComponentDeclaration';
import {isHookDeclaration} from '../Utils/HookDeclaration';
@@ -20,17 +24,16 @@ import {assertExhaustive} from '../Utils/utils';
import {insertGatedFunctionDeclaration} from './Gating';
import {
addImportsToProgram,
ProgramContext,
updateMemoCacheFunctionImport,
validateRestrictedImports,
} from './Imports';
import {CompilerReactTarget, PluginOptions} from './Options';
import {PluginOptions} from './Options';
import {compileFn} from './Pipeline';
import {
filterSuppressionsThatAffectFunction,
findProgramSuppressions,
suppressionsToCompilerError,
} from './Suppression';
import {GeneratedSource} from '../HIR';
export type CompilerPass = {
opts: PluginOptions;
@@ -268,9 +271,8 @@ function isFilePartOfSources(
return false;
}
export type CompileProgramResult = {
type CompileProgramResult = {
retryErrors: Array<{fn: BabelFn; error: CompilerError}>;
inferredEffectLocations: Set<t.SourceLocation>;
};
/**
* `compileProgram` is directly invoked by the react-compiler babel plugin, so
@@ -297,12 +299,8 @@ export function compileProgram(
handleError(restrictedImportsErr, pass, null);
return null;
}
const useMemoCacheIdentifier = program.scope.generateUidIdentifier('c');
const programContext = new ProgramContext(
program,
pass.opts.target,
environment.hookPattern,
);
/*
* Record lint errors and critical errors as depending on Forget's config,
* we may still need to run Forget's analysis on every function (even if we
@@ -371,7 +369,6 @@ export function compileProgram(
},
);
const retryErrors: Array<{fn: BabelFn; error: CompilerError}> = [];
const inferredEffectLocations = new Set<t.SourceLocation>();
const processFn = (
fn: BabelFn,
fnType: ReactFunctionType,
@@ -413,7 +410,7 @@ export function compileProgram(
environment,
fnType,
'all_features',
programContext,
useMemoCacheIdentifier.name,
pass.opts.logger,
pass.filename,
pass.code,
@@ -448,7 +445,7 @@ export function compileProgram(
environment,
fnType,
'no_inferred_memo',
programContext,
useMemoCacheIdentifier.name,
pass.opts.logger,
pass.filename,
pass.code,
@@ -456,7 +453,7 @@ export function compileProgram(
};
if (
!compileResult.compiledFn.hasFireRewrite &&
!compileResult.compiledFn.hasInferredEffect
!compileResult.compiledFn.hasLoweredContextAccess
) {
return null;
}
@@ -512,14 +509,6 @@ export function compileProgram(
if (!pass.opts.noEmit) {
return compileResult.compiledFn;
}
/**
* inferEffectDependencies + noEmit is currently only used for linting. In
* this mode, add source locations for where the compiler *can* infer effect
* dependencies.
*/
for (const loc of compileResult.compiledFn.inferredEffectLocations) {
if (loc !== GeneratedSource) inferredEffectLocations.add(loc);
}
return null;
};
@@ -565,29 +554,79 @@ export function compileProgram(
if (moduleScopeOptOutDirectives.length > 0) {
return null;
}
let gating: null | {
gatingFn: ExternalFunction;
referencedBeforeDeclared: Set<CompileResult>;
} = null;
if (pass.opts.gating != null) {
gating = {
gatingFn: pass.opts.gating,
referencedBeforeDeclared:
getFunctionReferencedBeforeDeclarationAtTopLevel(program, compiledFns),
};
}
const hasLoweredContextAccess = compiledFns.some(
c => c.compiledFn.hasLoweredContextAccess,
);
const externalFunctions: Array<ExternalFunction> = [];
try {
// TODO: check for duplicate import specifiers
if (gating != null) {
externalFunctions.push(gating.gatingFn);
}
const lowerContextAccess = environment.lowerContextAccess;
if (lowerContextAccess && hasLoweredContextAccess) {
externalFunctions.push(lowerContextAccess);
}
const enableEmitInstrumentForget = environment.enableEmitInstrumentForget;
if (enableEmitInstrumentForget != null) {
externalFunctions.push(enableEmitInstrumentForget.fn);
if (enableEmitInstrumentForget.gating != null) {
externalFunctions.push(enableEmitInstrumentForget.gating);
}
}
if (environment.enableEmitFreeze != null) {
externalFunctions.push(environment.enableEmitFreeze);
}
if (environment.enableEmitHookGuards != null) {
externalFunctions.push(environment.enableEmitHookGuards);
}
if (environment.enableChangeDetectionForDebugging != null) {
externalFunctions.push(environment.enableChangeDetectionForDebugging);
}
const hasFireRewrite = compiledFns.some(c => c.compiledFn.hasFireRewrite);
if (environment.enableFire && hasFireRewrite) {
externalFunctions.push({
source: getReactCompilerRuntimeModule(pass.opts),
importSpecifierName: 'useFire',
});
}
} catch (err) {
handleError(err, pass, null);
return null;
}
/*
* Only insert Forget-ified functions if we have not encountered a critical
* error elsewhere in the file, regardless of bailout mode.
*/
const referencedBeforeDeclared =
pass.opts.gating != null
? getFunctionReferencedBeforeDeclarationAtTopLevel(program, compiledFns)
: null;
for (const result of compiledFns) {
const {kind, originalFn, compiledFn} = result;
const transformedFn = createNewFunctionNode(originalFn, compiledFn);
if (referencedBeforeDeclared != null && kind === 'original') {
CompilerError.invariant(pass.opts.gating != null, {
reason: "Expected 'gating' import to be present",
loc: null,
});
if (gating != null && kind === 'original') {
insertGatedFunctionDeclaration(
originalFn,
transformedFn,
programContext,
pass.opts.gating,
referencedBeforeDeclared.has(result),
gating.gatingFn,
gating.referencedBeforeDeclared.has(result),
);
} else {
originalFn.replaceWith(transformedFn);
@@ -596,9 +635,24 @@ export function compileProgram(
// Forget compiled the component, we need to update existing imports of useMemoCache
if (compiledFns.length > 0) {
addImportsToProgram(program, programContext);
let needsMemoCacheFunctionImport = false;
for (const fn of compiledFns) {
if (fn.compiledFn.memoSlotsUsed > 0) {
needsMemoCacheFunctionImport = true;
break;
}
}
if (needsMemoCacheFunctionImport) {
updateMemoCacheFunctionImport(
program,
getReactCompilerRuntimeModule(pass.opts),
useMemoCacheIdentifier.name,
);
}
addImportsToProgram(program, externalFunctions);
}
return {retryErrors, inferredEffectLocations};
return {retryErrors};
}
function shouldSkipCompilation(
@@ -629,7 +683,7 @@ function shouldSkipCompilation(
if (
hasMemoCacheFunctionImport(
program,
getReactCompilerRuntimeModule(pass.opts.target),
getReactCompilerRuntimeModule(pass.opts),
)
) {
return true;
@@ -1123,18 +1177,16 @@ function getFunctionReferencedBeforeDeclarationAtTopLevel(
return referencedBeforeDeclaration;
}
export function getReactCompilerRuntimeModule(
target: CompilerReactTarget,
): string {
if (target === '19') {
function getReactCompilerRuntimeModule(opts: PluginOptions): string {
if (opts.target === '19') {
return 'react/compiler-runtime'; // from react namespace
} else if (target === '17' || target === '18') {
} else if (opts.target === '17' || opts.target === '18') {
return 'react-compiler-runtime'; // npm package
} else {
CompilerError.invariant(
target != null &&
target.kind === 'donotuse_meta_internal' &&
typeof target.runtimeModule === 'string',
opts.target != null &&
opts.target.kind === 'donotuse_meta_internal' &&
typeof opts.target.runtimeModule === 'string',
{
reason: 'Expected target to already be validated',
description: null,
@@ -1142,6 +1194,6 @@ export function getReactCompilerRuntimeModule(
suggestions: null,
},
);
return target.runtimeModule;
return opts.target.runtimeModule;
}
}

View File

@@ -11,7 +11,6 @@ import {
import {getOrInsertWith} from '../Utils/utils';
import {Environment} from '../HIR';
import {DEFAULT_EXPORT} from '../HIR/Environment';
import {CompileProgramResult} from './Program';
function throwInvalidReact(
options: Omit<CompilerErrorDetailOptions, 'severity'>,
@@ -37,16 +36,12 @@ function assertValidEffectImportReference(
const parent = path.parentPath;
if (parent != null && parent.isCallExpression()) {
const args = parent.get('arguments');
const maybeCalleeLoc = path.node.loc;
const hasInferredEffect =
maybeCalleeLoc != null &&
context.inferredEffectLocations.has(maybeCalleeLoc);
/**
* Only error on untransformed references of the form `useMyEffect(...)`
* or `moduleNamespace.useMyEffect(...)`, with matching argument counts.
* TODO: do we also want a mode to also hard error on non-call references?
*/
if (args.length === numArgs && !hasInferredEffect) {
if (args.length === numArgs) {
const maybeErrorDiagnostic = matchCompilerDiagnostic(
path,
context.transformErrors,
@@ -102,7 +97,7 @@ export default function validateNoUntransformedReferences(
filename: string | null,
logger: Logger | null,
env: EnvironmentConfig,
compileResult: CompileProgramResult | null,
transformErrors: Array<{fn: NodePath<t.Node>; error: CompilerError}>,
): void {
const moduleLoadChecks = new Map<
string,
@@ -131,7 +126,7 @@ export default function validateNoUntransformedReferences(
}
}
if (moduleLoadChecks.size > 0) {
transformProgram(path, moduleLoadChecks, filename, logger, compileResult);
transformProgram(path, moduleLoadChecks, filename, logger, transformErrors);
}
}
@@ -141,7 +136,6 @@ type TraversalState = {
logger: Logger | null;
filename: string | null;
transformErrors: Array<{fn: NodePath<t.Node>; error: CompilerError}>;
inferredEffectLocations: Set<t.SourceLocation>;
};
type CheckInvalidReferenceFn = (
paths: Array<NodePath<t.Node>>,
@@ -229,16 +223,14 @@ function transformProgram(
moduleLoadChecks: Map<string, Map<string, CheckInvalidReferenceFn>>,
filename: string | null,
logger: Logger | null,
compileResult: CompileProgramResult | null,
transformErrors: Array<{fn: NodePath<t.Node>; error: CompilerError}>,
): void {
const traversalState: TraversalState = {
shouldInvalidateScopes: true,
program: path,
filename,
logger,
transformErrors: compileResult?.retryErrors ?? [],
inferredEffectLocations:
compileResult?.inferredEffectLocations ?? new Set(),
transformErrors,
};
path.traverse({
ImportDeclaration(path: NodePath<t.ImportDeclaration>) {

View File

@@ -11,12 +11,10 @@ import {fromZodError} from 'zod-validation-error';
import {CompilerError} from '../CompilerError';
import {
CompilationMode,
defaultOptions,
Logger,
PanicThresholdOptions,
parsePluginOptions,
PluginOptions,
ProgramContext,
} from '../Entrypoint';
import {Err, Ok, Result} from '../Utils/Result';
import {
@@ -86,8 +84,6 @@ export const InstrumentationSchema = z
);
export type ExternalFunction = z.infer<typeof ExternalFunctionSchema>;
export const USE_FIRE_FUNCTION_NAME = 'useFire';
export const EMIT_FREEZE_GLOBAL_GATING = '__DEV__';
export const MacroMethodSchema = z.union([
z.object({type: z.literal('wildcard')}),
@@ -780,7 +776,6 @@ export function parseConfigPragmaForTests(
const environment = parseConfigPragmaEnvironmentForTest(pragma);
let compilationMode: CompilationMode = defaults.compilationMode;
let panicThreshold: PanicThresholdOptions = 'all_errors';
let noEmit: boolean = defaultOptions.noEmit;
for (const token of pragma.split(' ')) {
if (!token.startsWith('@')) {
continue;
@@ -806,17 +801,12 @@ export function parseConfigPragmaForTests(
panicThreshold = 'none';
break;
}
case '@noEmit': {
noEmit = true;
break;
}
}
}
return parsePluginOptions({
environment,
compilationMode,
panicThreshold,
noEmit,
});
}
@@ -856,10 +846,9 @@ export class Environment {
config: EnvironmentConfig;
fnType: ReactFunctionType;
compilerMode: CompilerMode;
programContext: ProgramContext;
useMemoCacheIdentifier: string;
hasLoweredContextAccess: boolean;
hasFireRewrite: boolean;
hasInferredEffect: boolean;
inferredEffectLocations: Set<SourceLocation> = new Set();
#contextIdentifiers: Set<t.Identifier>;
#hoistedIdentifiers: Set<t.Identifier>;
@@ -873,7 +862,7 @@ export class Environment {
logger: Logger | null,
filename: string | null,
code: string | null,
programContext: ProgramContext,
useMemoCacheIdentifier: string,
) {
this.#scope = scope;
this.fnType = fnType;
@@ -882,11 +871,11 @@ export class Environment {
this.filename = filename;
this.code = code;
this.logger = logger;
this.programContext = programContext;
this.useMemoCacheIdentifier = useMemoCacheIdentifier;
this.#shapes = new Map(DEFAULT_SHAPES);
this.#globals = new Map(DEFAULT_GLOBALS);
this.hasLoweredContextAccess = false;
this.hasFireRewrite = false;
this.hasInferredEffect = false;
if (
config.disableMemoizationForDebugging &&
@@ -948,10 +937,6 @@ export class Environment {
return makeScopeId(this.#nextScope++);
}
get scope(): BabelScope {
return this.#scope;
}
logErrors(errors: Result<void, CompilerError>): void {
if (errors.isOk() || this.logger == null) {
return;

View File

@@ -10,10 +10,7 @@ import {
BUILTIN_SHAPES,
BuiltInArrayId,
BuiltInFireId,
BuiltInMapId,
BuiltInMixedReadonlyId,
BuiltInObjectId,
BuiltInSetId,
BuiltInUseActionStateId,
BuiltInUseContextHookId,
BuiltInUseEffectHookId,
@@ -48,23 +45,29 @@ export const DEFAULT_SHAPES: ShapeRegistry = new Map(BUILTIN_SHAPES);
// Hack until we add ObjectShapes for all globals
const UNTYPED_GLOBALS: Set<string> = new Set([
'String',
'Object',
'Function',
'Number',
'RegExp',
'Date',
'Error',
'Function',
'TypeError',
'RangeError',
'ReferenceError',
'SyntaxError',
'URIError',
'EvalError',
'Boolean',
'DataView',
'Float32Array',
'Float64Array',
'Int8Array',
'Int16Array',
'Int32Array',
'Map',
'Set',
'WeakMap',
'Uint8Array',
'Uint8ClampedArray',
@@ -72,8 +75,16 @@ const UNTYPED_GLOBALS: Set<string> = new Set([
'Uint32Array',
'ArrayBuffer',
'JSON',
'parseFloat',
'parseInt',
'console',
'isNaN',
'eval',
'isFinite',
'encodeURI',
'decodeURI',
'encodeURIComponent',
'decodeURIComponent',
]);
const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
@@ -90,23 +101,6 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
returnValueKind: ValueKind.Mutable,
}),
],
[
/**
* Object.fromEntries(iterable)
* iterable: An iterable, such as an Array or Map, containing a list of
* objects. Each object should have two properties.
* Returns a new object whose properties are given by the entries of the
* iterable.
*/
'fromEntries',
addFunction(DEFAULT_SHAPES, [], {
positionalParams: [Effect.ConditionallyMutate],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInObjectId},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Mutable,
}),
],
]),
],
[
@@ -138,7 +132,7 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
'from',
addFunction(DEFAULT_SHAPES, [], {
positionalParams: [
Effect.ConditionallyMutateIterator,
Effect.ConditionallyMutate,
Effect.ConditionallyMutate,
Effect.ConditionallyMutate,
],
@@ -378,118 +372,6 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
returnValueKind: ValueKind.Primitive,
}),
],
[
'parseInt',
addFunction(DEFAULT_SHAPES, [], {
positionalParams: [],
restParam: Effect.Read,
returnType: {kind: 'Primitive'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
'parseFloat',
addFunction(DEFAULT_SHAPES, [], {
positionalParams: [],
restParam: Effect.Read,
returnType: {kind: 'Primitive'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
'isNaN',
addFunction(DEFAULT_SHAPES, [], {
positionalParams: [],
restParam: Effect.Read,
returnType: {kind: 'Primitive'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
'isFinite',
addFunction(DEFAULT_SHAPES, [], {
positionalParams: [],
restParam: Effect.Read,
returnType: {kind: 'Primitive'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
'encodeURI',
addFunction(DEFAULT_SHAPES, [], {
positionalParams: [],
restParam: Effect.Read,
returnType: {kind: 'Primitive'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
'encodeURIComponent',
addFunction(DEFAULT_SHAPES, [], {
positionalParams: [],
restParam: Effect.Read,
returnType: {kind: 'Primitive'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
'decodeURI',
addFunction(DEFAULT_SHAPES, [], {
positionalParams: [],
restParam: Effect.Read,
returnType: {kind: 'Primitive'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
'decodeURIComponent',
addFunction(DEFAULT_SHAPES, [], {
positionalParams: [],
restParam: Effect.Read,
returnType: {kind: 'Primitive'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
'Map',
addFunction(
DEFAULT_SHAPES,
[],
{
positionalParams: [Effect.ConditionallyMutateIterator],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInMapId},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Mutable,
},
null,
true,
),
],
[
'Set',
addFunction(
DEFAULT_SHAPES,
[],
{
positionalParams: [Effect.ConditionallyMutateIterator],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInSetId},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Mutable,
},
null,
true,
),
],
// TODO: rest of Global objects
];

View File

@@ -10,7 +10,7 @@ import * as t from '@babel/types';
import {CompilerError, CompilerErrorDetailOptions} from '../CompilerError';
import {assertExhaustive} from '../Utils/utils';
import {Environment, ReactFunctionType} from './Environment';
import type {HookKind} from './ObjectShape';
import {HookKind} from './ObjectShape';
import {Type, makeType} from './Types';
import {z} from 'zod';
@@ -829,13 +829,6 @@ export type CallExpression = {
typeArguments?: Array<t.FlowType>;
};
export type NewExpression = {
kind: 'NewExpression';
callee: Place;
args: Array<Place | SpreadPattern>;
loc: SourceLocation;
};
export type LoadLocal = {
kind: 'LoadLocal';
place: Place;
@@ -901,7 +894,12 @@ export type InstructionValue =
right: Place;
loc: SourceLocation;
}
| NewExpression
| {
kind: 'NewExpression';
callee: Place;
args: Array<Place | SpreadPattern>;
loc: SourceLocation;
}
| CallExpression
| MethodCall
| {
@@ -1167,21 +1165,18 @@ export type VariableBinding =
// bindings declard outside the current component/hook
| NonLocalBinding;
// `import {bar as baz} from 'foo'`: name=baz, module=foo, imported=bar
export type NonLocalImportSpecifier = {
kind: 'ImportSpecifier';
name: string;
module: string;
imported: string;
};
export type NonLocalBinding =
// `import Foo from 'foo'`: name=Foo, module=foo
| {kind: 'ImportDefault'; name: string; module: string}
// `import * as Foo from 'foo'`: name=Foo, module=foo
| {kind: 'ImportNamespace'; name: string; module: string}
// `import {bar as baz} from 'foo'`
| NonLocalImportSpecifier
// `import {bar as baz} from 'foo'`: name=baz, module=foo, imported=bar
| {
kind: 'ImportSpecifier';
name: string;
module: string;
imported: string;
}
// let, const, function, etc declared in the module but outside the current component/hook
| {kind: 'ModuleLocal'; name: string}
// an unresolved binding
@@ -1399,7 +1394,6 @@ export enum Effect {
Read = 'read',
// This reference reads and stores the value
Capture = 'capture',
ConditionallyMutateIterator = 'mutate-iterator?',
/*
* This reference *may* write to (mutate) the value. This covers two similar cases:
* - The compiler is being conservative and assuming that a value *may* be mutated
@@ -1418,11 +1412,11 @@ export enum Effect {
// This reference may alias to (mutate) the value
Store = 'store',
}
export const EffectSchema = z.enum([
Effect.Read,
Effect.Mutate,
Effect.ConditionallyMutate,
Effect.ConditionallyMutateIterator,
Effect.Capture,
Effect.Store,
Effect.Freeze,
@@ -1436,7 +1430,6 @@ export function isMutableEffect(
case Effect.Capture:
case Effect.Store:
case Effect.ConditionallyMutate:
case Effect.ConditionallyMutateIterator:
case Effect.Mutate: {
return true;
}
@@ -1656,14 +1649,6 @@ export function isArrayType(id: Identifier): boolean {
return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInArray';
}
export function isMapType(id: Identifier): boolean {
return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInMap';
}
export function isSetType(id: Identifier): boolean {
return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInSet';
}
export function isPropsType(id: Identifier): boolean {
return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInProps';
}

View File

@@ -331,7 +331,6 @@ export default class HIRBuilder {
type: makeType(),
loc: node.loc ?? GeneratedSource,
};
this.#env.programContext.addNewReference(name);
this.#bindings.set(name, {node, identifier});
return identifier;
} else if (mapping.node === node) {

View File

@@ -44,7 +44,6 @@ export function addFunction(
properties: Iterable<[string, BuiltInType | PolyType]>,
fn: Omit<FunctionSignature, 'hookKind'>,
id: string | null = null,
isConstructor: boolean = false,
): FunctionType {
const shapeId = id ?? createAnonId();
addShape(registry, shapeId, properties, {
@@ -55,7 +54,6 @@ export function addFunction(
kind: 'Function',
return: fn.returnType,
shapeId,
isConstructor,
};
}
@@ -75,7 +73,6 @@ export function addHook(
kind: 'Function',
return: fn.returnType,
shapeId,
isConstructor: false,
};
}
@@ -201,8 +198,6 @@ export type ObjectShape = {
export type ShapeRegistry = Map<string, ObjectShape>;
export const BuiltInPropsId = 'BuiltInProps';
export const BuiltInArrayId = 'BuiltInArray';
export const BuiltInSetId = 'BuiltInSet';
export const BuiltInMapId = 'BuiltInMap';
export const BuiltInFunctionId = 'BuiltInFunction';
export const BuiltInJsxId = 'BuiltInJsx';
export const BuiltInObjectId = 'BuiltInObject';
@@ -456,313 +451,6 @@ addObject(BUILTIN_SHAPES, BuiltInObjectId, [
*/
]);
/* Built-in Set shape */
addObject(BUILTIN_SHAPES, BuiltInSetId, [
[
/**
* add(value)
* Parameters
* value: the value of the element to add to the Set object.
* Returns the Set object with added value.
*/
'add',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Capture],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInSetId},
calleeEffect: Effect.Store,
// returnValueKind is technically dependent on the ValueKind of the set itself
returnValueKind: ValueKind.Mutable,
}),
],
[
/**
* clear()
* Parameters none
* Returns undefined
*/
'clear',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: null,
returnType: PRIMITIVE_TYPE,
calleeEffect: Effect.Store,
returnValueKind: ValueKind.Primitive,
}),
],
[
/**
* setInstance.delete(value)
* Returns true if value was already in Set; otherwise false.
*/
'delete',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Read],
restParam: null,
returnType: PRIMITIVE_TYPE,
calleeEffect: Effect.Store,
returnValueKind: ValueKind.Primitive,
}),
],
[
'has',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Read],
restParam: null,
returnType: PRIMITIVE_TYPE,
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
['size', PRIMITIVE_TYPE],
[
/**
* difference(other)
* Parameters
* other: A Set object, or set-like object.
* Returns a new Set object containing elements in this set but not in the other set.
*/
'difference',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Capture],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInSetId},
calleeEffect: Effect.Capture,
returnValueKind: ValueKind.Mutable,
}),
],
[
/**
* union(other)
* Parameters
* other: A Set object, or set-like object.
* Returns a new Set object containing elements in either this set or the other set.
*/
'union',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Capture],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInSetId},
calleeEffect: Effect.Capture,
returnValueKind: ValueKind.Mutable,
}),
],
[
/**
* symmetricalDifference(other)
* Parameters
* other: A Set object, or set-like object.
* A new Set object containing elements which are in either this set or the other set, but not in both.
*/
'symmetricalDifference',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Capture],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInSetId},
calleeEffect: Effect.Capture,
returnValueKind: ValueKind.Mutable,
}),
],
[
/**
* isSubsetOf(other)
* Parameters
* other: A Set object, or set-like object.
* Returns true if all elements in this set are also in the other set, and false otherwise.
*/
'isSubsetOf',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Read],
restParam: null,
returnType: PRIMITIVE_TYPE,
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
/**
* isSupersetOf(other)
* Parameters
* other: A Set object, or set-like object.
* Returns true if all elements in the other set are also in this set, and false otherwise.
*/
'isSupersetOf',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Read],
restParam: null,
returnType: PRIMITIVE_TYPE,
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
/**
* forEach(callbackFn)
* forEach(callbackFn, thisArg)
*/
'forEach',
addFunction(BUILTIN_SHAPES, [], {
/**
* see Array.map explanation for why arguments are marked `ConditionallyMutate`
*/
positionalParams: [],
restParam: Effect.ConditionallyMutate,
returnType: PRIMITIVE_TYPE,
calleeEffect: Effect.ConditionallyMutate,
returnValueKind: ValueKind.Primitive,
noAlias: true,
mutableOnlyIfOperandsAreMutable: true,
}),
],
/**
* Iterators
*/
[
'entries',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: null,
returnType: {kind: 'Poly'},
calleeEffect: Effect.Capture,
returnValueKind: ValueKind.Mutable,
}),
],
[
'keys',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: null,
returnType: {kind: 'Poly'},
calleeEffect: Effect.Capture,
returnValueKind: ValueKind.Mutable,
}),
],
[
'values',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: null,
returnType: {kind: 'Poly'},
calleeEffect: Effect.Capture,
returnValueKind: ValueKind.Mutable,
}),
],
]);
addObject(BUILTIN_SHAPES, BuiltInMapId, [
[
/**
* clear()
* Parameters none
* Returns undefined
*/
'clear',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: null,
returnType: PRIMITIVE_TYPE,
calleeEffect: Effect.Store,
returnValueKind: ValueKind.Primitive,
}),
],
[
'delete',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Read],
restParam: null,
returnType: PRIMITIVE_TYPE,
calleeEffect: Effect.Store,
returnValueKind: ValueKind.Primitive,
}),
],
[
'get',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Read],
restParam: null,
returnType: {kind: 'Poly'},
calleeEffect: Effect.Capture,
returnValueKind: ValueKind.Mutable,
}),
],
[
'has',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Read],
restParam: null,
returnType: PRIMITIVE_TYPE,
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
/**
* Params
* key: the key of the element to add to the Map object. The key may be
* any JavaScript type (any primitive value or any type of JavaScript
* object).
* value: the value of the element to add to the Map object.
* Returns the Map object.
*/
'set',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Capture, Effect.Capture],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInMapId},
calleeEffect: Effect.Store,
returnValueKind: ValueKind.Mutable,
}),
],
['size', PRIMITIVE_TYPE],
[
'forEach',
addFunction(BUILTIN_SHAPES, [], {
/**
* see Array.map explanation for why arguments are marked `ConditionallyMutate`
*/
positionalParams: [],
restParam: Effect.ConditionallyMutate,
returnType: PRIMITIVE_TYPE,
calleeEffect: Effect.ConditionallyMutate,
returnValueKind: ValueKind.Primitive,
noAlias: true,
mutableOnlyIfOperandsAreMutable: true,
}),
],
/**
* Iterators
*/
[
'entries',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: null,
returnType: {kind: 'Poly'},
calleeEffect: Effect.Capture,
returnValueKind: ValueKind.Mutable,
}),
],
[
'keys',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: null,
returnType: {kind: 'Poly'},
calleeEffect: Effect.Capture,
returnValueKind: ValueKind.Mutable,
}),
],
[
'values',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: null,
returnType: {kind: 'Poly'},
calleeEffect: Effect.Capture,
returnValueKind: ValueKind.Mutable,
}),
],
]);
addObject(BUILTIN_SHAPES, BuiltInUseStateId, [
['0', {kind: 'Poly'}],
[

View File

@@ -38,7 +38,6 @@ export type FunctionType = {
kind: 'Function';
shapeId: string | null;
return: Type;
isConstructor: boolean;
};
export type ObjectType = {
@@ -112,7 +111,6 @@ export function duplicateType(type: Type): Type {
kind: 'Function',
return: duplicateType(type.return),
shapeId: type.shapeId,
isConstructor: type.isConstructor,
};
}
case 'Object': {

View File

@@ -32,5 +32,5 @@ export {
} from './HIRBuilder';
export {mergeConsecutiveBlocks} from './MergeConsecutiveBlocks';
export {mergeOverlappingReactiveScopesHIR} from './MergeOverlappingReactiveScopesHIR';
export {printFunction, printHIR, printFunctionWithOutlined} from './PrintHIR';
export {printFunction, printHIR} from './PrintHIR';
export {pruneUnusedLabelsHIR} from './PruneUnusedLabelsHIR';

View File

@@ -217,7 +217,6 @@ export function inferEffectDependencies(fn: HIRFunction): void {
// Step 2: push the inferred deps array as an argument of the useEffect
value.args.push({...depsPlace, effect: Effect.Freeze});
rewriteInstrs.set(instr.id, newInstructions);
fn.env.inferredEffectLocations.add(callee.loc);
} else if (loadGlobals.has(value.args[0].identifier.id)) {
// Global functions have no reactive dependencies, so we can insert an empty array
newInstructions.push({
@@ -228,7 +227,6 @@ export function inferEffectDependencies(fn: HIRFunction): void {
});
value.args.push({...depsPlace, effect: Effect.Freeze});
rewriteInstrs.set(instr.id, newInstructions);
fn.env.inferredEffectLocations.add(callee.loc);
}
}
}
@@ -251,7 +249,6 @@ export function inferEffectDependencies(fn: HIRFunction): void {
// Renumber instructions and fix scope ranges
markInstructionIds(fn.body);
fixScopeAndIdentifierRanges(fn.body);
fn.env.hasInferredEffect = true;
}
}

View File

@@ -11,10 +11,7 @@ import {
Identifier,
InstructionId,
InstructionKind,
isArrayType,
isMapType,
isRefOrRefValue,
isSetType,
makeInstructionId,
Place,
} from '../HIR/HIR';
@@ -93,17 +90,6 @@ function inferPlace(
infer(place, instrId);
}
return;
case Effect.ConditionallyMutateIterator: {
const identifier = place.identifier;
if (
!isArrayType(identifier) &&
!isSetType(identifier) &&
!isMapType(identifier)
) {
infer(place, instrId);
}
return;
}
case Effect.ConditionallyMutate:
case Effect.Mutate: {
infer(place, instrId);

View File

@@ -230,7 +230,6 @@ export function inferReactivePlaces(fn: HIRFunction): void {
case Effect.Capture:
case Effect.Store:
case Effect.ConditionallyMutate:
case Effect.ConditionallyMutateIterator:
case Effect.Mutate: {
if (isMutable(instruction, operand)) {
reactiveIdentifiers.markReactive(operand);

View File

@@ -12,7 +12,6 @@ import {
BasicBlock,
BlockId,
CallExpression,
NewExpression,
Effect,
FunctionEffect,
GeneratedSource,
@@ -24,15 +23,12 @@ import {
Phi,
Place,
SpreadPattern,
TInstruction,
Type,
ValueKind,
ValueReason,
isArrayType,
isMapType,
isMutableEffect,
isObjectType,
isSetType,
} from '../HIR/HIR';
import {FunctionSignature} from '../HIR/ObjectShape';
import {
@@ -42,6 +38,7 @@ import {
printSourceLocation,
} from '../HIR/PrintHIR';
import {
eachCallArgument,
eachInstructionOperand,
eachInstructionValueOperand,
eachPatternOperand,
@@ -254,7 +251,7 @@ type FreezeAction = {values: Set<InstructionValue>; reason: Set<ValueReason>};
// Maintains a mapping of top-level variables to the kind of value they hold
class InferenceState {
env: Environment;
#env: Environment;
// The kind of each value, based on its allocation site
#values: Map<InstructionValue, AbstractValue>;
@@ -270,7 +267,7 @@ class InferenceState {
values: Map<InstructionValue, AbstractValue>,
variables: Map<IdentifierId, Set<InstructionValue>>,
) {
this.env = env;
this.#env = env;
this.#values = values;
this.#variables = variables;
}
@@ -412,8 +409,8 @@ class InferenceState {
});
if (
value.kind === 'FunctionExpression' &&
(this.env.config.enablePreserveExistingMemoizationGuarantees ||
this.env.config.enableTransitivelyFreezeFunctionExpressions)
(this.#env.config.enablePreserveExistingMemoizationGuarantees ||
this.#env.config.enableTransitivelyFreezeFunctionExpressions)
) {
for (const operand of value.loweredFunc.func.context) {
const operandValues = this.#variables.get(operand.identifier.id);
@@ -471,25 +468,6 @@ class InferenceState {
}
break;
}
case Effect.ConditionallyMutateIterator: {
if (
valueKind.kind === ValueKind.Mutable ||
valueKind.kind === ValueKind.Context
) {
if (
isArrayType(place.identifier) ||
isSetType(place.identifier) ||
isMapType(place.identifier)
) {
effect = Effect.Capture;
} else {
effect = Effect.ConditionallyMutate;
}
} else {
effect = Effect.Read;
}
break;
}
case Effect.Mutate: {
effect = Effect.Mutate;
break;
@@ -612,7 +590,7 @@ class InferenceState {
return null;
} else {
return new InferenceState(
this.env,
this.#env,
nextValues ?? new Map(this.#values),
nextVariables ?? new Map(this.#variables),
);
@@ -626,7 +604,7 @@ class InferenceState {
*/
clone(): InferenceState {
return new InferenceState(
this.env,
this.#env,
new Map(this.#values),
new Map(this.#variables),
);
@@ -901,7 +879,9 @@ function inferBlock(
state.referenceAndRecordEffects(
freezeActions,
element.place,
Effect.ConditionallyMutateIterator,
isArrayType(element.place.identifier)
? Effect.Capture
: Effect.ConditionallyMutate,
ValueReason.Other,
);
} else if (element.kind === 'Identifier') {
@@ -924,12 +904,43 @@ function inferBlock(
break;
}
case 'NewExpression': {
inferCallEffects(
state,
instr as TInstruction<NewExpression>,
/**
* For new expressions, we infer a `read` effect on the Class / Function type
* to avoid extending mutable ranges of locally created classes, e.g.
* ```js
* const MyClass = getClass();
* const value = new MyClass(val1, val2)
* ^ (read) ^ (conditionally mutate)
* ```
*
* Risks:
* Classes / functions created during render could technically capture and
* mutate their enclosing scope, which we currently do not detect.
*/
const valueKind: AbstractValue = {
kind: ValueKind.Mutable,
reason: new Set([ValueReason.Other]),
context: new Set(),
};
state.referenceAndRecordEffects(
freezeActions,
getFunctionCallSignature(env, instrValue.callee.identifier.type),
instrValue.callee,
Effect.Read,
ValueReason.Other,
);
for (const operand of eachCallArgument(instrValue.args)) {
state.referenceAndRecordEffects(
freezeActions,
operand,
Effect.ConditionallyMutate,
ValueReason.Other,
);
}
state.initialize(instrValue, valueKind);
state.define(instr.lvalue, instrValue);
instr.lvalue.effect = Effect.ConditionallyMutate;
continuation = {kind: 'funeffects'};
break;
}
@@ -1227,12 +1238,62 @@ function inferBlock(
break;
}
case 'CallExpression': {
inferCallEffects(
state,
instr as TInstruction<CallExpression>,
freezeActions,
getFunctionCallSignature(env, instrValue.callee.identifier.type),
const signature = getFunctionCallSignature(
env,
instrValue.callee.identifier.type,
);
const effects =
signature !== null ? getFunctionEffects(instrValue, signature) : null;
const returnValueKind: AbstractValue =
signature !== null
? {
kind: signature.returnValueKind,
reason: new Set([
signature.returnValueReason ??
ValueReason.KnownReturnSignature,
]),
context: new Set(),
}
: {
kind: ValueKind.Mutable,
reason: new Set([ValueReason.Other]),
context: new Set(),
};
let hasCaptureArgument = false;
for (let i = 0; i < instrValue.args.length; i++) {
const arg = instrValue.args[i];
const place = arg.kind === 'Identifier' ? arg : arg.place;
state.referenceAndRecordEffects(
freezeActions,
place,
getArgumentEffect(effects != null ? effects[i] : null, arg),
ValueReason.Other,
);
hasCaptureArgument ||= place.effect === Effect.Capture;
}
if (signature !== null) {
state.referenceAndRecordEffects(
freezeActions,
instrValue.callee,
signature.calleeEffect,
ValueReason.Other,
);
} else {
state.referenceAndRecordEffects(
freezeActions,
instrValue.callee,
Effect.ConditionallyMutate,
ValueReason.Other,
);
}
hasCaptureArgument ||= instrValue.callee.effect === Effect.Capture;
state.initialize(instrValue, returnValueKind);
state.define(instr.lvalue, instrValue);
instr.lvalue.effect = hasCaptureArgument
? Effect.Store
: Effect.ConditionallyMutate;
continuation = {kind: 'funeffects'};
break;
}
@@ -1250,12 +1311,102 @@ function inferBlock(
Effect.Read,
ValueReason.Other,
);
inferCallEffects(
state,
instr as TInstruction<MethodCall>,
freezeActions,
getFunctionCallSignature(env, instrValue.property.identifier.type),
const signature = getFunctionCallSignature(
env,
instrValue.property.identifier.type,
);
const returnValueKind: AbstractValue =
signature !== null
? {
kind: signature.returnValueKind,
reason: new Set([
signature.returnValueReason ??
ValueReason.KnownReturnSignature,
]),
context: new Set(),
}
: {
kind: ValueKind.Mutable,
reason: new Set([ValueReason.Other]),
context: new Set(),
};
if (
signature !== null &&
signature.mutableOnlyIfOperandsAreMutable &&
areArgumentsImmutableAndNonMutating(state, instrValue.args)
) {
/*
* None of the args are mutable or mutate their params, we can downgrade to
* treating as all reads (except that the receiver may be captured)
*/
for (const arg of instrValue.args) {
const place = arg.kind === 'Identifier' ? arg : arg.place;
state.referenceAndRecordEffects(
freezeActions,
place,
Effect.Read,
ValueReason.Other,
);
}
state.referenceAndRecordEffects(
freezeActions,
instrValue.receiver,
Effect.Capture,
ValueReason.Other,
);
state.initialize(instrValue, returnValueKind);
state.define(instr.lvalue, instrValue);
instr.lvalue.effect =
instrValue.receiver.effect === Effect.Capture
? Effect.Store
: Effect.ConditionallyMutate;
continuation = {kind: 'funeffects'};
break;
}
const effects =
signature !== null ? getFunctionEffects(instrValue, signature) : null;
let hasCaptureArgument = false;
for (let i = 0; i < instrValue.args.length; i++) {
const arg = instrValue.args[i];
const place = arg.kind === 'Identifier' ? arg : arg.place;
/*
* If effects are inferred for an argument, we should fail invalid
* mutating effects
*/
state.referenceAndRecordEffects(
freezeActions,
place,
getArgumentEffect(effects != null ? effects[i] : null, arg),
ValueReason.Other,
);
hasCaptureArgument ||= place.effect === Effect.Capture;
}
if (signature !== null) {
state.referenceAndRecordEffects(
freezeActions,
instrValue.receiver,
signature.calleeEffect,
ValueReason.Other,
);
} else {
state.referenceAndRecordEffects(
freezeActions,
instrValue.receiver,
Effect.ConditionallyMutate,
ValueReason.Other,
);
}
hasCaptureArgument ||= instrValue.receiver.effect === Effect.Capture;
state.initialize(instrValue, returnValueKind);
state.define(instr.lvalue, instrValue);
instr.lvalue.effect = hasCaptureArgument
? Effect.Store
: Effect.ConditionallyMutate;
continuation = {kind: 'funeffects'};
break;
}
@@ -1662,13 +1813,7 @@ function inferBlock(
kind === ValueKind.Mutable || kind === ValueKind.Context;
let effect;
let valueKind: AbstractValue;
const iterator = instrValue.collection.identifier;
if (
!isMutable ||
isArrayType(iterator) ||
isMapType(iterator) ||
isSetType(iterator)
) {
if (!isMutable || isArrayType(instrValue.collection.identifier)) {
// Case 1, assume iterator is a separate mutable object
effect = {
kind: Effect.Read,
@@ -1709,7 +1854,7 @@ function inferBlock(
state.referenceAndRecordEffects(
freezeActions,
instrValue.iterator,
Effect.ConditionallyMutateIterator,
Effect.ConditionallyMutate,
ValueReason.Other,
);
/**
@@ -1838,7 +1983,7 @@ export function getFunctionCallSignature(
* @returns Inferred effects of function arguments, or null if inference fails.
*/
export function getFunctionEffects(
fn: MethodCall | CallExpression | NewExpression,
fn: MethodCall | CallExpression,
sig: FunctionSignature,
): Array<Effect> | null {
const results = [];
@@ -1867,33 +2012,6 @@ export function getFunctionEffects(
return results;
}
export function isKnownMutableEffect(effect: Effect): boolean {
switch (effect) {
case Effect.Store:
case Effect.ConditionallyMutate:
case Effect.ConditionallyMutateIterator:
case Effect.Mutate: {
return true;
}
case Effect.Unknown: {
CompilerError.invariant(false, {
reason: 'Unexpected unknown effect',
description: null,
loc: GeneratedSource,
suggestions: null,
});
}
case Effect.Read:
case Effect.Capture:
case Effect.Freeze: {
return false;
}
default: {
assertExhaustive(effect, `Unexpected effect \`${effect}\``);
}
}
}
/**
* Returns true if all of the arguments are both non-mutable (immutable or frozen)
* _and_ are not functions which might mutate their arguments. Note that function
@@ -1905,20 +2023,10 @@ function areArgumentsImmutableAndNonMutating(
args: MethodCall['args'],
): boolean {
for (const arg of args) {
if (arg.kind === 'Identifier' && arg.identifier.type.kind === 'Function') {
const fnShape = state.env.getFunctionSignature(arg.identifier.type);
if (fnShape != null) {
return (
!fnShape.positionalParams.some(isKnownMutableEffect) &&
(fnShape.restParam == null ||
!isKnownMutableEffect(fnShape.restParam))
);
}
}
const place = arg.kind === 'Identifier' ? arg : arg.place;
const kind = state.kind(place).kind;
switch (kind) {
case ValueKind.Global:
case ValueKind.Primitive:
case ValueKind.Frozen: {
/*
@@ -1929,10 +2037,6 @@ function areArgumentsImmutableAndNonMutating(
break;
}
default: {
/**
* Globals, module locals, and other locally defined functions may
* mutate their arguments.
*/
return false;
}
}
@@ -1975,128 +2079,9 @@ function getArgumentEffect(
});
}
// effects[i] is Effect.Capture | Effect.Read | Effect.Store
return Effect.ConditionallyMutateIterator;
return Effect.ConditionallyMutate;
}
} else {
return Effect.ConditionallyMutate;
}
}
function inferCallEffects(
state: InferenceState,
instr:
| TInstruction<CallExpression>
| TInstruction<MethodCall>
| TInstruction<NewExpression>,
freezeActions: Array<FreezeAction>,
signature: FunctionSignature | null,
): void {
const instrValue = instr.value;
const returnValueKind: AbstractValue =
signature !== null
? {
kind: signature.returnValueKind,
reason: new Set([
signature.returnValueReason ?? ValueReason.KnownReturnSignature,
]),
context: new Set(),
}
: {
kind: ValueKind.Mutable,
reason: new Set([ValueReason.Other]),
context: new Set(),
};
if (
instrValue.kind === 'MethodCall' &&
signature !== null &&
signature.mutableOnlyIfOperandsAreMutable &&
areArgumentsImmutableAndNonMutating(state, instrValue.args)
) {
/*
* None of the args are mutable or mutate their params, we can downgrade to
* treating as all reads (except that the receiver may be captured)
*/
for (const arg of instrValue.args) {
const place = arg.kind === 'Identifier' ? arg : arg.place;
state.referenceAndRecordEffects(
freezeActions,
place,
Effect.Read,
ValueReason.Other,
);
}
state.referenceAndRecordEffects(
freezeActions,
instrValue.receiver,
Effect.Capture,
ValueReason.Other,
);
state.initialize(instrValue, returnValueKind);
state.define(instr.lvalue, instrValue);
instr.lvalue.effect =
instrValue.receiver.effect === Effect.Capture
? Effect.Store
: Effect.ConditionallyMutate;
return;
}
const effects =
signature !== null ? getFunctionEffects(instrValue, signature) : null;
let hasCaptureArgument = false;
for (let i = 0; i < instrValue.args.length; i++) {
const arg = instrValue.args[i];
const place = arg.kind === 'Identifier' ? arg : arg.place;
/*
* If effects are inferred for an argument, we should fail invalid
* mutating effects
*/
state.referenceAndRecordEffects(
freezeActions,
place,
getArgumentEffect(effects != null ? effects[i] : null, arg),
ValueReason.Other,
);
hasCaptureArgument ||= place.effect === Effect.Capture;
}
const callee =
instrValue.kind === 'MethodCall' ? instrValue.receiver : instrValue.callee;
if (signature !== null) {
state.referenceAndRecordEffects(
freezeActions,
callee,
signature.calleeEffect,
ValueReason.Other,
);
} else {
/**
* For new expressions, we infer a `read` effect on the Class / Function type
* to avoid extending mutable ranges of locally created classes, e.g.
* ```js
* const MyClass = getClass();
* const value = new MyClass(val1, val2)
* ^ (read) ^ (conditionally mutate)
* ```
*
* Risks:
* Classes / functions created during render could technically capture and
* mutate their enclosing scope, which we currently do not detect.
*/
state.referenceAndRecordEffects(
freezeActions,
callee,
instrValue.kind === 'NewExpression'
? Effect.Read
: Effect.ConditionallyMutate,
ValueReason.Other,
);
}
hasCaptureArgument ||= callee.effect === Effect.Capture;
state.initialize(instrValue, returnValueKind);
state.define(instr.lvalue, instrValue);
instr.lvalue.effect = hasCaptureArgument
? Effect.Store
: Effect.ConditionallyMutate;
}

View File

@@ -18,7 +18,6 @@ import {
Instruction,
LoadGlobal,
LoadLocal,
NonLocalImportSpecifier,
Place,
PropertyLoad,
isUseContextHookType,
@@ -36,7 +35,7 @@ import {inferTypes} from '../TypeInference';
export function lowerContextAccess(
fn: HIRFunction,
loweredContextCalleeConfig: ExternalFunction,
loweredContextCallee: ExternalFunction,
): void {
const contextAccess: Map<IdentifierId, CallExpression> = new Map();
const contextKeys: Map<IdentifierId, Array<string>> = new Map();
@@ -80,8 +79,6 @@ export function lowerContextAccess(
}
}
let importLoweredContextCallee: NonLocalImportSpecifier | null = null;
if (contextAccess.size > 0 && contextKeys.size > 0) {
for (const [, block] of fn.body.blocks) {
let nextInstructions: Array<Instruction> | null = null;
@@ -94,13 +91,9 @@ export function lowerContextAccess(
isUseContextHookType(value.callee.identifier) &&
contextKeys.has(lvalue.identifier.id)
) {
importLoweredContextCallee ??=
fn.env.programContext.addImportSpecifier(
loweredContextCalleeConfig,
);
const loweredContextCalleeInstr = emitLoadLoweredContextCallee(
fn.env,
importLoweredContextCallee,
loweredContextCallee,
);
if (nextInstructions === null) {
@@ -129,16 +122,21 @@ export function lowerContextAccess(
}
markInstructionIds(fn.body);
inferTypes(fn);
fn.env.hasLoweredContextAccess = true;
}
}
function emitLoadLoweredContextCallee(
env: Environment,
importedLowerContextCallee: NonLocalImportSpecifier,
loweredContextCallee: ExternalFunction,
): Instruction {
const loadGlobal: LoadGlobal = {
kind: 'LoadGlobal',
binding: {...importedLowerContextCallee},
binding: {
kind: 'ImportNamespace',
module: loweredContextCallee.source,
name: loweredContextCallee.importSpecifierName,
},
loc: GeneratedSource,
};

View File

@@ -196,7 +196,7 @@ function process(
return null;
}
const props = collectProps(fn.env, jsx);
const props = collectProps(jsx);
if (!props) return null;
const outlinedTag = fn.env.generateGloballyUniqueIdentifierName(null).value;
@@ -217,7 +217,6 @@ type OutlinedJsxAttribute = {
};
function collectProps(
env: Environment,
instructions: Array<JsxInstruction>,
): Array<OutlinedJsxAttribute> | null {
let id = 1;
@@ -228,7 +227,6 @@ function collectProps(
newName = `${oldName}${id++}`;
}
seen.add(newName);
env.programContext.addNewReference(newName);
return newName;
}

View File

@@ -52,8 +52,7 @@ import {assertExhaustive} from '../Utils/utils';
import {buildReactiveFunction} from './BuildReactiveFunction';
import {SINGLE_CHILD_FBT_TAGS} from './MemoizeFbtAndMacroOperandsInSameScope';
import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors';
import {EMIT_FREEZE_GLOBAL_GATING, ReactFunctionType} from '../HIR/Environment';
import {ProgramContext} from '../Entrypoint';
import {ReactFunctionType} from '../HIR/Environment';
export const MEMO_CACHE_SENTINEL = 'react.memo_cache_sentinel';
export const EARLY_RETURN_SENTINEL = 'react.early_return_sentinel';
@@ -101,10 +100,9 @@ export type CodegenFunction = {
}>;
/**
* This is true if the compiler has compiled inferred effect dependencies
* This is true if the compiler has the lowered useContext calls.
*/
hasInferredEffect: boolean;
inferredEffectLocations: Set<SourceLocation>;
hasLoweredContextAccess: boolean;
/**
* This is true if the compiler has compiled a fire to a useFire call
@@ -162,7 +160,6 @@ export function codegenFunction(
compiled.body = t.blockStatement([
createHookGuard(
hookGuard,
fn.env.programContext,
compiled.body.body,
GuardKind.PushHookGuard,
GuardKind.PopHookGuard,
@@ -173,15 +170,13 @@ export function codegenFunction(
const cacheCount = compiled.memoSlotsUsed;
if (cacheCount !== 0) {
const preface: Array<t.Statement> = [];
const useMemoCacheIdentifier =
fn.env.programContext.addMemoCacheImport().name;
// The import declaration for `useMemoCache` is inserted in the Babel plugin
preface.push(
t.variableDeclaration('const', [
t.variableDeclarator(
t.identifier(cx.synthesizeName('$')),
t.callExpression(t.identifier(useMemoCacheIdentifier), [
t.callExpression(t.identifier(fn.env.useMemoCacheIdentifier), [
t.numericLiteral(cacheCount),
]),
),
@@ -264,54 +259,34 @@ export function codegenFunction(
* Technically, this is a conditional hook call. However, we expect
* __DEV__ and gating identifier to be runtime constants
*/
const gating =
emitInstrumentForget.gating != null
? t.identifier(
fn.env.programContext.addImportSpecifier(
emitInstrumentForget.gating,
).name,
)
: null;
const globalGating =
let gating: t.Expression;
if (
emitInstrumentForget.gating != null &&
emitInstrumentForget.globalGating != null
? t.identifier(emitInstrumentForget.globalGating)
: null;
if (emitInstrumentForget.globalGating != null) {
const assertResult = fn.env.programContext.assertGlobalBinding(
emitInstrumentForget.globalGating,
) {
gating = t.logicalExpression(
'&&',
t.identifier(emitInstrumentForget.globalGating),
t.identifier(emitInstrumentForget.gating.importSpecifierName),
);
if (assertResult.isErr()) {
return assertResult;
}
}
let ifTest: t.Expression;
if (gating != null && globalGating != null) {
ifTest = t.logicalExpression('&&', globalGating, gating);
} else if (gating != null) {
ifTest = gating;
} else if (emitInstrumentForget.gating != null) {
gating = t.identifier(emitInstrumentForget.gating.importSpecifierName);
} else {
CompilerError.invariant(globalGating != null, {
CompilerError.invariant(emitInstrumentForget.globalGating != null, {
reason:
'Bad config not caught! Expected at least one of gating or globalGating',
loc: null,
suggestions: null,
});
ifTest = globalGating;
gating = t.identifier(emitInstrumentForget.globalGating);
}
const instrumentFnIdentifier = fn.env.programContext.addImportSpecifier(
emitInstrumentForget.fn,
).name;
const test: t.IfStatement = t.ifStatement(
ifTest,
gating,
t.expressionStatement(
t.callExpression(t.identifier(instrumentFnIdentifier), [
t.stringLiteral(fn.id),
t.stringLiteral(fn.env.filename ?? ''),
]),
t.callExpression(
t.identifier(emitInstrumentForget.fn.importSpecifierName),
[t.stringLiteral(fn.id), t.stringLiteral(fn.env.filename ?? '')],
),
),
);
compiled.body.body.unshift(test);
@@ -388,9 +363,8 @@ function codegenReactiveFunction(
prunedMemoBlocks: countMemoBlockVisitor.prunedMemoBlocks,
prunedMemoValues: countMemoBlockVisitor.prunedMemoValues,
outlined: [],
hasLoweredContextAccess: fn.env.hasLoweredContextAccess,
hasFireRewrite: fn.env.hasFireRewrite,
hasInferredEffect: fn.env.hasInferredEffect,
inferredEffectLocations: fn.env.inferredEffectLocations,
});
}
@@ -579,18 +553,13 @@ function codegenBlockNoReset(
function wrapCacheDep(cx: Context, value: t.Expression): t.Expression {
if (cx.env.config.enableEmitFreeze != null && cx.env.isInferredMemoEnabled) {
const emitFreezeIdentifier = cx.env.programContext.addImportSpecifier(
cx.env.config.enableEmitFreeze,
).name;
cx.env.programContext
.assertGlobalBinding(EMIT_FREEZE_GLOBAL_GATING, cx.env.scope)
.unwrap();
// The import declaration for emitFreeze is inserted in the Babel plugin
return t.conditionalExpression(
t.identifier(EMIT_FREEZE_GLOBAL_GATING),
t.callExpression(t.identifier(emitFreezeIdentifier), [
value,
t.stringLiteral(cx.fnName),
]),
t.identifier('__DEV__'),
t.callExpression(
t.identifier(cx.env.config.enableEmitFreeze.importSpecifierName),
[value, t.stringLiteral(cx.fnName)],
),
value,
);
} else {
@@ -744,14 +713,16 @@ function codegenReactiveScope(
let computationBlock = codegenBlock(cx, block);
let memoStatement;
const detectionFunction = cx.env.config.enableChangeDetectionForDebugging;
if (detectionFunction != null && changeExpressions.length > 0) {
if (
cx.env.config.enableChangeDetectionForDebugging != null &&
changeExpressions.length > 0
) {
const loc =
typeof scope.loc === 'symbol'
? 'unknown location'
: `(${scope.loc.start.line}:${scope.loc.end.line})`;
const importedDetectionFunctionIdentifier =
cx.env.programContext.addImportSpecifier(detectionFunction).name;
const detectionFunction =
cx.env.config.enableChangeDetectionForDebugging.importSpecifierName;
const cacheLoadOldValueStatements: Array<t.Statement> = [];
const changeDetectionStatements: Array<t.Statement> = [];
const idempotenceDetectionStatements: Array<t.Statement> = [];
@@ -773,7 +744,7 @@ function codegenReactiveScope(
);
changeDetectionStatements.push(
t.expressionStatement(
t.callExpression(t.identifier(importedDetectionFunctionIdentifier), [
t.callExpression(t.identifier(detectionFunction), [
t.identifier(loadName),
t.cloneNode(name, true),
t.stringLiteral(name.name),
@@ -785,7 +756,7 @@ function codegenReactiveScope(
);
idempotenceDetectionStatements.push(
t.expressionStatement(
t.callExpression(t.identifier(importedDetectionFunctionIdentifier), [
t.callExpression(t.identifier(detectionFunction), [
t.cloneNode(slot, true),
t.cloneNode(name, true),
t.stringLiteral(name.name),
@@ -1547,15 +1518,15 @@ const createStringLiteral = withLoc(t.stringLiteral);
function createHookGuard(
guard: ExternalFunction,
context: ProgramContext,
stmts: Array<t.Statement>,
before: GuardKind,
after: GuardKind,
): t.TryStatement {
const guardFnName = context.addImportSpecifier(guard).name;
function createHookGuardImpl(kind: number): t.ExpressionStatement {
return t.expressionStatement(
t.callExpression(t.identifier(guardFnName), [t.numericLiteral(kind)]),
t.callExpression(t.identifier(guard.importSpecifierName), [
t.numericLiteral(kind),
]),
);
}
@@ -1605,7 +1576,6 @@ function createCallExpression(
t.blockStatement([
createHookGuard(
hookGuard,
env.programContext,
[t.returnStatement(callExpr)],
GuardKind.AllowHook,
GuardKind.DisallowHook,

View File

@@ -5,7 +5,6 @@
* LICENSE file in the root directory of this source tree.
*/
import {CompilerError} from '..';
import {
DeclarationId,
InstructionKind,
@@ -28,17 +27,7 @@ export function pruneHoistedContexts(fn: ReactiveFunction): void {
visitReactiveFunction(fn, new Visitor(), hoistedIdentifiers);
}
const REWRITTEN_HOISTED_CONST: unique symbol = Symbol(
'REWRITTEN_HOISTED_CONST',
);
const REWRITTEN_HOISTED_LET: unique symbol = Symbol('REWRITTEN_HOISTED_LET');
type HoistedIdentifiers = Map<
DeclarationId,
| InstructionKind
| typeof REWRITTEN_HOISTED_CONST
| typeof REWRITTEN_HOISTED_LET
>;
type HoistedIdentifiers = Map<DeclarationId, InstructionKind>;
class Visitor extends ReactiveFunctionTransform<HoistedIdentifiers> {
override transformInstruction(
@@ -46,10 +35,6 @@ class Visitor extends ReactiveFunctionTransform<HoistedIdentifiers> {
state: HoistedIdentifiers,
): Transformed<ReactiveStatement> {
this.visitInstruction(instruction, state);
/**
* Remove hoisted declarations to preserve TDZ
*/
if (
instruction.value.kind === 'DeclareContext' &&
instruction.value.lvalue.kind === 'HoistedConst'
@@ -83,75 +68,31 @@ class Visitor extends ReactiveFunctionTransform<HoistedIdentifiers> {
return {kind: 'remove'};
}
if (instruction.value.kind === 'StoreContext') {
if (
instruction.value.kind === 'StoreContext' &&
state.has(instruction.value.lvalue.place.identifier.declarationId)
) {
const kind = state.get(
instruction.value.lvalue.place.identifier.declarationId,
);
if (kind != null) {
CompilerError.invariant(kind !== REWRITTEN_HOISTED_CONST, {
reason: 'Expected exactly one store to a hoisted const variable',
loc: instruction.loc,
});
if (
kind === InstructionKind.Const ||
kind === InstructionKind.Function
) {
state.set(
instruction.value.lvalue.place.identifier.declarationId,
REWRITTEN_HOISTED_CONST,
);
return {
kind: 'replace',
)!;
return {
kind: 'replace',
value: {
kind: 'instruction',
instruction: {
...instruction,
value: {
kind: 'instruction',
instruction: {
...instruction,
value: {
...instruction.value,
lvalue: {
...instruction.value.lvalue,
kind,
},
type: null,
kind: 'StoreLocal',
},
...instruction.value,
lvalue: {
...instruction.value.lvalue,
kind,
},
type: null,
kind: 'StoreLocal',
},
};
} else if (kind !== REWRITTEN_HOISTED_LET) {
/**
* Context variables declared with let may have reassignments. Only
* insert a `DeclareContext` for the first encountered `StoreContext`
* instruction.
*/
state.set(
instruction.value.lvalue.place.identifier.declarationId,
REWRITTEN_HOISTED_LET,
);
return {
kind: 'replace-many',
value: [
{
kind: 'instruction',
instruction: {
id: instruction.id,
lvalue: null,
value: {
kind: 'DeclareContext',
lvalue: {
kind: InstructionKind.Let,
place: {...instruction.value.lvalue.place},
},
loc: instruction.value.loc,
},
loc: instruction.loc,
},
},
{kind: 'instruction', instruction},
],
};
}
}
},
},
};
}
return {kind: 'keep'};

View File

@@ -5,7 +5,6 @@
* LICENSE file in the root directory of this source tree.
*/
import {ProgramContext} from '..';
import {CompilerError} from '../CompilerError';
import {
DeclarationId,
@@ -48,7 +47,7 @@ import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors';
*/
export function renameVariables(fn: ReactiveFunction): Set<string> {
const globals = collectReferencedGlobals(fn);
const scopes = new Scopes(globals, fn.env.programContext);
const scopes = new Scopes(globals);
renameVariablesImpl(fn, new Visitor(), scopes);
return new Set([...scopes.names, ...globals]);
}
@@ -125,12 +124,10 @@ class Scopes {
#seen: Map<DeclarationId, IdentifierName> = new Map();
#stack: Array<Map<string, DeclarationId>> = [new Map()];
#globals: Set<string>;
#programContext: ProgramContext;
names: Set<ValidIdentifierName> = new Set();
constructor(globals: Set<string>, programContext: ProgramContext) {
constructor(globals: Set<string>) {
this.#globals = globals;
this.#programContext = programContext;
}
visit(identifier: Identifier): void {
@@ -159,7 +156,6 @@ class Scopes {
name = `${originalName.value}$${id++}`;
}
}
this.#programContext.addNewReference(name);
const identifierName = makeIdentifierName(name);
identifier.name = identifierName;
this.#seen.set(identifier.declarationId, identifierName);

View File

@@ -14,10 +14,7 @@ export {extractScopeDeclarationsFromDestructuring} from './ExtractScopeDeclarati
export {inferReactiveScopeVariables} from './InferReactiveScopeVariables';
export {memoizeFbtAndMacroOperandsInSameScope} from './MemoizeFbtAndMacroOperandsInSameScope';
export {mergeReactiveScopesThatInvalidateTogether} from './MergeReactiveScopesThatInvalidateTogether';
export {
printReactiveFunction,
printReactiveFunctionWithOutlined,
} from './PrintReactiveFunction';
export {printReactiveFunction} from './PrintReactiveFunction';
export {promoteUsedTemporaries} from './PromoteUsedTemporaries';
export {propagateEarlyReturns} from './PropagateEarlyReturns';
export {pruneAllReactiveScopes} from './PruneAllReactiveScopes';

View File

@@ -28,7 +28,6 @@ import {
isUseEffectHookType,
LoadLocal,
makeInstructionId,
NonLocalImportSpecifier,
Place,
promoteTemporary,
} from '../HIR';
@@ -37,7 +36,6 @@ import {getOrInsertWith} from '../Utils/utils';
import {BuiltInFireId, DefaultNonmutatingHook} from '../HIR/ObjectShape';
import {eachInstructionOperand} from '../HIR/visitors';
import {printSourceLocationLine} from '../HIR/PrintHIR';
import {USE_FIRE_FUNCTION_NAME} from '../HIR/Environment';
/*
* TODO(jmbrown):
@@ -58,7 +56,6 @@ export function transformFire(fn: HIRFunction): void {
}
function replaceFireFunctions(fn: HIRFunction, context: Context): void {
let importedUseFire: NonLocalImportSpecifier | null = null;
let hasRewrite = false;
for (const [, block] of fn.body.blocks) {
const rewriteInstrs = new Map<InstructionId, Array<Instruction>>();
@@ -90,15 +87,7 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void {
] of capturedCallees.entries()) {
if (!context.hasCalleeWithInsertedFire(fireCalleePlace)) {
context.addCalleeWithInsertedFire(fireCalleePlace);
importedUseFire ??= fn.env.programContext.addImportSpecifier({
source: fn.env.programContext.reactRuntimeModule,
importSpecifierName: USE_FIRE_FUNCTION_NAME,
});
const loadUseFireInstr = makeLoadUseFireInstruction(
fn.env,
importedUseFire,
);
const loadUseFireInstr = makeLoadUseFireInstruction(fn.env);
const loadFireCalleeInstr = makeLoadFireCalleeInstruction(
fn.env,
fireCalleeInfo.capturedCalleeIdentifier,
@@ -415,16 +404,18 @@ function ensureNoMoreFireUses(fn: HIRFunction, context: Context): void {
}
}
function makeLoadUseFireInstruction(
env: Environment,
importedLoadUseFire: NonLocalImportSpecifier,
): Instruction {
function makeLoadUseFireInstruction(env: Environment): Instruction {
const useFirePlace = createTemporaryPlace(env, GeneratedSource);
useFirePlace.effect = Effect.Read;
useFirePlace.identifier.type = DefaultNonmutatingHook;
const instrValue: InstructionValue = {
kind: 'LoadGlobal',
binding: {...importedLoadUseFire},
binding: {
kind: 'ImportSpecifier',
name: 'useFire',
module: 'react',
imported: 'useFire',
},
loc: GeneratedSource,
};
return {

View File

@@ -261,7 +261,6 @@ function* generateInstructionTypes(
kind: 'Function',
shapeId: null,
return: returnType,
isConstructor: false,
});
yield equation(left, returnType);
break;
@@ -278,7 +277,6 @@ function* generateInstructionTypes(
kind: 'Function',
shapeId: null,
return: returnType,
isConstructor: false,
});
yield equation(left, returnType);
break;
@@ -335,7 +333,6 @@ function* generateInstructionTypes(
kind: 'Function',
return: returnType,
shapeId: null,
isConstructor: false,
});
yield equation(left, returnType);
@@ -408,7 +405,6 @@ function* generateInstructionTypes(
kind: 'Function',
shapeId: BuiltInFunctionId,
return: value.loweredFunc.func.returnType,
isConstructor: false,
});
break;
}
@@ -429,20 +425,9 @@ function* generateInstructionTypes(
yield equation(left, {kind: 'Object', shapeId: BuiltInJsxId});
break;
}
case 'NewExpression': {
const returnType = makeType();
yield equation(value.callee.identifier.type, {
kind: 'Function',
return: returnType,
shapeId: null,
isConstructor: true,
});
yield equation(left, returnType);
break;
}
case 'PropertyStore':
case 'DeclareLocal':
case 'NewExpression':
case 'RegExpLiteral':
case 'MetaProperty':
case 'ComputedStore':
@@ -520,11 +505,7 @@ class Unifier {
return;
}
if (
tB.kind === 'Function' &&
tA.kind === 'Function' &&
tA.isConstructor === tB.isConstructor
) {
if (tB.kind === 'Function' && tA.kind === 'Function') {
this.unify(tA.return, tB.return);
return;
}
@@ -667,7 +648,6 @@ class Unifier {
kind: 'Function',
return: returnType,
shapeId: type.shapeId,
isConstructor: type.isConstructor,
};
}
case 'ObjectMethod':

View File

@@ -50,55 +50,28 @@ import { useIdentity, Stringify } from "shared-runtime";
* (2) the 1st argument might mutate its callee
*/
function Component(t0) {
const $ = _c(10);
const $ = _c(4);
const { value } = t0;
let t1;
let t2;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t1 = { value: "foo" };
t2 = { value: "bar" };
$[0] = t1;
$[1] = t2;
} else {
t1 = $[0];
t2 = $[1];
}
let t3;
if ($[2] !== value) {
t3 = [t1, t2, { value }];
$[2] = value;
$[3] = t3;
} else {
t3 = $[3];
}
const arr = t3;
const arr = [{ value: "foo" }, { value: "bar" }, { value }];
useIdentity();
let t4;
if ($[4] !== arr) {
t4 = Array.from(arr, _temp);
$[4] = arr;
$[5] = t4;
const derived = Array.from(arr, _temp);
let t1;
if ($[0] !== derived) {
t1 = derived.at(-1);
$[0] = derived;
$[1] = t1;
} else {
t4 = $[5];
t1 = $[1];
}
const derived = t4;
let t5;
if ($[6] !== derived) {
t5 = derived.at(-1);
$[6] = derived;
$[7] = t5;
let t2;
if ($[2] !== t1) {
t2 = <Stringify>{t1}</Stringify>;
$[2] = t1;
$[3] = t2;
} else {
t5 = $[7];
t2 = $[3];
}
let t6;
if ($[8] !== t5) {
t6 = <Stringify>{t5}</Stringify>;
$[8] = t5;
$[9] = t6;
} else {
t6 = $[9];
}
return t6;
return t2;
}
function _temp(x, idx) {
return { ...x, id: idx };

View File

@@ -50,55 +50,28 @@ import { useIdentity, Stringify } from "shared-runtime";
* (2) the 1st argument might mutate its callee
*/
function Component(t0) {
const $ = _c(10);
const $ = _c(4);
const { value } = t0;
let t1;
let t2;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t1 = { value: "foo" };
t2 = { value: "bar" };
$[0] = t1;
$[1] = t2;
} else {
t1 = $[0];
t2 = $[1];
}
let t3;
if ($[2] !== value) {
t3 = [t1, t2, { value }];
$[2] = value;
$[3] = t3;
} else {
t3 = $[3];
}
const arr = t3;
const arr = [{ value: "foo" }, { value: "bar" }, { value }];
useIdentity();
let t4;
if ($[4] !== arr) {
t4 = Array.from(arr);
$[4] = arr;
$[5] = t4;
const derived = Array.from(arr);
let t1;
if ($[0] !== derived) {
t1 = derived.at(-1);
$[0] = derived;
$[1] = t1;
} else {
t4 = $[5];
t1 = $[1];
}
const derived = t4;
let t5;
if ($[6] !== derived) {
t5 = derived.at(-1);
$[6] = derived;
$[7] = t5;
let t2;
if ($[2] !== t1) {
t2 = <Stringify>{t1}</Stringify>;
$[2] = t1;
$[3] = t2;
} else {
t5 = $[7];
t2 = $[3];
}
let t6;
if ($[8] !== t5) {
t6 = <Stringify>{t5}</Stringify>;
$[8] = t5;
$[9] = t6;
} else {
t6 = $[9];
}
return t6;
return t2;
}
export const FIXTURE_ENTRYPOINT = {

View File

@@ -7,19 +7,14 @@ import {mutateAndReturn, Stringify, useIdentity} from 'shared-runtime';
function Component({value}) {
const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
useIdentity();
const derived = Array.from(arr).map(mutateAndReturn);
return (
<Stringify>
{derived.at(0)}
{derived.at(-1)}
</Stringify>
);
const derived = Array.from(arr, mutateAndReturn);
return <Stringify>{derived.at(-1)}</Stringify>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: 5}],
sequentialRenders: [{value: 5}, {value: 6}, {value: 6}, {value: 7}],
sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
};
```
@@ -31,54 +26,39 @@ import { c as _c } from "react/compiler-runtime";
import { mutateAndReturn, Stringify, useIdentity } from "shared-runtime";
function Component(t0) {
const $ = _c(7);
const $ = _c(4);
const { value } = t0;
const arr = [{ value: "foo" }, { value: "bar" }, { value }];
useIdentity();
const derived = Array.from(arr).map(mutateAndReturn);
const derived = Array.from(arr, mutateAndReturn);
let t1;
if ($[0] !== derived) {
t1 = derived.at(0);
t1 = derived.at(-1);
$[0] = derived;
$[1] = t1;
} else {
t1 = $[1];
}
let t2;
if ($[2] !== derived) {
t2 = derived.at(-1);
$[2] = derived;
if ($[2] !== t1) {
t2 = <Stringify>{t1}</Stringify>;
$[2] = t1;
$[3] = t2;
} else {
t2 = $[3];
}
let t3;
if ($[4] !== t1 || $[5] !== t2) {
t3 = (
<Stringify>
{t1}
{t2}
</Stringify>
);
$[4] = t1;
$[5] = t2;
$[6] = t3;
} else {
t3 = $[6];
}
return t3;
return t2;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ value: 5 }],
sequentialRenders: [{ value: 5 }, { value: 6 }, { value: 6 }, { value: 7 }],
sequentialRenders: [{ value: 5 }, { value: 6 }, { value: 6 }],
};
```
### Eval output
(kind: ok) <div>{"children":[{"value":"foo","wat0":"joe"},{"value":5,"wat0":"joe"}]}</div>
<div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>
<div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>
<div>{"children":[{"value":"foo","wat0":"joe"},{"value":7,"wat0":"joe"}]}</div>
(kind: ok) <div>{"children":{"value":5,"wat0":"joe"}}</div>
<div>{"children":{"value":6,"wat0":"joe"}}</div>
<div>{"children":{"value":6,"wat0":"joe"}}</div>

View File

@@ -3,17 +3,12 @@ import {mutateAndReturn, Stringify, useIdentity} from 'shared-runtime';
function Component({value}) {
const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
useIdentity();
const derived = Array.from(arr).map(mutateAndReturn);
return (
<Stringify>
{derived.at(0)}
{derived.at(-1)}
</Stringify>
);
const derived = Array.from(arr, mutateAndReturn);
return <Stringify>{derived.at(-1)}</Stringify>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: 5}],
sequentialRenders: [{value: 5}, {value: 6}, {value: 6}, {value: 7}],
sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
};

View File

@@ -1,73 +0,0 @@
## Input
```javascript
import * as React from 'react';
import {someImport} from 'react/compiler-runtime';
import {calculateExpensiveNumber} from 'shared-runtime';
function Component(props) {
const [x] = React.useState(0);
const expensiveNumber = React.useMemo(() => calculateExpensiveNumber(x), [x]);
return (
<div>
{expensiveNumber}
{`${someImport}`}
</div>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [],
};
```
## Code
```javascript
import * as React from "react";
import { someImport, c as _c } from "react/compiler-runtime";
import { calculateExpensiveNumber } from "shared-runtime";
function Component(props) {
const $ = _c(4);
const [x] = React.useState(0);
let t0;
let t1;
if ($[0] !== x) {
t1 = calculateExpensiveNumber(x);
$[0] = x;
$[1] = t1;
} else {
t1 = $[1];
}
t0 = t1;
const expensiveNumber = t0;
let t2;
if ($[2] !== expensiveNumber) {
t2 = (
<div>
{expensiveNumber}
{`${someImport}`}
</div>
);
$[2] = expensiveNumber;
$[3] = t2;
} else {
t2 = $[3];
}
return t2;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [],
};
```
### Eval output
(kind: ok) <div>0undefined</div>

View File

@@ -1,20 +0,0 @@
import * as React from 'react';
import {someImport} from 'react/compiler-runtime';
import {calculateExpensiveNumber} from 'shared-runtime';
function Component(props) {
const [x] = React.useState(0);
const expensiveNumber = React.useMemo(() => calculateExpensiveNumber(x), [x]);
return (
<div>
{expensiveNumber}
{`${someImport}`}
</div>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [],
};

View File

@@ -14,9 +14,9 @@ function useFoo(props) {
```javascript
import {
makeReadOnly,
shouldInstrument,
useRenderCounter,
shouldInstrument,
makeReadOnly,
} from "react-compiler-runtime";
import { c as _c } from "react/compiler-runtime"; // @enableEmitFreeze @enableEmitInstrumentForget

View File

@@ -23,7 +23,7 @@ function Foo(props) {
## Code
```javascript
import { shouldInstrument, useRenderCounter } from "react-compiler-runtime";
import { useRenderCounter, shouldInstrument } from "react-compiler-runtime";
import { c as _c } from "react/compiler-runtime"; // @enableEmitInstrumentForget @compilationMode(annotation)
function Bar(props) {

View File

@@ -1,97 +0,0 @@
## Input
```javascript
// @enableEmitInstrumentForget @compilationMode(annotation)
import {identity} from 'shared-runtime';
function Bar(props) {
'use forget';
const shouldInstrument = identity(null);
const _shouldInstrument = identity(null);
const _x2 = () => {
const _shouldInstrument2 = 'hello world';
return identity({_shouldInstrument2});
};
return (
<div style={shouldInstrument} other={_shouldInstrument}>
{props.bar}
</div>
);
}
function Foo(props) {
'use forget';
return <Foo>{props.bar}</Foo>;
}
```
## Code
```javascript
import {
shouldInstrument as _shouldInstrument3,
useRenderCounter,
} from "react-compiler-runtime";
import { c as _c } from "react/compiler-runtime"; // @enableEmitInstrumentForget @compilationMode(annotation)
import { identity } from "shared-runtime";
function Bar(props) {
"use forget";
if (DEV && _shouldInstrument3)
useRenderCounter("Bar", "/conflict-codegen-instrument-forget.ts");
const $ = _c(4);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = identity(null);
$[0] = t0;
} else {
t0 = $[0];
}
const shouldInstrument = t0;
let t1;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t1 = identity(null);
$[1] = t1;
} else {
t1 = $[1];
}
const _shouldInstrument = t1;
let t2;
if ($[2] !== props.bar) {
t2 = (
<div style={shouldInstrument} other={_shouldInstrument}>
{props.bar}
</div>
);
$[2] = props.bar;
$[3] = t2;
} else {
t2 = $[3];
}
return t2;
}
function Foo(props) {
"use forget";
if (DEV && _shouldInstrument3)
useRenderCounter("Foo", "/conflict-codegen-instrument-forget.ts");
const $ = _c(2);
let t0;
if ($[0] !== props.bar) {
t0 = <Foo>{props.bar}</Foo>;
$[0] = props.bar;
$[1] = t0;
} else {
t0 = $[1];
}
return t0;
}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -1,23 +0,0 @@
// @enableEmitInstrumentForget @compilationMode(annotation)
import {identity} from 'shared-runtime';
function Bar(props) {
'use forget';
const shouldInstrument = identity(null);
const _shouldInstrument = identity(null);
const _x2 = () => {
const _shouldInstrument2 = 'hello world';
return identity({_shouldInstrument2});
};
return (
<div style={shouldInstrument} other={_shouldInstrument}>
{props.bar}
</div>
);
}
function Foo(props) {
'use forget';
return <Foo>{props.bar}</Foo>;
}

View File

@@ -1,37 +0,0 @@
## Input
```javascript
// @enableEmitFreeze @instrumentForget
let makeReadOnly = 'conflicting identifier';
function useFoo(props) {
return foo(props.x);
}
```
## Code
```javascript
import { makeReadOnly as _makeReadOnly } from "react-compiler-runtime";
import { c as _c } from "react/compiler-runtime"; // @enableEmitFreeze @instrumentForget
let makeReadOnly = "conflicting identifier";
function useFoo(props) {
const $ = _c(2);
let t0;
if ($[0] !== props.x) {
t0 = foo(props.x);
$[0] = props.x;
$[1] = __DEV__ ? _makeReadOnly(t0, "useFoo") : t0;
} else {
t0 = $[1];
}
return t0;
}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -1,33 +0,0 @@
## Input
```javascript
// @enableEmitFreeze @instrumentForget
function useFoo(props) {
return foo(props.x, __DEV__);
}
```
## Code
```javascript
import { makeReadOnly } from "react-compiler-runtime";
import { c as _c } from "react/compiler-runtime"; // @enableEmitFreeze @instrumentForget
function useFoo(props) {
const $ = _c(2);
let t0;
if ($[0] !== props.x) {
t0 = foo(props.x, __DEV__);
$[0] = props.x;
$[1] = __DEV__ ? makeReadOnly(t0, "useFoo") : t0;
} else {
t0 = $[1];
}
return t0;
}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -1,4 +0,0 @@
// @enableEmitFreeze @instrumentForget
function useFoo(props) {
return foo(props.x, __DEV__);
}

View File

@@ -0,0 +1,21 @@
## Input
```javascript
// @enableEmitFreeze @instrumentForget
let makeReadOnly = 'conflicting identifier';
function useFoo(props) {
return foo(props.x);
}
```
## Error
```
Invariant: Encountered conflicting import specifiers for makeReadOnly in generated program.
```

View File

@@ -1,27 +0,0 @@
## Input
```javascript
// @enableEmitFreeze @instrumentForget
function useFoo(props) {
const __DEV__ = 'conflicting global';
console.log(__DEV__);
return foo(props.x);
}
```
## Error
```
1 | // @enableEmitFreeze @instrumentForget
2 | function useFoo(props) {
> 3 | const __DEV__ = 'conflicting global';
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Todo: Encountered conflicting global in generated program. Conflict from local binding __DEV__ (3:3)
4 | console.log(__DEV__);
5 | return foo(props.x);
6 | }
```

View File

@@ -1,6 +0,0 @@
// @enableEmitFreeze @instrumentForget
function useFoo(props) {
const __DEV__ = 'conflicting global';
console.log(__DEV__);
return foo(props.x);
}

View File

@@ -18,8 +18,8 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
import { c as _c } from "react/compiler-runtime"; // @gating
import { Stringify } from "shared-runtime";
const ErrorView = isForgetEnabled_Fixtures()
? (t0) => {

View File

@@ -36,9 +36,9 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { shouldInstrument, useRenderCounter } from "react-compiler-runtime";
import { c as _c } from "react/compiler-runtime";
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @enableEmitInstrumentForget @compilationMode(annotation) @gating
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
import { useRenderCounter, shouldInstrument } from "react-compiler-runtime";
import { c as _c } from "react/compiler-runtime"; // @enableEmitInstrumentForget @compilationMode(annotation) @gating
const Bar = isForgetEnabled_Fixtures()
? function Bar(props) {
"use forget";

View File

@@ -20,14 +20,14 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
import { c as _c } from "react/compiler-runtime";
import { Stringify } from "shared-runtime";
import * as React from "react";
const Foo = React.forwardRef(Foo_withRef);
const isForgetEnabled_Fixtures_result = isForgetEnabled_Fixtures();
function Foo_withRef_optimized(_$$empty_props_placeholder$$, ref) {
const _isForgetEnabled_Fixtures_result = isForgetEnabled_Fixtures();
function _Foo_withRef_optimized(_$$empty_props_placeholder$$, ref) {
const $ = _c(2);
let t0;
if ($[0] !== ref) {
@@ -39,15 +39,16 @@ function Foo_withRef_optimized(_$$empty_props_placeholder$$, ref) {
}
return t0;
}
function Foo_withRef_unoptimized(
function _Foo_withRef_unoptimized(
_$$empty_props_placeholder$$: $ReadOnly<{}>,
ref: React.RefSetter<Controls>,
): React.Node {
return <Stringify ref={ref} />;
}
function Foo_withRef(arg0, arg1) {
if (isForgetEnabled_Fixtures_result) return Foo_withRef_optimized(arg0, arg1);
else return Foo_withRef_unoptimized(arg0, arg1);
if (_isForgetEnabled_Fixtures_result)
return _Foo_withRef_optimized(arg0, arg1);
else return _Foo_withRef_unoptimized(arg0, arg1);
}
export const FIXTURE_ENTRYPOINT = {

View File

@@ -1,62 +0,0 @@
## Input
```javascript
// @gating
export const isForgetEnabled_Fixtures = () => {
'use no forget';
return false;
};
export function Bar(props) {
'use forget';
return <div>{props.bar}</div>;
}
export const FIXTURE_ENTRYPOINT = {
fn: eval('Bar'),
params: [{bar: 2}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { isForgetEnabled_Fixtures as _isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating
export const isForgetEnabled_Fixtures = () => {
"use no forget";
return false;
};
export const Bar = _isForgetEnabled_Fixtures()
? function Bar(props) {
"use forget";
const $ = _c(2);
let t0;
if ($[0] !== props.bar) {
t0 = <div>{props.bar}</div>;
$[0] = props.bar;
$[1] = t0;
} else {
t0 = $[1];
}
return t0;
}
: function Bar(props) {
"use forget";
return <div>{props.bar}</div>;
};
export const FIXTURE_ENTRYPOINT = {
fn: eval("Bar"),
params: [{ bar: 2 }],
};
```
### Eval output
(kind: ok) <div>2</div>

View File

@@ -1,16 +0,0 @@
// @gating
export const isForgetEnabled_Fixtures = () => {
'use no forget';
return false;
};
export function Bar(props) {
'use forget';
return <div>{props.bar}</div>;
}
export const FIXTURE_ENTRYPOINT = {
fn: eval('Bar'),
params: [{bar: 2}],
};

View File

@@ -18,8 +18,8 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
import { c as _c } from "react/compiler-runtime"; // @gating
const Component = isForgetEnabled_Fixtures()
? function Component() {
const $ = _c(1);

View File

@@ -24,8 +24,8 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
import { c as _c } from "react/compiler-runtime"; // @gating
import { identity, useHook as useRenamed } from "shared-runtime";
const _ = {
useHook: isForgetEnabled_Fixtures() ? () => {} : () => {},

View File

@@ -26,8 +26,8 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
import { c as _c } from "react/compiler-runtime"; // @gating
const Component = isForgetEnabled_Fixtures()
? function Component() {
const $ = _c(1);

View File

@@ -27,8 +27,8 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode(annotation)
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
import { c as _c } from "react/compiler-runtime"; // @gating @compilationMode(annotation)
const Bar = isForgetEnabled_Fixtures()
? function Bar(props) {
"use forget";

View File

@@ -34,8 +34,8 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode(annotation)
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
import { c as _c } from "react/compiler-runtime"; // @gating @compilationMode(annotation)
const Bar = isForgetEnabled_Fixtures()
? function Bar(props) {
"use forget";

View File

@@ -27,8 +27,8 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode(annotation)
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
import { c as _c } from "react/compiler-runtime"; // @gating @compilationMode(annotation)
export const Bar = isForgetEnabled_Fixtures()
? function Bar(props) {
"use forget";

View File

@@ -27,8 +27,8 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode(annotation)
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
import { c as _c } from "react/compiler-runtime"; // @gating @compilationMode(annotation)
const Bar = isForgetEnabled_Fixtures()
? function Bar(props) {
"use forget";

View File

@@ -21,14 +21,14 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
import { c as _c } from "react/compiler-runtime"; // @gating
import { createRef, forwardRef } from "react";
import { Stringify } from "shared-runtime";
const Foo = forwardRef(Foo_withRef);
const isForgetEnabled_Fixtures_result = isForgetEnabled_Fixtures();
function Foo_withRef_optimized(props, ref) {
const _isForgetEnabled_Fixtures_result = isForgetEnabled_Fixtures();
function _Foo_withRef_optimized(props, ref) {
const $ = _c(3);
let t0;
if ($[0] !== props || $[1] !== ref) {
@@ -41,12 +41,13 @@ function Foo_withRef_optimized(props, ref) {
}
return t0;
}
function Foo_withRef_unoptimized(props, ref) {
function _Foo_withRef_unoptimized(props, ref) {
return <Stringify ref={ref} {...props} />;
}
function Foo_withRef(arg0, arg1) {
if (isForgetEnabled_Fixtures_result) return Foo_withRef_optimized(arg0, arg1);
else return Foo_withRef_unoptimized(arg0, arg1);
if (_isForgetEnabled_Fixtures_result)
return _Foo_withRef_optimized(arg0, arg1);
else return _Foo_withRef_unoptimized(arg0, arg1);
}
export const FIXTURE_ENTRYPOINT = {

View File

@@ -22,14 +22,14 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
import { c as _c } from "react/compiler-runtime"; // @gating
import { memo } from "react";
import { Stringify } from "shared-runtime";
export default memo(Foo);
const isForgetEnabled_Fixtures_result = isForgetEnabled_Fixtures();
function Foo_optimized(t0) {
const _isForgetEnabled_Fixtures_result = isForgetEnabled_Fixtures();
function _Foo_optimized(t0) {
"use memo";
const $ = _c(3);
const { prop1, prop2 } = t0;
@@ -44,13 +44,13 @@ function Foo_optimized(t0) {
}
return t1;
}
function Foo_unoptimized({ prop1, prop2 }) {
function _Foo_unoptimized({ prop1, prop2 }) {
"use memo";
return <Stringify prop1={prop1} prop2={prop2} />;
}
function Foo(arg0) {
if (isForgetEnabled_Fixtures_result) return Foo_optimized(arg0);
else return Foo_unoptimized(arg0);
if (_isForgetEnabled_Fixtures_result) return _Foo_optimized(arg0);
else return _Foo_unoptimized(arg0);
}
export const FIXTURE_ENTRYPOINT = {

View File

@@ -23,8 +23,8 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
import { c as _c } from "react/compiler-runtime";
import { memo } from "react";
type Props = React.ElementConfig<typeof Component>;

View File

@@ -13,8 +13,8 @@ export default React.forwardRef(function notNamedLikeAComponent(props) {
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode(infer)
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
import { c as _c } from "react/compiler-runtime"; // @gating @compilationMode(infer)
import React from "react";
export default React.forwardRef(
isForgetEnabled_Fixtures()

View File

@@ -23,8 +23,8 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
import { c as _c } from "react/compiler-runtime"; // @gating
import * as React from "react";
let Foo;

View File

@@ -19,8 +19,8 @@ export default props => (
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
import { c as _c } from "react/compiler-runtime"; // @gating
import { Stringify } from "shared-runtime";
const ErrorView = isForgetEnabled_Fixtures()

View File

@@ -24,8 +24,8 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
import { c as _c } from "react/compiler-runtime"; // @gating
import { Stringify } from "shared-runtime";
const ErrorView = isForgetEnabled_Fixtures()

View File

@@ -26,8 +26,8 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
import { c as _c } from "react/compiler-runtime"; // @gating
import { Stringify } from "shared-runtime";
const ErrorView = isForgetEnabled_Fixtures()

View File

@@ -31,8 +31,8 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
import { c as _c } from "react/compiler-runtime"; // @gating
import * as React from "react";
/**

View File

@@ -1,66 +0,0 @@
## Input
```javascript
import {useIdentity} from 'shared-runtime';
/**
* Forked version of call-spread-argument-mutable-iterator that is known to not mutate
* the spread argument since it is a Set
*/
function useFoo() {
const s = new Set([1, 2]);
useIdentity(null);
return [Math.max(...s), s];
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{}],
sequentialRenders: [{}, {}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { useIdentity } from "shared-runtime";
/**
* Forked version of call-spread-argument-mutable-iterator that is known to not mutate
* the spread argument since it is a Set
*/
function useFoo() {
const $ = _c(2);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = new Set([1, 2]);
$[0] = t0;
} else {
t0 = $[0];
}
const s = t0;
useIdentity(null);
let t1;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t1 = [Math.max(...s), s];
$[1] = t1;
} else {
t1 = $[1];
}
return t1;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{}],
sequentialRenders: [{}, {}],
};
```
### Eval output
(kind: ok) [2,{"kind":"Set","value":[1,2]}]
[2,{"kind":"Set","value":[1,2]}]

View File

@@ -1,17 +0,0 @@
import {useIdentity} from 'shared-runtime';
/**
* Forked version of call-spread-argument-mutable-iterator that is known to not mutate
* the spread argument since it is a Set
*/
function useFoo() {
const s = new Set([1, 2]);
useIdentity(null);
return [Math.max(...s), s];
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{}],
sequentialRenders: [{}, {}],
};

View File

@@ -1,77 +0,0 @@
## Input
```javascript
import {makeArray} from 'shared-runtime';
function useHook({el1, el2}) {
const s = new Map();
s.set(el1, makeArray(el1));
s.set(el2, makeArray(el2));
return s.size;
}
export const FIXTURE_ENTRYPOINT = {
fn: useHook,
params: [{el1: 1, el2: 'foo'}],
sequentialRenders: [
{el1: 1, el2: 'foo'},
{el1: 2, el2: 'foo'},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { makeArray } from "shared-runtime";
function useHook(t0) {
const $ = _c(7);
const { el1, el2 } = t0;
let s;
if ($[0] !== el1 || $[1] !== el2) {
s = new Map();
let t1;
if ($[3] !== el1) {
t1 = makeArray(el1);
$[3] = el1;
$[4] = t1;
} else {
t1 = $[4];
}
s.set(el1, t1);
let t2;
if ($[5] !== el2) {
t2 = makeArray(el2);
$[5] = el2;
$[6] = t2;
} else {
t2 = $[6];
}
s.set(el2, t2);
$[0] = el1;
$[1] = el2;
$[2] = s;
} else {
s = $[2];
}
return s.size;
}
export const FIXTURE_ENTRYPOINT = {
fn: useHook,
params: [{ el1: 1, el2: "foo" }],
sequentialRenders: [
{ el1: 1, el2: "foo" },
{ el1: 2, el2: "foo" },
],
};
```
### Eval output
(kind: ok) 2
2

View File

@@ -1,17 +0,0 @@
import {makeArray} from 'shared-runtime';
function useHook({el1, el2}) {
const s = new Map();
s.set(el1, makeArray(el1));
s.set(el2, makeArray(el2));
return s.size;
}
export const FIXTURE_ENTRYPOINT = {
fn: useHook,
params: [{el1: 1, el2: 'foo'}],
sequentialRenders: [
{el1: 1, el2: 'foo'},
{el1: 2, el2: 'foo'},
],
};

View File

@@ -1,113 +0,0 @@
## Input
```javascript
import {mutateAndReturn, Stringify, useIdentity} from 'shared-runtime';
/**
* Repro for bug with `mutableOnlyIfOperandsAreMutable` flag
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok)
* <div>{"children":[{"value":"foo","wat0":"joe"},{"value":5,"wat0":"joe"}]}</div>
* <div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>
* <div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>
* Forget:
* (kind: ok)
* <div>{"children":[{"value":"foo","wat0":"joe"},{"value":5,"wat0":"joe"}]}</div>
* <div>{"children":[{"value":"foo","wat0":"joe","wat1":"joe"},{"value":6,"wat0":"joe"}]}</div>
* <div>{"children":[{"value":"foo","wat0":"joe","wat1":"joe"},{"value":6,"wat0":"joe"}]}</div>
*/
function Component({value}) {
const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
useIdentity(null);
const derived = arr.filter(mutateAndReturn);
return (
<Stringify>
{derived.at(0)}
{derived.at(-1)}
</Stringify>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: 5}],
sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { mutateAndReturn, Stringify, useIdentity } from "shared-runtime";
/**
* Repro for bug with `mutableOnlyIfOperandsAreMutable` flag
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok)
* <div>{"children":[{"value":"foo","wat0":"joe"},{"value":5,"wat0":"joe"}]}</div>
* <div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>
* <div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>
* Forget:
* (kind: ok)
* <div>{"children":[{"value":"foo","wat0":"joe"},{"value":5,"wat0":"joe"}]}</div>
* <div>{"children":[{"value":"foo","wat0":"joe","wat1":"joe"},{"value":6,"wat0":"joe"}]}</div>
* <div>{"children":[{"value":"foo","wat0":"joe","wat1":"joe"},{"value":6,"wat0":"joe"}]}</div>
*/
function Component(t0) {
const $ = _c(7);
const { value } = t0;
const arr = [{ value: "foo" }, { value: "bar" }, { value }];
useIdentity(null);
const derived = arr.filter(mutateAndReturn);
let t1;
if ($[0] !== derived) {
t1 = derived.at(0);
$[0] = derived;
$[1] = t1;
} else {
t1 = $[1];
}
let t2;
if ($[2] !== derived) {
t2 = derived.at(-1);
$[2] = derived;
$[3] = t2;
} else {
t2 = $[3];
}
let t3;
if ($[4] !== t1 || $[5] !== t2) {
t3 = (
<Stringify>
{t1}
{t2}
</Stringify>
);
$[4] = t1;
$[5] = t2;
$[6] = t3;
} else {
t3 = $[6];
}
return t3;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ value: 5 }],
sequentialRenders: [{ value: 5 }, { value: 6 }, { value: 6 }],
};
```
### Eval output
(kind: ok) <div>{"children":[{"value":"foo","wat0":"joe"},{"value":5,"wat0":"joe"}]}</div>
<div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>
<div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>

View File

@@ -1,34 +0,0 @@
import {mutateAndReturn, Stringify, useIdentity} from 'shared-runtime';
/**
* Repro for bug with `mutableOnlyIfOperandsAreMutable` flag
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok)
* <div>{"children":[{"value":"foo","wat0":"joe"},{"value":5,"wat0":"joe"}]}</div>
* <div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>
* <div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>
* Forget:
* (kind: ok)
* <div>{"children":[{"value":"foo","wat0":"joe"},{"value":5,"wat0":"joe"}]}</div>
* <div>{"children":[{"value":"foo","wat0":"joe","wat1":"joe"},{"value":6,"wat0":"joe"}]}</div>
* <div>{"children":[{"value":"foo","wat0":"joe","wat1":"joe"},{"value":6,"wat0":"joe"}]}</div>
*/
function Component({value}) {
const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
useIdentity(null);
const derived = arr.filter(mutateAndReturn);
return (
<Stringify>
{derived.at(0)}
{derived.at(-1)}
</Stringify>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: 5}],
sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
};

View File

@@ -1,118 +0,0 @@
## Input
```javascript
import {Stringify, useIdentity} from 'shared-runtime';
/**
* Also see repro-array-map-known-mutate-shape, which calls a global function
* that mutates its operands.
*/
function Component({value}) {
const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
useIdentity(null);
const derived = arr.filter(Boolean);
return (
<Stringify>
{derived.at(0)}
{derived.at(-1)}
</Stringify>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: 5}],
sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { Stringify, useIdentity } from "shared-runtime";
/**
* Also see repro-array-map-known-mutate-shape, which calls a global function
* that mutates its operands.
*/
function Component(t0) {
const $ = _c(13);
const { value } = t0;
let t1;
let t2;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t1 = { value: "foo" };
t2 = { value: "bar" };
$[0] = t1;
$[1] = t2;
} else {
t1 = $[0];
t2 = $[1];
}
let t3;
if ($[2] !== value) {
t3 = [t1, t2, { value }];
$[2] = value;
$[3] = t3;
} else {
t3 = $[3];
}
const arr = t3;
useIdentity(null);
let t4;
if ($[4] !== arr) {
t4 = arr.filter(Boolean);
$[4] = arr;
$[5] = t4;
} else {
t4 = $[5];
}
const derived = t4;
let t5;
if ($[6] !== derived) {
t5 = derived.at(0);
$[6] = derived;
$[7] = t5;
} else {
t5 = $[7];
}
let t6;
if ($[8] !== derived) {
t6 = derived.at(-1);
$[8] = derived;
$[9] = t6;
} else {
t6 = $[9];
}
let t7;
if ($[10] !== t5 || $[11] !== t6) {
t7 = (
<Stringify>
{t5}
{t6}
</Stringify>
);
$[10] = t5;
$[11] = t6;
$[12] = t7;
} else {
t7 = $[12];
}
return t7;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ value: 5 }],
sequentialRenders: [{ value: 5 }, { value: 6 }, { value: 6 }],
};
```
### Eval output
(kind: ok) <div>{"children":[{"value":"foo"},{"value":5}]}</div>
<div>{"children":[{"value":"foo"},{"value":6}]}</div>
<div>{"children":[{"value":"foo"},{"value":6}]}</div>

View File

@@ -1,23 +0,0 @@
import {Stringify, useIdentity} from 'shared-runtime';
/**
* Also see repro-array-map-known-mutate-shape, which calls a global function
* that mutates its operands.
*/
function Component({value}) {
const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
useIdentity(null);
const derived = arr.filter(Boolean);
return (
<Stringify>
{derived.at(0)}
{derived.at(-1)}
</Stringify>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: 5}],
sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
};

View File

@@ -1,91 +0,0 @@
## Input
```javascript
import {mutateAndReturn, Stringify, useIdentity} from 'shared-runtime';
/**
* Copy of repro-array-map-capture-mutate-bug, showing that the same issue applies to any
* function call which captures its callee when applying an operand.
*/
function Component({value}) {
const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
useIdentity(null);
const derived = arr.map(mutateAndReturn);
return (
<Stringify>
{derived.at(0)}
{derived.at(-1)}
</Stringify>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: 5}],
sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { mutateAndReturn, Stringify, useIdentity } from "shared-runtime";
/**
* Copy of repro-array-map-capture-mutate-bug, showing that the same issue applies to any
* function call which captures its callee when applying an operand.
*/
function Component(t0) {
const $ = _c(7);
const { value } = t0;
const arr = [{ value: "foo" }, { value: "bar" }, { value }];
useIdentity(null);
const derived = arr.map(mutateAndReturn);
let t1;
if ($[0] !== derived) {
t1 = derived.at(0);
$[0] = derived;
$[1] = t1;
} else {
t1 = $[1];
}
let t2;
if ($[2] !== derived) {
t2 = derived.at(-1);
$[2] = derived;
$[3] = t2;
} else {
t2 = $[3];
}
let t3;
if ($[4] !== t1 || $[5] !== t2) {
t3 = (
<Stringify>
{t1}
{t2}
</Stringify>
);
$[4] = t1;
$[5] = t2;
$[6] = t3;
} else {
t3 = $[6];
}
return t3;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ value: 5 }],
sequentialRenders: [{ value: 5 }, { value: 6 }, { value: 6 }],
};
```
### Eval output
(kind: ok) <div>{"children":[{"value":"foo","wat0":"joe"},{"value":5,"wat0":"joe"}]}</div>
<div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>
<div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>

View File

@@ -1,23 +0,0 @@
import {mutateAndReturn, Stringify, useIdentity} from 'shared-runtime';
/**
* Copy of repro-array-map-capture-mutate-bug, showing that the same issue applies to any
* function call which captures its callee when applying an operand.
*/
function Component({value}) {
const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
useIdentity(null);
const derived = arr.map(mutateAndReturn);
return (
<Stringify>
{derived.at(0)}
{derived.at(-1)}
</Stringify>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: 5}],
sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
};

View File

@@ -1,100 +0,0 @@
## Input
```javascript
import {Stringify, useIdentity} from 'shared-runtime';
/**
* Also see repro-array-map-known-nonmutate-Boolean, which calls a global
* function that does *not* mutate its operands.
*/
function Component({value}) {
const arr = [
new Set([['foo', 2]]).values(),
new Set([['bar', 4]]).values(),
[['baz', value]],
];
useIdentity(null);
const derived = arr.map(Object.fromEntries);
return (
<Stringify>
{derived.at(0)}
{derived.at(-1)}
</Stringify>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: 5}],
sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { Stringify, useIdentity } from "shared-runtime";
/**
* Also see repro-array-map-known-nonmutate-Boolean, which calls a global
* function that does *not* mutate its operands.
*/
function Component(t0) {
const $ = _c(7);
const { value } = t0;
const arr = [
new Set([["foo", 2]]).values(),
new Set([["bar", 4]]).values(),
[["baz", value]],
];
useIdentity(null);
const derived = arr.map(Object.fromEntries);
let t1;
if ($[0] !== derived) {
t1 = derived.at(0);
$[0] = derived;
$[1] = t1;
} else {
t1 = $[1];
}
let t2;
if ($[2] !== derived) {
t2 = derived.at(-1);
$[2] = derived;
$[3] = t2;
} else {
t2 = $[3];
}
let t3;
if ($[4] !== t1 || $[5] !== t2) {
t3 = (
<Stringify>
{t1}
{t2}
</Stringify>
);
$[4] = t1;
$[5] = t2;
$[6] = t3;
} else {
t3 = $[6];
}
return t3;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ value: 5 }],
sequentialRenders: [{ value: 5 }, { value: 6 }, { value: 6 }],
};
```
### Eval output
(kind: ok) <div>{"children":[{"foo":2},{"baz":5}]}</div>
<div>{"children":[{"foo":2},{"baz":6}]}</div>
<div>{"children":[{"foo":2},{"baz":6}]}</div>

View File

@@ -1,27 +0,0 @@
import {Stringify, useIdentity} from 'shared-runtime';
/**
* Also see repro-array-map-known-nonmutate-Boolean, which calls a global
* function that does *not* mutate its operands.
*/
function Component({value}) {
const arr = [
new Set([['foo', 2]]).values(),
new Set([['bar', 4]]).values(),
[['baz', value]],
];
useIdentity(null);
const derived = arr.map(Object.fromEntries);
return (
<Stringify>
{derived.at(0)}
{derived.at(-1)}
</Stringify>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: 5}],
sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
};

View File

@@ -1,76 +0,0 @@
## Input
```javascript
import {makeArray} from 'shared-runtime';
function useHook({el1, el2}) {
const s = new Set();
const arr = makeArray(el1);
s.add(arr);
// Mutate after store
arr.push(el2);
s.add(makeArray(el2));
return s.size;
}
export const FIXTURE_ENTRYPOINT = {
fn: useHook,
params: [{el1: 1, el2: 'foo'}],
sequentialRenders: [
{el1: 1, el2: 'foo'},
{el1: 2, el2: 'foo'},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { makeArray } from "shared-runtime";
function useHook(t0) {
const $ = _c(5);
const { el1, el2 } = t0;
let s;
if ($[0] !== el1 || $[1] !== el2) {
s = new Set();
const arr = makeArray(el1);
s.add(arr);
arr.push(el2);
let t1;
if ($[3] !== el2) {
t1 = makeArray(el2);
$[3] = el2;
$[4] = t1;
} else {
t1 = $[4];
}
s.add(t1);
$[0] = el1;
$[1] = el2;
$[2] = s;
} else {
s = $[2];
}
return s.size;
}
export const FIXTURE_ENTRYPOINT = {
fn: useHook,
params: [{ el1: 1, el2: "foo" }],
sequentialRenders: [
{ el1: 1, el2: "foo" },
{ el1: 2, el2: "foo" },
],
};
```
### Eval output
(kind: ok) 2
2

View File

@@ -1,21 +0,0 @@
import {makeArray} from 'shared-runtime';
function useHook({el1, el2}) {
const s = new Set();
const arr = makeArray(el1);
s.add(arr);
// Mutate after store
arr.push(el2);
s.add(makeArray(el2));
return s.size;
}
export const FIXTURE_ENTRYPOINT = {
fn: useHook,
params: [{el1: 1, el2: 'foo'}],
sequentialRenders: [
{el1: 1, el2: 'foo'},
{el1: 2, el2: 'foo'},
],
};

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