Compare commits

..

5 Commits

Author SHA1 Message Date
Mofei Zhang
c19f052b2d [compiler][ez] Move compiler gating tests
Move all gating tests to `gating/`
2025-03-13 11:52:25 -04:00
Mofei Zhang
d2d5350e65 [compiler][optim] more shapes for mixedreadonly
- Add `at`, `indexOf`, and `includes`
- Optimize MixedReadOnly which is currently only used by hook return values. Hook return values are typed as Frozen, this change propagates that to return values of aliasing function calls (such as `at`). One potential issue is that  developers may pass `enableAssumeHooksFollowRulesOfReact:false` and set `transitiveMixedData`, expecting their transitive mixed data to be mutable. This is a bit of an edge case and already doesn't have clear semantics.
2025-03-13 11:52:25 -04:00
Mofei Zhang
8ba22587b5 [compiler][optim] infer mixedReadOnly for numeric and computed properties
Expand type inference to infer mixedReadOnly types for numeric and computed property accesses.
```js
function Component({idx})
  const data = useFragment(...)
  // we want to type `posts` correctly as Array
  const posts = data.viewers[idx].posts.slice(0, 5);
  // ...
}
```
2025-03-13 11:52:25 -04:00
Mofei Zhang
7606b29048 [compiler][optim] Add shape for Array.from
(see title)
2025-03-13 11:52:25 -04:00
Mofei Zhang
54e602d891 [compiler] Patch array and argument spread mutability
Array and argument spreads may mutate stateful iterables. Spread sites should have `ConditionallyMutate` effects (e.g. mutate if the ValueKind is mutable, otherwise read).

See
- [ecma spec (13.2.4.1 Runtime Semantics: ArrayAccumulation. SpreadElement : ... AssignmentExpression)](https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#sec-runtime-semantics-arrayaccumulation).
- [ecma spec 13.3.8.1 Runtime Semantics: ArgumentListEvaluation](https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#sec-runtime-semantics-argumentlistevaluation)

Note that
- Object and JSX Attribute spreads do not evaluate iterables (srcs [mozilla](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax#description), [ecma](https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#sec-runtime-semantics-propertydefinitionevaluation))
- An ideal mutability inference system could model known collections (i.e. Arrays or Sets) as a "mutated collection of non-mutable objects" (see `todo-granular-iterator-semantics`), but this is not what we do today. As such, an array / argument spread will always extend the range of built-in arrays, sets, etc
- Due to HIR limitations, call expressions with argument spreads may cause unnecessary bailouts and/or scope merging when we know the call itself has `freeze`, `capture`, or `read` semantics (e.g. `useHook(...mutableValue)`)
  We can deal with this by rewriting these call instructions to (1) create an intermediate array to consume the iterator and (2) capture and spread the array at the callsite
2025-03-13 11:52:25 -04:00
217 changed files with 1835 additions and 8261 deletions

View File

@@ -336,11 +336,11 @@ module.exports = {
'packages/react-devtools-extensions/**/*.js',
'packages/react-devtools-timeline/**/*.js',
'packages/react-native-renderer/**/*.js',
'packages/eslint-plugin-react-hooks/**/*.js',
'packages/jest-react/**/*.js',
'packages/internal-test-utils/**/*.js',
'packages/**/__tests__/*.js',
'packages/**/npm/*.js',
'compiler/packages/eslint-plugin-react-hooks/**/*.js',
],
rules: {
'react-internal/prod-error-codes': OFF,
@@ -515,7 +515,7 @@ module.exports = {
},
},
{
files: ['compiler/packages/eslint-plugin-react-hooks/src/**/*'],
files: ['packages/eslint-plugin-react-hooks/src/**/*'],
extends: ['plugin:@typescript-eslint/recommended'],
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint', 'eslint-plugin'],

View File

@@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Discord Webhook Action
uses: tsickert/discord-webhook@86dc739f3f165f16dadc5666051c367efa1692f4
uses: tsickert/discord-webhook@v6.0.0
with:
webhook-url: ${{ secrets.COMPILER_DISCORD_WEBHOOK_URL }}
embed-author-name: ${{ github.event.pull_request.user.login }}

View File

@@ -36,9 +36,8 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
path: "**/node_modules"
key: compiler-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
- run: yarn install --frozen-lockfile
- run: npx playwright install --with-deps chromium
- run: CI=true yarn test

View File

@@ -46,9 +46,8 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
path: "**/node_modules"
key: compiler-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
- run: yarn install --frozen-lockfile
- name: Publish packages to npm
run: |

View File

@@ -46,9 +46,8 @@ jobs:
- name: Restore cached node_modules
uses: actions/cache@v4
with:
path: |
**/node_modules
key: compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
path: "**/node_modules"
key: compiler-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
- run: yarn install --frozen-lockfile
- run: yarn workspace babel-plugin-react-compiler lint
@@ -67,9 +66,8 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
path: "**/node_modules"
key: compiler-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
- run: yarn install --frozen-lockfile
- run: yarn workspace babel-plugin-react-compiler jest
@@ -92,9 +90,8 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
path: "**/node_modules"
key: compiler-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
- run: yarn install --frozen-lockfile
- run: xvfb-run -a yarn workspace ${{ matrix.workspace_name }} test
if: runner.os == 'Linux' && matrix.workspace_name == 'react-forgive'

View File

@@ -29,13 +29,13 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-release-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
path: "**/node_modules"
key: runtime-release-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- run: yarn --cwd scripts/release install --frozen-lockfile
- run: yarn install --frozen-lockfile
working-directory: scripts/release
- name: Download react-devtools artifacts for base revision
run: |
git fetch origin main
@@ -63,9 +63,8 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
@@ -121,9 +120,8 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
- name: Restore all archived build artifacts
uses: actions/download-artifact@v4
@@ -156,9 +154,8 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
- name: Restore all archived build artifacts
uses: actions/download-artifact@v4

View File

@@ -17,75 +17,6 @@ env:
SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1
jobs:
# ----- NODE_MODULES CACHE -----
# Centralize the node_modules cache so it is saved once and each subsequent job only needs to
# restore the cache. Prevents race conditions where multiple workflows try to write to the cache.
runtime_node_modules_cache:
name: Cache Runtime node_modules
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Check cache hit
uses: actions/cache/restore@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
lookup-only: true
- uses: actions/setup-node@v4
if: steps.node_modules.outputs.cache-hit != 'true'
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Save cache
if: steps.node_modules.outputs.cache-hit != 'true'
uses: actions/cache/save@v4
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
runtime_compiler_node_modules_cache:
name: Cache Runtime, Compiler node_modules
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Check cache hit
uses: actions/cache/restore@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
lookup-only: true
- uses: actions/setup-node@v4
if: steps.node_modules.outputs.cache-hit != 'true'
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: |
yarn.lock
compiler/yarn.lock
- 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: Save cache
if: steps.node_modules.outputs.cache-hit != 'true'
uses: actions/cache/save@v4
with:
path: |
**/node_modules
key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
# ----- FLOW -----
discover_flow_inline_configs:
name: Discover flow inline configs
@@ -105,7 +36,7 @@ jobs:
flow:
name: Flow check ${{ matrix.flow_inline_config_shortname }}
needs: [discover_flow_inline_configs, runtime_node_modules_cache]
needs: discover_flow_inline_configs
runs-on: ubuntu-latest
strategy:
fail-fast: false
@@ -121,22 +52,19 @@ jobs:
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache/restore@v4
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('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: node ./scripts/tasks/flow-ci ${{ matrix.flow_inline_config_shortname }}
# ----- FIZZ -----
check_generated_fizz_runtime:
name: Confirm generated inline Fizz runtime is up to date
needs: [runtime_node_modules_cache]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -148,16 +76,14 @@ jobs:
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache/restore@v4
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('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 generate-inline-fizz-runtime
git diff --quiet || (echo "There was a change to the Fizz runtime. Run `yarn generate-inline-fizz-runtime` and check in the result." && false)
@@ -165,7 +91,6 @@ jobs:
# ----- FEATURE FLAGS -----
flags:
name: Check flags
needs: [runtime_node_modules_cache]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -177,22 +102,19 @@ jobs:
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache/restore@v4
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('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 flags
# ----- TESTS -----
test:
name: yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }})
needs: [runtime_compiler_node_modules_cache]
runs-on: ubuntu-latest
strategy:
fail-fast: false
@@ -231,34 +153,27 @@ jobs:
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: |
yarn.lock
compiler/yarn.lock
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache/restore@v4
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('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'
- run: yarn test ${{ matrix.params }} --ci --shard=${{ matrix.shard }}
# ----- BUILD -----
build_and_lint:
name: yarn build and lint
needs: [runtime_compiler_node_modules_cache]
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# yml is dumb. update the --total arg to yarn build if you change the number of workers
worker_id: [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24]
worker_id: [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19]
release_channel: [stable, experimental]
steps:
- uses: actions/checkout@v4
@@ -268,27 +183,21 @@ jobs:
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: |
yarn.lock
compiler/yarn.lock
cache-dependency-path: yarn.lock
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 11.0.22
- name: Restore cached node_modules
uses: actions/cache/restore@v4
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('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'
- run: yarn build --index=${{ matrix.worker_id }} --total=25 --r=${{ matrix.release_channel }} --ci
- run: yarn build --index=${{ matrix.worker_id }} --total=20 --r=${{ matrix.release_channel }} --ci
env:
CI: github
RELEASE_CHANNEL: ${{ matrix.release_channel }}
@@ -305,7 +214,7 @@ jobs:
test_build:
name: yarn test-build
needs: [build_and_lint, runtime_compiler_node_modules_cache]
needs: build_and_lint
strategy:
fail-fast: false
matrix:
@@ -352,22 +261,16 @@ jobs:
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: |
yarn.lock
compiler/yarn.lock
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache/restore@v4
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('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: Restore archived build
uses: actions/download-artifact@v4
with:
@@ -380,7 +283,7 @@ jobs:
process_artifacts_combined:
name: Process artifacts combined
needs: [build_and_lint, runtime_node_modules_cache]
needs: build_and_lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -392,16 +295,14 @@ jobs:
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache/restore@v4
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- 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:
@@ -410,7 +311,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.sha }} >> build/COMMIT_SHA
- name: Scrape warning messages
run: |
mkdir -p ./build/__test_utils__
@@ -429,7 +330,7 @@ jobs:
check_error_codes:
name: Search build artifacts for unminified errors
needs: [build_and_lint, runtime_node_modules_cache]
needs: build_and_lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -441,16 +342,14 @@ jobs:
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache/restore@v4
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- 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:
@@ -466,7 +365,7 @@ jobs:
check_release_dependencies:
name: Check release dependencies
needs: [build_and_lint, runtime_node_modules_cache]
needs: build_and_lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -478,16 +377,14 @@ jobs:
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache/restore@v4
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- 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:
@@ -512,16 +409,15 @@ jobs:
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4 # note: this does not reuse centralized cache since it has unique cache key
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: fixtures_dom-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
path: "**/node_modules"
key: fixtures_dom-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn --cwd fixtures/dom install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn install --frozen-lockfile
working-directory: fixtures/dom
- name: Restore archived build
uses: actions/download-artifact@v4
with:
@@ -556,16 +452,14 @@ jobs:
# That means dependencies of the built packages are not installed.
# We need to install dependencies of the workroot to fulfill all dependency constraints
- name: Restore cached node_modules
uses: actions/cache@v4 # note: this does not reuse centralized cache since it has unique cache key
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: fixtures_flight-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
path: "**/node_modules"
key: fixtures_flight-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- 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:
@@ -604,7 +498,7 @@ jobs:
# ----- DEVTOOLS -----
build_devtools_and_process_artifacts:
name: Build DevTools and process artifacts
needs: [build_and_lint, runtime_node_modules_cache]
needs: build_and_lint
runs-on: ubuntu-latest
strategy:
fail-fast: false
@@ -620,16 +514,14 @@ jobs:
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache/restore@v4
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- 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:
@@ -661,7 +553,7 @@ jobs:
run_devtools_e2e_tests:
name: Run DevTools e2e tests
needs: [build_and_lint, runtime_node_modules_cache]
needs: build_and_lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -673,16 +565,14 @@ jobs:
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache/restore@v4
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- 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:
@@ -700,7 +590,7 @@ jobs:
sizebot:
if: ${{ github.event_name == 'pull_request' && github.ref_name != 'main' && github.event.pull_request.base.ref == 'main' }}
name: Run sizebot
needs: [build_and_lint, runtime_node_modules_cache]
needs: [build_and_lint]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -712,18 +602,15 @@ jobs:
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache/restore@v4
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-release-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('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 scripts/release install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
working-directory: scripts/release
- name: Download artifacts for base revision
run: |
GH_TOKEN=${{ github.token }} scripts/release/download-experimental-build.js --commit=$(git rev-parse ${{ github.event.pull_request.base.sha }})
@@ -739,7 +626,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 for PR
uses: actions/download-artifact@v4
with:
@@ -752,7 +638,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.sha }} >> build/COMMIT_SHA
- run: node ./scripts/tasks/danger
- name: Archive sizebot results
uses: actions/upload-artifact@v4

View File

@@ -16,11 +16,6 @@ on:
required: true
default: false
type: boolean
dry_run:
description: Perform a dry run (run everything except push)
required: true
default: false
type: boolean
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
@@ -78,13 +73,15 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-release-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
path: "**/node_modules"
key: runtime-release-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- run: yarn --cwd scripts/release install --frozen-lockfile
name: yarn install (react)
- run: yarn install --frozen-lockfile
name: yarn install (scripts/release)
working-directory: scripts/release
- name: Download artifacts for base revision
run: |
GH_TOKEN=${{ github.token }} scripts/release/download-experimental-build.js --commit=${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }}
@@ -110,10 +107,9 @@ jobs:
mkdir ./compiled/facebook-www/__test_utils__
mv build/__test_utils__/ReactAllWarnings.js ./compiled/facebook-www/__test_utils__/ReactAllWarnings.js
# Move eslint-plugin-react-hooks into eslint-plugin-react-hooks
mkdir ./compiled/eslint-plugin-react-hooks
# Move eslint-plugin-react-hooks into facebook-www
mv build/oss-experimental/eslint-plugin-react-hooks/cjs/eslint-plugin-react-hooks.development.js \
./compiled/eslint-plugin-react-hooks/index.js
./compiled/facebook-www/eslint-plugin-react-hooks.js
# Move unstable_server-external-runtime.js into facebook-www
mv build/oss-experimental/react-dom/unstable_server-external-runtime.js \
@@ -246,22 +242,18 @@ jobs:
run: |
echo ":"
git status -u
- name: Check commit message
if: inputs.dry_run
run: |
git fetch origin --quiet
git show ${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }} --no-patch --pretty=format:"%B"
- name: Commit changes to branch
if: inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true'
run: |
git config --global user.email "${{ format('{0}@users.noreply.github.com', github.triggering_actor) }}"
git config --global user.name "${{ github.triggering_actor }}"
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: |
${{ github.event.workflow_run.head_commit.message || format('Manual build of {0}', github.event.workflow_run.head_sha || github.sha) }}
git fetch origin --quiet
git commit -m "$(git show ${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }} --no-patch --pretty=format:'%B%n%nDiffTrain build for [${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }}](https://github.com/facebook/react/commit/${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha}})')" || echo "No changes to commit"
- name: Push changes to branch
if: inputs.dry_run == false && (inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true')
run: git push
DiffTrain build for [${{ github.event.workflow_run.head_sha || github.sha }}](https://github.com/facebook/react/commit/${{ github.event.workflow_run.head_sha || github.sha }})
branch: builds/facebook-www
commit_user_name: ${{ github.triggering_actor }}
commit_user_email: ${{ format('{0}@users.noreply.github.com', github.triggering_actor) }}
create_branch: true
commit_fbsource_artifacts:
needs: download_artifacts
@@ -417,19 +409,15 @@ jobs:
run: |
git add .
git status
- name: Check commit message
if: inputs.dry_run
run: |
git fetch origin --quiet
git show ${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }} --no-patch --pretty=format:"%B"
- name: Commit changes to branch
if: inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true'
run: |
git config --global user.email "${{ format('{0}@users.noreply.github.com', github.triggering_actor) }}"
git config --global user.name "${{ github.triggering_actor }}"
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: |
${{ github.event.workflow_run.head_commit.message || format('Manual build of {0}', github.event.workflow_run.head_sha || github.sha) }}
git fetch origin --quiet
git commit -m "$(git show ${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }} --no-patch --pretty=format:'%B%n%nDiffTrain build for [${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }}](https://github.com/facebook/react/commit/${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha}})')" || echo "No changes to commit"
- name: Push changes to branch
if: inputs.dry_run == false && (inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true')
run: git push
DiffTrain build for [${{ github.event.workflow_run.head_sha || github.sha }}](https://github.com/facebook/react/commit/${{ github.event.workflow_run.head_sha || github.sha }})
branch: builds/facebook-fbsource
commit_user_name: ${{ github.triggering_actor }}
commit_user_email: ${{ format('{0}@users.noreply.github.com', github.triggering_actor) }}
create_branch: true

View File

@@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Discord Webhook Action
uses: tsickert/discord-webhook@86dc739f3f165f16dadc5666051c367efa1692f4
uses: tsickert/discord-webhook@v6.0.0
with:
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
embed-author-name: ${{ github.event.pull_request.user.login }}

View File

@@ -35,20 +35,16 @@ jobs:
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: |
yarn.lock
compiler/yarn.lock
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-and-compiler-eslint_e2e-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
path: "node_modules"
key: runtime-eslint_e2e-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- run: yarn --cwd compiler install --frozen-lockfile
- name: Build plugin
working-directory: fixtures/eslint-v${{ matrix.eslint_major }}
run: node build.mjs

View File

@@ -39,13 +39,13 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-release-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
path: "**/node_modules"
key: runtime-release-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- run: yarn --cwd scripts/release install --frozen-lockfile
- run: yarn install --frozen-lockfile
working-directory: scripts/release
- run: |
scripts/release/prepare-release-from-ci.js --skipTests -r ${{ inputs.release_channel }} --commit=${{ inputs.commit_sha }}
cp ./scripts/release/ci-npmrc ~/.npmrc

View File

@@ -44,7 +44,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Discord Webhook Action
uses: tsickert/discord-webhook@86dc739f3f165f16dadc5666051c367efa1692f4
uses: tsickert/discord-webhook@v6.0.0
with:
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
embed-author-name: ${{ github.event.sender.login }}
@@ -71,13 +71,13 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-release-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
path: "**/node_modules"
key: runtime-release-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- run: yarn --cwd scripts/release install --frozen-lockfile
- run: yarn install --frozen-lockfile
working-directory: scripts/release
- run: cp ./scripts/release/ci-npmrc ~/.npmrc
- if: '${{ inputs.only_packages }}'
name: 'Prepare ${{ inputs.only_packages }} from NPM'

View File

@@ -1,35 +0,0 @@
# https://github.com/actions/cache/blob/main/tips-and-workarounds.md#force-deletion-of-caches-overriding-default-cache-eviction-policy
name: (Shared) Cleanup Branch Caches
on:
pull_request:
types:
- closed
workflow_dispatch:
jobs:
cleanup:
runs-on: ubuntu-latest
permissions:
# `actions:write` permission is required to delete caches
# See also: https://docs.github.com/en/rest/actions/cache?apiVersion=2022-11-28#delete-a-github-actions-cache-for-a-repository-using-a-cache-id
actions: write
contents: read
steps:
- name: Cleanup
run: |
echo "Fetching list of cache key"
cacheKeysForPR=$(gh cache list --ref $BRANCH --limit 100 --json id --jq '.[].id')
## Setting this to not fail the workflow while deleting cache keys.
set +e
echo "Deleting caches..."
for cacheKey in $cacheKeysForPR
do
gh cache delete $cacheKey
done
echo "Done"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
BRANCH: refs/pull/${{ github.event.pull_request.number }}/merge

View File

@@ -28,9 +28,8 @@ jobs:
- name: Restore cached node_modules
uses: actions/cache@v4
with:
path: |
**/node_modules
key: shared-lint-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
path: "**/node_modules"
key: shared-lint-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
@@ -49,9 +48,8 @@ jobs:
- name: Restore cached node_modules
uses: actions/cache@v4
with:
path: |
**/node_modules
key: shared-lint-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
path: "**/node_modules"
key: shared-lint-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
@@ -70,9 +68,8 @@ jobs:
- name: Restore cached node_modules
uses: actions/cache@v4
with:
path: |
**/node_modules
key: shared-lint-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
path: "**/node_modules"
key: shared-lint-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
@@ -91,9 +88,8 @@ jobs:
- name: Restore cached node_modules
uses: actions/cache@v4
with:
path: |
**/node_modules
key: shared-lint-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
path: "**/node_modules"
key: shared-lint-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile

View File

@@ -33,6 +33,7 @@ const canaryChannelLabel = 'canary';
const rcNumber = 0;
const stablePackages = {
'eslint-plugin-react-hooks': '5.2.0',
'jest-react': '0.17.0',
react: ReactVersion,
'react-art': ReactVersion,

View File

@@ -0,0 +1,19 @@
'use strict';
/**
* HACK: @poteto React Compiler inlines Zod in its build artifact. Zod spreads values passed to .map
* which causes issues in @babel/plugin-transform-spread in loose mode, as it will result in
* {undefined: undefined} which fails to parse.
*
* [@babel/plugin-transform-block-scoping', {throwIfClosureRequired: true}] also causes issues with
* the built version of the compiler. The minimal set of plugins needed for this file is reexported
* from babel.config-ts.
*
* I will remove this hack later when we move eslint-plugin-react-hooks into the compiler directory.
**/
const baseConfig = require('./babel.config-ts');
module.exports = {
plugins: baseConfig.plugins,
};

View File

@@ -4,7 +4,8 @@ module.exports = {
plugins: [
'@babel/plugin-syntax-jsx',
'@babel/plugin-transform-flow-strip-types',
['@babel/plugin-proposal-class-properties', {loose: true}],
['@babel/plugin-transform-class-properties', {loose: true}],
'@babel/plugin-transform-classes',
'syntax-trailing-function-commas',
[
'@babel/plugin-proposal-object-rest-spread',

View File

@@ -16,7 +16,7 @@
"snap:build": "yarn workspace snap run build",
"snap:ci": "yarn snap:build && yarn snap",
"ts:analyze-trace": "scripts/ts-analyze-trace.sh",
"lint": "../../node_modules/eslint-v8/bin/eslint.js src",
"lint": "yarn eslint src",
"watch": "yarn build --watch"
},
"dependencies": {
@@ -43,7 +43,7 @@
"babel-jest": "^29.0.3",
"babel-plugin-fbt": "^1.0.0",
"babel-plugin-fbt-runtime": "^1.0.0",
"eslint-v8": "npm:eslint@^8.57.1",
"eslint": "^8.57.1",
"invariant": "^2.2.4",
"jest": "^29.0.3",
"jest-environment-jsdom": "^29.0.3",

View File

@@ -181,7 +181,6 @@ function ReactForgetFunctionTransform() {
fn,
forgetOptions,
'Other',
'all_features',
'_c',
null,
null,

View File

@@ -11,7 +11,6 @@ import {
injectReanimatedFlag,
pipelineUsesReanimatedPlugin,
} from '../Entrypoint/Reanimated';
import validateNoUntransformedReferences from '../Entrypoint/ValidateNoUntransformedReferences';
const ENABLE_REACT_COMPILER_TIMINGS =
process.env['ENABLE_REACT_COMPILER_TIMINGS'] === '1';
@@ -62,19 +61,12 @@ export default function BabelPluginReactCompiler(
},
};
}
const result = compileProgram(prog, {
compileProgram(prog, {
opts,
filename: pass.filename ?? null,
comments: pass.file.ast.comments ?? [],
code: pass.file.code,
});
validateNoUntransformedReferences(
prog,
pass.filename ?? null,
opts.logger,
opts.environment,
result?.retryErrors ?? [],
);
if (ENABLE_REACT_COMPILER_TIMINGS === true) {
performance.mark(`${filename}:end`, {
detail: 'BabelPlugin:Program:end',

View File

@@ -8,119 +8,7 @@
import {NodePath} from '@babel/core';
import * as t from '@babel/types';
import {PluginOptions} from './Options';
import {CompilerError} from '../CompilerError';
/**
* Gating rewrite for function declarations which are referenced before their
* declaration site.
*
* ```js
* // original
* export default React.memo(Foo);
* function Foo() { ... }
*
* // React compiler optimized + gated
* import {gating} from 'myGating';
* export default React.memo(Foo);
* const gating_result = gating(); <- inserted
* function Foo_optimized() {} <- inserted
* function Foo_unoptimized() {} <- renamed from Foo
* function Foo() { <- inserted function, which can be hoisted by JS engines
* if (gating_result) return Foo_optimized();
* else return Foo_unoptimized();
* }
* ```
*/
function insertAdditionalFunctionDeclaration(
fnPath: NodePath<t.FunctionDeclaration>,
compiled: t.FunctionDeclaration,
gating: NonNullable<PluginOptions['gating']>,
): void {
const originalFnName = fnPath.node.id;
const originalFnParams = fnPath.node.params;
const compiledParams = fnPath.node.params;
/**
* Note that other than `export default function() {}`, all other function
* declarations must have a binding identifier. Since default exports cannot
* be referenced, it's safe to assume that all function declarations passed
* here will have an identifier.
* https://tc39.es/ecma262/multipage/ecmascript-language-functions-and-classes.html#sec-function-definitions
*/
CompilerError.invariant(originalFnName != null && compiled.id != null, {
reason:
'Expected function declarations that are referenced elsewhere to have a named identifier',
loc: fnPath.node.loc ?? null,
});
CompilerError.invariant(originalFnParams.length === compiledParams.length, {
reason:
'Expected React Compiler optimized function declarations to have the same number of parameters as source',
loc: fnPath.node.loc ?? null,
});
const gatingCondition = fnPath.scope.generateUidIdentifier(
`${gating.importSpecifierName}_result`,
);
const unoptimizedFnName = fnPath.scope.generateUidIdentifier(
`${originalFnName.name}_unoptimized`,
);
const optimizedFnName = fnPath.scope.generateUidIdentifier(
`${originalFnName.name}_optimized`,
);
/**
* Step 1: rename existing functions
*/
compiled.id.name = optimizedFnName.name;
fnPath.get('id').replaceInline(unoptimizedFnName);
/**
* Step 2: insert new function declaration
*/
const newParams: Array<t.Identifier | t.RestElement> = [];
const genNewArgs: Array<() => t.Identifier | t.SpreadElement> = [];
for (let i = 0; i < originalFnParams.length; i++) {
const argName = `arg${i}`;
if (originalFnParams[i].type === 'RestElement') {
newParams.push(t.restElement(t.identifier(argName)));
genNewArgs.push(() => t.spreadElement(t.identifier(argName)));
} else {
newParams.push(t.identifier(argName));
genNewArgs.push(() => t.identifier(argName));
}
}
// insertAfter called in reverse order of how nodes should appear in program
fnPath.insertAfter(
t.functionDeclaration(
originalFnName,
newParams,
t.blockStatement([
t.ifStatement(
gatingCondition,
t.returnStatement(
t.callExpression(
compiled.id,
genNewArgs.map(fn => fn()),
),
),
t.returnStatement(
t.callExpression(
unoptimizedFnName,
genNewArgs.map(fn => fn()),
),
),
),
]),
),
);
fnPath.insertBefore(
t.variableDeclaration('const', [
t.variableDeclarator(
gatingCondition,
t.callExpression(t.identifier(gating.importSpecifierName), []),
),
]),
);
fnPath.insertBefore(compiled);
}
export function insertGatedFunctionDeclaration(
fnPath: NodePath<
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
@@ -130,57 +18,47 @@ export function insertGatedFunctionDeclaration(
| t.ArrowFunctionExpression
| t.FunctionExpression,
gating: NonNullable<PluginOptions['gating']>,
referencedBeforeDeclaration: boolean,
): void {
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, gating);
} else {
const gatingExpression = t.conditionalExpression(
t.callExpression(t.identifier(gating.importSpecifierName), []),
buildFunctionExpression(compiled),
buildFunctionExpression(fnPath.node),
);
const gatingExpression = t.conditionalExpression(
t.callExpression(t.identifier(gating.importSpecifierName), []),
buildFunctionExpression(compiled),
buildFunctionExpression(fnPath.node),
);
/*
* Convert function declarations to named variables *unless* this is an
* `export default function ...` since `export default const ...` is
* not supported. For that case we fall through to replacing w the raw
* conditional expression
*/
if (
fnPath.parentPath.node.type !== 'ExportDefaultDeclaration' &&
fnPath.node.type === 'FunctionDeclaration' &&
fnPath.node.id != null
) {
fnPath.replaceWith(
t.variableDeclaration('const', [
t.variableDeclarator(fnPath.node.id, gatingExpression),
]),
);
} else if (
fnPath.parentPath.node.type === 'ExportDefaultDeclaration' &&
fnPath.node.type !== 'ArrowFunctionExpression' &&
fnPath.node.id != null
) {
fnPath.insertAfter(
t.exportDefaultDeclaration(t.identifier(fnPath.node.id.name)),
);
fnPath.parentPath.replaceWith(
t.variableDeclaration('const', [
t.variableDeclarator(
t.identifier(fnPath.node.id.name),
gatingExpression,
),
]),
);
} else {
fnPath.replaceWith(gatingExpression);
}
/*
* Convert function declarations to named variables *unless* this is an
* `export default function ...` since `export default const ...` is
* not supported. For that case we fall through to replacing w the raw
* conditional expression
*/
if (
fnPath.parentPath.node.type !== 'ExportDefaultDeclaration' &&
fnPath.node.type === 'FunctionDeclaration' &&
fnPath.node.id != null
) {
fnPath.replaceWith(
t.variableDeclaration('const', [
t.variableDeclarator(fnPath.node.id, gatingExpression),
]),
);
} else if (
fnPath.parentPath.node.type === 'ExportDefaultDeclaration' &&
fnPath.node.type !== 'ArrowFunctionExpression' &&
fnPath.node.id != null
) {
fnPath.insertAfter(
t.exportDefaultDeclaration(t.identifier(fnPath.node.id.name)),
);
fnPath.parentPath.replaceWith(
t.variableDeclaration('const', [
t.variableDeclarator(
t.identifier(fnPath.node.id.name),
gatingExpression,
),
]),
);
} else {
fnPath.replaceWith(gatingExpression);
}
}

View File

@@ -12,7 +12,6 @@ import {
EnvironmentConfig,
ExternalFunction,
parseEnvironmentConfig,
tryParseExternalFunction,
} from '../HIR/Environment';
import {hasOwnProperty} from '../Utils/utils';
import {fromZodError} from 'zod-validation-error';
@@ -272,14 +271,6 @@ export function parsePluginOptions(obj: unknown): PluginOptions {
parsedOptions[key] = parseTargetConfig(value);
break;
}
case 'gating': {
if (value == null) {
parsedOptions[key] = null;
} else {
parsedOptions[key] = tryParseExternalFunction(value);
}
break;
}
default: {
parsedOptions[key] = value;
}

View File

@@ -24,7 +24,6 @@ import {
pruneUnusedLabelsHIR,
} from '../HIR';
import {
CompilerMode,
Environment,
EnvironmentConfig,
ReactFunctionType,
@@ -101,7 +100,6 @@ import {outlineJSX} from '../Optimization/OutlineJsx';
import {optimizePropsMethodCalls} from '../Optimization/OptimizePropsMethodCalls';
import {transformFire} from '../Transform';
import {validateNoImpureFunctionsInRender} from '../Validation/ValiateNoImpureFunctionsInRender';
import {CompilerError} from '..';
export type CompilerPipelineValue =
| {kind: 'ast'; name: string; value: CodegenFunction}
@@ -115,7 +113,6 @@ function run(
>,
config: EnvironmentConfig,
fnType: ReactFunctionType,
mode: CompilerMode,
useMemoCacheIdentifier: string,
logger: Logger | null,
filename: string | null,
@@ -125,7 +122,6 @@ function run(
const env = new Environment(
func.scope,
fnType,
mode,
config,
contextIdentifiers,
logger,
@@ -164,10 +160,10 @@ function runWithEnvironment(
validateUseMemo(hir);
if (
env.isInferredMemoEnabled &&
!env.config.enablePreserveExistingManualUseMemo &&
!env.config.disableMemoizationForDebugging &&
!env.config.enableChangeDetectionForDebugging
!env.config.enableChangeDetectionForDebugging &&
!env.config.enableMinimalTransformsForRetry
) {
dropManualMemoization(hir);
log({kind: 'hir', name: 'DropManualMemoization', value: hir});
@@ -200,13 +196,8 @@ function runWithEnvironment(
inferTypes(hir);
log({kind: 'hir', name: 'InferTypes', value: hir});
if (env.isInferredMemoEnabled) {
if (env.config.validateHooksUsage) {
validateHooksUsage(hir);
}
if (env.config.validateNoCapitalizedCalls) {
validateNoCapitalizedCalls(hir);
}
if (env.config.validateHooksUsage) {
validateHooksUsage(hir);
}
if (env.config.enableFire) {
@@ -214,6 +205,10 @@ function runWithEnvironment(
log({kind: 'hir', name: 'TransformFire', value: hir});
}
if (env.config.validateNoCapitalizedCalls) {
validateNoCapitalizedCalls(hir);
}
if (env.config.lowerContextAccess) {
lowerContextAccess(hir, env.config.lowerContextAccess);
}
@@ -224,12 +219,7 @@ function runWithEnvironment(
analyseFunctions(hir);
log({kind: 'hir', name: 'AnalyseFunctions', value: hir});
const fnEffectErrors = inferReferenceEffects(hir);
if (env.isInferredMemoEnabled) {
if (fnEffectErrors.length > 0) {
CompilerError.throw(fnEffectErrors[0]);
}
}
inferReferenceEffects(hir);
log({kind: 'hir', name: 'InferReferenceEffects', value: hir});
validateLocalsNotReassignedAfterRender(hir);
@@ -249,30 +239,28 @@ function runWithEnvironment(
inferMutableRanges(hir);
log({kind: 'hir', name: 'InferMutableRanges', value: hir});
if (env.isInferredMemoEnabled) {
if (env.config.assertValidMutableRanges) {
assertValidMutableRanges(hir);
}
if (env.config.assertValidMutableRanges) {
assertValidMutableRanges(hir);
}
if (env.config.validateRefAccessDuringRender) {
validateNoRefAccessInRender(hir);
}
if (env.config.validateRefAccessDuringRender) {
validateNoRefAccessInRender(hir);
}
if (env.config.validateNoSetStateInRender) {
validateNoSetStateInRender(hir);
}
if (env.config.validateNoSetStateInRender) {
validateNoSetStateInRender(hir);
}
if (env.config.validateNoSetStateInPassiveEffects) {
validateNoSetStateInPassiveEffects(hir);
}
if (env.config.validateNoSetStateInPassiveEffects) {
validateNoSetStateInPassiveEffects(hir);
}
if (env.config.validateNoJSXInTryStatements) {
validateNoJSXInTryStatement(hir);
}
if (env.config.validateNoJSXInTryStatements) {
validateNoJSXInTryStatement(hir);
}
if (env.config.validateNoImpureFunctionsInRender) {
validateNoImpureFunctionsInRender(hir);
}
if (env.config.validateNoImpureFunctionsInRender) {
validateNoImpureFunctionsInRender(hir);
}
inferReactivePlaces(hir);
@@ -292,12 +280,7 @@ function runWithEnvironment(
value: hir,
});
if (env.isInferredMemoEnabled) {
/**
* Only create reactive scopes (which directly map to generated memo blocks)
* if inferred memoization is enabled. This makes all later passes which
* transform reactive-scope labeled instructions no-ops.
*/
if (!env.config.enableMinimalTransformsForRetry) {
inferReactiveScopeVariables(hir);
log({kind: 'hir', name: 'InferReactiveScopeVariables', value: hir});
}
@@ -546,7 +529,6 @@ export function compileFn(
>,
config: EnvironmentConfig,
fnType: ReactFunctionType,
mode: CompilerMode,
useMemoCacheIdentifier: string,
logger: Logger | null,
filename: string | null,
@@ -556,7 +538,6 @@ export function compileFn(
func,
config,
fnType,
mode,
useMemoCacheIdentifier,
logger,
filename,

View File

@@ -16,6 +16,8 @@ import {
EnvironmentConfig,
ExternalFunction,
ReactFunctionType,
MINIMAL_RETRY_CONFIG,
tryParseExternalFunction,
} from '../HIR/Environment';
import {CodegenFunction} from '../ReactiveScopes';
import {isComponentDeclaration} from '../Utils/ComponentDeclaration';
@@ -271,9 +273,6 @@ function isFilePartOfSources(
return false;
}
type CompileProgramResult = {
retryErrors: Array<{fn: BabelFn; error: CompilerError}>;
};
/**
* `compileProgram` is directly invoked by the react-compiler babel plugin, so
* exceptions thrown by this function will fail the babel build.
@@ -288,16 +287,16 @@ type CompileProgramResult = {
export function compileProgram(
program: NodePath<t.Program>,
pass: CompilerPass,
): CompileProgramResult | null {
): void {
if (shouldSkipCompilation(program, pass)) {
return null;
return;
}
const environment = pass.opts.environment;
const restrictedImportsErr = validateRestrictedImports(program, environment);
if (restrictedImportsErr) {
handleError(restrictedImportsErr, pass, null);
return null;
return;
}
const useMemoCacheIdentifier = program.scope.generateUidIdentifier('c');
@@ -368,7 +367,7 @@ export function compileProgram(
filename: pass.filename ?? null,
},
);
const retryErrors: Array<{fn: BabelFn; error: CompilerError}> = [];
const processFn = (
fn: BabelFn,
fnType: ReactFunctionType,
@@ -409,7 +408,6 @@ export function compileProgram(
fn,
environment,
fnType,
'all_features',
useMemoCacheIdentifier.name,
pass.opts.logger,
pass.filename,
@@ -420,7 +418,28 @@ export function compileProgram(
compileResult = {kind: 'error', error: err};
}
}
// If non-memoization features are enabled, retry regardless of error kind
if (compileResult.kind === 'error' && environment.enableFire) {
try {
compileResult = {
kind: 'compile',
compiledFn: compileFn(
fn,
{
...environment,
...MINIMAL_RETRY_CONFIG,
},
fnType,
useMemoCacheIdentifier.name,
pass.opts.logger,
pass.filename,
pass.code,
),
};
} catch (err) {
compileResult = {kind: 'error', error: err};
}
}
if (compileResult.kind === 'error') {
/**
* If an opt out directive is present, log only instead of throwing and don't mark as
@@ -431,33 +450,7 @@ export function compileProgram(
} else {
handleError(compileResult.error, pass, fn.node.loc ?? null);
}
// If non-memoization features are enabled, retry regardless of error kind
if (
!(environment.enableFire || environment.inferEffectDependencies != null)
) {
return null;
}
try {
compileResult = {
kind: 'compile',
compiledFn: compileFn(
fn,
environment,
fnType,
'no_inferred_memo',
useMemoCacheIdentifier.name,
pass.opts.logger,
pass.filename,
pass.code,
),
};
} catch (err) {
// TODO: we might want to log error here, but this will also result in duplicate logging
if (err instanceof CompilerError) {
retryErrors.push({fn, error: err});
}
return null;
}
return null;
}
pass.opts.logger?.logEvent(pass.filename, {
@@ -546,28 +539,32 @@ export function compileProgram(
program.node.directives,
);
if (moduleScopeOptOutDirectives.length > 0) {
return null;
return;
}
let gating: null | {
gatingFn: ExternalFunction;
referencedBeforeDeclared: Set<CompileResult>;
} = null;
if (pass.opts.gating != null) {
gating = {
gatingFn: pass.opts.gating,
referencedBeforeDeclared:
getFunctionReferencedBeforeDeclarationAtTopLevel(program, compiledFns),
};
const error = checkFunctionReferencedBeforeDeclarationAtTopLevel(
program,
compiledFns.map(result => {
return result.originalFn;
}),
);
if (error) {
handleError(error, pass, null);
return;
}
}
const hasLoweredContextAccess = compiledFns.some(
c => c.compiledFn.hasLoweredContextAccess,
);
const externalFunctions: Array<ExternalFunction> = [];
let gating: null | ExternalFunction = null;
try {
// TODO: check for duplicate import specifiers
if (gating != null) {
externalFunctions.push(gating.gatingFn);
if (pass.opts.gating != null) {
gating = tryParseExternalFunction(pass.opts.gating);
externalFunctions.push(gating);
}
const lowerContextAccess = environment.lowerContextAccess;
@@ -604,7 +601,7 @@ export function compileProgram(
}
} catch (err) {
handleError(err, pass, null);
return null;
return;
}
/*
@@ -616,12 +613,7 @@ export function compileProgram(
const transformedFn = createNewFunctionNode(originalFn, compiledFn);
if (gating != null && kind === 'original') {
insertGatedFunctionDeclaration(
originalFn,
transformedFn,
gating.gatingFn,
gating.referencedBeforeDeclared.has(result),
);
insertGatedFunctionDeclaration(originalFn, transformedFn, gating);
} else {
originalFn.replaceWith(transformedFn);
}
@@ -646,7 +638,6 @@ export function compileProgram(
}
addImportsToProgram(program, externalFunctions);
}
return {retryErrors};
}
function shouldSkipCompilation(
@@ -1102,23 +1093,20 @@ function getFunctionName(
}
}
function getFunctionReferencedBeforeDeclarationAtTopLevel(
function checkFunctionReferencedBeforeDeclarationAtTopLevel(
program: NodePath<t.Program>,
fns: Array<CompileResult>,
): Set<CompileResult> {
const fnNames = new Map<string, {id: t.Identifier; fn: CompileResult}>(
fns: Array<BabelFn>,
): CompilerError | null {
const fnIds = new Set(
fns
.map<[NodePath<t.Expression> | null, CompileResult]>(fn => [
getFunctionName(fn.originalFn),
fn,
])
.map(fn => getFunctionName(fn))
.filter(
(entry): entry is [NodePath<t.Identifier>, CompileResult] =>
!!entry[0] && entry[0].isIdentifier(),
(name): name is NodePath<t.Identifier> => !!name && name.isIdentifier(),
)
.map(entry => [entry[0].node.name, {id: entry[0].node, fn: entry[1]}]),
.map(name => name.node),
);
const referencedBeforeDeclaration = new Set<CompileResult>();
const fnNames = new Map([...fnIds].map(id => [id.name, id]));
const errors = new CompilerError();
program.traverse({
TypeAnnotation(path) {
@@ -1144,7 +1132,8 @@ function getFunctionReferencedBeforeDeclarationAtTopLevel(
* We've reached the declaration, hoisting is no longer possible, stop
* checking for this component name.
*/
if (id.node === fn.id) {
if (fnIds.has(id.node)) {
fnIds.delete(id.node);
fnNames.delete(id.node.name);
return;
}
@@ -1154,13 +1143,21 @@ function getFunctionReferencedBeforeDeclarationAtTopLevel(
* A null scope means there's no function scope, which means we're at the
* top level scope.
*/
if (scope === null && id.isReferencedIdentifier()) {
referencedBeforeDeclaration.add(fn.fn);
if (scope === null) {
errors.pushErrorDetail(
new CompilerErrorDetail({
reason: `Encountered a function used before its declaration, which breaks Forget's gating codegen due to hoisting`,
description: `Rewrite the reference to ${fn.name} to not rely on hoisting to fix this issue`,
loc: fn.loc ?? null,
suggestions: null,
severity: ErrorSeverity.Invariant,
}),
);
}
},
});
return referencedBeforeDeclaration;
return errors.details.length > 0 ? errors : null;
}
function getReactCompilerRuntimeModule(opts: PluginOptions): string {

View File

@@ -1,275 +0,0 @@
import {NodePath} from '@babel/core';
import * as t from '@babel/types';
import {
CompilerError,
CompilerErrorDetailOptions,
EnvironmentConfig,
ErrorSeverity,
Logger,
} from '..';
import {getOrInsertWith} from '../Utils/utils';
import {Environment} from '../HIR';
import {DEFAULT_EXPORT} from '../HIR/Environment';
function throwInvalidReact(
options: Omit<CompilerErrorDetailOptions, 'severity'>,
{logger, filename}: TraversalState,
): never {
const detail: CompilerErrorDetailOptions = {
...options,
severity: ErrorSeverity.InvalidReact,
};
logger?.logEvent(filename, {
kind: 'CompileError',
fnLoc: null,
detail,
});
CompilerError.throw(detail);
}
function assertValidEffectImportReference(
numArgs: number,
paths: Array<NodePath<t.Node>>,
context: TraversalState,
): void {
for (const path of paths) {
const parent = path.parentPath;
if (parent != null && parent.isCallExpression()) {
const args = parent.get('arguments');
/**
* 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) {
const maybeErrorDiagnostic = matchCompilerDiagnostic(
path,
context.transformErrors,
);
/**
* Note that we cannot easily check the type of the first argument here,
* as it may have already been transformed by the compiler (and not
* memoized).
*/
throwInvalidReact(
{
reason:
'[InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. ' +
'This will break your build! ' +
'To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics.',
description: maybeErrorDiagnostic
? `(Bailout reason: ${maybeErrorDiagnostic})`
: null,
loc: parent.node.loc ?? null,
},
context,
);
}
}
}
}
function assertValidFireImportReference(
paths: Array<NodePath<t.Node>>,
context: TraversalState,
): void {
if (paths.length > 0) {
const maybeErrorDiagnostic = matchCompilerDiagnostic(
paths[0],
context.transformErrors,
);
throwInvalidReact(
{
reason:
'[Fire] Untransformed reference to compiler-required feature. ' +
'Either remove this `fire` call or ensure it is successfully transformed by the compiler',
description: maybeErrorDiagnostic
? `(Bailout reason: ${maybeErrorDiagnostic})`
: null,
loc: paths[0].node.loc ?? null,
},
context,
);
}
}
export default function validateNoUntransformedReferences(
path: NodePath<t.Program>,
filename: string | null,
logger: Logger | null,
env: EnvironmentConfig,
transformErrors: Array<{fn: NodePath<t.Node>; error: CompilerError}>,
): void {
const moduleLoadChecks = new Map<
string,
Map<string, CheckInvalidReferenceFn>
>();
if (env.enableFire) {
/**
* Error on any untransformed references to `fire` (e.g. including non-call
* expressions)
*/
for (const module of Environment.knownReactModules) {
const react = getOrInsertWith(moduleLoadChecks, module, () => new Map());
react.set('fire', assertValidFireImportReference);
}
}
if (env.inferEffectDependencies) {
for (const {
function: {source, importSpecifierName},
numRequiredArgs,
} of env.inferEffectDependencies) {
const module = getOrInsertWith(moduleLoadChecks, source, () => new Map());
module.set(
importSpecifierName,
assertValidEffectImportReference.bind(null, numRequiredArgs),
);
}
}
if (moduleLoadChecks.size > 0) {
transformProgram(path, moduleLoadChecks, filename, logger, transformErrors);
}
}
type TraversalState = {
shouldInvalidateScopes: boolean;
program: NodePath<t.Program>;
logger: Logger | null;
filename: string | null;
transformErrors: Array<{fn: NodePath<t.Node>; error: CompilerError}>;
};
type CheckInvalidReferenceFn = (
paths: Array<NodePath<t.Node>>,
context: TraversalState,
) => void;
function validateImportSpecifier(
specifier: NodePath<t.ImportSpecifier>,
importSpecifierChecks: Map<string, CheckInvalidReferenceFn>,
state: TraversalState,
): void {
const imported = specifier.get('imported');
const specifierName: string =
imported.node.type === 'Identifier'
? imported.node.name
: imported.node.value;
const checkFn = importSpecifierChecks.get(specifierName);
if (checkFn == null) {
return;
}
if (state.shouldInvalidateScopes) {
state.shouldInvalidateScopes = false;
state.program.scope.crawl();
}
const local = specifier.get('local');
const binding = local.scope.getBinding(local.node.name);
CompilerError.invariant(binding != null, {
reason: 'Expected binding to be found for import specifier',
loc: local.node.loc ?? null,
});
checkFn(binding.referencePaths, state);
}
function validateNamespacedImport(
specifier: NodePath<t.ImportNamespaceSpecifier | t.ImportDefaultSpecifier>,
importSpecifierChecks: Map<string, CheckInvalidReferenceFn>,
state: TraversalState,
): void {
if (state.shouldInvalidateScopes) {
state.shouldInvalidateScopes = false;
state.program.scope.crawl();
}
const local = specifier.get('local');
const binding = local.scope.getBinding(local.node.name);
const defaultCheckFn = importSpecifierChecks.get(DEFAULT_EXPORT);
CompilerError.invariant(binding != null, {
reason: 'Expected binding to be found for import specifier',
loc: local.node.loc ?? null,
});
const filteredReferences = new Map<
CheckInvalidReferenceFn,
Array<NodePath<t.Node>>
>();
for (const reference of binding.referencePaths) {
if (defaultCheckFn != null) {
getOrInsertWith(filteredReferences, defaultCheckFn, () => []).push(
reference,
);
}
const parent = reference.parentPath;
if (
parent != null &&
parent.isMemberExpression() &&
parent.get('object') === reference
) {
if (parent.node.computed || parent.node.property.type !== 'Identifier') {
continue;
}
const checkFn = importSpecifierChecks.get(parent.node.property.name);
if (checkFn != null) {
getOrInsertWith(filteredReferences, checkFn, () => []).push(parent);
}
}
}
for (const [checkFn, references] of filteredReferences) {
checkFn(references, state);
}
}
function transformProgram(
path: NodePath<t.Program>,
moduleLoadChecks: Map<string, Map<string, CheckInvalidReferenceFn>>,
filename: string | null,
logger: Logger | null,
transformErrors: Array<{fn: NodePath<t.Node>; error: CompilerError}>,
): void {
const traversalState: TraversalState = {
shouldInvalidateScopes: true,
program: path,
filename,
logger,
transformErrors,
};
path.traverse({
ImportDeclaration(path: NodePath<t.ImportDeclaration>) {
const importSpecifierChecks = moduleLoadChecks.get(
path.node.source.value,
);
if (importSpecifierChecks == null) {
return;
}
const specifiers = path.get('specifiers');
for (const specifier of specifiers) {
if (specifier.isImportSpecifier()) {
validateImportSpecifier(
specifier,
importSpecifierChecks,
traversalState,
);
} else {
validateNamespacedImport(
specifier as NodePath<
t.ImportNamespaceSpecifier | t.ImportDefaultSpecifier
>,
importSpecifierChecks,
traversalState,
);
}
}
},
});
}
function matchCompilerDiagnostic(
badReference: NodePath<t.Node>,
transformErrors: Array<{fn: NodePath<t.Node>; error: CompilerError}>,
): string | null {
for (const {fn, error} of transformErrors) {
if (fn.isAncestor(badReference)) {
return error.toString();
}
}
return null;
}

View File

@@ -1455,11 +1455,6 @@ function lowerObjectPropertyKey(
kind: 'identifier',
name: key.node.name,
};
} else if (key.isNumericLiteral()) {
return {
kind: 'identifier',
name: String(key.node.value),
};
}
builder.errors.push({

View File

@@ -13,13 +13,11 @@ import {
BlockId,
DependencyPathEntry,
GeneratedSource,
getHookKind,
HIRFunction,
Identifier,
IdentifierId,
InstructionId,
InstructionValue,
LoweredFunction,
PropertyLiteral,
ReactiveScopeDependency,
ScopeId,
@@ -114,9 +112,6 @@ export function collectHoistablePropertyLoads(
hoistableFromOptionals,
registry,
nestedFnImmutableContext: null,
assumedInvokedFns: fn.env.config.enableTreatFunctionDepsAsConditional
? new Set()
: getAssumedInvokedFunctions(fn),
});
}
@@ -132,11 +127,6 @@ type CollectHoistablePropertyLoadsContext = {
* but are currently kept separate for readability.
*/
nestedFnImmutableContext: ReadonlySet<IdentifierId> | null;
/**
* Functions which are assumed to be eventually called (as opposed to ones which might
* not be called, e.g. the 0th argument of Array.map)
*/
assumedInvokedFns: ReadonlySet<LoweredFunction>;
};
function collectHoistablePropertyLoadsImpl(
fn: HIRFunction,
@@ -348,13 +338,7 @@ function collectNonNullsInBlocks(
context.registry.getOrCreateIdentifier(identifier),
);
}
const nodes = new Map<
BlockId,
{
block: BasicBlock;
assumedNonNullObjects: Set<PropertyPathNode>;
}
>();
const nodes = new Map<BlockId, BlockInfo>();
for (const [_, block] of fn.body.blocks) {
const assumedNonNullObjects = new Set<PropertyPathNode>(
knownNonNullIdentifiers,
@@ -374,30 +358,32 @@ function collectNonNullsInBlocks(
) {
assumedNonNullObjects.add(maybeNonNull);
}
if (instr.value.kind === 'FunctionExpression') {
if (
(instr.value.kind === 'FunctionExpression' ||
instr.value.kind === 'ObjectMethod') &&
!fn.env.config.enableTreatFunctionDepsAsConditional
) {
const innerFn = instr.value.loweredFunc;
if (context.assumedInvokedFns.has(innerFn)) {
const innerHoistableMap = collectHoistablePropertyLoadsImpl(
innerFn.func,
{
...context,
nestedFnImmutableContext:
context.nestedFnImmutableContext ??
new Set(
innerFn.func.context
.filter(place =>
isImmutableAtInstr(place.identifier, instr.id, context),
)
.map(place => place.identifier.id),
),
},
);
const innerHoistables = assertNonNull(
innerHoistableMap.get(innerFn.func.body.entry),
);
for (const entry of innerHoistables.assumedNonNullObjects) {
assumedNonNullObjects.add(entry);
}
const innerHoistableMap = collectHoistablePropertyLoadsImpl(
innerFn.func,
{
...context,
nestedFnImmutableContext:
context.nestedFnImmutableContext ??
new Set(
innerFn.func.context
.filter(place =>
isImmutableAtInstr(place.identifier, instr.id, context),
)
.map(place => place.identifier.id),
),
},
);
const innerHoistables = assertNonNull(
innerHoistableMap.get(innerFn.func.body.entry),
);
for (const entry of innerHoistables.assumedNonNullObjects) {
assumedNonNullObjects.add(entry);
}
}
}
@@ -605,130 +591,3 @@ function reduceMaybeOptionalChains(
}
} while (changed);
}
function getAssumedInvokedFunctions(
fn: HIRFunction,
temporaries: Map<
IdentifierId,
{fn: LoweredFunction; mayInvoke: Set<LoweredFunction>}
> = new Map(),
): ReadonlySet<LoweredFunction> {
const hoistableFunctions = new Set<LoweredFunction>();
/**
* Step 1: Conservatively collect identifier to function expression mappings
*/
for (const block of fn.body.blocks.values()) {
for (const {lvalue, value} of block.instructions) {
/**
* Conservatively only match function expressions which can have guaranteed ssa.
* ObjectMethods and ObjectProperties do not.
*/
if (value.kind === 'FunctionExpression') {
temporaries.set(lvalue.identifier.id, {
fn: value.loweredFunc,
mayInvoke: new Set(),
});
} else if (value.kind === 'StoreLocal') {
const lvalue = value.lvalue.place.identifier;
const maybeLoweredFunc = temporaries.get(value.value.identifier.id);
if (maybeLoweredFunc != null) {
temporaries.set(lvalue.id, maybeLoweredFunc);
}
} else if (value.kind === 'LoadLocal') {
const maybeLoweredFunc = temporaries.get(value.place.identifier.id);
if (maybeLoweredFunc != null) {
temporaries.set(lvalue.identifier.id, maybeLoweredFunc);
}
}
}
}
/**
* Step 2: Forward pass to do analysis of assumed function calls. Note that
* this is conservative and does not count indirect references through
* containers (e.g. `return {cb: () => {...}})`).
*/
for (const block of fn.body.blocks.values()) {
for (const {lvalue, value} of block.instructions) {
if (value.kind === 'CallExpression') {
const callee = value.callee;
const maybeHook = getHookKind(fn.env, callee.identifier);
const maybeLoweredFunc = temporaries.get(callee.identifier.id);
if (maybeLoweredFunc != null) {
// Direct calls
hoistableFunctions.add(maybeLoweredFunc.fn);
} else if (maybeHook != null) {
/**
* Assume arguments to all hooks are safe to invoke
*/
for (const arg of value.args) {
if (arg.kind === 'Identifier') {
const maybeLoweredFunc = temporaries.get(arg.identifier.id);
if (maybeLoweredFunc != null) {
hoistableFunctions.add(maybeLoweredFunc.fn);
}
}
}
}
} else if (value.kind === 'JsxExpression') {
/**
* Assume JSX attributes and children are safe to invoke
*/
for (const attr of value.props) {
if (attr.kind === 'JsxSpreadAttribute') {
continue;
}
const maybeLoweredFunc = temporaries.get(attr.place.identifier.id);
if (maybeLoweredFunc != null) {
hoistableFunctions.add(maybeLoweredFunc.fn);
}
}
for (const child of value.children ?? []) {
const maybeLoweredFunc = temporaries.get(child.identifier.id);
if (maybeLoweredFunc != null) {
hoistableFunctions.add(maybeLoweredFunc.fn);
}
}
} else if (value.kind === 'FunctionExpression') {
/**
* Recursively traverse into other function expressions which may invoke
* or pass already declared functions to react (e.g. as JSXAttributes).
*
* If lambda A calls lambda B, we assume lambda B is safe to invoke if
* lambda A is -- even if lambda B is conditionally called. (see
* `conditional-call-chain` fixture for example).
*/
const loweredFunc = value.loweredFunc.func;
const lambdasCalled = getAssumedInvokedFunctions(
loweredFunc,
temporaries,
);
const maybeLoweredFunc = temporaries.get(lvalue.identifier.id);
if (maybeLoweredFunc != null) {
for (const called of lambdasCalled) {
maybeLoweredFunc.mayInvoke.add(called);
}
}
}
}
if (block.terminal.kind === 'return') {
/**
* Assume directly returned functions are safe to call
*/
const maybeLoweredFunc = temporaries.get(
block.terminal.value.identifier.id,
);
if (maybeLoweredFunc != null) {
hoistableFunctions.add(maybeLoweredFunc.fn);
}
}
}
for (const [_, {fn, mayInvoke}] of temporaries) {
if (hoistableFunctions.has(fn)) {
for (const called of mayInvoke) {
hoistableFunctions.add(called);
}
}
}
return hoistableFunctions;
}

View File

@@ -96,8 +96,6 @@ export const MacroSchema = z.union([
z.tuple([z.string(), z.array(MacroMethodSchema)]),
]);
export type CompilerMode = 'all_features' | 'no_inferred_memo';
export type Macro = z.infer<typeof MacroSchema>;
export type MacroMethod = z.infer<typeof MacroMethodSchema>;
@@ -552,6 +550,8 @@ const EnvironmentConfigSchema = z.object({
*/
disableMemoizationForDebugging: z.boolean().default(false),
enableMinimalTransformsForRetry: z.boolean().default(false),
/**
* When true, rather using memoized values, the compiler will always re-compute
* values, and then use a heuristic to compare the memoized value to the newly
@@ -626,6 +626,17 @@ const EnvironmentConfigSchema = z.object({
export type EnvironmentConfig = z.infer<typeof EnvironmentConfigSchema>;
export const MINIMAL_RETRY_CONFIG: PartialEnvironmentConfig = {
validateHooksUsage: false,
validateRefAccessDuringRender: false,
validateNoSetStateInRender: false,
validateNoSetStateInPassiveEffects: false,
validateNoJSXInTryStatements: false,
validateMemoizedEffectDependencies: false,
validateNoCapitalizedCalls: null,
validateBlocklistedImports: null,
enableMinimalTransformsForRetry: true,
};
/**
* For test fixtures and playground only.
*
@@ -840,7 +851,6 @@ export class Environment {
code: string | null;
config: EnvironmentConfig;
fnType: ReactFunctionType;
compilerMode: CompilerMode;
useMemoCacheIdentifier: string;
hasLoweredContextAccess: boolean;
hasFireRewrite: boolean;
@@ -851,7 +861,6 @@ export class Environment {
constructor(
scope: BabelScope,
fnType: ReactFunctionType,
compilerMode: CompilerMode,
config: EnvironmentConfig,
contextIdentifiers: Set<t.Identifier>,
logger: Logger | null,
@@ -861,7 +870,6 @@ export class Environment {
) {
this.#scope = scope;
this.fnType = fnType;
this.compilerMode = compilerMode;
this.config = config;
this.filename = filename;
this.code = code;
@@ -916,10 +924,6 @@ export class Environment {
this.#hoistedIdentifiers = new Set();
}
get isInferredMemoEnabled(): boolean {
return this.compilerMode !== 'no_inferred_memo';
}
get nextIdentifierId(): IdentifierId {
return makeIdentifierId(this.#nextIdentifer++);
}
@@ -1121,7 +1125,6 @@ export class Environment {
moduleName.toLowerCase() === 'react-dom'
);
}
static knownReactModules: ReadonlyArray<string> = ['react', 'react-dom'];
getFallthroughPropertyType(
receiver: Type,

View File

@@ -703,10 +703,6 @@ export type ObjectPropertyKey =
| {
kind: 'computed';
name: Place;
}
| {
kind: 'number';
name: number;
};
export type ObjectProperty = {

View File

@@ -330,9 +330,6 @@ function printObjectPropertyKey(key: ObjectPropertyKey): string {
case 'computed': {
return `[${printPlace(key.name)}]`;
}
case 'number': {
return String(key.name);
}
}
}

View File

@@ -5,12 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import {
CompilerError,
CompilerErrorDetailOptions,
ErrorSeverity,
ValueKind,
} from '..';
import {CompilerError, ErrorSeverity, ValueKind} from '..';
import {
AbstractValue,
BasicBlock,
@@ -295,21 +290,21 @@ export function inferTerminalFunctionEffects(
return functionEffects;
}
export function transformFunctionEffectErrors(
export function raiseFunctionEffectErrors(
functionEffects: Array<FunctionEffect>,
): Array<CompilerErrorDetailOptions> {
return functionEffects.map(eff => {
): void {
functionEffects.forEach(eff => {
switch (eff.kind) {
case 'ReactMutation':
case 'GlobalMutation': {
return eff.error;
CompilerError.throw(eff.error);
}
case 'ContextMutation': {
return {
CompilerError.throw({
severity: ErrorSeverity.Invariant,
reason: `Unexpected ContextMutation in top-level function effects`,
loc: eff.loc,
};
});
}
default:
assertExhaustive(

View File

@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import {CompilerError, CompilerErrorDetailOptions} from '../CompilerError';
import {CompilerError} from '../CompilerError';
import {Environment} from '../HIR';
import {
AbstractValue,
@@ -49,7 +49,7 @@ import {assertExhaustive} from '../Utils/utils';
import {
inferTerminalFunctionEffects,
inferInstructionFunctionEffects,
transformFunctionEffectErrors,
raiseFunctionEffectErrors,
} from './InferFunctionEffects';
const UndefinedValue: InstructionValue = {
@@ -103,7 +103,7 @@ const UndefinedValue: InstructionValue = {
export default function inferReferenceEffects(
fn: HIRFunction,
options: {isFunctionExpression: boolean} = {isFunctionExpression: false},
): Array<CompilerErrorDetailOptions> {
): void {
/*
* Initial state contains function params
* TODO: include module declarations here as well
@@ -241,9 +241,8 @@ export default function inferReferenceEffects(
if (options.isFunctionExpression) {
fn.effects = functionEffects;
return [];
} else {
return transformFunctionEffectErrors(functionEffects);
} else if (!fn.env.config.enableMinimalTransformsForRetry) {
raiseFunctionEffectErrors(functionEffects);
}
}

View File

@@ -2429,9 +2429,6 @@ function codegenObjectPropertyKey(
});
return expr;
}
case 'number': {
return t.numericLiteral(key.name);
}
}
}

View File

@@ -23,11 +23,11 @@ import { c as _c } from "react/compiler-runtime";
function Component(props) {
const $ = _c(4);
let t0;
if ($[0] !== props) {
if ($[0] !== props.name) {
t0 = function () {
return <div>{props.name}</div>;
};
$[0] = props;
$[0] = props.name;
$[1] = t0;
} else {
t0 = $[1];

View File

@@ -1,62 +0,0 @@
## Input
```javascript
// @flow @gating
import {Stringify} from 'shared-runtime';
import * as React from 'react';
component Foo(ref: React.RefSetter<Controls>) {
return <Stringify ref={ref} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: eval('(...args) => React.createElement(Foo, args)'),
params: [{ref: React.createRef()}],
};
```
## Code
```javascript
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 $ = _c(2);
let t0;
if ($[0] !== ref) {
t0 = <Stringify ref={ref} />;
$[0] = ref;
$[1] = t0;
} else {
t0 = $[1];
}
return t0;
}
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);
}
export const FIXTURE_ENTRYPOINT = {
fn: eval("(...args) => React.createElement(Foo, args)"),
params: [{ ref: React.createRef() }],
};
```
### Eval output
(kind: ok) <div>{"ref":null}</div>

View File

@@ -1,12 +0,0 @@
// @flow @gating
import {Stringify} from 'shared-runtime';
import * as React from 'react';
component Foo(ref: React.RefSetter<Controls>) {
return <Stringify ref={ref} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: eval('(...args) => React.createElement(Foo, args)'),
params: [{ref: React.createRef()}],
};

View File

@@ -0,0 +1,24 @@
## Input
```javascript
// @flow @gating
component Foo(ref: React.RefSetter<Controls>) {
return <Bar ref={ref} />;
}
```
## Error
```
1 | // @flow @gating
> 2 | component Foo(ref: React.RefSetter<Controls>) {
| ^^^ Invariant: Encountered a function used before its declaration, which breaks Forget's gating codegen due to hoisting. Rewrite the reference to Foo_withRef to not rely on hoisting to fix this issue (2:2)
3 | return <Bar ref={ref} />;
4 | }
5 |
```

View File

@@ -0,0 +1,4 @@
// @flow @gating
component Foo(ref: React.RefSetter<Controls>) {
return <Bar ref={ref} />;
}

View File

@@ -0,0 +1,26 @@
## Input
```javascript
// @gating
const Foo = React.forwardRef(Foo_withRef);
function Foo_withRef(props, ref) {
return <Bar ref={ref} {...props}></Bar>;
}
```
## Error
```
1 | // @gating
2 | const Foo = React.forwardRef(Foo_withRef);
> 3 | function Foo_withRef(props, ref) {
| ^^^^^^^^^^^ Invariant: Encountered a function used before its declaration, which breaks Forget's gating codegen due to hoisting. Rewrite the reference to Foo_withRef to not rely on hoisting to fix this issue (3:3)
4 | return <Bar ref={ref} {...props}></Bar>;
5 | }
6 |
```

View File

@@ -0,0 +1,5 @@
// @gating
const Foo = React.forwardRef(Foo_withRef);
function Foo_withRef(props, ref) {
return <Bar ref={ref} {...props}></Bar>;
}

View File

@@ -0,0 +1,24 @@
## Input
```javascript
// @gating
import {memo} from 'react';
export default memo(Foo);
function Foo() {}
```
## Error
```
3 |
4 | export default memo(Foo);
> 5 | function Foo() {}
| ^^^ Invariant: Encountered a function used before its declaration, which breaks Forget's gating codegen due to hoisting. Rewrite the reference to Foo to not rely on hoisting to fix this issue (5:5)
6 |
```

View File

@@ -0,0 +1,5 @@
// @gating
import {memo} from 'react';
export default memo(Foo);
function Foo() {}

View File

@@ -1,60 +0,0 @@
## Input
```javascript
// @gating
import {identity, useHook as useRenamed} from 'shared-runtime';
const _ = {
useHook: () => {},
};
identity(_.useHook);
function useHook() {
useRenamed();
return <div>hello world!</div>;
}
export const FIXTURE_ENTRYPOINT = {
fn: useHook,
params: [{}],
};
```
## Code
```javascript
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() ? () => {} : () => {},
};
identity(_.useHook);
const useHook = isForgetEnabled_Fixtures()
? function useHook() {
const $ = _c(1);
useRenamed();
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = <div>hello world!</div>;
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
: function useHook() {
useRenamed();
return <div>hello world!</div>;
};
export const FIXTURE_ENTRYPOINT = {
fn: useHook,
params: [{}],
};
```
### Eval output
(kind: ok) <div>hello world!</div>

View File

@@ -1,16 +0,0 @@
// @gating
import {identity, useHook as useRenamed} from 'shared-runtime';
const _ = {
useHook: () => {},
};
identity(_.useHook);
function useHook() {
useRenamed();
return <div>hello world!</div>;
}
export const FIXTURE_ENTRYPOINT = {
fn: useHook,
params: [{}],
};

View File

@@ -1,61 +0,0 @@
## Input
```javascript
// @gating
import {createRef, forwardRef} from 'react';
import {Stringify} from 'shared-runtime';
const Foo = forwardRef(Foo_withRef);
function Foo_withRef(props, ref) {
return <Stringify ref={ref} {...props} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: eval('(...args) => React.createElement(Foo, args)'),
params: [{prop1: 1, prop2: 2, ref: createRef()}],
};
```
## Code
```javascript
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 $ = _c(3);
let t0;
if ($[0] !== props || $[1] !== ref) {
t0 = <Stringify ref={ref} {...props} />;
$[0] = props;
$[1] = ref;
$[2] = t0;
} else {
t0 = $[2];
}
return t0;
}
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);
}
export const FIXTURE_ENTRYPOINT = {
fn: eval("(...args) => React.createElement(Foo, args)"),
params: [{ prop1: 1, prop2: 2, ref: createRef() }],
};
```
### Eval output
(kind: ok) <div>{"0":{"prop1":1,"prop2":2,"ref":{"current":null}},"ref":"[[ cyclic ref *3 ]]"}</div>

View File

@@ -1,13 +0,0 @@
// @gating
import {createRef, forwardRef} from 'react';
import {Stringify} from 'shared-runtime';
const Foo = forwardRef(Foo_withRef);
function Foo_withRef(props, ref) {
return <Stringify ref={ref} {...props} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: eval('(...args) => React.createElement(Foo, args)'),
params: [{prop1: 1, prop2: 2, ref: createRef()}],
};

View File

@@ -1,64 +0,0 @@
## Input
```javascript
// @gating
import {memo} from 'react';
import {Stringify} from 'shared-runtime';
export default memo(Foo);
function Foo({prop1, prop2}) {
'use memo';
return <Stringify prop1={prop1} prop2={prop2} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: eval('Foo'),
params: [{prop1: 1, prop2: 2}],
};
```
## Code
```javascript
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) {
"use memo";
const $ = _c(3);
const { prop1, prop2 } = t0;
let t1;
if ($[0] !== prop1 || $[1] !== prop2) {
t1 = <Stringify prop1={prop1} prop2={prop2} />;
$[0] = prop1;
$[1] = prop2;
$[2] = t1;
} else {
t1 = $[2];
}
return t1;
}
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);
}
export const FIXTURE_ENTRYPOINT = {
fn: eval("Foo"),
params: [{ prop1: 1, prop2: 2 }],
};
```
### Eval output
(kind: ok) <div>{"prop1":1,"prop2":2}</div>

View File

@@ -1,14 +0,0 @@
// @gating
import {memo} from 'react';
import {Stringify} from 'shared-runtime';
export default memo(Foo);
function Foo({prop1, prop2}) {
'use memo';
return <Stringify prop1={prop1} prop2={prop2} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: eval('Foo'),
params: [{prop1: 1, prop2: 2}],
};

View File

@@ -1,59 +0,0 @@
## Input
```javascript
// @gating
import * as React from 'react';
let Foo;
const MemoFoo = React.memo(Foo);
Foo = () => <div>hello world!</div>;
/**
* Evaluate this fixture module to assert that compiler + original have the same
* runtime error message.
*/
export const FIXTURE_ENTRYPOINT = {
fn: () => {},
params: [],
};
```
## Code
```javascript
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
import { c as _c } from "react/compiler-runtime"; // @gating
import * as React from "react";
let Foo;
const MemoFoo = React.memo(Foo);
Foo = isForgetEnabled_Fixtures()
? () => {
const $ = _c(1);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = <div>hello world!</div>;
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
: () => <div>hello world!</div>;
/**
* Evaluate this fixture module to assert that compiler + original have the same
* runtime error message.
*/
export const FIXTURE_ENTRYPOINT = {
fn: isForgetEnabled_Fixtures() ? () => {} : () => {},
params: [],
};
```
### Eval output
(kind: ok)
logs: ['memo: The first argument must be a component. Instead received: %s','undefined']

View File

@@ -1,15 +0,0 @@
// @gating
import * as React from 'react';
let Foo;
const MemoFoo = React.memo(Foo);
Foo = () => <div>hello world!</div>;
/**
* Evaluate this fixture module to assert that compiler + original have the same
* runtime error message.
*/
export const FIXTURE_ENTRYPOINT = {
fn: () => {},
params: [],
};

View File

@@ -1,86 +0,0 @@
## Input
```javascript
// @gating
import * as React from 'react';
/**
* Test that the correct `Foo` is printed
*/
let Foo = () => <div>hello world 1!</div>;
const MemoOne = React.memo(Foo);
Foo = () => <div>hello world 2!</div>;
const MemoTwo = React.memo(Foo);
export const FIXTURE_ENTRYPOINT = {
fn: () => {
'use no memo';
return (
<>
<MemoOne />
<MemoTwo />
</>
);
},
params: [],
};
```
## Code
```javascript
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
import { c as _c } from "react/compiler-runtime"; // @gating
import * as React from "react";
/**
* Test that the correct `Foo` is printed
*/
let Foo = isForgetEnabled_Fixtures()
? () => {
const $ = _c(1);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = <div>hello world 1!</div>;
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
: () => <div>hello world 1!</div>;
const MemoOne = React.memo(Foo);
Foo = isForgetEnabled_Fixtures()
? () => {
const $ = _c(1);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = <div>hello world 2!</div>;
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
: () => <div>hello world 2!</div>;
const MemoTwo = React.memo(Foo);
export const FIXTURE_ENTRYPOINT = {
fn: () => {
"use no memo";
return (
<>
<MemoOne />
<MemoTwo />
</>
);
},
params: [],
};
```
### Eval output
(kind: ok) <div>hello world 1!</div><div>hello world 2!</div>

View File

@@ -1,23 +0,0 @@
// @gating
import * as React from 'react';
/**
* Test that the correct `Foo` is printed
*/
let Foo = () => <div>hello world 1!</div>;
const MemoOne = React.memo(Foo);
Foo = () => <div>hello world 2!</div>;
const MemoTwo = React.memo(Foo);
export const FIXTURE_ENTRYPOINT = {
fn: () => {
'use no memo';
return (
<>
<MemoOne />
<MemoTwo />
</>
);
},
params: [],
};

View File

@@ -1,26 +0,0 @@
## Input
```javascript
// @inferEffectDependencies @compilationMode(infer) @panicThreshold(none)
import useMyEffect from 'useEffectWrapper';
function nonReactFn(arg) {
useMyEffect(() => [1, 2, arg]);
}
```
## Error
```
3 |
4 | function nonReactFn(arg) {
> 5 | useMyEffect(() => [1, 2, arg]);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (5:5)
6 | }
7 |
```

View File

@@ -1,6 +0,0 @@
// @inferEffectDependencies @compilationMode(infer) @panicThreshold(none)
import useMyEffect from 'useEffectWrapper';
function nonReactFn(arg) {
useMyEffect(() => [1, 2, arg]);
}

View File

@@ -1,26 +0,0 @@
## Input
```javascript
// @inferEffectDependencies @compilationMode(infer) @panicThreshold(none)
import {useEffect} from 'react';
function nonReactFn(arg) {
useEffect(() => [1, 2, arg]);
}
```
## Error
```
3 |
4 | function nonReactFn(arg) {
> 5 | useEffect(() => [1, 2, arg]);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (5:5)
6 | }
7 |
```

View File

@@ -1,6 +0,0 @@
// @inferEffectDependencies @compilationMode(infer) @panicThreshold(none)
import {useEffect} from 'react';
function nonReactFn(arg) {
useEffect(() => [1, 2, arg]);
}

View File

@@ -1,41 +0,0 @@
## Input
```javascript
// @inferEffectDependencies @panicThreshold(none)
import {useEffect} from 'react';
/**
* Error on non-inlined effect functions:
* 1. From the effect hook callee's perspective, it only makes sense
* to either
* (a) never hard error (i.e. failing to infer deps is acceptable) or
* (b) always hard error,
* regardless of whether the callback function is an inline fn.
* 2. (Technical detail) it's harder to support detecting cases in which
* function (pre-Forget transform) was inline but becomes memoized
*/
function Component({foo}) {
function f() {
console.log(foo);
}
// No inferred dep array, the argument is not a lambda
useEffect(f);
}
```
## Error
```
18 |
19 | // No inferred dep array, the argument is not a lambda
> 20 | useEffect(f);
| ^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (20:20)
21 | }
22 |
```

View File

@@ -1,21 +0,0 @@
// @inferEffectDependencies @panicThreshold(none)
import {useEffect} from 'react';
/**
* Error on non-inlined effect functions:
* 1. From the effect hook callee's perspective, it only makes sense
* to either
* (a) never hard error (i.e. failing to infer deps is acceptable) or
* (b) always hard error,
* regardless of whether the callback function is an inline fn.
* 2. (Technical detail) it's harder to support detecting cases in which
* function (pre-Forget transform) was inline but becomes memoized
*/
function Component({foo}) {
function f() {
console.log(foo);
}
// No inferred dep array, the argument is not a lambda
useEffect(f);
}

View File

@@ -1,27 +0,0 @@
## Input
```javascript
// @inferEffectDependencies @panicThreshold(none)
import React from 'react';
function NonReactiveDepInEffect() {
const obj = makeObject_Primitives();
React.useEffect(() => print(obj));
}
```
## Error
```
4 | function NonReactiveDepInEffect() {
5 | const obj = makeObject_Primitives();
> 6 | React.useEffect(() => print(obj));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (6:6)
7 | }
8 |
```

View File

@@ -1,51 +0,0 @@
## Input
```javascript
// @inferEffectDependencies @panicThreshold(none)
import {useSpecialEffect} from 'shared-runtime';
/**
* Note that a react compiler-based transform still has limitations on JS syntax.
* We should surface these as actionable lint / build errors to devs.
*/
function Component({prop1}) {
'use memo';
useSpecialEffect(() => {
try {
console.log(prop1);
} finally {
console.log('exiting');
}
}, [prop1]);
return <div>{prop1}</div>;
}
```
## Error
```
8 | function Component({prop1}) {
9 | 'use memo';
> 10 | useSpecialEffect(() => {
| ^^^^^^^^^^^^^^^^^^^^^^^^
> 11 | try {
| ^^^^^^^^^
> 12 | console.log(prop1);
| ^^^^^^^^^
> 13 | } finally {
| ^^^^^^^^^
> 14 | console.log('exiting');
| ^^^^^^^^^
> 15 | }
| ^^^^^^^^^
> 16 | }, [prop1]);
| ^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics.. (Bailout reason: Todo: (BuildHIR::lowerStatement) Handle TryStatement without a catch clause (11:15)) (10:16)
17 | return <div>{prop1}</div>;
18 | }
19 |
```

View File

@@ -1,18 +0,0 @@
// @inferEffectDependencies @panicThreshold(none)
import {useSpecialEffect} from 'shared-runtime';
/**
* Note that a react compiler-based transform still has limitations on JS syntax.
* We should surface these as actionable lint / build errors to devs.
*/
function Component({prop1}) {
'use memo';
useSpecialEffect(() => {
try {
console.log(prop1);
} finally {
console.log('exiting');
}
}, [prop1]);
return <div>{prop1}</div>;
}

View File

@@ -1,27 +0,0 @@
## Input
```javascript
// @inferEffectDependencies @panicThreshold(none)
import {useEffect} from 'react';
function Component({propVal}) {
'use no memo';
useEffect(() => [propVal]);
}
```
## Error
```
4 | function Component({propVal}) {
5 | 'use no memo';
> 6 | useEffect(() => [propVal]);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (6:6)
7 | }
8 |
```

View File

@@ -1,7 +0,0 @@
// @inferEffectDependencies @panicThreshold(none)
import {useEffect} from 'react';
function Component({propVal}) {
'use no memo';
useEffect(() => [propVal]);
}

View File

@@ -34,6 +34,13 @@ function Component({foo, bar}) {
console.log(bar.qux);
});
function f() {
console.log(foo);
}
// No inferred dep array, the argument is not a lambda
useEffect(f);
useEffectWrapper(() => {
console.log(foo);
});
@@ -51,7 +58,7 @@ import useEffectWrapper from "useEffectWrapper";
const moduleNonReactive = 0;
function Component(t0) {
const $ = _c(12);
const $ = _c(14);
const { foo, bar } = t0;
const ref = useRef(0);
@@ -112,7 +119,7 @@ function Component(t0) {
useEffect(t4, [bar.baz, bar.qux]);
let t5;
if ($[10] !== foo) {
t5 = () => {
t5 = function f() {
console.log(foo);
};
$[10] = foo;
@@ -120,7 +127,20 @@ function Component(t0) {
} else {
t5 = $[11];
}
useEffectWrapper(t5, [foo]);
const f = t5;
useEffect(f);
let t6;
if ($[12] !== foo) {
t6 = () => {
console.log(foo);
};
$[12] = foo;
$[13] = t6;
} else {
t6 = $[13];
}
useEffectWrapper(t6, [foo]);
}
```

View File

@@ -30,6 +30,13 @@ function Component({foo, bar}) {
console.log(bar.qux);
});
function f() {
console.log(foo);
}
// No inferred dep array, the argument is not a lambda
useEffect(f);
useEffectWrapper(() => {
console.log(foo);
});

View File

@@ -0,0 +1,44 @@
## Input
```javascript
// @inferEffectDependencies
import React from 'react';
function NonReactiveDepInEffect() {
const obj = makeObject_Primitives();
React.useEffect(() => print(obj));
}
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies
import React from "react";
function NonReactiveDepInEffect() {
const $ = _c(2);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = makeObject_Primitives();
$[0] = t0;
} else {
t0 = $[0];
}
const obj = t0;
let t1;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t1 = () => print(obj);
$[1] = t1;
} else {
t1 = $[1];
}
React.useEffect(t1);
}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -1,133 +0,0 @@
## Input
```javascript
import {Stringify} from 'shared-runtime';
/**
* Forked from array-map-simple.js
*
* Named lambdas (e.g. cb1) may be defined in the top scope of a function and
* used in a different lambda (getArrMap1).
*
* Here, we should try to determine if cb1 is actually called. In this case:
* - getArrMap1 is assumed to be called as it's passed to JSX
* - cb1 is not assumed to be called since it's only used as a call operand
*/
function useFoo({arr1, arr2}) {
const cb1 = e => arr1[0].value + e.value;
const getArrMap1 = () => arr1.map(cb1);
const cb2 = e => arr2[0].value + e.value;
const getArrMap2 = () => arr1.map(cb2);
return (
<Stringify
getArrMap1={getArrMap1}
getArrMap2={getArrMap2}
shouldInvokeFns={true}
/>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{arr1: [], arr2: []}],
sequentialRenders: [
{arr1: [], arr2: []},
{arr1: [], arr2: null},
{arr1: [{value: 1}, {value: 2}], arr2: [{value: -1}]},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { Stringify } from "shared-runtime";
/**
* Forked from array-map-simple.js
*
* Named lambdas (e.g. cb1) may be defined in the top scope of a function and
* used in a different lambda (getArrMap1).
*
* Here, we should try to determine if cb1 is actually called. In this case:
* - getArrMap1 is assumed to be called as it's passed to JSX
* - cb1 is not assumed to be called since it's only used as a call operand
*/
function useFoo(t0) {
const $ = _c(13);
const { arr1, arr2 } = t0;
let t1;
if ($[0] !== arr1[0]) {
t1 = (e) => arr1[0].value + e.value;
$[0] = arr1[0];
$[1] = t1;
} else {
t1 = $[1];
}
const cb1 = t1;
let t2;
if ($[2] !== arr1 || $[3] !== cb1) {
t2 = () => arr1.map(cb1);
$[2] = arr1;
$[3] = cb1;
$[4] = t2;
} else {
t2 = $[4];
}
const getArrMap1 = t2;
let t3;
if ($[5] !== arr2) {
t3 = (e_0) => arr2[0].value + e_0.value;
$[5] = arr2;
$[6] = t3;
} else {
t3 = $[6];
}
const cb2 = t3;
let t4;
if ($[7] !== arr1 || $[8] !== cb2) {
t4 = () => arr1.map(cb2);
$[7] = arr1;
$[8] = cb2;
$[9] = t4;
} else {
t4 = $[9];
}
const getArrMap2 = t4;
let t5;
if ($[10] !== getArrMap1 || $[11] !== getArrMap2) {
t5 = (
<Stringify
getArrMap1={getArrMap1}
getArrMap2={getArrMap2}
shouldInvokeFns={true}
/>
);
$[10] = getArrMap1;
$[11] = getArrMap2;
$[12] = t5;
} else {
t5 = $[12];
}
return t5;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ arr1: [], arr2: [] }],
sequentialRenders: [
{ arr1: [], arr2: [] },
{ arr1: [], arr2: null },
{ arr1: [{ value: 1 }, { value: 2 }], arr2: [{ value: -1 }] },
],
};
```
### Eval output
(kind: ok) <div>{"getArrMap1":{"kind":"Function","result":[]},"getArrMap2":{"kind":"Function","result":[]},"shouldInvokeFns":true}</div>
<div>{"getArrMap1":{"kind":"Function","result":[]},"getArrMap2":{"kind":"Function","result":[]},"shouldInvokeFns":true}</div>
<div>{"getArrMap1":{"kind":"Function","result":[2,3]},"getArrMap2":{"kind":"Function","result":[0,1]},"shouldInvokeFns":true}</div>

View File

@@ -1,35 +0,0 @@
import {Stringify} from 'shared-runtime';
/**
* Forked from array-map-simple.js
*
* Named lambdas (e.g. cb1) may be defined in the top scope of a function and
* used in a different lambda (getArrMap1).
*
* Here, we should try to determine if cb1 is actually called. In this case:
* - getArrMap1 is assumed to be called as it's passed to JSX
* - cb1 is not assumed to be called since it's only used as a call operand
*/
function useFoo({arr1, arr2}) {
const cb1 = e => arr1[0].value + e.value;
const getArrMap1 = () => arr1.map(cb1);
const cb2 = e => arr2[0].value + e.value;
const getArrMap2 = () => arr1.map(cb2);
return (
<Stringify
getArrMap1={getArrMap1}
getArrMap2={getArrMap2}
shouldInvokeFns={true}
/>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{arr1: [], arr2: []}],
sequentialRenders: [
{arr1: [], arr2: []},
{arr1: [], arr2: null},
{arr1: [{value: 1}, {value: 2}], arr2: [{value: -1}]},
],
};

View File

@@ -1,108 +0,0 @@
## Input
```javascript
/**
* Forked from array-map-simple.js
*
* Whether lambdas are named or passed inline shouldn't affect whether we expect
* it to be called.
*/
function useFoo({arr1, arr2}) {
const cb1 = e => arr1[0].value + e.value;
const x = arr1.map(cb1);
const cb2 = e => arr2[0].value + e.value;
const y = arr1.map(cb2);
return [x, y];
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{arr1: [], arr2: []}],
sequentialRenders: [
{arr1: [], arr2: []},
{arr1: [], arr2: null},
{arr1: [{value: 1}, {value: 2}], arr2: [{value: -1}]},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; /**
* Forked from array-map-simple.js
*
* Whether lambdas are named or passed inline shouldn't affect whether we expect
* it to be called.
*/
function useFoo(t0) {
const $ = _c(13);
const { arr1, arr2 } = t0;
let t1;
if ($[0] !== arr1[0]) {
t1 = (e) => arr1[0].value + e.value;
$[0] = arr1[0];
$[1] = t1;
} else {
t1 = $[1];
}
const cb1 = t1;
let t2;
if ($[2] !== arr1 || $[3] !== cb1) {
t2 = arr1.map(cb1);
$[2] = arr1;
$[3] = cb1;
$[4] = t2;
} else {
t2 = $[4];
}
const x = t2;
let t3;
if ($[5] !== arr2) {
t3 = (e_0) => arr2[0].value + e_0.value;
$[5] = arr2;
$[6] = t3;
} else {
t3 = $[6];
}
const cb2 = t3;
let t4;
if ($[7] !== arr1 || $[8] !== cb2) {
t4 = arr1.map(cb2);
$[7] = arr1;
$[8] = cb2;
$[9] = t4;
} else {
t4 = $[9];
}
const y = t4;
let t5;
if ($[10] !== x || $[11] !== y) {
t5 = [x, y];
$[10] = x;
$[11] = y;
$[12] = t5;
} else {
t5 = $[12];
}
return t5;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ arr1: [], arr2: [] }],
sequentialRenders: [
{ arr1: [], arr2: [] },
{ arr1: [], arr2: null },
{ arr1: [{ value: 1 }, { value: 2 }], arr2: [{ value: -1 }] },
],
};
```
### Eval output
(kind: ok) [[],[]]
[[],[]]
[[2,3],[0,1]]

View File

@@ -1,23 +0,0 @@
/**
* Forked from array-map-simple.js
*
* Whether lambdas are named or passed inline shouldn't affect whether we expect
* it to be called.
*/
function useFoo({arr1, arr2}) {
const cb1 = e => arr1[0].value + e.value;
const x = arr1.map(cb1);
const cb2 = e => arr2[0].value + e.value;
const y = arr1.map(cb2);
return [x, y];
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{arr1: [], arr2: []}],
sequentialRenders: [
{arr1: [], arr2: []},
{arr1: [], arr2: null},
{arr1: [{value: 1}, {value: 2}], arr2: [{value: -1}]},
],
};

View File

@@ -1,130 +0,0 @@
## Input
```javascript
/**
* Forked from array-map-simple.js
*
* Here, getVal1 has a known callsite in `cb1`, but `cb1` isn't known to be
* called (it's only passed to array.map). In this case, we should be
* conservative and assume that all named lambdas are conditionally called.
*/
function useFoo({arr1, arr2}) {
const getVal1 = () => arr1[0].value;
const cb1 = e => getVal1() + e.value;
const x = arr1.map(cb1);
const getVal2 = () => arr2[0].value;
const cb2 = e => getVal2() + e.value;
const y = arr1.map(cb2);
return [x, y];
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{arr1: [], arr2: []}],
sequentialRenders: [
{arr1: [], arr2: []},
{arr1: [], arr2: null},
{arr1: [{value: 1}, {value: 2}], arr2: [{value: -1}]},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; /**
* Forked from array-map-simple.js
*
* Here, getVal1 has a known callsite in `cb1`, but `cb1` isn't known to be
* called (it's only passed to array.map). In this case, we should be
* conservative and assume that all named lambdas are conditionally called.
*/
function useFoo(t0) {
const $ = _c(17);
const { arr1, arr2 } = t0;
let t1;
if ($[0] !== arr1[0]) {
t1 = () => arr1[0].value;
$[0] = arr1[0];
$[1] = t1;
} else {
t1 = $[1];
}
const getVal1 = t1;
let t2;
if ($[2] !== getVal1) {
t2 = (e) => getVal1() + e.value;
$[2] = getVal1;
$[3] = t2;
} else {
t2 = $[3];
}
const cb1 = t2;
let t3;
if ($[4] !== arr1 || $[5] !== cb1) {
t3 = arr1.map(cb1);
$[4] = arr1;
$[5] = cb1;
$[6] = t3;
} else {
t3 = $[6];
}
const x = t3;
let t4;
if ($[7] !== arr2) {
t4 = () => arr2[0].value;
$[7] = arr2;
$[8] = t4;
} else {
t4 = $[8];
}
const getVal2 = t4;
let t5;
if ($[9] !== getVal2) {
t5 = (e_0) => getVal2() + e_0.value;
$[9] = getVal2;
$[10] = t5;
} else {
t5 = $[10];
}
const cb2 = t5;
let t6;
if ($[11] !== arr1 || $[12] !== cb2) {
t6 = arr1.map(cb2);
$[11] = arr1;
$[12] = cb2;
$[13] = t6;
} else {
t6 = $[13];
}
const y = t6;
let t7;
if ($[14] !== x || $[15] !== y) {
t7 = [x, y];
$[14] = x;
$[15] = y;
$[16] = t7;
} else {
t7 = $[16];
}
return t7;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ arr1: [], arr2: [] }],
sequentialRenders: [
{ arr1: [], arr2: [] },
{ arr1: [], arr2: null },
{ arr1: [{ value: 1 }, { value: 2 }], arr2: [{ value: -1 }] },
],
};
```
### Eval output
(kind: ok) [[],[]]
[[],[]]
[[2,3],[0,1]]

View File

@@ -1,26 +0,0 @@
/**
* Forked from array-map-simple.js
*
* Here, getVal1 has a known callsite in `cb1`, but `cb1` isn't known to be
* called (it's only passed to array.map). In this case, we should be
* conservative and assume that all named lambdas are conditionally called.
*/
function useFoo({arr1, arr2}) {
const getVal1 = () => arr1[0].value;
const cb1 = e => getVal1() + e.value;
const x = arr1.map(cb1);
const getVal2 = () => arr2[0].value;
const cb2 = e => getVal2() + e.value;
const y = arr1.map(cb2);
return [x, y];
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{arr1: [], arr2: []}],
sequentialRenders: [
{arr1: [], arr2: []},
{arr1: [], arr2: null},
{arr1: [{value: 1}, {value: 2}], arr2: [{value: -1}]},
],
};

View File

@@ -1,111 +0,0 @@
## Input
```javascript
/**
* Test that we're not hoisting property reads from lambdas that are created to
* pass to opaque functions, which often have maybe-invoke semantics.
*
* In this example, we shouldn't hoist `arr[0].value` out of the lambda.
* ```js
* e => arr[0].value + e.value <-- created to pass to map
* arr.map(<cb>) <-- argument only invoked if array is non-empty
* ```
*/
function useFoo({arr1, arr2}) {
const x = arr1.map(e => arr1[0].value + e.value);
const y = arr1.map(e => arr2[0].value + e.value);
return [x, y];
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{arr1: [], arr2: []}],
sequentialRenders: [
{arr1: [], arr2: []},
{arr1: [], arr2: null},
{arr1: [{value: 1}, {value: 2}], arr2: [{value: -1}]},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; /**
* Test that we're not hoisting property reads from lambdas that are created to
* pass to opaque functions, which often have maybe-invoke semantics.
*
* In this example, we shouldn't hoist `arr[0].value` out of the lambda.
* ```js
* e => arr[0].value + e.value <-- created to pass to map
* arr.map(<cb>) <-- argument only invoked if array is non-empty
* ```
*/
function useFoo(t0) {
const $ = _c(12);
const { arr1, arr2 } = t0;
let t1;
if ($[0] !== arr1) {
let t2;
if ($[2] !== arr1[0]) {
t2 = (e) => arr1[0].value + e.value;
$[2] = arr1[0];
$[3] = t2;
} else {
t2 = $[3];
}
t1 = arr1.map(t2);
$[0] = arr1;
$[1] = t1;
} else {
t1 = $[1];
}
const x = t1;
let t2;
if ($[4] !== arr1 || $[5] !== arr2) {
let t3;
if ($[7] !== arr2) {
t3 = (e_0) => arr2[0].value + e_0.value;
$[7] = arr2;
$[8] = t3;
} else {
t3 = $[8];
}
t2 = arr1.map(t3);
$[4] = arr1;
$[5] = arr2;
$[6] = t2;
} else {
t2 = $[6];
}
const y = t2;
let t3;
if ($[9] !== x || $[10] !== y) {
t3 = [x, y];
$[9] = x;
$[10] = y;
$[11] = t3;
} else {
t3 = $[11];
}
return t3;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ arr1: [], arr2: [] }],
sequentialRenders: [
{ arr1: [], arr2: [] },
{ arr1: [], arr2: null },
{ arr1: [{ value: 1 }, { value: 2 }], arr2: [{ value: -1 }] },
],
};
```
### Eval output
(kind: ok) [[],[]]
[[],[]]
[[2,3],[0,1]]

View File

@@ -1,25 +0,0 @@
/**
* Test that we're not hoisting property reads from lambdas that are created to
* pass to opaque functions, which often have maybe-invoke semantics.
*
* In this example, we shouldn't hoist `arr[0].value` out of the lambda.
* ```js
* e => arr[0].value + e.value <-- created to pass to map
* arr.map(<cb>) <-- argument only invoked if array is non-empty
* ```
*/
function useFoo({arr1, arr2}) {
const x = arr1.map(e => arr1[0].value + e.value);
const y = arr1.map(e => arr2[0].value + e.value);
return [x, y];
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{arr1: [], arr2: []}],
sequentialRenders: [
{arr1: [], arr2: []},
{arr1: [], arr2: null},
{arr1: [{value: 1}, {value: 2}], arr2: [{value: -1}]},
],
};

View File

@@ -1,112 +0,0 @@
## Input
```javascript
import {useRef} from 'react';
import {Stringify} from 'shared-runtime';
function Component({a, b}) {
const logA = () => {
console.log(a.value);
};
const logB = () => {
console.log(b.value);
};
const hasLogged = useRef(false);
const log = () => {
if (!hasLogged.current) {
logA();
logB();
hasLogged.current = true;
}
};
return <Stringify log={log} shouldInvokeFns={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{a: {value: 1}, b: {value: 2}}],
sequentialRenders: [
{a: {value: 1}, b: {value: 2}},
{a: {value: 3}, b: {value: 4}},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { useRef } from "react";
import { Stringify } from "shared-runtime";
function Component(t0) {
const $ = _c(9);
const { a, b } = t0;
let t1;
if ($[0] !== a.value) {
t1 = () => {
console.log(a.value);
};
$[0] = a.value;
$[1] = t1;
} else {
t1 = $[1];
}
const logA = t1;
let t2;
if ($[2] !== b.value) {
t2 = () => {
console.log(b.value);
};
$[2] = b.value;
$[3] = t2;
} else {
t2 = $[3];
}
const logB = t2;
const hasLogged = useRef(false);
let t3;
if ($[4] !== logA || $[5] !== logB) {
t3 = () => {
if (!hasLogged.current) {
logA();
logB();
hasLogged.current = true;
}
};
$[4] = logA;
$[5] = logB;
$[6] = t3;
} else {
t3 = $[6];
}
const log = t3;
let t4;
if ($[7] !== log) {
t4 = <Stringify log={log} shouldInvokeFns={true} />;
$[7] = log;
$[8] = t4;
} else {
t4 = $[8];
}
return t4;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ a: { value: 1 }, b: { value: 2 } }],
sequentialRenders: [
{ a: { value: 1 }, b: { value: 2 } },
{ a: { value: 3 }, b: { value: 4 } },
],
};
```
### Eval output
(kind: ok) <div>{"log":{"kind":"Function"},"shouldInvokeFns":true}</div>
<div>{"log":{"kind":"Function"},"shouldInvokeFns":true}</div>
logs: [1,2]

View File

@@ -1,29 +0,0 @@
import {useRef} from 'react';
import {Stringify} from 'shared-runtime';
function Component({a, b}) {
const logA = () => {
console.log(a.value);
};
const logB = () => {
console.log(b.value);
};
const hasLogged = useRef(false);
const log = () => {
if (!hasLogged.current) {
logA();
logB();
hasLogged.current = true;
}
};
return <Stringify log={log} shouldInvokeFns={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{a: {value: 1}, b: {value: 2}}],
sequentialRenders: [
{a: {value: 1}, b: {value: 2}},
{a: {value: 3}, b: {value: 4}},
],
};

View File

@@ -1,85 +0,0 @@
## Input
```javascript
import {useState} from 'react';
import {useIdentity} from 'shared-runtime';
/**
* Assume that conditionally called functions can be invoked and that their
* property loads are hoistable to the function declaration site.
*/
function useMakeCallback({obj}: {obj: {value: number}}) {
const [state, setState] = useState(0);
const cb = () => {
if (obj.value !== 0) setState(obj.value);
};
useIdentity(null);
if (state === 0) {
cb();
}
return {cb};
}
export const FIXTURE_ENTRYPOINT = {
fn: useMakeCallback,
params: [{obj: {value: 1}}],
sequentialRenders: [{obj: {value: 1}}, {obj: {value: 2}}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { useState } from "react";
import { useIdentity } from "shared-runtime";
/**
* Assume that conditionally called functions can be invoked and that their
* property loads are hoistable to the function declaration site.
*/
function useMakeCallback(t0) {
const $ = _c(4);
const { obj } = t0;
const [state, setState] = useState(0);
let t1;
if ($[0] !== obj.value) {
t1 = () => {
if (obj.value !== 0) {
setState(obj.value);
}
};
$[0] = obj.value;
$[1] = t1;
} else {
t1 = $[1];
}
const cb = t1;
useIdentity(null);
if (state === 0) {
cb();
}
let t2;
if ($[2] !== cb) {
t2 = { cb };
$[2] = cb;
$[3] = t2;
} else {
t2 = $[3];
}
return t2;
}
export const FIXTURE_ENTRYPOINT = {
fn: useMakeCallback,
params: [{ obj: { value: 1 } }],
sequentialRenders: [{ obj: { value: 1 } }, { obj: { value: 2 } }],
};
```
### Eval output
(kind: ok) {"cb":"[[ function params=0 ]]"}
{"cb":"[[ function params=0 ]]"}

View File

@@ -1,23 +0,0 @@
import {useState} from 'react';
import {useIdentity} from 'shared-runtime';
/**
* Assume that conditionally called functions can be invoked and that their
* property loads are hoistable to the function declaration site.
*/
function useMakeCallback({obj}: {obj: {value: number}}) {
const [state, setState] = useState(0);
const cb = () => {
if (obj.value !== 0) setState(obj.value);
};
useIdentity(null);
if (state === 0) {
cb();
}
return {cb};
}
export const FIXTURE_ENTRYPOINT = {
fn: useMakeCallback,
params: [{obj: {value: 1}}],
sequentialRenders: [{obj: {value: 1}}, {obj: {value: 2}}],
};

View File

@@ -1,87 +0,0 @@
## Input
```javascript
import {createHookWrapper} from 'shared-runtime';
/**
* Assume that conditionally returned functions can be invoked and that their
* property loads are hoistable to the function declaration site.
*/
function useMakeCallback({
obj,
shouldMakeCb,
setState,
}: {
obj: {value: number};
shouldMakeCb: boolean;
setState: (newState: number) => void;
}) {
const cb = () => setState(obj.value);
if (shouldMakeCb) return cb;
else return null;
}
const setState = (arg: number) => {
'use no memo';
return arg;
};
export const FIXTURE_ENTRYPOINT = {
fn: createHookWrapper(useMakeCallback),
params: [{obj: {value: 1}, shouldMakeCb: true, setState}],
sequentialRenders: [
{obj: {value: 1}, shouldMakeCb: true, setState},
{obj: {value: 2}, shouldMakeCb: true, setState},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { createHookWrapper } from "shared-runtime";
/**
* Assume that conditionally returned functions can be invoked and that their
* property loads are hoistable to the function declaration site.
*/
function useMakeCallback(t0) {
const $ = _c(3);
const { obj, shouldMakeCb, setState } = t0;
let t1;
if ($[0] !== obj.value || $[1] !== setState) {
t1 = () => setState(obj.value);
$[0] = obj.value;
$[1] = setState;
$[2] = t1;
} else {
t1 = $[2];
}
const cb = t1;
if (shouldMakeCb) {
return cb;
} else {
return null;
}
}
const setState = (arg: number) => {
"use no memo";
return arg;
};
export const FIXTURE_ENTRYPOINT = {
fn: createHookWrapper(useMakeCallback),
params: [{ obj: { value: 1 }, shouldMakeCb: true, setState }],
sequentialRenders: [
{ obj: { value: 1 }, shouldMakeCb: true, setState },
{ obj: { value: 2 }, shouldMakeCb: true, setState },
],
};
```
### Eval output
(kind: ok) <div>{"result":{"kind":"Function","result":1},"shouldInvokeFns":true}</div>
<div>{"result":{"kind":"Function","result":2},"shouldInvokeFns":true}</div>

View File

@@ -1,32 +0,0 @@
import {createHookWrapper} from 'shared-runtime';
/**
* Assume that conditionally returned functions can be invoked and that their
* property loads are hoistable to the function declaration site.
*/
function useMakeCallback({
obj,
shouldMakeCb,
setState,
}: {
obj: {value: number};
shouldMakeCb: boolean;
setState: (newState: number) => void;
}) {
const cb = () => setState(obj.value);
if (shouldMakeCb) return cb;
else return null;
}
const setState = (arg: number) => {
'use no memo';
return arg;
};
export const FIXTURE_ENTRYPOINT = {
fn: createHookWrapper(useMakeCallback),
params: [{obj: {value: 1}, shouldMakeCb: true, setState}],
sequentialRenders: [
{obj: {value: 1}, shouldMakeCb: true, setState},
{obj: {value: 2}, shouldMakeCb: true, setState},
],
};

View File

@@ -1,74 +0,0 @@
## Input
```javascript
import {useState} from 'react';
import {useIdentity} from 'shared-runtime';
function useMakeCallback({obj}: {obj: {value: number}}) {
const [state, setState] = useState(0);
const cb = () => {
if (obj.value !== state) setState(obj.value);
};
useIdentity();
cb();
return [cb];
}
export const FIXTURE_ENTRYPOINT = {
fn: useMakeCallback,
params: [{obj: {value: 1}}],
sequentialRenders: [{obj: {value: 1}}, {obj: {value: 2}}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { useState } from "react";
import { useIdentity } from "shared-runtime";
function useMakeCallback(t0) {
const $ = _c(5);
const { obj } = t0;
const [state, setState] = useState(0);
let t1;
if ($[0] !== obj.value || $[1] !== state) {
t1 = () => {
if (obj.value !== state) {
setState(obj.value);
}
};
$[0] = obj.value;
$[1] = state;
$[2] = t1;
} else {
t1 = $[2];
}
const cb = t1;
useIdentity();
cb();
let t2;
if ($[3] !== cb) {
t2 = [cb];
$[3] = cb;
$[4] = t2;
} else {
t2 = $[4];
}
return t2;
}
export const FIXTURE_ENTRYPOINT = {
fn: useMakeCallback,
params: [{ obj: { value: 1 } }],
sequentialRenders: [{ obj: { value: 1 } }, { obj: { value: 2 } }],
};
```
### Eval output
(kind: ok) ["[[ function params=0 ]]"]
["[[ function params=0 ]]"]

View File

@@ -1,17 +0,0 @@
import {useState} from 'react';
import {useIdentity} from 'shared-runtime';
function useMakeCallback({obj}: {obj: {value: number}}) {
const [state, setState] = useState(0);
const cb = () => {
if (obj.value !== state) setState(obj.value);
};
useIdentity();
cb();
return [cb];
}
export const FIXTURE_ENTRYPOINT = {
fn: useMakeCallback,
params: [{obj: {value: 1}}],
sequentialRenders: [{obj: {value: 1}}, {obj: {value: 2}}],
};

View File

@@ -1,130 +0,0 @@
## Input
```javascript
import {createHookWrapper} from 'shared-runtime';
/**
* (Given that the returned lambda is assumed to be invoked, see
* return-function)
*
* If lambda A conditionally calls lambda B, optimistically assume that property
* loads from lambda B has the same hoistability of ones from lambda A. This
* helps optimize components / hooks that create and chain many helper
* functions.
*
* Type systems and code readability encourage developers to colocate length and
* null checks values in the same function as where values are used. i.e.
* developers are unlikely to write the following code.
* ```js
* function useFoo(obj, objNotNullAndHasElements) {
* // ...
* const get0th = () => obj.arr[0].value;
* return () => objNotNullAndHasElements ? get0th : undefined;
* }
* ```
*
* In Meta code, this assumption helps reduce the number of memo dependency
* deopts.
*/
function useMakeCallback({
obj,
cond,
setState,
}: {
obj: {value: number};
cond: boolean;
setState: (newState: number) => void;
}) {
const cb = () => setState(obj.value);
// cb's property loads are assumed to be hoistable to the start of this lambda
return () => (cond ? cb() : undefined);
}
const setState = (arg: number) => {
'use no memo';
return arg;
};
export const FIXTURE_ENTRYPOINT = {
fn: createHookWrapper(useMakeCallback),
params: [{obj: {value: 1}, cond: true, setState}],
sequentialRenders: [
{obj: {value: 1}, cond: true, setState},
{obj: {value: 2}, cond: true, setState},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { createHookWrapper } from "shared-runtime";
/**
* (Given that the returned lambda is assumed to be invoked, see
* return-function)
*
* If lambda A conditionally calls lambda B, optimistically assume that property
* loads from lambda B has the same hoistability of ones from lambda A. This
* helps optimize components / hooks that create and chain many helper
* functions.
*
* Type systems and code readability encourage developers to colocate length and
* null checks values in the same function as where values are used. i.e.
* developers are unlikely to write the following code.
* ```js
* function useFoo(obj, objNotNullAndHasElements) {
* // ...
* const get0th = () => obj.arr[0].value;
* return () => objNotNullAndHasElements ? get0th : undefined;
* }
* ```
*
* In Meta code, this assumption helps reduce the number of memo dependency
* deopts.
*/
function useMakeCallback(t0) {
const $ = _c(6);
const { obj, cond, setState } = t0;
let t1;
if ($[0] !== obj.value || $[1] !== setState) {
t1 = () => setState(obj.value);
$[0] = obj.value;
$[1] = setState;
$[2] = t1;
} else {
t1 = $[2];
}
const cb = t1;
let t2;
if ($[3] !== cb || $[4] !== cond) {
t2 = () => (cond ? cb() : undefined);
$[3] = cb;
$[4] = cond;
$[5] = t2;
} else {
t2 = $[5];
}
return t2;
}
const setState = (arg: number) => {
"use no memo";
return arg;
};
export const FIXTURE_ENTRYPOINT = {
fn: createHookWrapper(useMakeCallback),
params: [{ obj: { value: 1 }, cond: true, setState }],
sequentialRenders: [
{ obj: { value: 1 }, cond: true, setState },
{ obj: { value: 2 }, cond: true, setState },
],
};
```
### Eval output
(kind: ok) <div>{"result":{"kind":"Function","result":1},"shouldInvokeFns":true}</div>
<div>{"result":{"kind":"Function","result":2},"shouldInvokeFns":true}</div>

View File

@@ -1,51 +0,0 @@
import {createHookWrapper} from 'shared-runtime';
/**
* (Given that the returned lambda is assumed to be invoked, see
* return-function)
*
* If lambda A conditionally calls lambda B, optimistically assume that property
* loads from lambda B has the same hoistability of ones from lambda A. This
* helps optimize components / hooks that create and chain many helper
* functions.
*
* Type systems and code readability encourage developers to colocate length and
* null checks values in the same function as where values are used. i.e.
* developers are unlikely to write the following code.
* ```js
* function useFoo(obj, objNotNullAndHasElements) {
* // ...
* const get0th = () => obj.arr[0].value;
* return () => objNotNullAndHasElements ? get0th : undefined;
* }
* ```
*
* In Meta code, this assumption helps reduce the number of memo dependency
* deopts.
*/
function useMakeCallback({
obj,
cond,
setState,
}: {
obj: {value: number};
cond: boolean;
setState: (newState: number) => void;
}) {
const cb = () => setState(obj.value);
// cb's property loads are assumed to be hoistable to the start of this lambda
return () => (cond ? cb() : undefined);
}
const setState = (arg: number) => {
'use no memo';
return arg;
};
export const FIXTURE_ENTRYPOINT = {
fn: createHookWrapper(useMakeCallback),
params: [{obj: {value: 1}, cond: true, setState}],
sequentialRenders: [
{obj: {value: 1}, cond: true, setState},
{obj: {value: 2}, cond: true, setState},
],
};

View File

@@ -1,80 +0,0 @@
## Input
```javascript
import {createHookWrapper, useIdentity} from 'shared-runtime';
/**
* Assume that functions passed hook arguments are invoked and that their
* property loads are hoistable.
*/
function useMakeCallback({
obj,
setState,
}: {
obj: {value: number};
setState: (newState: number) => void;
}) {
const cb = useIdentity(() => setState(obj.value));
return cb;
}
const setState = (arg: number) => {
'use no memo';
return arg;
};
export const FIXTURE_ENTRYPOINT = {
fn: createHookWrapper(useMakeCallback),
params: [{obj: {value: 1}, setState}],
sequentialRenders: [
{obj: {value: 1}, setState},
{obj: {value: 2}, setState},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { createHookWrapper, useIdentity } from "shared-runtime";
/**
* Assume that functions passed hook arguments are invoked and that their
* property loads are hoistable.
*/
function useMakeCallback(t0) {
const $ = _c(3);
const { obj, setState } = t0;
let t1;
if ($[0] !== obj.value || $[1] !== setState) {
t1 = () => setState(obj.value);
$[0] = obj.value;
$[1] = setState;
$[2] = t1;
} else {
t1 = $[2];
}
const cb = useIdentity(t1);
return cb;
}
const setState = (arg: number) => {
"use no memo";
return arg;
};
export const FIXTURE_ENTRYPOINT = {
fn: createHookWrapper(useMakeCallback),
params: [{ obj: { value: 1 }, setState }],
sequentialRenders: [
{ obj: { value: 1 }, setState },
{ obj: { value: 2 }, setState },
],
};
```
### Eval output
(kind: ok) <div>{"result":{"kind":"Function","result":1},"shouldInvokeFns":true}</div>
<div>{"result":{"kind":"Function","result":2},"shouldInvokeFns":true}</div>

View File

@@ -1,29 +0,0 @@
import {createHookWrapper, useIdentity} from 'shared-runtime';
/**
* Assume that functions passed hook arguments are invoked and that their
* property loads are hoistable.
*/
function useMakeCallback({
obj,
setState,
}: {
obj: {value: number};
setState: (newState: number) => void;
}) {
const cb = useIdentity(() => setState(obj.value));
return cb;
}
const setState = (arg: number) => {
'use no memo';
return arg;
};
export const FIXTURE_ENTRYPOINT = {
fn: createHookWrapper(useMakeCallback),
params: [{obj: {value: 1}, setState}],
sequentialRenders: [
{obj: {value: 1}, setState},
{obj: {value: 2}, setState},
],
};

View File

@@ -1,80 +0,0 @@
## Input
```javascript
import {createHookWrapper} from 'shared-runtime';
function useFoo({arr1}) {
const cb1 = e => arr1[0].value + e.value;
const x = arr1.map(cb1);
return [x, cb1];
}
export const FIXTURE_ENTRYPOINT = {
fn: createHookWrapper(useFoo),
params: [{arr1: [], arr2: []}],
sequentialRenders: [
{arr1: [], arr2: []},
{arr1: [], arr2: null},
{arr1: [{value: 1}, {value: 2}], arr2: [{value: -1}]},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { createHookWrapper } from "shared-runtime";
function useFoo(t0) {
const $ = _c(8);
const { arr1 } = t0;
let t1;
if ($[0] !== arr1[0]) {
t1 = (e) => arr1[0].value + e.value;
$[0] = arr1[0];
$[1] = t1;
} else {
t1 = $[1];
}
const cb1 = t1;
let t2;
if ($[2] !== arr1 || $[3] !== cb1) {
t2 = arr1.map(cb1);
$[2] = arr1;
$[3] = cb1;
$[4] = t2;
} else {
t2 = $[4];
}
const x = t2;
let t3;
if ($[5] !== cb1 || $[6] !== x) {
t3 = [x, cb1];
$[5] = cb1;
$[6] = x;
$[7] = t3;
} else {
t3 = $[7];
}
return t3;
}
export const FIXTURE_ENTRYPOINT = {
fn: createHookWrapper(useFoo),
params: [{ arr1: [], arr2: [] }],
sequentialRenders: [
{ arr1: [], arr2: [] },
{ arr1: [], arr2: null },
{ arr1: [{ value: 1 }, { value: 2 }], arr2: [{ value: -1 }] },
],
};
```
### Eval output
(kind: ok) <div>{"result":[[],"[[ function params=1 ]]"],"shouldInvokeFns":true}</div>
<div>{"result":[[],"[[ function params=1 ]]"],"shouldInvokeFns":true}</div>
<div>{"result":[[2,3],"[[ function params=1 ]]"],"shouldInvokeFns":true}</div>

View File

@@ -1,17 +0,0 @@
import {createHookWrapper} from 'shared-runtime';
function useFoo({arr1}) {
const cb1 = e => arr1[0].value + e.value;
const x = arr1.map(cb1);
return [x, cb1];
}
export const FIXTURE_ENTRYPOINT = {
fn: createHookWrapper(useFoo),
params: [{arr1: [], arr2: []}],
sequentialRenders: [
{arr1: [], arr2: []},
{arr1: [], arr2: null},
{arr1: [{value: 1}, {value: 2}], arr2: [{value: -1}]},
],
};

View File

@@ -1,75 +0,0 @@
## Input
```javascript
// @flow
import {Stringify} from 'shared-runtime';
/**
* Assume that functions captured directly as jsx attributes are invoked and
* that their property loads are hoistable.
*/
function useMakeCallback({
obj,
setState,
}: {
obj: {value: number};
setState: (newState: number) => void;
}) {
return <Stringify cb={() => setState(obj.value)} shouldInvokeFns={true} />;
}
const setState = (arg: number) => {
'use no memo';
return arg;
};
export const FIXTURE_ENTRYPOINT = {
fn: useMakeCallback,
params: [{obj: {value: 1}, setState}],
sequentialRenders: [
{obj: {value: 1}, setState},
{obj: {value: 2}, setState},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { Stringify } from "shared-runtime";
function useMakeCallback(t0) {
const $ = _c(3);
const { obj, setState } = t0;
let t1;
if ($[0] !== obj.value || $[1] !== setState) {
t1 = <Stringify cb={() => setState(obj.value)} shouldInvokeFns={true} />;
$[0] = obj.value;
$[1] = setState;
$[2] = t1;
} else {
t1 = $[2];
}
return t1;
}
const setState = (arg: number) => {
"use no memo";
return arg;
};
export const FIXTURE_ENTRYPOINT = {
fn: useMakeCallback,
params: [{ obj: { value: 1 }, setState }],
sequentialRenders: [
{ obj: { value: 1 }, setState },
{ obj: { value: 2 }, setState },
],
};
```
### Eval output
(kind: ok) <div>{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}</div>
<div>{"cb":{"kind":"Function","result":2},"shouldInvokeFns":true}</div>

View File

@@ -1,29 +0,0 @@
// @flow
import {Stringify} from 'shared-runtime';
/**
* Assume that functions captured directly as jsx attributes are invoked and
* that their property loads are hoistable.
*/
function useMakeCallback({
obj,
setState,
}: {
obj: {value: number};
setState: (newState: number) => void;
}) {
return <Stringify cb={() => setState(obj.value)} shouldInvokeFns={true} />;
}
const setState = (arg: number) => {
'use no memo';
return arg;
};
export const FIXTURE_ENTRYPOINT = {
fn: useMakeCallback,
params: [{obj: {value: 1}, setState}],
sequentialRenders: [
{obj: {value: 1}, setState},
{obj: {value: 2}, setState},
],
};

View File

@@ -1,78 +0,0 @@
## Input
```javascript
import {createHookWrapper} from 'shared-runtime';
/**
* Assume that directly returned functions are invoked and that their property
* loads are hoistable.
*/
function useMakeCallback({
obj,
setState,
}: {
obj: {value: number};
setState: (newState: number) => void;
}) {
return () => setState(obj.value);
}
const setState = (arg: number) => {
'use no memo';
return arg;
};
export const FIXTURE_ENTRYPOINT = {
fn: createHookWrapper(useMakeCallback),
params: [{obj: {value: 1}, setState}],
sequentialRenders: [
{obj: {value: 1}, setState},
{obj: {value: 2}, setState},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { createHookWrapper } from "shared-runtime";
/**
* Assume that directly returned functions are invoked and that their property
* loads are hoistable.
*/
function useMakeCallback(t0) {
const $ = _c(3);
const { obj, setState } = t0;
let t1;
if ($[0] !== obj.value || $[1] !== setState) {
t1 = () => setState(obj.value);
$[0] = obj.value;
$[1] = setState;
$[2] = t1;
} else {
t1 = $[2];
}
return t1;
}
const setState = (arg: number) => {
"use no memo";
return arg;
};
export const FIXTURE_ENTRYPOINT = {
fn: createHookWrapper(useMakeCallback),
params: [{ obj: { value: 1 }, setState }],
sequentialRenders: [
{ obj: { value: 1 }, setState },
{ obj: { value: 2 }, setState },
],
};
```
### Eval output
(kind: ok) <div>{"result":{"kind":"Function","result":1},"shouldInvokeFns":true}</div>
<div>{"result":{"kind":"Function","result":2},"shouldInvokeFns":true}</div>

View File

@@ -1,28 +0,0 @@
import {createHookWrapper} from 'shared-runtime';
/**
* Assume that directly returned functions are invoked and that their property
* loads are hoistable.
*/
function useMakeCallback({
obj,
setState,
}: {
obj: {value: number};
setState: (newState: number) => void;
}) {
return () => setState(obj.value);
}
const setState = (arg: number) => {
'use no memo';
return arg;
};
export const FIXTURE_ENTRYPOINT = {
fn: createHookWrapper(useMakeCallback),
params: [{obj: {value: 1}, setState}],
sequentialRenders: [
{obj: {value: 1}, setState},
{obj: {value: 2}, setState},
],
};

View File

@@ -1,82 +0,0 @@
## Input
```javascript
import {useState, useMemo} from 'react';
import {useIdentity} from 'shared-runtime';
/**
* Assume that conditionally called functions can be invoked and that their
* property loads are hoistable to the function declaration site.
*/
function useMakeCallback({
obj,
shouldSynchronizeState,
}: {
obj: {value: number};
shouldSynchronizeState: boolean;
}) {
const [state, setState] = useState(0);
const cb = useMemo(() => {
return () => {
if (obj.value !== 0) setState(obj.value);
};
}, [obj.value, shouldSynchronizeState]);
useIdentity(null);
return cb;
}
export const FIXTURE_ENTRYPOINT = {
fn: useMakeCallback,
params: [{obj: {value: 1}}],
sequentialRenders: [{obj: {value: 1}}, {obj: {value: 2}}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { useState, useMemo } from "react";
import { useIdentity } from "shared-runtime";
/**
* Assume that conditionally called functions can be invoked and that their
* property loads are hoistable to the function declaration site.
*/
function useMakeCallback(t0) {
const $ = _c(2);
const { obj, shouldSynchronizeState } = t0;
const [, setState] = useState(0);
let t1;
let t2;
if ($[0] !== obj.value) {
t2 = () => {
if (obj.value !== 0) {
setState(obj.value);
}
};
$[0] = obj.value;
$[1] = t2;
} else {
t2 = $[1];
}
t1 = t2;
const cb = t1;
useIdentity(null);
return cb;
}
export const FIXTURE_ENTRYPOINT = {
fn: useMakeCallback,
params: [{ obj: { value: 1 } }],
sequentialRenders: [{ obj: { value: 1 } }, { obj: { value: 2 } }],
};
```
### Eval output
(kind: ok) "[[ function params=0 ]]"
"[[ function params=0 ]]"

View File

@@ -1,28 +0,0 @@
import {useState, useMemo} from 'react';
import {useIdentity} from 'shared-runtime';
/**
* Assume that conditionally called functions can be invoked and that their
* property loads are hoistable to the function declaration site.
*/
function useMakeCallback({
obj,
shouldSynchronizeState,
}: {
obj: {value: number};
shouldSynchronizeState: boolean;
}) {
const [state, setState] = useState(0);
const cb = useMemo(() => {
return () => {
if (obj.value !== 0) setState(obj.value);
};
}, [obj.value, shouldSynchronizeState]);
useIdentity(null);
return cb;
}
export const FIXTURE_ENTRYPOINT = {
fn: useMakeCallback,
params: [{obj: {value: 1}}],
sequentialRenders: [{obj: {value: 1}}, {obj: {value: 2}}],
};

View File

@@ -1,68 +0,0 @@
## Input
```javascript
function useFoo({arr1, arr2}) {
const cb = e => arr2[0].value + e.value;
const y = [];
for (let i = 0; i < arr1.length; i++) {
y.push(cb(arr1[i]));
}
return y;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{arr1: [], arr2: []}],
sequentialRenders: [
{arr1: [], arr2: []},
{arr1: [], arr2: null},
{arr1: [{value: 1}, {value: 2}], arr2: [{value: -1}]},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
function useFoo(t0) {
const $ = _c(5);
const { arr1, arr2 } = t0;
let t1;
if ($[0] !== arr2[0].value) {
t1 = (e) => arr2[0].value + e.value;
$[0] = arr2[0].value;
$[1] = t1;
} else {
t1 = $[1];
}
const cb = t1;
let y;
if ($[2] !== arr1 || $[3] !== cb) {
y = [];
for (let i = 0; i < arr1.length; i++) {
y.push(cb(arr1[i]));
}
$[2] = arr1;
$[3] = cb;
$[4] = y;
} else {
y = $[4];
}
return y;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ arr1: [], arr2: [] }],
sequentialRenders: [
{ arr1: [], arr2: [] },
{ arr1: [], arr2: null },
{ arr1: [{ value: 1 }, { value: 2 }], arr2: [{ value: -1 }] },
],
};
```

View File

@@ -1,18 +0,0 @@
function useFoo({arr1, arr2}) {
const cb = e => arr2[0].value + e.value;
const y = [];
for (let i = 0; i < arr1.length; i++) {
y.push(cb(arr1[i]));
}
return y;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{arr1: [], arr2: []}],
sequentialRenders: [
{arr1: [], arr2: []},
{arr1: [], arr2: null},
{arr1: [{value: 1}, {value: 2}], arr2: [{value: -1}]},
],
};

View File

@@ -1,57 +0,0 @@
## Input
```javascript
/**
* Assume that only directly returned functions or JSX attributes are invoked.
* Conservatively estimate that functions wrapped in objects or other containers
* might never be called (and therefore their property loads are not hoistable).
*/
function useMakeCallback({arr}) {
return {
getElement0: () => arr[0].value,
getElement1: () => arr[1].value,
};
}
export const FIXTURE_ENTRYPOINT = {
fn: useMakeCallback,
params: [{arr: [1, 2]}],
sequentialRenders: [{arr: [1, 2]}, {arr: []}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; /**
* Assume that only directly returned functions or JSX attributes are invoked.
* Conservatively estimate that functions wrapped in objects or other containers
* might never be called (and therefore their property loads are not hoistable).
*/
function useMakeCallback(t0) {
const $ = _c(2);
const { arr } = t0;
let t1;
if ($[0] !== arr) {
t1 = { getElement0: () => arr[0].value, getElement1: () => arr[1].value };
$[0] = arr;
$[1] = t1;
} else {
t1 = $[1];
}
return t1;
}
export const FIXTURE_ENTRYPOINT = {
fn: useMakeCallback,
params: [{ arr: [1, 2] }],
sequentialRenders: [{ arr: [1, 2] }, { arr: [] }],
};
```
### Eval output
(kind: ok) {"getElement0":"[[ function params=0 ]]","getElement1":"[[ function params=0 ]]"}
{"getElement0":"[[ function params=0 ]]","getElement1":"[[ function params=0 ]]"}

View File

@@ -1,17 +0,0 @@
/**
* Assume that only directly returned functions or JSX attributes are invoked.
* Conservatively estimate that functions wrapped in objects or other containers
* might never be called (and therefore their property loads are not hoistable).
*/
function useMakeCallback({arr}) {
return {
getElement0: () => arr[0].value,
getElement1: () => arr[1].value,
};
}
export const FIXTURE_ENTRYPOINT = {
fn: useMakeCallback,
params: [{arr: [1, 2]}],
sequentialRenders: [{arr: [1, 2]}, {arr: []}],
};

View File

@@ -1,65 +0,0 @@
## Input
```javascript
function Test() {
const obj = {
21: 'dimaMachina',
};
// Destructuring assignment
const {21: myVar} = obj;
return (
<div>
{obj[21]}
{myVar}
</div>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: Test,
params: [{}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
function Test() {
const $ = _c(2);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = { 21: "dimaMachina" };
$[0] = t0;
} else {
t0 = $[0];
}
const obj = t0;
const { 21: myVar } = obj;
let t1;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t1 = (
<div>
{obj[21]}
{myVar}
</div>
);
$[1] = t1;
} else {
t1 = $[1];
}
return t1;
}
export const FIXTURE_ENTRYPOINT = {
fn: Test,
params: [{}],
};
```
### Eval output
(kind: ok) <div>dimaMachinadimaMachina</div>

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