diff --git a/.editorconfig b/.editorconfig index 07552cfff88ba..48d2b3d27e85a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,11 +8,9 @@ indent_size = 2 indent_style = space insert_final_newline = true max_line_length = 80 -trim_trailing_whitespace = true [*.md] max_line_length = 0 -trim_trailing_whitespace = false [COMMIT_EDITMSG] max_line_length = 0 diff --git a/.eslintrc.js b/.eslintrc.js index ed134392a9727..faeb5077f21a8 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -23,6 +23,7 @@ module.exports = { 'babel', 'ft-flow', 'jest', + 'es', 'no-for-of-loops', 'no-function-declare-after-return', 'react', @@ -47,7 +48,7 @@ module.exports = { 'ft-flow/no-unused-expressions': ERROR, // 'ft-flow/no-weak-types': WARNING, // 'ft-flow/require-valid-file-annotation': ERROR, - + 'es/no-optional-chaining': ERROR, 'no-cond-assign': OFF, 'no-constant-condition': OFF, 'no-control-regex': OFF, @@ -435,6 +436,7 @@ module.exports = { 'packages/react-dom/src/test-utils/*.js', ], rules: { + 'es/no-optional-chaining': OFF, 'react-internal/no-production-logging': OFF, 'react-internal/warning-args': OFF, 'react-internal/safe-string-coercion': [ @@ -567,6 +569,7 @@ module.exports = { React$Node: 'readonly', React$Portal: 'readonly', React$Ref: 'readonly', + React$RefSetter: 'readonly', ReadableStreamController: 'readonly', ReadableStreamReader: 'readonly', RequestInfo: 'readonly', diff --git a/.github/ISSUE_TEMPLATE/compiler_bug_report.yml b/.github/ISSUE_TEMPLATE/compiler_bug_report.yml index fc7952fd5a80f..233201d3f5b1a 100644 --- a/.github/ISSUE_TEMPLATE/compiler_bug_report.yml +++ b/.github/ISSUE_TEMPLATE/compiler_bug_report.yml @@ -55,3 +55,10 @@ body: Please provide your React version in the app where this issue occurred. validations: required: true +- type: input + attributes: + label: What version of React Compiler are you using? + description: | + Please provide the exact React Compiler version in the app where this issue occurred. + validations: + required: true diff --git a/.github/dependabot.yml b/.github/dependabot.yml index b436c26c50c29..11800ad757595 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,107 +1,10 @@ version: 2 updates: - package-ecosystem: "npm" - directory: "/fixtures/art" + directories: + - "/fixtures/*" schedule: - interval: "weekly" - open-pull-requests-limit: 0 - - package-ecosystem: "npm" - directory: "/fixtures/attribute-behavior" - schedule: - interval: "weekly" - open-pull-requests-limit: 0 - - package-ecosystem: "npm" - directory: "/fixtures/concurrent" - schedule: - interval: "weekly" - open-pull-requests-limit: 0 - - package-ecosystem: "npm" - directory: "/fixtures/devtools" - schedule: - interval: "weekly" - open-pull-requests-limit: 0 - - package-ecosystem: "npm" - directory: "/fixtures/dom" - schedule: - interval: "weekly" - open-pull-requests-limit: 0 - - package-ecosystem: "npm" - directory: "/fixtures/eslint" - schedule: - interval: "weekly" - open-pull-requests-limit: 0 - - package-ecosystem: "npm" - directory: "/fixtures/expiration" - schedule: - interval: "weekly" - open-pull-requests-limit: 0 - - package-ecosystem: "npm" - directory: "/fixtures/fiber-debugger" - schedule: - interval: "weekly" - open-pull-requests-limit: 0 - - package-ecosystem: "npm" - directory: "/fixtures/fiber-triangle" - schedule: - interval: "weekly" - open-pull-requests-limit: 0 - - package-ecosystem: "npm" - directory: "/fixtures/fizz" - schedule: - interval: "weekly" - open-pull-requests-limit: 0 - - package-ecosystem: "npm" - directory: "/fixtures/fizz-ssr-browser" - schedule: - interval: "weekly" - open-pull-requests-limit: 0 - - package-ecosystem: "npm" - directory: "/fixtures/flight" - schedule: - interval: "weekly" - open-pull-requests-limit: 0 - - package-ecosystem: "npm" - directory: "/fixtures/flight-browser" - schedule: - interval: "weekly" - open-pull-requests-limit: 0 - - package-ecosystem: "npm" - directory: "/fixtures/flight-esm" - schedule: - interval: "weekly" - open-pull-requests-limit: 0 - - package-ecosystem: "npm" - directory: "/fixtures/legacy-jsx-runtimes" - schedule: - interval: "weekly" - open-pull-requests-limit: 0 - - package-ecosystem: "npm" - directory: "/fixtures/nesting" - schedule: - interval: "weekly" - open-pull-requests-limit: 0 - - package-ecosystem: "npm" - directory: "/fixtures/packaging" - schedule: - interval: "weekly" - open-pull-requests-limit: 0 - - package-ecosystem: "npm" - directory: "/fixtures/scheduler" - schedule: - interval: "weekly" - open-pull-requests-limit: 0 - - package-ecosystem: "npm" - directory: "/fixtures/ssr" - schedule: - interval: "weekly" - open-pull-requests-limit: 0 - - package-ecosystem: "npm" - directory: "/fixtures/ssr-2" - schedule: - interval: "weekly" - open-pull-requests-limit: 0 - - package-ecosystem: "npm" - directory: "/fixtures/stacks" - schedule: - interval: "weekly" + interval: "monthly" open-pull-requests-limit: 0 + ignore: + - dependency-name: "*" diff --git a/.github/workflows/compiler_playground.yml b/.github/workflows/compiler_playground.yml index a96950ccc0b2a..81c6c3ee42a27 100644 --- a/.github/workflows/compiler_playground.yml +++ b/.github/workflows/compiler_playground.yml @@ -6,7 +6,11 @@ on: pull_request: paths: - compiler/** - - .github/workflows/compiler-playground.yml + - .github/workflows/compiler_playground.yml + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} + cancel-in-progress: true env: TZ: /usr/share/zoneinfo/America/Los_Angeles @@ -15,7 +19,7 @@ env: defaults: run: - working-directory: compiler + working-directory: compiler/apps/playground jobs: playground: @@ -27,13 +31,25 @@ jobs: with: node-version-file: '.nvmrc' cache: yarn - cache-dependency-path: compiler/yarn.lock + cache-dependency-path: compiler/**/yarn.lock - name: Restore cached node_modules uses: actions/cache@v4 id: node_modules with: path: "**/node_modules" - key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('compiler/**/yarn.lock') }} - - run: yarn install --frozen-lockfile + key: compiler-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }} + - name: yarn install compiler + run: yarn install --frozen-lockfile + working-directory: compiler + - name: yarn install playground + run: yarn install --frozen-lockfile - run: npx playwright install --with-deps chromium - - run: yarn workspace playground test + - run: CI=true yarn test + - run: ls -R test-results + if: '!cancelled()' + - name: Archive test results + if: '!cancelled()' + uses: actions/upload-artifact@v4 + with: + name: test-results + path: compiler/apps/playground/test-results diff --git a/.github/workflows/compiler_prereleases.yml b/.github/workflows/compiler_prereleases.yml index 25ad6adde8861..4f4954dd952b4 100644 --- a/.github/workflows/compiler_prereleases.yml +++ b/.github/workflows/compiler_prereleases.yml @@ -13,6 +13,9 @@ on: dist_tag: required: true type: string + version_name: + required: true + type: string secrets: NPM_TOKEN: required: true @@ -44,9 +47,9 @@ jobs: id: node_modules with: path: "**/node_modules" - key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('compiler/**/yarn.lock') }} + key: compiler-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }} - run: yarn install --frozen-lockfile - name: Publish packages to npm run: | cp ./scripts/release/ci-npmrc ~/.npmrc - scripts/release/publish.js --frfr --ci --tags ${{ inputs.dist_tag }} + scripts/release/publish.js --frfr --ci --versionName=${{ inputs.version_name }} --tag ${{ inputs.dist_tag }} diff --git a/.github/workflows/compiler_prereleases_manual.yml b/.github/workflows/compiler_prereleases_manual.yml index 87de411ddcb6e..3e42ae2cf200d 100644 --- a/.github/workflows/compiler_prereleases_manual.yml +++ b/.github/workflows/compiler_prereleases_manual.yml @@ -5,6 +5,15 @@ on: inputs: prerelease_commit_sha: required: false + release_channel: + required: true + type: string + dist_tag: + required: true + type: string + version_name: + required: true + type: string env: TZ: /usr/share/zoneinfo/America/Los_Angeles @@ -15,7 +24,8 @@ jobs: uses: facebook/react/.github/workflows/compiler_prereleases.yml@main with: commit_sha: ${{ inputs.prerelease_commit_sha || github.sha }} - release_channel: experimental - dist_tag: experimental + release_channel: ${{ inputs.release_channel }} + dist_tag: ${{ inputs.dist_tag }} + version_name: ${{ inputs.version_name }} secrets: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/compiler_prereleases_nightly.yml b/.github/workflows/compiler_prereleases_nightly.yml index 90aafac4f631f..82f893aa5ef43 100644 --- a/.github/workflows/compiler_prereleases_nightly.yml +++ b/.github/workflows/compiler_prereleases_nightly.yml @@ -16,5 +16,6 @@ jobs: commit_sha: ${{ github.sha }} release_channel: experimental dist_tag: experimental + version_name: '0.0.0' secrets: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/compiler_prereleases_weekly.yml b/.github/workflows/compiler_prereleases_weekly.yml new file mode 100644 index 0000000000000..79a9451b6972a --- /dev/null +++ b/.github/workflows/compiler_prereleases_weekly.yml @@ -0,0 +1,21 @@ +name: (Compiler) Publish Prereleases Weekly + +on: + schedule: + # At 10 minutes past 9:00 on Mon + - cron: 10 9 * * 1 + +env: + TZ: /usr/share/zoneinfo/America/Los_Angeles + +jobs: + publish_prerelease_beta: + name: Publish to beta channel + uses: facebook/react/.github/workflows/compiler_prereleases.yml@main + with: + commit_sha: ${{ github.sha }} + release_channel: beta + dist_tag: beta + version_name: '19.0.0' + secrets: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/compiler_rust.yml b/.github/workflows/compiler_rust.yml index 3cda1325ca49e..50d42a37763e3 100644 --- a/.github/workflows/compiler_rust.yml +++ b/.github/workflows/compiler_rust.yml @@ -15,6 +15,10 @@ on: - compiler/Cargo.* - compiler/*.toml +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} + cancel-in-progress: true + env: CARGO_TERM_COLOR: always RUSTFLAGS: -Dwarnings diff --git a/.github/workflows/compiler_typescript.yml b/.github/workflows/compiler_typescript.yml index 6fc00047a16f2..7ec6297038479 100644 --- a/.github/workflows/compiler_typescript.yml +++ b/.github/workflows/compiler_typescript.yml @@ -8,6 +8,10 @@ on: - compiler/** - .github/workflows/compiler_typescript.yml +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} + cancel-in-progress: true + env: TZ: /usr/share/zoneinfo/America/Los_Angeles # https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout @@ -43,7 +47,7 @@ jobs: uses: actions/cache@v4 with: path: "**/node_modules" - key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('compiler/**/yarn.lock') }} + 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 @@ -63,7 +67,7 @@ jobs: id: node_modules with: path: "**/node_modules" - key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('compiler/**/yarn.lock') }} + 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 @@ -87,6 +91,6 @@ jobs: id: node_modules with: path: "**/node_modules" - key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('compiler/**/yarn.lock') }} + key: compiler-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }} - run: yarn install --frozen-lockfile - run: yarn workspace ${{ matrix.workspace_name }} test diff --git a/.github/workflows/devtools_regression_tests.yml b/.github/workflows/devtools_regression_tests.yml index 1183d062d1abe..64d6707aa3688 100644 --- a/.github/workflows/devtools_regression_tests.yml +++ b/.github/workflows/devtools_regression_tests.yml @@ -30,7 +30,9 @@ jobs: id: node_modules with: path: "**/node_modules" - key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }} + 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 install --frozen-lockfile working-directory: scripts/release @@ -62,7 +64,9 @@ jobs: id: node_modules with: path: "**/node_modules" - key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }} + 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 - name: Restore archived build uses: actions/download-artifact@v4 @@ -103,6 +107,7 @@ jobs: - "16.8" # hooks - "17.0" - "18.0" + - "18.2" # compiler polyfill continue-on-error: true steps: - uses: actions/checkout@v4 @@ -116,7 +121,7 @@ jobs: id: node_modules with: path: "**/node_modules" - key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }} + 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 @@ -150,7 +155,7 @@ jobs: id: node_modules with: path: "**/node_modules" - key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }} + 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 diff --git a/.github/workflows/runtime_build_and_test.yml b/.github/workflows/runtime_build_and_test.yml index 1f780d4ee37c4..b9cbe4d447bbb 100644 --- a/.github/workflows/runtime_build_and_test.yml +++ b/.github/workflows/runtime_build_and_test.yml @@ -7,6 +7,10 @@ on: paths-ignore: - compiler/** +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} + cancel-in-progress: true + env: TZ: /usr/share/zoneinfo/America/Los_Angeles # https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout @@ -48,7 +52,9 @@ jobs: id: node_modules with: path: "**/node_modules" - key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }} + 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 - run: node ./scripts/tasks/flow-ci ${{ matrix.flow_inline_config_shortname }} @@ -68,7 +74,9 @@ jobs: id: node_modules with: path: "**/node_modules" - key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }} + 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 - run: | yarn generate-inline-fizz-runtime @@ -90,7 +98,9 @@ jobs: id: node_modules with: path: "**/node_modules" - key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }} + 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 - run: yarn flags @@ -139,7 +149,9 @@ jobs: id: node_modules with: path: "**/node_modules" - key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }} + 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 - run: yarn test ${{ matrix.params }} --ci --shard=${{ matrix.shard }} @@ -168,7 +180,9 @@ jobs: id: node_modules with: path: "**/node_modules" - key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }} + 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 - run: yarn build --index=${{ matrix.worker_id }} --total=20 --r=${{ matrix.release_channel }} --ci env: @@ -238,7 +252,9 @@ jobs: id: node_modules with: path: "**/node_modules" - key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }} + 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 - name: Restore archived build uses: actions/download-artifact@v4 @@ -266,7 +282,9 @@ jobs: id: node_modules with: path: "**/node_modules" - key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }} + 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 - name: Restore archived build uses: actions/download-artifact@v4 @@ -309,7 +327,9 @@ jobs: id: node_modules with: path: "**/node_modules" - key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }} + 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 - name: Restore archived build uses: actions/download-artifact@v4 @@ -340,7 +360,9 @@ jobs: id: node_modules with: path: "**/node_modules" - key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }} + 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 - name: Restore archived build uses: actions/download-artifact@v4 @@ -368,7 +390,9 @@ jobs: id: node_modules with: path: "**/node_modules" - key: v2-yarn_cache_fixtures_dom-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }} + key: fixtures_dom-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 install --frozen-lockfile --cache-folder ~/.cache/yarn working-directory: fixtures/dom @@ -408,7 +432,9 @@ jobs: id: node_modules with: path: "**/node_modules" - key: v2-yarn_cache_fixtures_flight-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }} + 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 - name: Restore archived build uses: actions/download-artifact@v4 @@ -464,7 +490,9 @@ jobs: id: node_modules with: path: "**/node_modules" - key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }} + 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 - name: Restore archived build uses: actions/download-artifact@v4 @@ -510,7 +538,9 @@ jobs: id: node_modules with: path: "**/node_modules" - key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }} + 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 - name: Restore archived build uses: actions/download-artifact@v4 @@ -543,6 +573,8 @@ jobs: with: path: "**/node_modules" key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }} + - name: Ensure clean build directory + run: rm -rf build - run: yarn install --frozen-lockfile - run: yarn install --frozen-lockfile working-directory: scripts/release @@ -581,7 +613,9 @@ jobs: id: node_modules with: path: "**/node_modules" - key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }} + 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 - name: Restore archived build for PR uses: actions/download-artifact@v4 diff --git a/.github/workflows/runtime_commit_artifacts.yml b/.github/workflows/runtime_commit_artifacts.yml index 9f786b9b5eccc..4315256affe45 100644 --- a/.github/workflows/runtime_commit_artifacts.yml +++ b/.github/workflows/runtime_commit_artifacts.yml @@ -11,6 +11,11 @@ on: commit_sha: required: false type: string + force: + description: 'Force a commit to the builds/... branches' + required: true + default: false + type: boolean env: TZ: /usr/share/zoneinfo/America/Los_Angeles @@ -69,7 +74,9 @@ jobs: id: node_modules with: path: "**/node_modules" - key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }} + 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 name: yarn install (react) - run: yarn install --frozen-lockfile @@ -77,7 +84,7 @@ jobs: 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 }} + GH_TOKEN=${{ github.token }} scripts/release/download-experimental-build.js --commit=${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }} - name: Display structure of build run: ls -R build - name: Strip @license from eslint plugin and react-refresh @@ -85,6 +92,11 @@ jobs: sed -i -e 's/ @license React*//' \ build/oss-experimental/eslint-plugin-react-hooks/cjs/eslint-plugin-react-hooks.development.js \ build/oss-experimental/react-refresh/cjs/react-refresh-babel.development.js + - name: Insert @headers into eslint plugin and react-refresh + run: | + sed -i -e 's/ LICENSE file in the root directory of this source tree./ LICENSE file in the root directory of this source tree.\n *\n * @noformat\n * @nolint\n * @lightSyntaxTransform\n * @preventMunge\n * @oncall react_core/' \ + build/oss-experimental/eslint-plugin-react-hooks/cjs/eslint-plugin-react-hooks.development.js \ + build/oss-experimental/react-refresh/cjs/react-refresh-babel.development.js - name: Move relevant files for React in www into compiled run: | # Move the facebook-www folder into compiled @@ -113,13 +125,14 @@ jobs: run: | BASE_FOLDER='compiled-rn/facebook-fbsource/xplat/js' mkdir -p ${BASE_FOLDER}/react-native-github/Libraries/Renderer/ - mkdir -p ${BASE_FOLDER}/RKJSModules/vendor/react/{scheduler,react,react-is,react-test-renderer}/ + mkdir -p ${BASE_FOLDER}/RKJSModules/vendor/react/{scheduler,react,react-dom,react-is,react-test-renderer}/ # Move React Native renderer mv build/react-native/implementations/ $BASE_FOLDER/react-native-github/Libraries/Renderer/ mv build/react-native/shims/ $BASE_FOLDER/react-native-github/Libraries/Renderer/ mv build/facebook-react-native/scheduler/cjs/ $BASE_FOLDER/RKJSModules/vendor/react/scheduler/ mv build/facebook-react-native/react/cjs/ $BASE_FOLDER/RKJSModules/vendor/react/react/ + mv build/facebook-react-native/react-dom/cjs/ $BASE_FOLDER/RKJSModules/vendor/react/react-dom/ mv build/facebook-react-native/react-is/cjs/ $BASE_FOLDER/RKJSModules/vendor/react/react-is/ mv build/facebook-react-native/react-test-renderer/cjs/ $BASE_FOLDER/RKJSModules/vendor/react/react-test-renderer/ @@ -134,9 +147,9 @@ jobs: ls -R ./compiled-rn - name: Add REVISION files run: | - echo ${{ github.event.workflow_run.head_sha }} >> ./compiled/facebook-www/REVISION + echo ${{ github.sha }} >> ./compiled/facebook-www/REVISION cp ./compiled/facebook-www/REVISION ./compiled/facebook-www/REVISION_TRANSFORMS - echo ${{ github.event.workflow_run.head_sha}} >> ./compiled-rn/facebook-fbsource/xplat/js/react-native-github/Libraries/Renderer/REVISION + echo ${{ github.sha}} >> ./compiled-rn/facebook-fbsource/xplat/js/react-native-github/Libraries/Renderer/REVISION - name: "Get current version string" id: get_current_version run: | @@ -160,7 +173,7 @@ jobs: commit_www_artifacts: needs: download_artifacts - if: ${{ (github.ref == 'refs/heads/main' && needs.download_artifacts.outputs.www_branch_count == '0') || github.ref == 'refs/heads/meta-www' }} + if: inputs.force == true || (github.ref == 'refs/heads/main' && needs.download_artifacts.outputs.www_branch_count == '0') runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -190,6 +203,7 @@ jobs: grep -rl "$CURRENT_VERSION_MODERN" ./compiled | xargs -r sed -i -e "s/$CURRENT_VERSION_MODERN/$LAST_VERSION_MODERN/g" grep -rl "$CURRENT_VERSION_MODERN" ./compiled || echo "Modern version reverted" - name: Check for changes + if: inputs.force != true id: check_should_commit run: | echo "Full git status" @@ -207,7 +221,7 @@ jobs: echo "should_commit=false" >> "$GITHUB_OUTPUT" fi - name: Re-apply version changes - if: steps.check_should_commit.outputs.should_commit == 'true' && needs.download_artifacts.outputs.last_version_classic != '' && needs.download_artifacts.outputs.last_version_modern != '' + if: inputs.force == true || (steps.check_should_commit.outputs.should_commit == 'true' && needs.download_artifacts.outputs.last_version_classic != '' && needs.download_artifacts.outputs.last_version_modern != '') env: CURRENT_VERSION_CLASSIC: ${{ needs.download_artifacts.outputs.current_version_classic }} CURRENT_VERSION_MODERN: ${{ needs.download_artifacts.outputs.current_version_modern }} @@ -224,26 +238,26 @@ jobs: grep -rl "$LAST_VERSION_MODERN" ./compiled | xargs -r sed -i -e "s/$LAST_VERSION_MODERN/$CURRENT_VERSION_MODERN/g" grep -rl "$LAST_VERSION_MODERN" ./compiled || echo "Classic version re-applied" - name: Will commit these changes - if: steps.check_should_commit.outputs.should_commit == 'true' + if: inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true' run: | echo ":" git status -u - name: Commit changes to branch - if: steps.check_should_commit.outputs.should_commit == 'true' - uses: stefanzweifel/git-auto-commit-action@v4 + if: inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true' + uses: stefanzweifel/git-auto-commit-action@v5 with: commit_message: | - ${{ github.event.workflow_run.head_commit.message }} + ${{ github.event.workflow_run.head_commit.message || format('Manual build of {0}', github.event.workflow_run.head_sha || github.sha) }} - DiffTrain build for [${{ github.event.workflow_run.head_sha }}](https://github.com/facebook/react/commit/${{ github.event.workflow_run.head_sha }}) + 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.event.workflow_run.triggering_actor.login }} - commit_user_email: ${{ github.event.workflow_run.triggering_actor.email || format('{0}@users.noreply.github.com', github.event.workflow_run.triggering_actor.login) }} + 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 - if: ${{ (github.ref == 'refs/heads/main' && needs.download_artifacts.outputs.fbsource_branch_count == '0') || github.ref == 'refs/heads/meta-fbsource' }} + if: inputs.force == true || (github.ref == 'refs/heads/main' && needs.download_artifacts.outputs.fbsource_branch_count == '0') runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -266,6 +280,7 @@ jobs: grep -rl "$CURRENT_VERSION" ./compiled-rn | xargs -r sed -i -e "s/$CURRENT_VERSION/$LAST_VERSION/g" grep -rl "$CURRENT_VERSION" ./compiled-rn || echo "Version reverted" - name: Check for changes + if: inputs.force != 'true' id: check_should_commit run: | echo "Full git status" @@ -284,7 +299,7 @@ jobs: echo "should_commit=false" >> "$GITHUB_OUTPUT" fi - name: Re-apply version changes - if: steps.check_should_commit.outputs.should_commit == 'true' && needs.download_artifacts.outputs.last_version_rn != '' + if: inputs.force == true || (steps.check_should_commit.outputs.should_commit == 'true' && needs.download_artifacts.outputs.last_version_rn != '') env: CURRENT_VERSION: ${{ needs.download_artifacts.outputs.current_version_rn }} LAST_VERSION: ${{ needs.download_artifacts.outputs.last_version_rn }} @@ -294,12 +309,12 @@ jobs: grep -rl "$LAST_VERSION" ./compiled-rn | xargs -r sed -i -e "s/$LAST_VERSION/$CURRENT_VERSION/g" grep -rl "$LAST_VERSION" ./compiled-rn || echo "Version re-applied" - name: Add files for signing - if: steps.check_should_commit.outputs.should_commit == 'true' + if: inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true' run: | echo ":" git add . - name: Signing files - if: steps.check_should_commit.outputs.should_commit == 'true' + if: inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true' uses: actions/github-script@v7 with: script: | @@ -353,8 +368,9 @@ jobs: console.log('Signing files in directory:', directory); try { const result = execSync(`git status --porcelain ${directory}`, {encoding: 'utf8'}); + console.log(result); - // Parse the git status output to get file paths + // Parse the git status output to get file paths! const files = result.split('\n').filter(file => file.endsWith('.js')); if (files.length === 0) { @@ -363,7 +379,14 @@ jobs: ); } else { files.forEach(line => { - const file = line.slice(3).trim(); + let file = null; + if (line.startsWith('D ')) { + return; + } else if (line.startsWith('R ')) { + file = line.slice(line.indexOf('->') + 3); + } else { + file = line.slice(3).trim(); + } if (file) { console.log(' Signing file:', file); const originalContents = fs.readFileSync(file, 'utf8'); @@ -382,19 +405,19 @@ jobs: console.error('Error signing files:', e); } - name: Will commit these changes - if: steps.check_should_commit.outputs.should_commit == 'true' + if: inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true' run: | git add . git status - name: Commit changes to branch - if: steps.check_should_commit.outputs.should_commit == 'true' - uses: stefanzweifel/git-auto-commit-action@v4 + if: inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true' + uses: stefanzweifel/git-auto-commit-action@v5 with: commit_message: | - ${{ github.event.workflow_run.head_commit.message }} + ${{ github.event.workflow_run.head_commit.message || format('Manual build of {0}', github.event.workflow_run.head_sha || github.sha) }} - DiffTrain build for commit https://github.com/facebook/react/commit/${{ github.event.workflow_run.head_sha }}. + 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.event.workflow_run.triggering_actor.login }} - commit_user_email: ${{ github.event.workflow_run.triggering_actor.email || format('{0}@users.noreply.github.com', github.event.workflow_run.triggering_actor.login) }} + commit_user_name: ${{ github.triggering_actor }} + commit_user_email: ${{ format('{0}@users.noreply.github.com', github.triggering_actor) }} create_branch: true diff --git a/.github/workflows/runtime_prereleases.yml b/.github/workflows/runtime_prereleases.yml index 0f3f79432b5c2..e52bed6bb16bf 100644 --- a/.github/workflows/runtime_prereleases.yml +++ b/.github/workflows/runtime_prereleases.yml @@ -40,7 +40,9 @@ jobs: id: node_modules with: path: "**/node_modules" - key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }} + 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 install --frozen-lockfile working-directory: scripts/release diff --git a/.github/workflows/runtime_prereleases_manual.yml b/.github/workflows/runtime_prereleases_manual.yml index 4d031e7b4d69d..4c25ddc79bd35 100644 --- a/.github/workflows/runtime_prereleases_manual.yml +++ b/.github/workflows/runtime_prereleases_manual.yml @@ -27,7 +27,7 @@ jobs: # because this used to be called the "next" channel and some # downstream consumers might still expect that tag. We can remove this # after some time has elapsed and the change has been communicated. - dist_tag: canary,next,rc + dist_tag: canary,next secrets: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/runtime_prereleases_nightly.yml b/.github/workflows/runtime_prereleases_nightly.yml index a4c2c2d0225b5..fe038042f332c 100644 --- a/.github/workflows/runtime_prereleases_nightly.yml +++ b/.github/workflows/runtime_prereleases_nightly.yml @@ -15,7 +15,7 @@ jobs: with: commit_sha: ${{ github.sha }} release_channel: stable - dist_tag: canary,next,rc + dist_tag: canary,next secrets: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/shared_lint.yml b/.github/workflows/shared_lint.yml index 31834c3fb9688..36c1df9eb86f9 100644 --- a/.github/workflows/shared_lint.yml +++ b/.github/workflows/shared_lint.yml @@ -5,6 +5,10 @@ on: branches: [main] pull_request: +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} + cancel-in-progress: true + env: TZ: /usr/share/zoneinfo/America/Los_Angeles # https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout @@ -25,7 +29,9 @@ jobs: uses: actions/cache@v4 with: path: "**/node_modules" - key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} + 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 - run: yarn prettier-check @@ -43,7 +49,9 @@ jobs: uses: actions/cache@v4 with: path: "**/node_modules" - key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} + 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 - run: node ./scripts/tasks/eslint @@ -61,7 +69,9 @@ jobs: uses: actions/cache@v4 with: path: "**/node_modules" - key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} + 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 - run: ./scripts/ci/check_license.sh @@ -79,6 +89,8 @@ jobs: uses: actions/cache@v4 with: path: "**/node_modules" - key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} + 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 - run: ./scripts/ci/test_print_warnings.sh diff --git a/.prettierignore b/.prettierignore index ecb518e4f0f6e..7e09af76a3af8 100644 --- a/.prettierignore +++ b/.prettierignore @@ -24,6 +24,7 @@ compiler/**/.next compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-kitchensink.js compiler/crates +compiler/target compiler/apps/playground/public compiler/**/LICENSE diff --git a/ReactVersions.js b/ReactVersions.js index 736d6c8f54717..ead100384e8bc 100644 --- a/ReactVersions.js +++ b/ReactVersions.js @@ -30,7 +30,7 @@ const canaryChannelLabel = 'rc'; // If the canaryChannelLabel is "rc", the build pipeline will use this to build // an RC version of the packages. -const rcNumber = 0; +const rcNumber = 1; const stablePackages = { 'eslint-plugin-react-hooks': '5.1.0', @@ -52,7 +52,7 @@ const stablePackages = { // These packages do not exist in the @canary or @latest channel, only // @experimental. We don't use semver, just the commit sha, so this is just a // list of package names instead of a map. -const experimentalPackages = []; +const experimentalPackages = ['react-markup']; module.exports = { ReactVersion, diff --git a/compiler/apps/playground/README.md b/compiler/apps/playground/README.md index 0a2fef224a0b0..7b6d5f729ad96 100644 --- a/compiler/apps/playground/README.md +++ b/compiler/apps/playground/README.md @@ -26,6 +26,13 @@ $ npm run dev $ yarn ``` +## Testing +```sh +# Install playwright browser binaries +$ npx playwright install --with-deps +# Run tests +$ yarn test +``` ## Deployment This project has been deployed using Vercel. Vercel does the exact same thing as we would diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/01-user-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/01-user-output.txt new file mode 100644 index 0000000000000..ba680bbb57232 --- /dev/null +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/01-user-output.txt @@ -0,0 +1,13 @@ +function TestComponent(t0) { + const $ = _c(2); + const { x } = t0; + let t1; + if ($[0] !== x) { + t1 = ; + $[0] = x; + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; +} diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/02-default-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/02-default-output.txt new file mode 100644 index 0000000000000..2cbd09bba6179 --- /dev/null +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/02-default-output.txt @@ -0,0 +1,11 @@ +function MyApp() { + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 =
Hello World
; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/default-input.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/default-input.txt deleted file mode 100644 index a26ec5baaf5ab..0000000000000 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/default-input.txt +++ /dev/null @@ -1,11 +0,0 @@ -function MyApp() { -  const $ = _c(1); -  let t0; -  if ($[0] === Symbol.for("react.memo_cache_sentinel")) { -    t0 = 
Hello World
; -    $[0] = t0; -  } else { -    t0 = $[0]; -  } -  return t0; -} \ No newline at end of file diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/function-scope-beats-module-scope-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/function-scope-beats-module-scope-output.txt new file mode 100644 index 0000000000000..5bcfcb75edd18 --- /dev/null +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/function-scope-beats-module-scope-output.txt @@ -0,0 +1,14 @@ +function TestComponent(t0) { + "use memo"; + const $ = _c(2); + const { x } = t0; + let t1; + if ($[0] !== x) { + t1 = ; + $[0] = x; + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; +} diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-memo-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-memo-output.txt new file mode 100644 index 0000000000000..ba680bbb57232 --- /dev/null +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-memo-output.txt @@ -0,0 +1,13 @@ +function TestComponent(t0) { + const $ = _c(2); + const { x } = t0; + let t1; + if ($[0] !== x) { + t1 = ; + $[0] = x; + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; +} diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-no-memo-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-no-memo-output.txt new file mode 100644 index 0000000000000..2c69ddc1d65b8 --- /dev/null +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-no-memo-output.txt @@ -0,0 +1,3 @@ +function TestComponent({ x }) { + return ; +} diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-memo-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-memo-output.txt new file mode 100644 index 0000000000000..804bacab97e05 --- /dev/null +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-memo-output.txt @@ -0,0 +1,28 @@ +function TestComponent(t0) { + "use memo"; + const $ = _c(2); + const { x } = t0; + let t1; + if ($[0] !== x) { + t1 = ; + $[0] = x; + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; +} +function anonymous_1(t0) { + "use memo"; + const $ = _c(2); + const { x } = t0; + let t1; + if ($[0] !== x) { + t1 = ; + $[0] = x; + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; +} diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-no-memo-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-no-memo-output.txt new file mode 100644 index 0000000000000..5fb66309fc70c --- /dev/null +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/use-no-memo-output.txt @@ -0,0 +1,8 @@ +function anonymous_1() { + "use no memo"; + return ; +} +function anonymous_3({ x }) { + "use no memo"; + return ; +} diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/user-input.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/user-input.txt deleted file mode 100644 index f45eb2c625c40..0000000000000 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/user-input.txt +++ /dev/null @@ -1,13 +0,0 @@ -function TestComponent(t0) { -  const $ = _c(2); -  const { x } = t0; -  let t1; -  if ($[0] !== x) { -    t1 = ; -    $[0] = x; -    $[1] = t1; -  } else { -    t1 = $[1]; -  } -  return t1; -} \ No newline at end of file diff --git a/compiler/apps/playground/__tests__/e2e/page.spec.ts b/compiler/apps/playground/__tests__/e2e/page.spec.ts index bc93352a0992a..846e6227bd1a1 100644 --- a/compiler/apps/playground/__tests__/e2e/page.spec.ts +++ b/compiler/apps/playground/__tests__/e2e/page.spec.ts @@ -7,43 +7,137 @@ import {expect, test} from '@playwright/test'; import {encodeStore, type Store} from '../../lib/stores'; +import {format} from 'prettier'; -const STORE: Store = { - source: `export default function TestComponent({ x }) { - return ; +function print(data: Array): Promise { + return format(data.join(''), {parser: 'babel'}); } -`, -}; -const HASH = encodeStore(STORE); -function concat(data: Array): string { - return data.join(''); +const DIRECTIVE_TEST_CASES = [ + { + name: 'module-scope-use-memo', + input: ` +'use memo'; +export default function TestComponent({ x }) { + return ; +}`, + }, + { + name: 'module-scope-use-no-memo', + input: ` +'use no memo'; +export default function TestComponent({ x }) { + return ; +}`, + }, + { + name: 'use-memo', + input: ` +function TestComponent({ x }) { + 'use memo'; + return ; } +const TestComponent2 = ({ x }) => { + 'use memo'; + return ; +};`, + }, + { + name: 'use-no-memo', + input: ` +const TestComponent = function() { + 'use no memo'; + return ; +}; +const TestComponent2 = ({ x }) => { + 'use no memo'; + return ; +};`, + }, + { + name: 'function-scope-beats-module-scope', + input: ` +'use no memo'; +function TestComponent({ x }) { + 'use memo'; + return ; +}`, + }, +]; -test('editor should compile successfully', async ({page}) => { - await page.goto(`/#${HASH}`, {waitUntil: 'networkidle'}); +test('editor should open successfully', async ({page}) => { + await page.goto(`/`, {waitUntil: 'networkidle'}); await page.screenshot({ fullPage: true, - path: 'test-results/00-on-networkidle.png', + path: 'test-results/00-fresh-page.png', }); +}); + +test('editor should compile from hash successfully', async ({page}) => { + const store: Store = { + source: `export default function TestComponent({ x }) { + return ; + } + `, + }; + const hash = encodeStore(store); + await page.goto(`/#${hash}`, {waitUntil: 'networkidle'}); // User input from hash compiles await page.screenshot({ fullPage: true, - path: 'test-results/01-show-js-before.png', + path: 'test-results/01-compiles-from-hash.png', }); - const userInput = - (await page.locator('.monaco-editor').nth(2).allInnerTexts()) ?? []; - expect(concat(userInput)).toMatchSnapshot('user-input.txt'); + const text = + (await page.locator('.monaco-editor').nth(1).allInnerTexts()) ?? []; + const output = await print(text); + + expect(output).not.toEqual(''); + expect(output).toMatchSnapshot('01-user-output.txt'); +}); + +test('reset button works', async ({page}) => { + const store: Store = { + source: `export default function TestComponent({ x }) { + return ; + } + `, + }; + const hash = encodeStore(store); + await page.goto(`/#${hash}`, {waitUntil: 'networkidle'}); // Reset button works page.on('dialog', dialog => dialog.accept()); await page.getByRole('button', {name: 'Reset'}).click(); await page.screenshot({ fullPage: true, - path: 'test-results/02-show-js-after.png', + path: 'test-results/02-reset-button-works.png', }); - const defaultInput = - (await page.locator('.monaco-editor').nth(2).allInnerTexts()) ?? []; - expect(concat(defaultInput)).toMatchSnapshot('default-input.txt'); + const text = + (await page.locator('.monaco-editor').nth(1).allInnerTexts()) ?? []; + const output = await print(text); + + expect(output).not.toEqual(''); + expect(output).toMatchSnapshot('02-default-output.txt'); }); + +DIRECTIVE_TEST_CASES.forEach((t, idx) => + test(`directives work: ${t.name}`, async ({page}) => { + const store: Store = { + source: t.input, + }; + const hash = encodeStore(store); + await page.goto(`/#${hash}`, {waitUntil: 'networkidle'}); + await page.screenshot({ + fullPage: true, + path: `test-results/03-0${idx}-${t.name}.png`, + }); + + const text = + (await page.locator('.monaco-editor').nth(1).allInnerTexts()) ?? []; + const output = await print(text); + + expect(output).not.toEqual(''); + expect(output).toMatchSnapshot(`${t.name}-output.txt`); + }), +); diff --git a/compiler/apps/playground/app/layout.tsx b/compiler/apps/playground/app/layout.tsx index 3e888ae955a89..9a6daac239faf 100644 --- a/compiler/apps/playground/app/layout.tsx +++ b/compiler/apps/playground/app/layout.tsx @@ -7,7 +7,11 @@ import '../styles/globals.css'; -export default function RootLayout({children}: {children: React.ReactNode}) { +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}): JSX.Element { 'use no memo'; return ( diff --git a/compiler/apps/playground/app/page.tsx b/compiler/apps/playground/app/page.tsx index 4b0da58a7028b..d5214af422e03 100644 --- a/compiler/apps/playground/app/page.tsx +++ b/compiler/apps/playground/app/page.tsx @@ -11,7 +11,7 @@ import {SnackbarProvider} from 'notistack'; import {Editor, Header, StoreProvider} from '../components'; import MessageSnackbar from '../components/Message'; -export default function Hoot() { +export default function Page(): JSX.Element { return ( + | NodePath + | NodePath; +enum MemoizeDirectiveState { + Enabled = 'Enabled', + Disabled = 'Disabled', + Undefined = 'Undefined', +} + +const MEMOIZE_ENABLED_OR_UNDEFINED_STATES = new Set([ + MemoizeDirectiveState.Enabled, + MemoizeDirectiveState.Undefined, +]); + +const MEMOIZE_ENABLED_OR_DISABLED_STATES = new Set([ + MemoizeDirectiveState.Enabled, + MemoizeDirectiveState.Disabled, +]); +function parseInput(input: string, language: 'flow' | 'typescript'): any { // Extract the first line to quickly check for custom test directives if (language === 'flow') { return HermesParser.parse(input, { @@ -65,29 +84,36 @@ function parseInput(input: string, language: 'flow' | 'typescript') { function parseFunctions( source: string, language: 'flow' | 'typescript', -): Array< - NodePath< - t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression - > -> { - const items: Array< - NodePath< - t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression - > - > = []; +): Array<{ + compilationEnabled: boolean; + fn: FunctionLike; +}> { + const items: Array<{ + compilationEnabled: boolean; + fn: FunctionLike; + }> = []; try { const ast = parseInput(source, language); traverse(ast, { FunctionDeclaration(nodePath) { - items.push(nodePath); + items.push({ + compilationEnabled: shouldCompile(nodePath), + fn: nodePath, + }); nodePath.skip(); }, ArrowFunctionExpression(nodePath) { - items.push(nodePath); + items.push({ + compilationEnabled: shouldCompile(nodePath), + fn: nodePath, + }); nodePath.skip(); }, FunctionExpression(nodePath) { - items.push(nodePath); + items.push({ + compilationEnabled: shouldCompile(nodePath), + fn: nodePath, + }); nodePath.skip(); }, }); @@ -100,9 +126,48 @@ function parseFunctions( suggestions: null, }); } + return items; } +function shouldCompile(fn: FunctionLike): boolean { + const {body} = fn.node; + if (t.isBlockStatement(body)) { + const selfCheck = checkExplicitMemoizeDirectives(body.directives); + if (selfCheck === MemoizeDirectiveState.Enabled) return true; + if (selfCheck === MemoizeDirectiveState.Disabled) return false; + + const parentWithDirective = fn.findParent(parentPath => { + if (parentPath.isBlockStatement() || parentPath.isProgram()) { + const directiveCheck = checkExplicitMemoizeDirectives( + parentPath.node.directives, + ); + return MEMOIZE_ENABLED_OR_DISABLED_STATES.has(directiveCheck); + } + return false; + }); + + if (!parentWithDirective) return true; + const parentDirectiveCheck = checkExplicitMemoizeDirectives( + (parentWithDirective.node as t.Program | t.BlockStatement).directives, + ); + return MEMOIZE_ENABLED_OR_UNDEFINED_STATES.has(parentDirectiveCheck); + } + return false; +} + +function checkExplicitMemoizeDirectives( + directives: Array, +): MemoizeDirectiveState { + if (findDirectiveEnablingMemoization(directives).length) { + return MemoizeDirectiveState.Enabled; + } + if (findDirectiveDisablingMemoization(directives).length) { + return MemoizeDirectiveState.Disabled; + } + return MemoizeDirectiveState.Undefined; +} + const COMMON_HOOKS: Array<[string, Hook]> = [ [ 'useFragment', @@ -155,26 +220,37 @@ function isHookName(s: string): boolean { return /^use[A-Z0-9]/.test(s); } -function getReactFunctionType( - id: NodePath, -): ReactFunctionType { - if (id && id.node && id.isIdentifier()) { - if (isHookName(id.node.name)) { +function getReactFunctionType(id: t.Identifier | null): ReactFunctionType { + if (id != null) { + if (isHookName(id.name)) { return 'Hook'; } const isPascalCaseNameSpace = /^[A-Z].*/; - if (isPascalCaseNameSpace.test(id.node.name)) { + if (isPascalCaseNameSpace.test(id.name)) { return 'Component'; } } return 'Other'; } +function getFunctionIdentifier( + fn: + | NodePath + | NodePath + | NodePath, +): t.Identifier | null { + if (fn.isArrowFunctionExpression()) { + return null; + } + const id = fn.get('id'); + return Array.isArray(id) === false && id.isIdentifier() ? id.node : null; +} + function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { - const results = new Map(); + const results = new Map>(); const error = new CompilerError(); - const upsert = (result: PrintedCompilerPipelineValue) => { + const upsert: (result: PrintedCompilerPipelineValue) => void = result => { const entry = results.get(result.name); if (Array.isArray(entry)) { entry.push(result); @@ -188,40 +264,50 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { } else { language = 'typescript'; } + let count = 0; + const withIdentifier = (id: t.Identifier | null): t.Identifier => { + if (id != null && id.name != null) { + return id; + } else { + return t.identifier(`anonymous_${count++}`); + } + }; try { // Extract the first line to quickly check for custom test directives const pragma = source.substring(0, source.indexOf('\n')); - const config = parseConfigPragma(pragma); - - for (const fn of parseFunctions(source, language)) { - if (!fn.isFunctionDeclaration()) { - error.pushErrorDetail( - new CompilerErrorDetail({ - reason: `Unexpected function type ${fn.node.type}`, - description: - 'Playground only supports parsing function declarations', - severity: ErrorSeverity.Todo, - loc: fn.node.loc ?? null, - suggestions: null, - }), - ); + const config = parseConfigPragmaForTests(pragma); + const parsedFunctions = parseFunctions(source, language); + for (const func of parsedFunctions) { + const id = withIdentifier(getFunctionIdentifier(func.fn)); + const fnName = id.name; + if (!func.compilationEnabled) { + upsert({ + kind: 'ast', + fnName, + name: 'CodeGen', + value: { + type: 'FunctionDeclaration', + id: + func.fn.isArrowFunctionExpression() || + func.fn.isFunctionExpression() + ? withIdentifier(null) + : func.fn.node.id, + async: func.fn.node.async, + generator: !!func.fn.node.generator, + body: func.fn.node.body as t.BlockStatement, + params: func.fn.node.params, + }, + }); continue; } - - const id = fn.get('id'); - for (const result of run( - fn, + for (const result of runPlayground( + func.fn, { ...config, customHooks: new Map([...COMMON_HOOKS]), }, getReactFunctionType(id), - '_c', - null, - null, - null, )) { - const fnName = fn.node.id?.name ?? null; switch (result.kind) { case 'ast': { upsert({ @@ -230,7 +316,7 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { name: result.name, value: { type: 'FunctionDeclaration', - id: result.value.id, + id: withIdentifier(result.value.id), async: result.value.async, generator: result.value.generator, body: result.value.body, @@ -274,13 +360,17 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { } } } catch (err) { - // error might be an invariant violation or other runtime error - // (i.e. object shape that is not CompilerError) + /** + * error might be an invariant violation or other runtime error + * (i.e. object shape that is not CompilerError) + */ if (err instanceof CompilerError && err.details.length > 0) { error.details.push(...err.details); } else { - // Handle unexpected failures by logging (to get a stack trace) - // and reporting + /** + * Handle unexpected failures by logging (to get a stack trace) + * and reporting + */ console.error(err); error.details.push( new CompilerErrorDetail({ @@ -298,7 +388,7 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { return [{kind: 'ok', results}, language]; } -export default function Editor() { +export default function Editor(): JSX.Element { const store = useStore(); const deferredStore = useDeferredValue(store); const dispatchStore = useStoreDispatch(); diff --git a/compiler/apps/playground/components/Editor/Input.tsx b/compiler/apps/playground/components/Editor/Input.tsx index c2ce8efc70d3f..34df68787db79 100644 --- a/compiler/apps/playground/components/Editor/Input.tsx +++ b/compiler/apps/playground/components/Editor/Input.tsx @@ -15,18 +15,17 @@ import {useEffect, useState} from 'react'; import {renderReactCompilerMarkers} from '../../lib/reactCompilerMonacoDiagnostics'; import {useStore, useStoreDispatch} from '../StoreContext'; import {monacoOptions} from './monacoOptions'; -// TODO: Make TS recognize .d.ts files, in addition to loading them with webpack. -// @ts-ignore +// @ts-expect-error TODO: Make TS recognize .d.ts files, in addition to loading them with webpack. import React$Types from '../../node_modules/@types/react/index.d.ts'; loader.config({monaco}); type Props = { - errors: CompilerErrorDetail[]; + errors: Array; language: 'flow' | 'typescript'; }; -export default function Input({errors, language}: Props) { +export default function Input({errors, language}: Props): JSX.Element { const [monaco, setMonaco] = useState(null); const store = useStore(); const dispatchStore = useStoreDispatch(); @@ -38,18 +37,19 @@ export default function Input({errors, language}: Props) { const model = monaco.editor.getModel(uri); invariant(model, 'Model must exist for the selected input file.'); renderReactCompilerMarkers({monaco, model, details: errors}); - // N.B. that `tabSize` is a model property, not an editor property. - // So, the tab size has to be set per model. + /** + * N.B. that `tabSize` is a model property, not an editor property. + * So, the tab size has to be set per model. + */ model.updateOptions({tabSize: 2}); }, [monaco, errors]); - const flowDiagnosticDisable = [ - 7028 /* unused label */, 6133 /* var declared but not read */, - ]; useEffect(() => { - // Ignore "can only be used in TypeScript files." errors, since - // we want to support syntax highlighting for Flow (*.js) files - // and Flow is not a built-in language. + /** + * Ignore "can only be used in TypeScript files." errors, since + * we want to support syntax highlighting for Flow (*.js) files + * and Flow is not a built-in language. + */ if (!monaco) return; monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({ diagnosticCodesToIgnore: [ @@ -64,7 +64,9 @@ export default function Input({errors, language}: Props) { 8011, 8012, 8013, - ...(language === 'flow' ? flowDiagnosticDisable : []), + ...(language === 'flow' + ? [7028 /* unused label */, 6133 /* var declared but not read */] + : []), ], noSemanticValidation: true, // Monaco can't validate Flow component syntax @@ -72,7 +74,7 @@ export default function Input({errors, language}: Props) { }); }, [monaco, language]); - const handleChange = (value: string | undefined) => { + const handleChange: (value: string | undefined) => void = value => { if (!value) return; dispatchStore({ @@ -83,7 +85,10 @@ export default function Input({errors, language}: Props) { }); }; - const handleMount = (_: editor.IStandaloneCodeEditor, monaco: Monaco) => { + const handleMount: ( + _: editor.IStandaloneCodeEditor, + monaco: Monaco, + ) => void = (_, monaco) => { setMonaco(monaco); const tscOptions = { @@ -111,10 +116,12 @@ export default function Input({errors, language}: Props) { monaco.languages.typescript.javascriptDefaults.addExtraLib(...reactLib); monaco.languages.typescript.typescriptDefaults.addExtraLib(...reactLib); - // Remeasure the font in case the custom font is loaded only after - // Monaco Editor is mounted. - // N.B. that this applies also to the output editor as it seems - // Monaco Editor instances share the same font config. + /** + * Remeasure the font in case the custom font is loaded only after + * Monaco Editor is mounted. + * N.B. that this applies also to the output editor as it seems + * Monaco Editor instances share the same font config. + */ document.fonts.ready.then(() => { monaco.editor.remeasureFonts(); }); @@ -125,14 +132,18 @@ export default function Input({errors, language}: Props) { } + | {kind: 'ok'; results: Map>} | { kind: 'err'; - results: Map; + results: Map>; error: CompilerError; }; @@ -54,7 +54,10 @@ type Props = { compilerOutput: CompilerOutput; }; -async function tabify(source: string, compilerOutput: CompilerOutput) { +async function tabify( + source: string, + compilerOutput: CompilerOutput, +): Promise> { const tabs = new Map(); const reorderedTabs = new Map(); const concattedResults = new Map(); @@ -112,8 +115,10 @@ async function tabify(source: string, compilerOutput: CompilerOutput) { } // Ensure that JS and the JS source map come first if (topLevelFnDecls.length > 0) { - // Make a synthetic Program so we can have a single AST with all the top level - // FunctionDeclarations + /** + * Make a synthetic Program so we can have a single AST with all the top level + * FunctionDeclarations + */ const ast = t.program(topLevelFnDecls); const {code, sourceMapUrl} = await codegen(ast, source); reorderedTabs.set( @@ -175,7 +180,7 @@ function getSourceMapUrl(code: string, map: string): string | null { )}`; } -function Output({store, compilerOutput}: Props) { +function Output({store, compilerOutput}: Props): JSX.Element { const [tabsOpen, setTabsOpen] = useState>(() => new Set(['JS'])); const [tabs, setTabs] = useState>( () => new Map(), @@ -236,11 +241,13 @@ function TextTabContent({ output: string; diff: string | null; showInfoPanel: boolean; -}) { +}): JSX.Element { const [diffMode, setDiffMode] = useState(false); return ( - // Restrict MonacoEditor's height, since the config autoLayout:true - // will grow the editor to fit within parent element + /** + * Restrict MonacoEditor's height, since the config autoLayout:true + * will grow the editor to fit within parent element + */
{showInfoPanel ? (
diff --git a/compiler/apps/playground/components/Editor/index.tsx b/compiler/apps/playground/components/Editor/index.tsx index 62f0f4440758a..6fe7234d29c2a 100644 --- a/compiler/apps/playground/components/Editor/index.tsx +++ b/compiler/apps/playground/components/Editor/index.tsx @@ -7,8 +7,10 @@ import dynamic from 'next/dynamic'; -// monaco-editor is currently not compatible with ssr -// https://github.com/vercel/next.js/issues/31692 +/** + * monaco-editor is currently not compatible with ssr + * https://github.com/vercel/next.js/issues/31692 + */ const Editor = dynamic(() => import('./EditorImpl'), { ssr: false, }); diff --git a/compiler/apps/playground/components/Header.tsx b/compiler/apps/playground/components/Header.tsx index 76b20be7df4f7..cbaa6788eca9c 100644 --- a/compiler/apps/playground/components/Header.tsx +++ b/compiler/apps/playground/components/Header.tsx @@ -16,26 +16,26 @@ import {IconGitHub} from './Icons/IconGitHub'; import Logo from './Logo'; import {useStoreDispatch} from './StoreContext'; -export default function Header() { +export default function Header(): JSX.Element { const [showCheck, setShowCheck] = useState(false); const dispatchStore = useStoreDispatch(); const {enqueueSnackbar, closeSnackbar} = useSnackbar(); - const handleReset = () => { + const handleReset: () => void = () => { if (confirm('Are you sure you want to reset the playground?')) { - /* - Close open snackbars if any. This is necessary because when displaying - outputs (Preview or not), we only close previous snackbars if we received - new messages, which is needed in order to display "Bad URL" or success - messages when loading Playground for the first time. Otherwise, messages - such as "Bad URL" will be closed by the outputs calling `closeSnackbar`. - */ + /** + * Close open snackbars if any. This is necessary because when displaying + * outputs (Preview or not), we only close previous snackbars if we received + * new messages, which is needed in order to display "Bad URL" or success + * messages when loading Playground for the first time. Otherwise, messages + * such as "Bad URL" will be closed by the outputs calling `closeSnackbar`. + */ closeSnackbar(); dispatchStore({type: 'setStore', payload: {store: defaultStore}}); } }; - const handleShare = () => { + const handleShare: () => void = () => { navigator.clipboard.writeText(location.href).then(() => { enqueueSnackbar('URL copied to clipboard'); setShowCheck(true); diff --git a/compiler/apps/playground/components/Logo.tsx b/compiler/apps/playground/components/Logo.tsx index 06a623e5569cd..bac1573239be1 100644 --- a/compiler/apps/playground/components/Logo.tsx +++ b/compiler/apps/playground/components/Logo.tsx @@ -7,7 +7,7 @@ // https://github.com/reactjs/reactjs.org/blob/main/beta/src/components/Logo.tsx -export default function Logo(props: JSX.IntrinsicElements['svg']) { +export default function Logo(props: JSX.IntrinsicElements['svg']): JSX.Element { return ( {isShow ? ( @@ -80,7 +83,7 @@ function TabbedWindowItem({ className={`p-4 duration-150 ease-in border-b cursor-pointer border-grey-200 ${ hasChanged ? 'font-bold' : 'font-light' } text-secondary hover:text-link`}> - - {name} + - {displayName} {tabs.get(name) ??
No output for {name}
} @@ -94,7 +97,7 @@ function TabbedWindowItem({ className={`flex-grow-0 w-5 transition-colors duration-150 ease-in ${ hasChanged ? 'font-bold' : 'font-light' } text-secondary hover:text-link`}> - {name} + {displayName}
)} diff --git a/compiler/apps/playground/lib/createContext.ts b/compiler/apps/playground/lib/createContext.ts index 8d5d6abd7e54a..e1a455fe4b1b0 100644 --- a/compiler/apps/playground/lib/createContext.ts +++ b/compiler/apps/playground/lib/createContext.ts @@ -23,10 +23,13 @@ import React from 'react'; * Instead, it throws an error when `useContext` is not called within a * Provider with a value. */ -export default function createContext() { +export default function createContext(): { + useContext: () => NonNullable; + Provider: React.Provider; +} { const context = React.createContext(null); - function useContext() { + function useContext(): NonNullable { const c = React.useContext(context); if (!c) throw new Error('useContext must be within a Provider with a value'); diff --git a/compiler/apps/playground/lib/reactCompilerMonacoDiagnostics.ts b/compiler/apps/playground/lib/reactCompilerMonacoDiagnostics.ts index 4352d643a50ac..76bcc5da37f55 100644 --- a/compiler/apps/playground/lib/reactCompilerMonacoDiagnostics.ts +++ b/compiler/apps/playground/lib/reactCompilerMonacoDiagnostics.ts @@ -46,9 +46,9 @@ function mapReactCompilerDiagnosticToMonacoMarker( type ReactCompilerMarkerConfig = { monaco: Monaco; model: editor.ITextModel; - details: CompilerErrorDetail[]; + details: Array; }; -let decorations: string[] = []; +let decorations: Array = []; export function renderReactCompilerMarkers({ monaco, model, diff --git a/compiler/apps/playground/lib/stores/store.ts b/compiler/apps/playground/lib/stores/store.ts index a916c1a0ada07..ad4a57cf914a9 100644 --- a/compiler/apps/playground/lib/stores/store.ts +++ b/compiler/apps/playground/lib/stores/store.ts @@ -28,7 +28,7 @@ export function decodeStore(hash: string): Store { /** * Serialize, encode, and save @param store to localStorage and update URL. */ -export function saveStore(store: Store) { +export function saveStore(store: Store): void { const hash = encodeStore(store); localStorage.setItem('playgroundStore', hash); history.replaceState({}, '', `#${hash}`); @@ -56,8 +56,10 @@ export function initStoreFromUrlOrLocalStorage(): Store { const encodedSourceFromLocal = localStorage.getItem('playgroundStore'); const encodedSource = encodedSourceFromUrl || encodedSourceFromLocal; - // No data in the URL and no data in the localStorage to fallback to. - // Initialize with the default store. + /** + * No data in the URL and no data in the localStorage to fallback to. + * Initialize with the default store. + */ if (!encodedSource) return defaultStore; const raw = decodeStore(encodedSource); diff --git a/compiler/apps/playground/next-env.d.ts b/compiler/apps/playground/next-env.d.ts index 4f11a03dc6cc3..40c3d68096c27 100644 --- a/compiler/apps/playground/next-env.d.ts +++ b/compiler/apps/playground/next-env.d.ts @@ -2,4 +2,4 @@ /// // NOTE: This file should not be edited -// see https://nextjs.org/docs/basic-features/typescript for more information. +// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information. diff --git a/compiler/apps/playground/next.config.js b/compiler/apps/playground/next.config.js index 0082e02532026..fc8a9492e4ed7 100644 --- a/compiler/apps/playground/next.config.js +++ b/compiler/apps/playground/next.config.js @@ -9,6 +9,9 @@ const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin'); const path = require('path'); const nextConfig = { + experimental: { + reactCompiler: true, + }, reactStrictMode: true, webpack: (config, options) => { // Load *.d.ts files as strings using https://webpack.js.org/guides/asset-modules/#source-assets. diff --git a/compiler/apps/playground/package.json b/compiler/apps/playground/package.json index 637f300ae1538..c3dd825f1a16f 100644 --- a/compiler/apps/playground/package.json +++ b/compiler/apps/playground/package.json @@ -3,58 +3,60 @@ "version": "0.1.0", "private": true, "scripts": { - "dev": "NODE_ENV=development next dev", - "build": "next build && node ./scripts/downloadFonts.js", - "vercel-build": "yarn workspaces run build", + "dev": "cd ../.. && concurrently --kill-others -n compiler,runtime,playground \"yarn workspace babel-plugin-react-compiler run build --watch\" \"yarn workspace react-compiler-runtime run build --watch\" \"wait-on packages/babel-plugin-react-compiler/dist/index.js && cd apps/playground && NODE_ENV=development next dev\"", + "build:compiler": "cd ../.. && concurrently -n compiler,runtime \"yarn workspace babel-plugin-react-compiler run build\" \"yarn workspace react-compiler-runtime run build\"", + "build": "yarn build:compiler && next build", + "postbuild": "node ./scripts/downloadFonts.js", + "postinstall": "./scripts/link-compiler.sh", + "vercel-build": "yarn build", "start": "next start", "lint": "next lint", "test": "playwright test" }, "dependencies": { - "@babel/core": "^7.19.1", - "@babel/generator": "^7.19.1", - "@babel/parser": "^7.19.1", - "@babel/plugin-syntax-typescript": "^7.18.6", + "@babel/core": "^7.18.9", + "@babel/generator": "^7.18.9", + "@babel/parser": "^7.18.9", + "@babel/plugin-syntax-typescript": "^7.18.9", "@babel/plugin-transform-block-scoping": "^7.18.9", - "@babel/plugin-transform-modules-commonjs": "^7.18.6", - "@babel/preset-react": "^7.18.6", - "@babel/preset-typescript": "^7.18.6", - "@babel/traverse": "^7.19.1", - "@babel/types": "^7.19.0", + "@babel/plugin-transform-modules-commonjs": "^7.18.9", + "@babel/preset-react": "^7.18.9", + "@babel/preset-typescript": "^7.18.9", + "@babel/traverse": "^7.18.9", + "@babel/types": "7.18.9", "@heroicons/react": "^1.0.6", "@monaco-editor/react": "^4.4.6", "@playwright/test": "^1.42.1", "@use-gesture/react": "^10.2.22", - "fs": "^0.0.1-security", - "hermes-eslint": "^0.14.0", - "hermes-parser": "^0.22.0", + "hermes-eslint": "^0.25.0", + "hermes-parser": "^0.25.0", "invariant": "^2.2.4", "lz-string": "^1.5.0", - "monaco-editor": "^0.34.1", - "next": "^13.5.6", + "monaco-editor": "^0.52.0", + "next": "^15.0.1", "notistack": "^3.0.0-alpha.7", "prettier": "^3.3.3", "pretty-format": "^29.3.1", "re-resizable": "^6.9.16", - "react": "18.2.0", - "react-compiler-runtime": "*", - "react-dom": "18.2.0" + "react": "19.0.0-rc-77b637d6-20241016", + "react-dom": "19.0.0-rc-77b637d6-20241016" }, "devDependencies": { "@types/node": "18.11.9", - "@types/react": "18.0.25", - "@types/react-dom": "18.0.9", + "@types/react": "npm:types-react@19.0.0-rc.1", + "@types/react-dom": "npm:types-react-dom@19.0.0-rc.1", "autoprefixer": "^10.4.13", "clsx": "^1.2.1", + "concurrently": "^7.4.0", "eslint": "^8.28.0", - "eslint-config-next": "^13.5.6", - "hermes-parser": "^0.22.0", + "eslint-config-next": "^15.0.1", "monaco-editor-webpack-plugin": "^7.1.0", "postcss": "^8.4.31", - "tailwindcss": "^3.2.4" + "tailwindcss": "^3.2.4", + "wait-on": "^7.2.0" }, "resolutions": { - "./**/@babel/parser": "7.7.4", - "./**/@babel/types": "7.7.4" + "@types/react": "npm:types-react@19.0.0-rc.1", + "@types/react-dom": "npm:types-react-dom@19.0.0-rc.1" } } diff --git a/compiler/apps/playground/playwright.config.js b/compiler/apps/playground/playwright.config.js index be7010feb9c5a..ca243742f5636 100644 --- a/compiler/apps/playground/playwright.config.js +++ b/compiler/apps/playground/playwright.config.js @@ -18,6 +18,8 @@ const baseURL = `http://localhost:${PORT}`; export default defineConfig({ // Timeout per test timeout: 30 * 1000, + // Run all tests in parallel. + fullyParallel: true, // Test directory testDir: path.join(__dirname, '__tests__/e2e'), // If a test fails, retry it additional 2 times @@ -30,13 +32,16 @@ export default defineConfig({ // Run your local dev server before starting the tests: // https://playwright.dev/docs/test-advanced#launching-a-development-web-server-during-the-tests webServer: { - command: - 'yarn workspace babel-plugin-react-compiler build && yarn workspace react-compiler-runtime build && yarn dev', + command: 'yarn dev', url: baseURL, timeout: 300 * 1000, reuseExistingServer: !process.env.CI, }, + // 'github' for GitHub Actions CI to generate annotations, plus a concise 'dot' + // default 'list' when running locally + reporter: process.env.CI ? 'github' : 'list', + use: { // Use baseURL so to make navigations relative. // More information: https://playwright.dev/docs/api/class-testoptions#test-options-base-url diff --git a/compiler/apps/playground/scripts/link-compiler.sh b/compiler/apps/playground/scripts/link-compiler.sh new file mode 100755 index 0000000000000..1ee5f0b81bf09 --- /dev/null +++ b/compiler/apps/playground/scripts/link-compiler.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +set -eo pipefail + +HERE=$(pwd) + +cd ../../packages/react-compiler-runtime && yarn --silent link && cd $HERE +cd ../../packages/babel-plugin-react-compiler && yarn --silent link && cd $HERE + +yarn --silent link babel-plugin-react-compiler +yarn --silent link react-compiler-runtime diff --git a/compiler/apps/playground/tsconfig.json b/compiler/apps/playground/tsconfig.json index 55d0b209b9529..eb7fcfe2b7228 100644 --- a/compiler/apps/playground/tsconfig.json +++ b/compiler/apps/playground/tsconfig.json @@ -31,6 +31,7 @@ ".next/types/**/*.ts" ], "exclude": [ - "node_modules" + "node_modules", + "../../../**" ] } diff --git a/compiler/apps/playground/yarn.lock b/compiler/apps/playground/yarn.lock new file mode 100644 index 0000000000000..dc5362548a7f7 --- /dev/null +++ b/compiler/apps/playground/yarn.lock @@ -0,0 +1,3789 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@alloc/quick-lru@^5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz#7bf68b20c0a350f936915fcae06f58e32007ce30" + integrity sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw== + +"@ampproject/remapping@^2.2.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" + integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + +"@babel/code-frame@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.7.tgz#882fd9e09e8ee324e496bd040401c6f046ef4465" + integrity sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA== + dependencies: + "@babel/highlight" "^7.24.7" + picocolors "^1.0.0" + +"@babel/compat-data@^7.25.2": + version "7.25.4" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.25.4.tgz#7d2a80ce229890edcf4cc259d4d696cb4dae2fcb" + integrity sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ== + +"@babel/core@^7.18.9": + version "7.25.2" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.25.2.tgz#ed8eec275118d7613e77a352894cd12ded8eba77" + integrity sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.24.7" + "@babel/generator" "^7.25.0" + "@babel/helper-compilation-targets" "^7.25.2" + "@babel/helper-module-transforms" "^7.25.2" + "@babel/helpers" "^7.25.0" + "@babel/parser" "^7.25.0" + "@babel/template" "^7.25.0" + "@babel/traverse" "^7.25.2" + "@babel/types" "^7.25.2" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/generator@^7.18.9", "@babel/generator@^7.25.0", "@babel/generator@^7.25.6": + version "7.25.6" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.25.6.tgz#0df1ad8cb32fe4d2b01d8bf437f153d19342a87c" + integrity sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw== + dependencies: + "@babel/types" "^7.25.6" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^2.5.1" + +"@babel/helper-annotate-as-pure@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz#5373c7bc8366b12a033b4be1ac13a206c6656aab" + integrity sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg== + dependencies: + "@babel/types" "^7.24.7" + +"@babel/helper-compilation-targets@^7.25.2": + version "7.25.2" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz#e1d9410a90974a3a5a66e84ff55ef62e3c02d06c" + integrity sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw== + dependencies: + "@babel/compat-data" "^7.25.2" + "@babel/helper-validator-option" "^7.24.8" + browserslist "^4.23.1" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-create-class-features-plugin@^7.25.0": + version "7.25.4" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.4.tgz#57eaf1af38be4224a9d9dd01ddde05b741f50e14" + integrity sha512-ro/bFs3/84MDgDmMwbcHgDa8/E6J3QKNTk4xJJnVeFtGE+tL0K26E3pNxhYz2b67fJpt7Aphw5XcploKXuCvCQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.24.7" + "@babel/helper-member-expression-to-functions" "^7.24.8" + "@babel/helper-optimise-call-expression" "^7.24.7" + "@babel/helper-replace-supers" "^7.25.0" + "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" + "@babel/traverse" "^7.25.4" + semver "^6.3.1" + +"@babel/helper-member-expression-to-functions@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.8.tgz#6155e079c913357d24a4c20480db7c712a5c3fb6" + integrity sha512-LABppdt+Lp/RlBxqrh4qgf1oEH/WxdzQNDJIu5gC/W1GyvPVrOBiItmmM8wan2fm4oYqFuFfkXmlGpLQhPY8CA== + dependencies: + "@babel/traverse" "^7.24.8" + "@babel/types" "^7.24.8" + +"@babel/helper-module-imports@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz#f2f980392de5b84c3328fc71d38bd81bbb83042b" + integrity sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA== + dependencies: + "@babel/traverse" "^7.24.7" + "@babel/types" "^7.24.7" + +"@babel/helper-module-transforms@^7.24.8", "@babel/helper-module-transforms@^7.25.2": + version "7.25.2" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz#ee713c29768100f2776edf04d4eb23b8d27a66e6" + integrity sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ== + dependencies: + "@babel/helper-module-imports" "^7.24.7" + "@babel/helper-simple-access" "^7.24.7" + "@babel/helper-validator-identifier" "^7.24.7" + "@babel/traverse" "^7.25.2" + +"@babel/helper-optimise-call-expression@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.7.tgz#8b0a0456c92f6b323d27cfd00d1d664e76692a0f" + integrity sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A== + dependencies: + "@babel/types" "^7.24.7" + +"@babel/helper-plugin-utils@^7.24.7", "@babel/helper-plugin-utils@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz#94ee67e8ec0e5d44ea7baeb51e571bd26af07878" + integrity sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg== + +"@babel/helper-replace-supers@^7.25.0": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.25.0.tgz#ff44deac1c9f619523fe2ca1fd650773792000a9" + integrity sha512-q688zIvQVYtZu+i2PsdIu/uWGRpfxzr5WESsfpShfZECkO+d2o+WROWezCi/Q6kJ0tfPa5+pUGUlfx2HhrA3Bg== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.24.8" + "@babel/helper-optimise-call-expression" "^7.24.7" + "@babel/traverse" "^7.25.0" + +"@babel/helper-simple-access@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz#bcade8da3aec8ed16b9c4953b74e506b51b5edb3" + integrity sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg== + dependencies: + "@babel/traverse" "^7.24.7" + "@babel/types" "^7.24.7" + +"@babel/helper-skip-transparent-expression-wrappers@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.24.7.tgz#5f8fa83b69ed5c27adc56044f8be2b3ea96669d9" + integrity sha512-IO+DLT3LQUElMbpzlatRASEyQtfhSE0+m465v++3jyyXeBTBUjtVZg28/gHeV5mrTJqvEKhKroBGAvhW+qPHiQ== + dependencies: + "@babel/traverse" "^7.24.7" + "@babel/types" "^7.24.7" + +"@babel/helper-string-parser@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz#5b3329c9a58803d5df425e5785865881a81ca48d" + integrity sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ== + +"@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz#75b889cfaf9e35c2aaf42cf0d72c8e91719251db" + integrity sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w== + +"@babel/helper-validator-option@^7.24.7", "@babel/helper-validator-option@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz#3725cdeea8b480e86d34df15304806a06975e33d" + integrity sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q== + +"@babel/helpers@^7.25.0": + version "7.25.6" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.25.6.tgz#57ee60141829ba2e102f30711ffe3afab357cc60" + integrity sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q== + dependencies: + "@babel/template" "^7.25.0" + "@babel/types" "^7.25.6" + +"@babel/highlight@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.7.tgz#a05ab1df134b286558aae0ed41e6c5f731bf409d" + integrity sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw== + dependencies: + "@babel/helper-validator-identifier" "^7.24.7" + chalk "^2.4.2" + js-tokens "^4.0.0" + picocolors "^1.0.0" + +"@babel/parser@^7.18.9", "@babel/parser@^7.25.0", "@babel/parser@^7.25.6": + version "7.25.6" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.25.6.tgz#85660c5ef388cbbf6e3d2a694ee97a38f18afe2f" + integrity sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q== + dependencies: + "@babel/types" "^7.25.6" + +"@babel/plugin-syntax-jsx@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz#39a1fa4a7e3d3d7f34e2acc6be585b718d30e02d" + integrity sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-syntax-typescript@^7.18.9", "@babel/plugin-syntax-typescript@^7.24.7": + version "7.25.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.4.tgz#04db9ce5a9043d9c635e75ae7969a2cd50ca97ff" + integrity sha512-uMOCoHVU52BsSWxPOMVv5qKRdeSlPuImUCB2dlPuBSU+W2/ROE7/Zg8F2Kepbk+8yBa68LlRKxO+xgEVWorsDg== + dependencies: + "@babel/helper-plugin-utils" "^7.24.8" + +"@babel/plugin-transform-block-scoping@^7.18.9": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.0.tgz#23a6ed92e6b006d26b1869b1c91d1b917c2ea2ac" + integrity sha512-yBQjYoOjXlFv9nlXb3f1casSHOZkWr29NX+zChVanLg5Nc157CrbEX9D7hxxtTpuFy7Q0YzmmWfJxzvps4kXrQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.8" + +"@babel/plugin-transform-modules-commonjs@^7.18.9", "@babel/plugin-transform-modules-commonjs@^7.24.7": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.8.tgz#ab6421e564b717cb475d6fff70ae7f103536ea3c" + integrity sha512-WHsk9H8XxRs3JXKWFiqtQebdh9b/pTk4EgueygFzYlTKAg0Ud985mSevdNjdXdFBATSKVJGQXP1tv6aGbssLKA== + dependencies: + "@babel/helper-module-transforms" "^7.24.8" + "@babel/helper-plugin-utils" "^7.24.8" + "@babel/helper-simple-access" "^7.24.7" + +"@babel/plugin-transform-react-display-name@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.24.7.tgz#9caff79836803bc666bcfe210aeb6626230c293b" + integrity sha512-H/Snz9PFxKsS1JLI4dJLtnJgCJRoo0AUm3chP6NYr+9En1JMKloheEiLIhlp5MDVznWo+H3AAC1Mc8lmUEpsgg== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-react-jsx-development@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.24.7.tgz#eaee12f15a93f6496d852509a850085e6361470b" + integrity sha512-QG9EnzoGn+Qar7rxuW+ZOsbWOt56FvvI93xInqsZDC5fsekx1AlIO4KIJ5M+D0p0SqSH156EpmZyXq630B8OlQ== + dependencies: + "@babel/plugin-transform-react-jsx" "^7.24.7" + +"@babel/plugin-transform-react-jsx@^7.24.7": + version "7.25.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.2.tgz#e37e8ebfa77e9f0b16ba07fadcb6adb47412227a" + integrity sha512-KQsqEAVBpU82NM/B/N9j9WOdphom1SZH3R+2V7INrQUH+V9EBFwZsEJl8eBIVeQE62FxJCc70jzEZwqU7RcVqA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.24.7" + "@babel/helper-module-imports" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.8" + "@babel/plugin-syntax-jsx" "^7.24.7" + "@babel/types" "^7.25.2" + +"@babel/plugin-transform-react-pure-annotations@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.24.7.tgz#bdd9d140d1c318b4f28b29a00fb94f97ecab1595" + integrity sha512-PLgBVk3fzbmEjBJ/u8kFzOqS9tUeDjiaWud/rRym/yjCo/M9cASPlnrd2ZmmZpQT40fOOrvR8jh+n8jikrOhNA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-typescript@^7.24.7": + version "7.25.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.25.2.tgz#237c5d10de6d493be31637c6b9fa30b6c5461add" + integrity sha512-lBwRvjSmqiMYe/pS0+1gggjJleUJi7NzjvQ1Fkqtt69hBa/0t1YuW/MLQMAPixfwaQOHUXsd6jeU3Z+vdGv3+A== + dependencies: + "@babel/helper-annotate-as-pure" "^7.24.7" + "@babel/helper-create-class-features-plugin" "^7.25.0" + "@babel/helper-plugin-utils" "^7.24.8" + "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" + "@babel/plugin-syntax-typescript" "^7.24.7" + +"@babel/preset-react@^7.18.9": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.24.7.tgz#480aeb389b2a798880bf1f889199e3641cbb22dc" + integrity sha512-AAH4lEkpmzFWrGVlHaxJB7RLH21uPQ9+He+eFLWHmF9IuFQVugz8eAsamaW0DXRrTfco5zj1wWtpdcXJUOfsag== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-validator-option" "^7.24.7" + "@babel/plugin-transform-react-display-name" "^7.24.7" + "@babel/plugin-transform-react-jsx" "^7.24.7" + "@babel/plugin-transform-react-jsx-development" "^7.24.7" + "@babel/plugin-transform-react-pure-annotations" "^7.24.7" + +"@babel/preset-typescript@^7.18.9": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.24.7.tgz#66cd86ea8f8c014855671d5ea9a737139cbbfef1" + integrity sha512-SyXRe3OdWwIwalxDg5UtJnJQO+YPcTfwiIY2B0Xlddh9o7jpWLvv8X1RthIeDOxQ+O1ML5BLPCONToObyVQVuQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-validator-option" "^7.24.7" + "@babel/plugin-syntax-jsx" "^7.24.7" + "@babel/plugin-transform-modules-commonjs" "^7.24.7" + "@babel/plugin-transform-typescript" "^7.24.7" + +"@babel/runtime@^7.21.0": + version "7.25.6" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.6.tgz#9afc3289f7184d8d7f98b099884c26317b9264d2" + integrity sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ== + dependencies: + regenerator-runtime "^0.14.0" + +"@babel/template@^7.25.0": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.0.tgz#e733dc3134b4fede528c15bc95e89cb98c52592a" + integrity sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q== + dependencies: + "@babel/code-frame" "^7.24.7" + "@babel/parser" "^7.25.0" + "@babel/types" "^7.25.0" + +"@babel/traverse@^7.18.9", "@babel/traverse@^7.24.7", "@babel/traverse@^7.24.8", "@babel/traverse@^7.25.0", "@babel/traverse@^7.25.2", "@babel/traverse@^7.25.4": + version "7.25.6" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.6.tgz#04fad980e444f182ecf1520504941940a90fea41" + integrity sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ== + dependencies: + "@babel/code-frame" "^7.24.7" + "@babel/generator" "^7.25.6" + "@babel/parser" "^7.25.6" + "@babel/template" "^7.25.0" + "@babel/types" "^7.25.6" + debug "^4.3.1" + globals "^11.1.0" + +"@babel/types@7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.9.tgz#7148d64ba133d8d73a41b3172ac4b83a1452205f" + integrity sha512-WwMLAg2MvJmt/rKEVQBBhIVffMmnilX4oe0sRe7iPOHIGsqpruFHHdrfj4O1CMMtgMtCU4oPafZjDPCRgO57Wg== + dependencies: + "@babel/helper-validator-identifier" "^7.18.6" + to-fast-properties "^2.0.0" + +"@babel/types@^7.24.7", "@babel/types@^7.24.8", "@babel/types@^7.25.0", "@babel/types@^7.25.2", "@babel/types@^7.25.6": + version "7.25.6" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.25.6.tgz#893942ddb858f32ae7a004ec9d3a76b3463ef8e6" + integrity sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw== + dependencies: + "@babel/helper-string-parser" "^7.24.8" + "@babel/helper-validator-identifier" "^7.24.7" + to-fast-properties "^2.0.0" + +"@emnapi/runtime@^1.2.0": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.3.1.tgz#0fcaa575afc31f455fd33534c19381cfce6c6f60" + integrity sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw== + dependencies: + tslib "^2.4.0" + +"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": + version "4.4.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" + integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== + dependencies: + eslint-visitor-keys "^3.3.0" + +"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.6.1": + version "4.11.1" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.11.1.tgz#a547badfc719eb3e5f4b556325e542fbe9d7a18f" + integrity sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q== + +"@eslint/eslintrc@^2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" + integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.6.0" + globals "^13.19.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@eslint/js@8.57.1": + version "8.57.1" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.1.tgz#de633db3ec2ef6a3c89e2f19038063e8a122e2c2" + integrity sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q== + +"@hapi/hoek@^9.0.0", "@hapi/hoek@^9.3.0": + version "9.3.0" + resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb" + integrity sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ== + +"@hapi/topo@^5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.1.0.tgz#dc448e332c6c6e37a4dc02fd84ba8d44b9afb012" + integrity sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg== + dependencies: + "@hapi/hoek" "^9.0.0" + +"@heroicons/react@^1.0.6": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@heroicons/react/-/react-1.0.6.tgz#35dd26987228b39ef2316db3b1245c42eb19e324" + integrity sha512-JJCXydOFWMDpCP4q13iEplA503MQO3xLoZiKum+955ZCtHINWnx26CUxVxxFQu/uLb4LW3ge15ZpzIkXKkJ8oQ== + +"@humanwhocodes/config-array@^0.13.0": + version "0.13.0" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.13.0.tgz#fb907624df3256d04b9aa2df50d7aa97ec648748" + integrity sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw== + dependencies: + "@humanwhocodes/object-schema" "^2.0.3" + debug "^4.3.1" + minimatch "^3.0.5" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/object-schema@^2.0.3": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" + integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== + +"@img/sharp-darwin-arm64@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz#ef5b5a07862805f1e8145a377c8ba6e98813ca08" + integrity sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ== + optionalDependencies: + "@img/sharp-libvips-darwin-arm64" "1.0.4" + +"@img/sharp-darwin-x64@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz#e03d3451cd9e664faa72948cc70a403ea4063d61" + integrity sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q== + optionalDependencies: + "@img/sharp-libvips-darwin-x64" "1.0.4" + +"@img/sharp-libvips-darwin-arm64@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz#447c5026700c01a993c7804eb8af5f6e9868c07f" + integrity sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg== + +"@img/sharp-libvips-darwin-x64@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz#e0456f8f7c623f9dbfbdc77383caa72281d86062" + integrity sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ== + +"@img/sharp-libvips-linux-arm64@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz#979b1c66c9a91f7ff2893556ef267f90ebe51704" + integrity sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA== + +"@img/sharp-libvips-linux-arm@1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz#99f922d4e15216ec205dcb6891b721bfd2884197" + integrity sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g== + +"@img/sharp-libvips-linux-s390x@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz#f8a5eb1f374a082f72b3f45e2fb25b8118a8a5ce" + integrity sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA== + +"@img/sharp-libvips-linux-x64@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz#d4c4619cdd157774906e15770ee119931c7ef5e0" + integrity sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw== + +"@img/sharp-libvips-linuxmusl-arm64@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz#166778da0f48dd2bded1fa3033cee6b588f0d5d5" + integrity sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA== + +"@img/sharp-libvips-linuxmusl-x64@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz#93794e4d7720b077fcad3e02982f2f1c246751ff" + integrity sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw== + +"@img/sharp-linux-arm64@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz#edb0697e7a8279c9fc829a60fc35644c4839bb22" + integrity sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA== + optionalDependencies: + "@img/sharp-libvips-linux-arm64" "1.0.4" + +"@img/sharp-linux-arm@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz#422c1a352e7b5832842577dc51602bcd5b6f5eff" + integrity sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ== + optionalDependencies: + "@img/sharp-libvips-linux-arm" "1.0.5" + +"@img/sharp-linux-s390x@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz#f5c077926b48e97e4a04d004dfaf175972059667" + integrity sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q== + optionalDependencies: + "@img/sharp-libvips-linux-s390x" "1.0.4" + +"@img/sharp-linux-x64@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz#d806e0afd71ae6775cc87f0da8f2d03a7c2209cb" + integrity sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA== + optionalDependencies: + "@img/sharp-libvips-linux-x64" "1.0.4" + +"@img/sharp-linuxmusl-arm64@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz#252975b915894fb315af5deea174651e208d3d6b" + integrity sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g== + optionalDependencies: + "@img/sharp-libvips-linuxmusl-arm64" "1.0.4" + +"@img/sharp-linuxmusl-x64@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz#3f4609ac5d8ef8ec7dadee80b560961a60fd4f48" + integrity sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw== + optionalDependencies: + "@img/sharp-libvips-linuxmusl-x64" "1.0.4" + +"@img/sharp-wasm32@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz#6f44f3283069d935bb5ca5813153572f3e6f61a1" + integrity sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg== + dependencies: + "@emnapi/runtime" "^1.2.0" + +"@img/sharp-win32-ia32@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz#1a0c839a40c5351e9885628c85f2e5dfd02b52a9" + integrity sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ== + +"@img/sharp-win32-x64@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz#56f00962ff0c4e0eb93d34a047d29fa995e3e342" + integrity sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg== + +"@isaacs/cliui@^8.0.2": + version "8.0.2" + resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" + integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== + dependencies: + string-width "^5.1.2" + string-width-cjs "npm:string-width@^4.2.0" + strip-ansi "^7.0.1" + strip-ansi-cjs "npm:strip-ansi@^6.0.1" + wrap-ansi "^8.1.0" + wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" + +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== + dependencies: + "@sinclair/typebox" "^0.27.8" + +"@jridgewell/gen-mapping@^0.3.2", "@jridgewell/gen-mapping@^0.3.5": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36" + integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg== + dependencies: + "@jridgewell/set-array" "^1.2.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/set-array@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" + integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== + +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" + integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== + +"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": + version "0.3.25" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" + integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@monaco-editor/loader@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@monaco-editor/loader/-/loader-1.4.0.tgz#f08227057331ec890fa1e903912a5b711a2ad558" + integrity sha512-00ioBig0x642hytVspPl7DbQyaSWRaolYie/UFNjoTdvoKPzo6xrXLhTk9ixgIKcLH5b5vDOjVNiGyY+uDCUlg== + dependencies: + state-local "^1.0.6" + +"@monaco-editor/react@^4.4.6": + version "4.6.0" + resolved "https://registry.yarnpkg.com/@monaco-editor/react/-/react-4.6.0.tgz#bcc68671e358a21c3814566b865a54b191e24119" + integrity sha512-RFkU9/i7cN2bsq/iTkurMWOEErmYcY6JiQI3Jn+WeR/FGISH8JbHERjpS9oRuSOPvDMJI0Z8nJeKkbOs9sBYQw== + dependencies: + "@monaco-editor/loader" "^1.4.0" + +"@next/env@15.0.1": + version "15.0.1" + resolved "https://registry.yarnpkg.com/@next/env/-/env-15.0.1.tgz#660fe9303e255cec112d3f4198d2897a24bc60b3" + integrity sha512-lc4HeDUKO9gxxlM5G2knTRifqhsY6yYpwuHspBZdboZe0Gp+rZHBNNSIjmQKDJIdRXiXGyVnSD6gafrbQPvILQ== + +"@next/eslint-plugin-next@15.0.1": + version "15.0.1" + resolved "https://registry.yarnpkg.com/@next/eslint-plugin-next/-/eslint-plugin-next-15.0.1.tgz#76117d88aadc52f6e04b1892d44654d05468d53c" + integrity sha512-bKWsMaGPbiFAaGqrDJvbE8b4Z0uKicGVcgOI77YM2ui3UfjHMr4emFPrZTLeZVchi7fT1mooG2LxREfUUClIKw== + dependencies: + fast-glob "3.3.1" + +"@next/swc-darwin-arm64@15.0.1": + version "15.0.1" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.0.1.tgz#b80a25f1569bd0ca03eca9473f7e93e64937e404" + integrity sha512-C9k/Xv4sxkQRTA37Z6MzNq3Yb1BJMmSqjmwowoWEpbXTkAdfOwnoKOpAb71ItSzoA26yUTIo6ZhN8rKGu4ExQw== + +"@next/swc-darwin-x64@15.0.1": + version "15.0.1" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-15.0.1.tgz#00dcf79ec7c638a85c3b9ff2e2de2bfb09c1c250" + integrity sha512-uHl13HXOuq1G7ovWFxCACDJHTSDVbn/sbLv8V1p+7KIvTrYQ5HNoSmKBdYeEKRRCbEmd+OohOgg9YOp8Ux3MBg== + +"@next/swc-linux-arm64-gnu@15.0.1": + version "15.0.1" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.0.1.tgz#faab5f7ffcc6d1a15e8dea1cb9953966658b39bf" + integrity sha512-LvyhvxHOihFTEIbb35KxOc3q8w8G4xAAAH/AQnsYDEnOvwawjL2eawsB59AX02ki6LJdgDaHoTEnC54Gw+82xw== + +"@next/swc-linux-arm64-musl@15.0.1": + version "15.0.1" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.0.1.tgz#97abada9a782ab5b3cb42cf0d4799cbc2e733351" + integrity sha512-vFmCGUFNyk/A5/BYcQNhAQqPIw01RJaK6dRO+ZEhz0DncoW+hJW1kZ8aH2UvTX27zPq3m85zN5waMSbZEmANcQ== + +"@next/swc-linux-x64-gnu@15.0.1": + version "15.0.1" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.0.1.tgz#548bd47c49fe6d819302139aff8766eb704322e2" + integrity sha512-5by7IYq0NCF8rouz6Qg9T97jYU68kaClHPfGpQG2lCZpSYHtSPQF1kjnqBTd34RIqPKMbCa4DqCufirgr8HM5w== + +"@next/swc-linux-x64-musl@15.0.1": + version "15.0.1" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.0.1.tgz#84423fbd3a058dd6ae8322e530878f0ec7a1027a" + integrity sha512-lmYr6H3JyDNBJLzklGXLfbehU3ay78a+b6UmBGlHls4xhDXBNZfgb0aI67sflrX+cGBnv1LgmWzFlYrAYxS1Qw== + +"@next/swc-win32-arm64-msvc@15.0.1": + version "15.0.1" + resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.0.1.tgz#723c2ced12a998fb40dc901b8faea9170e788c2f" + integrity sha512-DS8wQtl6diAj0eZTdH0sefykm4iXMbHT4MOvLwqZiIkeezKpkgPFcEdFlz3vKvXa2R/2UEgMh48z1nEpNhjeOQ== + +"@next/swc-win32-x64-msvc@15.0.1": + version "15.0.1" + resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.0.1.tgz#ec7e3befc0bcc47527537b1eda2b3745beb15a09" + integrity sha512-4Ho2ggvDdMKlZ/0e9HNdZ9ngeaBwtc+2VS5oCeqrbXqOgutX6I4U2X/42VBw0o+M5evn4/7v3zKgGHo+9v/VjA== + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@nolyfill/is-core-module@1.0.39": + version "1.0.39" + resolved "https://registry.yarnpkg.com/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz#3dc35ba0f1e66b403c00b39344f870298ebb1c8e" + integrity sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA== + +"@pkgjs/parseargs@^0.11.0": + version "0.11.0" + resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" + integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== + +"@playwright/test@^1.42.1": + version "1.47.2" + resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.47.2.tgz#dbe7051336bfc5cc599954214f9111181dbc7475" + integrity sha512-jTXRsoSPONAs8Za9QEQdyjFn+0ZQFjCiIztAIF6bi1HqhBzG9Ma7g1WotyiGqFSBRZjIEqMdT8RUlbk1QVhzCQ== + dependencies: + playwright "1.47.2" + +"@rtsao/scc@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@rtsao/scc/-/scc-1.1.0.tgz#927dd2fae9bc3361403ac2c7a00c32ddce9ad7e8" + integrity sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g== + +"@rushstack/eslint-patch@^1.10.3": + version "1.10.4" + resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.10.4.tgz#427d5549943a9c6fce808e39ea64dbe60d4047f1" + integrity sha512-WJgX9nzTqknM393q1QJDJmoW28kUfEnybeTfVNcNAPnIx210RXm2DiXiHzfNPJNIUUb1tJnz/l4QGtJ30PgWmA== + +"@sideway/address@^4.1.5": + version "4.1.5" + resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.5.tgz#4bc149a0076623ced99ca8208ba780d65a99b9d5" + integrity sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q== + dependencies: + "@hapi/hoek" "^9.0.0" + +"@sideway/formula@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.1.tgz#80fcbcbaf7ce031e0ef2dd29b1bfc7c3f583611f" + integrity sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg== + +"@sideway/pinpoint@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df" + integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ== + +"@sinclair/typebox@^0.27.8": + version "0.27.8" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== + +"@swc/counter@0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@swc/counter/-/counter-0.1.3.tgz#cc7463bd02949611c6329596fccd2b0ec782b0e9" + integrity sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ== + +"@swc/helpers@0.5.13": + version "0.5.13" + resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.13.tgz#33e63ff3cd0cade557672bd7888a39ce7d115a8c" + integrity sha512-UoKGxQ3r5kYI9dALKJapMmuK+1zWM/H17Z1+iwnNmzcJRnfFuevZs375TA5rW31pu4BS4NoSy1fRsexDXfWn5w== + dependencies: + tslib "^2.4.0" + +"@types/json5@^0.0.29": + version "0.0.29" + resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" + integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== + +"@types/node@18.11.9": + version "18.11.9" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.9.tgz#02d013de7058cea16d36168ef2fc653464cfbad4" + integrity sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg== + +"@types/react-dom@npm:types-react-dom@19.0.0-rc.1": + version "19.0.0-rc.1" + resolved "https://registry.yarnpkg.com/types-react-dom/-/types-react-dom-19.0.0-rc.1.tgz#1d544d02c5df2a82d87c2eff979afa2e21a8317a" + integrity sha512-VSLZJl8VXCD0fAWp7DUTFUDCcZ8DVXOQmjhJMD03odgeFmu14ZQJHCXeETm3BEAhJqfgJaFkLnGkQv88sRx0fQ== + dependencies: + "@types/react" "*" + +"@types/react@*", "@types/react@npm:types-react@19.0.0-rc.1": + version "19.0.0-rc.1" + resolved "https://registry.yarnpkg.com/types-react/-/types-react-19.0.0-rc.1.tgz#576d1a702f6d0cc5b24813a293913e5cbfeaa647" + integrity sha512-RshndUfqTW6K3STLPis8BtAYCGOkMbtvYsi90gmVNDZBXUyUc5juf2PE9LfS/JmOlUIRO8cWTS/1MTnmhjDqyQ== + dependencies: + csstype "^3.0.2" + +"@typescript-eslint/eslint-plugin@^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0": + version "8.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.10.0.tgz#9c8218ed62f9a322df10ded7c34990f014df44f2" + integrity sha512-phuB3hoP7FFKbRXxjl+DRlQDuJqhpOnm5MmtROXyWi3uS/Xg2ZXqiQfcG2BJHiN4QKyzdOJi3NEn/qTnjUlkmQ== + dependencies: + "@eslint-community/regexpp" "^4.10.0" + "@typescript-eslint/scope-manager" "8.10.0" + "@typescript-eslint/type-utils" "8.10.0" + "@typescript-eslint/utils" "8.10.0" + "@typescript-eslint/visitor-keys" "8.10.0" + graphemer "^1.4.0" + ignore "^5.3.1" + natural-compare "^1.4.0" + ts-api-utils "^1.3.0" + +"@typescript-eslint/parser@^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0": + version "8.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.10.0.tgz#3cbe7206f5e42835878a74a76da533549f977662" + integrity sha512-E24l90SxuJhytWJ0pTQydFT46Nk0Z+bsLKo/L8rtQSL93rQ6byd1V/QbDpHUTdLPOMsBCcYXZweADNCfOCmOAg== + dependencies: + "@typescript-eslint/scope-manager" "8.10.0" + "@typescript-eslint/types" "8.10.0" + "@typescript-eslint/typescript-estree" "8.10.0" + "@typescript-eslint/visitor-keys" "8.10.0" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@8.10.0": + version "8.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.10.0.tgz#606ffe18314d7b5c2f118f2f02aaa2958107a19c" + integrity sha512-AgCaEjhfql9MDKjMUxWvH7HjLeBqMCBfIaBbzzIcBbQPZE7CPh1m6FF+L75NUMJFMLYhCywJXIDEMa3//1A0dw== + dependencies: + "@typescript-eslint/types" "8.10.0" + "@typescript-eslint/visitor-keys" "8.10.0" + +"@typescript-eslint/type-utils@8.10.0": + version "8.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.10.0.tgz#99f1d2e21f8c74703e7d9c4a67a87271eaf57597" + integrity sha512-PCpUOpyQSpxBn230yIcK+LeCQaXuxrgCm2Zk1S+PTIRJsEfU6nJ0TtwyH8pIwPK/vJoA+7TZtzyAJSGBz+s/dg== + dependencies: + "@typescript-eslint/typescript-estree" "8.10.0" + "@typescript-eslint/utils" "8.10.0" + debug "^4.3.4" + ts-api-utils "^1.3.0" + +"@typescript-eslint/types@8.10.0": + version "8.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.10.0.tgz#eb29c4bc2ed23489348c297469c76d28c38fb618" + integrity sha512-k/E48uzsfJCRRbGLapdZgrX52csmWJ2rcowwPvOZ8lwPUv3xW6CcFeJAXgx4uJm+Ge4+a4tFOkdYvSpxhRhg1w== + +"@typescript-eslint/typescript-estree@8.10.0": + version "8.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.10.0.tgz#36cc66e06c5f44d6781f95cb03b132e985273a33" + integrity sha512-3OE0nlcOHaMvQ8Xu5gAfME3/tWVDpb/HxtpUZ1WeOAksZ/h/gwrBzCklaGzwZT97/lBbbxJ16dMA98JMEngW4w== + dependencies: + "@typescript-eslint/types" "8.10.0" + "@typescript-eslint/visitor-keys" "8.10.0" + debug "^4.3.4" + fast-glob "^3.3.2" + is-glob "^4.0.3" + minimatch "^9.0.4" + semver "^7.6.0" + ts-api-utils "^1.3.0" + +"@typescript-eslint/utils@8.10.0": + version "8.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.10.0.tgz#d78d1ce3ea3d2a88a2593ebfb1c98490131d00bf" + integrity sha512-Oq4uZ7JFr9d1ZunE/QKy5egcDRXT/FrS2z/nlxzPua2VHFtmMvFNDvpq1m/hq0ra+T52aUezfcjGRIB7vNJF9w== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@typescript-eslint/scope-manager" "8.10.0" + "@typescript-eslint/types" "8.10.0" + "@typescript-eslint/typescript-estree" "8.10.0" + +"@typescript-eslint/visitor-keys@8.10.0": + version "8.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.10.0.tgz#7ce4c0c3b82140415c9cd9babe09e0000b4e9979" + integrity sha512-k8nekgqwr7FadWk548Lfph6V3r9OVqjzAIVskE7orMZR23cGJjAOVazsZSJW+ElyjfTM4wx/1g88Mi70DDtG9A== + dependencies: + "@typescript-eslint/types" "8.10.0" + eslint-visitor-keys "^3.4.3" + +"@ungap/structured-clone@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" + integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== + +"@use-gesture/core@10.3.1": + version "10.3.1" + resolved "https://registry.yarnpkg.com/@use-gesture/core/-/core-10.3.1.tgz#976c9421e905f0079d49822cfd5c2e56b808fc56" + integrity sha512-WcINiDt8WjqBdUXye25anHiNxPc0VOrlT8F6LLkU6cycrOGUDyY/yyFmsg3k8i5OLvv25llc0QC45GhR/C8llw== + +"@use-gesture/react@^10.2.22": + version "10.3.1" + resolved "https://registry.yarnpkg.com/@use-gesture/react/-/react-10.3.1.tgz#17a743a894d9bd9a0d1980c618f37f0164469867" + integrity sha512-Yy19y6O2GJq8f7CHf7L0nxL8bf4PZCPaVOCgJrusOeFHY1LvHgYXnmnXg6N5iwAnbgbZCDjo60SiM6IPJi9C5g== + dependencies: + "@use-gesture/core" "10.3.1" + +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn@^8.9.0: + version "8.12.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" + integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== + +ajv@^6.12.4: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-regex@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.1.0.tgz#95ec409c69619d6cb1b8b34f14b660ef28ebd654" + integrity sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA== + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + +ansi-styles@^6.1.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" + integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== + +any-promise@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" + integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== + +anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +arg@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c" + integrity sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg== + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +aria-query@~5.1.3: + version "5.1.3" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.1.3.tgz#19db27cd101152773631396f7a95a3b58c22c35e" + integrity sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ== + dependencies: + deep-equal "^2.0.5" + +array-buffer-byte-length@^1.0.0, array-buffer-byte-length@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz#1e5583ec16763540a27ae52eed99ff899223568f" + integrity sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg== + dependencies: + call-bind "^1.0.5" + is-array-buffer "^3.0.4" + +array-includes@^3.1.6, array-includes@^3.1.8: + version "3.1.8" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.8.tgz#5e370cbe172fdd5dd6530c1d4aadda25281ba97d" + integrity sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-object-atoms "^1.0.0" + get-intrinsic "^1.2.4" + is-string "^1.0.7" + +array.prototype.findlast@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz#3e4fbcb30a15a7f5bf64cf2faae22d139c2e4904" + integrity sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + es-shim-unscopables "^1.0.2" + +array.prototype.findlastindex@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz#8c35a755c72908719453f87145ca011e39334d0d" + integrity sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + es-shim-unscopables "^1.0.2" + +array.prototype.flat@^1.3.1, array.prototype.flat@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz#1476217df8cff17d72ee8f3ba06738db5b387d18" + integrity sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + es-shim-unscopables "^1.0.0" + +array.prototype.flatmap@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz#c9a7c6831db8e719d6ce639190146c24bbd3e527" + integrity sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + es-shim-unscopables "^1.0.0" + +array.prototype.tosorted@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz#fe954678ff53034e717ea3352a03f0b0b86f7ffc" + integrity sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.3" + es-errors "^1.3.0" + es-shim-unscopables "^1.0.2" + +arraybuffer.prototype.slice@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz#097972f4255e41bc3425e37dc3f6421cf9aefde6" + integrity sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A== + dependencies: + array-buffer-byte-length "^1.0.1" + call-bind "^1.0.5" + define-properties "^1.2.1" + es-abstract "^1.22.3" + es-errors "^1.2.1" + get-intrinsic "^1.2.3" + is-array-buffer "^3.0.4" + is-shared-array-buffer "^1.0.2" + +ast-types-flow@^0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.8.tgz#0a85e1c92695769ac13a428bb653e7538bea27d6" + integrity sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ== + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +autoprefixer@^10.4.13: + version "10.4.20" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.20.tgz#5caec14d43976ef42e32dcb4bd62878e96be5b3b" + integrity sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g== + dependencies: + browserslist "^4.23.3" + caniuse-lite "^1.0.30001646" + fraction.js "^4.3.7" + normalize-range "^0.1.2" + picocolors "^1.0.1" + postcss-value-parser "^4.2.0" + +available-typed-arrays@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" + integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ== + dependencies: + possible-typed-array-names "^1.0.0" + +axe-core@^4.10.0: + version "4.10.0" + resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.10.0.tgz#d9e56ab0147278272739a000880196cdfe113b59" + integrity sha512-Mr2ZakwQ7XUAjp7pAwQWRhhK8mQQ6JAaNWSjmjxil0R8BPioMtQsTLOolGYkji1rcL++3dCqZA3zWqpT+9Ew6g== + +axios@^1.6.1: + version "1.7.7" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.7.tgz#2f554296f9892a72ac8d8e4c5b79c14a91d0a47f" + integrity sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q== + dependencies: + follow-redirects "^1.15.6" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + +axobject-query@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-4.1.0.tgz#28768c76d0e3cff21bc62a9e2d0b6ac30042a1ee" + integrity sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +big.js@^5.2.2: + version "5.2.2" + resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" + integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== + +binary-extensions@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" + integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +braces@^3.0.3, braces@~3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +browserslist@^4.23.1, browserslist@^4.23.3: + version "4.24.0" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.0.tgz#a1325fe4bc80b64fda169629fc01b3d6cecd38d4" + integrity sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A== + dependencies: + caniuse-lite "^1.0.30001663" + electron-to-chromium "^1.5.28" + node-releases "^2.0.18" + update-browserslist-db "^1.1.0" + +busboy@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893" + integrity sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA== + dependencies: + streamsearch "^1.1.0" + +call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" + integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + set-function-length "^1.2.1" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camelcase-css@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" + integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== + +caniuse-lite@^1.0.30001579: + version "1.0.30001669" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001669.tgz#fda8f1d29a8bfdc42de0c170d7f34a9cf19ed7a3" + integrity sha512-DlWzFDJqstqtIVx1zeSpIMLjunf5SmwOw0N2Ck/QSQdS8PLS4+9HrLaYei4w8BIAL7IB/UEDu889d8vhCTPA0w== + +caniuse-lite@^1.0.30001646, caniuse-lite@^1.0.30001663: + version "1.0.30001664" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001664.tgz#d588d75c9682d3301956b05a3749652a80677df4" + integrity sha512-AmE7k4dXiNKQipgn7a2xg558IRqPN3jMQY/rOsbxDhrd0tyChwbITBfiwtnqz8bi2M5mIWbxAYBvk7W7QBUS2g== + +chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^4.0.0, chalk@^4.1.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chokidar@^3.5.3: + version "3.6.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +client-only@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1" + integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA== + +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + +clsx@^1.1.0, clsx@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" + integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + +color-name@^1.0.0, color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +color-string@^1.9.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4" + integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== + dependencies: + color-name "^1.0.0" + simple-swizzle "^0.2.2" + +color@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/color/-/color-4.2.3.tgz#d781ecb5e57224ee43ea9627560107c0e0c6463a" + integrity sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A== + dependencies: + color-convert "^2.0.1" + color-string "^1.9.0" + +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +commander@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" + integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +concurrently@^7.4.0: + version "7.6.0" + resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-7.6.0.tgz#531a6f5f30cf616f355a4afb8f8fcb2bba65a49a" + integrity sha512-BKtRgvcJGeZ4XttiDiNcFiRlxoAeZOseqUvyYRUp/Vtd+9p1ULmeoSqGsDA+2ivdeDFpqrJvGvmI+StKfKl5hw== + dependencies: + chalk "^4.1.0" + date-fns "^2.29.1" + lodash "^4.17.21" + rxjs "^7.0.0" + shell-quote "^1.7.3" + spawn-command "^0.0.2-1" + supports-color "^8.1.0" + tree-kill "^1.2.2" + yargs "^17.3.1" + +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + +cross-spawn@^7.0.0, cross-spawn@^7.0.2: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +cssesc@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== + +csstype@^3.0.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" + integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== + +damerau-levenshtein@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7" + integrity sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA== + +data-view-buffer@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.1.tgz#8ea6326efec17a2e42620696e671d7d5a8bc66b2" + integrity sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA== + dependencies: + call-bind "^1.0.6" + es-errors "^1.3.0" + is-data-view "^1.0.1" + +data-view-byte-length@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz#90721ca95ff280677eb793749fce1011347669e2" + integrity sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + is-data-view "^1.0.1" + +data-view-byte-offset@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz#5e0bbfb4828ed2d1b9b400cd8a7d119bca0ff18a" + integrity sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA== + dependencies: + call-bind "^1.0.6" + es-errors "^1.3.0" + is-data-view "^1.0.1" + +date-fns@^2.29.1: + version "2.30.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.30.0.tgz#f367e644839ff57894ec6ac480de40cae4b0f4d0" + integrity sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw== + dependencies: + "@babel/runtime" "^7.21.0" + +debug@^3.2.7: + version "3.2.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + +debug@^4.1.0, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.5: + version "4.3.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" + integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== + dependencies: + ms "^2.1.3" + +deep-equal@^2.0.5: + version "2.2.3" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-2.2.3.tgz#af89dafb23a396c7da3e862abc0be27cf51d56e1" + integrity sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA== + dependencies: + array-buffer-byte-length "^1.0.0" + call-bind "^1.0.5" + es-get-iterator "^1.1.3" + get-intrinsic "^1.2.2" + is-arguments "^1.1.1" + is-array-buffer "^3.0.2" + is-date-object "^1.0.5" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.2" + isarray "^2.0.5" + object-is "^1.1.5" + object-keys "^1.1.1" + object.assign "^4.1.4" + regexp.prototype.flags "^1.5.1" + side-channel "^1.0.4" + which-boxed-primitive "^1.0.2" + which-collection "^1.0.1" + which-typed-array "^1.1.13" + +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +define-data-property@^1.0.1, define-data-property@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" + integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + gopd "^1.0.1" + +define-properties@^1.1.3, define-properties@^1.2.0, define-properties@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" + integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== + dependencies: + define-data-property "^1.0.1" + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + +detect-libc@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.3.tgz#f0cd503b40f9939b894697d19ad50895e30cf700" + integrity sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw== + +didyoumean@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037" + integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw== + +dlv@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79" + integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA== + +doctrine@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" + integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== + dependencies: + esutils "^2.0.2" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + +electron-to-chromium@^1.5.28: + version "1.5.29" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.29.tgz#aa592a3caa95d07cc26a66563accf99fa573a1ee" + integrity sha512-PF8n2AlIhCKXQ+gTpiJi0VhcHDb69kYX4MtCiivctc2QD3XuNZ/XIOlbGzt7WAjjEev0TtaH6Cu3arZExm5DOw== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +emoji-regex@^9.2.2: + version "9.2.2" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + +emojis-list@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" + integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== + +enhanced-resolve@^5.15.0: + version "5.17.1" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz#67bfbbcc2f81d511be77d686a90267ef7f898a15" + integrity sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg== + dependencies: + graceful-fs "^4.2.4" + tapable "^2.2.0" + +es-abstract@^1.17.5, es-abstract@^1.22.1, es-abstract@^1.22.3, es-abstract@^1.23.0, es-abstract@^1.23.1, es-abstract@^1.23.2, es-abstract@^1.23.3: + version "1.23.3" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.3.tgz#8f0c5a35cd215312573c5a27c87dfd6c881a0aa0" + integrity sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A== + dependencies: + array-buffer-byte-length "^1.0.1" + arraybuffer.prototype.slice "^1.0.3" + available-typed-arrays "^1.0.7" + call-bind "^1.0.7" + data-view-buffer "^1.0.1" + data-view-byte-length "^1.0.1" + data-view-byte-offset "^1.0.0" + es-define-property "^1.0.0" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + es-set-tostringtag "^2.0.3" + es-to-primitive "^1.2.1" + function.prototype.name "^1.1.6" + get-intrinsic "^1.2.4" + get-symbol-description "^1.0.2" + globalthis "^1.0.3" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + has-proto "^1.0.3" + has-symbols "^1.0.3" + hasown "^2.0.2" + internal-slot "^1.0.7" + is-array-buffer "^3.0.4" + is-callable "^1.2.7" + is-data-view "^1.0.1" + is-negative-zero "^2.0.3" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.3" + is-string "^1.0.7" + is-typed-array "^1.1.13" + is-weakref "^1.0.2" + object-inspect "^1.13.1" + object-keys "^1.1.1" + object.assign "^4.1.5" + regexp.prototype.flags "^1.5.2" + safe-array-concat "^1.1.2" + safe-regex-test "^1.0.3" + string.prototype.trim "^1.2.9" + string.prototype.trimend "^1.0.8" + string.prototype.trimstart "^1.0.8" + typed-array-buffer "^1.0.2" + typed-array-byte-length "^1.0.1" + typed-array-byte-offset "^1.0.2" + typed-array-length "^1.0.6" + unbox-primitive "^1.0.2" + which-typed-array "^1.1.15" + +es-define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845" + integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== + dependencies: + get-intrinsic "^1.2.4" + +es-errors@^1.2.1, es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +es-get-iterator@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/es-get-iterator/-/es-get-iterator-1.1.3.tgz#3ef87523c5d464d41084b2c3c9c214f1199763d6" + integrity sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.3" + has-symbols "^1.0.3" + is-arguments "^1.1.1" + is-map "^2.0.2" + is-set "^2.0.2" + is-string "^1.0.7" + isarray "^2.0.5" + stop-iteration-iterator "^1.0.0" + +es-iterator-helpers@^1.0.19: + version "1.0.19" + resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.0.19.tgz#117003d0e5fec237b4b5c08aded722e0c6d50ca8" + integrity sha512-zoMwbCcH5hwUkKJkT8kDIBZSz9I6mVG//+lDCinLCGov4+r7NIy0ld8o03M0cJxl2spVf6ESYVS6/gpIfq1FFw== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.3" + es-errors "^1.3.0" + es-set-tostringtag "^2.0.3" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + globalthis "^1.0.3" + has-property-descriptors "^1.0.2" + has-proto "^1.0.3" + has-symbols "^1.0.3" + internal-slot "^1.0.7" + iterator.prototype "^1.1.2" + safe-array-concat "^1.1.2" + +es-object-atoms@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.0.0.tgz#ddb55cd47ac2e240701260bc2a8e31ecb643d941" + integrity sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw== + dependencies: + es-errors "^1.3.0" + +es-set-tostringtag@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz#8bb60f0a440c2e4281962428438d58545af39777" + integrity sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ== + dependencies: + get-intrinsic "^1.2.4" + has-tostringtag "^1.0.2" + hasown "^2.0.1" + +es-shim-unscopables@^1.0.0, es-shim-unscopables@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz#1f6942e71ecc7835ed1c8a83006d8771a63a3763" + integrity sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw== + dependencies: + hasown "^2.0.0" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + +escalade@^3.1.1, escalade@^3.1.2: + version "3.2.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-config-next@^15.0.1: + version "15.0.1" + resolved "https://registry.yarnpkg.com/eslint-config-next/-/eslint-config-next-15.0.1.tgz#5f49a01d312420cdbf1e87299396ef779ae99004" + integrity sha512-3cYCrgbH6GS/ufApza7XCKz92vtq4dAdYhx++rMFNlH2cAV+/GsAKkrr4+bohYOACmzG2nAOR+uWprKC1Uld6A== + dependencies: + "@next/eslint-plugin-next" "15.0.1" + "@rushstack/eslint-patch" "^1.10.3" + "@typescript-eslint/eslint-plugin" "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0" + "@typescript-eslint/parser" "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0" + eslint-import-resolver-node "^0.3.6" + eslint-import-resolver-typescript "^3.5.2" + eslint-plugin-import "^2.31.0" + eslint-plugin-jsx-a11y "^6.10.0" + eslint-plugin-react "^7.35.0" + eslint-plugin-react-hooks "^5.0.0" + +eslint-import-resolver-node@^0.3.6, eslint-import-resolver-node@^0.3.9: + version "0.3.9" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz#d4eaac52b8a2e7c3cd1903eb00f7e053356118ac" + integrity sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g== + dependencies: + debug "^3.2.7" + is-core-module "^2.13.0" + resolve "^1.22.4" + +eslint-import-resolver-typescript@^3.5.2: + version "3.6.3" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.6.3.tgz#bb8e388f6afc0f940ce5d2c5fd4a3d147f038d9e" + integrity sha512-ud9aw4szY9cCT1EWWdGv1L1XR6hh2PaRWif0j2QjQ0pgTY/69iw+W0Z4qZv5wHahOl8isEr+k/JnyAqNQkLkIA== + dependencies: + "@nolyfill/is-core-module" "1.0.39" + debug "^4.3.5" + enhanced-resolve "^5.15.0" + eslint-module-utils "^2.8.1" + fast-glob "^3.3.2" + get-tsconfig "^4.7.5" + is-bun-module "^1.0.2" + is-glob "^4.0.3" + +eslint-module-utils@^2.12.0, eslint-module-utils@^2.8.1: + version "2.12.0" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz#fe4cfb948d61f49203d7b08871982b65b9af0b0b" + integrity sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg== + dependencies: + debug "^3.2.7" + +eslint-plugin-import@^2.31.0: + version "2.31.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz#310ce7e720ca1d9c0bb3f69adfd1c6bdd7d9e0e7" + integrity sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A== + dependencies: + "@rtsao/scc" "^1.1.0" + array-includes "^3.1.8" + array.prototype.findlastindex "^1.2.5" + array.prototype.flat "^1.3.2" + array.prototype.flatmap "^1.3.2" + debug "^3.2.7" + doctrine "^2.1.0" + eslint-import-resolver-node "^0.3.9" + eslint-module-utils "^2.12.0" + hasown "^2.0.2" + is-core-module "^2.15.1" + is-glob "^4.0.3" + minimatch "^3.1.2" + object.fromentries "^2.0.8" + object.groupby "^1.0.3" + object.values "^1.2.0" + semver "^6.3.1" + string.prototype.trimend "^1.0.8" + tsconfig-paths "^3.15.0" + +eslint-plugin-jsx-a11y@^6.10.0: + version "6.10.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.0.tgz#36fb9dead91cafd085ddbe3829602fb10ef28339" + integrity sha512-ySOHvXX8eSN6zz8Bywacm7CvGNhUtdjvqfQDVe6020TUK34Cywkw7m0KsCCk1Qtm9G1FayfTN1/7mMYnYO2Bhg== + dependencies: + aria-query "~5.1.3" + array-includes "^3.1.8" + array.prototype.flatmap "^1.3.2" + ast-types-flow "^0.0.8" + axe-core "^4.10.0" + axobject-query "^4.1.0" + damerau-levenshtein "^1.0.8" + emoji-regex "^9.2.2" + es-iterator-helpers "^1.0.19" + hasown "^2.0.2" + jsx-ast-utils "^3.3.5" + language-tags "^1.0.9" + minimatch "^3.1.2" + object.fromentries "^2.0.8" + safe-regex-test "^1.0.3" + string.prototype.includes "^2.0.0" + +eslint-plugin-react-hooks@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.0.0.tgz#72e2eefbac4b694f5324154619fee44f5f60f101" + integrity sha512-hIOwI+5hYGpJEc4uPRmz2ulCjAGD/N13Lukkh8cLV0i2IRk/bdZDYjgLVHj+U9Z704kLIdIO6iueGvxNur0sgw== + +eslint-plugin-react@^7.35.0: + version "7.37.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.37.1.tgz#56493d7d69174d0d828bc83afeffe96903fdadbd" + integrity sha512-xwTnwDqzbDRA8uJ7BMxPs/EXRB3i8ZfnOIp8BsxEQkT0nHPp+WWceqGgo6rKb9ctNi8GJLDT4Go5HAWELa/WMg== + dependencies: + array-includes "^3.1.8" + array.prototype.findlast "^1.2.5" + array.prototype.flatmap "^1.3.2" + array.prototype.tosorted "^1.1.4" + doctrine "^2.1.0" + es-iterator-helpers "^1.0.19" + estraverse "^5.3.0" + hasown "^2.0.2" + jsx-ast-utils "^2.4.1 || ^3.0.0" + minimatch "^3.1.2" + object.entries "^1.1.8" + object.fromentries "^2.0.8" + object.values "^1.2.0" + prop-types "^15.8.1" + resolve "^2.0.0-next.5" + semver "^6.3.1" + string.prototype.matchall "^4.0.11" + string.prototype.repeat "^1.0.0" + +eslint-scope@^7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" + integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +eslint@^8.28.0: + version "8.57.1" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.1.tgz#7df109654aba7e3bbe5c8eae533c5e461d3c6ca9" + integrity sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.6.1" + "@eslint/eslintrc" "^2.1.4" + "@eslint/js" "8.57.1" + "@humanwhocodes/config-array" "^0.13.0" + "@humanwhocodes/module-importer" "^1.0.1" + "@nodelib/fs.walk" "^1.2.8" + "@ungap/structured-clone" "^1.2.0" + ajv "^6.12.4" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.3.2" + doctrine "^3.0.0" + escape-string-regexp "^4.0.0" + eslint-scope "^7.2.2" + eslint-visitor-keys "^3.4.3" + espree "^9.6.1" + esquery "^1.4.2" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + find-up "^5.0.0" + glob-parent "^6.0.2" + globals "^13.19.0" + graphemer "^1.4.0" + ignore "^5.2.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + is-path-inside "^3.0.3" + js-yaml "^4.1.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.3" + strip-ansi "^6.0.1" + text-table "^0.2.0" + +espree@^9.6.0, espree@^9.6.1: + version "9.6.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" + integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== + dependencies: + acorn "^8.9.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.4.1" + +esquery@^1.4.2: + version "1.6.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" + integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-glob@3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.1.tgz#784b4e897340f3dbbef17413b3f11acf03c874c4" + integrity sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fast-glob@^3.3.0, fast-glob@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" + integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +fastq@^1.6.0: + version "1.17.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" + integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w== + dependencies: + reusify "^1.0.4" + +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +flat-cache@^3.0.4: + version "3.2.0" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee" + integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== + dependencies: + flatted "^3.2.9" + keyv "^4.5.3" + rimraf "^3.0.2" + +flatted@^3.2.9: + version "3.3.1" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a" + integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== + +follow-redirects@^1.15.6: + version "1.15.9" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1" + integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ== + +for-each@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" + integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== + dependencies: + is-callable "^1.1.3" + +foreground-child@^3.1.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.0.tgz#0ac8644c06e431439f8561db8ecf29a7b5519c77" + integrity sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg== + dependencies: + cross-spawn "^7.0.0" + signal-exit "^4.0.1" + +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + +fraction.js@^4.3.7: + version "4.3.7" + resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7" + integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + +fsevents@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +function.prototype.name@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.6.tgz#cdf315b7d90ee77a4c6ee216c3c3362da07533fd" + integrity sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + functions-have-names "^1.2.3" + +functions-have-names@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" + integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.2, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" + integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + has-proto "^1.0.1" + has-symbols "^1.0.3" + hasown "^2.0.0" + +get-symbol-description@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.2.tgz#533744d5aa20aca4e079c8e5daf7fd44202821f5" + integrity sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg== + dependencies: + call-bind "^1.0.5" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + +get-tsconfig@^4.7.5: + version "4.8.1" + resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.8.1.tgz#8995eb391ae6e1638d251118c7b56de7eb425471" + integrity sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg== + dependencies: + resolve-pkg-maps "^1.0.0" + +glob-parent@^5.1.2, glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob@^10.3.10: + version "10.4.5" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956" + integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg== + dependencies: + foreground-child "^3.1.0" + jackspeak "^3.1.2" + minimatch "^9.0.4" + minipass "^7.1.2" + package-json-from-dist "^1.0.0" + path-scurry "^1.11.1" + +glob@^7.1.3: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globals@^13.19.0: + version "13.24.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" + integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== + dependencies: + type-fest "^0.20.2" + +globalthis@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.4.tgz#7430ed3a975d97bfb59bcce41f5cabbafa651236" + integrity sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ== + dependencies: + define-properties "^1.2.1" + gopd "^1.0.1" + +goober@^2.0.33: + version "2.1.14" + resolved "https://registry.yarnpkg.com/goober/-/goober-2.1.14.tgz#4a5c94fc34dc086a8e6035360ae1800005135acd" + integrity sha512-4UpC0NdGyAFqLNPnhCT2iHpza2q+RAY3GV85a/mRPdzyPQMsj0KmMMuetdIkzWRbJ+Hgau1EZztq8ImmiMGhsg== + +gopd@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" + integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== + dependencies: + get-intrinsic "^1.1.3" + +graceful-fs@^4.2.4: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + +has-bigints@^1.0.1, has-bigints@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" + integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" + integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== + dependencies: + es-define-property "^1.0.0" + +has-proto@^1.0.1, has-proto@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd" + integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q== + +has-symbols@^1.0.2, has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + +has-tostringtag@^1.0.0, has-tostringtag@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" + integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== + dependencies: + has-symbols "^1.0.3" + +hasown@^2.0.0, hasown@^2.0.1, hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +hermes-eslint@^0.25.0: + version "0.25.0" + resolved "https://registry.yarnpkg.com/hermes-eslint/-/hermes-eslint-0.25.0.tgz#beec5f0d9e9e9bdef9e4a420a79038ca7fe84143" + integrity sha512-D9rdrqt7dudZHI5AJKS+1vXBbxxR6Wj9J1JI7eYowYCbXUIvHclsWFy8gSuRmug2V6HSYpsiyPwP3kQs/Q/Y8w== + dependencies: + esrecurse "^4.3.0" + hermes-estree "0.25.0" + hermes-parser "0.25.0" + +hermes-estree@0.25.0: + version "0.25.0" + resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.25.0.tgz#fd926ebf3d0d3441a934f19ef3d3d3d4145b1d71" + integrity sha512-xjILoUIyOpLoOHqj8UJs/HNYQ279IfLKTTv9nmXKNT2+QKT/TQF9AyQFrRMo+3xwZoO7k4azocYpCzA1cSvBDg== + +hermes-parser@0.25.0, hermes-parser@^0.25.0: + version "0.25.0" + resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.25.0.tgz#634934533a956e392ae0988421e4b0315e30351e" + integrity sha512-CeAdhgMfbZcrYh+HHKVKsj7VNhOTr0jiLFlcVVoRORbZ/Nr4J90WjEq2CZoahgH15/DYY/VBhuLqpIzJqfdBEQ== + dependencies: + hermes-estree "0.25.0" + +ignore@^5.2.0, ignore@^5.3.1: + version "5.3.2" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" + integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== + +import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +internal-slot@^1.0.4, internal-slot@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.7.tgz#c06dcca3ed874249881007b0a5523b172a190802" + integrity sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g== + dependencies: + es-errors "^1.3.0" + hasown "^2.0.0" + side-channel "^1.0.4" + +invariant@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" + integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== + dependencies: + loose-envify "^1.0.0" + +is-arguments@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" + integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-array-buffer@^3.0.2, is-array-buffer@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.4.tgz#7a1f92b3d61edd2bc65d24f130530ea93d7fae98" + integrity sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.2.1" + +is-arrayish@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" + integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== + +is-async-function@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-async-function/-/is-async-function-2.0.0.tgz#8e4418efd3e5d3a6ebb0164c05ef5afb69aa9646" + integrity sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA== + dependencies: + has-tostringtag "^1.0.0" + +is-bigint@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" + integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== + dependencies: + has-bigints "^1.0.1" + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-boolean-object@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" + integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-bun-module@^1.0.2: + version "1.2.1" + resolved "https://registry.yarnpkg.com/is-bun-module/-/is-bun-module-1.2.1.tgz#495e706f42e29f086fd5fe1ac3c51f106062b9fc" + integrity sha512-AmidtEM6D6NmUiLOvvU7+IePxjEjOzra2h0pSrsfSAcXwl/83zLLXDByafUJy9k/rKK0pvXMLdwKwGHlX2Ke6Q== + dependencies: + semver "^7.6.3" + +is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" + integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== + +is-core-module@^2.13.0, is-core-module@^2.15.1: + version "2.15.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.15.1.tgz#a7363a25bee942fefab0de13bf6aa372c82dcc37" + integrity sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ== + dependencies: + hasown "^2.0.2" + +is-data-view@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-data-view/-/is-data-view-1.0.1.tgz#4b4d3a511b70f3dc26d42c03ca9ca515d847759f" + integrity sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w== + dependencies: + is-typed-array "^1.1.13" + +is-date-object@^1.0.1, is-date-object@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" + integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== + dependencies: + has-tostringtag "^1.0.0" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-finalizationregistry@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz#c8749b65f17c133313e661b1289b95ad3dbd62e6" + integrity sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw== + dependencies: + call-bind "^1.0.2" + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-generator-function@^1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72" + integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A== + dependencies: + has-tostringtag "^1.0.0" + +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-map@^2.0.2, is-map@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.3.tgz#ede96b7fe1e270b3c4465e3a465658764926d62e" + integrity sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw== + +is-negative-zero@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz#ced903a027aca6381b777a5743069d7376a49747" + integrity sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw== + +is-number-object@^1.0.4: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc" + integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ== + dependencies: + has-tostringtag "^1.0.0" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-path-inside@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + +is-regex@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" + integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-set@^2.0.2, is-set@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.3.tgz#8ab209ea424608141372ded6e0cb200ef1d9d01d" + integrity sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg== + +is-shared-array-buffer@^1.0.2, is-shared-array-buffer@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz#1237f1cba059cdb62431d378dcc37d9680181688" + integrity sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg== + dependencies: + call-bind "^1.0.7" + +is-string@^1.0.5, is-string@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" + integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== + dependencies: + has-tostringtag "^1.0.0" + +is-symbol@^1.0.2, is-symbol@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" + integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== + dependencies: + has-symbols "^1.0.2" + +is-typed-array@^1.1.13: + version "1.1.13" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.13.tgz#d6c5ca56df62334959322d7d7dd1cca50debe229" + integrity sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw== + dependencies: + which-typed-array "^1.1.14" + +is-weakmap@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.2.tgz#bf72615d649dfe5f699079c54b83e47d1ae19cfd" + integrity sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w== + +is-weakref@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" + integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== + dependencies: + call-bind "^1.0.2" + +is-weakset@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.3.tgz#e801519df8c0c43e12ff2834eead84ec9e624007" + integrity sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ== + dependencies: + call-bind "^1.0.7" + get-intrinsic "^1.2.4" + +isarray@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" + integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +iterator.prototype@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/iterator.prototype/-/iterator.prototype-1.1.2.tgz#5e29c8924f01916cb9335f1ff80619dcff22b0c0" + integrity sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w== + dependencies: + define-properties "^1.2.1" + get-intrinsic "^1.2.1" + has-symbols "^1.0.3" + reflect.getprototypeof "^1.0.4" + set-function-name "^2.0.1" + +jackspeak@^3.1.2: + version "3.4.3" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a" + integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw== + dependencies: + "@isaacs/cliui" "^8.0.2" + optionalDependencies: + "@pkgjs/parseargs" "^0.11.0" + +jiti@^1.21.0: + version "1.21.6" + resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.6.tgz#6c7f7398dd4b3142767f9a168af2f317a428d268" + integrity sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w== + +joi@^17.11.0: + version "17.13.3" + resolved "https://registry.yarnpkg.com/joi/-/joi-17.13.3.tgz#0f5cc1169c999b30d344366d384b12d92558bcec" + integrity sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA== + dependencies: + "@hapi/hoek" "^9.3.0" + "@hapi/topo" "^5.1.0" + "@sideway/address" "^4.1.5" + "@sideway/formula" "^3.0.1" + "@sideway/pinpoint" "^2.0.0" + +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +json5@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" + integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA== + dependencies: + minimist "^1.2.0" + +json5@^2.1.2, json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.3.5: + version "3.3.5" + resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz#4766bd05a8e2a11af222becd19e15575e52a853a" + integrity sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ== + dependencies: + array-includes "^3.1.6" + array.prototype.flat "^1.3.1" + object.assign "^4.1.4" + object.values "^1.1.6" + +keyv@^4.5.3: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + +language-subtag-registry@^0.3.20: + version "0.3.23" + resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz#23529e04d9e3b74679d70142df3fd2eb6ec572e7" + integrity sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ== + +language-tags@^1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/language-tags/-/language-tags-1.0.9.tgz#1ffdcd0ec0fafb4b1be7f8b11f306ad0f9c08777" + integrity sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA== + dependencies: + language-subtag-registry "^0.3.20" + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +lilconfig@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52" + integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ== + +lilconfig@^3.0.0: + version "3.1.2" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.2.tgz#e4a7c3cb549e3a606c8dcc32e5ae1005e62c05cb" + integrity sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow== + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +loader-utils@^2.0.2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c" + integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw== + dependencies: + big.js "^5.2.2" + emojis-list "^3.0.0" + json5 "^2.1.2" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +loose-envify@^1.0.0, loose-envify@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +lru-cache@^10.2.0: + version "10.4.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" + integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +lz-string@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941" + integrity sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ== + +merge2@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromatch@^4.0.4, micromatch@^4.0.5: + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== + dependencies: + braces "^3.0.3" + picomatch "^2.3.1" + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^9.0.4: + version "9.0.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" + integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== + dependencies: + brace-expansion "^2.0.1" + +minimist@^1.2.0, minimist@^1.2.6, minimist@^1.2.8: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" + integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== + +monaco-editor-webpack-plugin@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/monaco-editor-webpack-plugin/-/monaco-editor-webpack-plugin-7.1.0.tgz#16f265c2b5dbb5fe08681b6b3b7d00d3c5b2ee97" + integrity sha512-ZjnGINHN963JQkFqjjcBtn1XBtUATDZBMgNQhDQwd78w2ukRhFXAPNgWuacaQiDZsUr4h1rWv5Mv6eriKuOSzA== + dependencies: + loader-utils "^2.0.2" + +monaco-editor@^0.52.0: + version "0.52.0" + resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.52.0.tgz#d47c02b191eae208d68878d679b3ee7456031be7" + integrity sha512-OeWhNpABLCeTqubfqLMXGsqf6OmPU6pHM85kF3dhy6kq5hnhuVS1p3VrEW/XhWHc71P2tHyS5JFySD8mgs1crw== + +ms@^2.1.1, ms@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +mz@^2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" + integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== + dependencies: + any-promise "^1.0.0" + object-assign "^4.0.1" + thenify-all "^1.0.0" + +nanoid@^3.3.6, nanoid@^3.3.7: + version "3.3.7" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" + integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + +next@^15.0.1: + version "15.0.1" + resolved "https://registry.yarnpkg.com/next/-/next-15.0.1.tgz#a0e8eda35d803cb7f8092b2a2eb9d072e22bf21d" + integrity sha512-PSkFkr/w7UnFWm+EP8y/QpHrJXMqpZzAXpergB/EqLPOh4SGPJXv1wj4mslr2hUZBAS9pX7/9YLIdxTv6fwytw== + dependencies: + "@next/env" "15.0.1" + "@swc/counter" "0.1.3" + "@swc/helpers" "0.5.13" + busboy "1.6.0" + caniuse-lite "^1.0.30001579" + postcss "8.4.31" + styled-jsx "5.1.6" + optionalDependencies: + "@next/swc-darwin-arm64" "15.0.1" + "@next/swc-darwin-x64" "15.0.1" + "@next/swc-linux-arm64-gnu" "15.0.1" + "@next/swc-linux-arm64-musl" "15.0.1" + "@next/swc-linux-x64-gnu" "15.0.1" + "@next/swc-linux-x64-musl" "15.0.1" + "@next/swc-win32-arm64-msvc" "15.0.1" + "@next/swc-win32-x64-msvc" "15.0.1" + sharp "^0.33.5" + +node-releases@^2.0.18: + version "2.0.18" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f" + integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g== + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +normalize-range@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" + integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA== + +notistack@^3.0.0-alpha.7: + version "3.0.1" + resolved "https://registry.yarnpkg.com/notistack/-/notistack-3.0.1.tgz#daf59888ab7e2c30a1fa8f71f9cba2978773236e" + integrity sha512-ntVZXXgSQH5WYfyU+3HfcXuKaapzAJ8fBLQ/G618rn3yvSzEbnOB8ZSOwhX+dAORy/lw+GC2N061JA0+gYWTVA== + dependencies: + clsx "^1.1.0" + goober "^2.0.33" + +object-assign@^4.0.1, object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +object-hash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9" + integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw== + +object-inspect@^1.13.1: + version "1.13.2" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.2.tgz#dea0088467fb991e67af4058147a24824a3043ff" + integrity sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g== + +object-is@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.6.tgz#1a6a53aed2dd8f7e6775ff870bea58545956ab07" + integrity sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + +object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@^4.1.4, object.assign@^4.1.5: + version "4.1.5" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.5.tgz#3a833f9ab7fdb80fc9e8d2300c803d216d8fdbb0" + integrity sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ== + dependencies: + call-bind "^1.0.5" + define-properties "^1.2.1" + has-symbols "^1.0.3" + object-keys "^1.1.1" + +object.entries@^1.1.8: + version "1.1.8" + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.8.tgz#bffe6f282e01f4d17807204a24f8edd823599c41" + integrity sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + +object.fromentries@^2.0.8: + version "2.0.8" + resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.8.tgz#f7195d8a9b97bd95cbc1999ea939ecd1a2b00c65" + integrity sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-object-atoms "^1.0.0" + +object.groupby@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/object.groupby/-/object.groupby-1.0.3.tgz#9b125c36238129f6f7b61954a1e7176148d5002e" + integrity sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + +object.values@^1.1.6, object.values@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.2.0.tgz#65405a9d92cee68ac2d303002e0b8470a4d9ab1b" + integrity sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +optionator@^0.9.3: + version "0.9.4" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" + integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.5" + +p-limit@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +package-json-from-dist@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505" + integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw== + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-scurry@^1.11.1: + version "1.11.1" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2" + integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== + dependencies: + lru-cache "^10.2.0" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + +picocolors@^1.0.0, picocolors@^1.0.1, picocolors@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.0.tgz#5358b76a78cde483ba5cef6a9dc9671440b27d59" + integrity sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw== + +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pify@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== + +pirates@^4.0.1: + version "4.0.6" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" + integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg== + +playwright-core@1.47.2: + version "1.47.2" + resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.47.2.tgz#7858da9377fa32a08be46ba47d7523dbd9460a4e" + integrity sha512-3JvMfF+9LJfe16l7AbSmU555PaTl2tPyQsVInqm3id16pdDfvZ8TTZ/pyzmkbDrZTQefyzU7AIHlZqQnxpqHVQ== + +playwright@1.47.2: + version "1.47.2" + resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.47.2.tgz#155688aa06491ee21fb3e7555b748b525f86eb20" + integrity sha512-nx1cLMmQWqmA3UsnjaaokyoUpdVaaDhJhMoxX2qj3McpjnsqFHs516QAKYhqHAgOP+oCFTEOCOAaD1RgD/RQfA== + dependencies: + playwright-core "1.47.2" + optionalDependencies: + fsevents "2.3.2" + +possible-typed-array-names@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f" + integrity sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q== + +postcss-import@^15.1.0: + version "15.1.0" + resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-15.1.0.tgz#41c64ed8cc0e23735a9698b3249ffdbf704adc70" + integrity sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew== + dependencies: + postcss-value-parser "^4.0.0" + read-cache "^1.0.0" + resolve "^1.1.7" + +postcss-js@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-4.0.1.tgz#61598186f3703bab052f1c4f7d805f3991bee9d2" + integrity sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw== + dependencies: + camelcase-css "^2.0.1" + +postcss-load-config@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-4.0.2.tgz#7159dcf626118d33e299f485d6afe4aff7c4a3e3" + integrity sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ== + dependencies: + lilconfig "^3.0.0" + yaml "^2.3.4" + +postcss-nested@^6.0.1: + version "6.2.0" + resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-6.2.0.tgz#4c2d22ab5f20b9cb61e2c5c5915950784d068131" + integrity sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ== + dependencies: + postcss-selector-parser "^6.1.1" + +postcss-selector-parser@^6.0.11, postcss-selector-parser@^6.1.1: + version "6.1.2" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz#27ecb41fb0e3b6ba7a1ec84fff347f734c7929de" + integrity sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg== + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + +postcss-value-parser@^4.0.0, postcss-value-parser@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" + integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== + +postcss@8.4.31: + version "8.4.31" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d" + integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ== + dependencies: + nanoid "^3.3.6" + picocolors "^1.0.0" + source-map-js "^1.0.2" + +postcss@^8.4.23, postcss@^8.4.31: + version "8.4.47" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.47.tgz#5bf6c9a010f3e724c503bf03ef7947dcb0fea365" + integrity sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ== + dependencies: + nanoid "^3.3.7" + picocolors "^1.1.0" + source-map-js "^1.2.1" + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +prettier@^3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.3.tgz#30c54fe0be0d8d12e6ae61dbb10109ea00d53105" + integrity sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew== + +pretty-format@^29.3.1: + version "29.7.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" + integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== + dependencies: + "@jest/schemas" "^29.6.3" + ansi-styles "^5.0.0" + react-is "^18.0.0" + +prop-types@^15.8.1: + version "15.8.1" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" + integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.13.1" + +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + +punycode@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +re-resizable@^6.9.16: + version "6.10.0" + resolved "https://registry.yarnpkg.com/re-resizable/-/re-resizable-6.10.0.tgz#d684a096ab438f1a93f59ad3a580a206b0ce31ee" + integrity sha512-hysSK0xmA5nz24HBVztlk4yCqCLCvS32E6ZpWxVKop9x3tqCa4yAj1++facrmkOf62JsJHjmjABdKxXofYioCw== + +react-dom@19.0.0-rc-77b637d6-20241016: + version "19.0.0-rc-77b637d6-20241016" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.0.0-rc-77b637d6-20241016.tgz#71afcba4abbd81a73e85086029202423cf85355e" + integrity sha512-xp5LvY+O6uvg0fNbSMyMXe0kbgzw6qn0mbqrdXStm4LBpFeMswLZ+XSNr+eJ0HyIiWrCw0rrXaVdqOxc9wtdKA== + dependencies: + scheduler "0.25.0-rc-77b637d6-20241016" + +react-is@^16.13.1: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + +react-is@^18.0.0: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" + integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== + +react@19.0.0-rc-77b637d6-20241016: + version "19.0.0-rc-77b637d6-20241016" + resolved "https://registry.yarnpkg.com/react/-/react-19.0.0-rc-77b637d6-20241016.tgz#9e20f116d0195979f192537e00a0fa1687680319" + integrity sha512-9A+i+PGSH/P4MezU4w38K9cbJuy0pzsXoPjPWIv6TQGCFmc5qCzC+8yce8dzfSEF1KJgCF2CLc5qtq/ePfiVqg== + +read-cache@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774" + integrity sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA== + dependencies: + pify "^2.3.0" + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +reflect.getprototypeof@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz#3ab04c32a8390b770712b7a8633972702d278859" + integrity sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.1" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + globalthis "^1.0.3" + which-builtin-type "^1.1.3" + +regenerator-runtime@^0.14.0: + version "0.14.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" + integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== + +regexp.prototype.flags@^1.5.1, regexp.prototype.flags@^1.5.2: + version "1.5.2" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz#138f644a3350f981a858c44f6bb1a61ff59be334" + integrity sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw== + dependencies: + call-bind "^1.0.6" + define-properties "^1.2.1" + es-errors "^1.3.0" + set-function-name "^2.0.1" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve-pkg-maps@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz#616b3dc2c57056b5588c31cdf4b3d64db133720f" + integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw== + +resolve@^1.1.7, resolve@^1.22.2, resolve@^1.22.4: + version "1.22.8" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" + integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +resolve@^2.0.0-next.5: + version "2.0.0-next.5" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.5.tgz#6b0ec3107e671e52b68cd068ef327173b90dc03c" + integrity sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +rxjs@^7.0.0, rxjs@^7.8.1: + version "7.8.1" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" + integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== + dependencies: + tslib "^2.1.0" + +safe-array-concat@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.2.tgz#81d77ee0c4e8b863635227c721278dd524c20edb" + integrity sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q== + dependencies: + call-bind "^1.0.7" + get-intrinsic "^1.2.4" + has-symbols "^1.0.3" + isarray "^2.0.5" + +safe-regex-test@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.3.tgz#a5b4c0f06e0ab50ea2c395c14d8371232924c377" + integrity sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw== + dependencies: + call-bind "^1.0.6" + es-errors "^1.3.0" + is-regex "^1.1.4" + +scheduler@0.25.0-rc-77b637d6-20241016: + version "0.25.0-rc-77b637d6-20241016" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.25.0-rc-77b637d6-20241016.tgz#ab8f8d1cccc9668946caaa1103acdcdb5c871122" + integrity sha512-R5NTrZXJaW4Dj2jHmad2MTehpFq4yUQOxRKDNV7clP1q4Pz6RtUIcofdPnGUWM0krlJAw8DHd/4jT41pFK4iEg== + +semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +semver@^7.6.0, semver@^7.6.3: + version "7.6.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" + integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== + +set-function-length@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" + integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + +set-function-name@^2.0.1, set-function-name@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.2.tgz#16a705c5a0dc2f5e638ca96d8a8cd4e1c2b90985" + integrity sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + functions-have-names "^1.2.3" + has-property-descriptors "^1.0.2" + +sharp@^0.33.5: + version "0.33.5" + resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.33.5.tgz#13e0e4130cc309d6a9497596715240b2ec0c594e" + integrity sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw== + dependencies: + color "^4.2.3" + detect-libc "^2.0.3" + semver "^7.6.3" + optionalDependencies: + "@img/sharp-darwin-arm64" "0.33.5" + "@img/sharp-darwin-x64" "0.33.5" + "@img/sharp-libvips-darwin-arm64" "1.0.4" + "@img/sharp-libvips-darwin-x64" "1.0.4" + "@img/sharp-libvips-linux-arm" "1.0.5" + "@img/sharp-libvips-linux-arm64" "1.0.4" + "@img/sharp-libvips-linux-s390x" "1.0.4" + "@img/sharp-libvips-linux-x64" "1.0.4" + "@img/sharp-libvips-linuxmusl-arm64" "1.0.4" + "@img/sharp-libvips-linuxmusl-x64" "1.0.4" + "@img/sharp-linux-arm" "0.33.5" + "@img/sharp-linux-arm64" "0.33.5" + "@img/sharp-linux-s390x" "0.33.5" + "@img/sharp-linux-x64" "0.33.5" + "@img/sharp-linuxmusl-arm64" "0.33.5" + "@img/sharp-linuxmusl-x64" "0.33.5" + "@img/sharp-wasm32" "0.33.5" + "@img/sharp-win32-ia32" "0.33.5" + "@img/sharp-win32-x64" "0.33.5" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +shell-quote@^1.7.3: + version "1.8.1" + resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.1.tgz#6dbf4db75515ad5bac63b4f1894c3a154c766680" + integrity sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA== + +side-channel@^1.0.4, side-channel@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" + integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + object-inspect "^1.13.1" + +signal-exit@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + +simple-swizzle@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" + integrity sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg== + dependencies: + is-arrayish "^0.3.1" + +source-map-js@^1.0.2, source-map-js@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" + integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== + +spawn-command@^0.0.2-1: + version "0.0.2" + resolved "https://registry.yarnpkg.com/spawn-command/-/spawn-command-0.0.2.tgz#9544e1a43ca045f8531aac1a48cb29bdae62338e" + integrity sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ== + +state-local@^1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/state-local/-/state-local-1.0.7.tgz#da50211d07f05748d53009bee46307a37db386d5" + integrity sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w== + +stop-iteration-iterator@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz#6a60be0b4ee757d1ed5254858ec66b10c49285e4" + integrity sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ== + dependencies: + internal-slot "^1.0.4" + +streamsearch@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764" + integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== + +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^5.0.1, string-width@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" + +string.prototype.includes@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/string.prototype.includes/-/string.prototype.includes-2.0.0.tgz#8986d57aee66d5460c144620a6d873778ad7289f" + integrity sha512-E34CkBgyeqNDcrbU76cDjL5JLcVrtSdYq0MEh/B10r17pRP4ciHLwTgnuLV8Ay6cgEMLkcBkFCKyFZ43YldYzg== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" + +string.prototype.matchall@^4.0.11: + version "4.0.11" + resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz#1092a72c59268d2abaad76582dccc687c0297e0a" + integrity sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-symbols "^1.0.3" + internal-slot "^1.0.7" + regexp.prototype.flags "^1.5.2" + set-function-name "^2.0.2" + side-channel "^1.0.6" + +string.prototype.repeat@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz#e90872ee0308b29435aa26275f6e1b762daee01a" + integrity sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" + +string.prototype.trim@^1.2.9: + version "1.2.9" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz#b6fa326d72d2c78b6df02f7759c73f8f6274faa4" + integrity sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.0" + es-object-atoms "^1.0.0" + +string.prototype.trimend@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz#3651b8513719e8a9f48de7f2f77640b26652b229" + integrity sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + +string.prototype.trimstart@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz#7ee834dda8c7c17eff3118472bb35bfedaa34dde" + integrity sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^7.0.1: + version "7.1.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" + integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== + dependencies: + ansi-regex "^6.0.1" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== + +strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +styled-jsx@5.1.6: + version "5.1.6" + resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-5.1.6.tgz#83b90c077e6c6a80f7f5e8781d0f311b2fe41499" + integrity sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA== + dependencies: + client-only "0.0.1" + +sucrase@^3.32.0: + version "3.35.0" + resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.35.0.tgz#57f17a3d7e19b36d8995f06679d121be914ae263" + integrity sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA== + dependencies: + "@jridgewell/gen-mapping" "^0.3.2" + commander "^4.0.0" + glob "^10.3.10" + lines-and-columns "^1.1.6" + mz "^2.7.0" + pirates "^4.0.1" + ts-interface-checker "^0.1.9" + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-color@^8.1.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +tailwindcss@^3.2.4: + version "3.4.13" + resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.4.13.tgz#3d11e5510660f99df4f1bfb2d78434666cb8f831" + integrity sha512-KqjHOJKogOUt5Bs752ykCeiwvi0fKVkr5oqsFNt/8px/tA8scFPIlkygsf6jXrfCqGHz7VflA6+yytWuM+XhFw== + dependencies: + "@alloc/quick-lru" "^5.2.0" + arg "^5.0.2" + chokidar "^3.5.3" + didyoumean "^1.2.2" + dlv "^1.1.3" + fast-glob "^3.3.0" + glob-parent "^6.0.2" + is-glob "^4.0.3" + jiti "^1.21.0" + lilconfig "^2.1.0" + micromatch "^4.0.5" + normalize-path "^3.0.0" + object-hash "^3.0.0" + picocolors "^1.0.0" + postcss "^8.4.23" + postcss-import "^15.1.0" + postcss-js "^4.0.1" + postcss-load-config "^4.0.1" + postcss-nested "^6.0.1" + postcss-selector-parser "^6.0.11" + resolve "^1.22.2" + sucrase "^3.32.0" + +tapable@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" + integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + +thenify-all@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" + integrity sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA== + dependencies: + thenify ">= 3.1.0 < 4" + +"thenify@>= 3.1.0 < 4": + version "3.3.1" + resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f" + integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== + dependencies: + any-promise "^1.0.0" + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +tree-kill@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" + integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== + +ts-api-utils@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.3.0.tgz#4b490e27129f1e8e686b45cc4ab63714dc60eea1" + integrity sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ== + +ts-interface-checker@^0.1.9: + version "0.1.13" + resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699" + integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA== + +tsconfig-paths@^3.15.0: + version "3.15.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz#5299ec605e55b1abb23ec939ef15edaf483070d4" + integrity sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg== + dependencies: + "@types/json5" "^0.0.29" + json5 "^1.0.2" + minimist "^1.2.6" + strip-bom "^3.0.0" + +tslib@^2.1.0, tslib@^2.4.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01" + integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA== + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + +typed-array-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz#1867c5d83b20fcb5ccf32649e5e2fc7424474ff3" + integrity sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + is-typed-array "^1.1.13" + +typed-array-byte-length@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz#d92972d3cff99a3fa2e765a28fcdc0f1d89dec67" + integrity sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw== + dependencies: + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + has-proto "^1.0.3" + is-typed-array "^1.1.13" + +typed-array-byte-offset@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz#f9ec1acb9259f395093e4567eb3c28a580d02063" + integrity sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA== + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + has-proto "^1.0.3" + is-typed-array "^1.1.13" + +typed-array-length@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.6.tgz#57155207c76e64a3457482dfdc1c9d1d3c4c73a3" + integrity sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g== + dependencies: + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + has-proto "^1.0.3" + is-typed-array "^1.1.13" + possible-typed-array-names "^1.0.0" + +unbox-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" + integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw== + dependencies: + call-bind "^1.0.2" + has-bigints "^1.0.2" + has-symbols "^1.0.3" + which-boxed-primitive "^1.0.2" + +update-browserslist-db@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz#7ca61c0d8650766090728046e416a8cde682859e" + integrity sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ== + dependencies: + escalade "^3.1.2" + picocolors "^1.0.1" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +util-deprecate@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +wait-on@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/wait-on/-/wait-on-7.2.0.tgz#d76b20ed3fc1e2bebc051fae5c1ff93be7892928" + integrity sha512-wCQcHkRazgjG5XoAq9jbTMLpNIjoSlZslrJ2+N9MxDsGEv1HnFoVjOCexL0ESva7Y9cu350j+DWADdk54s4AFQ== + dependencies: + axios "^1.6.1" + joi "^17.11.0" + lodash "^4.17.21" + minimist "^1.2.8" + rxjs "^7.8.1" + +which-boxed-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" + integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== + dependencies: + is-bigint "^1.0.1" + is-boolean-object "^1.1.0" + is-number-object "^1.0.4" + is-string "^1.0.5" + is-symbol "^1.0.3" + +which-builtin-type@^1.1.3: + version "1.1.4" + resolved "https://registry.yarnpkg.com/which-builtin-type/-/which-builtin-type-1.1.4.tgz#592796260602fc3514a1b5ee7fa29319b72380c3" + integrity sha512-bppkmBSsHFmIMSl8BO9TbsyzsvGjVoppt8xUiGzwiu/bhDCGxnpOKCxgqj6GuyHE0mINMDecBFPlOm2hzY084w== + dependencies: + function.prototype.name "^1.1.6" + has-tostringtag "^1.0.2" + is-async-function "^2.0.0" + is-date-object "^1.0.5" + is-finalizationregistry "^1.0.2" + is-generator-function "^1.0.10" + is-regex "^1.1.4" + is-weakref "^1.0.2" + isarray "^2.0.5" + which-boxed-primitive "^1.0.2" + which-collection "^1.0.2" + which-typed-array "^1.1.15" + +which-collection@^1.0.1, which-collection@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.2.tgz#627ef76243920a107e7ce8e96191debe4b16c2a0" + integrity sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw== + dependencies: + is-map "^2.0.3" + is-set "^2.0.3" + is-weakmap "^2.0.2" + is-weakset "^2.0.3" + +which-typed-array@^1.1.13, which-typed-array@^1.1.14, which-typed-array@^1.1.15: + version "1.1.15" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.15.tgz#264859e9b11a649b388bfaaf4f767df1f779b38d" + integrity sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA== + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + has-tostringtag "^1.0.2" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +word-wrap@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== + +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" + integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== + dependencies: + ansi-styles "^6.1.0" + string-width "^5.0.1" + strip-ansi "^7.0.1" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yaml@^2.3.4: + version "2.5.1" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.5.1.tgz#c9772aacf62cb7494a95b0c4f1fb065b563db130" + integrity sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q== + +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs@^17.3.1: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== diff --git a/compiler/fixtures/.gitkeep b/compiler/fixtures/.gitkeep new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/compiler/package.json b/compiler/package.json index 3adb2d279f159..c05e0e70d3e35 100644 --- a/compiler/package.json +++ b/compiler/package.json @@ -2,12 +2,7 @@ "private": true, "workspaces": { "packages": [ - "packages/*", - "apps/*" - ], - "nohoist": [ - "**/next", - "**/next/**" + "packages/*" ] }, "repository": { @@ -20,15 +15,15 @@ "start": "yarn workspace playground run start", "next": "yarn workspace playground run dev", "build": "yarn workspaces run build", - "dev": "concurrently --kill-others -n compiler,runtime,playground \"yarn workspace babel-plugin-react-compiler run build --watch\" \"yarn workspace react-compiler-runtime run build --watch\" \"wait-on packages/babel-plugin-react-compiler/dist/index.js && yarn workspace playground run dev\"", + "dev": "echo 'DEPRECATED: use `cd apps/playground && yarn dev` instead!' && sleep 5 && cd apps/playground && yarn dev", "test": "yarn workspaces run test", "snap": "yarn workspace babel-plugin-react-compiler run snap", "snap:build": "yarn workspace snap run build", - "postinstall": "perl -p -i -e 's/react\\.element/react.transitional.element/' packages/snap/node_modules/fbt/lib/FbtReactUtil.js && perl -p -i -e 's/didWarnAboutUsingAct = false;/didWarnAboutUsingAct = true;/' packages/babel-plugin-react-compiler/node_modules/react-dom/cjs/react-dom-test-utils.development.js", "npm:publish": "node scripts/release/publish" }, "dependencies": { - "fs-extra": "^4.0.2" + "fs-extra": "^4.0.2", + "react-is": "0.0.0-experimental-4beb1fd8-20241118" }, "devDependencies": { "@rollup/plugin-commonjs": "^25.0.7", @@ -39,19 +34,17 @@ "@tsconfig/strictest": "^2.0.5", "concurrently": "^7.4.0", "folder-hash": "^4.0.4", + "object-assign": "^4.1.1", "ora": "5.4.1", "prettier": "^3.3.3", - "prettier-plugin-hermes-parser": "^0.23.0", + "prettier-plugin-hermes-parser": "^0.25.1", "prompt-promise": "^1.0.3", - "rollup": "^4.13.2", + "rollup": "^4.22.4", "rollup-plugin-banner2": "^1.2.3", "rollup-plugin-prettier": "^4.1.1", "typescript": "^5.4.3", "wait-on": "^7.2.0", "yargs": "^17.7.2" }, - "resolutions": { - "react-is": "19.0.0-beta-b498834eab-20240506" - }, "packageManager": "yarn@1.22.22" } diff --git a/compiler/packages/babel-plugin-react-compiler/package.json b/compiler/packages/babel-plugin-react-compiler/package.json index c8d9f8b17bc96..7d55e7b27fe9b 100644 --- a/compiler/packages/babel-plugin-react-compiler/package.json +++ b/compiler/packages/babel-plugin-react-compiler/package.json @@ -5,11 +5,12 @@ "main": "dist/index.js", "license": "MIT", "files": [ - "dist" + "dist", + "!*.tsbuildinfo" ], "scripts": { "build": "rimraf dist && rollup --config --bundleConfigAsCjs", - "test": "yarn snap:ci", + "test": "./scripts/link-react-compiler-runtime.sh && yarn snap:ci", "jest": "yarn build && ts-node node_modules/.bin/jest", "snap": "node ../snap/dist/main.js", "snap:build": "yarn workspace snap run build", @@ -18,16 +19,11 @@ "lint": "yarn eslint src" }, "dependencies": { - "@babel/generator": "7.2.0", - "@babel/types": "^7.19.0", - "chalk": "4", - "invariant": "^2.2.4", - "pretty-format": "^24", - "zod": "^3.22.4", - "zod-validation-error": "^2.1.0" + "@babel/types": "^7.19.0" }, "devDependencies": { "@babel/core": "^7.2.0", + "@babel/generator": "7.2.0", "@babel/parser": "^7.2.0", "@babel/plugin-syntax-typescript": "^7.18.6", "@babel/plugin-transform-block-scoping": "^7.18.9", @@ -41,20 +37,25 @@ "@types/invariant": "^2.2.35", "@types/jest": "^29.0.3", "@types/node": "^18.7.18", - "@typescript-eslint/eslint-plugin": "^7.4.0", - "@typescript-eslint/parser": "^7.4.0", + "@typescript-eslint/eslint-plugin": "^8.7.0", + "@typescript-eslint/parser": "^8.7.0", "babel-jest": "^29.0.3", "babel-plugin-fbt": "^1.0.0", "babel-plugin-fbt-runtime": "^1.0.0", - "eslint": "8.27.0", + "chalk": "4", + "eslint": "^8.57.1", "glob": "^7.1.6", + "invariant": "^2.2.4", "jest": "^29.0.3", "jest-environment-jsdom": "^29.0.3", - "react": "19.0.0-beta-b498834eab-20240506", - "react-dom": "19.0.0-beta-b498834eab-20240506", + "pretty-format": "^24", + "react": "0.0.0-experimental-4beb1fd8-20241118", + "react-dom": "0.0.0-experimental-4beb1fd8-20241118", "rimraf": "^3.0.2", "ts-jest": "^29.1.1", - "ts-node": "^10.9.2" + "ts-node": "^10.9.2", + "zod": "^3.22.4", + "zod-validation-error": "^2.1.0" }, "resolutions": { "./**/@babel/parser": "7.7.4", diff --git a/compiler/packages/babel-plugin-react-compiler/rollup.config.js b/compiler/packages/babel-plugin-react-compiler/rollup.config.js index b5d7567846f25..77e785c4641f2 100644 --- a/compiler/packages/babel-plugin-react-compiler/rollup.config.js +++ b/compiler/packages/babel-plugin-react-compiler/rollup.config.js @@ -28,6 +28,7 @@ const DEV_ROLLUP_CONFIG = { plugins: [ typescript({ tsconfig: './tsconfig.json', + outputToFilesystem: true, compilerOptions: { noEmit: true, }, diff --git a/compiler/packages/babel-plugin-react-compiler/scripts/link-react-compiler-runtime.sh b/compiler/packages/babel-plugin-react-compiler/scripts/link-react-compiler-runtime.sh new file mode 100755 index 0000000000000..4d1bcf1ec35d0 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/scripts/link-react-compiler-runtime.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +set -eo pipefail + +yarn --silent workspace react-compiler-runtime link +yarn --silent workspace babel-plugin-react-compiler link react-compiler-runtime diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts index 722c62461d813..72ed9e7c866ce 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts @@ -7,9 +7,14 @@ import * as t from '@babel/types'; import {z} from 'zod'; -import {CompilerErrorDetailOptions} from '../CompilerError'; -import {ExternalFunction, PartialEnvironmentConfig} from '../HIR/Environment'; +import {CompilerError, CompilerErrorDetailOptions} from '../CompilerError'; +import { + EnvironmentConfig, + ExternalFunction, + parseEnvironmentConfig, +} from '../HIR/Environment'; import {hasOwnProperty} from '../Utils/utils'; +import {fromZodError} from 'zod-validation-error'; const PanicThresholdOptionsSchema = z.enum([ /* @@ -32,7 +37,7 @@ const PanicThresholdOptionsSchema = z.enum([ export type PanicThresholdOptions = z.infer; export type PluginOptions = { - environment: PartialEnvironmentConfig | null; + environment: EnvironmentConfig; logger: Logger | null; @@ -82,17 +87,6 @@ export type PluginOptions = { */ compilationMode: CompilationMode; - /* - * If enabled, Forget will import `useMemoCache` from the given module - * instead of `react/compiler-runtime`. - * - * ``` - * // If set to "react-compiler-runtime" - * import {c as useMemoCache} from 'react-compiler-runtime'; - * ``` - */ - runtimeModule?: string | null | undefined; - /** * By default React Compiler will skip compilation of code that suppresses the default * React ESLint rules, since this is a strong indication that the code may be breaking React rules @@ -117,8 +111,34 @@ export type PluginOptions = { * Set this flag (on by default) to automatically check for this library and activate the support. */ enableReanimatedCheck: boolean; + + /** + * The minimum major version of React that the compiler should emit code for. If the target is 19 + * or higher, the compiler emits direct imports of React runtime APIs needed by the compiler. On + * versions prior to 19, an extra runtime package react-compiler-runtime is necessary to provide + * a userspace approximation of runtime APIs. + */ + target: CompilerReactTarget; }; +const CompilerReactTargetSchema = z.union([ + z.literal('17'), + z.literal('18'), + z.literal('19'), + /** + * Used exclusively for Meta apps which are guaranteed to have compatible + * react runtime and compiler versions. Note that only the FB-internal bundles + * re-export useMemoCache (see + * https://github.com/facebook/react/blob/5b0ef217ef32333a8e56f39be04327c89efa346f/packages/react/index.fb.js#L68-L70), + * so this option is invalid / creates runtime errors for open-source users. + */ + z.object({ + kind: z.literal('donotuse_meta_internal'), + runtimeModule: z.string().default('react'), + }), +]); +export type CompilerReactTarget = z.infer; + const CompilationModeSchema = z.enum([ /* * Compiles functions annotated with "use forget" or component/hook-like functions. @@ -165,6 +185,12 @@ export type LoggerEvent = fnLoc: t.SourceLocation | null; detail: Omit, 'suggestions'>; } + | { + kind: 'CompileSkip'; + fnLoc: t.SourceLocation | null; + reason: string; + loc: t.SourceLocation | null; + } | { kind: 'CompileSuccess'; fnLoc: t.SourceLocation | null; @@ -188,11 +214,10 @@ export type Logger = { export const defaultOptions: PluginOptions = { compilationMode: 'infer', panicThreshold: 'none', - environment: {}, + environment: parseEnvironmentConfig({}).unwrap(), logger: null, gating: null, noEmit: false, - runtimeModule: null, eslintSuppressionRules: null, flowSuppressions: true, ignoreUseNoForget: false, @@ -200,6 +225,7 @@ export const defaultOptions: PluginOptions = { return filename.indexOf('node_modules') === -1; }, enableReanimatedCheck: true, + target: '19', } as const; export function parsePluginOptions(obj: unknown): PluginOptions { @@ -213,12 +239,48 @@ export function parsePluginOptions(obj: unknown): PluginOptions { value = value.toLowerCase(); } if (isCompilerFlag(key)) { - parsedOptions[key] = value; + switch (key) { + case 'environment': { + const environmentResult = parseEnvironmentConfig(value); + if (environmentResult.isErr()) { + CompilerError.throwInvalidConfig({ + reason: + 'Error in validating environment config. This is an advanced setting and not meant to be used directly', + description: environmentResult.unwrapErr().toString(), + suggestions: null, + loc: null, + }); + } + parsedOptions[key] = environmentResult.unwrap(); + break; + } + case 'target': { + parsedOptions[key] = parseTargetConfig(value); + break; + } + default: { + parsedOptions[key] = value; + } + } } } return {...defaultOptions, ...parsedOptions}; } +export function parseTargetConfig(value: unknown): CompilerReactTarget { + const parsed = CompilerReactTargetSchema.safeParse(value); + if (parsed.success) { + return parsed.data; + } else { + CompilerError.throwInvalidConfig({ + reason: 'Not a valid target', + description: `${fromZodError(parsed.error)}`, + suggestions: null, + loc: null, + }); + } +} + function isCompilerFlag(s: string): s is keyof PluginOptions { return hasOwnProperty(defaultOptions, s); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts index 590aba2fdc0c4..90921454c864f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts @@ -36,32 +36,28 @@ import { inferReactivePlaces, inferReferenceEffects, inlineImmediatelyInvokedFunctionExpressions, + inferEffectDependencies, } from '../Inference'; import { constantPropagation, deadCodeElimination, pruneMaybeThrows, + inlineJsxTransform, } from '../Optimization'; import {instructionReordering} from '../Optimization/InstructionReordering'; import { CodegenFunction, alignObjectMethodScopes, - alignReactiveScopesToBlockScopes, assertScopeInstructionsWithinScopes, assertWellFormedBreakTargets, - buildReactiveBlocks, buildReactiveFunction, codegenFunction, extractScopeDeclarationsFromDestructuring, - flattenReactiveLoops, - flattenScopesWithHooksOrUse, inferReactiveScopeVariables, memoizeFbtAndMacroOperandsInSameScope, - mergeOverlappingReactiveScopes, mergeReactiveScopesThatInvalidateTogether, promoteUsedTemporaries, propagateEarlyReturns, - propagateScopeDependencies, pruneHoistedContexts, pruneNonEscapingScopes, pruneNonReactiveDependencies, @@ -105,6 +101,9 @@ import {outlineFunctions} from '../Optimization/OutlineFunctions'; import {propagatePhiTypes} from '../TypeInference/PropagatePhiTypes'; import {lowerContextAccess} from '../Optimization/LowerContextAccess'; import {validateNoSetStateInPassiveEffects} from '../Validation/ValidateNoSetStateInPassiveEffects'; +import {validateNoJSXInTryStatement} from '../Validation/ValidateNoJSXInTryStatement'; +import {propagateScopeDependenciesHIR} from '../HIR/PropagateScopeDependenciesHIR'; +import {outlineJSX} from '../Optimization/OutlineJsx'; export type CompilerPipelineValue = | {kind: 'ast'; name: string; value: CodegenFunction} @@ -249,6 +248,10 @@ function* runWithEnvironment( validateNoSetStateInPassiveEffects(hir); } + if (env.config.validateNoJSXInTryStatements) { + validateNoJSXInTryStatement(hir); + } + inferReactivePlaces(hir); yield log({kind: 'hir', name: 'InferReactivePlaces', value: hir}); @@ -276,6 +279,10 @@ function* runWithEnvironment( value: hir, }); + if (env.config.enableJsxOutlining) { + outlineJSX(hir); + } + if (env.config.enableFunctionOutlining) { outlineFunctions(hir, fbtOperands); yield log({kind: 'hir', name: 'OutlineFunctions', value: hir}); @@ -295,53 +302,70 @@ function* runWithEnvironment( value: hir, }); - if (env.config.enableReactiveScopesInHIR) { - pruneUnusedLabelsHIR(hir); - yield log({ - kind: 'hir', - name: 'PruneUnusedLabelsHIR', - value: hir, - }); + pruneUnusedLabelsHIR(hir); + yield log({ + kind: 'hir', + name: 'PruneUnusedLabelsHIR', + value: hir, + }); - alignReactiveScopesToBlockScopesHIR(hir); - yield log({ - kind: 'hir', - name: 'AlignReactiveScopesToBlockScopesHIR', - value: hir, - }); + alignReactiveScopesToBlockScopesHIR(hir); + yield log({ + kind: 'hir', + name: 'AlignReactiveScopesToBlockScopesHIR', + value: hir, + }); - mergeOverlappingReactiveScopesHIR(hir); - yield log({ - kind: 'hir', - name: 'MergeOverlappingReactiveScopesHIR', - value: hir, - }); - assertValidBlockNesting(hir); + mergeOverlappingReactiveScopesHIR(hir); + yield log({ + kind: 'hir', + name: 'MergeOverlappingReactiveScopesHIR', + value: hir, + }); + assertValidBlockNesting(hir); - buildReactiveScopeTerminalsHIR(hir); - yield log({ - kind: 'hir', - name: 'BuildReactiveScopeTerminalsHIR', - value: hir, - }); + buildReactiveScopeTerminalsHIR(hir); + yield log({ + kind: 'hir', + name: 'BuildReactiveScopeTerminalsHIR', + value: hir, + }); - assertValidBlockNesting(hir); + assertValidBlockNesting(hir); - flattenReactiveLoopsHIR(hir); - yield log({ - kind: 'hir', - name: 'FlattenReactiveLoopsHIR', - value: hir, - }); + flattenReactiveLoopsHIR(hir); + yield log({ + kind: 'hir', + name: 'FlattenReactiveLoopsHIR', + value: hir, + }); - flattenScopesWithHooksOrUseHIR(hir); + flattenScopesWithHooksOrUseHIR(hir); + yield log({ + kind: 'hir', + name: 'FlattenScopesWithHooksOrUseHIR', + value: hir, + }); + assertTerminalSuccessorsExist(hir); + assertTerminalPredsExist(hir); + propagateScopeDependenciesHIR(hir); + yield log({ + kind: 'hir', + name: 'PropagateScopeDependenciesHIR', + value: hir, + }); + + if (env.config.inferEffectDependencies) { + inferEffectDependencies(hir); + } + + if (env.config.inlineJsxTransform) { + inlineJsxTransform(hir, env.config.inlineJsxTransform); yield log({ kind: 'hir', - name: 'FlattenScopesWithHooksOrUseHIR', + name: 'inlineJsxTransform', value: hir, }); - assertTerminalSuccessorsExist(hir); - assertTerminalPredsExist(hir); } const reactiveFunction = buildReactiveFunction(hir); @@ -359,53 +383,8 @@ function* runWithEnvironment( name: 'PruneUnusedLabels', value: reactiveFunction, }); - - if (!env.config.enableReactiveScopesInHIR) { - alignReactiveScopesToBlockScopes(reactiveFunction); - yield log({ - kind: 'reactive', - name: 'AlignReactiveScopesToBlockScopes', - value: reactiveFunction, - }); - - mergeOverlappingReactiveScopes(reactiveFunction); - yield log({ - kind: 'reactive', - name: 'MergeOverlappingReactiveScopes', - value: reactiveFunction, - }); - - buildReactiveBlocks(reactiveFunction); - yield log({ - kind: 'reactive', - name: 'BuildReactiveBlocks', - value: reactiveFunction, - }); - - flattenReactiveLoops(reactiveFunction); - yield log({ - kind: 'reactive', - name: 'FlattenReactiveLoops', - value: reactiveFunction, - }); - - flattenScopesWithHooksOrUse(reactiveFunction); - yield log({ - kind: 'reactive', - name: 'FlattenScopesWithHooks', - value: reactiveFunction, - }); - } - assertScopeInstructionsWithinScopes(reactiveFunction); - propagateScopeDependencies(reactiveFunction); - yield log({ - kind: 'reactive', - name: 'PropagateScopeDependencies', - value: reactiveFunction, - }); - pruneNonEscapingScopes(reactiveFunction); yield log({ kind: 'reactive', @@ -583,3 +562,14 @@ export function log(value: CompilerPipelineValue): CompilerPipelineValue { } return value; } + +export function* runPlayground( + func: NodePath< + t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression + >, + config: EnvironmentConfig, + fnType: ReactFunctionType, +): Generator { + const ast = yield* run(func, config, fnType, '_c', null, null, null); + return ast; +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts index 499a4d124ea67..a6e09a1d061da 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -16,7 +16,6 @@ import { EnvironmentConfig, ExternalFunction, ReactFunctionType, - parseEnvironmentConfig, tryParseExternalFunction, } from '../HIR/Environment'; import {CodegenFunction} from '../ReactiveScopes'; @@ -43,34 +42,23 @@ export type CompilerPass = { comments: Array; code: string | null; }; +export const OPT_IN_DIRECTIVES = new Set(['use forget', 'use memo']); +export const OPT_OUT_DIRECTIVES = new Set(['use no forget', 'use no memo']); -function findDirectiveEnablingMemoization( +export function findDirectiveEnablingMemoization( directives: Array, -): t.Directive | null { - for (const directive of directives) { - const directiveValue = directive.value.value; - if (directiveValue === 'use forget' || directiveValue === 'use memo') { - return directive; - } - } - return null; +): Array { + return directives.filter(directive => + OPT_IN_DIRECTIVES.has(directive.value.value), + ); } -function findDirectiveDisablingMemoization( +export function findDirectiveDisablingMemoization( directives: Array, - options: PluginOptions, -): t.Directive | null { - for (const directive of directives) { - const directiveValue = directive.value.value; - if ( - (directiveValue === 'use no forget' || - directiveValue === 'use no memo') && - !options.ignoreUseNoForget - ) { - return directive; - } - } - return null; +): Array { + return directives.filter(directive => + OPT_OUT_DIRECTIVES.has(directive.value.value), + ); } function isCriticalError(err: unknown): boolean { @@ -102,7 +90,7 @@ export type CompileResult = { compiledFn: CodegenFunction; }; -function handleError( +function logError( err: unknown, pass: CompilerPass, fnLoc: t.SourceLocation | null, @@ -131,6 +119,13 @@ function handleError( }); } } +} +function handleError( + err: unknown, + pass: CompilerPass, + fnLoc: t.SourceLocation | null, +): void { + logError(err, pass, fnLoc); if ( pass.opts.panicThreshold === 'all_errors' || (pass.opts.panicThreshold === 'critical_errors' && isCriticalError(err)) || @@ -204,7 +199,7 @@ function insertNewOutlinedFunctionNode( program: NodePath, originalFn: BabelFn, compiledFn: CodegenFunction, -): NodePath { +): BabelFn { switch (originalFn.type) { case 'FunctionDeclaration': { return originalFn.insertAfter( @@ -296,28 +291,13 @@ export function compileProgram( return; } - /* - * TODO(lauren): Remove pass.opts.environment nullcheck once PluginOptions - * is validated - */ - const environmentResult = parseEnvironmentConfig(pass.opts.environment ?? {}); - if (environmentResult.isErr()) { - CompilerError.throwInvalidConfig({ - reason: - 'Error in validating environment config. This is an advanced setting and not meant to be used directly', - description: environmentResult.unwrapErr().toString(), - suggestions: null, - loc: null, - }); - } - const environment = environmentResult.unwrap(); + const environment = pass.opts.environment; const restrictedImportsErr = validateRestrictedImports(program, environment); if (restrictedImportsErr) { handleError(restrictedImportsErr, pass, null); return; } const useMemoCacheIdentifier = program.scope.generateUidIdentifier('c'); - const moduleName = pass.opts.runtimeModule ?? 'react/compiler-runtime'; /* * Record lint errors and critical errors as depending on Forget's config, @@ -329,8 +309,6 @@ export function compileProgram( pass.opts.eslintSuppressionRules ?? DEFAULT_ESLINT_SUPPRESSIONS, pass.opts.flowSuppressions, ); - const lintError = suppressionsToCompilerError(suppressions); - let hasCriticalError = lintError != null; const queue: Array<{ kind: 'original' | 'outlined'; fn: BabelFn; @@ -393,7 +371,19 @@ export function compileProgram( fn: BabelFn, fnType: ReactFunctionType, ): null | CodegenFunction => { - if (lintError != null) { + let optInDirectives: Array = []; + let optOutDirectives: Array = []; + if (fn.node.body.type === 'BlockStatement') { + optInDirectives = findDirectiveEnablingMemoization( + fn.node.body.directives, + ); + optOutDirectives = findDirectiveDisablingMemoization( + fn.node.body.directives, + ); + } + + let compiledFn: CodegenFunction; + try { /** * Note that Babel does not attach comment nodes to nodes; they are dangling off of the * Program node itself. We need to figure out whether an eslint suppression range @@ -404,12 +394,15 @@ export function compileProgram( fn, ); if (suppressionsInFunction.length > 0) { - handleError(lintError, pass, fn.node.loc ?? null); + const lintError = suppressionsToCompilerError(suppressionsInFunction); + if (optOutDirectives.length > 0) { + logError(lintError, pass, fn.node.loc ?? null); + } else { + handleError(lintError, pass, fn.node.loc ?? null); + } + return null; } - } - let compiledFn: CodegenFunction; - try { compiledFn = compileFn( fn, environment, @@ -430,12 +423,50 @@ export function compileProgram( prunedMemoValues: compiledFn.prunedMemoValues, }); } catch (err) { - hasCriticalError ||= isCriticalError(err); + /** + * If an opt out directive is present, log only instead of throwing and don't mark as + * containing a critical error. + */ + if (fn.node.body.type === 'BlockStatement') { + if (optOutDirectives.length > 0) { + logError(err, pass, fn.node.loc ?? null); + return null; + } + } handleError(err, pass, fn.node.loc ?? null); return null; } - if (!pass.opts.noEmit && !hasCriticalError) { + /** + * Always compile functions with opt in directives. + */ + if (optInDirectives.length > 0) { + return compiledFn; + } else if (pass.opts.compilationMode === 'annotation') { + /** + * No opt-in directive in annotation mode, so don't insert the compiled function. + */ + return null; + } + + /** + * Otherwise if 'use no forget/memo' is present, we still run the code through the compiler + * for validation but we don't mutate the babel AST. This allows us to flag if there is an + * unused 'use no forget/memo' directive. + */ + if (pass.opts.ignoreUseNoForget === false && optOutDirectives.length > 0) { + for (const directive of optOutDirectives) { + pass.opts.logger?.logEvent(pass.filename, { + kind: 'CompileSkip', + fnLoc: fn.node.body.loc ?? null, + reason: `Skipped due to '${directive.value.value}' directive.`, + loc: directive.loc ?? null, + }); + } + return null; + } + + if (!pass.opts.noEmit) { return compiledFn; } return null; @@ -460,18 +491,11 @@ export function compileProgram( fn.skip(); ALREADY_COMPILED.add(fn.node); if (outlined.type !== null) { - CompilerError.throwTodo({ - reason: `Implement support for outlining React functions (components/hooks)`, - loc: outlined.fn.loc, + queue.push({ + kind: 'outlined', + fn, + fnType: outlined.type, }); - /* - * Above should be as simple as the following, but needs testing: - * queue.push({ - * kind: "outlined", - * fn, - * fnType: outlined.type, - * }); - */ } } compiledFns.push({ @@ -481,6 +505,16 @@ export function compileProgram( }); } + /** + * Do not modify source if there is a module scope level opt out directive. + */ + const moduleScopeOptOutDirectives = findDirectiveDisablingMemoization( + program.node.directives, + ); + if (moduleScopeOptOutDirectives.length > 0) { + return; + } + if (pass.opts.gating != null) { const error = checkFunctionReferencedBeforeDeclarationAtTopLevel( program, @@ -563,7 +597,7 @@ export function compileProgram( if (needsMemoCacheFunctionImport) { updateMemoCacheFunctionImport( program, - moduleName, + getReactCompilerRuntimeModule(pass.opts), useMemoCacheIdentifier.name, ); } @@ -596,26 +630,12 @@ function shouldSkipCompilation( } } - // Top level "use no forget", skip this file entirely - const useNoForget = findDirectiveDisablingMemoization( - program.node.directives, - pass.opts, - ); - if (useNoForget != null) { - pass.opts.logger?.logEvent(pass.filename, { - kind: 'CompileError', - fnLoc: null, - detail: { - severity: ErrorSeverity.Todo, - reason: 'Skipped due to "use no forget" directive.', - loc: useNoForget.loc ?? null, - suggestions: null, - }, - }); - return true; - } - const moduleName = pass.opts.runtimeModule ?? 'react/compiler-runtime'; - if (hasMemoCacheFunctionImport(program, moduleName)) { + if ( + hasMemoCacheFunctionImport( + program, + getReactCompilerRuntimeModule(pass.opts), + ) + ) { return true; } return false; @@ -631,28 +651,8 @@ function getReactFunctionType( ): ReactFunctionType | null { const hookPattern = environment.hookPattern; if (fn.node.body.type === 'BlockStatement') { - // Opt-outs disable compilation regardless of mode - const useNoForget = findDirectiveDisablingMemoization( - fn.node.body.directives, - pass.opts, - ); - if (useNoForget != null) { - pass.opts.logger?.logEvent(pass.filename, { - kind: 'CompileError', - fnLoc: fn.node.body.loc ?? null, - detail: { - severity: ErrorSeverity.Todo, - reason: 'Skipped due to "use no forget" directive.', - loc: useNoForget.loc ?? null, - suggestions: null, - }, - }); - return null; - } - // Otherwise opt-ins enable compilation regardless of mode - if (findDirectiveEnablingMemoization(fn.node.body.directives) != null) { + if (findDirectiveEnablingMemoization(fn.node.body.directives).length > 0) return getComponentOrHookLike(fn, hookPattern) ?? 'Other'; - } } // Component and hook declarations are known components/hooks @@ -1122,3 +1122,24 @@ function checkFunctionReferencedBeforeDeclarationAtTopLevel( return errors.details.length > 0 ? errors : null; } + +function getReactCompilerRuntimeModule(opts: PluginOptions): string { + if (opts.target === '19') { + return 'react/compiler-runtime'; // from react namespace + } else if (opts.target === '17' || opts.target === '18') { + return 'react-compiler-runtime'; // npm package + } else { + CompilerError.invariant( + opts.target != null && + opts.target.kind === 'donotuse_meta_internal' && + typeof opts.target.runtimeModule === 'string', + { + reason: 'Expected target to already be validated', + description: null, + loc: null, + suggestions: null, + }, + ); + return opts.target.runtimeModule; + } +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Suppression.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Suppression.ts index 71341989a7592..4d0369f5210ca 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Suppression.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Suppression.ts @@ -14,6 +14,7 @@ import { ErrorSeverity, } from '../CompilerError'; import {assertExhaustive} from '../Utils/utils'; +import {GeneratedSource} from '../HIR'; /** * Captures the start and end range of a pair of eslint-disable ... eslint-enable comments. In the @@ -148,10 +149,11 @@ export function findProgramSuppressions( export function suppressionsToCompilerError( suppressionRanges: Array, -): CompilerError | null { - if (suppressionRanges.length === 0) { - return null; - } +): CompilerError { + CompilerError.invariant(suppressionRanges.length !== 0, { + reason: `Expected at least suppression comment source range`, + loc: GeneratedSource, + }); const error = new CompilerError(); for (const suppressionRange of suppressionRanges) { if ( diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/AssertConsistentIdentifiers.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/AssertConsistentIdentifiers.ts index dbe662c609a32..2588ee1f9a8da 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/AssertConsistentIdentifiers.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/AssertConsistentIdentifiers.ts @@ -29,9 +29,9 @@ export function assertConsistentIdentifiers(fn: HIRFunction): void { const assignments: Set = new Set(); for (const [, block] of fn.body.blocks) { for (const phi of block.phis) { - validate(identifiers, phi.id); + validate(identifiers, phi.place.identifier); for (const [, operand] of phi.operands) { - validate(identifiers, operand); + validate(identifiers, operand.identifier); } } for (const instr of block.instructions) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/AssertValidMutableRanges.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/AssertValidMutableRanges.ts index 7788140ee64cc..d44f6108eaa57 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/AssertValidMutableRanges.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/AssertValidMutableRanges.ts @@ -20,8 +20,9 @@ import { export function assertValidMutableRanges(fn: HIRFunction): void { for (const [, block] of fn.body.blocks) { for (const phi of block.phis) { + visitIdentifier(phi.place.identifier); for (const [, operand] of phi.operands) { - visitIdentifier(operand); + visitIdentifier(operand.identifier); } } for (const instr of block.instructions) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts index 20fac9d610a08..ecc22365dd03c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts @@ -215,7 +215,8 @@ export function lower( id, params, fnType: parent == null ? env.fnType : 'Other', - returnType: null, // TODO: extract the actual return type node if present + returnTypeAnnotation: null, // TODO: extract the actual return type node if present + returnType: makeType(), body: builder.build(), context, generator: func.node.generator === true, @@ -419,7 +420,19 @@ function lowerStatement( // Already hoisted continue; } - if (!binding.path.isVariableDeclarator()) { + + let kind: + | InstructionKind.Let + | InstructionKind.HoistedConst + | InstructionKind.HoistedLet + | InstructionKind.HoistedFunction; + if (binding.kind === 'const' || binding.kind === 'var') { + kind = InstructionKind.HoistedConst; + } else if (binding.kind === 'let') { + kind = InstructionKind.HoistedLet; + } else if (binding.path.isFunctionDeclaration()) { + kind = InstructionKind.HoistedFunction; + } else if (!binding.path.isVariableDeclarator()) { builder.errors.push({ severity: ErrorSeverity.Todo, reason: 'Unsupported declaration type for hoisting', @@ -428,11 +441,7 @@ function lowerStatement( loc: id.parentPath.node.loc ?? GeneratedSource, }); continue; - } else if ( - binding.kind !== 'const' && - binding.kind !== 'var' && - binding.kind !== 'let' - ) { + } else { builder.errors.push({ severity: ErrorSeverity.Todo, reason: 'Handle non-const declarations for hoisting', @@ -442,6 +451,7 @@ function lowerStatement( }); continue; } + const identifier = builder.resolveIdentifier(id); CompilerError.invariant(identifier.kind === 'Identifier', { reason: @@ -455,13 +465,6 @@ function lowerStatement( reactive: false, loc: id.node.loc ?? GeneratedSource, }; - const kind = - // Avoid double errors on var declarations, which we do not plan to support anyways - binding.kind === 'const' || binding.kind === 'var' - ? InstructionKind.HoistedConst - : binding.kind === 'let' - ? InstructionKind.HoistedLet - : assertExhaustive(binding.kind, 'Unexpected binding kind'); lowerValueToTemporary(builder, { kind: 'DeclareContext', lvalue: { @@ -606,6 +609,7 @@ function lowerStatement( ), consequent: bodyBlock, alternate: continuationBlock.id, + fallthrough: continuationBlock.id, id: makeInstructionId(0), loc: stmt.node.loc ?? GeneratedSource, }, @@ -655,16 +659,13 @@ function lowerStatement( }, conditionalBlock, ); - /* - * The conditional block is empty and exists solely as conditional for - * (re)entering or exiting the loop - */ const test = lowerExpressionToTemporary(builder, stmt.get('test')); const terminal: BranchTerminal = { kind: 'branch', test, consequent: loopBlock, alternate: continuationBlock.id, + fallthrough: conditionalBlock.id, id: makeInstructionId(0), loc: stmt.node.loc ?? GeneratedSource, }; @@ -974,6 +975,7 @@ function lowerStatement( test, consequent: loopBlock, alternate: continuationBlock.id, + fallthrough: conditionalBlock.id, id: makeInstructionId(0), loc, }; @@ -999,7 +1001,7 @@ function lowerStatement( lowerAssignment( builder, stmt.node.loc ?? GeneratedSource, - InstructionKind.Let, + InstructionKind.Function, id, fn, 'Assignment', @@ -1117,6 +1119,7 @@ function lowerStatement( consequent: loopBlock, alternate: continuationBlock.id, loc: stmt.node.loc ?? GeneratedSource, + fallthrough: continuationBlock.id, }, continuationBlock, ); @@ -1202,6 +1205,7 @@ function lowerStatement( test, consequent: loopBlock, alternate: continuationBlock.id, + fallthrough: continuationBlock.id, loc: stmt.node.loc ?? GeneratedSource, }, continuationBlock, @@ -1414,7 +1418,7 @@ function lowerObjectPropertyKey( name: key.node.value, }; } else if (property.node.computed && key.isExpression()) { - if (!key.isIdentifier()) { + if (!key.isIdentifier() && !key.isMemberExpression()) { /* * NOTE: allowing complex key expressions can trigger a bug where a mutation is made conditional * see fixture @@ -1799,6 +1803,7 @@ function lowerExpression( test: {...testPlace}, consequent: consequentBlock, alternate: alternateBlock, + fallthrough: continuationBlock.id, id: makeInstructionId(0), loc: exprLoc, }, @@ -1877,6 +1882,7 @@ function lowerExpression( test: {...leftPlace}, consequent, alternate, + fallthrough: continuationBlock.id, id: makeInstructionId(0), loc: exprLoc, }, @@ -2610,6 +2616,7 @@ function lowerOptionalMemberExpression( test: {...object}, consequent: consequent.id, alternate, + fallthrough: continuationBlock.id, id: makeInstructionId(0), loc, }; @@ -2749,6 +2756,7 @@ function lowerOptionalCallExpression( test: {...testPlace}, consequent: consequent.id, alternate, + fallthrough: continuationBlock.id, id: makeInstructionId(0), loc, }; @@ -3178,7 +3186,13 @@ function lowerJsxMemberExpression( loc: object.node.loc ?? null, suggestions: null, }); - objectPlace = lowerIdentifier(builder, object); + + const kind = getLoadKind(builder, object); + objectPlace = lowerValueToTemporary(builder, { + kind: kind, + place: lowerIdentifier(builder, object), + loc: exprPath.node.loc ?? GeneratedSource, + }); } const property = exprPath.get('property').node.name; return lowerValueToTemporary(builder, { @@ -4024,6 +4038,7 @@ function lowerAssignment( test: {...test}, consequent, alternate, + fallthrough: continuationBlock.id, id: makeInstructionId(0), loc, }, diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildReactiveScopeTerminalsHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildReactiveScopeTerminalsHIR.ts index 0999a3492b4a4..7c1fb54ea8058 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildReactiveScopeTerminalsHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildReactiveScopeTerminalsHIR.ts @@ -14,6 +14,7 @@ import { ScopeId, } from './HIR'; import { + fixScopeAndIdentifierRanges, markInstructionIds, markPredecessors, reversePostorderBlocks, @@ -176,20 +177,7 @@ export function buildReactiveScopeTerminalsHIR(fn: HIRFunction): void { * Step 5: * Fix scope and identifier ranges to account for renumbered instructions */ - for (const [, block] of fn.body.blocks) { - const terminal = block.terminal; - if (terminal.kind === 'scope' || terminal.kind === 'pruned-scope') { - /* - * Scope ranges should always align to start at the 'scope' terminal - * and end at the first instruction of the fallthrough block - */ - const fallthroughBlock = fn.body.blocks.get(terminal.fallthrough)!; - const firstId = - fallthroughBlock.instructions[0]?.id ?? fallthroughBlock.terminal.id; - terminal.scope.range.start = terminal.id; - terminal.scope.range.end = firstId; - } - } + fixScopeAndIdentifierRanges(fn.body); } type TerminalRewriteInfo = diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts new file mode 100644 index 0000000000000..d3c919a6d8afe --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -0,0 +1,627 @@ +import {CompilerError} from '../CompilerError'; +import {inRange} from '../ReactiveScopes/InferReactiveScopeVariables'; +import {printDependency} from '../ReactiveScopes/PrintReactiveFunction'; +import { + Set_equal, + Set_filter, + Set_intersect, + Set_union, + getOrInsertDefault, +} from '../Utils/utils'; +import { + BasicBlock, + BlockId, + DependencyPathEntry, + GeneratedSource, + HIRFunction, + Identifier, + IdentifierId, + InstructionId, + InstructionValue, + ReactiveScopeDependency, + ScopeId, +} from './HIR'; + +const DEBUG_PRINT = false; + +/** + * Helper function for `PropagateScopeDependencies`. Uses control flow graph + * analysis to determine which `Identifier`s can be assumed to be non-null + * objects, on a per-block basis. + * + * Here is an example: + * ```js + * function useFoo(x, y, z) { + * // NOT safe to hoist PropertyLoads here + * if (...) { + * // safe to hoist loads from x + * read(x.a); + * return; + * } + * // safe to hoist loads from y, z + * read(y.b); + * if (...) { + * // safe to hoist loads from y, z + * read(z.a); + * } else { + * // safe to hoist loads from y, z + * read(z.b); + * } + * // safe to hoist loads from y, z + * return; + * } + * ``` + * + * Note that we currently do NOT account for mutable / declaration range when + * doing the CFG-based traversal, producing results that are technically + * incorrect but filtered by PropagateScopeDeps (which only takes dependencies + * on constructed value -- i.e. a scope's dependencies must have mutable ranges + * ending earlier than the scope start). + * + * Take this example, this function will infer x.foo.bar as non-nullable for + * bb0, via the intersection of bb1 & bb2 which in turn comes from bb3. This is + * technically incorrect bb0 is before / during x's mutable range. + * ``` + * bb0: + * const x = ...; + * if cond then bb1 else bb2 + * bb1: + * ... + * goto bb3 + * bb2: + * ... + * goto bb3: + * bb3: + * x.foo.bar + * ``` + * + * @param fn + * @param temporaries sidemap of identifier -> baseObject.a.b paths. Does not + * contain optional chains. + * @param hoistableFromOptionals sidemap of optionalBlock -> baseObject?.a + * optional paths for which it's safe to evaluate non-optional loads (see + * CollectOptionalChainDependencies). + * @returns + */ +export function collectHoistablePropertyLoads( + fn: HIRFunction, + temporaries: ReadonlyMap, + hoistableFromOptionals: ReadonlyMap, +): ReadonlyMap { + const registry = new PropertyPathRegistry(); + /** + * Due to current limitations of mutable range inference, there are edge cases in + * which we infer known-immutable values (e.g. props or hook params) to have a + * mutable range and scope. + * (see `destructure-array-declaration-to-context-var` fixture) + * We track known immutable identifiers to reduce regressions (as PropagateScopeDeps + * is being rewritten to HIR). + */ + const knownImmutableIdentifiers = new Set(); + if (fn.fnType === 'Component' || fn.fnType === 'Hook') { + for (const p of fn.params) { + if (p.kind === 'Identifier') { + knownImmutableIdentifiers.add(p.identifier.id); + } + } + } + return collectHoistablePropertyLoadsImpl(fn, { + temporaries, + knownImmutableIdentifiers, + hoistableFromOptionals, + registry, + nestedFnImmutableContext: null, + }); +} + +type CollectHoistablePropertyLoadsContext = { + temporaries: ReadonlyMap; + knownImmutableIdentifiers: ReadonlySet; + hoistableFromOptionals: ReadonlyMap; + registry: PropertyPathRegistry; + /** + * (For nested / inner function declarations) + * Context variables (i.e. captured from an outer scope) that are immutable. + * Note that this technically could be merged into `knownImmutableIdentifiers`, + * but are currently kept separate for readability. + */ + nestedFnImmutableContext: ReadonlySet | null; +}; +function collectHoistablePropertyLoadsImpl( + fn: HIRFunction, + context: CollectHoistablePropertyLoadsContext, +): ReadonlyMap { + const functionExpressionLoads = collectFunctionExpressionFakeLoads(fn); + const actuallyEvaluatedTemporaries = new Map( + [...context.temporaries].filter(([id]) => !functionExpressionLoads.has(id)), + ); + + const nodes = collectNonNullsInBlocks(fn, { + ...context, + temporaries: actuallyEvaluatedTemporaries, + }); + propagateNonNull(fn, nodes, context.registry); + + if (DEBUG_PRINT) { + console.log('(printing hoistable nodes in blocks)'); + for (const [blockId, node] of nodes) { + console.log( + `bb${blockId}: ${[...node.assumedNonNullObjects].map(n => printDependency(n.fullPath)).join(' ')}`, + ); + } + } + + return nodes; +} + +export function keyByScopeId( + fn: HIRFunction, + source: ReadonlyMap, +): ReadonlyMap { + const keyedByScopeId = new Map(); + for (const [_, block] of fn.body.blocks) { + if (block.terminal.kind === 'scope') { + keyedByScopeId.set( + block.terminal.scope.id, + source.get(block.terminal.block)!, + ); + } + } + return keyedByScopeId; +} + +export type BlockInfo = { + block: BasicBlock; + assumedNonNullObjects: ReadonlySet; +}; + +/** + * PropertyLoadRegistry data structure to dedupe property loads (e.g. a.b.c) + * and make computing sets intersections simpler. + */ +type RootNode = { + properties: Map; + optionalProperties: Map; + parent: null; + // Recorded to make later computations simpler + fullPath: ReactiveScopeDependency; + hasOptional: boolean; + root: IdentifierId; +}; + +type PropertyPathNode = + | { + properties: Map; + optionalProperties: Map; + parent: PropertyPathNode; + fullPath: ReactiveScopeDependency; + hasOptional: boolean; + } + | RootNode; + +class PropertyPathRegistry { + roots: Map = new Map(); + + getOrCreateIdentifier(identifier: Identifier): PropertyPathNode { + /** + * Reads from a statically scoped variable are always safe in JS, + * with the exception of TDZ (not addressed by this pass). + */ + let rootNode = this.roots.get(identifier.id); + + if (rootNode === undefined) { + rootNode = { + root: identifier.id, + properties: new Map(), + optionalProperties: new Map(), + fullPath: { + identifier, + path: [], + }, + hasOptional: false, + parent: null, + }; + this.roots.set(identifier.id, rootNode); + } + return rootNode; + } + + static getOrCreatePropertyEntry( + parent: PropertyPathNode, + entry: DependencyPathEntry, + ): PropertyPathNode { + const map = entry.optional ? parent.optionalProperties : parent.properties; + let child = map.get(entry.property); + if (child == null) { + child = { + properties: new Map(), + optionalProperties: new Map(), + parent: parent, + fullPath: { + identifier: parent.fullPath.identifier, + path: parent.fullPath.path.concat(entry), + }, + hasOptional: parent.hasOptional || entry.optional, + }; + map.set(entry.property, child); + } + return child; + } + + getOrCreateProperty(n: ReactiveScopeDependency): PropertyPathNode { + /** + * We add ReactiveScopeDependencies according to instruction ordering, + * so all subpaths of a PropertyLoad should already exist + * (e.g. a.b is added before a.b.c), + */ + let currNode = this.getOrCreateIdentifier(n.identifier); + if (n.path.length === 0) { + return currNode; + } + for (let i = 0; i < n.path.length - 1; i++) { + currNode = PropertyPathRegistry.getOrCreatePropertyEntry( + currNode, + n.path[i], + ); + } + + return PropertyPathRegistry.getOrCreatePropertyEntry( + currNode, + n.path.at(-1)!, + ); + } +} + +function getMaybeNonNullInInstruction( + instr: InstructionValue, + context: CollectHoistablePropertyLoadsContext, +): PropertyPathNode | null { + let path = null; + if (instr.kind === 'PropertyLoad') { + path = context.temporaries.get(instr.object.identifier.id) ?? { + identifier: instr.object.identifier, + path: [], + }; + } else if (instr.kind === 'Destructure') { + path = context.temporaries.get(instr.value.identifier.id) ?? null; + } else if (instr.kind === 'ComputedLoad') { + path = context.temporaries.get(instr.object.identifier.id) ?? null; + } + return path != null ? context.registry.getOrCreateProperty(path) : null; +} + +function isImmutableAtInstr( + identifier: Identifier, + instr: InstructionId, + context: CollectHoistablePropertyLoadsContext, +): boolean { + if (context.nestedFnImmutableContext != null) { + /** + * Comparing instructions ids across inner-outer function bodies is not valid, as they are numbered + */ + return context.nestedFnImmutableContext.has(identifier.id); + } else { + /** + * Since this runs *after* buildReactiveScopeTerminals, identifier mutable ranges + * are not valid with respect to current instruction id numbering. + * We use attached reactive scope ranges as a proxy for mutable range, but this + * is an overestimate as (1) scope ranges merge and align to form valid program + * blocks and (2) passes like MemoizeFbtAndMacroOperands may assign scopes to + * non-mutable identifiers. + * + * See comment in exported function for why we track known immutable identifiers. + */ + const mutableAtInstr = + identifier.mutableRange.end > identifier.mutableRange.start + 1 && + identifier.scope != null && + inRange( + { + id: instr, + }, + identifier.scope.range, + ); + return ( + !mutableAtInstr || context.knownImmutableIdentifiers.has(identifier.id) + ); + } +} + +function collectNonNullsInBlocks( + fn: HIRFunction, + context: CollectHoistablePropertyLoadsContext, +): ReadonlyMap { + /** + * Known non-null objects such as functional component props can be safely + * read from any block. + */ + const knownNonNullIdentifiers = new Set(); + if ( + fn.fnType === 'Component' && + fn.params.length > 0 && + fn.params[0].kind === 'Identifier' + ) { + const identifier = fn.params[0].identifier; + knownNonNullIdentifiers.add( + context.registry.getOrCreateIdentifier(identifier), + ); + } + const nodes = new Map(); + for (const [_, block] of fn.body.blocks) { + const assumedNonNullObjects = new Set( + knownNonNullIdentifiers, + ); + + const maybeOptionalChain = context.hoistableFromOptionals.get(block.id); + if (maybeOptionalChain != null) { + assumedNonNullObjects.add( + context.registry.getOrCreateProperty(maybeOptionalChain), + ); + } + for (const instr of block.instructions) { + const maybeNonNull = getMaybeNonNullInInstruction(instr.value, context); + if ( + maybeNonNull != null && + isImmutableAtInstr(maybeNonNull.fullPath.identifier, instr.id, context) + ) { + assumedNonNullObjects.add(maybeNonNull); + } + if ( + (instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod') && + !fn.env.config.enableTreatFunctionDepsAsConditional + ) { + const innerFn = instr.value.loweredFunc; + 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); + } + } + } + + nodes.set(block.id, { + block, + assumedNonNullObjects, + }); + } + return nodes; +} + +function propagateNonNull( + fn: HIRFunction, + nodes: ReadonlyMap, + registry: PropertyPathRegistry, +): void { + const blockSuccessors = new Map>(); + const terminalPreds = new Set(); + + for (const [blockId, block] of fn.body.blocks) { + for (const pred of block.preds) { + getOrInsertDefault(blockSuccessors, pred, new Set()).add(blockId); + } + if (block.terminal.kind === 'throw' || block.terminal.kind === 'return') { + terminalPreds.add(blockId); + } + } + + /** + * In the context of a control flow graph, the identifiers that a block + * can assume are non-null can be calculated from the following: + * X = Union(Intersect(X_neighbors), X) + */ + function recursivelyPropagateNonNull( + nodeId: BlockId, + direction: 'forward' | 'backward', + traversalState: Map, + ): boolean { + /** + * Avoid re-visiting computed or currently active nodes, which can + * occur when the control flow graph has backedges. + */ + if (traversalState.has(nodeId)) { + return false; + } + traversalState.set(nodeId, 'active'); + + const node = nodes.get(nodeId); + if (node == null) { + CompilerError.invariant(false, { + reason: `Bad node ${nodeId}, kind: ${direction}`, + loc: GeneratedSource, + }); + } + const neighbors = Array.from( + direction === 'backward' + ? (blockSuccessors.get(nodeId) ?? []) + : node.block.preds, + ); + + let changed = false; + for (const pred of neighbors) { + if (!traversalState.has(pred)) { + const neighborChanged = recursivelyPropagateNonNull( + pred, + direction, + traversalState, + ); + changed ||= neighborChanged; + } + } + /** + * Note that a predecessor / successor can only be active (status != 'done') + * if it is a self-loop or other transitive cycle. Active neighbors can be + * filtered out (i.e. not included in the intersection) + * Example: self loop. + * X = Union(Intersect(X, ...X_other_neighbors), X) + * + * Example: transitive cycle through node Y, for some Y that is a + * predecessor / successor of X. + * X = Union( + * Intersect( + * Union(Intersect(X, ...Y_other_neighbors), Y), + * ...X_neighbors + * ), + * X + * ) + * + * Non-active neighbors with no recorded results can occur due to backedges. + * it's not safe to assume they can be filtered out (e.g. not included in + * the intersection) + */ + const neighborAccesses = Set_intersect( + Array.from(neighbors) + .filter(n => traversalState.get(n) === 'done') + .map(n => assertNonNull(nodes.get(n)).assumedNonNullObjects), + ); + + const prevObjects = assertNonNull(nodes.get(nodeId)).assumedNonNullObjects; + const mergedObjects = Set_union(prevObjects, neighborAccesses); + reduceMaybeOptionalChains(mergedObjects, registry); + + assertNonNull(nodes.get(nodeId)).assumedNonNullObjects = mergedObjects; + traversalState.set(nodeId, 'done'); + /** + * Note that it's not sufficient to compare set sizes since + * reduceMaybeOptionalChains may replace optional-chain loads with + * unconditional loads. This could in turn change `assumedNonNullObjects` of + * downstream blocks and backedges. + */ + changed ||= !Set_equal(prevObjects, mergedObjects); + return changed; + } + const traversalState = new Map(); + const reversedBlocks = [...fn.body.blocks]; + reversedBlocks.reverse(); + + let changed; + let i = 0; + do { + CompilerError.invariant(i++ < 100, { + reason: + '[CollectHoistablePropertyLoads] fixed point iteration did not terminate after 100 loops', + loc: GeneratedSource, + }); + + changed = false; + for (const [blockId] of fn.body.blocks) { + const forwardChanged = recursivelyPropagateNonNull( + blockId, + 'forward', + traversalState, + ); + changed ||= forwardChanged; + } + traversalState.clear(); + for (const [blockId] of reversedBlocks) { + const backwardChanged = recursivelyPropagateNonNull( + blockId, + 'backward', + traversalState, + ); + changed ||= backwardChanged; + } + traversalState.clear(); + } while (changed); +} + +export function assertNonNull, U>( + value: T | null | undefined, + source?: string, +): T { + CompilerError.invariant(value != null, { + reason: 'Unexpected null', + description: source != null ? `(from ${source})` : null, + loc: GeneratedSource, + }); + return value; +} + +/** + * Any two optional chains with different operations . vs ?. but the same set of + * property strings paths de-duplicates. + * + * Intuitively: given ?.b, we know to be either hoistable or not. + * If unconditional reads from are hoistable, we can replace all + * ?.PROPERTY_STRING subpaths with .PROPERTY_STRING + */ +function reduceMaybeOptionalChains( + nodes: Set, + registry: PropertyPathRegistry, +): void { + let optionalChainNodes = Set_filter(nodes, n => n.hasOptional); + if (optionalChainNodes.size === 0) { + return; + } + let changed: boolean; + do { + changed = false; + + for (const original of optionalChainNodes) { + let {identifier, path: origPath} = original.fullPath; + let currNode: PropertyPathNode = + registry.getOrCreateIdentifier(identifier); + for (let i = 0; i < origPath.length; i++) { + const entry = origPath[i]; + // If the base is known to be non-null, replace with a non-optional load + const nextEntry: DependencyPathEntry = + entry.optional && nodes.has(currNode) + ? {property: entry.property, optional: false} + : entry; + currNode = PropertyPathRegistry.getOrCreatePropertyEntry( + currNode, + nextEntry, + ); + } + if (currNode !== original) { + changed = true; + optionalChainNodes.delete(original); + optionalChainNodes.add(currNode); + nodes.delete(original); + nodes.add(currNode); + } + } + } while (changed); +} + +function collectFunctionExpressionFakeLoads( + fn: HIRFunction, +): Set { + const sources = new Map(); + const functionExpressionReferences = new Set(); + + for (const [_, block] of fn.body.blocks) { + for (const {lvalue, value} of block.instructions) { + if ( + value.kind === 'FunctionExpression' || + value.kind === 'ObjectMethod' + ) { + for (const reference of value.loweredFunc.dependencies) { + let curr: IdentifierId | undefined = reference.identifier.id; + while (curr != null) { + functionExpressionReferences.add(curr); + curr = sources.get(curr); + } + } + } else if (value.kind === 'PropertyLoad') { + sources.set(lvalue.identifier.id, value.object.identifier.id); + } + } + } + return functionExpressionReferences; +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts new file mode 100644 index 0000000000000..2b7c9f2134e71 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectOptionalChainDependencies.ts @@ -0,0 +1,402 @@ +import {CompilerError} from '..'; +import {assertNonNull} from './CollectHoistablePropertyLoads'; +import { + BlockId, + BasicBlock, + IdentifierId, + ReactiveScopeDependency, + BranchTerminal, + TInstruction, + PropertyLoad, + StoreLocal, + GotoVariant, + TBasicBlock, + OptionalTerminal, + HIRFunction, + DependencyPathEntry, + Instruction, + Terminal, +} from './HIR'; +import {printIdentifier} from './PrintHIR'; + +export function collectOptionalChainSidemap( + fn: HIRFunction, +): OptionalChainSidemap { + const context: OptionalTraversalContext = { + currFn: fn, + blocks: fn.body.blocks, + seenOptionals: new Set(), + processedInstrsInOptional: new Set(), + temporariesReadInOptional: new Map(), + hoistableObjects: new Map(), + }; + traverseFunction(fn, context); + return { + temporariesReadInOptional: context.temporariesReadInOptional, + processedInstrsInOptional: context.processedInstrsInOptional, + hoistableObjects: context.hoistableObjects, + }; +} +export type OptionalChainSidemap = { + /** + * Stores the correct property mapping (e.g. `a?.b` instead of `a.b`) for + * dependency calculation. Note that we currently do not store anything on + * outer phi nodes. + */ + temporariesReadInOptional: ReadonlyMap; + /** + * Records instructions (PropertyLoads, StoreLocals, and test terminals) + * processed in this pass. When extracting dependencies in + * PropagateScopeDependencies, these instructions are skipped. + * + * E.g. given a?.b + * ``` + * bb0 + * $0 = LoadLocal 'a' + * test $0 then=bb1 <- Avoid adding dependencies from these instructions, as + * bb1 the sidemap produced by readOptionalBlock already maps + * $1 = PropertyLoad $0.'b' <- $1 and $2 back to a?.b. Instead, we want to add a?.b + * StoreLocal $2 = $1 <- as a dependency when $1 or $2 are later used in either + * - an unhoistable expression within an outer optional + * block e.g. MethodCall + * - a phi node (if the entire optional value is hoistable) + * ``` + * + * Note that mapping blockIds to their evaluated dependency path does not + * work, since values produced by inner optional chains may be referenced in + * outer ones + * ``` + * a?.b.c() + * -> + * bb0 + * $0 = LoadLocal 'a' + * test $0 then=bb1 + * bb1 + * $1 = PropertyLoad $0.'b' + * StoreLocal $2 = $1 + * goto bb2 + * bb2 + * test $2 then=bb3 + * bb3: + * $3 = PropertyLoad $2.'c' + * StoreLocal $4 = $3 + * goto bb4 + * bb4 + * test $4 then=bb5 + * bb5: + * $5 = MethodCall $2.$4() <--- here, we want to take a dep on $2 and $4! + * ``` + * + * Also note that InstructionIds are not unique across inner functions. + */ + processedInstrsInOptional: ReadonlySet; + /** + * Records optional chains for which we can safely evaluate non-optional + * PropertyLoads. e.g. given `a?.b.c`, we can evaluate any load from `a?.b` at + * the optional terminal in bb1. + * ```js + * bb1: + * ... + * Optional optional=false test=bb2 fallth=... + * bb2: + * Optional optional=true test=bb3 fallth=... + * ... + * ``` + */ + hoistableObjects: ReadonlyMap; +}; + +type OptionalTraversalContext = { + currFn: HIRFunction; + blocks: ReadonlyMap; + + // Track optional blocks to avoid outer calls into nested optionals + seenOptionals: Set; + + processedInstrsInOptional: Set; + temporariesReadInOptional: Map; + hoistableObjects: Map; +}; + +function traverseFunction( + fn: HIRFunction, + context: OptionalTraversalContext, +): void { + for (const [_, block] of fn.body.blocks) { + for (const instr of block.instructions) { + if ( + instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod' + ) { + traverseFunction(instr.value.loweredFunc.func, { + ...context, + currFn: instr.value.loweredFunc.func, + blocks: instr.value.loweredFunc.func.body.blocks, + }); + } + } + if ( + block.terminal.kind === 'optional' && + !context.seenOptionals.has(block.id) + ) { + traverseOptionalBlock( + block as TBasicBlock, + context, + null, + ); + } + } +} +/** + * Match the consequent and alternate blocks of an optional. + * @returns propertyload computed by the consequent block, or null if the + * consequent block is not a simple PropertyLoad. + */ +function matchOptionalTestBlock( + terminal: BranchTerminal, + blocks: ReadonlyMap, +): { + consequentId: IdentifierId; + property: string; + propertyId: IdentifierId; + storeLocalInstr: Instruction; + consequentGoto: BlockId; +} | null { + const consequentBlock = assertNonNull(blocks.get(terminal.consequent)); + if ( + consequentBlock.instructions.length === 2 && + consequentBlock.instructions[0].value.kind === 'PropertyLoad' && + consequentBlock.instructions[1].value.kind === 'StoreLocal' + ) { + const propertyLoad: TInstruction = consequentBlock + .instructions[0] as TInstruction; + const storeLocal: StoreLocal = consequentBlock.instructions[1].value; + const storeLocalInstr = consequentBlock.instructions[1]; + CompilerError.invariant( + propertyLoad.value.object.identifier.id === terminal.test.identifier.id, + { + reason: + '[OptionalChainDeps] Inconsistent optional chaining property load', + description: `Test=${printIdentifier(terminal.test.identifier)} PropertyLoad base=${printIdentifier(propertyLoad.value.object.identifier)}`, + loc: propertyLoad.loc, + }, + ); + + CompilerError.invariant( + storeLocal.value.identifier.id === propertyLoad.lvalue.identifier.id, + { + reason: '[OptionalChainDeps] Unexpected storeLocal', + loc: propertyLoad.loc, + }, + ); + if ( + consequentBlock.terminal.kind !== 'goto' || + consequentBlock.terminal.variant !== GotoVariant.Break + ) { + return null; + } + const alternate = assertNonNull(blocks.get(terminal.alternate)); + + CompilerError.invariant( + alternate.instructions.length === 2 && + alternate.instructions[0].value.kind === 'Primitive' && + alternate.instructions[1].value.kind === 'StoreLocal', + { + reason: 'Unexpected alternate structure', + loc: terminal.loc, + }, + ); + + return { + consequentId: storeLocal.lvalue.place.identifier.id, + property: propertyLoad.value.property, + propertyId: propertyLoad.lvalue.identifier.id, + storeLocalInstr, + consequentGoto: consequentBlock.terminal.block, + }; + } + return null; +} + +/** + * Traverse into the optional block and all transitively referenced blocks to + * collect sidemaps of optional chain dependencies. + * + * @returns the IdentifierId representing the optional block if the block and + * all transitively referenced optional blocks precisely represent a chain of + * property loads. If any part of the optional chain is not hoistable, returns + * null. + */ +function traverseOptionalBlock( + optional: TBasicBlock, + context: OptionalTraversalContext, + outerAlternate: BlockId | null, +): IdentifierId | null { + context.seenOptionals.add(optional.id); + const maybeTest = context.blocks.get(optional.terminal.test)!; + let test: BranchTerminal; + let baseObject: ReactiveScopeDependency; + if (maybeTest.terminal.kind === 'branch') { + CompilerError.invariant(optional.terminal.optional, { + reason: '[OptionalChainDeps] Expect base case to be always optional', + loc: optional.terminal.loc, + }); + /** + * Optional base expressions are currently within value blocks which cannot + * be interrupted by scope boundaries. As such, the only dependencies we can + * hoist out of optional chains are property load chains with no intervening + * instructions. + * + * Ideally, we would be able to flatten base instructions out of optional + * blocks, but this would require changes to HIR. + * + * For now, only match base expressions that are straightforward + * PropertyLoad chains + */ + if ( + maybeTest.instructions.length === 0 || + maybeTest.instructions[0].value.kind !== 'LoadLocal' + ) { + return null; + } + const path: Array = []; + for (let i = 1; i < maybeTest.instructions.length; i++) { + const instrVal = maybeTest.instructions[i].value; + const prevInstr = maybeTest.instructions[i - 1]; + if ( + instrVal.kind === 'PropertyLoad' && + instrVal.object.identifier.id === prevInstr.lvalue.identifier.id + ) { + path.push({property: instrVal.property, optional: false}); + } else { + return null; + } + } + CompilerError.invariant( + maybeTest.terminal.test.identifier.id === + maybeTest.instructions.at(-1)!.lvalue.identifier.id, + { + reason: '[OptionalChainDeps] Unexpected test expression', + loc: maybeTest.terminal.loc, + }, + ); + baseObject = { + identifier: maybeTest.instructions[0].value.place.identifier, + path, + }; + test = maybeTest.terminal; + } else if (maybeTest.terminal.kind === 'optional') { + /** + * This is either + * - ?.property (optional=true) + * - .property (optional=false) + * - + * - a optional base block with a separate nested optional-chain (e.g. a(c?.d)?.d) + */ + const testBlock = context.blocks.get(maybeTest.terminal.fallthrough)!; + if (testBlock!.terminal.kind !== 'branch') { + /** + * Fallthrough of the inner optional should be a block with no + * instructions, terminating with Test($) + */ + CompilerError.throwTodo({ + reason: `Unexpected terminal kind \`${testBlock.terminal.kind}\` for optional fallthrough block`, + loc: maybeTest.terminal.loc, + }); + } + /** + * Recurse into inner optional blocks to collect inner optional-chain + * expressions, regardless of whether we can match the outer one to a + * PropertyLoad. + */ + const innerOptional = traverseOptionalBlock( + maybeTest as TBasicBlock, + context, + testBlock.terminal.alternate, + ); + if (innerOptional == null) { + return null; + } + + /** + * Check that the inner optional is part of the same optional-chain as the + * outer one. This is not guaranteed, e.g. given a(c?.d)?.d + * ``` + * bb0: + * Optional test=bb1 + * bb1: + * $0 = LoadLocal a <-- part 1 of the outer optional-chaining base + * Optional test=bb2 fallth=bb5 <-- start of optional chain for c?.d + * bb2: + * ... (optional chain for c?.d) + * ... + * bb5: + * $1 = phi(c.d, undefined) <-- part 2 (continuation) of the outer optional-base + * $2 = Call $0($1) + * Branch $2 ... + * ``` + */ + if (testBlock.terminal.test.identifier.id !== innerOptional) { + return null; + } + + if (!optional.terminal.optional) { + /** + * If this is an non-optional load participating in an optional chain + * (e.g. loading the `c` property in `a?.b.c`), record that PropertyLoads + * from the inner optional value are hoistable. + */ + context.hoistableObjects.set( + optional.id, + assertNonNull(context.temporariesReadInOptional.get(innerOptional)), + ); + } + baseObject = assertNonNull( + context.temporariesReadInOptional.get(innerOptional), + ); + test = testBlock.terminal; + } else { + return null; + } + + if (test.alternate === outerAlternate) { + CompilerError.invariant(optional.instructions.length === 0, { + reason: + '[OptionalChainDeps] Unexpected instructions an inner optional block. ' + + 'This indicates that the compiler may be incorrectly concatenating two unrelated optional chains', + loc: optional.terminal.loc, + }); + } + const matchConsequentResult = matchOptionalTestBlock(test, context.blocks); + if (!matchConsequentResult) { + // Optional chain consequent is not hoistable e.g. a?.[computed()] + return null; + } + CompilerError.invariant( + matchConsequentResult.consequentGoto === optional.terminal.fallthrough, + { + reason: '[OptionalChainDeps] Unexpected optional goto-fallthrough', + description: `${matchConsequentResult.consequentGoto} != ${optional.terminal.fallthrough}`, + loc: optional.terminal.loc, + }, + ); + const load = { + identifier: baseObject.identifier, + path: [ + ...baseObject.path, + { + property: matchConsequentResult.property, + optional: optional.terminal.optional, + }, + ], + }; + context.processedInstrsInOptional.add(matchConsequentResult.storeLocalInstr); + context.processedInstrsInOptional.add(test); + context.temporariesReadInOptional.set( + matchConsequentResult.consequentId, + load, + ); + context.temporariesReadInOptional.set(matchConsequentResult.propertyId, load); + return matchConsequentResult.consequentId; +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/DeriveMinimalDependenciesHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/DeriveMinimalDependenciesHIR.ts new file mode 100644 index 0000000000000..f5567b3e536d1 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/DeriveMinimalDependenciesHIR.ts @@ -0,0 +1,361 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {CompilerError} from '../CompilerError'; +import { + DependencyPathEntry, + GeneratedSource, + Identifier, + ReactiveScopeDependency, +} from '../HIR'; +import {printIdentifier} from '../HIR/PrintHIR'; +import {ReactiveScopePropertyDependency} from '../ReactiveScopes/DeriveMinimalDependencies'; + +/** + * Simpler fork of DeriveMinimalDependencies, see PropagateScopeDependenciesHIR + * for detailed explanation. + */ +export class ReactiveScopeDependencyTreeHIR { + /** + * Paths from which we can hoist PropertyLoads. If an `identifier`, + * `identifier.path`, or `identifier?.path` is in this map, it is safe to + * evaluate (non-optional) PropertyLoads from. + */ + #hoistableObjects: Map = new Map(); + #deps: Map = new Map(); + + /** + * @param hoistableObjects a set of paths from which we can safely evaluate + * PropertyLoads. Note that we expect these to not contain duplicates (e.g. + * both `a?.b` and `a.b`) only because CollectHoistablePropertyLoads merges + * duplicates when traversing the CFG. + */ + constructor(hoistableObjects: Iterable) { + for (const {path, identifier} of hoistableObjects) { + let currNode = ReactiveScopeDependencyTreeHIR.#getOrCreateRoot( + identifier, + this.#hoistableObjects, + path.length > 0 && path[0].optional ? 'Optional' : 'NonNull', + ); + + for (let i = 0; i < path.length; i++) { + const prevAccessType = currNode.properties.get( + path[i].property, + )?.accessType; + const accessType = + i + 1 < path.length && path[i + 1].optional ? 'Optional' : 'NonNull'; + CompilerError.invariant( + prevAccessType == null || prevAccessType === accessType, + { + reason: 'Conflicting access types', + loc: GeneratedSource, + }, + ); + let nextNode = currNode.properties.get(path[i].property); + if (nextNode == null) { + nextNode = { + properties: new Map(), + accessType, + }; + currNode.properties.set(path[i].property, nextNode); + } + currNode = nextNode; + } + } + } + + static #getOrCreateRoot( + identifier: Identifier, + roots: Map>, + defaultAccessType: T, + ): TreeNode { + // roots can always be accessed unconditionally in JS + let rootNode = roots.get(identifier); + + if (rootNode === undefined) { + rootNode = { + properties: new Map(), + accessType: defaultAccessType, + }; + roots.set(identifier, rootNode); + } + return rootNode; + } + + /** + * Join a dependency with `#hoistableObjects` to record the hoistable + * dependency. This effectively truncates @param dep to its maximal + * safe-to-evaluate subpath + */ + addDependency(dep: ReactiveScopePropertyDependency): void { + const {identifier, path} = dep; + let depCursor = ReactiveScopeDependencyTreeHIR.#getOrCreateRoot( + identifier, + this.#deps, + PropertyAccessType.UnconditionalAccess, + ); + /** + * hoistableCursor is null if depCursor is not an object we can hoist + * property reads from otherwise, it represents the same node in the + * hoistable / cfg-informed tree + */ + let hoistableCursor: HoistableNode | undefined = + this.#hoistableObjects.get(identifier); + + // All properties read 'on the way' to a dependency are marked as 'access' + for (const entry of path) { + let nextHoistableCursor: HoistableNode | undefined; + let nextDepCursor: DependencyNode; + if (entry.optional) { + /** + * No need to check the access type since we can match both optional or non-optionals + * in the hoistable + * e.g. a?.b is hoistable if a.b is hoistable + */ + if (hoistableCursor != null) { + nextHoistableCursor = hoistableCursor?.properties.get(entry.property); + } + + let accessType; + if ( + hoistableCursor != null && + hoistableCursor.accessType === 'NonNull' + ) { + /** + * For an optional chain dep `a?.b`: if the hoistable tree only + * contains `a`, we can keep either `a?.b` or 'a.b' as a dependency. + * (note that we currently do the latter for perf) + */ + accessType = PropertyAccessType.UnconditionalAccess; + } else { + /** + * Given that it's safe to evaluate `depCursor` and optional load + * never throws, it's also safe to evaluate `depCursor?.entry` + */ + accessType = PropertyAccessType.OptionalAccess; + } + nextDepCursor = makeOrMergeProperty( + depCursor, + entry.property, + accessType, + ); + } else if ( + hoistableCursor != null && + hoistableCursor.accessType === 'NonNull' + ) { + nextHoistableCursor = hoistableCursor.properties.get(entry.property); + nextDepCursor = makeOrMergeProperty( + depCursor, + entry.property, + PropertyAccessType.UnconditionalAccess, + ); + } else { + /** + * Break to truncate the dependency on its first non-optional entry that PropertyLoads are not hoistable from + */ + break; + } + depCursor = nextDepCursor; + hoistableCursor = nextHoistableCursor; + } + // mark the final node as a dependency + depCursor.accessType = merge( + depCursor.accessType, + PropertyAccessType.OptionalDependency, + ); + } + + deriveMinimalDependencies(): Set { + const results = new Set(); + for (const [rootId, rootNode] of this.#deps.entries()) { + collectMinimalDependenciesInSubtree(rootNode, rootId, [], results); + } + + return results; + } + + /* + * Prints dependency tree to string for debugging. + * @param includeAccesses + * @returns string representation of DependencyTree + */ + printDeps(includeAccesses: boolean): string { + let res: Array> = []; + + for (const [rootId, rootNode] of this.#deps.entries()) { + const rootResults = printSubtree(rootNode, includeAccesses).map( + result => `${printIdentifier(rootId)}.${result}`, + ); + res.push(rootResults); + } + return res.flat().join('\n'); + } + + static debug(roots: Map>): string { + const buf: Array = [`tree() [`]; + for (const [rootId, rootNode] of roots) { + buf.push(`${printIdentifier(rootId)} (${rootNode.accessType}):`); + this.#debugImpl(buf, rootNode, 1); + } + buf.push(']'); + return buf.length > 2 ? buf.join('\n') : buf.join(''); + } + + static #debugImpl( + buf: Array, + node: TreeNode, + depth: number = 0, + ): void { + for (const [property, childNode] of node.properties) { + buf.push(`${' '.repeat(depth)}.${property} (${childNode.accessType}):`); + this.#debugImpl(buf, childNode, depth + 1); + } + } +} + +/* + * Enum representing the access type of single property on a parent object. + * We distinguish on two independent axes: + * Optional / Unconditional: + * - whether this property is an optional load (within an optional chain) + * Access / Dependency: + * - Access: this property is read on the path of a dependency. We do not + * need to track change variables for accessed properties. Tracking accesses + * helps Forget do more granular dependency tracking. + * - Dependency: this property is read as a dependency and we must track changes + * to it for correctness. + * ```javascript + * // props.a is a dependency here and must be tracked + * deps: {props.a, props.a.b} ---> minimalDeps: {props.a} + * // props.a is just an access here and does not need to be tracked + * deps: {props.a.b} ---> minimalDeps: {props.a.b} + * ``` + */ +enum PropertyAccessType { + OptionalAccess = 'OptionalAccess', + UnconditionalAccess = 'UnconditionalAccess', + OptionalDependency = 'OptionalDependency', + UnconditionalDependency = 'UnconditionalDependency', +} + +function isOptional(access: PropertyAccessType): boolean { + return ( + access === PropertyAccessType.OptionalAccess || + access === PropertyAccessType.OptionalDependency + ); +} +function isDependency(access: PropertyAccessType): boolean { + return ( + access === PropertyAccessType.OptionalDependency || + access === PropertyAccessType.UnconditionalDependency + ); +} + +function merge( + access1: PropertyAccessType, + access2: PropertyAccessType, +): PropertyAccessType { + const resultIsUnconditional = !(isOptional(access1) && isOptional(access2)); + const resultIsDependency = isDependency(access1) || isDependency(access2); + + /* + * Straightforward merge. + * This can be represented as bitwise OR, but is written out for readability + * + * Observe that `UnconditionalAccess | ConditionalDependency` produces an + * unconditionally accessed conditional dependency. We currently use these + * as we use unconditional dependencies. (i.e. to codegen change variables) + */ + if (resultIsUnconditional) { + if (resultIsDependency) { + return PropertyAccessType.UnconditionalDependency; + } else { + return PropertyAccessType.UnconditionalAccess; + } + } else { + // result is optional + if (resultIsDependency) { + return PropertyAccessType.OptionalDependency; + } else { + return PropertyAccessType.OptionalAccess; + } + } +} + +type TreeNode = { + properties: Map>; + accessType: T; +}; +type HoistableNode = TreeNode<'Optional' | 'NonNull'>; +type DependencyNode = TreeNode; + +/** + * TODO: this is directly pasted from DeriveMinimalDependencies. Since we no + * longer have conditionally accessed nodes, we can simplify + * + * Recursively calculates minimal dependencies in a subtree. + * @param node DependencyNode representing a dependency subtree. + * @returns a minimal list of dependencies in this subtree. + */ +function collectMinimalDependenciesInSubtree( + node: DependencyNode, + rootIdentifier: Identifier, + path: Array, + results: Set, +): void { + if (isDependency(node.accessType)) { + results.add({identifier: rootIdentifier, path}); + } else { + for (const [childName, childNode] of node.properties) { + collectMinimalDependenciesInSubtree( + childNode, + rootIdentifier, + [ + ...path, + { + property: childName, + optional: isOptional(childNode.accessType), + }, + ], + results, + ); + } + } +} + +function printSubtree( + node: DependencyNode, + includeAccesses: boolean, +): Array { + const results: Array = []; + for (const [propertyName, propertyNode] of node.properties) { + if (includeAccesses || isDependency(propertyNode.accessType)) { + results.push(`${propertyName} (${propertyNode.accessType})`); + } + const propertyResults = printSubtree(propertyNode, includeAccesses); + results.push(...propertyResults.map(result => `${propertyName}.${result}`)); + } + return results; +} + +function makeOrMergeProperty( + node: DependencyNode, + property: string, + accessType: PropertyAccessType, +): DependencyNode { + let child = node.properties.get(property); + if (child == null) { + child = { + properties: new Map(), + accessType, + }; + node.properties.set(property, child); + } else { + child.accessType = merge(child.accessType, accessType); + } + return child; +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index 7eef0a7cc24b9..fa581d8ed8d81 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -16,7 +16,8 @@ import { DEFAULT_SHAPES, Global, GlobalRegistry, - installReAnimatedTypes, + getReanimatedModuleType, + installTypeConfig, } from './Globals'; import { BlockId, @@ -28,9 +29,11 @@ import { NonLocalBinding, PolyType, ScopeId, + SourceLocation, Type, ValidatedIdentifier, ValueKind, + getHookKindForType, makeBlockId, makeIdentifierId, makeIdentifierName, @@ -45,6 +48,15 @@ import { addHook, } from './ObjectShape'; import {Scope as BabelScope} from '@babel/traverse'; +import {TypeSchema} from './TypeSchema'; + +export const ReactElementSymbolSchema = z.object({ + elementSymbol: z.union([ + z.literal('react.element'), + z.literal('react.transitional.element'), + ]), + globalDevVar: z.string(), +}); export const ExternalFunctionSchema = z.object({ // Source for the imported module that exports the `importSpecifierName` functions @@ -57,8 +69,8 @@ export const ExternalFunctionSchema = z.object({ export const InstrumentationSchema = z .object({ fn: ExternalFunctionSchema, - gating: ExternalFunctionSchema.nullish(), - globalGating: z.string().nullish(), + gating: ExternalFunctionSchema.nullable(), + globalGating: z.string().nullable(), }) .refine( opts => opts.gating != null || opts.globalGating != null, @@ -135,7 +147,13 @@ export type Hook = z.infer; */ const EnvironmentConfigSchema = z.object({ - customHooks: z.map(z.string(), HookSchema).optional().default(new Map()), + customHooks: z.map(z.string(), HookSchema).default(new Map()), + + /** + * A function that, given the name of a module, can optionally return a description + * of that module's type signature. + */ + moduleTypeProvider: z.nullable(z.function().args(z.string())).default(null), /** * A list of functions which the application compiles as macros, where @@ -213,7 +231,60 @@ const EnvironmentConfigSchema = z.object({ */ enableUseTypeAnnotations: z.boolean().default(false), - enableReactiveScopesInHIR: z.boolean().default(true), + enableFunctionDependencyRewrite: z.boolean().default(true), + + /** + * Enables inference of optional dependency chains. Without this flag + * a property chain such as `props?.items?.foo` will infer as a dep on + * just `props`. With this flag enabled, we'll infer that full path as + * the dependency. + */ + enableOptionalDependencies: z.boolean().default(true), + + /** + * Enables inference and auto-insertion of effect dependencies. Takes in an array of + * configurable module and import pairs to allow for user-land experimentation. For example, + * [ + * { + * module: 'react', + * imported: 'useEffect', + * numRequiredArgs: 1, + * },{ + * module: 'MyExperimentalEffectHooks', + * imported: 'useExperimentalEffect', + * numRequiredArgs: 2, + * }, + * ] + * would insert dependencies for calls of `useEffect` imported from `react` and calls of + * useExperimentalEffect` from `MyExperimentalEffectHooks`. + * + * `numRequiredArgs` tells the compiler the amount of arguments required to append a dependency + * array to the end of the call. With the configuration above, we'd insert dependencies for + * `useEffect` if it is only given a single argument and it would be appended to the argument list. + * + * numRequiredArgs must always be greater than 0, otherwise there is no function to analyze for dependencies + * + * Still experimental. + */ + inferEffectDependencies: z + .nullable( + z.array( + z.object({ + function: ExternalFunctionSchema, + numRequiredArgs: z.number(), + }), + ), + ) + .default(null), + + /** + * Enables inlining ReactElement object literals in place of JSX + * An alternative to the standard JSX transform which replaces JSX with React's jsxProd() runtime + * Currently a prod-only optimization, requiring Fast JSX dependencies + * + * The symbol configuration is set for backwards compatability with pre-React 19 transforms + */ + inlineJsxTransform: ReactElementSymbolSchema.nullable().default(null), /* * Enable validation of hooks to partially check that the component honors the rules of hooks. @@ -223,7 +294,7 @@ const EnvironmentConfigSchema = z.object({ validateHooksUsage: z.boolean().default(true), // Validate that ref values (`ref.current`) are not accessed during render. - validateRefAccessDuringRender: z.boolean().default(false), + validateRefAccessDuringRender: z.boolean().default(true), /* * Validates that setState is not unconditionally called during render, as it can lead to @@ -237,6 +308,12 @@ const EnvironmentConfigSchema = z.object({ */ validateNoSetStateInPassiveEffects: z.boolean().default(false), + /** + * Validates against creating JSX within a try block and recommends using an error boundary + * instead. + */ + validateNoJSXInTryStatements: z.boolean().default(false), + /** * Validates that the dependencies of all effect hooks are memoized. This helps ensure * that Forget does not introduce infinite renders caused by a dependency changing, @@ -298,9 +375,9 @@ const EnvironmentConfigSchema = z.object({ * } * } */ - enableEmitFreeze: ExternalFunctionSchema.nullish(), + enableEmitFreeze: ExternalFunctionSchema.nullable().default(null), - enableEmitHookGuards: ExternalFunctionSchema.nullish(), + enableEmitHookGuards: ExternalFunctionSchema.nullable().default(null), /** * Enable instruction reordering. See InstructionReordering.ts for the details @@ -314,6 +391,54 @@ const EnvironmentConfigSchema = z.object({ */ enableFunctionOutlining: z.boolean().default(true), + /** + * If enabled, this will outline nested JSX into a separate component. + * + * This will enable the compiler to memoize the separate component, giving us + * the same behavior as compiling _within_ the callback. + * + * ``` + * function Component(countries, onDelete) { + * const name = useFoo(); + * return countries.map(() => { + * return ( + * + * {name} + * + * + * ); + * }); + * } + * ``` + * + * will be transpiled to: + * + * ``` + * function Component(countries, onDelete) { + * const name = useFoo(); + * return countries.map(() => { + * return ( + * + * ); + * }); + * } + * + * function Temp({name, onDelete}) { + * return ( + * + * {name} + * + * + * ); + * } + * + * Both, `Component` and `Temp` will then be memoized by the compiler. + * + * With this change, when `countries` is updated by adding one single value, + * only the newly added value is re-rendered and not the entire list. + */ + enableJsxOutlining: z.boolean().default(false), + /* * Enables instrumentation codegen. This emits a dev-mode only call to an * instrumentation function, for components and hooks that Forget compiles. @@ -336,7 +461,7 @@ const EnvironmentConfigSchema = z.object({ * } * */ - enableEmitInstrumentForget: InstrumentationSchema.nullish(), + enableEmitInstrumentForget: InstrumentationSchema.nullable().default(null), // Enable validation of mutable ranges assertValidMutableRanges: z.boolean().default(false), @@ -375,8 +500,6 @@ const EnvironmentConfigSchema = z.object({ */ throwUnknownException__testonly: z.boolean().default(false), - enableSharedRuntime__testonly: z.boolean().default(false), - /** * Enables deps of a function epxression to be treated as conditional. This * makes sure we don't load a dep when it's a property (to check if it has @@ -414,7 +537,8 @@ const EnvironmentConfigSchema = z.object({ * computed one. This detects cases where rules of react violations may cause the * compiled code to behave differently than the original. */ - enableChangeDetectionForDebugging: ExternalFunctionSchema.nullish(), + enableChangeDetectionForDebugging: + ExternalFunctionSchema.nullable().default(null), /** * The react native re-animated library uses custom Babel transforms that @@ -454,7 +578,7 @@ const EnvironmentConfigSchema = z.object({ * * Here the variables `ref` and `myRef` will be typed as Refs. */ - enableTreatRefLikeIdentifiersAsRefs: z.boolean().nullable().default(false), + enableTreatRefLikeIdentifiersAsRefs: z.boolean().default(false), /* * If specified a value, the compiler lowers any calls to `useContext` to use @@ -476,12 +600,80 @@ const EnvironmentConfigSchema = z.object({ * const {foo, bar} = useCompiledContext(MyContext, (c) => [c.foo, c.bar]); * ``` */ - lowerContextAccess: ExternalFunctionSchema.nullish(), + lowerContextAccess: ExternalFunctionSchema.nullable().default(null), }); export type EnvironmentConfig = z.infer; -export function parseConfigPragma(pragma: string): EnvironmentConfig { +/** + * For test fixtures and playground only. + * + * Pragmas are straightforward to parse for boolean options (`:true` and + * `:false`). These are 'enabled' config values for non-boolean configs (i.e. + * what is used when parsing `:true`). + */ +const testComplexConfigDefaults: PartialEnvironmentConfig = { + validateNoCapitalizedCalls: [], + enableChangeDetectionForDebugging: { + source: 'react-compiler-runtime', + importSpecifierName: '$structuralCheck', + }, + enableEmitFreeze: { + source: 'react-compiler-runtime', + importSpecifierName: 'makeReadOnly', + }, + enableEmitInstrumentForget: { + fn: { + source: 'react-compiler-runtime', + importSpecifierName: 'useRenderCounter', + }, + gating: { + source: 'react-compiler-runtime', + importSpecifierName: 'shouldInstrument', + }, + globalGating: '__DEV__', + }, + enableEmitHookGuards: { + source: 'react-compiler-runtime', + importSpecifierName: '$dispatcherGuard', + }, + inlineJsxTransform: { + elementSymbol: 'react.transitional.element', + globalDevVar: 'DEV', + }, + lowerContextAccess: { + source: 'react-compiler-runtime', + importSpecifierName: 'useContext_withSelector', + }, + inferEffectDependencies: [ + { + function: { + source: 'react', + importSpecifierName: 'useEffect', + }, + numRequiredArgs: 1, + }, + { + function: { + source: 'shared-runtime', + importSpecifierName: 'useSpecialEffect', + }, + numRequiredArgs: 2, + }, + { + function: { + source: 'useEffectWrapper', + importSpecifierName: 'default', + }, + numRequiredArgs: 1, + }, + ], +}; + +/** + * For snap test fixtures and playground only. + */ +export function parseConfigPragmaForTests(pragma: string): EnvironmentConfig { const maybeConfig: any = {}; // Get the defaults to programmatically check for boolean properties const defaultConfig = EnvironmentConfigSchema.parse({}); @@ -491,21 +683,12 @@ export function parseConfigPragma(pragma: string): EnvironmentConfig { continue; } const keyVal = token.slice(1); - let [key, val]: any = keyVal.split(':'); - - if (key === 'validateNoCapitalizedCalls') { - maybeConfig[key] = []; - continue; - } + let [key, val = undefined] = keyVal.split(':'); + const isSet = val === undefined || val === 'true'; - if ( - key === 'enableChangeDetectionForDebugging' && - (val === undefined || val === 'true') - ) { - maybeConfig[key] = { - source: 'react-compiler-runtime', - importSpecifierName: '$structuralCheck', - }; + if (isSet && key in testComplexConfigDefaults) { + maybeConfig[key] = + testComplexConfigDefaults[key as keyof PartialEnvironmentConfig]; continue; } @@ -520,7 +703,6 @@ export function parseConfigPragma(pragma: string): EnvironmentConfig { props.push({type: 'name', name: elt}); } } - console.log([valSplit[0], props.map(x => x.name ?? '*').join('.')]); maybeConfig[key] = [[valSplit[0], props]]; } continue; @@ -531,11 +713,10 @@ export function parseConfigPragma(pragma: string): EnvironmentConfig { continue; } if (val === undefined || val === 'true') { - val = true; + maybeConfig[key] = true; } else { - val = false; + maybeConfig[key] = false; } - maybeConfig[key] = val; } const config = EnvironmentConfigSchema.safeParse(maybeConfig); @@ -571,6 +752,7 @@ export function printFunctionType(type: ReactFunctionType): string { export class Environment { #globals: GlobalRegistry; #shapes: ShapeRegistry; + #moduleTypes: Map = new Map(); #nextIdentifer: number = 0; #nextBlock: number = 0; #nextScope: number = 0; @@ -647,7 +829,8 @@ export class Environment { } if (config.enableCustomTypeDefinitionForReanimated) { - installReAnimatedTypes(this.#globals, this.#shapes); + const reanimatedModuleType = getReanimatedModuleType(this.#shapes); + this.#moduleTypes.set(REANIMATED_MODULE_NAME, reanimatedModuleType); } this.#contextIdentifiers = contextIdentifiers; @@ -692,7 +875,42 @@ export class Environment { return this.#outlinedFunctions; } - getGlobalDeclaration(binding: NonLocalBinding): Global | null { + #resolveModuleType(moduleName: string, loc: SourceLocation): Global | null { + let moduleType = this.#moduleTypes.get(moduleName); + if (moduleType === undefined) { + if (this.config.moduleTypeProvider == null) { + return null; + } + const unparsedModuleConfig = this.config.moduleTypeProvider(moduleName); + if (unparsedModuleConfig != null) { + const parsedModuleConfig = TypeSchema.safeParse(unparsedModuleConfig); + if (!parsedModuleConfig.success) { + CompilerError.throwInvalidConfig({ + reason: `Could not parse module type, the configured \`moduleTypeProvider\` function returned an invalid module description`, + description: parsedModuleConfig.error.toString(), + loc, + }); + } + const moduleConfig = parsedModuleConfig.data; + moduleType = installTypeConfig( + this.#globals, + this.#shapes, + moduleConfig, + moduleName, + loc, + ); + } else { + moduleType = null; + } + this.#moduleTypes.set(moduleName, moduleType); + } + return moduleType; + } + + getGlobalDeclaration( + binding: NonLocalBinding, + loc: SourceLocation, + ): Global | null { if (this.config.hookPattern != null) { const match = new RegExp(this.config.hookPattern).exec(binding.name); if ( @@ -727,9 +945,37 @@ export class Environment { */ return ( this.#globals.get(binding.imported) ?? - (isHookName(binding.imported) ? this.#getCustomHookType() : null) + (isHookName(binding.imported) || isHookName(binding.name) + ? this.#getCustomHookType() + : null) ); } else { + const moduleType = this.#resolveModuleType(binding.module, loc); + if (moduleType !== null) { + const importedType = this.getPropertyType( + moduleType, + binding.imported, + ); + if (importedType != null) { + /* + * Check that hook-like export names are hook types, and non-hook names are non-hook types. + * The user-assigned alias isn't decidable by the type provider, so we ignore that for the check. + * Thus we allow `import {fooNonHook as useFoo} from ...` because the name and type both say + * that it's not a hook. + */ + const expectHook = isHookName(binding.imported); + const isHook = getHookKindForType(this, importedType) != null; + if (expectHook !== isHook) { + CompilerError.throwInvalidConfig({ + reason: `Invalid type configuration for module`, + description: `Expected type for \`import {${binding.imported}} from '${binding.module}'\` ${expectHook ? 'to be a hook' : 'not to be a hook'} based on the exported name`, + loc, + }); + } + return importedType; + } + } + /** * For modules we don't own, we look at whether the original name or import alias * are hook-like. Both of the following are likely hooks so we would return a hook @@ -752,6 +998,34 @@ export class Environment { (isHookName(binding.name) ? this.#getCustomHookType() : null) ); } else { + const moduleType = this.#resolveModuleType(binding.module, loc); + if (moduleType !== null) { + let importedType: Type | null = null; + if (binding.kind === 'ImportDefault') { + const defaultType = this.getPropertyType(moduleType, 'default'); + if (defaultType !== null) { + importedType = defaultType; + } + } else { + importedType = moduleType; + } + if (importedType !== null) { + /* + * Check that the hook-like modules are defined as types, and non hook-like modules are not typed as hooks. + * So `import Foo from 'useFoo'` is expected to be a hook based on the module name + */ + const expectHook = isHookName(binding.module); + const isHook = getHookKindForType(this, importedType) != null; + if (expectHook !== isHook) { + CompilerError.throwInvalidConfig({ + reason: `Invalid type configuration for module`, + description: `Expected type for \`import ... from '${binding.module}'\` ${expectHook ? 'to be a hook' : 'not to be a hook'} based on the module name`, + loc, + }); + } + return importedType; + } + } return isHookName(binding.name) ? this.#getCustomHookType() : null; } } @@ -761,9 +1035,7 @@ export class Environment { #isKnownReactModule(moduleName: string): boolean { return ( moduleName.toLowerCase() === 'react' || - moduleName.toLowerCase() === 'react-dom' || - (this.config.enableSharedRuntime__testonly && - moduleName === 'shared-runtime') + moduleName.toLowerCase() === 'react-dom' ); } @@ -829,6 +1101,8 @@ export class Environment { } } +const REANIMATED_MODULE_NAME = 'react-native-reanimated'; + // From https://github.com/facebook/react/blob/main/packages/eslint-plugin-react-hooks/src/RulesOfHooks.js#LL18C1-L23C2 export function isHookName(name: string): boolean { return /^use[A-Z0-9]/.test(name); @@ -880,3 +1154,5 @@ export function tryParseExternalFunction( suggestions: null, }); } + +export const DEFAULT_EXPORT = 'default'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts index e9066f85b8193..2525b87bd86bc 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts @@ -9,6 +9,7 @@ import {Effect, ValueKind, ValueReason} from './HIR'; import { BUILTIN_SHAPES, BuiltInArrayId, + BuiltInMixedReadonlyId, BuiltInUseActionStateId, BuiltInUseContextHookId, BuiltInUseEffectHookId, @@ -24,7 +25,11 @@ import { addHook, addObject, } from './ObjectShape'; -import {BuiltInType, PolyType} from './Types'; +import {BuiltInType, ObjectType, PolyType} from './Types'; +import {TypeConfig} from './TypeSchema'; +import {assertExhaustive} from '../Utils/utils'; +import {isHookName} from './Environment'; +import {CompilerError, SourceLocation} from '..'; /* * This file exports types and defaults for JavaScript global objects. @@ -359,6 +364,17 @@ const REACT_APIS: Array<[string, BuiltInType]> = [ returnValueKind: ValueKind.Mutable, }), ], + [ + 'useImperativeHandle', + addHook(DEFAULT_SHAPES, { + positionalParams: [], + restParam: Effect.Freeze, + returnType: {kind: 'Primitive'}, + calleeEffect: Effect.Read, + hookKind: 'useImperativeHandle', + returnValueKind: ValueKind.Frozen, + }), + ], [ 'useMemo', addHook(DEFAULT_SHAPES, { @@ -528,10 +544,115 @@ DEFAULT_GLOBALS.set( addObject(DEFAULT_SHAPES, 'global', TYPED_GLOBALS), ); -export function installReAnimatedTypes( +export function installTypeConfig( globals: GlobalRegistry, - registry: ShapeRegistry, -): void { + shapes: ShapeRegistry, + typeConfig: TypeConfig, + moduleName: string, + loc: SourceLocation, +): Global { + switch (typeConfig.kind) { + case 'type': { + switch (typeConfig.name) { + case 'Array': { + return {kind: 'Object', shapeId: BuiltInArrayId}; + } + case 'MixedReadonly': { + return {kind: 'Object', shapeId: BuiltInMixedReadonlyId}; + } + case 'Primitive': { + return {kind: 'Primitive'}; + } + case 'Ref': { + return {kind: 'Object', shapeId: BuiltInUseRefId}; + } + case 'Any': { + return {kind: 'Poly'}; + } + default: { + assertExhaustive( + typeConfig.name, + `Unexpected type '${(typeConfig as any).name}'`, + ); + } + } + } + case 'function': { + return addFunction(shapes, [], { + positionalParams: typeConfig.positionalParams, + restParam: typeConfig.restParam, + calleeEffect: typeConfig.calleeEffect, + returnType: installTypeConfig( + globals, + shapes, + typeConfig.returnType, + moduleName, + loc, + ), + returnValueKind: typeConfig.returnValueKind, + noAlias: typeConfig.noAlias === true, + mutableOnlyIfOperandsAreMutable: + typeConfig.mutableOnlyIfOperandsAreMutable === true, + }); + } + case 'hook': { + return addHook(shapes, { + hookKind: 'Custom', + positionalParams: typeConfig.positionalParams ?? [], + restParam: typeConfig.restParam ?? Effect.Freeze, + calleeEffect: Effect.Read, + returnType: installTypeConfig( + globals, + shapes, + typeConfig.returnType, + moduleName, + loc, + ), + returnValueKind: typeConfig.returnValueKind ?? ValueKind.Frozen, + noAlias: typeConfig.noAlias === true, + }); + } + case 'object': { + return addObject( + shapes, + null, + Object.entries(typeConfig.properties ?? {}).map(([key, value]) => { + const type = installTypeConfig( + globals, + shapes, + value, + moduleName, + loc, + ); + const expectHook = isHookName(key); + let isHook = false; + if (type.kind === 'Function' && type.shapeId !== null) { + const functionType = shapes.get(type.shapeId); + if (functionType?.functionType?.hookKind !== null) { + isHook = true; + } + } + if (expectHook !== isHook) { + CompilerError.throwInvalidConfig({ + reason: `Invalid type configuration for module`, + description: `Expected type for object property '${key}' from module '${moduleName}' ${expectHook ? 'to be a hook' : 'not to be a hook'} based on the property name`, + loc, + }); + } + return [key, type]; + }), + ); + } + default: { + assertExhaustive( + typeConfig, + `Unexpected type kind '${(typeConfig as any).kind}'`, + ); + } + } +} + +export function getReanimatedModuleType(registry: ShapeRegistry): ObjectType { // hooks that freeze args and return frozen value const frozenHooks = [ 'useFrameCallback', @@ -541,8 +662,9 @@ export function installReAnimatedTypes( 'useAnimatedReaction', 'useWorkletCallback', ]; + const reanimatedType: Array<[string, BuiltInType]> = []; for (const hook of frozenHooks) { - globals.set( + reanimatedType.push([ hook, addHook(registry, { positionalParams: [], @@ -553,7 +675,7 @@ export function installReAnimatedTypes( calleeEffect: Effect.Read, hookKind: 'Custom', }), - ); + ]); } /** @@ -562,7 +684,7 @@ export function installReAnimatedTypes( */ const mutableHooks = ['useSharedValue', 'useDerivedValue']; for (const hook of mutableHooks) { - globals.set( + reanimatedType.push([ hook, addHook(registry, { positionalParams: [], @@ -573,7 +695,7 @@ export function installReAnimatedTypes( calleeEffect: Effect.Read, hookKind: 'Custom', }), - ); + ]); } // functions that return mutable value @@ -587,7 +709,7 @@ export function installReAnimatedTypes( 'executeOnUIRuntimeSync', ]; for (const fn of funcs) { - globals.set( + reanimatedType.push([ fn, addFunction(registry, [], { positionalParams: [], @@ -597,6 +719,8 @@ export function installReAnimatedTypes( returnValueKind: ValueKind.Mutable, noAlias: true, }), - ); + ]); } + + return addObject(registry, null, reanimatedType); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts index e61665ce4c6bb..954fb6f40053a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts @@ -12,6 +12,7 @@ import {assertExhaustive} from '../Utils/utils'; import {Environment, ReactFunctionType} from './Environment'; import {HookKind} from './ObjectShape'; import {Type, makeType} from './Types'; +import {z} from 'zod'; /* * ******************************************************************************************* @@ -284,7 +285,8 @@ export type HIRFunction = { fnType: ReactFunctionType; env: Environment; params: Array; - returnType: t.FlowType | t.TSType | null; + returnTypeAnnotation: t.FlowType | t.TSType | null; + returnType: Type; context: Array; effects: Array | null; body: HIR; @@ -365,6 +367,7 @@ export type BasicBlock = { preds: Set; phis: Set; }; +export type TBasicBlock = BasicBlock & {terminal: T}; /* * Terminal nodes generally represent statements that affect control flow, such as @@ -489,7 +492,7 @@ export type BranchTerminal = { alternate: BlockId; id: InstructionId; loc: SourceLocation; - fallthrough?: never; + fallthrough: BlockId; }; export type SwitchTerminal = { @@ -744,6 +747,9 @@ export enum InstructionKind { // hoisted const declarations HoistedLet = 'HoistedLet', + + HoistedFunction = 'HoistedFunction', + Function = 'Function', } function _staticInvariantInstructionValueHasLocation( @@ -755,9 +761,8 @@ function _staticInvariantInstructionValueHasLocation( export type Phi = { kind: 'Phi'; - id: Identifier; - operands: Map; - type: Type; + place: Place; + operands: Map; }; /** @@ -775,7 +780,7 @@ export type ManualMemoDependency = { value: Place; } | {kind: 'Global'; identifierName: string}; - path: Array; + path: DependencyPath; }; export type StartMemoize = { @@ -864,18 +869,13 @@ export type InstructionValue = kind: | InstructionKind.Let | InstructionKind.HoistedConst - | InstructionKind.HoistedLet; + | InstructionKind.HoistedLet + | InstructionKind.HoistedFunction; place: Place; }; loc: SourceLocation; } - | { - kind: 'StoreLocal'; - lvalue: LValue; - value: Place; - type: t.FlowType | t.TSType | null; - loc: SourceLocation; - } + | StoreLocal | { kind: 'StoreContext'; lvalue: { @@ -920,15 +920,7 @@ export type InstructionValue = type: Type; loc: SourceLocation; } - | { - kind: 'JsxExpression'; - tag: Place | BuiltinTag; - props: Array; - children: Array | null; // null === no children - loc: SourceLocation; - openingLoc: SourceLocation; - closingLoc: SourceLocation; - } + | JsxExpression | { kind: 'ObjectExpression'; properties: Array; @@ -1074,6 +1066,16 @@ export type InstructionValue = loc: SourceLocation; }; +export type JsxExpression = { + kind: 'JsxExpression'; + tag: Place | BuiltinTag; + props: Array; + children: Array | null; // null === no children + loc: SourceLocation; + openingLoc: SourceLocation; + closingLoc: SourceLocation; +}; + export type JsxAttribute = | {kind: 'JsxSpreadAttribute'; argument: Place} | {kind: 'JsxAttribute'; name: string; place: Place}; @@ -1118,6 +1120,13 @@ export type Primitive = { export type JSXText = {kind: 'JSXText'; value: string; loc: SourceLocation}; +export type StoreLocal = { + kind: 'StoreLocal'; + lvalue: LValue; + value: Place; + type: t.FlowType | t.TSType | null; + loc: SourceLocation; +}; export type PropertyLoad = { kind: 'PropertyLoad'; object: Place; @@ -1234,6 +1243,17 @@ export function makeTemporaryIdentifier( }; } +export function forkTemporaryIdentifier( + id: IdentifierId, + source: Identifier, +): Identifier { + return { + ...source, + mutableRange: {start: makeInstructionId(0), end: makeInstructionId(0)}, + id, + }; +} + /** * Creates a valid identifier name. This should *not* be used for synthesizing * identifier names: only call this method for identifier names that appear in the @@ -1360,6 +1380,15 @@ export enum ValueKind { Context = 'context', } +export const ValueKindSchema = z.enum([ + ValueKind.MaybeFrozen, + ValueKind.Frozen, + ValueKind.Primitive, + ValueKind.Global, + ValueKind.Mutable, + ValueKind.Context, +]); + // The effect with which a value is modified. export enum Effect { // Default value: not allowed after lifetime inference @@ -1389,6 +1418,15 @@ export enum Effect { Store = 'store', } +export const EffectSchema = z.enum([ + Effect.Read, + Effect.Mutate, + Effect.ConditionallyMutate, + Effect.Capture, + Effect.Store, + Effect.Freeze, +]); + export function isMutableEffect( effect: Effect, location: SourceLocation, @@ -1473,11 +1511,38 @@ export type ReactiveScopeDeclaration = { scope: ReactiveScope; // the scope in which the variable was originally declared }; +export type DependencyPathEntry = {property: string; optional: boolean}; +export type DependencyPath = Array; export type ReactiveScopeDependency = { identifier: Identifier; - path: Array; + path: DependencyPath; }; +export function areEqualPaths(a: DependencyPath, b: DependencyPath): boolean { + return ( + a.length === b.length && + a.every( + (item, ix) => + item.property === b[ix].property && item.optional === b[ix].optional, + ) + ); +} + +export function getPlaceScope( + id: InstructionId, + place: Place, +): ReactiveScope | null { + const scope = place.identifier.scope; + if (scope !== null && isScopeActive(scope, id)) { + return scope; + } + return null; +} + +function isScopeActive(scope: ReactiveScope, id: InstructionId): boolean { + return id >= scope.range.start && id < scope.range.end; +} + /* * Simulated opaque type for BlockIds to prevent using normal numbers as block ids * accidentally. @@ -1591,6 +1656,10 @@ export function isUseStateType(id: Identifier): boolean { return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInUseState'; } +export function isRefOrRefValue(id: Identifier): boolean { + return isUseRefType(id) || isRefValueType(id); +} + export function isSetStateType(id: Identifier): boolean { return id.type.kind === 'Function' && id.type.shapeId === 'BuiltInSetState'; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts index c694cf310fb39..9202f2145f27e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts @@ -912,3 +912,23 @@ export function clonePlaceToTemporary(env: Environment, place: Place): Place { temp.reactive = place.reactive; return temp; } + +/** + * Fix scope and identifier ranges to account for renumbered instructions + */ +export function fixScopeAndIdentifierRanges(func: HIR): void { + for (const [, block] of func.blocks) { + const terminal = block.terminal; + if (terminal.kind === 'scope' || terminal.kind === 'pruned-scope') { + /* + * Scope ranges should always align to start at the 'scope' terminal + * and end at the first instruction of the fallthrough block + */ + const fallthroughBlock = func.blocks.get(terminal.fallthrough)!; + const firstId = + fallthroughBlock.instructions[0]?.id ?? fallthroughBlock.terminal.id; + terminal.scope.range.start = terminal.id; + terminal.scope.range.end = firstId; + } + } +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/MergeConsecutiveBlocks.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/MergeConsecutiveBlocks.ts index 98721f636f8f9..ea132b772aa44 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/MergeConsecutiveBlocks.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/MergeConsecutiveBlocks.ts @@ -84,20 +84,14 @@ export function mergeConsecutiveBlocks(fn: HIRFunction): void { id: predecessor.terminal.id, lvalue: { kind: 'Identifier', - identifier: phi.id, + identifier: phi.place.identifier, effect: Effect.ConditionallyMutate, reactive: false, loc: GeneratedSource, }, value: { kind: 'LoadLocal', - place: { - kind: 'Identifier', - identifier: operand, - effect: Effect.Read, - reactive: false, - loc: GeneratedSource, - }, + place: {...operand}, loc: GeneratedSource, }, loc: GeneratedSource, diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/MergeOverlappingReactiveScopesHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/MergeOverlappingReactiveScopesHIR.ts index 98645d5c549af..a3740539b295b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/MergeOverlappingReactiveScopesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/MergeOverlappingReactiveScopesHIR.ts @@ -5,7 +5,7 @@ import { ReactiveScope, makeInstructionId, } from '.'; -import {getPlaceScope} from '../ReactiveScopes/BuildReactiveBlocks'; +import {getPlaceScope} from '../HIR/HIR'; import {isMutable} from '../ReactiveScopes/InferReactiveScopeVariables'; import DisjointSet from '../Utils/DisjointSet'; import {getOrInsertDefault} from '../Utils/utils'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/ObjectShape.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/ObjectShape.ts index 9554878578ca0..14f809f2c4082 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/ObjectShape.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/ObjectShape.ts @@ -127,6 +127,7 @@ export type HookKind = | 'useMemo' | 'useCallback' | 'useTransition' + | 'useImperativeHandle' | 'Custom'; /* @@ -317,6 +318,23 @@ addObject(BUILTIN_SHAPES, BuiltInArrayId, [ mutableOnlyIfOperandsAreMutable: true, }), ], + [ + 'flatMap', + addFunction(BUILTIN_SHAPES, [], { + positionalParams: [], + restParam: Effect.ConditionallyMutate, + returnType: {kind: 'Object', shapeId: BuiltInArrayId}, + /* + * callee is ConditionallyMutate because items of the array + * flow into the lambda and may be mutated there, even though + * the array object itself is not modified + */ + calleeEffect: Effect.ConditionallyMutate, + returnValueKind: ValueKind.Mutable, + noAlias: true, + mutableOnlyIfOperandsAreMutable: true, + }), + ], [ 'filter', addFunction(BUILTIN_SHAPES, [], { @@ -534,6 +552,17 @@ addObject(BUILTIN_SHAPES, BuiltInMixedReadonlyId, [ noAlias: true, }), ], + [ + 'flatMap', + addFunction(BUILTIN_SHAPES, [], { + positionalParams: [], + restParam: Effect.Read, + returnType: {kind: 'Object', shapeId: BuiltInArrayId}, + calleeEffect: Effect.ConditionallyMutate, + returnValueKind: ValueKind.Mutable, + noAlias: true, + }), + ], [ 'filter', addFunction(BUILTIN_SHAPES, [], { diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts index 59f067787359f..526ab7c7e52bb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts @@ -72,6 +72,7 @@ export function printFunction(fn: HIRFunction): string { if (definition.length !== 0) { output.push(definition); } + output.push(printType(fn.returnType)); output.push(printHIR(fn.body)); output.push(...fn.directives); return output.join('\n'); @@ -162,13 +163,13 @@ export function printInstruction(instr: ReactiveInstruction): string { export function printPhi(phi: Phi): string { const items = []; - items.push(printIdentifier(phi.id)); - items.push(printMutableRange(phi.id)); - items.push(printType(phi.type)); + items.push(printPlace(phi.place)); + items.push(printMutableRange(phi.place.identifier)); + items.push(printType(phi.place.identifier.type)); items.push(': phi('); const phis = []; - for (const [blockId, id] of phi.operands) { - phis.push(`bb${blockId}: ${printIdentifier(id)}`); + for (const [blockId, place] of phi.operands) { + phis.push(`bb${blockId}: ${printPlace(place)}`); } items.push(phis.join(', ')); @@ -190,7 +191,7 @@ export function printTerminal(terminal: Terminal): Array | string { case 'branch': { value = `[${terminal.id}] Branch (${printPlace(terminal.test)}) then:bb${ terminal.consequent - } else:bb${terminal.alternate}`; + } else:bb${terminal.alternate} fallthrough:bb${terminal.fallthrough}`; break; } case 'logical': { @@ -555,7 +556,8 @@ export function printInstructionValue(instrValue: ReactiveValue): string { } }) .join(', ') ?? ''; - value = `${kind} ${name} @deps[${deps}] @context[${context}] @effects[${effects}]:\n${fn}`; + const type = printType(instrValue.loweredFunc.func.returnType).trim(); + value = `${kind} ${name} @deps[${deps}] @context[${context}] @effects[${effects}]${type !== '' ? ` return${type}` : ''}:\n${fn}`; break; } case 'TaggedTemplateExpression': { @@ -763,6 +765,12 @@ export function printLValue(lval: LValue): string { case InstructionKind.HoistedLet: { return `HoistedLet ${lvalue}$`; } + case InstructionKind.Function: { + return `Function ${lvalue}$`; + } + case InstructionKind.HoistedFunction: { + return `HoistedFunction ${lvalue}$`; + } default: { assertExhaustive(lval.kind, `Unexpected lvalue kind \`${lval.kind}\``); } @@ -867,7 +875,7 @@ export function printManualMemoDependency( ? val.root.value.identifier.name.value : printIdentifier(val.root.value.identifier); } - return `${rootStr}${val.path.length > 0 ? '.' : ''}${val.path.join('.')}`; + return `${rootStr}${val.path.map(v => `${v.optional ? '?.' : '.'}${v.property}`).join('')}`; } export function printType(type: Type): string { if (type.kind === 'Type') return ''; diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts new file mode 100644 index 0000000000000..8aed17f8ee847 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -0,0 +1,716 @@ +import { + ScopeId, + HIRFunction, + Place, + Instruction, + ReactiveScopeDependency, + Identifier, + ReactiveScope, + isObjectMethodType, + isRefValueType, + isUseRefType, + makeInstructionId, + InstructionId, + InstructionKind, + GeneratedSource, + DeclarationId, + areEqualPaths, + IdentifierId, + Terminal, +} from './HIR'; +import { + collectHoistablePropertyLoads, + keyByScopeId, +} from './CollectHoistablePropertyLoads'; +import { + ScopeBlockTraversal, + eachInstructionOperand, + eachInstructionValueOperand, + eachPatternOperand, + eachTerminalOperand, +} from './visitors'; +import {Stack, empty} from '../Utils/Stack'; +import {CompilerError} from '../CompilerError'; +import {Iterable_some} from '../Utils/utils'; +import {ReactiveScopeDependencyTreeHIR} from './DeriveMinimalDependenciesHIR'; +import {collectOptionalChainSidemap} from './CollectOptionalChainDependencies'; + +export function propagateScopeDependenciesHIR(fn: HIRFunction): void { + const usedOutsideDeclaringScope = + findTemporariesUsedOutsideDeclaringScope(fn); + const temporaries = collectTemporariesSidemap(fn, usedOutsideDeclaringScope); + const { + temporariesReadInOptional, + processedInstrsInOptional, + hoistableObjects, + } = collectOptionalChainSidemap(fn); + + const hoistablePropertyLoads = keyByScopeId( + fn, + collectHoistablePropertyLoads(fn, temporaries, hoistableObjects), + ); + + const scopeDeps = collectDependencies( + fn, + usedOutsideDeclaringScope, + new Map([...temporaries, ...temporariesReadInOptional]), + processedInstrsInOptional, + ); + + /** + * Derive the minimal set of hoistable dependencies for each scope. + */ + for (const [scope, deps] of scopeDeps) { + if (deps.length === 0) { + continue; + } + + /** + * Step 1: Find hoistable accesses, given the basic block in which the scope + * begins. + */ + const hoistables = hoistablePropertyLoads.get(scope.id); + CompilerError.invariant(hoistables != null, { + reason: '[PropagateScopeDependencies] Scope not found in tracked blocks', + loc: GeneratedSource, + }); + /** + * Step 2: Calculate hoistable dependencies. + */ + const tree = new ReactiveScopeDependencyTreeHIR( + [...hoistables.assumedNonNullObjects].map(o => o.fullPath), + ); + for (const dep of deps) { + tree.addDependency({...dep}); + } + + /** + * Step 3: Reduce dependencies to a minimal set. + */ + const candidates = tree.deriveMinimalDependencies(); + for (const candidateDep of candidates) { + if ( + !Iterable_some( + scope.dependencies, + existingDep => + existingDep.identifier.declarationId === + candidateDep.identifier.declarationId && + areEqualPaths(existingDep.path, candidateDep.path), + ) + ) + scope.dependencies.add(candidateDep); + } + } +} + +function findTemporariesUsedOutsideDeclaringScope( + fn: HIRFunction, +): ReadonlySet { + /* + * tracks all relevant LoadLocal and PropertyLoad lvalues + * and the scope where they are defined + */ + const declarations = new Map(); + const prunedScopes = new Set(); + const scopeTraversal = new ScopeBlockTraversal(); + const usedOutsideDeclaringScope = new Set(); + + function handlePlace(place: Place): void { + const declaringScope = declarations.get(place.identifier.declarationId); + if ( + declaringScope != null && + !scopeTraversal.isScopeActive(declaringScope) && + !prunedScopes.has(declaringScope) + ) { + // Declaring scope is not active === used outside declaring scope + usedOutsideDeclaringScope.add(place.identifier.declarationId); + } + } + + function handleInstruction(instr: Instruction): void { + const scope = scopeTraversal.currentScope; + if (scope == null || prunedScopes.has(scope)) { + return; + } + switch (instr.value.kind) { + case 'LoadLocal': + case 'LoadContext': + case 'PropertyLoad': { + declarations.set(instr.lvalue.identifier.declarationId, scope); + break; + } + default: { + break; + } + } + } + + for (const [blockId, block] of fn.body.blocks) { + scopeTraversal.recordScopes(block); + const scopeStartInfo = scopeTraversal.blockInfos.get(blockId); + if (scopeStartInfo?.kind === 'begin' && scopeStartInfo.pruned) { + prunedScopes.add(scopeStartInfo.scope.id); + } + for (const instr of block.instructions) { + for (const place of eachInstructionOperand(instr)) { + handlePlace(place); + } + handleInstruction(instr); + } + + for (const place of eachTerminalOperand(block.terminal)) { + handlePlace(place); + } + } + return usedOutsideDeclaringScope; +} + +/** + * @returns mapping of LoadLocal and PropertyLoad to the source of the load. + * ```js + * // source + * foo(a.b); + * + * // HIR: a potential sidemap is {0: a, 1: a.b, 2: foo} + * $0 = LoadLocal 'a' + * $1 = PropertyLoad $0, 'b' + * $2 = LoadLocal 'foo' + * $3 = CallExpression $2($1) + * ``` + * @param usedOutsideDeclaringScope is used to check the correctness of + * reordering LoadLocal / PropertyLoad calls. We only track a LoadLocal / + * PropertyLoad in the returned temporaries map if reordering the read (from the + * time-of-load to time-of-use) is valid. + * + * If a LoadLocal or PropertyLoad instruction is within the reactive scope range + * (a proxy for mutable range) of the load source, later instructions may + * reassign / mutate the source value. Since it's incorrect to reorder these + * load instructions to after their scope ranges, we also do not store them in + * identifier sidemaps. + * + * Take this example (from fixture + * `evaluation-order-mutate-call-after-dependency-load`) + * ```js + * // source + * function useFoo(arg) { + * const arr = [1, 2, 3, ...arg]; + * return [ + * arr.length, + * arr.push(0) + * ]; + * } + * + * // IR pseudocode + * scope @0 { + * $0 = arr = ArrayExpression [1, 2, 3, ...arg] + * $1 = arr.length + * $2 = arr.push(0) + * } + * scope @1 { + * $3 = ArrayExpression [$1, $2] + * } + * ``` + * Here, it's invalid for scope@1 to take `arr.length` as a dependency instead + * of $1, as the evaluation of `arr.length` changes between instructions $1 and + * $3. We do not track $1 -> arr.length in this case. + */ +export function collectTemporariesSidemap( + fn: HIRFunction, + usedOutsideDeclaringScope: ReadonlySet, +): ReadonlyMap { + const temporaries = new Map(); + collectTemporariesSidemapImpl( + fn, + usedOutsideDeclaringScope, + temporaries, + false, + ); + return temporaries; +} + +/** + * Recursive collect a sidemap of all `LoadLocal` and `PropertyLoads` with a + * function and all nested functions. + * + * Note that IdentifierIds are currently unique, so we can use a single + * Map across all nested functions. + */ +function collectTemporariesSidemapImpl( + fn: HIRFunction, + usedOutsideDeclaringScope: ReadonlySet, + temporaries: Map, + isInnerFn: boolean, +): void { + for (const [_, block] of fn.body.blocks) { + for (const instr of block.instructions) { + const {value, lvalue} = instr; + const usedOutside = usedOutsideDeclaringScope.has( + lvalue.identifier.declarationId, + ); + + if (value.kind === 'PropertyLoad' && !usedOutside) { + if (!isInnerFn || temporaries.has(value.object.identifier.id)) { + /** + * All dependencies of a inner / nested function must have a base + * identifier from the outermost component / hook. This is because the + * compiler cannot break an inner function into multiple granular + * scopes. + */ + const property = getProperty( + value.object, + value.property, + false, + temporaries, + ); + temporaries.set(lvalue.identifier.id, property); + } + } else if ( + value.kind === 'LoadLocal' && + lvalue.identifier.name == null && + value.place.identifier.name !== null && + !usedOutside + ) { + if ( + !isInnerFn || + fn.context.some( + context => context.identifier.id === value.place.identifier.id, + ) + ) { + temporaries.set(lvalue.identifier.id, { + identifier: value.place.identifier, + path: [], + }); + } + } else if ( + value.kind === 'FunctionExpression' || + value.kind === 'ObjectMethod' + ) { + collectTemporariesSidemapImpl( + value.loweredFunc.func, + usedOutsideDeclaringScope, + temporaries, + true, + ); + } + } + } +} + +function getProperty( + object: Place, + propertyName: string, + optional: boolean, + temporaries: ReadonlyMap, +): ReactiveScopeDependency { + /* + * (1) Get the base object either from the temporary sidemap (e.g. a LoadLocal) + * or a deep copy of an existing property dependency. + * Example 1: + * $0 = LoadLocal x + * $1 = PropertyLoad $0.y + * getProperty($0, ...) -> resolvedObject = x, resolvedDependency = null + * + * Example 2: + * $0 = LoadLocal x + * $1 = PropertyLoad $0.y + * $2 = PropertyLoad $1.z + * getProperty($1, ...) -> resolvedObject = null, resolvedDependency = x.y + * + * Example 3: + * $0 = Call(...) + * $1 = PropertyLoad $0.y + * getProperty($0, ...) -> resolvedObject = null, resolvedDependency = null + */ + const resolvedDependency = temporaries.get(object.identifier.id); + + /** + * (2) Push the last PropertyLoad + * TODO(mofeiZ): understand optional chaining + */ + let property: ReactiveScopeDependency; + if (resolvedDependency == null) { + property = { + identifier: object.identifier, + path: [{property: propertyName, optional}], + }; + } else { + property = { + identifier: resolvedDependency.identifier, + path: [...resolvedDependency.path, {property: propertyName, optional}], + }; + } + return property; +} + +type Decl = { + id: InstructionId; + scope: Stack; +}; + +class Context { + #declarations: Map = new Map(); + #reassignments: Map = new Map(); + + #scopes: Stack = empty(); + // Reactive dependencies used in the current reactive scope. + #dependencies: Stack> = empty(); + deps: Map> = new Map(); + + #temporaries: ReadonlyMap; + #temporariesUsedOutsideScope: ReadonlySet; + + /** + * Tracks the traversal state. See Context.declare for explanation of why this + * is needed. + */ + inInnerFn: boolean = false; + + constructor( + temporariesUsedOutsideScope: ReadonlySet, + temporaries: ReadonlyMap, + ) { + this.#temporariesUsedOutsideScope = temporariesUsedOutsideScope; + this.#temporaries = temporaries; + } + + enterScope(scope: ReactiveScope): void { + // Set context for new scope + this.#dependencies = this.#dependencies.push([]); + this.#scopes = this.#scopes.push(scope); + } + + exitScope(scope: ReactiveScope, pruned: boolean): void { + // Save dependencies we collected from the exiting scope + const scopedDependencies = this.#dependencies.value; + CompilerError.invariant(scopedDependencies != null, { + reason: '[PropagateScopeDeps]: Unexpected scope mismatch', + loc: scope.loc, + }); + + // Restore context of previous scope + this.#scopes = this.#scopes.pop(); + this.#dependencies = this.#dependencies.pop(); + + /* + * Collect dependencies we recorded for the exiting scope and propagate + * them upward using the same rules as normal dependency collection. + * Child scopes may have dependencies on values created within the outer + * scope, which necessarily cannot be dependencies of the outer scope. + */ + for (const dep of scopedDependencies) { + if (this.#checkValidDependency(dep)) { + this.#dependencies.value?.push(dep); + } + } + + if (!pruned) { + this.deps.set(scope, scopedDependencies); + } + } + + isUsedOutsideDeclaringScope(place: Place): boolean { + return this.#temporariesUsedOutsideScope.has( + place.identifier.declarationId, + ); + } + + /* + * Records where a value was declared, and optionally, the scope where the + * value originated from. This is later used to determine if a dependency + * should be added to a scope; if the current scope we are visiting is the + * same scope where the value originates, it can't be a dependency on itself. + * + * Note that we do not track declarations or reassignments within inner + * functions for the following reasons: + * - inner functions cannot be split by scope boundaries and are guaranteed + * to consume their own declarations + * - reassignments within inner functions are tracked as context variables, + * which already have extended mutable ranges to account for reassignments + * - *most importantly* it's currently simply incorrect to compare inner + * function instruction ids (tracked by `decl`) with outer ones (as stored + * by root identifier mutable ranges). + */ + declare(identifier: Identifier, decl: Decl): void { + if (this.inInnerFn) return; + if (!this.#declarations.has(identifier.declarationId)) { + this.#declarations.set(identifier.declarationId, decl); + } + this.#reassignments.set(identifier, decl); + } + + // Checks if identifier is a valid dependency in the current scope + #checkValidDependency(maybeDependency: ReactiveScopeDependency): boolean { + // ref value is not a valid dep + if (isRefValueType(maybeDependency.identifier)) { + return false; + } + + /* + * object methods are not deps because they will be codegen'ed back in to + * the object literal. + */ + if (isObjectMethodType(maybeDependency.identifier)) { + return false; + } + + const identifier = maybeDependency.identifier; + /* + * If this operand is used in a scope, has a dynamic value, and was defined + * before this scope, then its a dependency of the scope. + */ + const currentDeclaration = + this.#reassignments.get(identifier) ?? + this.#declarations.get(identifier.declarationId); + const currentScope = this.currentScope.value; + return ( + currentScope != null && + currentDeclaration !== undefined && + currentDeclaration.id < currentScope.range.start + ); + } + + #isScopeActive(scope: ReactiveScope): boolean { + if (this.#scopes === null) { + return false; + } + return this.#scopes.find(state => state === scope); + } + + get currentScope(): Stack { + return this.#scopes; + } + + visitOperand(place: Place): void { + /* + * if this operand is a temporary created for a property load, try to resolve it to + * the expanded Place. Fall back to using the operand as-is. + */ + this.visitDependency( + this.#temporaries.get(place.identifier.id) ?? { + identifier: place.identifier, + path: [], + }, + ); + } + + visitProperty(object: Place, property: string, optional: boolean): void { + const nextDependency = getProperty( + object, + property, + optional, + this.#temporaries, + ); + this.visitDependency(nextDependency); + } + + visitDependency(maybeDependency: ReactiveScopeDependency): void { + /* + * Any value used after its originally defining scope has concluded must be added as an + * output of its defining scope. Regardless of whether its a const or not, + * some later code needs access to the value. If the current + * scope we are visiting is the same scope where the value originates, it can't be a dependency + * on itself. + */ + + /* + * if originalDeclaration is undefined here, then this is not a local var + * (all decls e.g. `let x;` should be initialized in BuildHIR) + */ + const originalDeclaration = this.#declarations.get( + maybeDependency.identifier.declarationId, + ); + if ( + originalDeclaration !== undefined && + originalDeclaration.scope.value !== null + ) { + originalDeclaration.scope.each(scope => { + if ( + !this.#isScopeActive(scope) && + !Iterable_some( + scope.declarations.values(), + decl => + decl.identifier.declarationId === + maybeDependency.identifier.declarationId, + ) + ) { + scope.declarations.set(maybeDependency.identifier.id, { + identifier: maybeDependency.identifier, + scope: originalDeclaration.scope.value!, + }); + } + }); + } + + // ref.current access is not a valid dep + if ( + isUseRefType(maybeDependency.identifier) && + maybeDependency.path.at(0)?.property === 'current' + ) { + maybeDependency = { + identifier: maybeDependency.identifier, + path: [], + }; + } + if (this.#checkValidDependency(maybeDependency)) { + this.#dependencies.value!.push(maybeDependency); + } + } + + /* + * Record a variable that is declared in some other scope and that is being reassigned in the + * current one as a {@link ReactiveScope.reassignments} + */ + visitReassignment(place: Place): void { + const currentScope = this.currentScope.value; + if ( + currentScope != null && + !Iterable_some( + currentScope.reassignments, + identifier => + identifier.declarationId === place.identifier.declarationId, + ) && + this.#checkValidDependency({identifier: place.identifier, path: []}) + ) { + currentScope.reassignments.add(place.identifier); + } + } +} + +function handleInstruction(instr: Instruction, context: Context): void { + const {id, value, lvalue} = instr; + if (value.kind === 'LoadLocal') { + if ( + value.place.identifier.name === null || + lvalue.identifier.name !== null || + context.isUsedOutsideDeclaringScope(lvalue) + ) { + context.visitOperand(value.place); + } + } else if (value.kind === 'PropertyLoad') { + if (context.isUsedOutsideDeclaringScope(lvalue)) { + context.visitProperty(value.object, value.property, false); + } + } else if (value.kind === 'StoreLocal') { + context.visitOperand(value.value); + if (value.lvalue.kind === InstructionKind.Reassign) { + context.visitReassignment(value.lvalue.place); + } + context.declare(value.lvalue.place.identifier, { + id, + scope: context.currentScope, + }); + } else if (value.kind === 'DeclareLocal' || value.kind === 'DeclareContext') { + /* + * Some variables may be declared and never initialized. We need + * to retain (and hoist) these declarations if they are included + * in a reactive scope. One approach is to simply add all `DeclareLocal`s + * as scope declarations. + */ + + /* + * We add context variable declarations here, not at `StoreContext`, since + * context Store / Loads are modeled as reads and mutates to the underlying + * variable reference (instead of through intermediate / inlined temporaries) + */ + context.declare(value.lvalue.place.identifier, { + id, + scope: context.currentScope, + }); + } else if (value.kind === 'Destructure') { + context.visitOperand(value.value); + for (const place of eachPatternOperand(value.lvalue.pattern)) { + if (value.lvalue.kind === InstructionKind.Reassign) { + context.visitReassignment(place); + } + context.declare(place.identifier, { + id, + scope: context.currentScope, + }); + } + } else { + for (const operand of eachInstructionValueOperand(value)) { + context.visitOperand(operand); + } + } + + context.declare(lvalue.identifier, { + id, + scope: context.currentScope, + }); +} + +function collectDependencies( + fn: HIRFunction, + usedOutsideDeclaringScope: ReadonlySet, + temporaries: ReadonlyMap, + processedInstrsInOptional: ReadonlySet, +): Map> { + const context = new Context(usedOutsideDeclaringScope, temporaries); + + for (const param of fn.params) { + if (param.kind === 'Identifier') { + context.declare(param.identifier, { + id: makeInstructionId(0), + scope: empty(), + }); + } else { + context.declare(param.place.identifier, { + id: makeInstructionId(0), + scope: empty(), + }); + } + } + + const scopeTraversal = new ScopeBlockTraversal(); + + const handleFunction = (fn: HIRFunction): void => { + for (const [blockId, block] of fn.body.blocks) { + scopeTraversal.recordScopes(block); + const scopeBlockInfo = scopeTraversal.blockInfos.get(blockId); + if (scopeBlockInfo?.kind === 'begin') { + context.enterScope(scopeBlockInfo.scope); + } else if (scopeBlockInfo?.kind === 'end') { + context.exitScope(scopeBlockInfo.scope, scopeBlockInfo.pruned); + } + // Record referenced optional chains in phis + for (const phi of block.phis) { + for (const operand of phi.operands) { + const maybeOptionalChain = temporaries.get(operand[1].identifier.id); + if (maybeOptionalChain) { + context.visitDependency(maybeOptionalChain); + } + } + } + for (const instr of block.instructions) { + if ( + fn.env.config.enableFunctionDependencyRewrite && + (instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod') + ) { + context.declare(instr.lvalue.identifier, { + id: instr.id, + scope: context.currentScope, + }); + /** + * Recursively visit the inner function to extract dependencies there + */ + const wasInInnerFn = context.inInnerFn; + context.inInnerFn = true; + handleFunction(instr.value.loweredFunc.func); + context.inInnerFn = wasInInnerFn; + } else if (!processedInstrsInOptional.has(instr)) { + handleInstruction(instr, context); + } + } + + if (!processedInstrsInOptional.has(block.terminal)) { + for (const place of eachTerminalOperand(block.terminal)) { + context.visitOperand(place); + } + } + } + }; + + handleFunction(fn); + return context.deps; +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/TypeSchema.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/TypeSchema.ts new file mode 100644 index 0000000000000..362328db72155 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/TypeSchema.ts @@ -0,0 +1,105 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {isValidIdentifier} from '@babel/types'; +import {z} from 'zod'; +import {Effect, ValueKind} from '..'; +import {EffectSchema, ValueKindSchema} from './HIR'; + +export type ObjectPropertiesConfig = {[key: string]: TypeConfig}; +export const ObjectPropertiesSchema: z.ZodType = z + .record( + z.string(), + z.lazy(() => TypeSchema), + ) + .refine(record => { + return Object.keys(record).every( + key => key === '*' || key === 'default' || isValidIdentifier(key), + ); + }, 'Expected all "object" property names to be valid identifier, `*` to match any property, of `default` to define a module default export'); + +export type ObjectTypeConfig = { + kind: 'object'; + properties: ObjectPropertiesConfig | null; +}; +export const ObjectTypeSchema: z.ZodType = z.object({ + kind: z.literal('object'), + properties: ObjectPropertiesSchema.nullable(), +}); + +export type FunctionTypeConfig = { + kind: 'function'; + positionalParams: Array; + restParam: Effect | null; + calleeEffect: Effect; + returnType: TypeConfig; + returnValueKind: ValueKind; + noAlias?: boolean | null | undefined; + mutableOnlyIfOperandsAreMutable?: boolean | null | undefined; +}; +export const FunctionTypeSchema: z.ZodType = z.object({ + kind: z.literal('function'), + positionalParams: z.array(EffectSchema), + restParam: EffectSchema.nullable(), + calleeEffect: EffectSchema, + returnType: z.lazy(() => TypeSchema), + returnValueKind: ValueKindSchema, + noAlias: z.boolean().nullable().optional(), + mutableOnlyIfOperandsAreMutable: z.boolean().nullable().optional(), +}); + +export type HookTypeConfig = { + kind: 'hook'; + positionalParams?: Array | null | undefined; + restParam?: Effect | null | undefined; + returnType: TypeConfig; + returnValueKind?: ValueKind | null | undefined; + noAlias?: boolean | null | undefined; +}; +export const HookTypeSchema: z.ZodType = z.object({ + kind: z.literal('hook'), + positionalParams: z.array(EffectSchema).nullable().optional(), + restParam: EffectSchema.nullable().optional(), + returnType: z.lazy(() => TypeSchema), + returnValueKind: ValueKindSchema.nullable().optional(), + noAlias: z.boolean().nullable().optional(), +}); + +export type BuiltInTypeConfig = + | 'Any' + | 'Ref' + | 'Array' + | 'Primitive' + | 'MixedReadonly'; +export const BuiltInTypeSchema: z.ZodType = z.union([ + z.literal('Any'), + z.literal('Ref'), + z.literal('Array'), + z.literal('Primitive'), + z.literal('MixedReadonly'), +]); + +export type TypeReferenceConfig = { + kind: 'type'; + name: BuiltInTypeConfig; +}; +export const TypeReferenceSchema: z.ZodType = z.object({ + kind: z.literal('type'), + name: BuiltInTypeSchema, +}); + +export type TypeConfig = + | ObjectTypeConfig + | FunctionTypeConfig + | HookTypeConfig + | TypeReferenceConfig; +export const TypeSchema: z.ZodType = z.union([ + ObjectTypeSchema, + FunctionTypeSchema, + HookTypeSchema, + TypeReferenceSchema, +]); diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/index.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/index.ts index a0fd782572fc6..45267dd9a1833 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/index.ts @@ -17,7 +17,7 @@ export {buildReactiveScopeTerminalsHIR} from './BuildReactiveScopeTerminalsHIR'; export {computeDominatorTree, computePostDominatorTree} from './Dominator'; export { Environment, - parseConfigPragma, + parseConfigPragmaForTests, validateEnvironmentConfig, type EnvironmentConfig, type ExternalFunction, diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts index 0df8478b39c45..c9ee803bfaffd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts @@ -6,7 +6,9 @@ */ import {assertExhaustive} from '../Utils/utils'; +import {CompilerError} from '..'; import { + BasicBlock, BlockId, Instruction, InstructionValue, @@ -14,7 +16,9 @@ import { Pattern, Place, ReactiveInstruction, + ReactiveScope, ReactiveValue, + ScopeId, SpreadPattern, Terminal, } from './HIR'; @@ -660,11 +664,13 @@ export function mapTerminalSuccessors( case 'branch': { const consequent = fn(terminal.consequent); const alternate = fn(terminal.alternate); + const fallthrough = fn(terminal.fallthrough); return { kind: 'branch', test: terminal.test, consequent, alternate, + fallthrough, id: makeInstructionId(0), loc: terminal.loc, }; @@ -883,7 +889,6 @@ export function terminalHasFallthrough< >(terminal: T): terminal is U { switch (terminal.kind) { case 'maybe-throw': - case 'branch': case 'goto': case 'return': case 'throw': @@ -892,6 +897,7 @@ export function terminalHasFallthrough< const _: undefined = terminal.fallthrough; return false; } + case 'branch': case 'try': case 'do-while': case 'for-of': @@ -1147,3 +1153,80 @@ export function* eachTerminalOperand(terminal: Terminal): Iterable { } } } + +/** + * Helper class for traversing scope blocks in HIR-form. + */ +export class ScopeBlockTraversal { + // Live stack of active scopes + #activeScopes: Array = []; + blockInfos: Map< + BlockId, + | { + kind: 'end'; + scope: ReactiveScope; + pruned: boolean; + } + | { + kind: 'begin'; + scope: ReactiveScope; + pruned: boolean; + fallthrough: BlockId; + } + > = new Map(); + + recordScopes(block: BasicBlock): void { + const blockInfo = this.blockInfos.get(block.id); + if (blockInfo?.kind === 'begin') { + this.#activeScopes.push(blockInfo.scope.id); + } else if (blockInfo?.kind === 'end') { + const top = this.#activeScopes.at(-1); + CompilerError.invariant(blockInfo.scope.id === top, { + reason: + 'Expected traversed block fallthrough to match top-most active scope', + loc: block.instructions[0]?.loc ?? block.terminal.id, + }); + this.#activeScopes.pop(); + } + + if ( + block.terminal.kind === 'scope' || + block.terminal.kind === 'pruned-scope' + ) { + CompilerError.invariant( + !this.blockInfos.has(block.terminal.block) && + !this.blockInfos.has(block.terminal.fallthrough), + { + reason: 'Expected unique scope blocks and fallthroughs', + loc: block.terminal.loc, + }, + ); + this.blockInfos.set(block.terminal.block, { + kind: 'begin', + scope: block.terminal.scope, + pruned: block.terminal.kind === 'pruned-scope', + fallthrough: block.terminal.fallthrough, + }); + this.blockInfos.set(block.terminal.fallthrough, { + kind: 'end', + scope: block.terminal.scope, + pruned: block.terminal.kind === 'pruned-scope', + }); + } + } + + /** + * @returns if the given scope is currently 'active', i.e. if the scope start + * block but not the scope fallthrough has been recorded. + */ + isScopeActive(scopeId: ScopeId): boolean { + return this.#activeScopes.indexOf(scopeId) !== -1; + } + + /** + * The current, innermost active scope. + */ + get currentScope(): ScopeId | null { + return this.#activeScopes.at(-1) ?? null; + } +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts index b18e19606ce0d..684acaf298388 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts @@ -13,9 +13,7 @@ import { IdentifierName, LoweredFunction, Place, - ReactiveScopeDependency, - isRefValueType, - isUseRefType, + isRefOrRefValue, makeInstructionId, } from '../HIR'; import {deadCodeElimination} from '../Optimization'; @@ -26,9 +24,14 @@ import {inferMutableContextVariables} from './InferMutableContextVariables'; import {inferMutableRanges} from './InferMutableRanges'; import inferReferenceEffects from './InferReferenceEffects'; +type Dependency = { + identifier: Identifier; + path: Array; +}; + // Helper class to track indirections such as LoadLocal and PropertyLoad. export class IdentifierState { - properties: Map = new Map(); + properties: Map = new Map(); resolve(identifier: Identifier): Identifier { const resolved = this.properties.get(identifier); @@ -40,7 +43,7 @@ export class IdentifierState { declareProperty(lvalue: Place, object: Place, property: string): void { const objectDependency = this.properties.get(object.identifier); - let nextDependency: ReactiveScopeDependency; + let nextDependency: Dependency; if (objectDependency === undefined) { nextDependency = {identifier: object.identifier, path: [property]}; } else { @@ -53,9 +56,7 @@ export class IdentifierState { } declareTemporary(lvalue: Place, value: Place): void { - const resolved: ReactiveScopeDependency = this.properties.get( - value.identifier, - ) ?? { + const resolved: Dependency = this.properties.get(value.identifier) ?? { identifier: value.identifier, path: [], }; @@ -139,7 +140,7 @@ function infer( name = dep.identifier.name; } - if (isUseRefType(dep.identifier) || isRefValueType(dep.identifier)) { + if (isRefOrRefValue(dep.identifier)) { /* * TODO: this is a hack to ensure we treat functions which reference refs * as having a capture and therefore being considered mutable. this ensures diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts index 2d9e21af1d61b..4dcdc21e15ac5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts @@ -42,6 +42,7 @@ type IdentifierSidemap = { react: Set; maybeDepsLists: Map>; maybeDeps: Map; + optionals: Set; }; /** @@ -52,6 +53,7 @@ type IdentifierSidemap = { export function collectMaybeMemoDependencies( value: InstructionValue, maybeDeps: Map, + optional: boolean, ): ManualMemoDependency | null { switch (value.kind) { case 'LoadGlobal': { @@ -68,7 +70,8 @@ export function collectMaybeMemoDependencies( if (object != null) { return { root: object.root, - path: [...object.path, value.property], + // TODO: determine if the access is optional + path: [...object.path, {property: value.property, optional}], }; } break; @@ -127,7 +130,7 @@ function collectTemporaries( break; } case 'LoadGlobal': { - const global = env.getGlobalDeclaration(value.binding); + const global = env.getGlobalDeclaration(value.binding, value.loc); const hookKind = global !== null ? getHookKindForType(env, global) : null; const lvalId = instr.lvalue.identifier.id; if (hookKind === 'useMemo' || hookKind === 'useCallback') { @@ -161,7 +164,11 @@ function collectTemporaries( break; } } - const maybeDep = collectMaybeMemoDependencies(value, sidemap.maybeDeps); + const maybeDep = collectMaybeMemoDependencies( + value, + sidemap.maybeDeps, + sidemap.optionals.has(lvalue.identifier.id), + ); // We don't expect named lvalues during this pass (unlike ValidatePreservingManualMemo) if (maybeDep != null) { sidemap.maybeDeps.set(lvalue.identifier.id, maybeDep); @@ -337,12 +344,14 @@ export function dropManualMemoization(func: HIRFunction): void { func.env.config.validatePreserveExistingMemoizationGuarantees || func.env.config.validateNoSetStateInRender || func.env.config.enablePreserveExistingMemoizationGuarantees; + const optionals = findOptionalPlaces(func); const sidemap: IdentifierSidemap = { functions: new Map(), manualMemos: new Map(), react: new Set(), maybeDeps: new Map(), maybeDepsLists: new Map(), + optionals, }; let nextManualMemoId = 0; @@ -475,3 +484,46 @@ export function dropManualMemoization(func: HIRFunction): void { } } } + +function findOptionalPlaces(fn: HIRFunction): Set { + const optionals = new Set(); + for (const [, block] of fn.body.blocks) { + if (block.terminal.kind === 'optional' && block.terminal.optional) { + const optionalTerminal = block.terminal; + let testBlock = fn.body.blocks.get(block.terminal.test)!; + loop: while (true) { + const terminal = testBlock.terminal; + switch (terminal.kind) { + case 'branch': { + if (terminal.fallthrough === optionalTerminal.fallthrough) { + // found it + const consequent = fn.body.blocks.get(terminal.consequent)!; + const last = consequent.instructions.at(-1); + if (last !== undefined && last.value.kind === 'StoreLocal') { + optionals.add(last.value.value.identifier.id); + } + break loop; + } else { + testBlock = fn.body.blocks.get(terminal.fallthrough)!; + } + break; + } + case 'optional': + case 'logical': + case 'sequence': + case 'ternary': { + testBlock = fn.body.blocks.get(terminal.fallthrough)!; + break; + } + default: { + CompilerError.invariant(false, { + reason: `Unexpected terminal in optional`, + loc: terminal.loc, + }); + } + } + } + } + } + return optionals; +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferAliasForPhis.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferAliasForPhis.ts index 126990196ccbd..e81e3ebdae7a2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferAliasForPhis.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferAliasForPhis.ts @@ -15,11 +15,11 @@ export function inferAliasForPhis( for (const [_, block] of func.body.blocks) { for (const phi of block.phis) { const isPhiMutatedAfterCreation: boolean = - phi.id.mutableRange.end > + phi.place.identifier.mutableRange.end > (block.instructions.at(0)?.id ?? block.terminal.id); if (isPhiMutatedAfterCreation) { for (const [, operand] of phi.operands) { - aliases.union([phi.id, operand]); + aliases.union([phi.place.identifier, operand.identifier]); } } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts new file mode 100644 index 0000000000000..8c9823765bbb6 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts @@ -0,0 +1,296 @@ +import {CompilerError, SourceLocation} from '..'; +import { + ArrayExpression, + Effect, + Environment, + FunctionExpression, + GeneratedSource, + HIRFunction, + IdentifierId, + Instruction, + makeInstructionId, + TInstruction, + InstructionId, + ScopeId, + ReactiveScopeDependency, + Place, + ReactiveScopeDependencies, +} from '../HIR'; +import {DEFAULT_EXPORT} from '../HIR/Environment'; +import { + createTemporaryPlace, + fixScopeAndIdentifierRanges, + markInstructionIds, +} from '../HIR/HIRBuilder'; +import {eachInstructionOperand, eachTerminalOperand} from '../HIR/visitors'; +import {getOrInsertWith} from '../Utils/utils'; + +/** + * Infers reactive dependencies captured by useEffect lambdas and adds them as + * a second argument to the useEffect call if no dependency array is provided. + */ +export function inferEffectDependencies(fn: HIRFunction): void { + let hasRewrite = false; + const fnExpressions = new Map< + IdentifierId, + TInstruction + >(); + + const autodepFnConfigs = new Map>(); + for (const effectTarget of fn.env.config.inferEffectDependencies!) { + const moduleTargets = getOrInsertWith( + autodepFnConfigs, + effectTarget.function.source, + () => new Map(), + ); + moduleTargets.set( + effectTarget.function.importSpecifierName, + effectTarget.numRequiredArgs, + ); + } + const autodepFnLoads = new Map(); + + const scopeInfos = new Map< + ScopeId, + {pruned: boolean; deps: ReactiveScopeDependencies; hasSingleInstr: boolean} + >(); + + const loadGlobals = new Set(); + + /** + * When inserting LoadLocals, we need to retain the reactivity of the base + * identifier, as later passes e.g. PruneNonReactiveDeps take the reactivity of + * a base identifier as the "maximal" reactivity of all its references. + * Concretely, + * reactive(Identifier i) = Union_{reference of i}(reactive(reference)) + */ + const reactiveIds = inferReactiveIdentifiers(fn); + + for (const [, block] of fn.body.blocks) { + if ( + block.terminal.kind === 'scope' || + block.terminal.kind === 'pruned-scope' + ) { + const scopeBlock = fn.body.blocks.get(block.terminal.block)!; + scopeInfos.set(block.terminal.scope.id, { + pruned: block.terminal.kind === 'pruned-scope', + deps: block.terminal.scope.dependencies, + hasSingleInstr: + scopeBlock.instructions.length === 1 && + scopeBlock.terminal.kind === 'goto' && + scopeBlock.terminal.block === block.terminal.fallthrough, + }); + } + const rewriteInstrs = new Map>(); + for (const instr of block.instructions) { + const {value, lvalue} = instr; + if (value.kind === 'FunctionExpression') { + fnExpressions.set( + lvalue.identifier.id, + instr as TInstruction, + ); + } else if (value.kind === 'LoadGlobal') { + loadGlobals.add(lvalue.identifier.id); + + if ( + value.binding.kind === 'ImportSpecifier' || + value.binding.kind === 'ImportDefault' + ) { + const moduleTargets = autodepFnConfigs.get(value.binding.module); + if (moduleTargets != null) { + const importSpecifierName = + value.binding.kind === 'ImportSpecifier' + ? value.binding.imported + : DEFAULT_EXPORT; + const numRequiredArgs = moduleTargets.get(importSpecifierName); + if (numRequiredArgs != null) { + autodepFnLoads.set(lvalue.identifier.id, numRequiredArgs); + } + } + } + } else if ( + /* + * TODO: Handle method calls + */ + value.kind === 'CallExpression' && + autodepFnLoads.get(value.callee.identifier.id) === value.args.length && + value.args[0].kind === 'Identifier' + ) { + const effectDeps: Array = []; + const newInstructions: Array = []; + const deps: ArrayExpression = { + kind: 'ArrayExpression', + elements: effectDeps, + loc: GeneratedSource, + }; + const depsPlace = createTemporaryPlace(fn.env, GeneratedSource); + depsPlace.effect = Effect.Read; + + const fnExpr = fnExpressions.get(value.args[0].identifier.id); + if (fnExpr != null) { + // We have a function expression, so we can infer its dependencies + const scopeInfo = + fnExpr.lvalue.identifier.scope != null + ? scopeInfos.get(fnExpr.lvalue.identifier.scope.id) + : null; + CompilerError.invariant(scopeInfo != null, { + reason: 'Expected function expression scope to exist', + loc: value.loc, + }); + if (scopeInfo.pruned || !scopeInfo.hasSingleInstr) { + /** + * TODO: retry pipeline that ensures effect function expressions + * are placed into their own scope + */ + CompilerError.throwTodo({ + reason: + '[InferEffectDependencies] Expected effect function to have non-pruned scope and its scope to have exactly one instruction', + loc: fnExpr.loc, + }); + } + + /** + * Step 1: push dependencies to the effect deps array + * + * Note that it's invalid to prune non-reactive deps in this pass, see + * the `infer-effect-deps/pruned-nonreactive-obj` fixture for an + * explanation. + */ + for (const dep of scopeInfo.deps) { + const {place, instructions} = writeDependencyToInstructions( + dep, + reactiveIds.has(dep.identifier.id), + fn.env, + fnExpr.loc, + ); + newInstructions.push(...instructions); + effectDeps.push(place); + } + + newInstructions.push({ + id: makeInstructionId(0), + loc: GeneratedSource, + lvalue: {...depsPlace, effect: Effect.Mutate}, + value: deps, + }); + + // Step 2: push the inferred deps array as an argument of the useEffect + value.args.push({...depsPlace, effect: Effect.Freeze}); + rewriteInstrs.set(instr.id, newInstructions); + } else if (loadGlobals.has(value.args[0].identifier.id)) { + // Global functions have no reactive dependencies, so we can insert an empty array + newInstructions.push({ + id: makeInstructionId(0), + loc: GeneratedSource, + lvalue: {...depsPlace, effect: Effect.Mutate}, + value: deps, + }); + value.args.push({...depsPlace, effect: Effect.Freeze}); + rewriteInstrs.set(instr.id, newInstructions); + } + } + } + if (rewriteInstrs.size > 0) { + hasRewrite = true; + const newInstrs = []; + for (const instr of block.instructions) { + const newInstr = rewriteInstrs.get(instr.id); + if (newInstr != null) { + newInstrs.push(...newInstr, instr); + } else { + newInstrs.push(instr); + } + } + block.instructions = newInstrs; + } + } + if (hasRewrite) { + // Renumber instructions and fix scope ranges + markInstructionIds(fn.body); + fixScopeAndIdentifierRanges(fn.body); + } +} + +function writeDependencyToInstructions( + dep: ReactiveScopeDependency, + reactive: boolean, + env: Environment, + loc: SourceLocation, +): {place: Place; instructions: Array} { + const instructions: Array = []; + let currValue = createTemporaryPlace(env, GeneratedSource); + currValue.reactive = reactive; + instructions.push({ + id: makeInstructionId(0), + loc: GeneratedSource, + lvalue: {...currValue, effect: Effect.Mutate}, + value: { + kind: 'LoadLocal', + place: { + kind: 'Identifier', + identifier: dep.identifier, + effect: Effect.Capture, + reactive, + loc: loc, + }, + loc: loc, + }, + }); + for (const path of dep.path) { + if (path.optional) { + /** + * TODO: instead of truncating optional paths, reuse + * instructions from hoisted dependencies block(s) + */ + break; + } + if (path.property === 'current') { + /* + * Prune ref.current accesses. This may over-capture for non-ref values with + * a current property, but that's fine. + */ + break; + } + const nextValue = createTemporaryPlace(env, GeneratedSource); + nextValue.reactive = reactive; + instructions.push({ + id: makeInstructionId(0), + loc: GeneratedSource, + lvalue: {...nextValue, effect: Effect.Mutate}, + value: { + kind: 'PropertyLoad', + object: {...currValue, effect: Effect.Capture}, + property: path.property, + loc: loc, + }, + }); + currValue = nextValue; + } + currValue.effect = Effect.Freeze; + return {place: currValue, instructions}; +} + +function inferReactiveIdentifiers(fn: HIRFunction): Set { + const reactiveIds: Set = new Set(); + for (const [, block] of fn.body.blocks) { + for (const instr of block.instructions) { + /** + * No need to traverse into nested functions as + * 1. their effects are recorded in `LoweredFunction.dependencies` + * 2. we don't mark `reactive` in these anyways + */ + for (const place of eachInstructionOperand(instr)) { + if (place.reactive) { + reactiveIds.add(place.identifier.id); + } + } + } + + for (const place of eachTerminalOperand(block.terminal)) { + if (place.reactive) { + reactiveIds.add(place.identifier.id); + } + } + } + return reactiveIds; +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferFunctionEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferFunctionEffects.ts new file mode 100644 index 0000000000000..0ae54839b6fa3 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferFunctionEffects.ts @@ -0,0 +1,335 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {CompilerError, ErrorSeverity, ValueKind} from '..'; +import { + AbstractValue, + BasicBlock, + Effect, + Environment, + FunctionEffect, + Instruction, + InstructionValue, + Place, + ValueReason, + getHookKind, + isRefOrRefValue, +} from '../HIR'; +import {eachInstructionOperand, eachTerminalOperand} from '../HIR/visitors'; +import {assertExhaustive} from '../Utils/utils'; + +interface State { + kind(place: Place): AbstractValue; + values(place: Place): Array; + isDefined(place: Place): boolean; +} + +function inferOperandEffect(state: State, place: Place): null | FunctionEffect { + const value = state.kind(place); + CompilerError.invariant(value != null, { + reason: 'Expected operand to have a kind', + loc: null, + }); + + switch (place.effect) { + case Effect.Store: + case Effect.Mutate: { + if (isRefOrRefValue(place.identifier)) { + break; + } else if (value.kind === ValueKind.Context) { + return { + kind: 'ContextMutation', + loc: place.loc, + effect: place.effect, + places: value.context.size === 0 ? new Set([place]) : value.context, + }; + } else if ( + value.kind !== ValueKind.Mutable && + // We ignore mutations of primitives since this is not a React-specific problem + value.kind !== ValueKind.Primitive + ) { + let reason = getWriteErrorReason(value); + return { + kind: + value.reason.size === 1 && value.reason.has(ValueReason.Global) + ? 'GlobalMutation' + : 'ReactMutation', + error: { + reason, + description: + place.identifier.name !== null && + place.identifier.name.kind === 'named' + ? `Found mutation of \`${place.identifier.name.value}\`` + : null, + loc: place.loc, + suggestions: null, + severity: ErrorSeverity.InvalidReact, + }, + }; + } + break; + } + } + return null; +} + +function inheritFunctionEffects( + state: State, + place: Place, +): Array { + const effects = inferFunctionInstrEffects(state, place); + + return effects + .flatMap(effect => { + if (effect.kind === 'GlobalMutation' || effect.kind === 'ReactMutation') { + return [effect]; + } else { + const effects: Array = []; + CompilerError.invariant(effect.kind === 'ContextMutation', { + reason: 'Expected ContextMutation', + loc: null, + }); + /** + * Contextual effects need to be replayed against the current inference + * state, which may know more about the value to which the effect applied. + * The main cases are: + * 1. The mutated context value is _still_ a context value in the current scope, + * so we have to continue propagating the original context mutation. + * 2. The mutated context value is a mutable value in the current scope, + * so the context mutation was fine and we can skip propagating the effect. + * 3. The mutated context value is an immutable value in the current scope, + * resulting in a non-ContextMutation FunctionEffect. We propagate that new, + * more detailed effect to the current function context. + */ + for (const place of effect.places) { + if (state.isDefined(place)) { + const replayedEffect = inferOperandEffect(state, { + ...place, + loc: effect.loc, + effect: effect.effect, + }); + if (replayedEffect != null) { + if (replayedEffect.kind === 'ContextMutation') { + // Case 1, still a context value so propagate the original effect + effects.push(effect); + } else { + // Case 3, immutable value so propagate the more precise effect + effects.push(replayedEffect); + } + } // else case 2, local mutable value so this effect was fine + } + } + return effects; + } + }) + .filter((effect): effect is FunctionEffect => effect != null); +} + +function inferFunctionInstrEffects( + state: State, + place: Place, +): Array { + const effects: Array = []; + const instrs = state.values(place); + CompilerError.invariant(instrs != null, { + reason: 'Expected operand to have instructions', + loc: null, + }); + + for (const instr of instrs) { + if ( + (instr.kind === 'FunctionExpression' || instr.kind === 'ObjectMethod') && + instr.loweredFunc.func.effects != null + ) { + effects.push(...instr.loweredFunc.func.effects); + } + } + + return effects; +} + +function operandEffects( + state: State, + place: Place, + filterRenderSafe: boolean, +): Array { + const functionEffects: Array = []; + const effect = inferOperandEffect(state, place); + effect && functionEffects.push(effect); + functionEffects.push(...inheritFunctionEffects(state, place)); + if (filterRenderSafe) { + return functionEffects.filter(effect => !isEffectSafeOutsideRender(effect)); + } else { + return functionEffects; + } +} + +export function inferInstructionFunctionEffects( + env: Environment, + state: State, + instr: Instruction, +): Array { + const functionEffects: Array = []; + switch (instr.value.kind) { + case 'JsxExpression': { + if (instr.value.tag.kind === 'Identifier') { + functionEffects.push(...operandEffects(state, instr.value.tag, false)); + } + instr.value.children?.forEach(child => + functionEffects.push(...operandEffects(state, child, false)), + ); + for (const attr of instr.value.props) { + if (attr.kind === 'JsxSpreadAttribute') { + functionEffects.push(...operandEffects(state, attr.argument, false)); + } else { + functionEffects.push(...operandEffects(state, attr.place, true)); + } + } + break; + } + case 'ObjectMethod': + case 'FunctionExpression': { + /** + * If this function references other functions, propagate the referenced function's + * effects to this function. + * + * ``` + * let f = () => global = true; + * let g = () => f(); + * g(); + * ``` + * + * In this example, because `g` references `f`, we propagate the GlobalMutation from + * `f` to `g`. Thus, referencing `g` in `g()` will evaluate the GlobalMutation in the outer + * function effect context and report an error. But if instead we do: + * + * ``` + * let f = () => global = true; + * let g = () => f(); + * useEffect(() => g(), [g]) + * ``` + * + * Now `g`'s effects will be discarded since they're in a useEffect. + */ + for (const operand of eachInstructionOperand(instr)) { + instr.value.loweredFunc.func.effects ??= []; + instr.value.loweredFunc.func.effects.push( + ...inferFunctionInstrEffects(state, operand), + ); + } + break; + } + case 'MethodCall': + case 'CallExpression': { + let callee; + if (instr.value.kind === 'MethodCall') { + callee = instr.value.property; + functionEffects.push( + ...operandEffects(state, instr.value.receiver, false), + ); + } else { + callee = instr.value.callee; + } + functionEffects.push(...operandEffects(state, callee, false)); + let isHook = getHookKind(env, callee.identifier) != null; + for (const arg of instr.value.args) { + const place = arg.kind === 'Identifier' ? arg : arg.place; + /* + * Join the effects of the argument with the effects of the enclosing function, + * unless the we're detecting a global mutation inside a useEffect hook + */ + functionEffects.push(...operandEffects(state, place, isHook)); + } + break; + } + case 'StartMemoize': + case 'FinishMemoize': + case 'LoadLocal': + case 'StoreLocal': { + break; + } + case 'StoreGlobal': { + functionEffects.push({ + kind: 'GlobalMutation', + error: { + reason: + 'Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render)', + loc: instr.loc, + suggestions: null, + severity: ErrorSeverity.InvalidReact, + }, + }); + break; + } + default: { + for (const operand of eachInstructionOperand(instr)) { + functionEffects.push(...operandEffects(state, operand, false)); + } + } + } + return functionEffects; +} + +export function inferTerminalFunctionEffects( + state: State, + block: BasicBlock, +): Array { + const functionEffects: Array = []; + for (const operand of eachTerminalOperand(block.terminal)) { + functionEffects.push(...operandEffects(state, operand, true)); + } + return functionEffects; +} + +export function raiseFunctionEffectErrors( + functionEffects: Array, +): void { + functionEffects.forEach(eff => { + switch (eff.kind) { + case 'ReactMutation': + case 'GlobalMutation': { + CompilerError.throw(eff.error); + } + case 'ContextMutation': { + CompilerError.throw({ + severity: ErrorSeverity.Invariant, + reason: `Unexpected ContextMutation in top-level function effects`, + loc: eff.loc, + }); + } + default: + assertExhaustive( + eff, + `Unexpected function effect kind \`${(eff as any).kind}\``, + ); + } + }); +} + +function isEffectSafeOutsideRender(effect: FunctionEffect): boolean { + return effect.kind === 'GlobalMutation'; +} + +function getWriteErrorReason(abstractValue: AbstractValue): string { + if (abstractValue.reason.has(ValueReason.Global)) { + return 'Writing to a variable defined outside a component or hook is not allowed. Consider using an effect'; + } else if (abstractValue.reason.has(ValueReason.JsxCaptured)) { + return 'Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX'; + } else if (abstractValue.reason.has(ValueReason.Context)) { + return `Mutating a value returned from 'useContext()', which should not be mutated`; + } else if (abstractValue.reason.has(ValueReason.KnownReturnSignature)) { + return 'Mutating a value returned from a function whose return value should not be mutated'; + } else if (abstractValue.reason.has(ValueReason.ReactiveFunctionArgument)) { + return 'Mutating component props or hook arguments is not allowed. Consider using a local variable instead'; + } else if (abstractValue.reason.has(ValueReason.State)) { + return "Mutating a value returned from 'useState()', which should not be mutated. Use the setter function to update instead"; + } else if (abstractValue.reason.has(ValueReason.ReducerState)) { + return "Mutating a value returned from 'useReducer()', which should not be mutated. Use the dispatch function to update instead"; + } else { + return 'This mutates a variable that React considers immutable'; + } +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableLifetimes.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableLifetimes.ts index 2ce1aebbf8577..508a970d931b0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableLifetimes.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableLifetimes.ts @@ -11,6 +11,7 @@ import { Identifier, InstructionId, InstructionKind, + isRefOrRefValue, makeInstructionId, Place, } from '../HIR/HIR'; @@ -66,7 +67,9 @@ import {assertExhaustive} from '../Utils/utils'; */ function infer(place: Place, instrId: InstructionId): void { - place.identifier.mutableRange.end = makeInstructionId(instrId + 1); + if (!isRefOrRefValue(place.identifier)) { + place.identifier.mutableRange.end = makeInstructionId(instrId + 1); + } } function inferPlace( @@ -113,19 +116,23 @@ export function inferMutableLifetimes( for (const [_, block] of func.body.blocks) { for (const phi of block.phis) { const isPhiMutatedAfterCreation: boolean = - phi.id.mutableRange.end > + phi.place.identifier.mutableRange.end > (block.instructions.at(0)?.id ?? block.terminal.id); if ( inferMutableRangeForStores && isPhiMutatedAfterCreation && - phi.id.mutableRange.start === 0 + phi.place.identifier.mutableRange.start === 0 ) { for (const [, operand] of phi.operands) { - if (phi.id.mutableRange.start === 0) { - phi.id.mutableRange.start = operand.mutableRange.start; + if (phi.place.identifier.mutableRange.start === 0) { + phi.place.identifier.mutableRange.start = + operand.identifier.mutableRange.start; } else { - phi.id.mutableRange.start = makeInstructionId( - Math.min(phi.id.mutableRange.start, operand.mutableRange.start), + phi.place.identifier.mutableRange.start = makeInstructionId( + Math.min( + phi.place.identifier.mutableRange.start, + operand.identifier.mutableRange.start, + ), ); } } @@ -171,7 +178,10 @@ export function inferMutableLifetimes( const declaration = contextVariableDeclarationInstructions.get( instr.value.lvalue.place.identifier, ); - if (declaration != null) { + if ( + declaration != null && + !isRefOrRefValue(instr.value.lvalue.place.identifier) + ) { const range = instr.value.lvalue.place.identifier.mutableRange; if (range.start === 0) { range.start = declaration; diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableRanges.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableRanges.ts index 96d52675a354d..a8f33b31d58f3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableRanges.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableRanges.ts @@ -50,8 +50,38 @@ export function inferMutableRanges(ir: HIRFunction): void { // Re-infer mutable ranges for all values inferMutableLifetimes(ir, true); - // Re-infer mutable ranges for aliases - inferMutableRangesForAlias(ir, aliases); + /** + * The second inferMutableLifetimes() call updates mutable ranges + * of values to account for Store effects. Now we need to update + * all aliases of such values to extend their ranges as well. Note + * that the store only mutates the the directly aliased value and + * not any of its inner captured references. For example: + * + * ``` + * let y; + * if (cond) { + * y = []; + * } else { + * y = [{}]; + * } + * y.push(z); + * ``` + * + * The Store effect from the `y.push` modifies the values that `y` + * directly aliases - the two arrays from the if/else branches - + * but does not modify values that `y` "contains" such as the + * object literal or `z`. + */ + prevAliases = aliases.canonicalize(); + while (true) { + inferMutableRangesForAlias(ir, aliases); + inferAliasForPhis(ir, aliases); + const nextAliases = aliases.canonicalize(); + if (areEqualMaps(prevAliases, nextAliases)) { + break; + } + prevAliases = nextAliases; + } } function areEqualMaps(a: Map, b: Map): boolean { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableRangesForAlias.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableRangesForAlias.ts index 975acf6fbf55a..a7e8b5c1f7a80 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableRangesForAlias.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableRangesForAlias.ts @@ -5,7 +5,12 @@ * LICENSE file in the root directory of this source tree. */ -import {HIRFunction, Identifier, InstructionId} from '../HIR/HIR'; +import { + HIRFunction, + Identifier, + InstructionId, + isRefOrRefValue, +} from '../HIR/HIR'; import DisjointSet from '../Utils/DisjointSet'; export function inferMutableRangesForAlias( @@ -19,7 +24,8 @@ export function inferMutableRangesForAlias( * mutated. */ const mutatingIdentifiers = [...aliasSet].filter( - id => id.mutableRange.end - id.mutableRange.start > 1, + id => + id.mutableRange.end - id.mutableRange.start > 1 && !isRefOrRefValue(id), ); if (mutatingIdentifiers.length > 0) { @@ -36,7 +42,10 @@ export function inferMutableRangesForAlias( * last mutation. */ for (const alias of aliasSet) { - if (alias.mutableRange.end < lastMutatingInstructionId) { + if ( + alias.mutableRange.end < lastMutatingInstructionId && + !isRefOrRefValue(alias) + ) { alias.mutableRange.end = lastMutatingInstructionId as InstructionId; } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReactivePlaces.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReactivePlaces.ts index 20e1a97b08790..344949b020a99 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReactivePlaces.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReactivePlaces.ts @@ -157,28 +157,27 @@ export function inferReactivePlaces(fn: HIRFunction): void { } do { - const identifierMapping = new Map(); for (const [, block] of fn.body.blocks) { let hasReactiveControl = isReactiveControlledBlock(block.id); for (const phi of block.phis) { - if (reactiveIdentifiers.isReactiveIdentifier(phi.id)) { + if (reactiveIdentifiers.isReactive(phi.place)) { // Already marked reactive on a previous pass continue; } let isPhiReactive = false; for (const [, operand] of phi.operands) { - if (reactiveIdentifiers.isReactiveIdentifier(operand)) { + if (reactiveIdentifiers.isReactive(operand)) { isPhiReactive = true; break; } } if (isPhiReactive) { - reactiveIdentifiers.markReactiveIdentifier(phi.id); + reactiveIdentifiers.markReactive(phi.place); } else { for (const [pred] of phi.operands) { if (isReactiveControlledBlock(pred)) { - reactiveIdentifiers.markReactiveIdentifier(phi.id); + reactiveIdentifiers.markReactive(phi.place); break; } } @@ -233,10 +232,6 @@ export function inferReactivePlaces(fn: HIRFunction): void { case Effect.ConditionallyMutate: case Effect.Mutate: { if (isMutable(instruction, operand)) { - const resolvedId = identifierMapping.get(operand.identifier); - if (resolvedId !== undefined) { - reactiveIdentifiers.markReactiveIdentifier(resolvedId); - } reactiveIdentifiers.markReactive(operand); } break; @@ -263,31 +258,6 @@ export function inferReactivePlaces(fn: HIRFunction): void { } } } - - switch (value.kind) { - case 'LoadLocal': { - identifierMapping.set( - instruction.lvalue.identifier, - value.place.identifier, - ); - break; - } - case 'PropertyLoad': - case 'ComputedLoad': { - const resolvedId = - identifierMapping.get(value.object.identifier) ?? - value.object.identifier; - identifierMapping.set(instruction.lvalue.identifier, resolvedId); - break; - } - case 'LoadContext': { - identifierMapping.set( - instruction.lvalue.identifier, - value.place.identifier, - ); - break; - } - } } for (const operand of eachTerminalOperand(block.terminal)) { reactiveIdentifiers.isReactive(operand); @@ -372,27 +342,19 @@ class ReactivityMap { } isReactive(place: Place): boolean { - const reactive = this.isReactiveIdentifier(place.identifier); + const identifier = + this.aliasedIdentifiers.find(place.identifier) ?? place.identifier; + const reactive = this.reactive.has(identifier.id); if (reactive) { place.reactive = true; } return reactive; } - isReactiveIdentifier(inputIdentifier: Identifier): boolean { - const identifier = - this.aliasedIdentifiers.find(inputIdentifier) ?? inputIdentifier; - return this.reactive.has(identifier.id); - } - markReactive(place: Place): void { place.reactive = true; - this.markReactiveIdentifier(place.identifier); - } - - markReactiveIdentifier(inputIdentifier: Identifier): void { const identifier = - this.aliasedIdentifiers.find(inputIdentifier) ?? inputIdentifier; + this.aliasedIdentifiers.find(place.identifier) ?? place.identifier; if (!this.reactive.has(identifier.id)) { this.hasChanges = true; this.reactive.add(identifier.id); diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts index 356bc8af08bfd..8cf30a9666e25 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import {CompilerError, ErrorSeverity} from '../CompilerError'; +import {CompilerError} from '../CompilerError'; import {Environment} from '../HIR'; import { AbstractValue, @@ -26,12 +26,9 @@ import { Type, ValueKind, ValueReason, - getHookKind, isArrayType, isMutableEffect, isObjectType, - isRefValueType, - isUseRefType, } from '../HIR/HIR'; import {FunctionSignature} from '../HIR/ObjectShape'; import { @@ -49,6 +46,11 @@ import { eachTerminalSuccessor, } from '../HIR/visitors'; import {assertExhaustive} from '../Utils/utils'; +import { + inferTerminalFunctionEffects, + inferInstructionFunctionEffects, + raiseFunctionEffectErrors, +} from './InferFunctionEffects'; const UndefinedValue: InstructionValue = { kind: 'Primitive', @@ -229,7 +231,7 @@ export default function inferReferenceEffects( statesByBlock.set(blockId, incomingState); const state = incomingState.clone(); - inferBlock(fn.env, functionEffects, state, block); + inferBlock(fn.env, state, block, functionEffects); for (const nextBlockId of eachTerminalSuccessor(block.terminal)) { queue(nextBlockId, state); @@ -237,37 +239,20 @@ export default function inferReferenceEffects( } } - if (!options.isFunctionExpression) { - functionEffects.forEach(eff => { - switch (eff.kind) { - case 'ReactMutation': - case 'GlobalMutation': { - CompilerError.throw(eff.error); - } - case 'ContextMutation': { - CompilerError.throw({ - severity: ErrorSeverity.Invariant, - reason: `Unexpected ContextMutation in top-level function effects`, - loc: eff.loc, - }); - } - default: - assertExhaustive( - eff, - `Unexpected function effect kind \`${(eff as any).kind}\``, - ); - } - }); - } else { + if (options.isFunctionExpression) { fn.effects = functionEffects; + } else { + raiseFunctionEffectErrors(functionEffects); } } +type FreezeAction = {values: Set; reason: Set}; + // Maintains a mapping of top-level variables to the kind of value they hold class InferenceState { #env: Environment; - // The kind of reach value, based on its allocation site + // The kind of each value, based on its allocation site #values: Map; /* * The set of values pointed to by each identifier. This is a set @@ -379,10 +364,10 @@ class InferenceState { * value is already frozen or is immutable. */ referenceAndRecordEffects( + freezeActions: Array, place: Place, effectKind: Effect, reason: ValueReason, - functionEffects: Array, ): void { const values = this.#variables.get(place.identifier.id); if (values === undefined) { @@ -399,66 +384,46 @@ class InferenceState { return; } - // Propagate effects of function expressions to the outer (ie current) effect context + const action = this.reference(place, effectKind, reason); + action && freezeActions.push(action); + } + + freezeValues(values: Set, reason: Set): void { for (const value of values) { - if ( - (value.kind === 'FunctionExpression' || - value.kind === 'ObjectMethod') && - value.loweredFunc.func.effects != null - ) { - for (const effect of value.loweredFunc.func.effects) { - if ( - effect.kind === 'GlobalMutation' || - effect.kind === 'ReactMutation' - ) { - // Known effects are always propagated upwards - functionEffects.push(effect); - } else { - /** - * Contextual effects need to be replayed against the current inference - * state, which may know more about the value to which the effect applied. - * The main cases are: - * 1. The mutated context value is _still_ a context value in the current scope, - * so we have to continue propagating the original context mutation. - * 2. The mutated context value is a mutable value in the current scope, - * so the context mutation was fine and we can skip propagating the effect. - * 3. The mutated context value is an immutable value in the current scope, - * resulting in a non-ContextMutation FunctionEffect. We propagate that new, - * more detailed effect to the current function context. + this.#values.set(value, { + kind: ValueKind.Frozen, + reason, + context: new Set(), + }); + if (value.kind === 'FunctionExpression') { + if ( + this.#env.config.enablePreserveExistingMemoizationGuarantees || + this.#env.config.enableTransitivelyFreezeFunctionExpressions + ) { + if (value.kind === 'FunctionExpression') { + /* + * We want to freeze the captured values, not mark the operands + * themselves as frozen. There could be mutations that occur + * before the freeze we are processing, and it would be invalid + * to overwrite those mutations as a freeze. */ - for (const place of effect.places) { - if (this.isDefined(place)) { - const replayedEffect = this.reference( - {...place, loc: effect.loc}, - effect.effect, - reason, - ); - if (replayedEffect != null) { - if (replayedEffect.kind === 'ContextMutation') { - // Case 1, still a context value so propagate the original effect - functionEffects.push(effect); - } else { - // Case 3, immutable value so propagate the more precise effect - functionEffects.push(replayedEffect); - } - } // else case 2, local mutable value so this effect was fine + for (const operand of eachInstructionValueOperand(value)) { + const operandValues = this.#variables.get(operand.identifier.id); + if (operandValues !== undefined) { + this.freezeValues(operandValues, reason); } } } } } } - const functionEffect = this.reference(place, effectKind, reason); - if (functionEffect !== null) { - functionEffects.push(functionEffect); - } } reference( place: Place, effectKind: Effect, reason: ValueReason, - ): FunctionEffect | null { + ): null | FreezeAction { const values = this.#variables.get(place.identifier.id); CompilerError.invariant(values !== undefined, { reason: '[InferReferenceEffects] Expected value to be initialized', @@ -468,7 +433,7 @@ class InferenceState { }); let valueKind: AbstractValue | null = this.kind(place); let effect: Effect | null = null; - let functionEffect: FunctionEffect | null = null; + let freeze: null | FreezeAction = null; switch (effectKind) { case Effect.Freeze: { if ( @@ -483,29 +448,7 @@ class InferenceState { reason: reasonSet, context: new Set(), }; - values.forEach(value => { - this.#values.set(value, { - kind: ValueKind.Frozen, - reason: reasonSet, - context: new Set(), - }); - - if ( - this.#env.config.enablePreserveExistingMemoizationGuarantees || - this.#env.config.enableTransitivelyFreezeFunctionExpressions - ) { - if (value.kind === 'FunctionExpression') { - for (const operand of eachInstructionValueOperand(value)) { - this.referenceAndRecordEffects( - operand, - Effect.Freeze, - ValueReason.Other, - [], - ); - } - } - } - }); + freeze = {values, reason: reasonSet}; } else { effect = Effect.Read; } @@ -523,91 +466,10 @@ class InferenceState { break; } case Effect.Mutate: { - if ( - isRefValueType(place.identifier) || - isUseRefType(place.identifier) - ) { - // no-op: refs are validate via ValidateNoRefAccessInRender - } else if (valueKind.kind === ValueKind.Context) { - functionEffect = { - kind: 'ContextMutation', - loc: place.loc, - effect: effectKind, - places: - valueKind.context.size === 0 - ? new Set([place]) - : valueKind.context, - }; - } else if ( - valueKind.kind !== ValueKind.Mutable && - // We ignore mutations of primitives since this is not a React-specific problem - valueKind.kind !== ValueKind.Primitive - ) { - let reason = getWriteErrorReason(valueKind); - functionEffect = { - kind: - valueKind.reason.size === 1 && - valueKind.reason.has(ValueReason.Global) - ? 'GlobalMutation' - : 'ReactMutation', - error: { - reason, - description: - place.identifier.name !== null && - place.identifier.name.kind === 'named' - ? `Found mutation of \`${place.identifier.name.value}\`` - : null, - loc: place.loc, - suggestions: null, - severity: ErrorSeverity.InvalidReact, - }, - }; - } effect = Effect.Mutate; break; } case Effect.Store: { - if ( - isRefValueType(place.identifier) || - isUseRefType(place.identifier) - ) { - // no-op: refs are validate via ValidateNoRefAccessInRender - } else if (valueKind.kind === ValueKind.Context) { - functionEffect = { - kind: 'ContextMutation', - loc: place.loc, - effect: effectKind, - places: - valueKind.context.size === 0 - ? new Set([place]) - : valueKind.context, - }; - } else if ( - valueKind.kind !== ValueKind.Mutable && - // We ignore mutations of primitives since this is not a React-specific problem - valueKind.kind !== ValueKind.Primitive - ) { - let reason = getWriteErrorReason(valueKind); - functionEffect = { - kind: - valueKind.reason.size === 1 && - valueKind.reason.has(ValueReason.Global) - ? 'GlobalMutation' - : 'ReactMutation', - error: { - reason, - description: - place.identifier.name !== null && - place.identifier.name.kind === 'named' - ? `Found mutation of \`${place.identifier.name.value}\`` - : null, - loc: place.loc, - suggestions: null, - severity: ErrorSeverity.InvalidReact, - }, - }; - } - /* * TODO(gsn): This should be bailout once we add bailout infra. * @@ -659,7 +521,7 @@ class InferenceState { suggestions: null, }); place.effect = effect; - return functionEffect; + return freeze; } /* @@ -773,7 +635,7 @@ class InferenceState { inferPhi(phi: Phi): void { const values: Set = new Set(); for (const [_, operand] of phi.operands) { - const operandValues = this.#variables.get(operand.id); + const operandValues = this.#variables.get(operand.identifier.id); // This is a backedge that will be handled later by State.merge if (operandValues === undefined) continue; for (const v of operandValues) { @@ -782,7 +644,7 @@ class InferenceState { } if (values.size > 0) { - this.#variables.set(phi.id.id, values); + this.#variables.set(phi.place.identifier.id, values); } } } @@ -950,15 +812,24 @@ function mergeAbstractValues( return {kind, reason, context}; } +type Continuation = + | { + kind: 'initialize'; + valueKind: AbstractValue; + effect: {kind: Effect; reason: ValueReason} | null; + lvalueEffect?: Effect; + } + | {kind: 'funeffects'}; + /* * Iterates over the given @param block, defining variables and * recording references on the @param state according to JS semantics. */ function inferBlock( env: Environment, - functionEffects: Array, state: InferenceState, block: BasicBlock, + functionEffects: Array, ): void { for (const phi of block.phis) { state.inferPhi(phi); @@ -966,24 +837,27 @@ function inferBlock( for (const instr of block.instructions) { const instrValue = instr.value; - let effect: {kind: Effect; reason: ValueReason} | null = null; - let lvalueEffect = Effect.ConditionallyMutate; - let valueKind: AbstractValue; + const defaultLvalueEffect = Effect.ConditionallyMutate; + let continuation: Continuation; + const freezeActions: Array = []; switch (instrValue.kind) { case 'BinaryExpression': { - valueKind = { - kind: ValueKind.Primitive, - reason: new Set([ValueReason.Other]), - context: new Set(), - }; - effect = { - kind: Effect.Read, - reason: ValueReason.Other, + continuation = { + kind: 'initialize', + valueKind: { + kind: ValueKind.Primitive, + reason: new Set([ValueReason.Other]), + context: new Set(), + }, + effect: { + kind: Effect.Read, + reason: ValueReason.Other, + }, }; break; } case 'ArrayExpression': { - valueKind = hasContextRefOperand(state, instrValue) + const valueKind: AbstractValue = hasContextRefOperand(state, instrValue) ? { kind: ValueKind.Context, reason: new Set([ValueReason.Other]), @@ -994,8 +868,12 @@ function inferBlock( reason: new Set([ValueReason.Other]), context: new Set(), }; - effect = {kind: Effect.Capture, reason: ValueReason.Other}; - lvalueEffect = Effect.Store; + continuation = { + kind: 'initialize', + valueKind, + effect: {kind: Effect.Capture, reason: ValueReason.Other}, + lvalueEffect: Effect.Store, + }; break; } case 'NewExpression': { @@ -1012,34 +890,35 @@ function inferBlock( * Classes / functions created during render could technically capture and * mutate their enclosing scope, which we currently do not detect. */ - valueKind = { + const valueKind: AbstractValue = { kind: ValueKind.Mutable, reason: new Set([ValueReason.Other]), context: new Set(), }; state.referenceAndRecordEffects( + freezeActions, instrValue.callee, Effect.Read, ValueReason.Other, - functionEffects, ); for (const operand of eachCallArgument(instrValue.args)) { state.referenceAndRecordEffects( + freezeActions, operand, Effect.ConditionallyMutate, ValueReason.Other, - functionEffects, ); } state.initialize(instrValue, valueKind); state.define(instr.lvalue, instrValue); - instr.lvalue.effect = lvalueEffect; - continue; + instr.lvalue.effect = Effect.ConditionallyMutate; + continuation = {kind: 'funeffects'}; + break; } case 'ObjectExpression': { - valueKind = hasContextRefOperand(state, instrValue) + const valueKind: AbstractValue = hasContextRefOperand(state, instrValue) ? { kind: ValueKind.Context, reason: new Set([ValueReason.Other]), @@ -1057,28 +936,28 @@ function inferBlock( if (property.key.kind === 'computed') { // Object keys must be primitives, so we know they're frozen at this point state.referenceAndRecordEffects( + freezeActions, property.key.name, Effect.Freeze, ValueReason.Other, - functionEffects, ); } // Object construction captures but does not modify the key/property values state.referenceAndRecordEffects( + freezeActions, property.place, Effect.Capture, ValueReason.Other, - functionEffects, ); break; } case 'Spread': { // Object construction captures but does not modify the key/property values state.referenceAndRecordEffects( + freezeActions, property.place, Effect.Capture, ValueReason.Other, - functionEffects, ); break; } @@ -1094,65 +973,67 @@ function inferBlock( state.initialize(instrValue, valueKind); state.define(instr.lvalue, instrValue); instr.lvalue.effect = Effect.Store; - continue; + continuation = {kind: 'funeffects'}; + break; } case 'UnaryExpression': { - valueKind = { - kind: ValueKind.Primitive, - reason: new Set([ValueReason.Other]), - context: new Set(), + continuation = { + kind: 'initialize', + valueKind: { + kind: ValueKind.Primitive, + reason: new Set([ValueReason.Other]), + context: new Set(), + }, + effect: {kind: Effect.Read, reason: ValueReason.Other}, }; - effect = {kind: Effect.Read, reason: ValueReason.Other}; break; } case 'UnsupportedNode': { // TODO: handle other statement kinds - valueKind = { - kind: ValueKind.Mutable, - reason: new Set([ValueReason.Other]), - context: new Set(), + continuation = { + kind: 'initialize', + valueKind: { + kind: ValueKind.Mutable, + reason: new Set([ValueReason.Other]), + context: new Set(), + }, + effect: null, }; break; } case 'JsxExpression': { if (instrValue.tag.kind === 'Identifier') { state.referenceAndRecordEffects( + freezeActions, instrValue.tag, Effect.Freeze, ValueReason.JsxCaptured, - functionEffects, ); } if (instrValue.children !== null) { for (const child of instrValue.children) { state.referenceAndRecordEffects( + freezeActions, child, Effect.Freeze, ValueReason.JsxCaptured, - functionEffects, ); } } for (const attr of instrValue.props) { if (attr.kind === 'JsxSpreadAttribute') { state.referenceAndRecordEffects( + freezeActions, attr.argument, Effect.Freeze, ValueReason.JsxCaptured, - functionEffects, ); } else { - const propEffects: Array = []; state.referenceAndRecordEffects( + freezeActions, attr.place, Effect.Freeze, ValueReason.JsxCaptured, - propEffects, - ); - functionEffects.push( - ...propEffects.filter( - effect => !isEffectSafeOutsideRender(effect), - ), ); } } @@ -1164,29 +1045,21 @@ function inferBlock( }); state.define(instr.lvalue, instrValue); instr.lvalue.effect = Effect.ConditionallyMutate; - continue; - } - case 'JsxFragment': { - valueKind = { - kind: ValueKind.Frozen, - reason: new Set([ValueReason.Other]), - context: new Set(), - }; - effect = { - kind: Effect.Freeze, - reason: ValueReason.Other, - }; + continuation = {kind: 'funeffects'}; break; } - case 'TaggedTemplateExpression': { - valueKind = { - kind: ValueKind.Mutable, - reason: new Set([ValueReason.Other]), - context: new Set(), - }; - effect = { - kind: Effect.ConditionallyMutate, - reason: ValueReason.Other, + case 'JsxFragment': { + continuation = { + kind: 'initialize', + valueKind: { + kind: ValueKind.Frozen, + reason: new Set([ValueReason.Other]), + context: new Set(), + }, + effect: { + kind: Effect.Freeze, + reason: ValueReason.Other, + }, }; break; } @@ -1195,103 +1068,89 @@ function inferBlock( * template literal (with no tag function) always produces * an immutable string */ - valueKind = { - kind: ValueKind.Primitive, - reason: new Set([ValueReason.Other]), - context: new Set(), + continuation = { + kind: 'initialize', + valueKind: { + kind: ValueKind.Primitive, + reason: new Set([ValueReason.Other]), + context: new Set(), + }, + effect: {kind: Effect.Read, reason: ValueReason.Other}, }; - effect = {kind: Effect.Read, reason: ValueReason.Other}; break; } case 'RegExpLiteral': { // RegExp instances are mutable objects - valueKind = { - kind: ValueKind.Mutable, - reason: new Set([ValueReason.Other]), - context: new Set(), - }; - effect = { - kind: Effect.ConditionallyMutate, - reason: ValueReason.Other, + continuation = { + kind: 'initialize', + valueKind: { + kind: ValueKind.Mutable, + reason: new Set([ValueReason.Other]), + context: new Set(), + }, + effect: { + kind: Effect.ConditionallyMutate, + reason: ValueReason.Other, + }, }; break; } case 'MetaProperty': { if (instrValue.meta !== 'import' || instrValue.property !== 'meta') { - continue; + continuation = {kind: 'funeffects'}; + break; } - - valueKind = { - kind: ValueKind.Global, - reason: new Set([ValueReason.Global]), - context: new Set(), + continuation = { + kind: 'initialize', + valueKind: { + kind: ValueKind.Global, + reason: new Set([ValueReason.Global]), + context: new Set(), + }, + effect: null, }; break; } case 'LoadGlobal': - valueKind = { - kind: ValueKind.Global, - reason: new Set([ValueReason.Global]), - context: new Set(), + continuation = { + kind: 'initialize', + valueKind: { + kind: ValueKind.Global, + reason: new Set([ValueReason.Global]), + context: new Set(), + }, + effect: null, }; break; case 'Debugger': case 'JSXText': case 'Primitive': { - valueKind = { - kind: ValueKind.Primitive, - reason: new Set([ValueReason.Other]), - context: new Set(), + continuation = { + kind: 'initialize', + valueKind: { + kind: ValueKind.Primitive, + reason: new Set([ValueReason.Other]), + context: new Set(), + }, + effect: null, }; break; } case 'ObjectMethod': case 'FunctionExpression': { let hasMutableOperand = false; + const mutableOperands: Array = []; for (const operand of eachInstructionOperand(instr)) { state.referenceAndRecordEffects( + freezeActions, operand, operand.effect === Effect.Unknown ? Effect.Read : operand.effect, ValueReason.Other, - [], ); - hasMutableOperand ||= isMutableEffect(operand.effect, operand.loc); - - /** - * If this function references other functions, propagate the referenced function's - * effects to this function. - * - * ``` - * let f = () => global = true; - * let g = () => f(); - * g(); - * ``` - * - * In this example, because `g` references `f`, we propagate the GlobalMutation from - * `f` to `g`. Thus, referencing `g` in `g()` will evaluate the GlobalMutation in the outer - * function effect context and report an error. But if instead we do: - * - * ``` - * let f = () => global = true; - * let g = () => f(); - * useEffect(() => g(), [g]) - * ``` - * - * Now `g`'s effects will be discarded since they're in a useEffect. - */ - const values = state.values(operand); - for (const value of values) { - if ( - (value.kind === 'ObjectMethod' || - value.kind === 'FunctionExpression') && - value.loweredFunc.func.effects !== null - ) { - instrValue.loweredFunc.func.effects ??= []; - instrValue.loweredFunc.func.effects.push( - ...value.loweredFunc.func.effects, - ); - } + if (isMutableEffect(operand.effect, operand.loc)) { + mutableOperands.push(operand); } + hasMutableOperand ||= isMutableEffect(operand.effect, operand.loc); } /* * If a closure did not capture any mutable values, then we can consider it to be @@ -1304,7 +1163,50 @@ function inferBlock( }); state.define(instr.lvalue, instrValue); instr.lvalue.effect = Effect.Store; - continue; + continuation = {kind: 'funeffects'}; + break; + } + case 'TaggedTemplateExpression': { + const operands = [...eachInstructionValueOperand(instrValue)]; + if (operands.length !== 1) { + // future-proofing to make sure we update this case when we support interpolation + CompilerError.throwTodo({ + reason: 'Support tagged template expressions with interpolations', + loc: instrValue.loc, + }); + } + const signature = getFunctionCallSignature( + env, + instrValue.tag.identifier.type, + ); + let calleeEffect = + signature?.calleeEffect ?? Effect.ConditionallyMutate; + const returnValueKind: AbstractValue = + signature !== null + ? { + kind: signature.returnValueKind, + reason: new Set([ + signature.returnValueReason ?? + ValueReason.KnownReturnSignature, + ]), + context: new Set(), + } + : { + kind: ValueKind.Mutable, + reason: new Set([ValueReason.Other]), + context: new Set(), + }; + state.referenceAndRecordEffects( + freezeActions, + instrValue.tag, + calleeEffect, + ValueReason.Other, + ); + state.initialize(instrValue, returnValueKind); + state.define(instr.lvalue, instrValue); + instr.lvalue.effect = Effect.ConditionallyMutate; + continuation = {kind: 'funeffects'}; + break; } case 'CallExpression': { const signature = getFunctionCallSignature( @@ -1330,50 +1232,39 @@ function inferBlock( context: new Set(), }; let hasCaptureArgument = false; - let isHook = getHookKind(env, instrValue.callee.identifier) != null; for (let i = 0; i < instrValue.args.length; i++) { - const argumentEffects: Array = []; const arg = instrValue.args[i]; const place = arg.kind === 'Identifier' ? arg : arg.place; if (effects !== null) { state.referenceAndRecordEffects( + freezeActions, place, effects[i], ValueReason.Other, - argumentEffects, ); } else { state.referenceAndRecordEffects( + freezeActions, place, Effect.ConditionallyMutate, ValueReason.Other, - argumentEffects, ); } - /* - * Join the effects of the argument with the effects of the enclosing function, - * unless the we're detecting a global mutation inside a useEffect hook - */ - functionEffects.push( - ...argumentEffects.filter( - argEffect => !isHook || !isEffectSafeOutsideRender(argEffect), - ), - ); hasCaptureArgument ||= place.effect === Effect.Capture; } if (signature !== null) { state.referenceAndRecordEffects( + freezeActions, instrValue.callee, signature.calleeEffect, ValueReason.Other, - functionEffects, ); } else { state.referenceAndRecordEffects( + freezeActions, instrValue.callee, Effect.ConditionallyMutate, ValueReason.Other, - functionEffects, ); } hasCaptureArgument ||= instrValue.callee.effect === Effect.Capture; @@ -1383,7 +1274,8 @@ function inferBlock( instr.lvalue.effect = hasCaptureArgument ? Effect.Store : Effect.ConditionallyMutate; - continue; + continuation = {kind: 'funeffects'}; + break; } case 'MethodCall': { CompilerError.invariant(state.isDefined(instrValue.receiver), { @@ -1394,10 +1286,10 @@ function inferBlock( suggestions: null, }); state.referenceAndRecordEffects( + freezeActions, instrValue.property, Effect.Read, ValueReason.Other, - functionEffects, ); const signature = getFunctionCallSignature( @@ -1430,17 +1322,17 @@ function inferBlock( for (const arg of instrValue.args) { const place = arg.kind === 'Identifier' ? arg : arg.place; state.referenceAndRecordEffects( + freezeActions, place, Effect.Read, ValueReason.Other, - functionEffects, ); } state.referenceAndRecordEffects( + freezeActions, instrValue.receiver, Effect.Capture, ValueReason.Other, - functionEffects, ); state.initialize(instrValue, returnValueKind); state.define(instr.lvalue, instrValue); @@ -1448,15 +1340,14 @@ function inferBlock( instrValue.receiver.effect === Effect.Capture ? Effect.Store : Effect.ConditionallyMutate; - continue; + continuation = {kind: 'funeffects'}; + break; } const effects = signature !== null ? getFunctionEffects(instrValue, signature) : null; let hasCaptureArgument = false; - let isHook = getHookKind(env, instrValue.property.identifier) != null; for (let i = 0; i < instrValue.args.length; i++) { - const argumentEffects: Array = []; const arg = instrValue.args[i]; const place = arg.kind === 'Identifier' ? arg : arg.place; if (effects !== null) { @@ -1465,43 +1356,34 @@ function inferBlock( * mutating effects */ state.referenceAndRecordEffects( + freezeActions, place, effects[i], ValueReason.Other, - argumentEffects, ); } else { state.referenceAndRecordEffects( + freezeActions, place, Effect.ConditionallyMutate, ValueReason.Other, - argumentEffects, ); } - /* - * Join the effects of the argument with the effects of the enclosing function, - * unless the we're detecting a global mutation inside a useEffect hook - */ - functionEffects.push( - ...argumentEffects.filter( - argEffect => !isHook || !isEffectSafeOutsideRender(argEffect), - ), - ); hasCaptureArgument ||= place.effect === Effect.Capture; } if (signature !== null) { state.referenceAndRecordEffects( + freezeActions, instrValue.receiver, signature.calleeEffect, ValueReason.Other, - functionEffects, ); } else { state.referenceAndRecordEffects( + freezeActions, instrValue.receiver, Effect.ConditionallyMutate, ValueReason.Other, - functionEffects, ); } hasCaptureArgument ||= instrValue.receiver.effect === Effect.Capture; @@ -1511,7 +1393,8 @@ function inferBlock( instr.lvalue.effect = hasCaptureArgument ? Effect.Store : Effect.ConditionallyMutate; - continue; + continuation = {kind: 'funeffects'}; + break; } case 'PropertyStore': { const effect = @@ -1519,45 +1402,50 @@ function inferBlock( ? Effect.ConditionallyMutate : Effect.Capture; state.referenceAndRecordEffects( + freezeActions, instrValue.value, effect, ValueReason.Other, - functionEffects, ); state.referenceAndRecordEffects( + freezeActions, instrValue.object, Effect.Store, ValueReason.Other, - functionEffects, ); const lvalue = instr.lvalue; state.alias(lvalue, instrValue.value); lvalue.effect = Effect.Store; - continue; + continuation = {kind: 'funeffects'}; + break; } case 'PropertyDelete': { // `delete` returns a boolean (immutable) and modifies the object - valueKind = { - kind: ValueKind.Primitive, - reason: new Set([ValueReason.Other]), - context: new Set(), + continuation = { + kind: 'initialize', + valueKind: { + kind: ValueKind.Primitive, + reason: new Set([ValueReason.Other]), + context: new Set(), + }, + effect: {kind: Effect.Mutate, reason: ValueReason.Other}, }; - effect = {kind: Effect.Mutate, reason: ValueReason.Other}; break; } case 'PropertyLoad': { state.referenceAndRecordEffects( + freezeActions, instrValue.object, Effect.Read, ValueReason.Other, - functionEffects, ); const lvalue = instr.lvalue; lvalue.effect = Effect.ConditionallyMutate; state.initialize(instrValue, state.kind(instrValue.object)); state.define(lvalue, instrValue); - continue; + continuation = {kind: 'funeffects'}; + break; } case 'ComputedStore': { const effect = @@ -1565,41 +1453,42 @@ function inferBlock( ? Effect.ConditionallyMutate : Effect.Capture; state.referenceAndRecordEffects( + freezeActions, instrValue.value, effect, ValueReason.Other, - functionEffects, ); state.referenceAndRecordEffects( + freezeActions, instrValue.property, Effect.Capture, ValueReason.Other, - functionEffects, ); state.referenceAndRecordEffects( + freezeActions, instrValue.object, Effect.Store, ValueReason.Other, - functionEffects, ); const lvalue = instr.lvalue; state.alias(lvalue, instrValue.value); lvalue.effect = Effect.Store; - continue; + continuation = {kind: 'funeffects'}; + break; } case 'ComputedDelete': { state.referenceAndRecordEffects( + freezeActions, instrValue.object, Effect.Mutate, ValueReason.Other, - functionEffects, ); state.referenceAndRecordEffects( + freezeActions, instrValue.property, Effect.Read, ValueReason.Other, - functionEffects, ); state.initialize(instrValue, { kind: ValueKind.Primitive, @@ -1608,26 +1497,28 @@ function inferBlock( }); state.define(instr.lvalue, instrValue); instr.lvalue.effect = Effect.Mutate; - continue; + continuation = {kind: 'funeffects'}; + break; } case 'ComputedLoad': { state.referenceAndRecordEffects( + freezeActions, instrValue.object, Effect.Read, ValueReason.Other, - functionEffects, ); state.referenceAndRecordEffects( + freezeActions, instrValue.property, Effect.Read, ValueReason.Other, - functionEffects, ); const lvalue = instr.lvalue; lvalue.effect = Effect.ConditionallyMutate; state.initialize(instrValue, state.kind(instrValue.object)); state.define(lvalue, instrValue); - continue; + continuation = {kind: 'funeffects'}; + break; } case 'Await': { state.initialize(instrValue, state.kind(instrValue.value)); @@ -1637,15 +1528,16 @@ function inferBlock( * will occur. */ state.referenceAndRecordEffects( + freezeActions, instrValue.value, Effect.ConditionallyMutate, ValueReason.Other, - functionEffects, ); const lvalue = instr.lvalue; lvalue.effect = Effect.ConditionallyMutate; state.alias(lvalue, instrValue.value); - continue; + continuation = {kind: 'funeffects'}; + break; } case 'TypeCastExpression': { /* @@ -1658,32 +1550,33 @@ function inferBlock( */ state.initialize(instrValue, state.kind(instrValue.value)); state.referenceAndRecordEffects( + freezeActions, instrValue.value, Effect.Read, ValueReason.Other, - functionEffects, ); const lvalue = instr.lvalue; lvalue.effect = Effect.ConditionallyMutate; state.alias(lvalue, instrValue.value); - continue; + continuation = {kind: 'funeffects'}; + break; } case 'StartMemoize': case 'FinishMemoize': { for (const val of eachInstructionValueOperand(instrValue)) { if (env.config.enablePreserveExistingMemoizationGuarantees) { state.referenceAndRecordEffects( + freezeActions, val, Effect.Freeze, ValueReason.Other, - [], ); } else { state.referenceAndRecordEffects( + freezeActions, val, Effect.Read, ValueReason.Other, - [], ); } } @@ -1695,7 +1588,8 @@ function inferBlock( context: new Set(), }); state.define(lvalue, instrValue); - continue; + continuation = {kind: 'funeffects'}; + break; } case 'LoadLocal': { const lvalue = instr.lvalue; @@ -1705,29 +1599,31 @@ function inferBlock( ? Effect.ConditionallyMutate : Effect.Capture; state.referenceAndRecordEffects( + freezeActions, instrValue.place, effect, ValueReason.Other, - [], ); lvalue.effect = Effect.ConditionallyMutate; // direct aliasing: `a = b`; state.alias(lvalue, instrValue.place); - continue; + continuation = {kind: 'funeffects'}; + break; } case 'LoadContext': { state.referenceAndRecordEffects( + freezeActions, instrValue.place, Effect.Capture, ValueReason.Other, - functionEffects, ); const lvalue = instr.lvalue; lvalue.effect = Effect.ConditionallyMutate; const valueKind = state.kind(instrValue.place); state.initialize(instrValue, valueKind); state.define(lvalue, instrValue); - continue; + continuation = {kind: 'funeffects'}; + break; } case 'DeclareLocal': { const value = UndefinedValue; @@ -1747,7 +1643,8 @@ function inferBlock( }, ); state.define(instrValue.lvalue.place, value); - continue; + continuation = {kind: 'funeffects'}; + break; } case 'DeclareContext': { state.initialize(instrValue, { @@ -1756,7 +1653,8 @@ function inferBlock( context: new Set(), }); state.define(instrValue.lvalue.place, instrValue); - continue; + continuation = {kind: 'funeffects'}; + break; } case 'PostfixUpdate': case 'PrefixUpdate': { @@ -1766,10 +1664,10 @@ function inferBlock( ? Effect.ConditionallyMutate : Effect.Capture; state.referenceAndRecordEffects( + freezeActions, instrValue.value, effect, ValueReason.Other, - functionEffects, ); const lvalue = instr.lvalue; @@ -1783,7 +1681,8 @@ function inferBlock( * replacing it */ instrValue.lvalue.effect = Effect.Store; - continue; + continuation = {kind: 'funeffects'}; + break; } case 'StoreLocal': { const effect = @@ -1792,10 +1691,10 @@ function inferBlock( ? Effect.ConditionallyMutate : Effect.Capture; state.referenceAndRecordEffects( + freezeActions, instrValue.value, effect, ValueReason.Other, - [], ); const lvalue = instr.lvalue; @@ -1809,48 +1708,40 @@ function inferBlock( * replacing it */ instrValue.lvalue.place.effect = Effect.Store; - continue; + continuation = {kind: 'funeffects'}; + break; } case 'StoreContext': { state.referenceAndRecordEffects( + freezeActions, instrValue.value, Effect.ConditionallyMutate, ValueReason.Other, - functionEffects, ); state.referenceAndRecordEffects( + freezeActions, instrValue.lvalue.place, Effect.Mutate, ValueReason.Other, - functionEffects, ); const lvalue = instr.lvalue; state.alias(lvalue, instrValue.value); lvalue.effect = Effect.Store; - continue; + continuation = {kind: 'funeffects'}; + break; } case 'StoreGlobal': { state.referenceAndRecordEffects( + freezeActions, instrValue.value, Effect.Capture, ValueReason.Other, - functionEffects, ); const lvalue = instr.lvalue; lvalue.effect = Effect.Store; - - functionEffects.push({ - kind: 'GlobalMutation', - error: { - reason: - 'Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render)', - loc: instr.loc, - suggestions: null, - severity: ErrorSeverity.InvalidReact, - }, - }); - continue; + continuation = {kind: 'funeffects'}; + break; } case 'Destructure': { let effect: Effect = Effect.Capture; @@ -1864,10 +1755,10 @@ function inferBlock( } } state.referenceAndRecordEffects( + freezeActions, instrValue.value, effect, ValueReason.Other, - functionEffects, ); const lvalue = instr.lvalue; @@ -1883,7 +1774,8 @@ function inferBlock( */ place.effect = Effect.Store; } - continue; + continuation = {kind: 'funeffects'}; + break; } case 'GetIterator': { /** @@ -1903,6 +1795,8 @@ function inferBlock( const kind = state.kind(instrValue.collection).kind; const isMutable = kind === ValueKind.Mutable || kind === ValueKind.Context; + let effect; + let valueKind: AbstractValue; if (!isMutable || isArrayType(instrValue.collection.identifier)) { // Case 1, assume iterator is a separate mutable object effect = { @@ -1922,7 +1816,12 @@ function inferBlock( }; valueKind = state.kind(instrValue.collection); } - lvalueEffect = Effect.Store; + continuation = { + kind: 'initialize', + effect, + valueKind, + lvalueEffect: Effect.Store, + }; break; } case 'IteratorNext': { @@ -1937,10 +1836,10 @@ function inferBlock( * ConditionallyMutate reflects this "mutate if mutable" semantic. */ state.referenceAndRecordEffects( + freezeActions, instrValue.iterator, Effect.ConditionallyMutate, ValueReason.Other, - functionEffects, ); /** * Regardless of the effect on the iterator, the *result* of advancing the iterator @@ -1949,23 +1848,27 @@ function inferBlock( * ensure that the item is mutable or frozen if the collection is mutable/frozen. */ state.referenceAndRecordEffects( + freezeActions, instrValue.collection, Effect.Capture, ValueReason.Other, - functionEffects, ); state.initialize(instrValue, state.kind(instrValue.collection)); state.define(instr.lvalue, instrValue); instr.lvalue.effect = Effect.Store; - continue; + continuation = {kind: 'funeffects'}; + break; } case 'NextPropertyOf': { - effect = {kind: Effect.Read, reason: ValueReason.Other}; - lvalueEffect = Effect.Store; - valueKind = { - kind: ValueKind.Primitive, - reason: new Set([ValueReason.Other]), - context: new Set(), + continuation = { + kind: 'initialize', + effect: {kind: Effect.Read, reason: ValueReason.Other}, + lvalueEffect: Effect.Store, + valueKind: { + kind: ValueKind.Primitive, + reason: new Set([ValueReason.Other]), + context: new Set(), + }, }; break; } @@ -1974,26 +1877,34 @@ function inferBlock( } } - for (const operand of eachInstructionOperand(instr)) { - CompilerError.invariant(effect != null, { - reason: `effectKind must be set for instruction value \`${instrValue.kind}\``, - description: null, - loc: instrValue.loc, - suggestions: null, - }); - state.referenceAndRecordEffects( - operand, - effect.kind, - effect.reason, - functionEffects, - ); + if (continuation.kind === 'initialize') { + for (const operand of eachInstructionOperand(instr)) { + CompilerError.invariant(continuation.effect != null, { + reason: `effectKind must be set for instruction value \`${instrValue.kind}\``, + description: null, + loc: instrValue.loc, + suggestions: null, + }); + state.referenceAndRecordEffects( + freezeActions, + operand, + continuation.effect.kind, + continuation.effect.reason, + ); + } + + state.initialize(instrValue, continuation.valueKind); + state.define(instr.lvalue, instrValue); + instr.lvalue.effect = continuation.lvalueEffect ?? defaultLvalueEffect; } - state.initialize(instrValue, valueKind); - state.define(instr.lvalue, instrValue); - instr.lvalue.effect = lvalueEffect; + functionEffects.push(...inferInstructionFunctionEffects(env, state, instr)); + freezeActions.forEach(({values, reason}) => + state.freezeValues(values, reason), + ); } + const terminalFreezeActions: Array = []; for (const operand of eachTerminalOperand(block.terminal)) { let effect; if (block.terminal.kind === 'return' || block.terminal.kind === 'throw') { @@ -2008,17 +1919,17 @@ function inferBlock( } else { effect = Effect.Read; } - const propEffects: Array = []; state.referenceAndRecordEffects( + terminalFreezeActions, operand, effect, ValueReason.Other, - propEffects, - ); - functionEffects.push( - ...propEffects.filter(effect => !isEffectSafeOutsideRender(effect)), ); } + functionEffects.push(...inferTerminalFunctionEffects(state, block)); + terminalFreezeActions.forEach(({values, reason}) => + state.freezeValues(values, reason), + ); } function hasContextRefOperand( @@ -2054,7 +1965,7 @@ export function getFunctionCallSignature( * @param sig * @returns Inferred effects of function arguments, or null if inference fails. */ -function getFunctionEffects( +export function getFunctionEffects( fn: MethodCall | CallExpression, sig: FunctionSignature, ): Array | null { @@ -2129,27 +2040,3 @@ function areArgumentsImmutableAndNonMutating( } return true; } - -function isEffectSafeOutsideRender(effect: FunctionEffect): boolean { - return effect.kind === 'GlobalMutation'; -} - -function getWriteErrorReason(abstractValue: AbstractValue): string { - if (abstractValue.reason.has(ValueReason.Global)) { - return 'Writing to a variable defined outside a component or hook is not allowed. Consider using an effect'; - } else if (abstractValue.reason.has(ValueReason.JsxCaptured)) { - return 'Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX'; - } else if (abstractValue.reason.has(ValueReason.Context)) { - return `Mutating a value returned from 'useContext()', which should not be mutated`; - } else if (abstractValue.reason.has(ValueReason.KnownReturnSignature)) { - return 'Mutating a value returned from a function whose return value should not be mutated'; - } else if (abstractValue.reason.has(ValueReason.ReactiveFunctionArgument)) { - return 'Mutating component props or hook arguments is not allowed. Consider using a local variable instead'; - } else if (abstractValue.reason.has(ValueReason.State)) { - return "Mutating a value returned from 'useState()', which should not be mutated. Use the setter function to update instead"; - } else if (abstractValue.reason.has(ValueReason.ReducerState)) { - return "Mutating a value returned from 'useReducer()', which should not be mutated. Use the dispatch function to update instead"; - } else { - return 'This mutates a variable that React considers immutable'; - } -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/index.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/index.ts index ee76a37bcb4b7..93b99fb385262 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/index.ts @@ -11,3 +11,4 @@ export {inferMutableRanges} from './InferMutableRanges'; export {inferReactivePlaces} from './InferReactivePlaces'; export {default as inferReferenceEffects} from './InferReferenceEffects'; export {inlineImmediatelyInvokedFunctionExpressions} from './InlineImmediatelyInvokedFunctionExpressions'; +export {inferEffectDependencies} from './InferEffectDependencies'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/ConstantPropagation.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/ConstantPropagation.ts index 112cca7491483..a9f62c1986efa 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/ConstantPropagation.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/ConstantPropagation.ts @@ -8,7 +8,6 @@ import {isValidIdentifier} from '@babel/types'; import {CompilerError} from '../CompilerError'; import { - Environment, GotoVariant, HIRFunction, IdentifierId, @@ -117,7 +116,7 @@ function applyConstantPropagation( for (const phi of block.phis) { let value = evaluatePhi(phi, constants); if (value !== null) { - constants.set(phi.id.id, value); + constants.set(phi.place.identifier.id, value); } } @@ -130,7 +129,7 @@ function applyConstantPropagation( continue; } const instr = block.instructions[i]!; - const value = evaluateInstruction(fn.env, constants, instr); + const value = evaluateInstruction(constants, instr); if (value !== null) { constants.set(instr.lvalue.identifier.id, value); } @@ -167,7 +166,7 @@ function applyConstantPropagation( function evaluatePhi(phi: Phi, constants: Constants): Constant | null { let value: Constant | null = null; for (const [, operand] of phi.operands) { - const operandValue = constants.get(operand.id) ?? null; + const operandValue = constants.get(operand.identifier.id) ?? null; // did not find a constant, can't constant propogate if (operandValue === null) { return null; @@ -223,7 +222,6 @@ function evaluatePhi(phi: Phi, constants: Constants): Constant | null { } function evaluateInstruction( - env: Environment, constants: Constants, instr: Instruction, ): Constant | null { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts index 3cdba101a82ac..2b752c6dfd28e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts @@ -6,7 +6,6 @@ */ import { - ArrayPattern, BlockId, HIRFunction, Identifier, @@ -42,7 +41,7 @@ export function deadCodeElimination(fn: HIRFunction): void { */ for (const [, block] of fn.body.blocks) { for (const phi of block.phis) { - if (!state.isIdOrNameUsed(phi.id)) { + if (!state.isIdOrNameUsed(phi.place.identifier)) { block.phis.delete(phi); } } @@ -58,6 +57,14 @@ export function deadCodeElimination(fn: HIRFunction): void { } } } + + /** + * Constant propagation and DCE may have deleted or rewritten instructions + * that reference context variables. + */ + retainWhere(fn.context, contextVar => + state.isIdOrNameUsed(contextVar.identifier), + ); } class State { @@ -159,9 +166,9 @@ function findReferencedIdentifiers(fn: HIRFunction): State { } } for (const phi of block.phis) { - if (state.isIdOrNameUsed(phi.id)) { + if (state.isIdOrNameUsed(phi.place.identifier)) { for (const [_pred, operand] of phi.operands) { - state.reference(operand); + state.reference(operand.identifier); } } } @@ -176,29 +183,28 @@ function rewriteInstruction(instr: Instruction, state: State): void { switch (instr.value.lvalue.pattern.kind) { case 'ArrayPattern': { /* - * For arrays, we can only eliminate unused items from the end of the array, - * so we iterate from the end and break once we find a used item. Note that - * we already know at least one item is used, from the pruneableValue check. + * For arrays, we can prune items prior to the end by replacing + * them with a hole. Items at the end can simply be dropped. */ - let nextItems: ArrayPattern['items'] | null = null; - const originalItems = instr.value.lvalue.pattern.items; - for (let i = originalItems.length - 1; i >= 0; i--) { - const item = originalItems[i]; + let lastEntryIndex = 0; + const items = instr.value.lvalue.pattern.items; + for (let i = 0; i < items.length; i++) { + const item = items[i]; if (item.kind === 'Identifier') { - if (state.isIdOrNameUsed(item.identifier)) { - nextItems = originalItems.slice(0, i + 1); - break; + if (!state.isIdOrNameUsed(item.identifier)) { + items[i] = {kind: 'Hole'}; + } else { + lastEntryIndex = i; } } else if (item.kind === 'Spread') { - if (state.isIdOrNameUsed(item.place.identifier)) { - nextItems = originalItems.slice(0, i + 1); - break; + if (!state.isIdOrNameUsed(item.place.identifier)) { + items[i] = {kind: 'Hole'}; + } else { + lastEntryIndex = i; } } } - if (nextItems !== null) { - instr.value.lvalue.pattern.items = nextItems; - } + items.length = lastEntryIndex + 1; break; } case 'ObjectPattern': { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/InlineJsxTransform.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/InlineJsxTransform.ts new file mode 100644 index 0000000000000..2851ed7ee39a1 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/InlineJsxTransform.ts @@ -0,0 +1,775 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { + BasicBlock, + BlockId, + BuiltinTag, + DeclarationId, + Effect, + forkTemporaryIdentifier, + GotoTerminal, + GotoVariant, + HIRFunction, + Identifier, + IfTerminal, + Instruction, + InstructionKind, + JsxAttribute, + makeInstructionId, + ObjectProperty, + Phi, + Place, + promoteTemporary, + SpreadPattern, +} from '../HIR'; +import { + createTemporaryPlace, + fixScopeAndIdentifierRanges, + markInstructionIds, + markPredecessors, + reversePostorderBlocks, +} from '../HIR/HIRBuilder'; +import {CompilerError, EnvironmentConfig} from '..'; +import { + mapInstructionLValues, + mapInstructionOperands, + mapInstructionValueOperands, + mapTerminalOperands, +} from '../HIR/visitors'; + +type InlinedJsxDeclarationMap = Map< + DeclarationId, + {identifier: Identifier; blockIdsToIgnore: Set} +>; + +/** + * A prod-only, RN optimization to replace JSX with inlined ReactElement object literals + * + * Example: + * <>foo + * _______________ + * let t1; + * if (__DEV__) { + * t1 = <>foo + * } else { + * t1 = {...} + * } + * + */ +export function inlineJsxTransform( + fn: HIRFunction, + inlineJsxTransformConfig: NonNullable< + EnvironmentConfig['inlineJsxTransform'] + >, +): void { + const inlinedJsxDeclarations: InlinedJsxDeclarationMap = new Map(); + /** + * Step 1: Codegen the conditional and ReactElement object literal + */ + for (const [_, currentBlock] of [...fn.body.blocks]) { + let fallthroughBlockInstructions: Array | null = null; + const instructionCount = currentBlock.instructions.length; + for (let i = 0; i < instructionCount; i++) { + const instr = currentBlock.instructions[i]!; + // TODO: Support value blocks + if (currentBlock.kind === 'value') { + fn.env.logger?.logEvent(fn.env.filename, { + kind: 'CompileDiagnostic', + fnLoc: null, + detail: { + reason: 'JSX Inlining is not supported on value blocks', + loc: instr.loc, + }, + }); + continue; + } + switch (instr.value.kind) { + case 'JsxExpression': + case 'JsxFragment': { + /** + * Split into blocks for new IfTerminal: + * current, then, else, fallthrough + */ + const currentBlockInstructions = currentBlock.instructions.slice( + 0, + i, + ); + const thenBlockInstructions = currentBlock.instructions.slice( + i, + i + 1, + ); + const elseBlockInstructions: Array = []; + fallthroughBlockInstructions ??= currentBlock.instructions.slice( + i + 1, + ); + + const fallthroughBlockId = fn.env.nextBlockId; + const fallthroughBlock: BasicBlock = { + kind: currentBlock.kind, + id: fallthroughBlockId, + instructions: fallthroughBlockInstructions, + terminal: currentBlock.terminal, + preds: new Set(), + phis: new Set(), + }; + + /** + * Complete current block + * - Add instruction for variable declaration + * - Add instruction for LoadGlobal used by conditional + * - End block with a new IfTerminal + */ + const varPlace = createTemporaryPlace(fn.env, instr.value.loc); + promoteTemporary(varPlace.identifier); + const varLValuePlace = createTemporaryPlace(fn.env, instr.value.loc); + const thenVarPlace = { + ...varPlace, + identifier: forkTemporaryIdentifier( + fn.env.nextIdentifierId, + varPlace.identifier, + ), + }; + const elseVarPlace = { + ...varPlace, + identifier: forkTemporaryIdentifier( + fn.env.nextIdentifierId, + varPlace.identifier, + ), + }; + const varInstruction: Instruction = { + id: makeInstructionId(0), + lvalue: {...varLValuePlace}, + value: { + kind: 'DeclareLocal', + lvalue: {place: {...varPlace}, kind: InstructionKind.Let}, + type: null, + loc: instr.value.loc, + }, + loc: instr.loc, + }; + currentBlockInstructions.push(varInstruction); + + const devGlobalPlace = createTemporaryPlace(fn.env, instr.value.loc); + const devGlobalInstruction: Instruction = { + id: makeInstructionId(0), + lvalue: {...devGlobalPlace, effect: Effect.Mutate}, + value: { + kind: 'LoadGlobal', + binding: { + kind: 'Global', + name: inlineJsxTransformConfig.globalDevVar, + }, + loc: instr.value.loc, + }, + loc: instr.loc, + }; + currentBlockInstructions.push(devGlobalInstruction); + const thenBlockId = fn.env.nextBlockId; + const elseBlockId = fn.env.nextBlockId; + const ifTerminal: IfTerminal = { + kind: 'if', + test: {...devGlobalPlace, effect: Effect.Read}, + consequent: thenBlockId, + alternate: elseBlockId, + fallthrough: fallthroughBlockId, + loc: instr.loc, + id: makeInstructionId(0), + }; + currentBlock.instructions = currentBlockInstructions; + currentBlock.terminal = ifTerminal; + + /** + * Set up then block where we put the original JSX return + */ + const thenBlock: BasicBlock = { + id: thenBlockId, + instructions: thenBlockInstructions, + kind: 'block', + phis: new Set(), + preds: new Set(), + terminal: { + kind: 'goto', + block: fallthroughBlockId, + variant: GotoVariant.Break, + id: makeInstructionId(0), + loc: instr.loc, + }, + }; + fn.body.blocks.set(thenBlockId, thenBlock); + + const resassignElsePlace = createTemporaryPlace( + fn.env, + instr.value.loc, + ); + const reassignElseInstruction: Instruction = { + id: makeInstructionId(0), + lvalue: {...resassignElsePlace}, + value: { + kind: 'StoreLocal', + lvalue: { + place: elseVarPlace, + kind: InstructionKind.Reassign, + }, + value: {...instr.lvalue}, + type: null, + loc: instr.value.loc, + }, + loc: instr.loc, + }; + thenBlockInstructions.push(reassignElseInstruction); + + /** + * Set up else block where we add new codegen + */ + const elseBlockTerminal: GotoTerminal = { + kind: 'goto', + block: fallthroughBlockId, + variant: GotoVariant.Break, + id: makeInstructionId(0), + loc: instr.loc, + }; + const elseBlock: BasicBlock = { + id: elseBlockId, + instructions: elseBlockInstructions, + kind: 'block', + phis: new Set(), + preds: new Set(), + terminal: elseBlockTerminal, + }; + fn.body.blocks.set(elseBlockId, elseBlock); + + /** + * ReactElement object literal codegen + */ + const {refProperty, keyProperty, propsProperty} = + createPropsProperties( + fn, + instr, + elseBlockInstructions, + instr.value.kind === 'JsxExpression' ? instr.value.props : [], + instr.value.children, + ); + const reactElementInstructionPlace = createTemporaryPlace( + fn.env, + instr.value.loc, + ); + const reactElementInstruction: Instruction = { + id: makeInstructionId(0), + lvalue: {...reactElementInstructionPlace, effect: Effect.Store}, + value: { + kind: 'ObjectExpression', + properties: [ + createSymbolProperty( + fn, + instr, + elseBlockInstructions, + '$$typeof', + inlineJsxTransformConfig.elementSymbol, + ), + instr.value.kind === 'JsxExpression' + ? createTagProperty( + fn, + instr, + elseBlockInstructions, + instr.value.tag, + ) + : createSymbolProperty( + fn, + instr, + elseBlockInstructions, + 'type', + 'react.fragment', + ), + refProperty, + keyProperty, + propsProperty, + ], + loc: instr.value.loc, + }, + loc: instr.loc, + }; + elseBlockInstructions.push(reactElementInstruction); + + const reassignConditionalInstruction: Instruction = { + id: makeInstructionId(0), + lvalue: {...createTemporaryPlace(fn.env, instr.value.loc)}, + value: { + kind: 'StoreLocal', + lvalue: { + place: {...elseVarPlace}, + kind: InstructionKind.Reassign, + }, + value: {...reactElementInstruction.lvalue}, + type: null, + loc: instr.value.loc, + }, + loc: instr.loc, + }; + elseBlockInstructions.push(reassignConditionalInstruction); + + /** + * Create phis to reassign the var + */ + const operands: Map = new Map(); + operands.set(thenBlockId, { + ...elseVarPlace, + }); + operands.set(elseBlockId, { + ...thenVarPlace, + }); + + const phiIdentifier = forkTemporaryIdentifier( + fn.env.nextIdentifierId, + varPlace.identifier, + ); + const phiPlace = { + ...createTemporaryPlace(fn.env, instr.value.loc), + identifier: phiIdentifier, + }; + const phis: Set = new Set([ + { + kind: 'Phi', + operands, + place: phiPlace, + }, + ]); + fallthroughBlock.phis = phis; + fn.body.blocks.set(fallthroughBlockId, fallthroughBlock); + + /** + * Track this JSX instruction so we can replace references in step 2 + */ + inlinedJsxDeclarations.set(instr.lvalue.identifier.declarationId, { + identifier: phiIdentifier, + blockIdsToIgnore: new Set([thenBlockId, elseBlockId]), + }); + break; + } + case 'FunctionExpression': + case 'ObjectMethod': { + inlineJsxTransform( + instr.value.loweredFunc.func, + inlineJsxTransformConfig, + ); + break; + } + } + } + } + + /** + * Step 2: Replace declarations with new phi values + */ + for (const [blockId, block] of fn.body.blocks) { + for (const instr of block.instructions) { + mapInstructionOperands(instr, place => + handlePlace(place, blockId, inlinedJsxDeclarations), + ); + + mapInstructionLValues(instr, lvalue => + handlelValue(lvalue, blockId, inlinedJsxDeclarations), + ); + + mapInstructionValueOperands(instr.value, place => + handlePlace(place, blockId, inlinedJsxDeclarations), + ); + } + + mapTerminalOperands(block.terminal, place => + handlePlace(place, blockId, inlinedJsxDeclarations), + ); + + if (block.terminal.kind === 'scope') { + const scope = block.terminal.scope; + for (const dep of scope.dependencies) { + dep.identifier = handleIdentifier( + dep.identifier, + inlinedJsxDeclarations, + ); + } + + for (const [origId, decl] of [...scope.declarations]) { + const newDecl = handleIdentifier( + decl.identifier, + inlinedJsxDeclarations, + ); + if (newDecl.id !== origId) { + scope.declarations.delete(origId); + scope.declarations.set(decl.identifier.id, { + identifier: newDecl, + scope: decl.scope, + }); + } + } + } + } + + /** + * Step 3: Fixup the HIR + * Restore RPO, ensure correct predecessors, renumber instructions, fix scope and ranges. + */ + reversePostorderBlocks(fn.body); + markPredecessors(fn.body); + markInstructionIds(fn.body); + fixScopeAndIdentifierRanges(fn.body); +} + +function createSymbolProperty( + fn: HIRFunction, + instr: Instruction, + nextInstructions: Array, + propertyName: string, + symbolName: string, +): ObjectProperty { + const symbolPlace = createTemporaryPlace(fn.env, instr.value.loc); + const symbolInstruction: Instruction = { + id: makeInstructionId(0), + lvalue: {...symbolPlace, effect: Effect.Mutate}, + value: { + kind: 'LoadGlobal', + binding: {kind: 'Global', name: 'Symbol'}, + loc: instr.value.loc, + }, + loc: instr.loc, + }; + nextInstructions.push(symbolInstruction); + + const symbolForPlace = createTemporaryPlace(fn.env, instr.value.loc); + const symbolForInstruction: Instruction = { + id: makeInstructionId(0), + lvalue: {...symbolForPlace, effect: Effect.Read}, + value: { + kind: 'PropertyLoad', + object: {...symbolInstruction.lvalue}, + property: 'for', + loc: instr.value.loc, + }, + loc: instr.loc, + }; + nextInstructions.push(symbolForInstruction); + + const symbolValuePlace = createTemporaryPlace(fn.env, instr.value.loc); + const symbolValueInstruction: Instruction = { + id: makeInstructionId(0), + lvalue: {...symbolValuePlace, effect: Effect.Mutate}, + value: { + kind: 'Primitive', + value: symbolName, + loc: instr.value.loc, + }, + loc: instr.loc, + }; + nextInstructions.push(symbolValueInstruction); + + const $$typeofPlace = createTemporaryPlace(fn.env, instr.value.loc); + const $$typeofInstruction: Instruction = { + id: makeInstructionId(0), + lvalue: {...$$typeofPlace, effect: Effect.Mutate}, + value: { + kind: 'MethodCall', + receiver: symbolInstruction.lvalue, + property: symbolForInstruction.lvalue, + args: [symbolValueInstruction.lvalue], + loc: instr.value.loc, + }, + loc: instr.loc, + }; + const $$typeofProperty: ObjectProperty = { + kind: 'ObjectProperty', + key: {name: propertyName, kind: 'string'}, + type: 'property', + place: {...$$typeofPlace, effect: Effect.Capture}, + }; + nextInstructions.push($$typeofInstruction); + return $$typeofProperty; +} + +function createTagProperty( + fn: HIRFunction, + instr: Instruction, + nextInstructions: Array, + componentTag: BuiltinTag | Place, +): ObjectProperty { + let tagProperty: ObjectProperty; + switch (componentTag.kind) { + case 'BuiltinTag': { + const tagPropertyPlace = createTemporaryPlace(fn.env, instr.value.loc); + const tagInstruction: Instruction = { + id: makeInstructionId(0), + lvalue: {...tagPropertyPlace, effect: Effect.Mutate}, + value: { + kind: 'Primitive', + value: componentTag.name, + loc: instr.value.loc, + }, + loc: instr.loc, + }; + tagProperty = { + kind: 'ObjectProperty', + key: {name: 'type', kind: 'string'}, + type: 'property', + place: {...tagPropertyPlace, effect: Effect.Capture}, + }; + nextInstructions.push(tagInstruction); + break; + } + case 'Identifier': { + tagProperty = { + kind: 'ObjectProperty', + key: {name: 'type', kind: 'string'}, + type: 'property', + place: {...componentTag, effect: Effect.Capture}, + }; + break; + } + } + + return tagProperty; +} + +function createPropsProperties( + fn: HIRFunction, + instr: Instruction, + nextInstructions: Array, + propAttributes: Array, + children: Array | null, +): { + refProperty: ObjectProperty; + keyProperty: ObjectProperty; + propsProperty: ObjectProperty; +} { + let refProperty: ObjectProperty | undefined; + let keyProperty: ObjectProperty | undefined; + const props: Array = []; + const jsxAttributesWithoutKeyAndRef = propAttributes.filter( + p => p.kind === 'JsxAttribute' && p.name !== 'key' && p.name !== 'ref', + ); + const jsxSpreadAttributes = propAttributes.filter( + p => p.kind === 'JsxSpreadAttribute', + ); + const spreadPropsOnly = + jsxAttributesWithoutKeyAndRef.length === 0 && + jsxSpreadAttributes.length === 1; + + propAttributes.forEach(prop => { + switch (prop.kind) { + case 'JsxAttribute': { + switch (prop.name) { + case 'key': { + keyProperty = { + kind: 'ObjectProperty', + key: {name: 'key', kind: 'string'}, + type: 'property', + place: {...prop.place}, + }; + break; + } + case 'ref': { + /** + * In the current JSX implementation, ref is both + * a property on the element and a property on props. + */ + refProperty = { + kind: 'ObjectProperty', + key: {name: 'ref', kind: 'string'}, + type: 'property', + place: {...prop.place}, + }; + const refPropProperty: ObjectProperty = { + kind: 'ObjectProperty', + key: {name: 'ref', kind: 'string'}, + type: 'property', + place: {...prop.place}, + }; + props.push(refPropProperty); + break; + } + default: { + const attributeProperty: ObjectProperty = { + kind: 'ObjectProperty', + key: {name: prop.name, kind: 'string'}, + type: 'property', + place: {...prop.place}, + }; + props.push(attributeProperty); + } + } + break; + } + case 'JsxSpreadAttribute': { + props.push({ + kind: 'Spread', + place: {...prop.argument}, + }); + break; + } + } + }); + + const propsPropertyPlace = createTemporaryPlace(fn.env, instr.value.loc); + if (children) { + let childrenPropProperty: ObjectProperty; + if (children.length === 1) { + childrenPropProperty = { + kind: 'ObjectProperty', + key: {name: 'children', kind: 'string'}, + type: 'property', + place: {...children[0], effect: Effect.Capture}, + }; + } else { + const childrenPropPropertyPlace = createTemporaryPlace( + fn.env, + instr.value.loc, + ); + + const childrenPropInstruction: Instruction = { + id: makeInstructionId(0), + lvalue: {...childrenPropPropertyPlace, effect: Effect.Mutate}, + value: { + kind: 'ArrayExpression', + elements: [...children], + loc: instr.value.loc, + }, + loc: instr.loc, + }; + nextInstructions.push(childrenPropInstruction); + childrenPropProperty = { + kind: 'ObjectProperty', + key: {name: 'children', kind: 'string'}, + type: 'property', + place: {...childrenPropPropertyPlace, effect: Effect.Capture}, + }; + } + props.push(childrenPropProperty); + } + + if (refProperty == null) { + const refPropertyPlace = createTemporaryPlace(fn.env, instr.value.loc); + const refInstruction: Instruction = { + id: makeInstructionId(0), + lvalue: {...refPropertyPlace, effect: Effect.Mutate}, + value: { + kind: 'Primitive', + value: null, + loc: instr.value.loc, + }, + loc: instr.loc, + }; + refProperty = { + kind: 'ObjectProperty', + key: {name: 'ref', kind: 'string'}, + type: 'property', + place: {...refPropertyPlace, effect: Effect.Capture}, + }; + nextInstructions.push(refInstruction); + } + + if (keyProperty == null) { + const keyPropertyPlace = createTemporaryPlace(fn.env, instr.value.loc); + const keyInstruction: Instruction = { + id: makeInstructionId(0), + lvalue: {...keyPropertyPlace, effect: Effect.Mutate}, + value: { + kind: 'Primitive', + value: null, + loc: instr.value.loc, + }, + loc: instr.loc, + }; + keyProperty = { + kind: 'ObjectProperty', + key: {name: 'key', kind: 'string'}, + type: 'property', + place: {...keyPropertyPlace, effect: Effect.Capture}, + }; + nextInstructions.push(keyInstruction); + } + + let propsProperty: ObjectProperty; + if (spreadPropsOnly) { + const spreadProp = jsxSpreadAttributes[0]; + CompilerError.invariant(spreadProp.kind === 'JsxSpreadAttribute', { + reason: 'Spread prop attribute must be of kind JSXSpreadAttribute', + loc: instr.loc, + }); + propsProperty = { + kind: 'ObjectProperty', + key: {name: 'props', kind: 'string'}, + type: 'property', + place: {...spreadProp.argument, effect: Effect.Mutate}, + }; + } else { + const propsInstruction: Instruction = { + id: makeInstructionId(0), + lvalue: {...propsPropertyPlace, effect: Effect.Mutate}, + value: { + kind: 'ObjectExpression', + properties: props, + loc: instr.value.loc, + }, + loc: instr.loc, + }; + propsProperty = { + kind: 'ObjectProperty', + key: {name: 'props', kind: 'string'}, + type: 'property', + place: {...propsPropertyPlace, effect: Effect.Capture}, + }; + nextInstructions.push(propsInstruction); + } + + return {refProperty, keyProperty, propsProperty}; +} + +function handlePlace( + place: Place, + blockId: BlockId, + inlinedJsxDeclarations: InlinedJsxDeclarationMap, +): Place { + const inlinedJsxDeclaration = inlinedJsxDeclarations.get( + place.identifier.declarationId, + ); + if ( + inlinedJsxDeclaration == null || + inlinedJsxDeclaration.blockIdsToIgnore.has(blockId) + ) { + return place; + } + + return {...place, identifier: inlinedJsxDeclaration.identifier}; +} + +function handlelValue( + lvalue: Place, + blockId: BlockId, + inlinedJsxDeclarations: InlinedJsxDeclarationMap, +): Place { + const inlinedJsxDeclaration = inlinedJsxDeclarations.get( + lvalue.identifier.declarationId, + ); + if ( + inlinedJsxDeclaration == null || + inlinedJsxDeclaration.blockIdsToIgnore.has(blockId) + ) { + return lvalue; + } + + return {...lvalue, identifier: inlinedJsxDeclaration.identifier}; +} + +function handleIdentifier( + identifier: Identifier, + inlinedJsxDeclarations: InlinedJsxDeclarationMap, +): Identifier { + const inlinedJsxDeclaration = inlinedJsxDeclarations.get( + identifier.declarationId, + ); + return inlinedJsxDeclaration == null + ? identifier + : inlinedJsxDeclaration.identifier; +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts index 8455a094f05e7..e27b8f952148a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts @@ -23,6 +23,7 @@ import { isUseContextHookType, makeBlockId, makeInstructionId, + makeType, markInstructionIds, promoteTemporary, reversePostorderBlocks, @@ -244,7 +245,8 @@ function emitSelectorFn(env: Environment, keys: Array): Instruction { fnType: 'Other', env, params: [obj], - returnType: null, + returnTypeAnnotation: null, + returnType: makeType(), context: [], effects: null, body: { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineJsx.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineJsx.ts new file mode 100644 index 0000000000000..a6b94075cce26 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineJsx.ts @@ -0,0 +1,520 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import invariant from 'invariant'; +import {Environment} from '../HIR'; +import { + BasicBlock, + GeneratedSource, + HIRFunction, + IdentifierId, + Instruction, + InstructionId, + InstructionKind, + JsxAttribute, + JsxExpression, + LoadGlobal, + makeBlockId, + makeIdentifierName, + makeInstructionId, + makeType, + ObjectProperty, + Place, + promoteTemporary, + promoteTemporaryJsxTag, +} from '../HIR/HIR'; +import {createTemporaryPlace} from '../HIR/HIRBuilder'; +import {printIdentifier} from '../HIR/PrintHIR'; +import {deadCodeElimination} from './DeadCodeElimination'; +import {assertExhaustive} from '../Utils/utils'; + +export function outlineJSX(fn: HIRFunction): void { + const outlinedFns: Array = []; + outlineJsxImpl(fn, outlinedFns); + + for (const outlinedFn of outlinedFns) { + fn.env.outlineFunction(outlinedFn, 'Component'); + } +} + +type JsxInstruction = Instruction & {value: JsxExpression}; +type LoadGlobalInstruction = Instruction & {value: LoadGlobal}; +type LoadGlobalMap = Map; + +type State = { + jsx: Array; + children: Set; +}; + +function outlineJsxImpl( + fn: HIRFunction, + outlinedFns: Array, +): void { + const globals: LoadGlobalMap = new Map(); + + function processAndOutlineJSX( + state: State, + rewriteInstr: Map>, + ): void { + if (state.jsx.length <= 1) { + return; + } + const result = process( + fn, + [...state.jsx].sort((a, b) => a.id - b.id), + globals, + ); + if (result) { + outlinedFns.push(result.fn); + rewriteInstr.set(state.jsx.at(0)!.id, result.instrs); + } + } + + for (const [, block] of fn.body.blocks) { + const rewriteInstr = new Map(); + let state: State = { + jsx: [], + children: new Set(), + }; + + for (let i = block.instructions.length - 1; i >= 0; i--) { + const instr = block.instructions[i]; + const {value, lvalue} = instr; + switch (value.kind) { + case 'LoadGlobal': { + globals.set(lvalue.identifier.id, instr as LoadGlobalInstruction); + break; + } + case 'FunctionExpression': { + outlineJsxImpl(value.loweredFunc.func, outlinedFns); + break; + } + + case 'JsxExpression': { + if (!state.children.has(lvalue.identifier.id)) { + processAndOutlineJSX(state, rewriteInstr); + + state = { + jsx: [], + children: new Set(), + }; + } + state.jsx.push(instr as JsxInstruction); + if (value.children) { + for (const child of value.children) { + state.children.add(child.identifier.id); + } + } + break; + } + case 'ArrayExpression': + case 'Await': + case 'BinaryExpression': + case 'CallExpression': + case 'ComputedDelete': + case 'ComputedLoad': + case 'ComputedStore': + case 'Debugger': + case 'DeclareContext': + case 'DeclareLocal': + case 'Destructure': + case 'FinishMemoize': + case 'GetIterator': + case 'IteratorNext': + case 'JSXText': + case 'JsxFragment': + case 'LoadContext': + case 'LoadLocal': + case 'MetaProperty': + case 'MethodCall': + case 'NewExpression': + case 'NextPropertyOf': + case 'ObjectExpression': + case 'ObjectMethod': + case 'PostfixUpdate': + case 'PrefixUpdate': + case 'Primitive': + case 'PropertyDelete': + case 'PropertyLoad': + case 'PropertyStore': + case 'RegExpLiteral': + case 'StartMemoize': + case 'StoreContext': + case 'StoreGlobal': + case 'StoreLocal': + case 'TaggedTemplateExpression': + case 'TemplateLiteral': + case 'TypeCastExpression': + case 'UnsupportedNode': + case 'UnaryExpression': { + break; + } + default: { + assertExhaustive(value, `Unexpected instruction: ${value}`); + } + } + } + processAndOutlineJSX(state, rewriteInstr); + + if (rewriteInstr.size > 0) { + const newInstrs = []; + for (let i = 0; i < block.instructions.length; i++) { + // InstructionId's are one-indexed, so add one to account for them. + const id = i + 1; + if (rewriteInstr.has(id)) { + const instrs = rewriteInstr.get(id); + newInstrs.push(...instrs); + } else { + newInstrs.push(block.instructions[i]); + } + } + block.instructions = newInstrs; + } + deadCodeElimination(fn); + } +} + +type OutlinedResult = { + instrs: Array; + fn: HIRFunction; +}; + +function process( + fn: HIRFunction, + jsx: Array, + globals: LoadGlobalMap, +): OutlinedResult | null { + /** + * In the future, add a check for backedge to outline jsx inside loops in a + * top level component. For now, only outline jsx in callbacks. + */ + if (fn.fnType === 'Component') { + return null; + } + + const props = collectProps(jsx); + if (!props) return null; + + const outlinedTag = fn.env.generateGloballyUniqueIdentifierName(null).value; + const newInstrs = emitOutlinedJsx(fn.env, jsx, props, outlinedTag); + if (!newInstrs) return null; + + const outlinedFn = emitOutlinedFn(fn.env, jsx, props, globals); + if (!outlinedFn) return null; + outlinedFn.id = outlinedTag; + + return {instrs: newInstrs, fn: outlinedFn}; +} + +type OutlinedJsxAttribute = { + originalName: string; + newName: string; + place: Place; +}; + +function collectProps( + instructions: Array, +): Array | null { + let id = 1; + + function generateName(oldName: string): string { + let newName = oldName; + while (seen.has(newName)) { + newName = `${oldName}${id++}`; + } + seen.add(newName); + return newName; + } + + const attributes: Array = []; + const jsxIds = new Set(instructions.map(i => i.lvalue.identifier.id)); + const seen: Set = new Set(); + + for (const instr of instructions) { + const {value} = instr; + + for (const at of value.props) { + if (at.kind === 'JsxSpreadAttribute') { + return null; + } + + if (at.kind === 'JsxAttribute') { + const newName = generateName(at.name); + attributes.push({ + originalName: at.name, + newName, + place: at.place, + }); + } + } + + if (value.children) { + for (const child of value.children) { + if (jsxIds.has(child.identifier.id)) { + continue; + } + + promoteTemporary(child.identifier); + const newName = generateName('t'); + attributes.push({ + originalName: child.identifier.name!.value, + newName: newName, + place: child, + }); + } + } + } + return attributes; +} + +function emitOutlinedJsx( + env: Environment, + instructions: Array, + outlinedProps: Array, + outlinedTag: string, +): Array { + const props: Array = outlinedProps.map(p => ({ + kind: 'JsxAttribute', + name: p.newName, + place: p.place, + })); + + const loadJsx: Instruction = { + id: makeInstructionId(0), + loc: GeneratedSource, + lvalue: createTemporaryPlace(env, GeneratedSource), + value: { + kind: 'LoadGlobal', + binding: { + kind: 'ModuleLocal', + name: outlinedTag, + }, + loc: GeneratedSource, + }, + }; + promoteTemporaryJsxTag(loadJsx.lvalue.identifier); + const jsxExpr: Instruction = { + id: makeInstructionId(0), + loc: GeneratedSource, + lvalue: instructions.at(-1)!.lvalue, + value: { + kind: 'JsxExpression', + tag: {...loadJsx.lvalue}, + props, + children: null, + loc: GeneratedSource, + openingLoc: GeneratedSource, + closingLoc: GeneratedSource, + }, + }; + + return [loadJsx, jsxExpr]; +} + +function emitOutlinedFn( + env: Environment, + jsx: Array, + oldProps: Array, + globals: LoadGlobalMap, +): HIRFunction | null { + const instructions: Array = []; + const oldToNewProps = createOldToNewPropsMapping(env, oldProps); + + const propsObj: Place = createTemporaryPlace(env, GeneratedSource); + promoteTemporary(propsObj.identifier); + + const destructurePropsInstr = emitDestructureProps( + env, + propsObj, + oldToNewProps, + ); + instructions.push(destructurePropsInstr); + + const updatedJsxInstructions = emitUpdatedJsx(jsx, oldToNewProps); + const loadGlobalInstrs = emitLoadGlobals(jsx, globals); + if (!loadGlobalInstrs) { + return null; + } + instructions.push(...loadGlobalInstrs); + instructions.push(...updatedJsxInstructions); + + const block: BasicBlock = { + kind: 'block', + id: makeBlockId(0), + instructions, + terminal: { + id: makeInstructionId(0), + kind: 'return', + loc: GeneratedSource, + value: instructions.at(-1)!.lvalue, + }, + preds: new Set(), + phis: new Set(), + }; + + const fn: HIRFunction = { + loc: GeneratedSource, + id: null, + fnType: 'Other', + env, + params: [propsObj], + returnTypeAnnotation: null, + returnType: makeType(), + context: [], + effects: null, + body: { + entry: block.id, + blocks: new Map([[block.id, block]]), + }, + generator: false, + async: false, + directives: [], + }; + return fn; +} + +function emitLoadGlobals( + jsx: Array, + globals: LoadGlobalMap, +): Array | null { + const instructions: Array = []; + for (const {value} of jsx) { + // Add load globals instructions for jsx tags + if (value.tag.kind === 'Identifier') { + const loadGlobalInstr = globals.get(value.tag.identifier.id); + if (!loadGlobalInstr) { + return null; + } + instructions.push(loadGlobalInstr); + } + } + + return instructions; +} + +function emitUpdatedJsx( + jsx: Array, + oldToNewProps: Map, +): Array { + const newInstrs: Array = []; + const jsxIds = new Set(jsx.map(i => i.lvalue.identifier.id)); + + for (const instr of jsx) { + const {value} = instr; + const newProps: Array = []; + // Update old props references to use the newly destructured props param + for (const prop of value.props) { + invariant( + prop.kind === 'JsxAttribute', + `Expected only attributes but found ${prop.kind}`, + ); + if (prop.name === 'key') { + continue; + } + const newProp = oldToNewProps.get(prop.place.identifier.id); + invariant( + newProp !== undefined, + `Expected a new property for ${printIdentifier(prop.place.identifier)}`, + ); + newProps.push({ + kind: 'JsxAttribute', + name: newProp.originalName, + place: newProp.place, + }); + } + + let newChildren: Array | null = null; + if (value.children) { + newChildren = []; + for (const child of value.children) { + if (jsxIds.has(child.identifier.id)) { + newChildren.push({...child}); + continue; + } + + const newChild = oldToNewProps.get(child.identifier.id); + invariant( + newChild !== undefined, + `Expected a new prop for ${printIdentifier(child.identifier)}`, + ); + newChildren.push({...newChild.place}); + } + } + + newInstrs.push({ + ...instr, + value: { + ...value, + props: newProps, + children: newChildren, + }, + }); + } + + return newInstrs; +} + +function createOldToNewPropsMapping( + env: Environment, + oldProps: Array, +): Map { + const oldToNewProps = new Map(); + + for (const oldProp of oldProps) { + // Do not read key prop in the outlined component + if (oldProp.originalName === 'key') { + continue; + } + + const newProp: OutlinedJsxAttribute = { + ...oldProp, + place: createTemporaryPlace(env, GeneratedSource), + }; + newProp.place.identifier.name = makeIdentifierName(oldProp.newName); + oldToNewProps.set(oldProp.place.identifier.id, newProp); + } + + return oldToNewProps; +} + +function emitDestructureProps( + env: Environment, + propsObj: Place, + oldToNewProps: Map, +): Instruction { + const properties: Array = []; + for (const [_, prop] of oldToNewProps) { + properties.push({ + kind: 'ObjectProperty', + key: { + kind: 'string', + name: prop.newName, + }, + type: 'property', + place: prop.place, + }); + } + + const destructurePropsInstr: Instruction = { + id: makeInstructionId(0), + lvalue: createTemporaryPlace(env, GeneratedSource), + loc: GeneratedSource, + value: { + kind: 'Destructure', + lvalue: { + pattern: { + kind: 'ObjectPattern', + properties, + }, + kind: InstructionKind.Let, + }, + loc: GeneratedSource, + value: propsObj, + }, + }; + return destructurePropsInstr; +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/PruneMaybeThrows.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/PruneMaybeThrows.ts index 3843cdf23fc2d..9175fbdd1af1b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/PruneMaybeThrows.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/PruneMaybeThrows.ts @@ -23,7 +23,7 @@ import { removeUnnecessaryTryCatch, removeUnreachableForUpdates, } from '../HIR/HIRBuilder'; -import {printIdentifier} from '../HIR/PrintHIR'; +import {printPlace} from '../HIR/PrintHIR'; /* * This pass prunes `maybe-throw` terminals for blocks that can provably *never* throw. @@ -55,7 +55,7 @@ export function pruneMaybeThrows(fn: HIRFunction): void { loc: GeneratedSource, description: `Could not find mapping for predecessor bb${predecessor} in block bb${ block.id - } for phi ${printIdentifier(phi.id)}`, + } for phi ${printPlace(phi.place)}`, suggestions: null, }); phi.operands.delete(predecessor); diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/index.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/index.ts index 722b05a809960..bb060b8dc285c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/index.ts @@ -8,3 +8,4 @@ export {constantPropagation} from './ConstantPropagation'; export {deadCodeElimination} from './DeadCodeElimination'; export {pruneMaybeThrows} from './PruneMaybeThrows'; +export {inlineJsxTransform} from './InlineJsxTransform'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/AlignReactiveScopesToBlockScopes.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/AlignReactiveScopesToBlockScopes.ts deleted file mode 100644 index 132788f0d418b..0000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/AlignReactiveScopesToBlockScopes.ts +++ /dev/null @@ -1,187 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import { - InstructionId, - Place, - ReactiveBlock, - ReactiveFunction, - ReactiveInstruction, - ReactiveScope, - ScopeId, - makeInstructionId, -} from '../HIR/HIR'; -import {getPlaceScope} from './BuildReactiveBlocks'; -import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors'; - -/* - * Note: this is the 2nd of 4 passes that determine how to break a function into discrete - * reactive scopes (independently memoizeable units of code): - * 1. InferReactiveScopeVariables (on HIR) determines operands that mutate together and assigns - * them a unique reactive scope. - * 2. AlignReactiveScopesToBlockScopes (this pass, on ReactiveFunction) aligns reactive scopes - * to block scopes. - * 3. MergeOverlappingReactiveScopes (on ReactiveFunction) ensures that reactive scopes do not - * overlap, merging any such scopes. - * 4. BuildReactiveBlocks (on ReactiveFunction) groups the statements for each scope into - * a ReactiveScopeBlock. - * - * Prior inference passes assign a reactive scope to each operand, but the ranges of these - * scopes are based on specific instructions at arbitrary points in the control-flow graph. - * However, to codegen blocks around the instructions in each scope, the scopes must be - * aligned to block-scope boundaries - we can't memoize half of a loop! - * - * This pass updates reactive scope boundaries to align to control flow boundaries, for - * example: - * - * ```javascript - * function foo(cond, a) { - * ⌵ original scope - * ⌵ expanded scope - * const x = []; ⌝ ⌝ - * if (cond) { ⎮ ⎮ - * ... ⎮ ⎮ - * x.push(a); ⌟ ⎮ - * ... ⎮ - * } ⌟ - * } - * ``` - * - * Here the original scope for `x` ended partway through the if consequent, but we can't - * memoize part of that block. This pass would align the scope to the end of the consequent. - * - * The more general rule is that a reactive scope may only end at the same block scope as it - * began: this pass therefore finds, for each scope, the block where that scope started and - * finds the first instruction after the scope's mutable range in that same block scope (which - * will be the updated end for that scope). - */ - -export function alignReactiveScopesToBlockScopes(fn: ReactiveFunction): void { - const context = new Context(); - visitReactiveFunction(fn, new Visitor(), context); -} - -class Visitor extends ReactiveFunctionVisitor { - override visitID(id: InstructionId, state: Context): void { - state.visitId(id); - } - override visitPlace(id: InstructionId, place: Place, state: Context): void { - const scope = getPlaceScope(id, place); - if (scope !== null) { - state.visitScope(scope); - } - } - override visitLValue(id: InstructionId, lvalue: Place, state: Context): void { - const scope = getPlaceScope(id, lvalue); - if (scope !== null) { - state.visitScope(scope); - } - } - - override visitInstruction(instr: ReactiveInstruction, state: Context): void { - switch (instr.value.kind) { - case 'OptionalExpression': - case 'SequenceExpression': - case 'ConditionalExpression': - case 'LogicalExpression': { - const prevScopeCount = state.currentScopes().length; - this.traverseInstruction(instr, state); - - /** - * These compound value types can have nested sequences of instructions - * with scopes that start "partway" through a block-level instruction. - * This would cause the start of the scope to not align with any block-level - * instruction and get skipped by the later BuildReactiveBlocks pass. - * - * Here we detect scopes created within compound instructions and align the - * start of these scopes to the outer instruction id to ensure the scopes - * aren't skipped. - */ - const scopes = state.currentScopes(); - for (let i = prevScopeCount; i < scopes.length; i++) { - const scope = scopes[i]; - scope.scope.range.start = makeInstructionId( - Math.min(instr.id, scope.scope.range.start), - ); - } - break; - } - default: { - this.traverseInstruction(instr, state); - } - } - } - - override visitBlock(block: ReactiveBlock, state: Context): void { - state.enter(() => { - this.traverseBlock(block, state); - }); - } -} - -type PendingReactiveScope = {active: boolean; scope: ReactiveScope}; - -class Context { - /* - * For each block scope (outer array) stores a list of ReactiveScopes that start - * in that block scope. - */ - #blockScopes: Array> = []; - - /* - * ReactiveScopes whose declaring block scope has ended but may still need to - * be "closed" (ie have their range.end be updated). A given scope can be in - * blockScopes OR this array but not both. - */ - #unclosedScopes: Array = []; - - /* - * Set of all scope ids that have been seen so far, regardless of which of - * the above data structures they're in, to avoid tracking the same scope twice. - */ - #seenScopes: Set = new Set(); - - currentScopes(): Array { - return this.#blockScopes.at(-1) ?? []; - } - - enter(fn: () => void): void { - this.#blockScopes.push([]); - fn(); - const lastScope = this.#blockScopes.pop()!; - for (const scope of lastScope) { - if (scope.active) { - this.#unclosedScopes.push(scope); - } - } - } - - visitId(id: InstructionId): void { - const currentScopes = this.#blockScopes.at(-1)!; - const scopes = [...currentScopes, ...this.#unclosedScopes]; - for (const pending of scopes) { - if (!pending.active) { - continue; - } - if (id >= pending.scope.range.end) { - pending.active = false; - pending.scope.range.end = id; - } - } - } - - visitScope(scope: ReactiveScope): void { - if (!this.#seenScopes.has(scope.id)) { - const currentScopes = this.#blockScopes.at(-1)!; - this.#seenScopes.add(scope.id); - currentScopes.push({ - active: true, - scope, - }); - } - } -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/AlignReactiveScopesToBlockScopesHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/AlignReactiveScopesToBlockScopesHIR.ts index 6517918d02e16..2b4e890a40da8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/AlignReactiveScopesToBlockScopesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/AlignReactiveScopesToBlockScopesHIR.ts @@ -13,6 +13,7 @@ import { MutableRange, Place, ReactiveScope, + getPlaceScope, makeInstructionId, } from '../HIR/HIR'; import { @@ -23,7 +24,6 @@ import { terminalFallthrough, } from '../HIR/visitors'; import {retainWhere_Set} from '../Utils/utils'; -import {getPlaceScope} from './BuildReactiveBlocks'; type InstructionRange = MutableRange; /* @@ -140,7 +140,7 @@ export function alignReactiveScopesToBlockScopesHIR(fn: HIRFunction): void { } const fallthrough = terminalFallthrough(terminal); - if (fallthrough !== null) { + if (fallthrough !== null && terminal.kind !== 'branch') { /* * Any currently active scopes that overlaps the block-fallthrough range * need their range extended to at least the first instruction of the diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/AssertScopeInstructionsWithinScope.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/AssertScopeInstructionsWithinScope.ts index 2bce100050175..718e28f6101d1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/AssertScopeInstructionsWithinScope.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/AssertScopeInstructionsWithinScope.ts @@ -14,7 +14,7 @@ import { ReactiveScopeBlock, ScopeId, } from '../HIR'; -import {getPlaceScope} from './BuildReactiveBlocks'; +import {getPlaceScope} from '../HIR/HIR'; import {ReactiveFunctionVisitor} from './visitors'; /* diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/BuildReactiveBlocks.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/BuildReactiveBlocks.ts deleted file mode 100644 index 7737423e5e1c1..0000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/BuildReactiveBlocks.ts +++ /dev/null @@ -1,244 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import {CompilerError} from '../CompilerError'; -import { - BlockId, - InstructionId, - Place, - ReactiveBlock, - ReactiveFunction, - ReactiveInstruction, - ReactiveScope, - ReactiveScopeBlock, - ReactiveStatement, - ScopeId, -} from '../HIR'; -import {eachInstructionLValue} from '../HIR/visitors'; -import {assertExhaustive} from '../Utils/utils'; -import {eachReactiveValueOperand, mapTerminalBlocks} from './visitors'; - -/* - * Note: this is the 4th of 4 passes that determine how to break a function into discrete - * reactive scopes (independently memoizeable units of code): - * 1. InferReactiveScopeVariables (on HIR) determines operands that mutate together and assigns - * them a unique reactive scope. - * 2. AlignReactiveScopesToBlockScopes (on ReactiveFunction) aligns reactive scopes - * to block scopes. - * 3. MergeOverlappingReactiveScopes (this pass, on ReactiveFunction) ensures that reactive - * scopes do not overlap, merging any such scopes. - * 4. BuildReactiveBlocks (on ReactiveFunction) groups the statements for each scope into - * a ReactiveScopeBlock. - * - * Given a function where the reactive scopes have been correctly aligned and merged, - * this pass groups the instructions for each reactive scope into ReactiveBlocks. - */ -export function buildReactiveBlocks(fn: ReactiveFunction): void { - const context = new Context(); - fn.body = context.enter(() => { - visitBlock(context, fn.body); - }); -} - -class Context { - #builders: Array = []; - #scopes: Set = new Set(); - - visitId(id: InstructionId): void { - const builder = this.#builders.at(-1)!; - builder.visitId(id); - } - - visitScope(scope: ReactiveScope): void { - if (this.#scopes.has(scope.id)) { - return; - } - this.#scopes.add(scope.id); - this.#builders.at(-1)!.startScope(scope); - } - - append( - stmt: ReactiveStatement, - label: {id: BlockId; implicit: boolean} | null, - ): void { - this.#builders.at(-1)!.append(stmt, label); - } - - enter(fn: () => void): ReactiveBlock { - const builder = new Builder(); - this.#builders.push(builder); - fn(); - const popped = this.#builders.pop(); - CompilerError.invariant(popped === builder, { - reason: 'Expected push/pop to be called 1:1', - description: null, - loc: null, - suggestions: null, - }); - return builder.complete(); - } -} - -class Builder { - #instructions: ReactiveBlock; - #stack: Array< - | {kind: 'scope'; block: ReactiveScopeBlock} - | {kind: 'block'; block: ReactiveBlock} - >; - - constructor() { - const block: ReactiveBlock = []; - this.#instructions = block; - this.#stack = [{kind: 'block', block}]; - } - - append( - item: ReactiveStatement, - label: {id: BlockId; implicit: boolean} | null, - ): void { - if (label !== null) { - CompilerError.invariant(item.kind === 'terminal', { - reason: 'Only terminals may have a label', - description: null, - loc: null, - suggestions: null, - }); - item.label = label; - } - this.#instructions.push(item); - } - - startScope(scope: ReactiveScope): void { - const block: ReactiveScopeBlock = { - kind: 'scope', - scope, - instructions: [], - }; - this.append(block, null); - this.#instructions = block.instructions; - this.#stack.push({kind: 'scope', block}); - } - - visitId(id: InstructionId): void { - for (let i = 0; i < this.#stack.length; i++) { - const entry = this.#stack[i]!; - if (entry.kind === 'scope' && id >= entry.block.scope.range.end) { - this.#stack.length = i; - break; - } - } - const last = this.#stack[this.#stack.length - 1]!; - if (last.kind === 'block') { - this.#instructions = last.block; - } else { - this.#instructions = last.block.instructions; - } - } - - complete(): ReactiveBlock { - /* - * TODO: @josephsavona debug violations of this invariant - * invariant( - * this.#stack.length === 1, - * "Expected all scopes to be closed when exiting a block" - * ); - */ - const first = this.#stack[0]!; - CompilerError.invariant(first.kind === 'block', { - reason: 'Expected first stack item to be a basic block', - description: null, - loc: null, - suggestions: null, - }); - return first.block; - } -} - -function visitBlock(context: Context, block: ReactiveBlock): void { - for (const stmt of block) { - switch (stmt.kind) { - case 'instruction': { - context.visitId(stmt.instruction.id); - const scope = getInstructionScope(stmt.instruction); - if (scope !== null) { - context.visitScope(scope); - } - context.append(stmt, null); - break; - } - case 'terminal': { - const id = stmt.terminal.id; - if (id !== null) { - context.visitId(id); - } - mapTerminalBlocks(stmt.terminal, block => { - return context.enter(() => { - visitBlock(context, block); - }); - }); - context.append(stmt, stmt.label); - break; - } - case 'pruned-scope': - case 'scope': { - CompilerError.invariant(false, { - reason: 'Expected the function to not have scopes already assigned', - description: null, - loc: null, - suggestions: null, - }); - } - default: { - assertExhaustive( - stmt, - `Unexpected statement kind \`${(stmt as any).kind}\``, - ); - } - } - } -} - -export function getInstructionScope( - instr: ReactiveInstruction, -): ReactiveScope | null { - CompilerError.invariant(instr.lvalue !== null, { - reason: - 'Expected lvalues to not be null when assigning scopes. ' + - 'Pruning lvalues too early can result in missing scope information.', - description: null, - loc: instr.loc, - suggestions: null, - }); - for (const operand of eachInstructionLValue(instr)) { - const operandScope = getPlaceScope(instr.id, operand); - if (operandScope !== null) { - return operandScope; - } - } - for (const operand of eachReactiveValueOperand(instr.value)) { - const operandScope = getPlaceScope(instr.id, operand); - if (operandScope !== null) { - return operandScope; - } - } - return null; -} - -export function getPlaceScope( - id: InstructionId, - place: Place, -): ReactiveScope | null { - const scope = place.identifier.scope; - if (scope !== null && isScopeActive(scope, id)) { - return scope; - } - return null; -} - -function isScopeActive(scope: ReactiveScope, id: InstructionId): boolean { - return id >= scope.range.start && id < scope.range.end; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts index 624a4b604d66f..167db6dedeccc 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts @@ -34,6 +34,7 @@ import { ReactiveInstruction, ReactiveScope, ReactiveScopeBlock, + ReactiveScopeDeclaration, ReactiveScopeDependency, ReactiveTerminal, ReactiveValue, @@ -572,7 +573,8 @@ function codegenReactiveScope( const changeExpressions: Array = []; const changeExpressionComments: Array = []; const outputComments: Array = []; - for (const dep of scope.dependencies) { + + for (const dep of [...scope.dependencies].sort(compareScopeDependency)) { const index = cx.nextCacheIndex; changeExpressionComments.push(printDependencyComment(dep)); const comparison = t.binaryExpression( @@ -615,7 +617,10 @@ function codegenReactiveScope( ); } let firstOutputIndex: number | null = null; - for (const [, {identifier}] of scope.declarations) { + + for (const [, {identifier}] of [...scope.declarations].sort(([, a], [, b]) => + compareScopeDeclaration(a, b), + )) { const index = cx.nextCacheIndex; if (firstOutputIndex === null) { firstOutputIndex = index; @@ -981,22 +986,12 @@ function codegenTerminal( suggestions: null, }); case InstructionKind.Catch: - CompilerError.invariant(false, { - reason: 'Unexpected catch variable as for..in collection', - description: null, - loc: iterableItem.loc, - suggestions: null, - }); case InstructionKind.HoistedConst: - CompilerError.invariant(false, { - reason: 'Unexpected HoistedConst variable in for..in collection', - description: null, - loc: iterableItem.loc, - suggestions: null, - }); case InstructionKind.HoistedLet: + case InstructionKind.HoistedFunction: + case InstructionKind.Function: CompilerError.invariant(false, { - reason: 'Unexpected HoistedLet variable in for..in collection', + reason: `Unexpected ${iterableItem.value.lvalue.kind} variable in for..in collection`, description: null, loc: iterableItem.loc, suggestions: null, @@ -1075,30 +1070,13 @@ function codegenTerminal( varDeclKind = 'let' as const; break; case InstructionKind.Reassign: - CompilerError.invariant(false, { - reason: - 'Destructure should never be Reassign as it would be an Object/ArrayPattern', - description: null, - loc: iterableItem.loc, - suggestions: null, - }); case InstructionKind.Catch: - CompilerError.invariant(false, { - reason: 'Unexpected catch variable as for..of collection', - description: null, - loc: iterableItem.loc, - suggestions: null, - }); case InstructionKind.HoistedConst: - CompilerError.invariant(false, { - reason: 'Unexpected HoistedConst variable in for..of collection', - description: null, - loc: iterableItem.loc, - suggestions: null, - }); case InstructionKind.HoistedLet: + case InstructionKind.HoistedFunction: + case InstructionKind.Function: CompilerError.invariant(false, { - reason: 'Unexpected HoistedLet variable in for..of collection', + reason: `Unexpected ${iterableItem.value.lvalue.kind} variable in for..of collection`, description: null, loc: iterableItem.loc, suggestions: null, @@ -1261,6 +1239,35 @@ function codegenInstructionNullable( t.variableDeclarator(codegenLValue(cx, lvalue), value), ]); } + case InstructionKind.Function: { + CompilerError.invariant(instr.lvalue === null, { + reason: `Function declaration cannot be referenced as an expression`, + description: null, + loc: instr.value.loc, + suggestions: null, + }); + const genLvalue = codegenLValue(cx, lvalue); + CompilerError.invariant(genLvalue.type === 'Identifier', { + reason: 'Expected an identifier as a function declaration lvalue', + description: null, + loc: instr.value.loc, + suggestions: null, + }); + CompilerError.invariant(value?.type === 'FunctionExpression', { + reason: 'Expected a function as a function declaration value', + description: null, + loc: instr.value.loc, + suggestions: null, + }); + return createFunctionDeclaration( + instr.loc, + genLvalue, + value.params, + value.body, + value.generator, + value.async, + ); + } case InstructionKind.Let: { CompilerError.invariant(instr.lvalue === null, { reason: `Const declaration cannot be referenced as an expression`, @@ -1303,19 +1310,11 @@ function codegenInstructionNullable( case InstructionKind.Catch: { return t.emptyStatement(); } - case InstructionKind.HoistedLet: { - CompilerError.invariant(false, { - reason: - 'Expected HoistedLet to have been pruned in PruneHoistedContexts', - description: null, - loc: instr.loc, - suggestions: null, - }); - } - case InstructionKind.HoistedConst: { + case InstructionKind.HoistedLet: + case InstructionKind.HoistedConst: + case InstructionKind.HoistedFunction: { CompilerError.invariant(false, { - reason: - 'Expected HoistedConsts to have been pruned in PruneHoistedContexts', + reason: `Expected ${kind} to have been pruned in PruneHoistedContexts`, description: null, loc: instr.loc, suggestions: null, @@ -1411,7 +1410,7 @@ function printDependencyComment(dependency: ReactiveScopeDependency): string { let name = identifier.name; if (dependency.path !== null) { for (const path of dependency.path) { - name += `.${path}`; + name += `.${path.property}`; } } return name; @@ -1446,9 +1445,19 @@ function codegenDependency( dependency: ReactiveScopeDependency, ): t.Expression { let object: t.Expression = convertIdentifier(dependency.identifier); - if (dependency.path !== null) { + if (dependency.path.length !== 0) { + const hasOptional = dependency.path.some(path => path.optional); for (const path of dependency.path) { - object = t.memberExpression(object, t.identifier(path)); + if (hasOptional) { + object = t.optionalMemberExpression( + object, + t.identifier(path.property), + false, + path.optional, + ); + } else { + object = t.memberExpression(object, t.identifier(path.property)); + } } } return object; @@ -1476,6 +1485,7 @@ const createBinaryExpression = withLoc(t.binaryExpression); const createExpressionStatement = withLoc(t.expressionStatement); const _createLabelledStatement = withLoc(t.labeledStatement); const createVariableDeclaration = withLoc(t.variableDeclaration); +const createFunctionDeclaration = withLoc(t.functionDeclaration); const _createWhileStatement = withLoc(t.whileStatement); const createTaggedTemplateExpression = withLoc(t.taggedTemplateExpression); const createLogicalExpression = withLoc(t.logicalExpression); @@ -2561,3 +2571,45 @@ function convertIdentifier(identifier: Identifier): t.Identifier { ); return t.identifier(identifier.name.value); } + +function compareScopeDependency( + a: ReactiveScopeDependency, + b: ReactiveScopeDependency, +): number { + CompilerError.invariant( + a.identifier.name?.kind === 'named' && b.identifier.name?.kind === 'named', + { + reason: '[Codegen] Expected named identifier for dependency', + loc: a.identifier.loc, + }, + ); + const aName = [ + a.identifier.name.value, + ...a.path.map(entry => `${entry.optional ? '?' : ''}${entry.property}`), + ].join('.'); + const bName = [ + b.identifier.name.value, + ...b.path.map(entry => `${entry.optional ? '?' : ''}${entry.property}`), + ].join('.'); + if (aName < bName) return -1; + else if (aName > bName) return 1; + else return 0; +} + +function compareScopeDeclaration( + a: ReactiveScopeDeclaration, + b: ReactiveScopeDeclaration, +): number { + CompilerError.invariant( + a.identifier.name?.kind === 'named' && b.identifier.name?.kind === 'named', + { + reason: '[Codegen] Expected named identifier for declaration', + loc: a.identifier.loc, + }, + ); + const aName = a.identifier.name.value; + const bName = b.identifier.name.value; + if (aName < bName) return -1; + else if (aName > bName) return 1; + else return 0; +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CollectReactiveIdentifiers.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CollectReactiveIdentifiers.ts index 610e93965fc1a..385123400581f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CollectReactiveIdentifiers.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CollectReactiveIdentifiers.ts @@ -12,6 +12,8 @@ import { PrunedReactiveScopeBlock, ReactiveFunction, isPrimitiveType, + isUseRefType, + Identifier, } from '../HIR/HIR'; import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors'; @@ -50,13 +52,21 @@ class Visitor extends ReactiveFunctionVisitor> { this.traversePrunedScope(scopeBlock, state); for (const [id, decl] of scopeBlock.scope.declarations) { - if (!isPrimitiveType(decl.identifier)) { + if ( + !isPrimitiveType(decl.identifier) && + !isStableRefType(decl.identifier, state) + ) { state.add(id); } } } } - +function isStableRefType( + identifier: Identifier, + reactiveIdentifiers: Set, +): boolean { + return isUseRefType(identifier) && !reactiveIdentifiers.has(identifier.id); +} /* * Computes a set of identifiers which are reactive, using the analysis previously performed * in `InferReactivePlaces`. diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/DeriveMinimalDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/DeriveMinimalDependencies.ts index 8c2e31fa9666e..c7e16cce7ac86 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/DeriveMinimalDependencies.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/DeriveMinimalDependencies.ts @@ -6,7 +6,7 @@ */ import {CompilerError} from '../CompilerError'; -import {Identifier, ReactiveScopeDependency} from '../HIR'; +import {DependencyPath, Identifier, ReactiveScopeDependency} from '../HIR'; import {printIdentifier} from '../HIR/PrintHIR'; import {assertExhaustive} from '../Utils/utils'; @@ -14,20 +14,8 @@ import {assertExhaustive} from '../Utils/utils'; * We need to understand optional member expressions only when determining * dependencies of a ReactiveScope (i.e. in {@link PropagateScopeDependencies}), * hence why this type lives here (not in HIR.ts) - * - * {@link ReactiveScopePropertyDependency.optionalPath} is populated only if the Property - * represents an optional member expression, and it represents the property path - * loaded conditionally. - * e.g. the member expr a.b.c?.d.e?.f is represented as - * { - * identifier: 'a'; - * path: ['b', 'c'], - * optionalPath: ['d', 'e', 'f']. - * } */ -export type ReactiveScopePropertyDependency = ReactiveScopeDependency & { - optionalPath: Array; -}; +export type ReactiveScopePropertyDependency = ReactiveScopeDependency; /* * Finalizes a set of ReactiveScopeDependencies to produce a set of minimal unconditional @@ -69,68 +57,43 @@ export class ReactiveScopeDependencyTree { } add(dep: ReactiveScopePropertyDependency, inConditional: boolean): void { - const {path, optionalPath} = dep; + const {path} = dep; let currNode = this.#getOrCreateRoot(dep.identifier); - const accessType = inConditional - ? PropertyAccessType.ConditionalAccess - : PropertyAccessType.UnconditionalAccess; - - for (const property of path) { + for (const item of path) { // all properties read 'on the way' to a dependency are marked as 'access' - let currChild = getOrMakeProperty(currNode, property); + let currChild = getOrMakeProperty(currNode, item.property); + const accessType = inConditional + ? PropertyAccessType.ConditionalAccess + : item.optional + ? PropertyAccessType.OptionalAccess + : PropertyAccessType.UnconditionalAccess; currChild.accessType = merge(currChild.accessType, accessType); currNode = currChild; } - if (optionalPath.length === 0) { - /* - * If this property does not have a conditional path (i.e. a.b.c), the - * final property node should be marked as an conditional/unconditional - * `dependency` as based on control flow. - */ - const depType = inConditional - ? PropertyAccessType.ConditionalDependency + /** + * The final property node should be marked as an conditional/unconditional + * `dependency` as based on control flow. + */ + const depType = inConditional + ? PropertyAccessType.ConditionalDependency + : isOptional(currNode.accessType) + ? PropertyAccessType.OptionalDependency : PropertyAccessType.UnconditionalDependency; - currNode.accessType = merge(currNode.accessType, depType); - } else { - /* - * Technically, we only depend on whether unconditional path `dep.path` - * is nullish (not its actual value). As long as we preserve the nullthrows - * behavior of `dep.path`, we can keep it as an access (and not promote - * to a dependency). - * See test `reduce-reactive-cond-memberexpr-join` for example. - */ - - /* - * If this property has an optional path (i.e. a?.b.c), all optional - * nodes should be marked accordingly. - */ - for (const property of optionalPath) { - let currChild = getOrMakeProperty(currNode, property); - currChild.accessType = merge( - currChild.accessType, - PropertyAccessType.ConditionalAccess, - ); - currNode = currChild; - } - - // The final node should be marked as a conditional dependency. - currNode.accessType = merge( - currNode.accessType, - PropertyAccessType.ConditionalDependency, - ); - } + currNode.accessType = merge(currNode.accessType, depType); } deriveMinimalDependencies(): Set { const results = new Set(); for (const [rootId, rootNode] of this.#roots.entries()) { - const deps = deriveMinimalDependenciesInSubtree(rootNode); + const deps = deriveMinimalDependenciesInSubtree(rootNode, null); CompilerError.invariant( deps.every( - dep => dep.accessType === PropertyAccessType.UnconditionalDependency, + dep => + dep.accessType === PropertyAccessType.UnconditionalDependency || + dep.accessType == PropertyAccessType.OptionalDependency, ), { reason: @@ -215,6 +178,27 @@ export class ReactiveScopeDependencyTree { } return res.flat().join('\n'); } + + debug(): string { + const buf: Array = [`tree() [`]; + for (const [rootId, rootNode] of this.#roots) { + buf.push(`${printIdentifier(rootId)} (${rootNode.accessType}):`); + this.#debugImpl(buf, rootNode, 1); + } + buf.push(']'); + return buf.length > 2 ? buf.join('\n') : buf.join(''); + } + + #debugImpl( + buf: Array, + node: DependencyNode, + depth: number = 0, + ): void { + for (const [property, childNode] of node.properties) { + buf.push(`${' '.repeat(depth)}.${property} (${childNode.accessType}):`); + this.#debugImpl(buf, childNode, depth + 1); + } + } } /* @@ -238,8 +222,10 @@ export class ReactiveScopeDependencyTree { */ enum PropertyAccessType { ConditionalAccess = 'ConditionalAccess', + OptionalAccess = 'OptionalAccess', UnconditionalAccess = 'UnconditionalAccess', ConditionalDependency = 'ConditionalDependency', + OptionalDependency = 'OptionalDependency', UnconditionalDependency = 'UnconditionalDependency', } @@ -253,9 +239,16 @@ function isUnconditional(access: PropertyAccessType): boolean { function isDependency(access: PropertyAccessType): boolean { return ( access === PropertyAccessType.ConditionalDependency || + access === PropertyAccessType.OptionalDependency || access === PropertyAccessType.UnconditionalDependency ); } +function isOptional(access: PropertyAccessType): boolean { + return ( + access === PropertyAccessType.OptionalAccess || + access === PropertyAccessType.OptionalDependency + ); +} function merge( access1: PropertyAccessType, @@ -264,6 +257,7 @@ function merge( const resultIsUnconditional = isUnconditional(access1) || isUnconditional(access2); const resultIsDependency = isDependency(access1) || isDependency(access2); + const resultIsOptional = isOptional(access1) || isOptional(access2); /* * Straightforward merge. @@ -279,6 +273,12 @@ function merge( } else { return PropertyAccessType.UnconditionalAccess; } + } else if (resultIsOptional) { + if (resultIsDependency) { + return PropertyAccessType.OptionalDependency; + } else { + return PropertyAccessType.OptionalAccess; + } } else { if (resultIsDependency) { return PropertyAccessType.ConditionalDependency; @@ -294,23 +294,38 @@ type DependencyNode = { }; type ReduceResultNode = { - relativePath: Array; + relativePath: DependencyPath; accessType: PropertyAccessType; }; -const promoteUncondResult = [ - { +function promoteResult( + accessType: PropertyAccessType, + path: {property: string; optional: boolean} | null, +): Array { + const result: ReduceResultNode = { relativePath: [], - accessType: PropertyAccessType.UnconditionalDependency, - }, -]; + accessType, + }; + if (path !== null) { + result.relativePath.push(path); + } + return [result]; +} -const promoteCondResult = [ - { - relativePath: [], - accessType: PropertyAccessType.ConditionalDependency, - }, -]; +function prependPath( + results: Array, + path: {property: string; optional: boolean} | null, +): Array { + if (path === null) { + return results; + } + return results.map(result => { + return { + accessType: result.accessType, + relativePath: [path, ...result.relativePath], + }; + }); +} /* * Recursively calculates minimal dependencies in a subtree. @@ -319,39 +334,76 @@ const promoteCondResult = [ */ function deriveMinimalDependenciesInSubtree( dep: DependencyNode, + property: string | null, ): Array { const results: Array = []; for (const [childName, childNode] of dep.properties) { - const childResult = deriveMinimalDependenciesInSubtree(childNode).map( - ({relativePath, accessType}) => { - return { - relativePath: [childName, ...relativePath], - accessType, - }; - }, + const childResult = deriveMinimalDependenciesInSubtree( + childNode, + childName, ); results.push(...childResult); } switch (dep.accessType) { case PropertyAccessType.UnconditionalDependency: { - return promoteUncondResult; + return promoteResult( + PropertyAccessType.UnconditionalDependency, + property !== null ? {property, optional: false} : null, + ); } case PropertyAccessType.UnconditionalAccess: { if ( results.every( ({accessType}) => - accessType === PropertyAccessType.UnconditionalDependency, + accessType === PropertyAccessType.UnconditionalDependency || + accessType === PropertyAccessType.OptionalDependency, + ) + ) { + // all children are unconditional dependencies, return them to preserve granularity + return prependPath( + results, + property !== null ? {property, optional: false} : null, + ); + } else { + /* + * at least one child is accessed conditionally, so this node needs to be promoted to + * unconditional dependency + */ + return promoteResult( + PropertyAccessType.UnconditionalDependency, + property !== null ? {property, optional: false} : null, + ); + } + } + case PropertyAccessType.OptionalDependency: { + return promoteResult( + PropertyAccessType.OptionalDependency, + property !== null ? {property, optional: true} : null, + ); + } + case PropertyAccessType.OptionalAccess: { + if ( + results.every( + ({accessType}) => + accessType === PropertyAccessType.UnconditionalDependency || + accessType === PropertyAccessType.OptionalDependency, ) ) { // all children are unconditional dependencies, return them to preserve granularity - return results; + return prependPath( + results, + property !== null ? {property, optional: true} : null, + ); } else { /* * at least one child is accessed conditionally, so this node needs to be promoted to * unconditional dependency */ - return promoteUncondResult; + return promoteResult( + PropertyAccessType.OptionalDependency, + property !== null ? {property, optional: true} : null, + ); } } case PropertyAccessType.ConditionalAccess: @@ -367,13 +419,19 @@ function deriveMinimalDependenciesInSubtree( * unconditional access. * Truncate results of child nodes here, since we shouldn't access them anyways */ - return promoteCondResult; + return promoteResult( + PropertyAccessType.ConditionalDependency, + property !== null ? {property, optional: true} : null, + ); } else { /* * at least one child is accessed unconditionally, so this node can be promoted to * unconditional dependency */ - return promoteUncondResult; + return promoteResult( + PropertyAccessType.UnconditionalDependency, + property !== null ? {property, optional: true} : null, + ); } } default: { diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/FlattenReactiveLoops.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/FlattenReactiveLoops.ts deleted file mode 100644 index 2119b8c16729f..0000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/FlattenReactiveLoops.ts +++ /dev/null @@ -1,84 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import { - ReactiveFunction, - ReactiveScopeBlock, - ReactiveStatement, - ReactiveTerminal, - ReactiveTerminalStatement, -} from '../HIR/HIR'; -import {assertExhaustive} from '../Utils/utils'; -import { - ReactiveFunctionTransform, - Transformed, - visitReactiveFunction, -} from './visitors'; - -/* - * Given a reactive function, flattens any scopes contained within a loop construct. - * We won't initially support memoization within loops though this is possible in the future. - */ -export function flattenReactiveLoops(fn: ReactiveFunction): void { - visitReactiveFunction(fn, new Transform(), false); -} - -class Transform extends ReactiveFunctionTransform { - override transformScope( - scope: ReactiveScopeBlock, - isWithinLoop: boolean, - ): Transformed { - this.visitScope(scope, isWithinLoop); - if (isWithinLoop) { - return { - kind: 'replace', - value: { - kind: 'pruned-scope', - scope: scope.scope, - instructions: scope.instructions, - }, - }; - } else { - return {kind: 'keep'}; - } - } - - override visitTerminal( - stmt: ReactiveTerminalStatement, - isWithinLoop: boolean, - ): void { - switch (stmt.terminal.kind) { - // Loop terminals flatten nested scopes - case 'do-while': - case 'while': - case 'for': - case 'for-of': - case 'for-in': { - this.traverseTerminal(stmt, true); - break; - } - // Non-loop terminals passthrough is contextual, inherits the parent isWithinScope - case 'try': - case 'label': - case 'break': - case 'continue': - case 'if': - case 'return': - case 'switch': - case 'throw': { - this.traverseTerminal(stmt, isWithinLoop); - break; - } - default: { - assertExhaustive( - stmt.terminal, - `Unexpected terminal kind \`${(stmt.terminal as any).kind}\``, - ); - } - } - } -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/FlattenScopesWithHooksOrUse.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/FlattenScopesWithHooksOrUse.ts deleted file mode 100644 index 753cd3d6e87b7..0000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/FlattenScopesWithHooksOrUse.ts +++ /dev/null @@ -1,123 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import { - Environment, - InstructionId, - ReactiveFunction, - ReactiveScopeBlock, - ReactiveStatement, - ReactiveValue, - getHookKind, - isUseOperator, -} from '../HIR'; -import { - ReactiveFunctionTransform, - Transformed, - visitReactiveFunction, -} from './visitors'; - -/** - * For simplicity the majority of compiler passes do not treat hooks specially. However, hooks are different - * from regular functions in two key ways: - * - They can introduce reactivity even when their arguments are non-reactive (accounted for in InferReactivePlaces) - * - They cannot be called conditionally - * - * The `use` operator is similar: - * - It can access context, and therefore introduce reactivity - * - It can be called conditionally, but _it must be called if the component needs the return value_. This is because - * React uses the fact that use was called to remember that the component needs the value, and that changes to the - * input should invalidate the component itself. - * - * This pass accounts for the "can't call conditionally" aspect of both hooks and use. Though the reasoning is slightly - * different for reach, the result is that we can't memoize scopes that call hooks or use since this would make them - * called conditionally in the output. - * - * The pass finds and removes any scopes that transitively contain a hook or use call. By running all - * the reactive scope inference first, agnostic of hooks, we know that the reactive scopes accurately - * describe the set of values which "construct together", and remove _all_ that memoization in order - * to ensure the hook call does not inadvertently become conditional. - */ -export function flattenScopesWithHooksOrUse(fn: ReactiveFunction): void { - visitReactiveFunction(fn, new Transform(), { - env: fn.env, - hasHook: false, - }); -} - -type State = { - env: Environment; - hasHook: boolean; -}; - -class Transform extends ReactiveFunctionTransform { - override transformScope( - scope: ReactiveScopeBlock, - outerState: State, - ): Transformed { - const innerState: State = { - env: outerState.env, - hasHook: false, - }; - this.visitScope(scope, innerState); - outerState.hasHook ||= innerState.hasHook; - if (innerState.hasHook) { - if (scope.instructions.length === 1) { - /* - * This was a scope just for a hook call, which doesn't need memoization. - * flatten it away - */ - return { - kind: 'replace-many', - value: scope.instructions, - }; - } - /* - * else this scope had multiple instructions and produced some other value: - * mark it as pruned - */ - return { - kind: 'replace', - value: { - kind: 'pruned-scope', - scope: scope.scope, - instructions: scope.instructions, - }, - }; - } else { - return {kind: 'keep'}; - } - } - - override visitValue( - id: InstructionId, - value: ReactiveValue, - state: State, - ): void { - this.traverseValue(id, value, state); - switch (value.kind) { - case 'CallExpression': { - if ( - getHookKind(state.env, value.callee.identifier) != null || - isUseOperator(value.callee.identifier) - ) { - state.hasHook = true; - } - break; - } - case 'MethodCall': { - if ( - getHookKind(state.env, value.property.identifier) != null || - isUseOperator(value.property.identifier) - ) { - state.hasHook = true; - } - break; - } - } - } -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/InferReactiveScopeVariables.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/InferReactiveScopeVariables.ts index 27aba91af2b1c..098139b150d5a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/InferReactiveScopeVariables.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/InferReactiveScopeVariables.ts @@ -13,6 +13,8 @@ import { HIRFunction, Identifier, Instruction, + InstructionId, + MutableRange, Place, ReactiveScope, makeInstructionId, @@ -186,8 +188,14 @@ function mergeLocation(l: SourceLocation, r: SourceLocation): SourceLocation { } // Is the operand mutable at this given instruction -export function isMutable({id}: Instruction, place: Place): boolean { - const range = place.identifier.mutableRange; +export function isMutable(instr: {id: InstructionId}, place: Place): boolean { + return inRange(instr, place.identifier.mutableRange); +} + +export function inRange( + {id}: {id: InstructionId}, + range: MutableRange, +): boolean { return id >= range.start && id < range.end; } @@ -227,6 +235,7 @@ function mayAllocate(env: Environment, instruction: Instruction): boolean { case 'StoreGlobal': { return false; } + case 'TaggedTemplateExpression': case 'CallExpression': case 'MethodCall': { return instruction.lvalue.identifier.type.kind !== 'Primitive'; @@ -241,8 +250,7 @@ function mayAllocate(env: Environment, instruction: Instruction): boolean { case 'ObjectExpression': case 'UnsupportedNode': case 'ObjectMethod': - case 'FunctionExpression': - case 'TaggedTemplateExpression': { + case 'FunctionExpression': { return true; } default: { @@ -273,22 +281,25 @@ export function findDisjointMutableValues( */ for (const phi of block.phis) { if ( - phi.id.mutableRange.start + 1 !== phi.id.mutableRange.end && - phi.id.mutableRange.end > + phi.place.identifier.mutableRange.start + 1 !== + phi.place.identifier.mutableRange.end && + phi.place.identifier.mutableRange.end > (block.instructions.at(0)?.id ?? block.terminal.id) ) { - const operands = [phi.id]; - const declaration = declarations.get(phi.id.declarationId); + const operands = [phi.place.identifier]; + const declaration = declarations.get( + phi.place.identifier.declarationId, + ); if (declaration !== undefined) { operands.push(declaration); } for (const [_, phiId] of phi.operands) { - operands.push(phiId); + operands.push(phiId.identifier); } scopeIdentifiers.union(operands); } else if (fn.env.config.enableForest) { for (const [, phiId] of phi.operands) { - scopeIdentifiers.union([phi.id, phiId]); + scopeIdentifiers.union([phi.place.identifier, phiId.identifier]); } } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/MergeOverlappingReactiveScopes.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/MergeOverlappingReactiveScopes.ts deleted file mode 100644 index 733730fdec5f2..0000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/MergeOverlappingReactiveScopes.ts +++ /dev/null @@ -1,281 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import { - InstructionId, - makeInstructionId, - Place, - ReactiveBlock, - ReactiveFunction, - ReactiveInstruction, - ReactiveScope, - ScopeId, -} from '../HIR'; -import DisjointSet from '../Utils/DisjointSet'; -import {retainWhere} from '../Utils/utils'; -import {getPlaceScope} from './BuildReactiveBlocks'; -import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors'; - -/* - * Note: this is the 3rd of 4 passes that determine how to break a function into discrete - * reactive scopes (independently memoizeable units of code): - * 1. InferReactiveScopeVariables (on HIR) determines operands that mutate together and assigns - * them a unique reactive scope. - * 2. AlignReactiveScopesToBlockScopes (on ReactiveFunction) aligns reactive scopes - * to block scopes. - * 3. MergeOverlappingReactiveScopes (this pass, on ReactiveFunction) ensures that reactive - * scopes do not overlap, merging any such scopes. - * 4. BuildReactiveBlocks (on ReactiveFunction) groups the statements for each scope into - * a ReactiveScopeBlock. - * - * Previous passes may leave "overlapping" scopes, ie where one or more instructions are within - * the mutable range of multiple reactive scopes. We prefer to avoid executing instructions twice - * for performance reasons (side effects are less of a concern bc components are required to be - * idempotent), so we cannot simply repeat the instruction once for each scope. Instead, the only - * option is to combine the two scopes into one. This is an area where an eventual Forget IDE - * could provide real-time feedback to the developer that two computations are accidentally merged. - * - * ## Detailed Walkthrough - * - * Two scopes overlap if there is one or more instruction that is inside the range - * of both scopes. In general, overlapping scopes are merged togther. The only - * exception to this is when one scope *shadows* another scope. For example: - * - * ```javascript - * function foo(cond, a) { - * ⌵ scope for x - * let x = []; ⌝ - * if (cond) { ⎮ - * ⌵ scope for y ⎮ - * let y = []; ⌝ ⎮ - * if (b) { ⎮ ⎮ - * y.push(b); ⌟ ⎮ - * } ⎮ - * x.push(
{y}
); ⎮ - * } ⌟ - * } - * ``` - * - * In this example the two scopes overlap, but mutation of the two scopes is not - * interleaved. Specifically within the y scope there are no instructions that - * modify any other scope: the inner scope "shadows" the outer one. This category - * of overlap does *NOT* merge the scopes together. - * - * The implementation is inspired by the Rust notion of "stacked borrows". We traverse - * the control-flow graph in tree form, at each point keeping track of which scopes are - * active. So initially we see - * - * `let x = []` - * active scopes: [x] - * - * and mark the x scope as active. - * - * Then we later encounter - * - * `let y = [];` - * active scopes: [x, y] - * - * Here we first check to see if 'y' is already in the list of active scopes. It isn't, - * so we push it to the stop of the stack. - * - * Then - * - * `y.push(b)` - * active scopes: [x, y] - * - * Mutates y, so we check if y is the top of the stack. It is, so no merging must occur. - * - * If instead we saw eg - * - * `x.push(b)` - * active scopes: [x, y] - * - * Then we would see that 'x' is active, but that it is shadowed. The two scopes would have - * to be merged. - */ -export function mergeOverlappingReactiveScopes(fn: ReactiveFunction): void { - const context = new Context(); - visitReactiveFunction(fn, new Visitor(), context); - context.complete(); -} - -class Visitor extends ReactiveFunctionVisitor { - override visitID(id: InstructionId, state: Context): void { - state.visitId(id); - } - override visitPlace(id: InstructionId, place: Place, state: Context): void { - state.visitPlace(id, place); - } - override visitLValue(id: InstructionId, lvalue: Place, state: Context): void { - state.visitPlace(id, lvalue); - } - override visitBlock(block: ReactiveBlock, state: Context): void { - state.enter(() => { - this.traverseBlock(block, state); - }); - } - override visitInstruction( - instruction: ReactiveInstruction, - state: Context, - ): void { - if ( - instruction.value.kind === 'ConditionalExpression' || - instruction.value.kind === 'LogicalExpression' || - instruction.value.kind === 'OptionalExpression' - ) { - state.enter(() => { - super.visitInstruction(instruction, state); - }); - } else { - super.visitInstruction(instruction, state); - } - } -} - -class BlockScope { - seen: Set = new Set(); - scopes: Array = []; -} - -type ShadowableReactiveScope = { - scope: ReactiveScope; - shadowedBy: ReactiveScope | null; -}; - -class Context { - scopes: Array = []; - seenScopes: Set = new Set(); - joinedScopes: DisjointSet = new DisjointSet(); - operandScopes: Map = new Map(); - - visitId(id: InstructionId): void { - const currentBlock = this.scopes[this.scopes.length - 1]!; - retainWhere(currentBlock.scopes, pending => { - if (pending.scope.range.end > id) { - return true; - } else { - currentBlock.seen.delete(pending.scope.id); - return false; - } - }); - } - - visitPlace(id: InstructionId, place: Place): void { - const scope = getPlaceScope(id, place); - if (scope === null) { - return; - } - this.operandScopes.set(place, scope); - const currentBlock = this.scopes[this.scopes.length - 1]!; - // Fast-path for the first time we see a new scope - if (!this.seenScopes.has(scope.id)) { - this.seenScopes.add(scope.id); - currentBlock.seen.add(scope.id); - currentBlock.scopes.push({shadowedBy: null, scope}); - return; - } - // Scope has already been seen, find it in the current block or a parent - let index = this.scopes.length - 1; - let nextBlock = currentBlock; - while (!nextBlock.seen.has(scope.id)) { - /* - * scopes that cross control-flow boundaries are merged with overlapping - * scopes - */ - this.joinedScopes.union([scope, ...nextBlock.scopes.map(s => s.scope)]); - index--; - if (index < 0) { - /* - * TODO: handle reassignments in multiple branches. these create new identifiers that - * add an entry to this.seenScopes but which are then removed when their blocks exit. - * this is also wrong for codegen, different versions of an identifier could be cached - * differently and so a reassigned version of a variable needs a separate declaration. - * console.log(`scope ${scope.id} not found`); - */ - - /* - * for (let i = this.scopes.length - 1; i > index; i--) { - * const s = this.scopes[i]; - * console.log( - * JSON.stringify( - * { - * seen: Array.from(s.seen), - * scopes: s.scopes, - * }, - * null, - * 2 - * ) - * ); - * } - */ - currentBlock.seen.add(scope.id); - currentBlock.scopes.push({shadowedBy: null, scope}); - return; - } - nextBlock = this.scopes[index]!; - } - - // Handle interleaving within a given block scope - let found = false; - for (let i = 0; i < nextBlock.scopes.length; i++) { - const current = nextBlock.scopes[i]!; - if (current.scope.id === scope.id) { - found = true; - if (current.shadowedBy !== null) { - this.joinedScopes.union([current.shadowedBy, current.scope]); - } - } else if (found && current.shadowedBy === null) { - // `scope` is shadowing `current` and may interleave - current.shadowedBy = scope; - if (current.scope.range.end > scope.range.end) { - /* - * Current is shadowed by `scope`, and we know that `current` will mutate - * again (per its range), so the scopes are already known to interleave. - * - * Eagerly extend the ranges of the scopes so that we don't prematurely end - * a scope relative to its eventual post-merge mutable range - */ - const end = makeInstructionId( - Math.max(current.scope.range.end, scope.range.end), - ); - current.scope.range.end = end; - scope.range.end = end; - this.joinedScopes.union([current.scope, scope]); - } - } - } - if (!currentBlock.seen.has(scope.id)) { - currentBlock.seen.add(scope.id); - currentBlock.scopes.push({shadowedBy: null, scope}); - } - } - - enter(fn: () => void): void { - this.scopes.push(new BlockScope()); - fn(); - this.scopes.pop(); - } - - complete(): void { - this.joinedScopes.forEach((scope, groupScope) => { - if (scope !== groupScope) { - groupScope.range.start = makeInstructionId( - Math.min(groupScope.range.start, scope.range.start), - ); - groupScope.range.end = makeInstructionId( - Math.max(groupScope.range.end, scope.range.end), - ); - } - }); - for (const [operand, originalScope] of this.operandScopes) { - const mergedScope = this.joinedScopes.find(originalScope); - if (mergedScope !== null) { - operand.identifier.scope = mergedScope; - } - } - } -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/MergeReactiveScopesThatInvalidateTogether.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/MergeReactiveScopesThatInvalidateTogether.ts index 2c9004e6ad9a6..08d2212d86b95 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/MergeReactiveScopesThatInvalidateTogether.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/MergeReactiveScopesThatInvalidateTogether.ts @@ -19,6 +19,7 @@ import { ReactiveScopeDependency, ReactiveStatement, Type, + areEqualPaths, makeInstructionId, } from '../HIR'; import { @@ -481,14 +482,20 @@ function canMergeScopes( } function isAlwaysInvalidatingType(type: Type): boolean { - if (type.kind === 'Object') { - switch (type.shapeId) { - case BuiltInArrayId: - case BuiltInObjectId: - case BuiltInFunctionId: - case BuiltInJsxId: { - return true; + switch (type.kind) { + case 'Object': { + switch (type.shapeId) { + case BuiltInArrayId: + case BuiltInObjectId: + case BuiltInFunctionId: + case BuiltInJsxId: { + return true; + } } + break; + } + case 'Function': { + return true; } } return false; @@ -519,10 +526,6 @@ function areEqualDependencies( return true; } -export function areEqualPaths(a: Array, b: Array): boolean { - return a.length === b.length && a.every((item, ix) => item === b[ix]); -} - /** * Is this scope eligible for merging with subsequent scopes? In general this * is only true if the scope's output values are guaranteed to change when its diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PrintReactiveFunction.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PrintReactiveFunction.ts index f85f7071f1c49..b5aa44ead095d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PrintReactiveFunction.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PrintReactiveFunction.ts @@ -113,7 +113,7 @@ export function printDependency(dependency: ReactiveScopeDependency): string { const identifier = printIdentifier(dependency.identifier) + printType(dependency.identifier.type); - return `${identifier}${dependency.path.map(prop => `.${prop}`).join('')}`; + return `${identifier}${dependency.path.map(token => `${token.optional ? '?.' : '.'}${token.property}`).join('')}`; } export function printReactiveInstructions( diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts deleted file mode 100644 index 690bdb758839d..0000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PropagateScopeDependencies.ts +++ /dev/null @@ -1,1096 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import {CompilerError} from '../CompilerError'; -import { - BlockId, - DeclarationId, - GeneratedSource, - Identifier, - InstructionId, - InstructionKind, - isObjectMethodType, - isRefValueType, - isUseRefType, - makeInstructionId, - Place, - PrunedReactiveScopeBlock, - ReactiveFunction, - ReactiveInstruction, - ReactiveScope, - ReactiveScopeBlock, - ReactiveScopeDependency, - ReactiveTerminalStatement, - ReactiveValue, - ScopeId, -} from '../HIR/HIR'; -import {eachInstructionValueOperand, eachPatternOperand} from '../HIR/visitors'; -import {empty, Stack} from '../Utils/Stack'; -import {assertExhaustive, Iterable_some} from '../Utils/utils'; -import { - ReactiveScopeDependencyTree, - ReactiveScopePropertyDependency, -} from './DeriveMinimalDependencies'; -import {areEqualPaths} from './MergeReactiveScopesThatInvalidateTogether'; -import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors'; - -/* - * Infers the dependencies of each scope to include variables whose values - * are non-stable and created prior to the start of the scope. Also propagates - * dependencies upwards, so that parent scope dependencies are the union of - * their direct dependencies and those of their child scopes. - */ -export function propagateScopeDependencies(fn: ReactiveFunction): void { - const escapingTemporaries: TemporariesUsedOutsideDefiningScope = { - declarations: new Map(), - usedOutsideDeclaringScope: new Set(), - }; - visitReactiveFunction(fn, new FindPromotedTemporaries(), escapingTemporaries); - - const context = new Context(escapingTemporaries.usedOutsideDeclaringScope); - for (const param of fn.params) { - if (param.kind === 'Identifier') { - context.declare(param.identifier, { - id: makeInstructionId(0), - scope: empty(), - }); - } else { - context.declare(param.place.identifier, { - id: makeInstructionId(0), - scope: empty(), - }); - } - } - visitReactiveFunction( - fn, - new PropagationVisitor(fn.env.config.enableTreatFunctionDepsAsConditional), - context, - ); -} - -type TemporariesUsedOutsideDefiningScope = { - /* - * tracks all relevant temporary declarations (currently LoadLocal and PropertyLoad) - * and the scope where they are defined - */ - declarations: Map; - // temporaries used outside of their defining scope - usedOutsideDeclaringScope: Set; -}; -class FindPromotedTemporaries extends ReactiveFunctionVisitor { - scopes: Array = []; - - override visitScope( - scope: ReactiveScopeBlock, - state: TemporariesUsedOutsideDefiningScope, - ): void { - this.scopes.push(scope.scope.id); - this.traverseScope(scope, state); - this.scopes.pop(); - } - - override visitInstruction( - instruction: ReactiveInstruction, - state: TemporariesUsedOutsideDefiningScope, - ): void { - // Visit all places first, then record temporaries which may need to be promoted - this.traverseInstruction(instruction, state); - - const scope = this.scopes.at(-1); - if (instruction.lvalue === null || scope === undefined) { - return; - } - switch (instruction.value.kind) { - case 'LoadLocal': - case 'LoadContext': - case 'PropertyLoad': { - state.declarations.set( - instruction.lvalue.identifier.declarationId, - scope, - ); - break; - } - default: { - break; - } - } - } - - override visitPlace( - _id: InstructionId, - place: Place, - state: TemporariesUsedOutsideDefiningScope, - ): void { - const declaringScope = state.declarations.get( - place.identifier.declarationId, - ); - if (declaringScope === undefined) { - return; - } - if (this.scopes.indexOf(declaringScope) === -1) { - // Declaring scope is not active === used outside declaring scope - state.usedOutsideDeclaringScope.add(place.identifier.declarationId); - } - } -} - -type DeclMap = Map; -type Decl = { - id: InstructionId; - scope: Stack; -}; - -/** - * TraversalState and PoisonState is used to track the poisoned state of a scope. - * - * A scope is poisoned when either of these conditions hold: - * - one of its own nested blocks is a jump target (for break/continues) - * - it is a outermost scope and contains a throw / return - * - * When a scope is poisoned, all dependencies (from instructions and inner scopes) - * are added as conditionally accessed. - */ -type ScopeTraversalState = { - value: ReactiveScope; - ownBlocks: Stack; -}; - -class PoisonState { - poisonedBlocks: Set = new Set(); - poisonedScopes: Set = new Set(); - isPoisoned: boolean = false; - - constructor( - poisonedBlocks: Set, - poisonedScopes: Set, - isPoisoned: boolean, - ) { - this.poisonedBlocks = poisonedBlocks; - this.poisonedScopes = poisonedScopes; - this.isPoisoned = isPoisoned; - } - - clone(): PoisonState { - return new PoisonState( - new Set(this.poisonedBlocks), - new Set(this.poisonedScopes), - this.isPoisoned, - ); - } - - take(other: PoisonState): PoisonState { - const copy = new PoisonState( - this.poisonedBlocks, - this.poisonedScopes, - this.isPoisoned, - ); - this.poisonedBlocks = other.poisonedBlocks; - this.poisonedScopes = other.poisonedScopes; - this.isPoisoned = other.isPoisoned; - return copy; - } - - merge( - others: Array, - currentScope: ScopeTraversalState | null, - ): void { - for (const other of others) { - for (const id of other.poisonedBlocks) { - this.poisonedBlocks.add(id); - } - for (const id of other.poisonedScopes) { - this.poisonedScopes.add(id); - } - } - this.#invalidate(currentScope); - } - - #invalidate(currentScope: ScopeTraversalState | null): void { - if (currentScope != null) { - if (this.poisonedScopes.has(currentScope.value.id)) { - this.isPoisoned = true; - return; - } else if ( - currentScope.ownBlocks.find(blockId => this.poisonedBlocks.has(blockId)) - ) { - this.isPoisoned = true; - return; - } - } - this.isPoisoned = false; - } - - /** - * Mark a block or scope as poisoned and update the `isPoisoned` flag. - * - * @param targetBlock id of the block which ends non-linear control flow. - * For a break/continue instruction, this is the target block. - * Throw and return instructions have no target and will poison the earliest - * active scope - */ - addPoisonTarget( - target: BlockId | null, - activeScopes: Stack, - ): void { - const currentScope = activeScopes.value; - if (target == null && currentScope != null) { - let cursor = activeScopes; - while (true) { - const next = cursor.pop(); - if (next.value == null) { - const poisonedScope = cursor.value!.value.id; - this.poisonedScopes.add(poisonedScope); - if (poisonedScope === currentScope?.value.id) { - this.isPoisoned = true; - } - break; - } else { - cursor = next; - } - } - } else if (target != null) { - this.poisonedBlocks.add(target); - if ( - !this.isPoisoned && - currentScope?.ownBlocks.find(blockId => blockId === target) - ) { - this.isPoisoned = true; - } - } - } - - /** - * Invoked during traversal when a poisoned scope becomes inactive - * @param id - * @param currentScope - */ - removeMaybePoisonedScope( - id: ScopeId, - currentScope: ScopeTraversalState | null, - ): void { - this.poisonedScopes.delete(id); - this.#invalidate(currentScope); - } - - removeMaybePoisonedBlock( - id: BlockId, - currentScope: ScopeTraversalState | null, - ): void { - this.poisonedBlocks.delete(id); - this.#invalidate(currentScope); - } -} - -class Context { - #temporariesUsedOutsideScope: Set; - #declarations: DeclMap = new Map(); - #reassignments: Map = new Map(); - // Reactive dependencies used in the current reactive scope. - #dependencies: ReactiveScopeDependencyTree = - new ReactiveScopeDependencyTree(); - /* - * We keep a sidemap for temporaries created by PropertyLoads, and do - * not store any control flow (i.e. #inConditionalWithinScope) here. - * - a ReactiveScope (A) containing a PropertyLoad may differ from the - * ReactiveScope (B) that uses the produced temporary. - * - codegen will inline these PropertyLoads back into scope (B) - */ - #properties: Map = new Map(); - #temporaries: Map = new Map(); - #inConditionalWithinScope: boolean = false; - /* - * Reactive dependencies used unconditionally in the current conditional. - * Composed of dependencies: - * - directly accessed within block (added in visitDep) - * - accessed by all cfg branches (added through promoteDeps) - */ - #depsInCurrentConditional: ReactiveScopeDependencyTree = - new ReactiveScopeDependencyTree(); - #scopes: Stack = empty(); - poisonState: PoisonState = new PoisonState(new Set(), new Set(), false); - - constructor(temporariesUsedOutsideScope: Set) { - this.#temporariesUsedOutsideScope = temporariesUsedOutsideScope; - } - - enter(scope: ReactiveScope, fn: () => void): Set { - // Save context of previous scope - const prevInConditional = this.#inConditionalWithinScope; - const previousDependencies = this.#dependencies; - const prevDepsInConditional: ReactiveScopeDependencyTree | null = this - .isPoisoned - ? this.#depsInCurrentConditional - : null; - if (prevDepsInConditional != null) { - this.#depsInCurrentConditional = new ReactiveScopeDependencyTree(); - } - - /* - * Set context for new scope - * A nested scope should add all deps it directly uses as its own - * unconditional deps, regardless of whether the nested scope is itself - * within a conditional - */ - const scopedDependencies = new ReactiveScopeDependencyTree(); - this.#inConditionalWithinScope = false; - this.#dependencies = scopedDependencies; - this.#scopes = this.#scopes.push({ - value: scope, - ownBlocks: empty(), - }); - this.poisonState.isPoisoned = false; - - fn(); - - // Restore context of previous scope - this.#scopes = this.#scopes.pop(); - this.poisonState.removeMaybePoisonedScope(scope.id, this.#scopes.value); - - this.#dependencies = previousDependencies; - this.#inConditionalWithinScope = prevInConditional; - - // Derive minimal dependencies now, since next line may mutate scopedDependencies - const minInnerScopeDependencies = - scopedDependencies.deriveMinimalDependencies(); - - /* - * propagate dependencies upward using the same rules as normal dependency - * collection. child scopes may have dependencies on values created within - * the outer scope, which necessarily cannot be dependencies of the outer - * scope - */ - this.#dependencies.addDepsFromInnerScope( - scopedDependencies, - this.#inConditionalWithinScope || this.isPoisoned, - this.#checkValidDependency.bind(this), - ); - - if (prevDepsInConditional != null) { - // Outer scope is poisoned - prevDepsInConditional.addDepsFromInnerScope( - this.#depsInCurrentConditional, - true, - this.#checkValidDependency.bind(this), - ); - this.#depsInCurrentConditional = prevDepsInConditional; - } - - return minInnerScopeDependencies; - } - - isUsedOutsideDeclaringScope(place: Place): boolean { - return this.#temporariesUsedOutsideScope.has( - place.identifier.declarationId, - ); - } - - /* - * Prints dependency tree to string for debugging. - * @param includeAccesses - * @returns string representation of DependencyTree - */ - printDeps(includeAccesses: boolean = false): string { - return this.#dependencies.printDeps(includeAccesses); - } - - /* - * We track and return unconditional accesses / deps within this conditional. - * If an object property is always used (i.e. in every conditional path), we - * want to promote it to an unconditional access / dependency. - * - * The caller of `enterConditional` is responsible determining for promotion. - * i.e. call promoteDepsFromExhaustiveConditionals to merge returned results. - * - * e.g. we want to mark props.a.b as an unconditional dep here - * if (foo(...)) { - * access(props.a.b); - * } else { - * access(props.a.b); - * } - */ - enterConditional(fn: () => void): ReactiveScopeDependencyTree { - const prevInConditional = this.#inConditionalWithinScope; - const prevUncondAccessed = this.#depsInCurrentConditional; - this.#inConditionalWithinScope = true; - this.#depsInCurrentConditional = new ReactiveScopeDependencyTree(); - fn(); - const result = this.#depsInCurrentConditional; - this.#inConditionalWithinScope = prevInConditional; - this.#depsInCurrentConditional = prevUncondAccessed; - return result; - } - - /* - * Add dependencies from exhaustive CFG paths into the current ReactiveDeps - * tree. If a property is used in every CFG path, it is promoted to an - * unconditional access / dependency here. - * @param depsInConditionals - */ - promoteDepsFromExhaustiveConditionals( - depsInConditionals: Array, - ): void { - this.#dependencies.promoteDepsFromExhaustiveConditionals( - depsInConditionals, - ); - this.#depsInCurrentConditional.promoteDepsFromExhaustiveConditionals( - depsInConditionals, - ); - } - - /* - * Records where a value was declared, and optionally, the scope where the value originated from. - * This is later used to determine if a dependency should be added to a scope; if the current - * scope we are visiting is the same scope where the value originates, it can't be a dependency - * on itself. - */ - declare(identifier: Identifier, decl: Decl): void { - if (!this.#declarations.has(identifier.declarationId)) { - this.#declarations.set(identifier.declarationId, decl); - } - this.#reassignments.set(identifier, decl); - } - - declareTemporary(lvalue: Place, place: Place): void { - this.#temporaries.set(lvalue.identifier, place); - } - - resolveTemporary(place: Place): Place { - return this.#temporaries.get(place.identifier) ?? place; - } - - #getProperty( - object: Place, - property: string, - isConditional: boolean, - ): ReactiveScopePropertyDependency { - const resolvedObject = this.resolveTemporary(object); - const resolvedDependency = this.#properties.get(resolvedObject.identifier); - let objectDependency: ReactiveScopePropertyDependency; - /* - * (1) Create the base property dependency as either a LoadLocal (from a temporary) - * or a deep copy of an existing property dependency. - */ - if (resolvedDependency === undefined) { - objectDependency = { - identifier: resolvedObject.identifier, - path: [], - optionalPath: [], - }; - } else { - objectDependency = { - identifier: resolvedDependency.identifier, - path: [...resolvedDependency.path], - optionalPath: [...resolvedDependency.optionalPath], - }; - } - - // (2) Determine whether property is an optional access - if (objectDependency.optionalPath.length > 0) { - /* - * If the base property dependency represents a optional member expression, - * property is on the optionalPath (regardless of whether this PropertyLoad - * itself was conditional) - * e.g. for `a.b?.c.d`, `d` should be added to optionalPath - */ - objectDependency.optionalPath.push(property); - } else if (isConditional) { - objectDependency.optionalPath.push(property); - } else { - objectDependency.path.push(property); - } - - return objectDependency; - } - - declareProperty(lvalue: Place, object: Place, property: string): void { - const nextDependency = this.#getProperty(object, property, false); - this.#properties.set(lvalue.identifier, nextDependency); - } - - // Checks if identifier is a valid dependency in the current scope - #checkValidDependency(maybeDependency: ReactiveScopeDependency): boolean { - // ref.current access is not a valid dep - if ( - isUseRefType(maybeDependency.identifier) && - maybeDependency.path.at(0) === 'current' - ) { - return false; - } - - // ref value is not a valid dep - if (isRefValueType(maybeDependency.identifier)) { - return false; - } - - /* - * object methods are not deps because they will be codegen'ed back in to - * the object literal. - */ - if (isObjectMethodType(maybeDependency.identifier)) { - return false; - } - - const identifier = maybeDependency.identifier; - /* - * If this operand is used in a scope, has a dynamic value, and was defined - * before this scope, then its a dependency of the scope. - */ - const currentDeclaration = - this.#reassignments.get(identifier) ?? - this.#declarations.get(identifier.declarationId); - const currentScope = this.currentScope.value?.value; - return ( - currentScope != null && - currentDeclaration !== undefined && - currentDeclaration.id < currentScope.range.start && - (currentDeclaration.scope == null || - currentDeclaration.scope.value?.value !== currentScope) - ); - } - - #isScopeActive(scope: ReactiveScope): boolean { - if (this.#scopes === null) { - return false; - } - return this.#scopes.find(state => state.value === scope); - } - - get currentScope(): Stack { - return this.#scopes; - } - - get isPoisoned(): boolean { - return this.poisonState.isPoisoned; - } - - visitOperand(place: Place): void { - const resolved = this.resolveTemporary(place); - /* - * if this operand is a temporary created for a property load, try to resolve it to - * the expanded Place. Fall back to using the operand as-is. - */ - - let dependency: ReactiveScopePropertyDependency = { - identifier: resolved.identifier, - path: [], - optionalPath: [], - }; - if (resolved.identifier.name === null) { - const propertyDependency = this.#properties.get(resolved.identifier); - if (propertyDependency !== undefined) { - dependency = {...propertyDependency}; - } - } - this.visitDependency(dependency); - } - - visitProperty(object: Place, property: string): void { - const nextDependency = this.#getProperty(object, property, false); - this.visitDependency(nextDependency); - } - - visitDependency(maybeDependency: ReactiveScopePropertyDependency): void { - /* - * Any value used after its originally defining scope has concluded must be added as an - * output of its defining scope. Regardless of whether its a const or not, - * some later code needs access to the value. If the current - * scope we are visiting is the same scope where the value originates, it can't be a dependency - * on itself. - */ - - /* - * if originalDeclaration is undefined here, then this is a free var - * (all other decls e.g. `let x;` should be initialized in BuildHIR) - */ - const originalDeclaration = this.#declarations.get( - maybeDependency.identifier.declarationId, - ); - if ( - originalDeclaration !== undefined && - originalDeclaration.scope.value !== null - ) { - originalDeclaration.scope.each(scope => { - if ( - !this.#isScopeActive(scope.value) && - // TODO LeaveSSA: key scope.declarations by DeclarationId - !Iterable_some( - scope.value.declarations.values(), - decl => - decl.identifier.declarationId === - maybeDependency.identifier.declarationId, - ) - ) { - scope.value.declarations.set(maybeDependency.identifier.id, { - identifier: maybeDependency.identifier, - scope: originalDeclaration.scope.value!.value, - }); - } - }); - } - - if (this.#checkValidDependency(maybeDependency)) { - const isPoisoned = this.isPoisoned; - this.#depsInCurrentConditional.add(maybeDependency, isPoisoned); - /* - * Add info about this dependency to the existing tree - * We do not try to join/reduce dependencies here due to missing info - */ - this.#dependencies.add( - maybeDependency, - this.#inConditionalWithinScope || isPoisoned, - ); - } - } - - /* - * Record a variable that is declared in some other scope and that is being reassigned in the - * current one as a {@link ReactiveScope.reassignments} - */ - visitReassignment(place: Place): void { - const currentScope = this.currentScope.value?.value; - if ( - currentScope != null && - !Iterable_some( - currentScope.reassignments, - identifier => - identifier.declarationId === place.identifier.declarationId, - ) && - this.#checkValidDependency({identifier: place.identifier, path: []}) - ) { - // TODO LeaveSSA: scope.reassignments should be keyed by declarationid - currentScope.reassignments.add(place.identifier); - } - } - - pushLabeledBlock(id: BlockId): void { - const currentScope = this.#scopes.value; - if (currentScope != null) { - currentScope.ownBlocks = currentScope.ownBlocks.push(id); - } - } - popLabeledBlock(id: BlockId): void { - const currentScope = this.#scopes.value; - if (currentScope != null) { - const last = currentScope.ownBlocks.value; - currentScope.ownBlocks = currentScope.ownBlocks.pop(); - - CompilerError.invariant(last != null && last === id, { - reason: '[PropagateScopeDependencies] Misformed block stack', - loc: GeneratedSource, - }); - } - this.poisonState.removeMaybePoisonedBlock(id, currentScope); - } -} - -class PropagationVisitor extends ReactiveFunctionVisitor { - enableTreatFunctionDepsAsConditional = false; - - constructor(enableTreatFunctionDepsAsConditional: boolean) { - super(); - this.enableTreatFunctionDepsAsConditional = - enableTreatFunctionDepsAsConditional; - } - - override visitScope(scope: ReactiveScopeBlock, context: Context): void { - const scopeDependencies = context.enter(scope.scope, () => { - this.visitBlock(scope.instructions, context); - }); - for (const candidateDep of scopeDependencies) { - if ( - !Iterable_some( - scope.scope.dependencies, - existingDep => - existingDep.identifier.declarationId === - candidateDep.identifier.declarationId && - areEqualPaths(existingDep.path, candidateDep.path), - ) - ) { - scope.scope.dependencies.add(candidateDep); - } - } - /* - * TODO LeaveSSA: fix existing bug with duplicate deps and reassignments - * see fixture ssa-cascading-eliminated-phis, note that we cache `x` - * twice because its both a dep and a reassignment. - * - * for (const reassignment of scope.scope.reassignments) { - * if ( - * Iterable_some( - * scope.scope.dependencies.values(), - * dep => - * dep.identifier.declarationId === reassignment.declarationId && - * dep.path.length === 0, - * ) - * ) { - * scope.scope.reassignments.delete(reassignment); - * } - * } - */ - } - - override visitPrunedScope( - scopeBlock: PrunedReactiveScopeBlock, - context: Context, - ): void { - /* - * NOTE: we explicitly throw away the deps, we only enter() the scope to record its - * declarations - */ - const _scopeDepdencies = context.enter(scopeBlock.scope, () => { - this.visitBlock(scopeBlock.instructions, context); - }); - } - - override visitInstruction( - instruction: ReactiveInstruction, - context: Context, - ): void { - const {id, value, lvalue} = instruction; - this.visitInstructionValue(context, id, value, lvalue); - if (lvalue == null) { - return; - } - context.declare(lvalue.identifier, { - id, - scope: context.currentScope, - }); - } - - visitReactiveValue( - context: Context, - id: InstructionId, - value: ReactiveValue, - ): void { - switch (value.kind) { - case 'OptionalExpression': { - const inner = value.value; - /* - * OptionalExpression value is a SequenceExpression where the instructions - * represent the code prior to the `?` and the final value represents the - * conditional code that follows. - */ - CompilerError.invariant(inner.kind === 'SequenceExpression', { - reason: - 'Expected OptionalExpression value to be a SequenceExpression', - description: `Found a \`${value.kind}\``, - loc: value.loc, - suggestions: null, - }); - // Instructions are the unconditionally executed portion before the `?` - for (const instr of inner.instructions) { - this.visitInstruction(instr, context); - } - // The final value is the conditional portion following the `?` - context.enterConditional(() => { - this.visitReactiveValue(context, id, inner.value); - }); - break; - } - case 'LogicalExpression': { - this.visitReactiveValue(context, id, value.left); - context.enterConditional(() => { - this.visitReactiveValue(context, id, value.right); - }); - break; - } - case 'ConditionalExpression': { - this.visitReactiveValue(context, id, value.test); - - const consequentDeps = context.enterConditional(() => { - this.visitReactiveValue(context, id, value.consequent); - }); - const alternateDeps = context.enterConditional(() => { - this.visitReactiveValue(context, id, value.alternate); - }); - context.promoteDepsFromExhaustiveConditionals([ - consequentDeps, - alternateDeps, - ]); - break; - } - case 'SequenceExpression': { - for (const instr of value.instructions) { - this.visitInstruction(instr, context); - } - this.visitInstructionValue(context, id, value.value, null); - break; - } - case 'FunctionExpression': { - if (this.enableTreatFunctionDepsAsConditional) { - context.enterConditional(() => { - for (const operand of eachInstructionValueOperand(value)) { - context.visitOperand(operand); - } - }); - } else { - for (const operand of eachInstructionValueOperand(value)) { - context.visitOperand(operand); - } - } - break; - } - case 'ReactiveFunctionValue': { - CompilerError.invariant(false, { - reason: `Unexpected ReactiveFunctionValue`, - loc: value.loc, - description: null, - suggestions: null, - }); - } - default: { - for (const operand of eachInstructionValueOperand(value)) { - context.visitOperand(operand); - } - } - } - } - - visitInstructionValue( - context: Context, - id: InstructionId, - value: ReactiveValue, - lvalue: Place | null, - ): void { - if (value.kind === 'LoadLocal' && lvalue !== null) { - if ( - value.place.identifier.name !== null && - lvalue.identifier.name === null && - !context.isUsedOutsideDeclaringScope(lvalue) - ) { - context.declareTemporary(lvalue, value.place); - } else { - context.visitOperand(value.place); - } - } else if (value.kind === 'PropertyLoad') { - if (lvalue !== null && !context.isUsedOutsideDeclaringScope(lvalue)) { - context.declareProperty(lvalue, value.object, value.property); - } else { - context.visitProperty(value.object, value.property); - } - } else if (value.kind === 'StoreLocal') { - context.visitOperand(value.value); - if (value.lvalue.kind === InstructionKind.Reassign) { - context.visitReassignment(value.lvalue.place); - } - context.declare(value.lvalue.place.identifier, { - id, - scope: context.currentScope, - }); - } else if ( - value.kind === 'DeclareLocal' || - value.kind === 'DeclareContext' - ) { - /* - * Some variables may be declared and never initialized. We need - * to retain (and hoist) these declarations if they are included - * in a reactive scope. One approach is to simply add all `DeclareLocal`s - * as scope declarations. - */ - - /* - * We add context variable declarations here, not at `StoreContext`, since - * context Store / Loads are modeled as reads and mutates to the underlying - * variable reference (instead of through intermediate / inlined temporaries) - */ - context.declare(value.lvalue.place.identifier, { - id, - scope: context.currentScope, - }); - } else if (value.kind === 'Destructure') { - context.visitOperand(value.value); - for (const place of eachPatternOperand(value.lvalue.pattern)) { - if (value.lvalue.kind === InstructionKind.Reassign) { - context.visitReassignment(place); - } - context.declare(place.identifier, { - id, - scope: context.currentScope, - }); - } - } else { - this.visitReactiveValue(context, id, value); - } - } - - enterTerminal(stmt: ReactiveTerminalStatement, context: Context): void { - if (stmt.label != null) { - context.pushLabeledBlock(stmt.label.id); - } - const terminal = stmt.terminal; - switch (terminal.kind) { - case 'continue': - case 'break': { - context.poisonState.addPoisonTarget( - terminal.target, - context.currentScope, - ); - break; - } - case 'throw': - case 'return': { - context.poisonState.addPoisonTarget(null, context.currentScope); - break; - } - } - } - exitTerminal(stmt: ReactiveTerminalStatement, context: Context): void { - if (stmt.label != null) { - context.popLabeledBlock(stmt.label.id); - } - } - - override visitTerminal( - stmt: ReactiveTerminalStatement, - context: Context, - ): void { - this.enterTerminal(stmt, context); - const terminal = stmt.terminal; - switch (terminal.kind) { - case 'break': - case 'continue': { - break; - } - case 'return': { - context.visitOperand(terminal.value); - break; - } - case 'throw': { - context.visitOperand(terminal.value); - break; - } - case 'for': { - this.visitReactiveValue(context, terminal.id, terminal.init); - this.visitReactiveValue(context, terminal.id, terminal.test); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - if (terminal.update !== null) { - this.visitReactiveValue(context, terminal.id, terminal.update); - } - }); - break; - } - case 'for-of': { - this.visitReactiveValue(context, terminal.id, terminal.init); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - }); - break; - } - case 'for-in': { - this.visitReactiveValue(context, terminal.id, terminal.init); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - }); - break; - } - case 'do-while': { - this.visitBlock(terminal.loop, context); - context.enterConditional(() => { - this.visitReactiveValue(context, terminal.id, terminal.test); - }); - break; - } - case 'while': { - this.visitReactiveValue(context, terminal.id, terminal.test); - context.enterConditional(() => { - this.visitBlock(terminal.loop, context); - }); - break; - } - case 'if': { - context.visitOperand(terminal.test); - const {consequent, alternate} = terminal; - /* - * Consequent and alternate branches are mutually exclusive, - * so we save and restore the poison state here. - */ - const prevPoisonState = context.poisonState.clone(); - const depsInIf = context.enterConditional(() => { - this.visitBlock(consequent, context); - }); - if (alternate !== null) { - const ifPoisonState = context.poisonState.take(prevPoisonState); - const depsInElse = context.enterConditional(() => { - this.visitBlock(alternate, context); - }); - context.poisonState.merge( - [ifPoisonState], - context.currentScope.value, - ); - context.promoteDepsFromExhaustiveConditionals([depsInIf, depsInElse]); - } - break; - } - case 'switch': { - context.visitOperand(terminal.test); - const isDefaultOnly = - terminal.cases.length === 1 && terminal.cases[0].test == null; - if (isDefaultOnly) { - const case_ = terminal.cases[0]; - if (case_.block != null) { - this.visitBlock(case_.block, context); - break; - } - } - const depsInCases = []; - let foundDefault = false; - /** - * Switch branches are mutually exclusive - */ - const prevPoisonState = context.poisonState.clone(); - const mutExPoisonStates: Array = []; - /* - * This can underestimate unconditional accesses due to the current - * CFG representation for fallthrough. This is safe. It only - * reduces granularity of dependencies. - */ - for (const {test, block} of terminal.cases) { - if (test !== null) { - context.visitOperand(test); - } else { - foundDefault = true; - } - if (block !== undefined) { - mutExPoisonStates.push( - context.poisonState.take(prevPoisonState.clone()), - ); - depsInCases.push( - context.enterConditional(() => { - this.visitBlock(block, context); - }), - ); - } - } - if (foundDefault) { - context.promoteDepsFromExhaustiveConditionals(depsInCases); - } - context.poisonState.merge( - mutExPoisonStates, - context.currentScope.value, - ); - break; - } - case 'label': { - this.visitBlock(terminal.block, context); - break; - } - case 'try': { - this.visitBlock(terminal.block, context); - this.visitBlock(terminal.handler, context); - break; - } - default: { - assertExhaustive( - terminal, - `Unexpected terminal kind \`${(terminal as any).kind}\``, - ); - } - } - this.exitTerminal(stmt, context); - } -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneHoistedContexts.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneHoistedContexts.ts index 1df211afc3ae4..07b099c2ea5fe 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneHoistedContexts.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneHoistedContexts.ts @@ -57,6 +57,17 @@ class Visitor extends ReactiveFunctionTransform { return {kind: 'remove'}; } + if ( + instruction.value.kind === 'DeclareContext' && + instruction.value.lvalue.kind === 'HoistedFunction' + ) { + state.set( + instruction.value.lvalue.place.identifier.declarationId, + InstructionKind.Function, + ); + return {kind: 'remove'}; + } + if ( instruction.value.kind === 'StoreContext' && state.has(instruction.value.lvalue.place.identifier.declarationId) diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneInitializationDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneInitializationDependencies.ts index 721fa7b0ec65d..2a9d0b9793d9f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneInitializationDependencies.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneInitializationDependencies.ts @@ -180,8 +180,8 @@ class Visitor extends ReactiveFunctionVisitor { [...scope.scope.dependencies].forEach(ident => { let target: undefined | IdentifierId = this.aliases.find(ident.identifier.id) ?? ident.identifier.id; - ident.path.forEach(key => { - target &&= this.paths.get(target)?.get(key); + ident.path.forEach(token => { + target &&= this.paths.get(target)?.get(token.property); }); if (target && this.map.get(target) === 'Create') { scope.scope.dependencies.delete(ident); diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneNonEscapingScopes.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneNonEscapingScopes.ts index b2e91fa302728..5a9aa6b2a7368 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneNonEscapingScopes.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneNonEscapingScopes.ts @@ -26,7 +26,7 @@ import { } from '../HIR'; import {getFunctionCallSignature} from '../Inference/InferReferenceEffects'; import {assertExhaustive, getOrInsertDefault} from '../Utils/utils'; -import {getPlaceScope} from './BuildReactiveBlocks'; +import {getPlaceScope} from '../HIR/HIR'; import { ReactiveFunctionTransform, ReactiveFunctionVisitor, @@ -671,12 +671,37 @@ function computeMemoizationInputs( ], }; } + case 'TaggedTemplateExpression': { + const signature = getFunctionCallSignature( + env, + value.tag.identifier.type, + ); + let lvalues = []; + if (lvalue !== null) { + lvalues.push({place: lvalue, level: MemoizationLevel.Memoized}); + } + if (signature?.noAlias === true) { + return { + lvalues, + rvalues: [], + }; + } + const operands = [...eachReactiveValueOperand(value)]; + lvalues.push( + ...operands + .filter(operand => isMutableEffect(operand.effect, operand.loc)) + .map(place => ({place, level: MemoizationLevel.Memoized})), + ); + return { + lvalues, + rvalues: operands, + }; + } case 'CallExpression': { const signature = getFunctionCallSignature( env, value.callee.identifier.type, ); - const operands = [...eachReactiveValueOperand(value)]; let lvalues = []; if (lvalue !== null) { lvalues.push({place: lvalue, level: MemoizationLevel.Memoized}); @@ -687,6 +712,7 @@ function computeMemoizationInputs( rvalues: [], }; } + const operands = [...eachReactiveValueOperand(value)]; lvalues.push( ...operands .filter(operand => isMutableEffect(operand.effect, operand.loc)) @@ -702,7 +728,6 @@ function computeMemoizationInputs( env, value.property.identifier.type, ); - const operands = [...eachReactiveValueOperand(value)]; let lvalues = []; if (lvalue !== null) { lvalues.push({place: lvalue, level: MemoizationLevel.Memoized}); @@ -713,6 +738,7 @@ function computeMemoizationInputs( rvalues: [], }; } + const operands = [...eachReactiveValueOperand(value)]; lvalues.push( ...operands .filter(operand => isMutableEffect(operand.effect, operand.loc)) @@ -726,7 +752,6 @@ function computeMemoizationInputs( case 'RegExpLiteral': case 'ObjectMethod': case 'FunctionExpression': - case 'TaggedTemplateExpression': case 'ArrayExpression': case 'NewExpression': case 'ObjectExpression': diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts index 55f67fc2f7d23..8841ae92795c8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/index.ts @@ -6,23 +6,17 @@ */ export {alignObjectMethodScopes} from './AlignObjectMethodScopes'; -export {alignReactiveScopesToBlockScopes} from './AlignReactiveScopesToBlockScopes'; export {assertScopeInstructionsWithinScopes} from './AssertScopeInstructionsWithinScope'; export {assertWellFormedBreakTargets} from './AssertWellFormedBreakTargets'; -export {buildReactiveBlocks} from './BuildReactiveBlocks'; export {buildReactiveFunction} from './BuildReactiveFunction'; export {codegenFunction, type CodegenFunction} from './CodegenReactiveFunction'; export {extractScopeDeclarationsFromDestructuring} from './ExtractScopeDeclarationsFromDestructuring'; -export {flattenReactiveLoops} from './FlattenReactiveLoops'; -export {flattenScopesWithHooksOrUse} from './FlattenScopesWithHooksOrUse'; export {inferReactiveScopeVariables} from './InferReactiveScopeVariables'; export {memoizeFbtAndMacroOperandsInSameScope} from './MemoizeFbtAndMacroOperandsInSameScope'; -export {mergeOverlappingReactiveScopes} from './MergeOverlappingReactiveScopes'; export {mergeReactiveScopesThatInvalidateTogether} from './MergeReactiveScopesThatInvalidateTogether'; export {printReactiveFunction} from './PrintReactiveFunction'; export {promoteUsedTemporaries} from './PromoteUsedTemporaries'; export {propagateEarlyReturns} from './PropagateEarlyReturns'; -export {propagateScopeDependencies} from './PropagateScopeDependencies'; export {pruneAllReactiveScopes} from './PruneAllReactiveScopes'; export {pruneHoistedContexts} from './PruneHoistedContexts'; export {pruneNonEscapingScopes} from './PruneNonEscapingScopes'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/SSA/EliminateRedundantPhi.ts b/compiler/packages/babel-plugin-react-compiler/src/SSA/EliminateRedundantPhi.ts index b5b0afb41236a..bae038f9bd9df 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/SSA/EliminateRedundantPhi.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/SSA/EliminateRedundantPhi.ts @@ -68,18 +68,13 @@ export function eliminateRedundantPhi( // Find any redundant phis phis: for (const phi of block.phis) { // Remap phis in case operands are from eliminated phis - phi.operands = new Map( - Array.from(phi.operands).map(([block, id]) => [ - block, - rewrites.get(id) ?? id, - ]), - ); + phi.operands.forEach((place, _) => rewritePlace(place, rewrites)); // Find if the phi can be eliminated let same: Identifier | null = null; for (const [_, operand] of phi.operands) { if ( - (same !== null && operand.id === same.id) || - operand.id === phi.id.id + (same !== null && operand.identifier.id === same.id) || + operand.identifier.id === phi.place.identifier.id ) { /* * This operand is the same as the phi or is the same as the @@ -94,7 +89,7 @@ export function eliminateRedundantPhi( continue phis; } else { // First non-phi operand - same = operand; + same = operand.identifier; } } CompilerError.invariant(same !== null, { @@ -103,7 +98,7 @@ export function eliminateRedundantPhi( loc: null, suggestions: null, }); - rewrites.set(phi.id, same); + rewrites.set(phi.place.identifier, same); block.phis.delete(phi); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts b/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts index 7d665514f4f6e..caba0d3c36992 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts @@ -18,7 +18,7 @@ import { Phi, Place, } from '../HIR/HIR'; -import {printIdentifier} from '../HIR/PrintHIR'; +import {printIdentifier, printPlace} from '../HIR/PrintHIR'; import { eachTerminalSuccessor, mapInstructionLValues, @@ -27,8 +27,8 @@ import { } from '../HIR/visitors'; type IncompletePhi = { - oldId: Identifier; - newId: Identifier; + oldPlace: Place; + newPlace: Place; }; type State = { @@ -122,33 +122,33 @@ class SSABuilder { } getPlace(oldPlace: Place): Place { - const newId = this.getIdAt(oldPlace.identifier, this.#current!.id); + const newId = this.getIdAt(oldPlace, this.#current!.id); return { ...oldPlace, identifier: newId, }; } - getIdAt(oldId: Identifier, blockId: BlockId): Identifier { + getIdAt(oldPlace: Place, blockId: BlockId): Identifier { // check if Place is defined locally const block = this.#blocks.get(blockId)!; const state = this.#states.get(block)!; - if (state.defs.has(oldId)) { - return state.defs.get(oldId)!; + if (state.defs.has(oldPlace.identifier)) { + return state.defs.get(oldPlace.identifier)!; } if (block.preds.size == 0) { /* * We're at the entry block and haven't found our defintion yet. * console.log( - * `Unable to find "${printIdentifier( - * oldId + * `Unable to find "${printPlace( + * oldPlace * )}" in bb${blockId}, assuming it's a global` * ); */ - this.#unknown.add(oldId); - return oldId; + this.#unknown.add(oldPlace.identifier); + return oldPlace.identifier; } if (this.unsealedPreds.get(block)! > 0) { @@ -156,53 +156,55 @@ class SSABuilder { * We haven't visited all our predecessors, let's place an incomplete phi * for now. */ - const newId = this.makeId(oldId); - state.incompletePhis.push({oldId, newId}); - state.defs.set(oldId, newId); + const newId = this.makeId(oldPlace.identifier); + state.incompletePhis.push({ + oldPlace, + newPlace: {...oldPlace, identifier: newId}, + }); + state.defs.set(oldPlace.identifier, newId); return newId; } // Only one predecessor, let's check there if (block.preds.size == 1) { const [pred] = block.preds; - const newId = this.getIdAt(oldId, pred); - state.defs.set(oldId, newId); + const newId = this.getIdAt(oldPlace, pred); + state.defs.set(oldPlace.identifier, newId); return newId; } // There are multiple predecessors, we may need a phi. - const newId = this.makeId(oldId); + const newId = this.makeId(oldPlace.identifier); /* * Adding a phi may loop back to our block if there is a loop in the CFG. We * update our defs before adding the phi to terminate the recursion rather than * looping infinitely. */ - state.defs.set(oldId, newId); - return this.addPhi(block, oldId, newId); + state.defs.set(oldPlace.identifier, newId); + return this.addPhi(block, oldPlace, {...oldPlace, identifier: newId}); } - addPhi(block: BasicBlock, oldId: Identifier, newId: Identifier): Identifier { - const predDefs: Map = new Map(); + addPhi(block: BasicBlock, oldPlace: Place, newPlace: Place): Identifier { + const predDefs: Map = new Map(); for (const predBlockId of block.preds) { - const predId = this.getIdAt(oldId, predBlockId); - predDefs.set(predBlockId, predId); + const predId = this.getIdAt(oldPlace, predBlockId); + predDefs.set(predBlockId, {...oldPlace, identifier: predId}); } const phi: Phi = { kind: 'Phi', - id: newId, + place: newPlace, operands: predDefs, - type: makeType(), }; block.phis.add(phi); - return newId; + return newPlace.identifier; } fixIncompletePhis(block: BasicBlock): void { const state = this.#states.get(block)!; for (const phi of state.incompletePhis) { - this.addPhi(block, phi.oldId, phi.newId); + this.addPhi(block, phi.oldPlace, phi.newPlace); } } @@ -224,9 +226,9 @@ class SSABuilder { for (const incompletePhi of state.incompletePhis) { text.push( - ` iphi \$${printIdentifier( - incompletePhi.newId, - )} = \$${printIdentifier(incompletePhi.oldId)}`, + ` iphi \$${printPlace( + incompletePhi.newPlace, + )} = \$${printPlace(incompletePhi.oldPlace)}`, ); } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/TypeInference/InferTypes.ts b/compiler/packages/babel-plugin-react-compiler/src/TypeInference/InferTypes.ts index 0b8949e1977ff..0270723c3e863 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/TypeInference/InferTypes.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/TypeInference/InferTypes.ts @@ -25,6 +25,7 @@ import { BuiltInArrayId, BuiltInFunctionId, BuiltInJsxId, + BuiltInMixedReadonlyId, BuiltInObjectId, BuiltInPropsId, BuiltInRefValueId, @@ -68,7 +69,7 @@ export function inferTypes(func: HIRFunction): void { function apply(func: HIRFunction, unifier: Unifier): void { for (const [_, block] of func.body.blocks) { for (const phi of block.phis) { - phi.type = unifier.get(phi.type); + phi.place.identifier.type = unifier.get(phi.place.identifier.type); } for (const instr of block.instructions) { for (const operand of eachInstructionLValue(instr)) { @@ -88,6 +89,7 @@ function apply(func: HIRFunction, unifier: Unifier): void { } } } + func.returnType = unifier.get(func.returnType); } type TypeEquation = { @@ -105,7 +107,7 @@ function equation(left: Type, right: Type): TypeEquation { function* generate( func: HIRFunction, ): Generator { - if (func.env.fnType === 'Component') { + if (func.fnType === 'Component') { const [props, ref] = func.params; if (props && props.kind === 'Identifier') { yield equation(props.identifier.type, { @@ -122,17 +124,30 @@ function* generate( } const names = new Map(); + const returnTypes: Array = []; for (const [_, block] of func.body.blocks) { for (const phi of block.phis) { - yield equation(phi.type, { + yield equation(phi.place.identifier.type, { kind: 'Phi', - operands: [...phi.operands.values()].map(id => id.type), + operands: [...phi.operands.values()].map(id => id.identifier.type), }); } for (const instr of block.instructions) { yield* generateInstructionTypes(func.env, names, instr); } + const terminal = block.terminal; + if (terminal.kind === 'return') { + returnTypes.push(terminal.value.identifier.type); + } + } + if (returnTypes.length > 1) { + yield equation(func.returnType, { + kind: 'Phi', + operands: returnTypes, + }); + } else if (returnTypes.length === 1) { + yield equation(func.returnType, returnTypes[0]!); } } @@ -227,7 +242,7 @@ function* generateInstructionTypes( } case 'LoadGlobal': { - const globalType = env.getGlobalDeclaration(value.binding); + const globalType = env.getGlobalDeclaration(value.binding, value.loc); if (globalType) { yield equation(left, globalType); } @@ -235,6 +250,7 @@ function* generateInstructionTypes( } case 'CallExpression': { + const returnType = makeType(); /* * TODO: callee could be a hook or a function, so this type equation isn't correct. * We should change Hook to a subtype of Function or change unifier logic. @@ -243,8 +259,25 @@ function* generateInstructionTypes( yield equation(value.callee.identifier.type, { kind: 'Function', shapeId: null, - return: left, + return: returnType, }); + yield equation(left, returnType); + break; + } + + case 'TaggedTemplateExpression': { + const returnType = makeType(); + /* + * TODO: callee could be a hook or a function, so this type equation isn't correct. + * We should change Hook to a subtype of Function or change unifier logic. + * (see https://github.com/facebook/react-forget/pull/1427) + */ + yield equation(value.tag.identifier.type, { + kind: 'Function', + shapeId: null, + return: returnType, + }); + yield equation(left, returnType); break; } @@ -346,7 +379,11 @@ function* generateInstructionTypes( case 'FunctionExpression': { yield* generate(value.loweredFunc.func); - yield equation(left, {kind: 'Object', shapeId: BuiltInFunctionId}); + yield equation(left, { + kind: 'Function', + shapeId: BuiltInFunctionId, + return: value.loweredFunc.func.returnType, + }); break; } @@ -373,7 +410,6 @@ function* generateInstructionTypes( case 'MetaProperty': case 'ComputedStore': case 'ComputedLoad': - case 'TaggedTemplateExpression': case 'Await': case 'GetIterator': case 'IteratorNext': @@ -465,30 +501,140 @@ class Unifier { } if (type.kind === 'Phi') { - const operands = new Set(type.operands.map(i => this.get(i).kind)); - - CompilerError.invariant(operands.size > 0, { + CompilerError.invariant(type.operands.length > 0, { reason: 'there should be at least one operand', description: null, loc: null, suggestions: null, }); - const kind = operands.values().next().value; - // there's only one unique type and it's not a type var - if (operands.size === 1 && kind !== 'Type') { - this.unify(v, type.operands[0]); + let candidateType: Type | null = null; + for (const operand of type.operands) { + const resolved = this.get(operand); + if (candidateType === null) { + candidateType = resolved; + } else if (!typeEquals(resolved, candidateType)) { + const unionType = tryUnionTypes(resolved, candidateType); + if (unionType === null) { + candidateType = null; + break; + } else { + candidateType = unionType; + } + } // else same type, continue + } + + if (candidateType !== null) { + this.unify(v, candidateType); return; } } if (this.occursCheck(v, type)) { + const resolvedType = this.tryResolveType(v, type); + if (resolvedType !== null) { + this.substitutions.set(v.id, resolvedType); + return; + } throw new Error('cycle detected'); } this.substitutions.set(v.id, type); } + tryResolveType(v: TypeVar, type: Type): Type | null { + switch (type.kind) { + case 'Phi': { + /** + * Resolve the type of the phi by recursively removing `v` as an operand. + * For example we can end up with types like this: + * + * v = Phi [ + * T1 + * T2 + * Phi [ + * T3 + * Phi [ + * T4 + * v <-- cycle! + * ] + * ] + * ] + * + * By recursively removing `v`, we end up with: + * + * v = Phi [ + * T1 + * T2 + * Phi [ + * T3 + * Phi [ + * T4 + * ] + * ] + * ] + * + * Which avoids the cycle + */ + const operands = []; + for (const operand of type.operands) { + if (operand.kind === 'Type' && operand.id === v.id) { + continue; + } + const resolved = this.tryResolveType(v, operand); + if (resolved === null) { + return null; + } + operands.push(resolved); + } + return {kind: 'Phi', operands}; + } + case 'Type': { + const substitution = this.get(type); + if (substitution !== type) { + const resolved = this.tryResolveType(v, substitution); + if (resolved !== null) { + this.substitutions.set(type.id, resolved); + } + return resolved; + } + return type; + } + case 'Property': { + const objectType = this.tryResolveType(v, this.get(type.objectType)); + if (objectType === null) { + return null; + } + return { + kind: 'Property', + objectName: type.objectName, + objectType, + propertyName: type.propertyName, + }; + } + case 'Function': { + const returnType = this.tryResolveType(v, this.get(type.return)); + if (returnType === null) { + return null; + } + return { + kind: 'Function', + return: returnType, + shapeId: type.shapeId, + }; + } + case 'ObjectMethod': + case 'Object': + case 'Primitive': + case 'Poly': { + return type; + } + default: { + assertExhaustive(type, `Unexpected type kind '${(type as any).kind}'`); + } + } + } + occursCheck(v: TypeVar, type: Type): boolean { if (typeEquals(v, type)) return true; @@ -527,3 +673,39 @@ const RefLikeNameRE = /^(?:[a-zA-Z$_][a-zA-Z$_0-9]*)Ref$|^ref$/; function isRefLikeName(t: PropType): boolean { return RefLikeNameRE.test(t.objectName) && t.propertyName === 'current'; } + +function tryUnionTypes(ty1: Type, ty2: Type): Type | null { + let readonlyType: Type; + let otherType: Type; + if (ty1.kind === 'Object' && ty1.shapeId === BuiltInMixedReadonlyId) { + readonlyType = ty1; + otherType = ty2; + } else if (ty2.kind === 'Object' && ty2.shapeId === BuiltInMixedReadonlyId) { + readonlyType = ty2; + otherType = ty1; + } else { + return null; + } + if (otherType.kind === 'Primitive') { + /** + * Union(Primitive | MixedReadonly) = MixedReadonly + * + * For example, `data ?? null` could return `data`, the fact that RHS + * is a primitive doesn't guarantee the result is a primitive. + */ + return readonlyType; + } else if ( + otherType.kind === 'Object' && + otherType.shapeId === BuiltInArrayId + ) { + /** + * Union(Array | MixedReadonly) = Array + * + * In practice this pattern means the result is always an array. Given + * that this behavior requires opting-in to the mixedreadonly type + * (via moduleTypeProvider) this seems like a reasonable heuristic. + */ + return otherType; + } + return null; +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/TypeInference/PropagatePhiTypes.ts b/compiler/packages/babel-plugin-react-compiler/src/TypeInference/PropagatePhiTypes.ts index 81335c175a31b..0369945525bcf 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/TypeInference/PropagatePhiTypes.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/TypeInference/PropagatePhiTypes.ts @@ -62,22 +62,24 @@ export function propagatePhiTypes(fn: HIRFunction): void { * We also don't propagate scopes for named variables, to preserve compatibility * with previous LeaveSSA behavior. */ - if (phi.id.type.kind !== 'Type' || phi.id.name !== null) { + if ( + phi.place.identifier.type.kind !== 'Type' || + phi.place.identifier.name !== null + ) { continue; } let type: Type | null = null; for (const [, operand] of phi.operands) { if (type === null) { - type = operand.type; - } else if (!typeEquals(type, operand.type)) { + type = operand.identifier.type; + } else if (!typeEquals(type, operand.identifier.type)) { type = null; break; } } if (type !== null) { - phi.id.type = type; - phi.type = type; - propagated.add(phi.id.id); + phi.place.identifier.type = type; + propagated.add(phi.place.identifier.id); } } for (const instr of block.instructions) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Utils/utils.ts b/compiler/packages/babel-plugin-react-compiler/src/Utils/utils.ts index 6e07e14a8d0e6..aa91c48b1b0db 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Utils/utils.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Utils/utils.ts @@ -82,17 +82,45 @@ export function getOrInsertDefault( return defaultValue; } } - -export function Set_union(a: Set, b: Set): Set { - const union = new Set(); +export function Set_equal(a: ReadonlySet, b: ReadonlySet): boolean { + if (a.size !== b.size) { + return false; + } for (const item of a) { - if (b.has(item)) { - union.add(item); + if (!b.has(item)) { + return false; } } + return true; +} + +export function Set_union(a: ReadonlySet, b: ReadonlySet): Set { + const union = new Set(a); + for (const item of b) { + union.add(item); + } return union; } +export function Set_intersect(sets: Array>): Set { + if (sets.length === 0 || sets.some(s => s.size === 0)) { + return new Set(); + } else if (sets.length === 1) { + return new Set(sets[0]); + } + const result: Set = new Set(); + const first = sets[0]; + outer: for (const e of first) { + for (let i = 1; i < sets.length; i++) { + if (!sets[i].has(e)) { + continue outer; + } + } + result.add(e); + } + return result; +} + export function Iterable_some( iter: Iterable, pred: (item: T) => boolean, @@ -111,6 +139,19 @@ export function nonNull, U>( return value != null; } +export function Set_filter( + source: ReadonlySet, + fn: (arg: T) => boolean, +): Set { + const result = new Set(); + for (const entry of source) { + if (fn(entry)) { + result.add(entry); + } + } + return result; +} + export function hasNode( input: NodePath, ): input is NodePath> { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateHooksUsage.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateHooksUsage.ts index 74f6346b3a37b..53640da5020bd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateHooksUsage.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateHooksUsage.ts @@ -198,11 +198,12 @@ export function validateHooksUsage(fn: HIRFunction): void { for (const [, block] of fn.body.blocks) { for (const phi of block.phis) { let kind: Kind = - phi.id.name !== null && isHookName(phi.id.name.value) + phi.place.identifier.name !== null && + isHookName(phi.place.identifier.name.value) ? Kind.PotentialHook : Kind.Local; for (const [, operand] of phi.operands) { - const operandKind = valueKinds.get(operand.id); + const operandKind = valueKinds.get(operand.identifier.id); /* * NOTE: we currently skip operands whose value is unknown * (which can only occur for functions with loops), we may @@ -213,7 +214,7 @@ export function validateHooksUsage(fn: HIRFunction): void { kind = joinKinds(kind, operandKind); } } - valueKinds.set(phi.id.id, kind); + valueKinds.set(phi.place.identifier.id, kind); } for (const instr of block.instructions) { switch (instr.value.kind) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateLocalsNotReassignedAfterRender.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateLocalsNotReassignedAfterRender.ts index 0ea1814349f7f..9c41ebcae19f6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateLocalsNotReassignedAfterRender.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateLocalsNotReassignedAfterRender.ts @@ -161,6 +161,14 @@ function getContextReassignment( if (signature?.noAlias) { operands = [value.receiver, value.property]; } + } else if (value.kind === 'TaggedTemplateExpression') { + const signature = getFunctionCallSignature( + fn.env, + value.tag.identifier.type, + ); + if (signature?.noAlias) { + operands = [value.tag]; + } } for (const operand of operands) { CompilerError.invariant(operand.effect !== Effect.Unknown, { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoJSXInTryStatement.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoJSXInTryStatement.ts new file mode 100644 index 0000000000000..b92a89d764301 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoJSXInTryStatement.ts @@ -0,0 +1,52 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {CompilerError, ErrorSeverity} from '..'; +import {BlockId, HIRFunction} from '../HIR'; +import {retainWhere} from '../Utils/utils'; + +/** + * Developers may not be aware of error boundaries and lazy evaluation of JSX, leading them + * to use patterns such as `let el; try { el = } catch { ... }` to attempt to + * catch rendering errors. Such code will fail to catch errors in rendering, but developers + * may not realize this right away. + * + * This validation pass validates against this pattern: specifically, it errors for JSX + * created within a try block. JSX is allowed within a catch statement, unless that catch + * is itself nested inside an outer try. + */ +export function validateNoJSXInTryStatement(fn: HIRFunction): void { + const activeTryBlocks: Array = []; + const errors = new CompilerError(); + for (const [, block] of fn.body.blocks) { + retainWhere(activeTryBlocks, id => id !== block.id); + + if (activeTryBlocks.length !== 0) { + for (const instr of block.instructions) { + const {value} = instr; + switch (value.kind) { + case 'JsxExpression': + case 'JsxFragment': { + errors.push({ + severity: ErrorSeverity.InvalidReact, + reason: `Unexpected JSX element within a try statement. To catch errors in rendering a given component, wrap that component in an error boundary. (https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary)`, + loc: value.loc, + }); + break; + } + } + } + } + + if (block.terminal.kind === 'try') { + activeTryBlocks.push(block.terminal.handler); + } + } + if (errors.hasErrors()) { + throw errors; + } +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccesInRender.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccesInRender.ts index b4fb2a618ada4..b361b2016a1dd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccesInRender.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccesInRender.ts @@ -7,20 +7,22 @@ import {CompilerError, ErrorSeverity} from '../CompilerError'; import { + BlockId, HIRFunction, IdentifierId, Place, SourceLocation, + getHookKindForType, isRefValueType, isUseRefType, } from '../HIR'; -import {printPlace} from '../HIR/PrintHIR'; import { + eachInstructionOperand, eachInstructionValueOperand, + eachPatternOperand, eachTerminalOperand, } from '../HIR/visitors'; import {Err, Ok, Result} from '../Utils/Result'; -import {isEffectHook} from './ValidateMemoizedEffectDependencies'; /** * Validates that a function does not access a ref value during render. This includes a partial check @@ -42,183 +44,570 @@ import {isEffectHook} from './ValidateMemoizedEffectDependencies'; * In the future we may reject more cases, based on either object names (`fooRef.current` is likely a ref) * or based on property name alone (`foo.current` might be a ref). */ + +const opaqueRefId = Symbol(); +type RefId = number & {[opaqueRefId]: 'RefId'}; + +function makeRefId(id: number): RefId { + CompilerError.invariant(id >= 0 && Number.isInteger(id), { + reason: 'Expected identifier id to be a non-negative integer', + description: null, + loc: null, + suggestions: null, + }); + return id as RefId; +} +let _refId = 0; +function nextRefId(): RefId { + return makeRefId(_refId++); +} + +type RefAccessType = + | {kind: 'None'} + | {kind: 'Nullable'} + | {kind: 'Guard'; refId: RefId} + | RefAccessRefType; + +type RefAccessRefType = + | {kind: 'Ref'; refId: RefId} + | {kind: 'RefValue'; loc?: SourceLocation; refId?: RefId} + | {kind: 'Structure'; value: null | RefAccessRefType; fn: null | RefFnType}; + +type RefFnType = {readRefEffect: boolean; returnType: RefAccessType}; + +class Env extends Map { + #changed = false; + + resetChanged(): void { + this.#changed = false; + } + + hasChanged(): boolean { + return this.#changed; + } + + override set(key: IdentifierId, value: RefAccessType): this { + const cur = this.get(key); + const widenedValue = joinRefAccessTypes(value, cur ?? {kind: 'None'}); + if ( + !(cur == null && widenedValue.kind === 'None') && + (cur == null || !tyEqual(cur, widenedValue)) + ) { + this.#changed = true; + } + return super.set(key, widenedValue); + } +} + export function validateNoRefAccessInRender(fn: HIRFunction): void { - const refAccessingFunctions: Set = new Set(); - validateNoRefAccessInRenderImpl(fn, refAccessingFunctions).unwrap(); + const env = new Env(); + validateNoRefAccessInRenderImpl(fn, env).unwrap(); +} + +function refTypeOfType(place: Place): RefAccessType { + if (isRefValueType(place.identifier)) { + return {kind: 'RefValue'}; + } else if (isUseRefType(place.identifier)) { + return {kind: 'Ref', refId: nextRefId()}; + } else { + return {kind: 'None'}; + } +} + +function tyEqual(a: RefAccessType, b: RefAccessType): boolean { + if (a.kind !== b.kind) { + return false; + } + switch (a.kind) { + case 'None': + return true; + case 'Ref': + return true; + case 'Nullable': + return true; + case 'Guard': + CompilerError.invariant(b.kind === 'Guard', { + reason: 'Expected ref value', + loc: null, + }); + return a.refId === b.refId; + case 'RefValue': + CompilerError.invariant(b.kind === 'RefValue', { + reason: 'Expected ref value', + loc: null, + }); + return a.loc == b.loc; + case 'Structure': { + CompilerError.invariant(b.kind === 'Structure', { + reason: 'Expected structure', + loc: null, + }); + const fnTypesEqual = + (a.fn === null && b.fn === null) || + (a.fn !== null && + b.fn !== null && + a.fn.readRefEffect === b.fn.readRefEffect && + tyEqual(a.fn.returnType, b.fn.returnType)); + return ( + fnTypesEqual && + (a.value === b.value || + (a.value !== null && b.value !== null && tyEqual(a.value, b.value))) + ); + } + } +} + +function joinRefAccessTypes(...types: Array): RefAccessType { + function joinRefAccessRefTypes( + a: RefAccessRefType, + b: RefAccessRefType, + ): RefAccessRefType { + if (a.kind === 'RefValue') { + if (b.kind === 'RefValue' && a.refId === b.refId) { + return a; + } + return {kind: 'RefValue'}; + } else if (b.kind === 'RefValue') { + return b; + } else if (a.kind === 'Ref' || b.kind === 'Ref') { + if (a.kind === 'Ref' && b.kind === 'Ref' && a.refId === b.refId) { + return a; + } + return {kind: 'Ref', refId: nextRefId()}; + } else { + CompilerError.invariant( + a.kind === 'Structure' && b.kind === 'Structure', + { + reason: 'Expected structure', + loc: null, + }, + ); + const fn = + a.fn === null + ? b.fn + : b.fn === null + ? a.fn + : { + readRefEffect: a.fn.readRefEffect || b.fn.readRefEffect, + returnType: joinRefAccessTypes( + a.fn.returnType, + b.fn.returnType, + ), + }; + const value = + a.value === null + ? b.value + : b.value === null + ? a.value + : joinRefAccessRefTypes(a.value, b.value); + return { + kind: 'Structure', + fn, + value, + }; + } + } + + return types.reduce( + (a, b) => { + if (a.kind === 'None') { + return b; + } else if (b.kind === 'None') { + return a; + } else if (a.kind === 'Guard') { + if (b.kind === 'Guard' && a.refId === b.refId) { + return a; + } else if (b.kind === 'Nullable' || b.kind === 'Guard') { + return {kind: 'None'}; + } else { + return b; + } + } else if (b.kind === 'Guard') { + if (a.kind === 'Nullable') { + return {kind: 'None'}; + } else { + return b; + } + } else if (a.kind === 'Nullable') { + return b; + } else if (b.kind === 'Nullable') { + return a; + } else { + return joinRefAccessRefTypes(a, b); + } + }, + {kind: 'None'}, + ); } function validateNoRefAccessInRenderImpl( fn: HIRFunction, - refAccessingFunctions: Set, -): Result { - const errors = new CompilerError(); - for (const [, block] of fn.body.blocks) { - for (const instr of block.instructions) { - switch (instr.value.kind) { - case 'JsxExpression': - case 'JsxFragment': { - for (const operand of eachInstructionValueOperand(instr.value)) { - if (isRefValueType(operand.identifier)) { - errors.push({ - severity: ErrorSeverity.InvalidReact, - reason: - 'Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)', - loc: operand.loc, - description: `Cannot access ref value at ${printPlace( - operand, - )}`, - suggestions: null, - }); + env: Env, +): Result { + let returnValues: Array = []; + let place; + for (const param of fn.params) { + if (param.kind === 'Identifier') { + place = param; + } else { + place = param.place; + } + const type = refTypeOfType(place); + env.set(place.identifier.id, type); + } + + for (let i = 0; (i == 0 || env.hasChanged()) && i < 10; i++) { + env.resetChanged(); + returnValues = []; + const safeBlocks = new Map(); + const errors = new CompilerError(); + for (const [, block] of fn.body.blocks) { + for (const phi of block.phis) { + env.set( + phi.place.identifier.id, + joinRefAccessTypes( + ...Array(...phi.operands.values()).map( + operand => + env.get(operand.identifier.id) ?? ({kind: 'None'} as const), + ), + ), + ); + } + + for (const instr of block.instructions) { + switch (instr.value.kind) { + case 'JsxExpression': + case 'JsxFragment': { + for (const operand of eachInstructionValueOperand(instr.value)) { + validateNoDirectRefValueAccess(errors, operand, env); } + break; } - break; - } - case 'PropertyLoad': { - break; - } - case 'LoadLocal': { - if (refAccessingFunctions.has(instr.value.place.identifier.id)) { - refAccessingFunctions.add(instr.lvalue.identifier.id); + case 'ComputedLoad': + case 'PropertyLoad': { + if (typeof instr.value.property !== 'string') { + validateNoDirectRefValueAccess(errors, instr.value.property, env); + } + const objType = env.get(instr.value.object.identifier.id); + let lookupType: null | RefAccessType = null; + if (objType?.kind === 'Structure') { + lookupType = objType.value; + } else if (objType?.kind === 'Ref') { + lookupType = { + kind: 'RefValue', + loc: instr.loc, + refId: objType.refId, + }; + } + env.set( + instr.lvalue.identifier.id, + lookupType ?? refTypeOfType(instr.lvalue), + ); + break; } - break; - } - case 'StoreLocal': { - if (refAccessingFunctions.has(instr.value.value.identifier.id)) { - refAccessingFunctions.add(instr.value.lvalue.place.identifier.id); - refAccessingFunctions.add(instr.lvalue.identifier.id); + case 'LoadContext': + case 'LoadLocal': { + env.set( + instr.lvalue.identifier.id, + env.get(instr.value.place.identifier.id) ?? + refTypeOfType(instr.lvalue), + ); + break; } - break; - } - case 'ObjectMethod': - case 'FunctionExpression': { - if ( - /* - * check if the function expression accesses a ref *or* some other - * function which accesses a ref - */ - [...eachInstructionValueOperand(instr.value)].some( - operand => - isRefValueType(operand.identifier) || - refAccessingFunctions.has(operand.identifier.id), - ) || - // check for cases where .current is accessed through an aliased ref - ([...eachInstructionValueOperand(instr.value)].some(operand => - isUseRefType(operand.identifier), - ) && - validateNoRefAccessInRenderImpl( - instr.value.loweredFunc.func, - refAccessingFunctions, - ).isErr()) - ) { - // This function expression unconditionally accesses a ref - refAccessingFunctions.add(instr.lvalue.identifier.id); + case 'StoreContext': + case 'StoreLocal': { + env.set( + instr.value.lvalue.place.identifier.id, + env.get(instr.value.value.identifier.id) ?? + refTypeOfType(instr.value.lvalue.place), + ); + env.set( + instr.lvalue.identifier.id, + env.get(instr.value.value.identifier.id) ?? + refTypeOfType(instr.lvalue), + ); + break; } - break; - } - case 'MethodCall': { - if (!isEffectHook(instr.value.property.identifier)) { + case 'Destructure': { + const objType = env.get(instr.value.value.identifier.id); + let lookupType = null; + if (objType?.kind === 'Structure') { + lookupType = objType.value; + } + env.set( + instr.lvalue.identifier.id, + lookupType ?? refTypeOfType(instr.lvalue), + ); + for (const lval of eachPatternOperand(instr.value.lvalue.pattern)) { + env.set(lval.identifier.id, lookupType ?? refTypeOfType(lval)); + } + break; + } + case 'ObjectMethod': + case 'FunctionExpression': { + let returnType: RefAccessType = {kind: 'None'}; + let readRefEffect = false; + const result = validateNoRefAccessInRenderImpl( + instr.value.loweredFunc.func, + env, + ); + if (result.isOk()) { + returnType = result.unwrap(); + } else if (result.isErr()) { + readRefEffect = true; + } + env.set(instr.lvalue.identifier.id, { + kind: 'Structure', + fn: { + readRefEffect, + returnType, + }, + value: null, + }); + break; + } + case 'MethodCall': + case 'CallExpression': { + const callee = + instr.value.kind === 'CallExpression' + ? instr.value.callee + : instr.value.property; + const hookKind = getHookKindForType(fn.env, callee.identifier.type); + let returnType: RefAccessType = {kind: 'None'}; + const fnType = env.get(callee.identifier.id); + if (fnType?.kind === 'Structure' && fnType.fn !== null) { + returnType = fnType.fn.returnType; + if (fnType.fn.readRefEffect) { + errors.push({ + severity: ErrorSeverity.InvalidReact, + reason: + 'This function accesses a ref value (the `current` property), which may not be accessed during render. (https://react.dev/reference/react/useRef)', + loc: callee.loc, + description: + callee.identifier.name !== null && + callee.identifier.name.kind === 'named' + ? `Function \`${callee.identifier.name.value}\` accesses a ref` + : null, + suggestions: null, + }); + } + } for (const operand of eachInstructionValueOperand(instr.value)) { - validateNoRefAccess( - errors, - refAccessingFunctions, - operand, - operand.loc, - ); + if (hookKind != null) { + validateNoDirectRefValueAccess(errors, operand, env); + } else { + validateNoRefAccess(errors, env, operand, operand.loc); + } } + env.set(instr.lvalue.identifier.id, returnType); + break; } - break; - } - case 'CallExpression': { - const callee = instr.value.callee; - const isUseEffect = isEffectHook(callee.identifier); - if (!isUseEffect) { - // Report a more precise error when calling a local function that accesses a ref - if (refAccessingFunctions.has(callee.identifier.id)) { - errors.push({ - severity: ErrorSeverity.InvalidReact, - reason: - 'This function accesses a ref value (the `current` property), which may not be accessed during render. (https://react.dev/reference/react/useRef)', - loc: callee.loc, - description: `Function ${printPlace(callee)} accesses a ref`, - suggestions: null, + case 'ObjectExpression': + case 'ArrayExpression': { + const types: Array = []; + for (const operand of eachInstructionValueOperand(instr.value)) { + validateNoDirectRefValueAccess(errors, operand, env); + types.push(env.get(operand.identifier.id) ?? {kind: 'None'}); + } + const value = joinRefAccessTypes(...types); + if ( + value.kind === 'None' || + value.kind === 'Guard' || + value.kind === 'Nullable' + ) { + env.set(instr.lvalue.identifier.id, {kind: 'None'}); + } else { + env.set(instr.lvalue.identifier.id, { + kind: 'Structure', + value, + fn: null, }); } + break; + } + case 'PropertyDelete': + case 'PropertyStore': + case 'ComputedDelete': + case 'ComputedStore': { + const safe = safeBlocks.get(block.id); + const target = env.get(instr.value.object.identifier.id); + if ( + instr.value.kind === 'PropertyStore' && + safe != null && + target?.kind === 'Ref' && + target.refId === safe + ) { + safeBlocks.delete(block.id); + } else { + validateNoRefAccess(errors, env, instr.value.object, instr.loc); + } for (const operand of eachInstructionValueOperand(instr.value)) { - validateNoRefAccess( - errors, - refAccessingFunctions, - operand, - operand.loc, - ); + if (operand === instr.value.object) { + continue; + } + validateNoRefValueAccess(errors, env, operand); } + break; } - break; - } - case 'ObjectExpression': - case 'ArrayExpression': { - for (const operand of eachInstructionValueOperand(instr.value)) { - validateNoRefAccess( - errors, - refAccessingFunctions, - operand, - operand.loc, - ); + case 'StartMemoize': + case 'FinishMemoize': + break; + case 'Primitive': { + if (instr.value.value == null) { + env.set(instr.lvalue.identifier.id, {kind: 'Nullable'}); + } + break; } - break; - } - case 'PropertyDelete': - case 'PropertyStore': - case 'ComputedDelete': - case 'ComputedStore': { - validateNoRefAccess( - errors, - refAccessingFunctions, - instr.value.object, - instr.loc, - ); - for (const operand of eachInstructionValueOperand(instr.value)) { - if (operand === instr.value.object) { - continue; + case 'BinaryExpression': { + const left = env.get(instr.value.left.identifier.id); + const right = env.get(instr.value.right.identifier.id); + let nullish: boolean = false; + let refId: RefId | null = null; + if (left?.kind === 'RefValue' && left.refId != null) { + refId = left.refId; + } else if (right?.kind === 'RefValue' && right.refId != null) { + refId = right.refId; + } + + if (left?.kind === 'Nullable') { + nullish = true; + } else if (right?.kind === 'Nullable') { + nullish = true; } - validateNoRefValueAccess(errors, refAccessingFunctions, operand); + + if (refId !== null && nullish) { + env.set(instr.lvalue.identifier.id, {kind: 'Guard', refId}); + } else { + for (const operand of eachInstructionValueOperand(instr.value)) { + validateNoRefValueAccess(errors, env, operand); + } + } + break; + } + default: { + for (const operand of eachInstructionValueOperand(instr.value)) { + validateNoRefValueAccess(errors, env, operand); + } + break; } - break; } - default: { - for (const operand of eachInstructionValueOperand(instr.value)) { - validateNoRefValueAccess(errors, refAccessingFunctions, operand); + + // Guard values are derived from ref.current, so they can only be used in if statement targets + for (const operand of eachInstructionOperand(instr)) { + guardCheck(errors, operand, env); + } + + if ( + isUseRefType(instr.lvalue.identifier) && + env.get(instr.lvalue.identifier.id)?.kind !== 'Ref' + ) { + env.set( + instr.lvalue.identifier.id, + joinRefAccessTypes( + env.get(instr.lvalue.identifier.id) ?? {kind: 'None'}, + {kind: 'Ref', refId: nextRefId()}, + ), + ); + } + if ( + isRefValueType(instr.lvalue.identifier) && + env.get(instr.lvalue.identifier.id)?.kind !== 'RefValue' + ) { + env.set( + instr.lvalue.identifier.id, + joinRefAccessTypes( + env.get(instr.lvalue.identifier.id) ?? {kind: 'None'}, + {kind: 'RefValue', loc: instr.loc}, + ), + ); + } + } + + if (block.terminal.kind === 'if') { + const test = env.get(block.terminal.test.identifier.id); + if (test?.kind === 'Guard') { + safeBlocks.set(block.terminal.consequent, test.refId); + } + } + + for (const operand of eachTerminalOperand(block.terminal)) { + if (block.terminal.kind !== 'return') { + validateNoRefValueAccess(errors, env, operand); + if (block.terminal.kind !== 'if') { + guardCheck(errors, operand, env); } - break; + } else { + // Allow functions containing refs to be returned, but not direct ref values + validateNoDirectRefValueAccess(errors, operand, env); + guardCheck(errors, operand, env); + returnValues.push(env.get(operand.identifier.id)); } } } - for (const operand of eachTerminalOperand(block.terminal)) { - validateNoRefValueAccess(errors, refAccessingFunctions, operand); + + if (errors.hasErrors()) { + return Err(errors); } } - if (errors.hasErrors()) { - return Err(errors); - } else { - return Ok(undefined); + CompilerError.invariant(!env.hasChanged(), { + reason: 'Ref type environment did not converge', + loc: null, + }); + + return Ok( + joinRefAccessTypes( + ...returnValues.filter((env): env is RefAccessType => env !== undefined), + ), + ); +} + +function destructure( + type: RefAccessType | undefined, +): RefAccessType | undefined { + if (type?.kind === 'Structure' && type.value !== null) { + return destructure(type.value); + } + return type; +} + +function guardCheck(errors: CompilerError, operand: Place, env: Env): void { + if (env.get(operand.identifier.id)?.kind === 'Guard') { + errors.push({ + severity: ErrorSeverity.InvalidReact, + reason: + 'Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)', + loc: operand.loc, + description: + operand.identifier.name !== null && + operand.identifier.name.kind === 'named' + ? `Cannot access ref value \`${operand.identifier.name.value}\`` + : null, + suggestions: null, + }); } } function validateNoRefValueAccess( errors: CompilerError, - refAccessingFunctions: Set, + env: Env, operand: Place, ): void { + const type = destructure(env.get(operand.identifier.id)); if ( - isRefValueType(operand.identifier) || - refAccessingFunctions.has(operand.identifier.id) + type?.kind === 'RefValue' || + (type?.kind === 'Structure' && type.fn?.readRefEffect) ) { errors.push({ severity: ErrorSeverity.InvalidReact, reason: 'Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)', - loc: operand.loc, - description: `Cannot access ref value at ${printPlace(operand)}`, + loc: (type.kind === 'RefValue' && type.loc) || operand.loc, + description: + operand.identifier.name !== null && + operand.identifier.name.kind === 'named' + ? `Cannot access ref value \`${operand.identifier.name.value}\`` + : null, suggestions: null, }); } @@ -226,20 +615,43 @@ function validateNoRefValueAccess( function validateNoRefAccess( errors: CompilerError, - refAccessingFunctions: Set, + env: Env, operand: Place, loc: SourceLocation, ): void { + const type = destructure(env.get(operand.identifier.id)); if ( - isRefValueType(operand.identifier) || - isUseRefType(operand.identifier) || - refAccessingFunctions.has(operand.identifier.id) + type?.kind === 'Ref' || + type?.kind === 'RefValue' || + (type?.kind === 'Structure' && type.fn?.readRefEffect) ) { errors.push({ severity: ErrorSeverity.InvalidReact, reason: 'Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)', - loc: loc, + loc: (type.kind === 'RefValue' && type.loc) || loc, + description: + operand.identifier.name !== null && + operand.identifier.name.kind === 'named' + ? `Cannot access ref value \`${operand.identifier.name.value}\`` + : null, + suggestions: null, + }); + } +} + +function validateNoDirectRefValueAccess( + errors: CompilerError, + operand: Place, + env: Env, +): void { + const type = destructure(env.get(operand.identifier.id)); + if (type?.kind === 'RefValue') { + errors.push({ + severity: ErrorSeverity.InvalidReact, + reason: + 'Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)', + loc: type.loc ?? operand.loc, description: operand.identifier.name !== null && operand.identifier.name.kind === 'named' diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts index 50d1c986cea5a..e7615320c7b95 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts @@ -23,7 +23,7 @@ import { ScopeId, SourceLocation, } from '../HIR'; -import {printManualMemoDependency} from '../HIR/PrintHIR'; +import {printIdentifier, printManualMemoDependency} from '../HIR/PrintHIR'; import {eachInstructionValueOperand} from '../HIR/visitors'; import {collectMaybeMemoDependencies} from '../Inference/DropManualMemoization'; import { @@ -116,7 +116,7 @@ function prettyPrintScopeDependency(val: ReactiveScopeDependency): string { } else { rootStr = '[unnamed]'; } - return `${rootStr}${val.path.length > 0 ? '.' : ''}${val.path.join('.')}`; + return `${rootStr}${val.path.map(v => `${v.optional ? '?.' : '.'}${v.property}`).join('')}`; } enum CompareDependencyResult { @@ -167,9 +167,16 @@ function compareDeps( let isSubpath = true; for (let i = 0; i < Math.min(inferred.path.length, source.path.length); i++) { - if (inferred.path[i] !== source.path[i]) { + if (inferred.path[i].property !== source.path[i].property) { isSubpath = false; break; + } else if (inferred.path[i].optional !== source.path[i].optional) { + /** + * The inferred path must be at least as precise as the manual path: + * if the inferred path is optional, then the source path must have + * been optional too. + */ + return CompareDependencyResult.PathDifference; } } @@ -177,14 +184,14 @@ function compareDeps( isSubpath && (source.path.length === inferred.path.length || (inferred.path.length >= source.path.length && - !inferred.path.includes('current'))) + !inferred.path.some(token => token.property === 'current'))) ) { return CompareDependencyResult.Ok; } else { if (isSubpath) { if ( - source.path.includes('current') || - inferred.path.includes('current') + source.path.some(token => token.property === 'current') || + inferred.path.some(token => token.property === 'current') ) { return CompareDependencyResult.RefAccessDifference; } else { @@ -339,7 +346,11 @@ class Visitor extends ReactiveFunctionVisitor { return null; } default: { - const dep = collectMaybeMemoDependencies(value, this.temporaries); + const dep = collectMaybeMemoDependencies( + value, + this.temporaries, + false, + ); if (value.kind === 'StoreLocal' || value.kind === 'StoreContext') { const storeTarget = value.lvalue.place; state.manualMemoState?.decls.add( @@ -537,7 +548,9 @@ class Visitor extends ReactiveFunctionVisitor { state.errors.push({ reason: 'React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This value was memoized in source but not in compilation output.', - description: null, + description: DEBUG + ? `${printIdentifier(identifier)} was not memoized` + : null, severity: ErrorSeverity.CannotPreserveMemoization, loc, suggestions: null, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/alias-capture-in-method-receiver-and-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/alias-capture-in-method-receiver-and-mutate.expect.md index ee8e4f0d3670a..0b03ac9978951 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/alias-capture-in-method-receiver-and-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/alias-capture-in-method-receiver-and-mutate.expect.md @@ -57,4 +57,4 @@ export const FIXTURE_ENTRYPOINT = { ``` ### Eval output -(kind: ok) [[{"a":0,"b":"value1","c":true}],"[[ cyclic ref *2 ]]"] \ No newline at end of file +(kind: ok) [[{"a":0,"b":"value1","c":true},"joe"],"[[ cyclic ref *2 ]]"] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/align-scopes-reactive-scope-overlaps-try.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/align-scopes-reactive-scope-overlaps-try.expect.md index 6d55ba736486a..1bc6b9b51ebf6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/align-scopes-reactive-scope-overlaps-try.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/align-scopes-reactive-scope-overlaps-try.expect.md @@ -60,6 +60,6 @@ export const FIXTURE_ENTRYPOINT = { ``` ### Eval output -(kind: ok) [2] -[2] -[3] \ No newline at end of file +(kind: ok) [2,"joe"] +[2,"joe"] +[3,"joe"] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/align-scopes-within-nested-valueblock-in-array.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/align-scopes-within-nested-valueblock-in-array.expect.md index d59fb182c3cdb..97b3bb13d7017 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/align-scopes-within-nested-valueblock-in-array.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/align-scopes-within-nested-valueblock-in-array.expect.md @@ -2,8 +2,6 @@ ## Input ```javascript -// @enableReactiveScopesInHIR:false - import {Stringify, identity, makeArray, mutate} from 'shared-runtime'; /** @@ -37,8 +35,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @enableReactiveScopesInHIR:false - +import { c as _c } from "react/compiler-runtime"; import { Stringify, identity, makeArray, mutate } from "shared-runtime"; /** @@ -52,11 +49,12 @@ import { Stringify, identity, makeArray, mutate } from "shared-runtime"; * handles this correctly. */ function Foo(t0) { - const $ = _c(4); + const $ = _c(3); const { cond1, cond2 } = t0; - const arr = makeArray({ a: 2 }, 2, []); let t1; - if ($[0] !== cond1 || $[1] !== cond2 || $[2] !== arr) { + if ($[0] !== cond1 || $[1] !== cond2) { + const arr = makeArray({ a: 2 }, 2, []); + t1 = cond1 ? ( <>
{identity("foo")}
@@ -65,10 +63,9 @@ function Foo(t0) { ) : null; $[0] = cond1; $[1] = cond2; - $[2] = arr; - $[3] = t1; + $[2] = t1; } else { - t1 = $[3]; + t1 = $[2]; } return t1; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/align-scopes-within-nested-valueblock-in-array.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/align-scopes-within-nested-valueblock-in-array.tsx index a8f46eaf38a54..b5e2fa0c19ece 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/align-scopes-within-nested-valueblock-in-array.tsx +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/align-scopes-within-nested-valueblock-in-array.tsx @@ -1,5 +1,3 @@ -// @enableReactiveScopesInHIR:false - import {Stringify, identity, makeArray, mutate} from 'shared-runtime'; /** diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allocating-primitive-as-dep-nested-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allocating-primitive-as-dep-nested-scope.expect.md index cb550b42302c2..6702b26e92dbc 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allocating-primitive-as-dep-nested-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allocating-primitive-as-dep-nested-scope.expect.md @@ -47,7 +47,7 @@ import { identity, mutate, setProperty } from "shared-runtime"; function AllocatingPrimitiveAsDepNested(props) { const $ = _c(5); let t0; - if ($[0] !== props.b || $[1] !== props.a) { + if ($[0] !== props.a || $[1] !== props.b) { const x = {}; mutate(x); const t1 = identity(props.b) + 1; @@ -62,8 +62,8 @@ function AllocatingPrimitiveAsDepNested(props) { const y = t2; setProperty(x, props.a); t0 = [x, y]; - $[0] = props.b; - $[1] = props.a; + $[0] = props.a; + $[1] = props.b; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-mutate-global-in-effect-fixpoint.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-mutate-global-in-effect-fixpoint.expect.md index 670236e1f7498..942daec1dd08c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-mutate-global-in-effect-fixpoint.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-mutate-global-in-effect-fixpoint.expect.md @@ -46,54 +46,57 @@ import { useEffect, useState } from "react"; let someGlobal = { value: null }; function Component() { - const $ = _c(6); + const $ = _c(7); const [state, setState] = useState(someGlobal); - - let x = someGlobal; - while (x == null) { - x = someGlobal; - } - - const y = x; let t0; let t1; + let t2; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = () => { + let x = someGlobal; + while (x == null) { + x = someGlobal; + } + + const y = x; + t0 = useEffect; + t1 = () => { y.value = "hello"; }; - t1 = []; + t2 = []; $[0] = t0; $[1] = t1; + $[2] = t2; } else { t0 = $[0]; t1 = $[1]; + t2 = $[2]; } - useEffect(t0, t1); - let t2; + t0(t1, t2); let t3; - if ($[2] === Symbol.for("react.memo_cache_sentinel")) { - t2 = () => { + let t4; + if ($[3] === Symbol.for("react.memo_cache_sentinel")) { + t3 = () => { setState(someGlobal.value); }; - t3 = [someGlobal]; - $[2] = t2; + t4 = [someGlobal]; $[3] = t3; + $[4] = t4; } else { - t2 = $[2]; t3 = $[3]; + t4 = $[4]; } - useEffect(t2, t3); + useEffect(t3, t4); - const t4 = String(state); - let t5; - if ($[4] !== t4) { - t5 =
{t4}
; - $[4] = t4; + const t5 = String(state); + let t6; + if ($[5] !== t5) { + t6 =
{t5}
; $[5] = t5; + $[6] = t6; } else { - t5 = $[5]; + t6 = $[6]; } - return t5; + return t6; } export const FIXTURE_ENTRYPOINT = { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-initialization.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-initialization.expect.md new file mode 100644 index 0000000000000..560cef900ffa0 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-initialization.expect.md @@ -0,0 +1,42 @@ + +## Input + +```javascript +//@flow +import {useRef} from 'react'; + +component C() { + const r = useRef(null); + if (r.current == null) { + r.current = 1; + } +} + +export const FIXTURE_ENTRYPOINT = { + fn: C, + params: [{}], +}; + +``` + +## Code + +```javascript +import { useRef } from "react"; + +function C() { + const r = useRef(null); + if (r.current == null) { + r.current = 1; + } +} + +export const FIXTURE_ENTRYPOINT = { + fn: C, + params: [{}], +}; + +``` + +### Eval output +(kind: ok) \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-initialization.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-initialization.js new file mode 100644 index 0000000000000..7c7c3d9cb90ba --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-initialization.js @@ -0,0 +1,14 @@ +//@flow +import {useRef} from 'react'; + +component C() { + const r = useRef(null); + if (r.current == null) { + r.current = 1; + } +} + +export const FIXTURE_ENTRYPOINT = { + fn: C, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-at-effect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-at-effect.expect.md index 3aa51ba6d6f3b..a8bad51215473 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-at-effect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-at-effect.expect.md @@ -41,7 +41,7 @@ function ArrayAtTest(props) { } const arr = t1; let t2; - if ($[4] !== props.y || $[5] !== arr) { + if ($[4] !== arr || $[5] !== props.y) { let t3; if ($[7] !== props.y) { t3 = bar(props.y); @@ -51,8 +51,8 @@ function ArrayAtTest(props) { t3 = $[8]; } t2 = arr.at(t3); - $[4] = props.y; - $[5] = arr; + $[4] = arr; + $[5] = props.y; $[6] = t2; } else { t2 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-expression-spread.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-expression-spread.expect.md index 46ae9492389af..f3af7efcf62e7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-expression-spread.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-expression-spread.expect.md @@ -22,10 +22,10 @@ import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(3); let t0; - if ($[0] !== props.foo || $[1] !== props.bar) { + if ($[0] !== props.bar || $[1] !== props.foo) { t0 = [0, ...props.foo, null, ...props.bar, "z"]; - $[0] = props.foo; - $[1] = props.bar; + $[0] = props.bar; + $[1] = props.foo; $[2] = t0; } else { t0 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-property-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-property-call.expect.md index 7aa47c5803a22..6618be4a6c04c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-property-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-property-call.expect.md @@ -24,18 +24,18 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(11); - let t0; let a; + let t0; if ($[0] !== props.a || $[1] !== props.b) { a = [props.a, props.b, "hello"]; t0 = a.push(42); $[0] = props.a; $[1] = props.b; - $[2] = t0; - $[3] = a; + $[2] = a; + $[3] = t0; } else { - t0 = $[2]; - a = $[3]; + a = $[2]; + t0 = $[3]; } const x = t0; let t1; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-expr-directive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-expr-directive.expect.md index 4586bfb103cbf..93eb2bd28ac6c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-expr-directive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-expr-directive.expect.md @@ -28,7 +28,7 @@ function Component() { t0 = () => { "worklet"; - setCount((count_0) => count_0 + 1); + setCount(_temp); }; $[0] = t0; } else { @@ -45,6 +45,9 @@ function Component() { } return t1; } +function _temp(count_0) { + return count_0 + 1; +} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-aliased-capture-aliased-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-aliased-capture-aliased-mutate.expect.md new file mode 100644 index 0000000000000..d0ad9e2f9dbe5 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-aliased-capture-aliased-mutate.expect.md @@ -0,0 +1,107 @@ + +## Input + +```javascript +// @flow @enableTransitivelyFreezeFunctionExpressions:false +import {arrayPush, setPropertyByKey, Stringify} from 'shared-runtime'; + +/** + * 1. `InferMutableRanges` derives the mutable range of identifiers and their + * aliases from `LoadLocal`, `PropertyLoad`, etc + * - After this pass, y's mutable range only extends to `arrayPush(x, y)` + * - We avoid assigning mutable ranges to loads after y's mutable range, as + * these are working with an immutable value. As a result, `LoadLocal y` and + * `PropertyLoad y` do not get mutable ranges + * 2. `InferReactiveScopeVariables` extends mutable ranges and creates scopes, + * as according to the 'co-mutation' of different values + * - Here, we infer that + * - `arrayPush(y, x)` might alias `x` and `y` to each other + * - `setPropertyKey(x, ...)` may mutate both `x` and `y` + * - This pass correctly extends the mutable range of `y` + * - Since we didn't run `InferMutableRange` logic again, the LoadLocal / + * PropertyLoads still don't have a mutable range + * + * Note that the this bug is an edge case. Compiler output is only invalid for: + * - function expressions with + * `enableTransitivelyFreezeFunctionExpressions:false` + * - functions that throw and get retried without clearing the memocache + * + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + *
{"cb":{"kind":"Function","result":10},"shouldInvokeFns":true}
+ *
{"cb":{"kind":"Function","result":11},"shouldInvokeFns":true}
+ * Forget: + * (kind: ok) + *
{"cb":{"kind":"Function","result":10},"shouldInvokeFns":true}
+ *
{"cb":{"kind":"Function","result":10},"shouldInvokeFns":true}
+ */ +function useFoo({a, b}: {a: number, b: number}) { + const x = []; + const y = {value: a}; + + arrayPush(x, y); // x and y co-mutate + const y_alias = y; + const cb = () => y_alias.value; + setPropertyByKey(x[0], 'value', b); // might overwrite y.value + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{a: 2, b: 10}], + sequentialRenders: [ + {a: 2, b: 10}, + {a: 2, b: 11}, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { arrayPush, setPropertyByKey, Stringify } from "shared-runtime"; + +function useFoo(t0) { + const $ = _c(5); + const { a, b } = t0; + let t1; + if ($[0] !== a || $[1] !== b) { + const x = []; + const y = { value: a }; + + arrayPush(x, y); + const y_alias = y; + let t2; + if ($[3] !== y_alias.value) { + t2 = () => y_alias.value; + $[3] = y_alias.value; + $[4] = t2; + } else { + t2 = $[4]; + } + const cb = t2; + setPropertyByKey(x[0], "value", b); + t1 = ; + $[0] = a; + $[1] = b; + $[2] = t1; + } else { + t1 = $[2]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{ a: 2, b: 10 }], + sequentialRenders: [ + { a: 2, b: 10 }, + { a: 2, b: 11 }, + ], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-aliased-capture-aliased-mutate.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-aliased-capture-aliased-mutate.js new file mode 100644 index 0000000000000..c46ecd6250b42 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-aliased-capture-aliased-mutate.js @@ -0,0 +1,53 @@ +// @flow @enableTransitivelyFreezeFunctionExpressions:false +import {arrayPush, setPropertyByKey, Stringify} from 'shared-runtime'; + +/** + * 1. `InferMutableRanges` derives the mutable range of identifiers and their + * aliases from `LoadLocal`, `PropertyLoad`, etc + * - After this pass, y's mutable range only extends to `arrayPush(x, y)` + * - We avoid assigning mutable ranges to loads after y's mutable range, as + * these are working with an immutable value. As a result, `LoadLocal y` and + * `PropertyLoad y` do not get mutable ranges + * 2. `InferReactiveScopeVariables` extends mutable ranges and creates scopes, + * as according to the 'co-mutation' of different values + * - Here, we infer that + * - `arrayPush(y, x)` might alias `x` and `y` to each other + * - `setPropertyKey(x, ...)` may mutate both `x` and `y` + * - This pass correctly extends the mutable range of `y` + * - Since we didn't run `InferMutableRange` logic again, the LoadLocal / + * PropertyLoads still don't have a mutable range + * + * Note that the this bug is an edge case. Compiler output is only invalid for: + * - function expressions with + * `enableTransitivelyFreezeFunctionExpressions:false` + * - functions that throw and get retried without clearing the memocache + * + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + *
{"cb":{"kind":"Function","result":10},"shouldInvokeFns":true}
+ *
{"cb":{"kind":"Function","result":11},"shouldInvokeFns":true}
+ * Forget: + * (kind: ok) + *
{"cb":{"kind":"Function","result":10},"shouldInvokeFns":true}
+ *
{"cb":{"kind":"Function","result":10},"shouldInvokeFns":true}
+ */ +function useFoo({a, b}: {a: number, b: number}) { + const x = []; + const y = {value: a}; + + arrayPush(x, y); // x and y co-mutate + const y_alias = y; + const cb = () => y_alias.value; + setPropertyByKey(x[0], 'value', b); // might overwrite y.value + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{a: 2, b: 10}], + sequentialRenders: [ + {a: 2, b: 10}, + {a: 2, b: 11}, + ], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-aliased-capture-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-aliased-capture-mutate.expect.md new file mode 100644 index 0000000000000..c35efe6a16bb5 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-aliased-capture-mutate.expect.md @@ -0,0 +1,87 @@ + +## Input + +```javascript +// @flow @enableTransitivelyFreezeFunctionExpressions:false +import {setPropertyByKey, Stringify} from 'shared-runtime'; + +/** + * Variation of bug in `bug-aliased-capture-aliased-mutate` + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + *
{"cb":{"kind":"Function","result":2},"shouldInvokeFns":true}
+ *
{"cb":{"kind":"Function","result":3},"shouldInvokeFns":true}
+ * Forget: + * (kind: ok) + *
{"cb":{"kind":"Function","result":2},"shouldInvokeFns":true}
+ *
{"cb":{"kind":"Function","result":2},"shouldInvokeFns":true}
+ */ + +function useFoo({a}: {a: number, b: number}) { + const arr = []; + const obj = {value: a}; + + setPropertyByKey(obj, 'arr', arr); + const obj_alias = obj; + const cb = () => obj_alias.arr.length; + for (let i = 0; i < a; i++) { + arr.push(i); + } + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{a: 2}], + sequentialRenders: [{a: 2}, {a: 3}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { setPropertyByKey, Stringify } from "shared-runtime"; + +function useFoo(t0) { + const $ = _c(4); + const { a } = t0; + let t1; + if ($[0] !== a) { + const arr = []; + const obj = { value: a }; + + setPropertyByKey(obj, "arr", arr); + const obj_alias = obj; + let t2; + if ($[2] !== obj_alias.arr.length) { + t2 = () => obj_alias.arr.length; + $[2] = obj_alias.arr.length; + $[3] = t2; + } else { + t2 = $[3]; + } + const cb = t2; + for (let i = 0; i < a; i++) { + arr.push(i); + } + + t1 = ; + $[0] = a; + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{ a: 2 }], + sequentialRenders: [{ a: 2 }, { a: 3 }], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-aliased-capture-mutate.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-aliased-capture-mutate.js new file mode 100644 index 0000000000000..a7e57672665f6 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-aliased-capture-mutate.js @@ -0,0 +1,34 @@ +// @flow @enableTransitivelyFreezeFunctionExpressions:false +import {setPropertyByKey, Stringify} from 'shared-runtime'; + +/** + * Variation of bug in `bug-aliased-capture-aliased-mutate` + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + *
{"cb":{"kind":"Function","result":2},"shouldInvokeFns":true}
+ *
{"cb":{"kind":"Function","result":3},"shouldInvokeFns":true}
+ * Forget: + * (kind: ok) + *
{"cb":{"kind":"Function","result":2},"shouldInvokeFns":true}
+ *
{"cb":{"kind":"Function","result":2},"shouldInvokeFns":true}
+ */ + +function useFoo({a}: {a: number, b: number}) { + const arr = []; + const obj = {value: a}; + + setPropertyByKey(obj, 'arr', arr); + const obj_alias = obj; + const cb = () => obj_alias.arr.length; + for (let i = 0; i < a; i++) { + arr.push(i); + } + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{a: 2}], + sequentialRenders: [{a: 2}, {a: 3}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-functiondecl-hoisting.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-functiondecl-hoisting.expect.md new file mode 100644 index 0000000000000..2b0031b117be2 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-functiondecl-hoisting.expect.md @@ -0,0 +1,81 @@ + +## Input + +```javascript +import {Stringify} from 'shared-runtime'; + +/** + * Fixture currently fails with + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok)
{"result":{"value":2},"fn":{"kind":"Function","result":{"value":2}},"shouldInvokeFns":true}
+ * Forget: + * (kind: exception) bar is not a function + */ +function Foo({value}) { + const result = bar(); + function bar() { + return {value}; + } + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{value: 2}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { Stringify } from "shared-runtime"; + +/** + * Fixture currently fails with + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok)
{"result":{"value":2},"fn":{"kind":"Function","result":{"value":2}},"shouldInvokeFns":true}
+ * Forget: + * (kind: exception) bar is not a function + */ +function Foo(t0) { + const $ = _c(6); + const { value } = t0; + let bar; + let result; + if ($[0] !== value) { + result = bar(); + bar = function bar() { + return { value }; + }; + $[0] = value; + $[1] = bar; + $[2] = result; + } else { + bar = $[1]; + result = $[2]; + } + + const t1 = bar; + let t2; + if ($[3] !== result || $[4] !== t1) { + t2 = ; + $[3] = result; + $[4] = t1; + $[5] = t2; + } else { + t2 = $[5]; + } + return t2; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{ value: 2 }], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-functiondecl-hoisting.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-functiondecl-hoisting.tsx new file mode 100644 index 0000000000000..c454101282994 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-functiondecl-hoisting.tsx @@ -0,0 +1,22 @@ +import {Stringify} from 'shared-runtime'; + +/** + * Fixture currently fails with + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok)
{"result":{"value":2},"fn":{"kind":"Function","result":{"value":2}},"shouldInvokeFns":true}
+ * Forget: + * (kind: exception) bar is not a function + */ +function Foo({value}) { + const result = bar(); + function bar() { + return {value}; + } + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{value: 2}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md index e4e47dfde9e2b..d6331db4e7ea3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-hoisting-functionexpr.expect.md @@ -58,7 +58,7 @@ function Component(t0) { const $ = _c(5); const { obj, isObjNull } = t0; let t1; - if ($[0] !== isObjNull || $[1] !== obj.prop) { + if ($[0] !== isObjNull || $[1] !== obj) { t1 = () => { if (!isObjNull) { return obj.prop; @@ -67,7 +67,7 @@ function Component(t0) { } }; $[0] = isObjNull; - $[1] = obj.prop; + $[1] = obj; $[2] = t1; } else { t1 = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-phi-as-dependency.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-phi-as-dependency.expect.md new file mode 100644 index 0000000000000..09d2d8800b789 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-phi-as-dependency.expect.md @@ -0,0 +1,91 @@ + +## Input + +```javascript +import {CONST_TRUE, Stringify, mutate, useIdentity} from 'shared-runtime'; + +/** + * Fixture showing an edge case for ReactiveScope variable propagation. + * + * Found differences in evaluator results + * Non-forget (expected): + *
{"obj":{"inner":{"value":"hello"},"wat0":"joe"},"inner":["[[ cyclic ref *2 ]]"]}
+ *
{"obj":{"inner":{"value":"hello"},"wat0":"joe"},"inner":["[[ cyclic ref *2 ]]"]}
+ * Forget: + *
{"obj":{"inner":{"value":"hello"},"wat0":"joe"},"inner":["[[ cyclic ref *2 ]]"]}
+ * [[ (exception in render) Error: invariant broken ]] + * + */ +function Component() { + const obj = CONST_TRUE ? {inner: {value: 'hello'}} : null; + const boxedInner = [obj?.inner]; + useIdentity(null); + mutate(obj); + if (boxedInner[0] !== obj?.inner) { + throw new Error('invariant broken'); + } + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{arg: 0}], + sequentialRenders: [{arg: 0}, {arg: 1}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { CONST_TRUE, Stringify, mutate, useIdentity } from "shared-runtime"; + +/** + * Fixture showing an edge case for ReactiveScope variable propagation. + * + * Found differences in evaluator results + * Non-forget (expected): + *
{"obj":{"inner":{"value":"hello"},"wat0":"joe"},"inner":["[[ cyclic ref *2 ]]"]}
+ *
{"obj":{"inner":{"value":"hello"},"wat0":"joe"},"inner":["[[ cyclic ref *2 ]]"]}
+ * Forget: + *
{"obj":{"inner":{"value":"hello"},"wat0":"joe"},"inner":["[[ cyclic ref *2 ]]"]}
+ * [[ (exception in render) Error: invariant broken ]] + * + */ +function Component() { + const $ = _c(4); + const obj = CONST_TRUE ? { inner: { value: "hello" } } : null; + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = [obj?.inner]; + $[0] = t0; + } else { + t0 = $[0]; + } + const boxedInner = t0; + useIdentity(null); + mutate(obj); + if (boxedInner[0] !== obj?.inner) { + throw new Error("invariant broken"); + } + let t1; + if ($[1] !== boxedInner || $[2] !== obj) { + t1 = ; + $[1] = boxedInner; + $[2] = obj; + $[3] = t1; + } else { + t1 = $[3]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ arg: 0 }], + sequentialRenders: [{ arg: 0 }, { arg: 1 }], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-phi-as-dependency.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-phi-as-dependency.tsx new file mode 100644 index 0000000000000..a1a78bfa7e6b1 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-invalid-phi-as-dependency.tsx @@ -0,0 +1,30 @@ +import {CONST_TRUE, Stringify, mutate, useIdentity} from 'shared-runtime'; + +/** + * Fixture showing an edge case for ReactiveScope variable propagation. + * + * Found differences in evaluator results + * Non-forget (expected): + *
{"obj":{"inner":{"value":"hello"},"wat0":"joe"},"inner":["[[ cyclic ref *2 ]]"]}
+ *
{"obj":{"inner":{"value":"hello"},"wat0":"joe"},"inner":["[[ cyclic ref *2 ]]"]}
+ * Forget: + *
{"obj":{"inner":{"value":"hello"},"wat0":"joe"},"inner":["[[ cyclic ref *2 ]]"]}
+ * [[ (exception in render) Error: invariant broken ]] + * + */ +function Component() { + const obj = CONST_TRUE ? {inner: {value: 'hello'}} : null; + const boxedInner = [obj?.inner]; + useIdentity(null); + mutate(obj); + if (boxedInner[0] !== obj?.inner) { + throw new Error('invariant broken'); + } + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{arg: 0}], + sequentialRenders: [{arg: 0}, {arg: 1}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr.expect.md new file mode 100644 index 0000000000000..4ffe0fcb6a541 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr.expect.md @@ -0,0 +1,109 @@ + +## Input + +```javascript +import {identity, mutate} from 'shared-runtime'; + +/** + * Bug: copy of error.todo-object-expression-computed-key-modified-during-after-construction-sequence-expr + * with the mutation hoisted to a named variable instead of being directly + * inlined into the Object key. + * + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}] + * [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}] + * Forget: + * (kind: ok) [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}] + * [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe","wat2":"joe"}] + */ +function Component(props) { + const key = {}; + const tmp = (mutate(key), key); + const context = { + // Here, `tmp` is frozen (as it's inferred to be a primitive/string) + [tmp]: identity([props.value]), + }; + mutate(key); + return [context, key]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{value: 42}], + sequentialRenders: [{value: 42}, {value: 42}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { identity, mutate } from "shared-runtime"; + +/** + * Bug: copy of error.todo-object-expression-computed-key-modified-during-after-construction-sequence-expr + * with the mutation hoisted to a named variable instead of being directly + * inlined into the Object key. + * + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}] + * [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}] + * Forget: + * (kind: ok) [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}] + * [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe","wat2":"joe"}] + */ +function Component(props) { + const $ = _c(8); + let key; + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + key = {}; + t0 = (mutate(key), key); + $[0] = key; + $[1] = t0; + } else { + key = $[0]; + t0 = $[1]; + } + const tmp = t0; + let t1; + if ($[2] !== props.value) { + t1 = identity([props.value]); + $[2] = props.value; + $[3] = t1; + } else { + t1 = $[3]; + } + let t2; + if ($[4] !== t1) { + t2 = { [tmp]: t1 }; + $[4] = t1; + $[5] = t2; + } else { + t2 = $[5]; + } + const context = t2; + + mutate(key); + let t3; + if ($[6] !== context) { + t3 = [context, key]; + $[6] = context; + $[7] = t3; + } else { + t3 = $[7]; + } + return t3; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ value: 42 }], + sequentialRenders: [{ value: 42 }, { value: 42 }], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr.js new file mode 100644 index 0000000000000..94befbdd17b77 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr.js @@ -0,0 +1,31 @@ +import {identity, mutate} from 'shared-runtime'; + +/** + * Bug: copy of error.todo-object-expression-computed-key-modified-during-after-construction-sequence-expr + * with the mutation hoisted to a named variable instead of being directly + * inlined into the Object key. + * + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}] + * [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}] + * Forget: + * (kind: ok) [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}] + * [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe","wat2":"joe"}] + */ +function Component(props) { + const key = {}; + const tmp = (mutate(key), key); + const context = { + // Here, `tmp` is frozen (as it's inferred to be a primitive/string) + [tmp]: identity([props.value]), + }; + mutate(key); + return [context, key]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{value: 42}], + sequentialRenders: [{value: 42}, {value: 42}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md new file mode 100644 index 0000000000000..839821b349a6f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.expect.md @@ -0,0 +1,73 @@ + +## Input + +```javascript +import {identity} from 'shared-runtime'; + +/** + * Not safe to hoist read of maybeNullObject.value.inner outside of the + * try-catch block, as that might throw + */ +function useFoo(maybeNullObject: {value: {inner: number}} | null) { + const y = []; + try { + y.push(identity(maybeNullObject.value.inner)); + } catch { + y.push('null'); + } + + return y; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [null], + sequentialRenders: [null, {value: 2}, {value: 3}, null], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { identity } from "shared-runtime"; + +/** + * Not safe to hoist read of maybeNullObject.value.inner outside of the + * try-catch block, as that might throw + */ +function useFoo(maybeNullObject) { + const $ = _c(4); + let y; + if ($[0] !== maybeNullObject) { + y = []; + try { + let t0; + if ($[2] !== maybeNullObject.value.inner) { + t0 = identity(maybeNullObject.value.inner); + $[2] = maybeNullObject.value.inner; + $[3] = t0; + } else { + t0 = $[3]; + } + y.push(t0); + } catch { + y.push("null"); + } + $[0] = maybeNullObject; + $[1] = y; + } else { + y = $[1]; + } + return y; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [null], + sequentialRenders: [null, { value: 2 }, { value: 3 }, null], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.ts new file mode 100644 index 0000000000000..555ace19405a6 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-try-catch-maybe-null-dependency.ts @@ -0,0 +1,22 @@ +import {identity} from 'shared-runtime'; + +/** + * Not safe to hoist read of maybeNullObject.value.inner outside of the + * try-catch block, as that might throw + */ +function useFoo(maybeNullObject: {value: {inner: number}} | null) { + const y = []; + try { + y.push(identity(maybeNullObject.value.inner)); + } catch { + y.push('null'); + } + + return y; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [null], + sequentialRenders: [null, {value: 2}, {value: 3}, null], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-type-inference-control-flow.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-type-inference-control-flow.expect.md new file mode 100644 index 0000000000000..8fc049fa6f915 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-type-inference-control-flow.expect.md @@ -0,0 +1,114 @@ + +## Input + +```javascript +import {arrayPush, CONST_NUMBER0, mutate} from 'shared-runtime'; + +/** + * Repro for bug in our type inference system. We currently propagate inferred + * types through control flow / potential type guards. Note that this is + * inconsistent with both Flow and Typescript. + * https://flow.org/try/#1N4Igxg9gdgZglgcxALlAIwIZoKYBsD6uEEAztvhgE6UYCe+JADpdhgCYowa5kA0I2KAFcAtiRQAXSkOz9sADwxgJ+NPTbYuQ3BMnTZA+Y2yU4IwRO4A6SFBIrGVDGM7c+h46fNRLuKxJIGWh8MeT0ZfhYlCStpHzNsFBAMIQkIEQwJODAQfiEyfBE4eWw2fDgofDBMsAALfAA3KjgsXGxxZC4eAw0G-GhcWn9aY3wWZldu-g1mbGqJUoBaCRHEzrcDEgBrbAk62kXhXFxJ923d-cPRHEpTgyEoMDaqZdW7vKgoOfaSKgOKpqmDA+d4gB5fMA-P6LCCMLLQbiLOoYCqgh6-GDYRYIXYLSgkRZkCR4jpddwPfJLZjpOBkO4AX34kA0SRWxgABAAxYjsgC87OAAB0oOzReythU2Mh2YKQNyILLeMKxeymrgZNLhCIbsL6QBuYVs7DsgBCVD5AuVYolUClMpAZsoiqtorVGvZWpuSqg9OFMAeyjg0HZdTmW3lAAp5NKAPJoABWcwkAEppWZGLg4O12fJ2bSuTyhSKxSwJEJKCKAOQ2tiVvMi3MAMkbOasNb5vP5svlsoNPuFfoD8JFGQqUel8vZAB9TVReCHoHa0MRnlBUwWIJbi6K4DB2RHbGxk1uVSrd-uAIShsDh4hR5PHoun5-siS1SgQADuHuw34AotQECUBGsqysmfYvuyvrbqepblg2EFitBKpwRWOZ9vSuQgA0JgkEGUBJBk9gmCA9JAA + * https://www.typescriptlang.org/play/?#code/C4TwDgpgBAYg9nKBeKBvAUFLUDWBLAOwBMAuKAInjnIBpNsA3AQwBsBXCMgtgWwCMIAJ3QBfANzpQkKACEmg5GnpZ8xMuTmDayqM3aco3fkLoj0AMzYEAxsDxwCUawAsI1nFQAUADzJw+AFZuwACUZEwAzhFCwBFQ3lB4cVRK2InmUJ4AhJ4A5KpEuYmOCQBkpfEAdAXISCiUCOQhIalp2MDOgnAA7oYQvQCigl2CnuRWEN6QthBETTpmZhZWtvaOPEyEPmQpAD6y8jRODqRQfAgsEEwEYbAIrVh4GZ7WJy0Ybdgubh4IPiEST5YQQQYBsQQlQHYMxpEFgiHxCQiIA + * + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * [2] + * [3] + * Forget: + * (kind: ok) + * [2] + * [2,3] + */ +function useFoo({cond, value}: {cond: boolean; value: number}) { + const x = {value: cond ? CONST_NUMBER0 : []}; + mutate(x); + + const xValue = x.value; + let result; + if (typeof xValue === 'number') { + result = xValue + 1; // (1) here we infer xValue is a primitive + } else { + result = arrayPush(xValue, value); // (2) and propagate it to all other xValue references + } + + return result; +} +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{cond: true}], + sequentialRenders: [ + {cond: false, value: 2}, + {cond: false, value: 3}, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { arrayPush, CONST_NUMBER0, mutate } from "shared-runtime"; + +/** + * Repro for bug in our type inference system. We currently propagate inferred + * types through control flow / potential type guards. Note that this is + * inconsistent with both Flow and Typescript. + * https://flow.org/try/#1N4Igxg9gdgZglgcxALlAIwIZoKYBsD6uEEAztvhgE6UYCe+JADpdhgCYowa5kA0I2KAFcAtiRQAXSkOz9sADwxgJ+NPTbYuQ3BMnTZA+Y2yU4IwRO4A6SFBIrGVDGM7c+h46fNRLuKxJIGWh8MeT0ZfhYlCStpHzNsFBAMIQkIEQwJODAQfiEyfBE4eWw2fDgofDBMsAALfAA3KjgsXGxxZC4eAw0G-GhcWn9aY3wWZldu-g1mbGqJUoBaCRHEzrcDEgBrbAk62kXhXFxJ923d-cPRHEpTgyEoMDaqZdW7vKgoOfaSKgOKpqmDA+d4gB5fMA-P6LCCMLLQbiLOoYCqgh6-GDYRYIXYLSgkRZkCR4jpddwPfJLZjpOBkO4AX34kA0SRWxgABAAxYjsgC87OAAB0oOzReythU2Mh2YKQNyILLeMKxeymrgZNLhCIbsL6QBuYVs7DsgBCVD5AuVYolUClMpAZsoiqtorVGvZWpuSqg9OFMAeyjg0HZdTmW3lAAp5NKAPJoABWcwkAEppWZGLg4O12fJ2bSuTyhSKxSwJEJKCKAOQ2tiVvMi3MAMkbOasNb5vP5svlsoNPuFfoD8JFGQqUel8vZAB9TVReCHoHa0MRnlBUwWIJbi6K4DB2RHbGxk1uVSrd-uAIShsDh4hR5PHoun5-siS1SgQADuHuw34AotQECUBGsqysmfYvuyvrbqepblg2EFitBKpwRWOZ9vSuQgA0JgkEGUBJBk9gmCA9JAA + * https://www.typescriptlang.org/play/?#code/C4TwDgpgBAYg9nKBeKBvAUFLUDWBLAOwBMAuKAInjnIBpNsA3AQwBsBXCMgtgWwCMIAJ3QBfANzpQkKACEmg5GnpZ8xMuTmDayqM3aco3fkLoj0AMzYEAxsDxwCUawAsI1nFQAUADzJw+AFZuwACUZEwAzhFCwBFQ3lB4cVRK2InmUJ4AhJ4A5KpEuYmOCQBkpfEAdAXISCiUCOQhIalp2MDOgnAA7oYQvQCigl2CnuRWEN6QthBETTpmZhZWtvaOPEyEPmQpAD6y8jRODqRQfAgsEEwEYbAIrVh4GZ7WJy0Ybdgubh4IPiEST5YQQQYBsQQlQHYMxpEFgiHxCQiIA + * + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * [2] + * [3] + * Forget: + * (kind: ok) + * [2] + * [2,3] + */ +function useFoo(t0) { + const $ = _c(5); + const { cond, value } = t0; + let x; + if ($[0] !== cond) { + x = { value: cond ? CONST_NUMBER0 : [] }; + mutate(x); + $[0] = cond; + $[1] = x; + } else { + x = $[1]; + } + + const xValue = x.value; + let result; + if (typeof xValue === "number") { + result = xValue + 1; + } else { + let t1; + if ($[2] !== value || $[3] !== xValue) { + t1 = arrayPush(xValue, value); + $[2] = value; + $[3] = xValue; + $[4] = t1; + } else { + t1 = $[4]; + } + result = t1; + } + return result; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{ cond: true }], + sequentialRenders: [ + { cond: false, value: 2 }, + { cond: false, value: 3 }, + ], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-type-inference-control-flow.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-type-inference-control-flow.ts new file mode 100644 index 0000000000000..4b8957dc8d277 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-type-inference-control-flow.ts @@ -0,0 +1,41 @@ +import {arrayPush, CONST_NUMBER0, mutate} from 'shared-runtime'; + +/** + * Repro for bug in our type inference system. We currently propagate inferred + * types through control flow / potential type guards. Note that this is + * inconsistent with both Flow and Typescript. + * https://flow.org/try/#1N4Igxg9gdgZglgcxALlAIwIZoKYBsD6uEEAztvhgE6UYCe+JADpdhgCYowa5kA0I2KAFcAtiRQAXSkOz9sADwxgJ+NPTbYuQ3BMnTZA+Y2yU4IwRO4A6SFBIrGVDGM7c+h46fNRLuKxJIGWh8MeT0ZfhYlCStpHzNsFBAMIQkIEQwJODAQfiEyfBE4eWw2fDgofDBMsAALfAA3KjgsXGxxZC4eAw0G-GhcWn9aY3wWZldu-g1mbGqJUoBaCRHEzrcDEgBrbAk62kXhXFxJ923d-cPRHEpTgyEoMDaqZdW7vKgoOfaSKgOKpqmDA+d4gB5fMA-P6LCCMLLQbiLOoYCqgh6-GDYRYIXYLSgkRZkCR4jpddwPfJLZjpOBkO4AX34kA0SRWxgABAAxYjsgC87OAAB0oOzReythU2Mh2YKQNyILLeMKxeymrgZNLhCIbsL6QBuYVs7DsgBCVD5AuVYolUClMpAZsoiqtorVGvZWpuSqg9OFMAeyjg0HZdTmW3lAAp5NKAPJoABWcwkAEppWZGLg4O12fJ2bSuTyhSKxSwJEJKCKAOQ2tiVvMi3MAMkbOasNb5vP5svlsoNPuFfoD8JFGQqUel8vZAB9TVReCHoHa0MRnlBUwWIJbi6K4DB2RHbGxk1uVSrd-uAIShsDh4hR5PHoun5-siS1SgQADuHuw34AotQECUBGsqysmfYvuyvrbqepblg2EFitBKpwRWOZ9vSuQgA0JgkEGUBJBk9gmCA9JAA + * https://www.typescriptlang.org/play/?#code/C4TwDgpgBAYg9nKBeKBvAUFLUDWBLAOwBMAuKAInjnIBpNsA3AQwBsBXCMgtgWwCMIAJ3QBfANzpQkKACEmg5GnpZ8xMuTmDayqM3aco3fkLoj0AMzYEAxsDxwCUawAsI1nFQAUADzJw+AFZuwACUZEwAzhFCwBFQ3lB4cVRK2InmUJ4AhJ4A5KpEuYmOCQBkpfEAdAXISCiUCOQhIalp2MDOgnAA7oYQvQCigl2CnuRWEN6QthBETTpmZhZWtvaOPEyEPmQpAD6y8jRODqRQfAgsEEwEYbAIrVh4GZ7WJy0Ybdgubh4IPiEST5YQQQYBsQQlQHYMxpEFgiHxCQiIA + * + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) + * [2] + * [3] + * Forget: + * (kind: ok) + * [2] + * [2,3] + */ +function useFoo({cond, value}: {cond: boolean; value: number}) { + const x = {value: cond ? CONST_NUMBER0 : []}; + mutate(x); + + const xValue = x.value; + let result; + if (typeof xValue === 'number') { + result = xValue + 1; // (1) here we infer xValue is a primitive + } else { + result = arrayPush(xValue, value); // (2) and propagate it to all other xValue references + } + + return result; +} +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{cond: true}], + sequentialRenders: [ + {cond: false, value: 2}, + {cond: false, value: 3}, + ], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capture-param-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capture-param-mutate.expect.md index c9c197345c2c6..9e4709616d5ec 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capture-param-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capture-param-mutate.expect.md @@ -55,11 +55,7 @@ function getNativeLogFunction(level) { if (arguments.length === 1 && typeof arguments[0] === "string") { str = arguments[0]; } else { - str = Array.prototype.map - .call(arguments, function (arg) { - return inspect(arg, { depth: 10 }); - }) - .join(", "); + str = Array.prototype.map.call(arguments, _temp).join(", "); } const firstArg = arguments[0]; @@ -92,6 +88,9 @@ function getNativeLogFunction(level) { } return t0; } +function _temp(arg) { + return inspect(arg, { depth: 10 }); +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capture-ref-for-later-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capture-ref-for-later-mutation.expect.md index 41748867b35d5..b7371108d5867 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capture-ref-for-later-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capture-ref-for-later-mutation.expect.md @@ -36,27 +36,26 @@ import { useRef } from "react"; import { addOne } from "shared-runtime"; function useKeyCommand() { - const $ = _c(2); + const $ = _c(1); const currentPosition = useRef(0); - const handleKey = (direction) => () => { - const position = currentPosition.current; - const nextPosition = direction === "left" ? addOne(position) : position; - currentPosition.current = nextPosition; - }; + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + const handleKey = (direction) => () => { + const position = currentPosition.current; + const nextPosition = direction === "left" ? addOne(position) : position; + currentPosition.current = nextPosition; + }; - const moveLeft = { handler: handleKey("left") }; + const moveLeft = { handler: handleKey("left") }; - const t0 = handleKey("right"); - let t1; - if ($[0] !== t0) { - t1 = { handler: t0 }; + const moveRight = { handler: handleKey("right") }; + + t0 = [moveLeft, moveRight]; $[0] = t0; - $[1] = t1; } else { - t1 = $[1]; + t0 = $[0]; } - const moveRight = t1; - return [moveLeft, moveRight]; + return t0; } export const FIXTURE_ENTRYPOINT = { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2-iife.expect.md index 65ab9c277c2d1..5e0b32709b32e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2-iife.expect.md @@ -32,7 +32,7 @@ import { mutate } from "shared-runtime"; function component(foo, bar) { const $ = _c(3); let x; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { x = { foo }; const y = { bar }; @@ -41,8 +41,8 @@ function component(foo, bar) { a.x = b; mutate(y); - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2.expect.md index 170f68badeffe..f9ce3f2e98cf5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-2.expect.md @@ -24,7 +24,7 @@ import { c as _c } from "react/compiler-runtime"; function component(foo, bar) { const $ = _c(3); let x; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { x = { foo }; const y = { bar }; const f0 = function () { @@ -35,8 +35,8 @@ function component(foo, bar) { f0(); mutate(y); - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2-iife.expect.md index e315cd401d433..81737a1ed500f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2-iife.expect.md @@ -32,7 +32,7 @@ const { mutate } = require("shared-runtime"); function component(foo, bar) { const $ = _c(3); let x; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { x = { foo }; const y = { bar }; @@ -41,8 +41,8 @@ function component(foo, bar) { a.x = b; mutate(y); - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2.expect.md index 7371f3d27a29d..38590d1559bb5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-fun-alias-captured-mutate-arr-2.expect.md @@ -24,7 +24,7 @@ import { c as _c } from "react/compiler-runtime"; function component(foo, bar) { const $ = _c(3); let x; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { x = { foo }; const y = { bar }; const f0 = function () { @@ -35,8 +35,8 @@ function component(foo, bar) { f0(); mutate(y); - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = x; } else { x = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr-iife.expect.md index cc41adcbbcff9..4882aa822f430 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr-iife.expect.md @@ -32,7 +32,7 @@ const { mutate } = require("shared-runtime"); function component(foo, bar) { const $ = _c(3); let y; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { const x = { foo }; y = { bar }; @@ -41,8 +41,8 @@ function component(foo, bar) { a.x = b; mutate(y); - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = y; } else { y = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr.expect.md index 34f6a55740c31..7c94c33e495a8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-arr.expect.md @@ -24,7 +24,7 @@ import { c as _c } from "react/compiler-runtime"; function component(foo, bar) { const $ = _c(3); let y; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { const x = { foo }; y = { bar }; const f0 = function () { @@ -35,8 +35,8 @@ function component(foo, bar) { f0(); mutate(y); - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = y; } else { y = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-iife.expect.md index b2d704875631e..60493dd25ad74 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate-iife.expect.md @@ -32,7 +32,7 @@ const { mutate } = require("shared-runtime"); function component(foo, bar) { const $ = _c(3); let y; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { const x = { foo }; y = { bar }; @@ -41,8 +41,8 @@ function component(foo, bar) { a.x = b; mutate(y); - $[0] = foo; - $[1] = bar; + $[0] = bar; + $[1] = foo; $[2] = y; } else { y = $[2]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md index a68e919c9678f..9a98f76b97170 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.expect.md @@ -2,41 +2,57 @@ ## Input ```javascript -function component(foo, bar) { +import {mutate} from 'shared-runtime'; + +function Component({foo, bar}) { let x = {foo}; let y = {bar}; const f0 = function () { - let a = {y}; + let a = [y]; let b = x; - a.x = b; + // this writes y.x = x + a[0].x = b; }; f0(); - mutate(y); + mutate(y.x); return y; } +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 3, bar: 4}], + sequentialRenders: [ + {foo: 3, bar: 4}, + {foo: 3, bar: 5}, + ], +}; + ``` ## Code ```javascript import { c as _c } from "react/compiler-runtime"; -function component(foo, bar) { +import { mutate } from "shared-runtime"; + +function Component(t0) { const $ = _c(3); + const { foo, bar } = t0; let y; - if ($[0] !== foo || $[1] !== bar) { + if ($[0] !== bar || $[1] !== foo) { const x = { foo }; y = { bar }; const f0 = function () { - const a = { y }; + const a = [y]; const b = x; - a.x = b; + + a[0].x = b; }; f0(); - mutate(y); - $[0] = foo; - $[1] = bar; + mutate(y.x); + $[0] = bar; + $[1] = foo; $[2] = y; } else { y = $[2]; @@ -44,5 +60,17 @@ function component(foo, bar) { return y; } +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ foo: 3, bar: 4 }], + sequentialRenders: [ + { foo: 3, bar: 4 }, + { foo: 3, bar: 5 }, + ], +}; + ``` - \ No newline at end of file + +### Eval output +(kind: ok) {"bar":4,"x":{"foo":3,"wat0":"joe"}} +{"bar":5,"x":{"foo":3,"wat0":"joe"}} \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.js index ed4e097b66ace..b88ad567184fe 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-alias-captured-mutate.js @@ -1,12 +1,24 @@ -function component(foo, bar) { +import {mutate} from 'shared-runtime'; + +function Component({foo, bar}) { let x = {foo}; let y = {bar}; const f0 = function () { - let a = {y}; + let a = [y]; let b = x; - a.x = b; + // this writes y.x = x + a[0].x = b; }; f0(); - mutate(y); + mutate(y.x); return y; } + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{foo: 3, bar: 4}], + sequentialRenders: [ + {foo: 3, bar: 4}, + {foo: 3, bar: 5}, + ], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md index 53deac41495c6..c071d5d20ed99 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md @@ -26,29 +26,20 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function component(a, b) { - const $ = _c(5); - let t0; - if ($[0] !== b) { - t0 = { b }; - $[0] = b; - $[1] = t0; - } else { - t0 = $[1]; - } - const y = t0; + const $ = _c(2); + const y = { b }; let z; - if ($[2] !== a || $[3] !== y.b) { + if ($[0] !== a) { z = { a }; const x = function () { z.a = 2; }; x(); - $[2] = a; - $[3] = y.b; - $[4] = z; + $[0] = a; + $[1] = z; } else { - z = $[4]; + z = $[1]; } return z; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.expect.md index 7ad5c47da75f8..fcde7d675c846 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.expect.md @@ -2,21 +2,28 @@ ## Input ```javascript -function component(a, b) { +import {mutate} from 'shared-runtime'; + +function Component({a, b}) { let z = {a}; - let y = {b}; + let y = {b: {b}}; let x = function () { z.a = 2; - console.log(y.b); + mutate(y.b); }; x(); - return z; + return [y, z]; } export const FIXTURE_ENTRYPOINT = { - fn: component, - params: ['TodoAdd'], - isComponent: 'TodoAdd', + fn: Component, + params: [{a: 2, b: 3}], + sequentialRenders: [ + {a: 2, b: 3}, + {a: 2, b: 3}, + {a: 4, b: 3}, + {a: 4, b: 5}, + ], }; ``` @@ -25,32 +32,46 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; -function component(a, b) { +import { mutate } from "shared-runtime"; + +function Component(t0) { const $ = _c(3); - let z; + const { a, b } = t0; + let t1; if ($[0] !== a || $[1] !== b) { - z = { a }; - const y = { b }; + const z = { a }; + const y = { b: { b } }; const x = function () { z.a = 2; - console.log(y.b); + mutate(y.b); }; x(); + t1 = [y, z]; $[0] = a; $[1] = b; - $[2] = z; + $[2] = t1; } else { - z = $[2]; + t1 = $[2]; } - return z; + return t1; } export const FIXTURE_ENTRYPOINT = { - fn: component, - params: ["TodoAdd"], - isComponent: "TodoAdd", + fn: Component, + params: [{ a: 2, b: 3 }], + sequentialRenders: [ + { a: 2, b: 3 }, + { a: 2, b: 3 }, + { a: 4, b: 3 }, + { a: 4, b: 5 }, + ], }; ``` - \ No newline at end of file + +### Eval output +(kind: ok) [{"b":{"b":3,"wat0":"joe"}},{"a":2}] +[{"b":{"b":3,"wat0":"joe"}},{"a":2}] +[{"b":{"b":3,"wat0":"joe"}},{"a":2}] +[{"b":{"b":5,"wat0":"joe"}},{"a":2}] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.js index 62014ee084eae..2ec7bcbe86cfd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate.js @@ -1,16 +1,23 @@ -function component(a, b) { +import {mutate} from 'shared-runtime'; + +function Component({a, b}) { let z = {a}; - let y = {b}; + let y = {b: {b}}; let x = function () { z.a = 2; - console.log(y.b); + mutate(y.b); }; x(); - return z; + return [y, z]; } export const FIXTURE_ENTRYPOINT = { - fn: component, - params: ['TodoAdd'], - isComponent: 'TodoAdd', + fn: Component, + params: [{a: 2, b: 3}], + sequentialRenders: [ + {a: 2, b: 3}, + {a: 2, b: 3}, + {a: 4, b: 3}, + {a: 4, b: 5}, + ], }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md new file mode 100644 index 0000000000000..aa32b3260ef50 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md @@ -0,0 +1,72 @@ + +## Input + +```javascript +function Component({a, b}) { + let z = {a}; + let y = {b}; + let x = function () { + z.a = 2; + return Math.max(y.b, 0); + }; + x(); + return z; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{a: 2, b: 3}], + sequentialRenders: [ + {a: 2, b: 3}, + {a: 2, b: 3}, + {a: 4, b: 3}, + {a: 4, b: 5}, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +function Component(t0) { + const $ = _c(3); + const { a, b } = t0; + let z; + if ($[0] !== a || $[1] !== b) { + z = { a }; + const y = { b }; + const x = function () { + z.a = 2; + return Math.max(y.b, 0); + }; + + x(); + $[0] = a; + $[1] = b; + $[2] = z; + } else { + z = $[2]; + } + return z; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ a: 2, b: 3 }], + sequentialRenders: [ + { a: 2, b: 3 }, + { a: 2, b: 3 }, + { a: 4, b: 3 }, + { a: 4, b: 5 }, + ], +}; + +``` + +### Eval output +(kind: ok) {"a":2} +{"a":2} +{"a":2} +{"a":2} \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.js new file mode 100644 index 0000000000000..8fe3bb3db52f6 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.js @@ -0,0 +1,21 @@ +function Component({a, b}) { + let z = {a}; + let y = {b}; + let x = function () { + z.a = 2; + return Math.max(y.b, 0); + }; + x(); + return z; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{a: 2, b: 3}], + sequentialRenders: [ + {a: 2, b: 3}, + {a: 2, b: 3}, + {a: 4, b: 3}, + {a: 4, b: 5}, + ], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-member-expr-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-member-expr-call.expect.md index 51679299fee72..cab9c9a500b9c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-member-expr-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-member-expr-call.expect.md @@ -46,10 +46,10 @@ function component(t0) { } const hide = t2; let t3; - if ($[4] !== poke || $[5] !== hide) { + if ($[4] !== hide || $[5] !== poke) { t3 = ; - $[4] = poke; - $[5] = hide; + $[4] = hide; + $[5] = poke; $[6] = t3; } else { t3 = $[6]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-imports-same-source.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-imports-same-source.expect.md index dd67bcfbff021..9a59b36cc0f10 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-imports-same-source.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-imports-same-source.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableEmitFreeze @instrumentForget +// @enableEmitFreeze @enableEmitInstrumentForget function useFoo(props) { return foo(props.x); @@ -18,7 +18,7 @@ import { shouldInstrument, makeReadOnly, } from "react-compiler-runtime"; -import { c as _c } from "react/compiler-runtime"; // @enableEmitFreeze @instrumentForget +import { c as _c } from "react/compiler-runtime"; // @enableEmitFreeze @enableEmitInstrumentForget function useFoo(props) { if (__DEV__ && shouldInstrument) diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-imports-same-source.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-imports-same-source.js index 4edff1c3fcaeb..bd66353319d6b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-imports-same-source.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-emit-imports-same-source.js @@ -1,4 +1,4 @@ -// @enableEmitFreeze @instrumentForget +// @enableEmitFreeze @enableEmitInstrumentForget function useFoo(props) { return foo(props.x); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-gating-test.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-gating-test.expect.md index 4aa29992eb99b..fc9247344d56c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-gating-test.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-gating-test.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @instrumentForget @compilationMode(annotation) @gating +// @enableEmitInstrumentForget @compilationMode(annotation) @gating function Bar(props) { 'use forget'; @@ -25,7 +25,7 @@ function Foo(props) { ```javascript import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; import { useRenderCounter, shouldInstrument } from "react-compiler-runtime"; -import { c as _c } from "react/compiler-runtime"; // @instrumentForget @compilationMode(annotation) @gating +import { c as _c } from "react/compiler-runtime"; // @enableEmitInstrumentForget @compilationMode(annotation) @gating const Bar = isForgetEnabled_Fixtures() ? function Bar(props) { "use forget"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-gating-test.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-gating-test.js index 85fbd97ee7b96..dffb8ce795357 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-gating-test.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-gating-test.js @@ -1,4 +1,4 @@ -// @instrumentForget @compilationMode(annotation) @gating +// @enableEmitInstrumentForget @compilationMode(annotation) @gating function Bar(props) { 'use forget'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.expect.md index ba8ed5056b3a0..b5da853b6e5c7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @instrumentForget @compilationMode(annotation) +// @enableEmitInstrumentForget @compilationMode(annotation) function Bar(props) { 'use forget'; @@ -24,7 +24,7 @@ function Foo(props) { ```javascript import { useRenderCounter, shouldInstrument } from "react-compiler-runtime"; -import { c as _c } from "react/compiler-runtime"; // @instrumentForget @compilationMode(annotation) +import { c as _c } from "react/compiler-runtime"; // @enableEmitInstrumentForget @compilationMode(annotation) function Bar(props) { "use forget"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.js index 894750327748b..2aef527e6be73 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.js @@ -1,4 +1,4 @@ -// @instrumentForget @compilationMode(annotation) +// @enableEmitInstrumentForget @compilationMode(annotation) function Bar(props) { 'use forget'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component.expect.md index 80d6e6df8c086..c6037fd2bb390 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component.expect.md @@ -46,7 +46,7 @@ function Component(props) { const items = props.items; const maxItems = props.maxItems; let renderedItems; - if ($[0] !== maxItems || $[1] !== items) { + if ($[0] !== items || $[1] !== maxItems) { renderedItems = []; const seen = new Set(); const max = Math.max(0, maxItems); @@ -62,8 +62,8 @@ function Component(props) { break; } } - $[0] = maxItems; - $[1] = items; + $[0] = items; + $[1] = maxItems; $[2] = renderedItems; } else { renderedItems = $[2]; @@ -79,15 +79,15 @@ function Component(props) { t0 = $[4]; } let t1; - if ($[5] !== t0 || $[6] !== renderedItems) { + if ($[5] !== renderedItems || $[6] !== t0) { t1 = (
{t0} {renderedItems}
); - $[5] = t0; - $[6] = renderedItems; + $[5] = renderedItems; + $[6] = t0; $[7] = t1; } else { t1 = $[7]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/computed-call-spread.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/computed-call-spread.expect.md index cb20d97cb7c49..0329450b13e34 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/computed-call-spread.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/computed-call-spread.expect.md @@ -16,11 +16,11 @@ import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(4); let t0; - if ($[0] !== props.method || $[1] !== props.a || $[2] !== props.b) { + if ($[0] !== props.a || $[1] !== props.b || $[2] !== props.method) { t0 = foo[props.method](...props.a, null, ...props.b); - $[0] = props.method; - $[1] = props.a; - $[2] = props.b; + $[0] = props.a; + $[1] = props.b; + $[2] = props.method; $[3] = t0; } else { t0 = $[3]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/concise-arrow-expr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/concise-arrow-expr.expect.md index 787418f75b3fb..996afa1cb224b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/concise-arrow-expr.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/concise-arrow-expr.expect.md @@ -16,7 +16,7 @@ function component() { import { c as _c } from "react/compiler-runtime"; function component() { const $ = _c(1); - const [x, setX] = useState(0); + const [, setX] = useState(0); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { const handler = (v) => setX(v); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md index 76648c251a101..3f795b604e382 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-break-labeled.expect.md @@ -33,9 +33,14 @@ import { c as _c } from "react/compiler-runtime"; /** * props.b *does* influence `a` */ function Component(props) { - const $ = _c(2); + const $ = _c(5); let a; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { a = []; a.push(props.a); bb0: { @@ -47,10 +52,13 @@ function Component(props) { } a.push(props.d); - $[0] = props; - $[1] = a; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; } else { - a = $[1]; + a = $[4]; } return a; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md index 82537902bfa19..5e708b95c6fe9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-early-return.expect.md @@ -70,10 +70,10 @@ import { c as _c } from "react/compiler-runtime"; /** * props.b does *not* influence `a` */ function ComponentA(props) { - const $ = _c(3); + const $ = _c(5); let a_DEBUG; let t0; - if ($[0] !== props) { + if ($[0] !== props.a || $[1] !== props.b || $[2] !== props.d) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { a_DEBUG = []; @@ -85,12 +85,14 @@ function ComponentA(props) { a_DEBUG.push(props.d); } - $[0] = props; - $[1] = a_DEBUG; - $[2] = t0; + $[0] = props.a; + $[1] = props.b; + $[2] = props.d; + $[3] = a_DEBUG; + $[4] = t0; } else { - a_DEBUG = $[1]; - t0 = $[2]; + a_DEBUG = $[3]; + t0 = $[4]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; @@ -102,9 +104,14 @@ function ComponentA(props) { * props.b *does* influence `a` */ function ComponentB(props) { - const $ = _c(2); + const $ = _c(5); let a; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { a = []; a.push(props.a); if (props.b) { @@ -112,10 +119,13 @@ function ComponentB(props) { } a.push(props.d); - $[0] = props; - $[1] = a; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; } else { - a = $[1]; + a = $[4]; } return a; } @@ -124,10 +134,15 @@ function ComponentB(props) { * props.b *does* influence `a`, but only in a way that is never observable */ function ComponentC(props) { - const $ = _c(3); + const $ = _c(6); let a; let t0; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { a = []; @@ -140,12 +155,15 @@ function ComponentC(props) { a.push(props.d); } - $[0] = props; - $[1] = a; - $[2] = t0; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; + $[5] = t0; } else { - a = $[1]; - t0 = $[2]; + a = $[4]; + t0 = $[5]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; @@ -157,10 +175,15 @@ function ComponentC(props) { * props.b *does* influence `a` */ function ComponentD(props) { - const $ = _c(3); + const $ = _c(6); let a; let t0; - if ($[0] !== props) { + if ( + $[0] !== props.a || + $[1] !== props.b || + $[2] !== props.c || + $[3] !== props.d + ) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { a = []; @@ -173,12 +196,15 @@ function ComponentD(props) { a.push(props.d); } - $[0] = props; - $[1] = a; - $[2] = t0; + $[0] = props.a; + $[1] = props.b; + $[2] = props.c; + $[3] = props.d; + $[4] = a; + $[5] = t0; } else { - a = $[1]; - t0 = $[2]; + a = $[4]; + t0 = $[5]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md index ad638cf28d871..fa8348c200972 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conditional-on-mutable.expect.md @@ -36,9 +36,9 @@ function mayMutate() {} ```javascript import { c as _c } from "react/compiler-runtime"; function ComponentA(props) { - const $ = _c(2); + const $ = _c(4); let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p1 || $[2] !== props.p2) { const a = []; const b = []; if (b) { @@ -49,18 +49,20 @@ function ComponentA(props) { } t0 = ; - $[0] = props; - $[1] = t0; + $[0] = props.p0; + $[1] = props.p1; + $[2] = props.p2; + $[3] = t0; } else { - t0 = $[1]; + t0 = $[3]; } return t0; } function ComponentB(props) { - const $ = _c(2); + const $ = _c(4); let t0; - if ($[0] !== props) { + if ($[0] !== props.p0 || $[1] !== props.p1 || $[2] !== props.p2) { const a = []; const b = []; if (mayMutate(b)) { @@ -71,10 +73,12 @@ function ComponentB(props) { } t0 = ; - $[0] = props; - $[1] = t0; + $[0] = props.p0; + $[1] = props.p1; + $[2] = props.p2; + $[3] = t0; } else { - t0 = $[1]; + t0 = $[3]; } return t0; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dependencies-outputs.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dependencies-outputs.expect.md index 6fc686cb197fc..f0f9911c07e92 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dependencies-outputs.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dependencies-outputs.expect.md @@ -41,7 +41,7 @@ function foo(a, b) { x = $[1]; } let y; - if ($[2] !== x || $[3] !== b) { + if ($[2] !== b || $[3] !== x) { y = []; if (x.length) { y.push(x); @@ -49,8 +49,8 @@ function foo(a, b) { if (b) { y.push(b); } - $[2] = x; - $[3] = b; + $[2] = b; + $[3] = x; $[4] = y; } else { y = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-array-declaration-to-context-var.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-array-declaration-to-context-var.expect.md index fdb7203785716..26b56ea2a4f4d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-array-declaration-to-context-var.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-array-declaration-to-context-var.expect.md @@ -10,7 +10,7 @@ function Component(props) { x = identity(props.value[0]); }; foo(); - return {x}; + return
{x}
; } export const FIXTURE_ENTRYPOINT = { @@ -45,7 +45,7 @@ function Component(props) { const t0 = x; let t1; if ($[2] !== t0) { - t1 = { x: t0 }; + t1 =
{t0}
; $[2] = t0; $[3] = t1; } else { @@ -62,4 +62,4 @@ export const FIXTURE_ENTRYPOINT = { ``` ### Eval output -(kind: ok) {"x":42} \ No newline at end of file +(kind: ok)
42
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-array-declaration-to-context-var.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-array-declaration-to-context-var.js index 55675de9abb40..bc9324a35f06d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-array-declaration-to-context-var.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-array-declaration-to-context-var.js @@ -6,7 +6,7 @@ function Component(props) { x = identity(props.value[0]); }; foo(); - return {x}; + return
{x}
; } export const FIXTURE_ENTRYPOINT = { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-in-branch-ssa.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-in-branch-ssa.expect.md index d65082cbc87c7..b159789106267 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-in-branch-ssa.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-in-branch-ssa.expect.md @@ -61,11 +61,11 @@ function useFoo(props) { z = $[4]; } let t0; - if ($[5] !== x || $[6] !== y || $[7] !== myList) { + if ($[5] !== myList || $[6] !== x || $[7] !== y) { t0 = { x, y, myList }; - $[5] = x; - $[6] = y; - $[7] = myList; + $[5] = myList; + $[6] = x; + $[7] = y; $[8] = t0; } else { t0 = $[8]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-mixed-scope-and-local-variables-with-default.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-mixed-scope-and-local-variables-with-default.expect.md index 5e8f199206f58..17dd0f835942d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-mixed-scope-and-local-variables-with-default.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-mixed-scope-and-local-variables-with-default.expect.md @@ -63,67 +63,63 @@ function useFragment(_arg1, _arg2) { } function Component(props) { - const $ = _c(9); - let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = graphql` + const $ = _c(8); + const post = useFragment( + graphql` fragment F on T { id } - `; - $[0] = t0; - } else { - t0 = $[0]; - } - const post = useFragment(t0, props.post); - let t1; - if ($[1] !== post) { + `, + props.post, + ); + let t0; + if ($[0] !== post) { const allUrls = []; - const { media: t2, comments: t3, urls: t4 } = post; - const media = t2 === undefined ? null : t2; + const { media: t1, comments: t2, urls: t3 } = post; + const media = t1 === undefined ? null : t1; + let t4; + if ($[2] !== t2) { + t4 = t2 === undefined ? [] : t2; + $[2] = t2; + $[3] = t4; + } else { + t4 = $[3]; + } + const comments = t4; let t5; - if ($[3] !== t3) { + if ($[4] !== t3) { t5 = t3 === undefined ? [] : t3; - $[3] = t3; - $[4] = t5; + $[4] = t3; + $[5] = t5; } else { - t5 = $[4]; + t5 = $[5]; } - const comments = t5; + const urls = t5; let t6; - if ($[5] !== t4) { - t6 = t4 === undefined ? [] : t4; - $[5] = t4; - $[6] = t6; - } else { - t6 = $[6]; - } - const urls = t6; - let t7; - if ($[7] !== comments.length) { - t7 = (e) => { + if ($[6] !== comments.length) { + t6 = (e) => { if (!comments.length) { return; } console.log(comments.length); }; - $[7] = comments.length; - $[8] = t7; + $[6] = comments.length; + $[7] = t6; } else { - t7 = $[8]; + t6 = $[7]; } - const onClick = t7; + const onClick = t6; allUrls.push(...urls); - t1 = ; - $[1] = post; - $[2] = t1; + t0 = ; + $[0] = post; + $[1] = t0; } else { - t1 = $[2]; + t0 = $[1]; } - return t1; + return t0; } export const FIXTURE_ENTRYPOINT = { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-mixed-scope-declarations-and-locals.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-mixed-scope-declarations-and-locals.expect.md index ed566a605fd5a..f69149aba4175 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-mixed-scope-declarations-and-locals.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-mixed-scope-declarations-and-locals.expect.md @@ -2,6 +2,8 @@ ## Input ```javascript +import {useFragment} from 'shared-runtime'; + function Component(props) { const post = useFragment( graphql` @@ -36,6 +38,8 @@ function Component(props) { ```javascript import { c as _c } from "react/compiler-runtime"; +import { useFragment } from "shared-runtime"; + function Component(props) { const $ = _c(4); const post = useFragment( diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-mixed-scope-declarations-and-locals.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-mixed-scope-declarations-and-locals.js index f44fe5c57baf3..5d1377c458855 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-mixed-scope-declarations-and-locals.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-mixed-scope-declarations-and-locals.js @@ -1,3 +1,5 @@ +import {useFragment} from 'shared-runtime'; + function Component(props) { const post = useFragment( graphql` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-same-property-identifier-names.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-same-property-identifier-names.expect.md index b86498b922d38..3e1c4771eabfb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-same-property-identifier-names.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-same-property-identifier-names.expect.md @@ -41,10 +41,10 @@ function Component(props) { } const sameName = t1; let t2; - if ($[2] !== sameName || $[3] !== renamed) { + if ($[2] !== renamed || $[3] !== sameName) { t2 = [sameName, renamed]; - $[2] = sameName; - $[3] = renamed; + $[2] = renamed; + $[3] = sameName; $[4] = t2; } else { t2 = $[4]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring.expect.md index c175cc558cf06..f292e83e16438 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring.expect.md @@ -36,45 +36,45 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; function foo(a, b, c) { const $ = _c(18); - let t0; let d; let h; + let t0; if ($[0] !== a) { [d, t0, ...h] = a; $[0] = a; - $[1] = t0; - $[2] = d; - $[3] = h; + $[1] = d; + $[2] = h; + $[3] = t0; } else { - t0 = $[1]; - d = $[2]; - h = $[3]; + d = $[1]; + h = $[2]; + t0 = $[3]; } const [t1] = t0; - let t2; let g; + let t2; if ($[4] !== t1) { ({ e: t2, ...g } = t1); $[4] = t1; - $[5] = t2; - $[6] = g; + $[5] = g; + $[6] = t2; } else { - t2 = $[5]; - g = $[6]; + g = $[5]; + t2 = $[6]; } const { f } = t2; const { l: t3, p } = b; const { m: t4 } = t3; - let t5; let o; + let t5; if ($[7] !== t4) { [t5, ...o] = t4; $[7] = t4; - $[8] = t5; - $[9] = o; + $[8] = o; + $[9] = t5; } else { - t5 = $[8]; - o = $[9]; + o = $[8]; + t5 = $[9]; } const [n] = t5; let t6; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dont-merge-if-dep-is-inner-declaration-of-previous-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dont-merge-if-dep-is-inner-declaration-of-previous-scope.expect.md index 29780eb76c2b6..ce5bfda64406b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dont-merge-if-dep-is-inner-declaration-of-previous-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/dont-merge-if-dep-is-inner-declaration-of-previous-scope.expect.md @@ -53,8 +53,8 @@ import { ValidateMemoization } from "shared-runtime"; function Component(t0) { const $ = _c(25); const { a, b, c } = t0; - let y; let x; + let y; if ($[0] !== a || $[1] !== b || $[2] !== c) { x = []; if (a) { @@ -73,11 +73,11 @@ function Component(t0) { $[0] = a; $[1] = b; $[2] = c; - $[3] = y; - $[4] = x; + $[3] = x; + $[4] = y; } else { - y = $[3]; - x = $[4]; + x = $[3]; + y = $[4]; } let t1; if ($[7] !== y) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-nested-early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-nested-early-return-within-reactive-scope.expect.md index 2d33981f73fd1..68b0122ea92dd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-nested-early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-nested-early-return-within-reactive-scope.expect.md @@ -31,9 +31,9 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(props) { - const $ = _c(5); + const $ = _c(7); let t0; - if ($[0] !== props) { + if ($[0] !== props.a || $[1] !== props.b || $[2] !== props.cond) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const x = []; @@ -41,12 +41,12 @@ function Component(props) { x.push(props.a); if (props.b) { let t1; - if ($[2] !== props.b) { + if ($[4] !== props.b) { t1 = [props.b]; - $[2] = props.b; - $[3] = t1; + $[4] = props.b; + $[5] = t1; } else { - t1 = $[3]; + t1 = $[5]; } const y = t1; x.push(y); @@ -58,20 +58,22 @@ function Component(props) { break bb0; } else { let t1; - if ($[4] === Symbol.for("react.memo_cache_sentinel")) { + if ($[6] === Symbol.for("react.memo_cache_sentinel")) { t1 = foo(); - $[4] = t1; + $[6] = t1; } else { - t1 = $[4]; + t1 = $[6]; } t0 = t1; break bb0; } } - $[0] = props; - $[1] = t0; + $[0] = props.a; + $[1] = props.b; + $[2] = props.cond; + $[3] = t0; } else { - t0 = $[1]; + t0 = $[3]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-within-reactive-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-within-reactive-scope.expect.md index 6c3525e9e77eb..31df829e0c203 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-within-reactive-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/early-return-within-reactive-scope.expect.md @@ -45,9 +45,9 @@ import { c as _c } from "react/compiler-runtime"; import { makeArray } from "shared-runtime"; function Component(props) { - const $ = _c(4); + const $ = _c(6); let t0; - if ($[0] !== props) { + if ($[0] !== props.a || $[1] !== props.b || $[2] !== props.cond) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { const x = []; @@ -57,21 +57,23 @@ function Component(props) { break bb0; } else { let t1; - if ($[2] !== props.b) { + if ($[4] !== props.b) { t1 = makeArray(props.b); - $[2] = props.b; - $[3] = t1; + $[4] = props.b; + $[5] = t1; } else { - t1 = $[3]; + t1 = $[5]; } t0 = t1; break bb0; } } - $[0] = props; - $[1] = t0; + $[0] = props.a; + $[1] = props.b; + $[2] = props.cond; + $[3] = t0; } else { - t0 = $[1]; + t0 = $[3]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.capture-ref-for-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.capture-ref-for-mutation.expect.md new file mode 100644 index 0000000000000..cff34e3449376 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.capture-ref-for-mutation.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +import {useRef} from 'react'; +import {addOne} from 'shared-runtime'; + +function useKeyCommand() { + const currentPosition = useRef(0); + const handleKey = direction => () => { + const position = currentPosition.current; + const nextPosition = direction === 'left' ? addOne(position) : position; + currentPosition.current = nextPosition; + }; + const moveLeft = { + handler: handleKey('left')(), + }; + const moveRight = { + handler: handleKey('right')(), + }; + return [moveLeft, moveRight]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useKeyCommand, + params: [], +}; + +``` + + +## Error + +``` + 10 | }; + 11 | const moveLeft = { +> 12 | handler: handleKey('left')(), + | ^^^^^^^^^^^^^^^^^ InvalidReact: This function accesses a ref value (the `current` property), which may not be accessed during render. (https://react.dev/reference/react/useRef) (12:12) + +InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (12:12) + +InvalidReact: This function accesses a ref value (the `current` property), which may not be accessed during render. (https://react.dev/reference/react/useRef) (15:15) + +InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (15:15) + 13 | }; + 14 | const moveRight = { + 15 | handler: handleKey('right')(), +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.capture-ref-for-mutation.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.capture-ref-for-mutation.tsx new file mode 100644 index 0000000000000..41e117ed15e80 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.capture-ref-for-mutation.tsx @@ -0,0 +1,23 @@ +import {useRef} from 'react'; +import {addOne} from 'shared-runtime'; + +function useKeyCommand() { + const currentPosition = useRef(0); + const handleKey = direction => () => { + const position = currentPosition.current; + const nextPosition = direction === 'left' ? addOne(position) : position; + currentPosition.current = nextPosition; + }; + const moveLeft = { + handler: handleKey('left')(), + }; + const moveRight = { + handler: handleKey('right')(), + }; + return [moveLeft, moveRight]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useKeyCommand, + params: [], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md new file mode 100644 index 0000000000000..d9c2b599998b7 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies +import {ValidateMemoization} from 'shared-runtime'; +function Component(props) { + const data = useMemo(() => { + const x = []; + x.push(props?.items); + if (props.cond) { + x.push(props?.items); + } + return x; + }, [props?.items, props.cond]); + return ( + + ); +} + +``` + + +## Error + +``` + 2 | import {ValidateMemoization} from 'shared-runtime'; + 3 | function Component(props) { +> 4 | const data = useMemo(() => { + | ^^^^^^^ +> 5 | const x = []; + | ^^^^^^^^^^^^^^^^^ +> 6 | x.push(props?.items); + | ^^^^^^^^^^^^^^^^^ +> 7 | if (props.cond) { + | ^^^^^^^^^^^^^^^^^ +> 8 | x.push(props?.items); + | ^^^^^^^^^^^^^^^^^ +> 9 | } + | ^^^^^^^^^^^^^^^^^ +> 10 | return x; + | ^^^^^^^^^^^^^^^^^ +> 11 | }, [props?.items, props.cond]); + | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (4:11) + 12 | return ( + 13 | + 14 | ); +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.js new file mode 100644 index 0000000000000..760f345e90210 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.js @@ -0,0 +1,15 @@ +// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies +import {ValidateMemoization} from 'shared-runtime'; +function Component(props) { + const data = useMemo(() => { + const x = []; + x.push(props?.items); + if (props.cond) { + x.push(props?.items); + } + return x; + }, [props?.items, props.cond]); + return ( + + ); +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md new file mode 100644 index 0000000000000..57b7d48facbd5 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies +import {ValidateMemoization} from 'shared-runtime'; +function Component(props) { + const data = useMemo(() => { + const x = []; + x.push(props?.items); + if (props.cond) { + x.push(props.items); + } + return x; + }, [props?.items, props.cond]); + return ( + + ); +} + +``` + + +## Error + +``` + 2 | import {ValidateMemoization} from 'shared-runtime'; + 3 | function Component(props) { +> 4 | const data = useMemo(() => { + | ^^^^^^^ +> 5 | const x = []; + | ^^^^^^^^^^^^^^^^^ +> 6 | x.push(props?.items); + | ^^^^^^^^^^^^^^^^^ +> 7 | if (props.cond) { + | ^^^^^^^^^^^^^^^^^ +> 8 | x.push(props.items); + | ^^^^^^^^^^^^^^^^^ +> 9 | } + | ^^^^^^^^^^^^^^^^^ +> 10 | return x; + | ^^^^^^^^^^^^^^^^^ +> 11 | }, [props?.items, props.cond]); + | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (4:11) + 12 | return ( + 13 | + 14 | ); +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.js new file mode 100644 index 0000000000000..3f773f4fe4e4b --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.js @@ -0,0 +1,15 @@ +// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies +import {ValidateMemoization} from 'shared-runtime'; +function Component(props) { + const data = useMemo(() => { + const x = []; + x.push(props?.items); + if (props.cond) { + x.push(props.items); + } + return x; + }, [props?.items, props.cond]); + return ( + + ); +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisted-function-declaration.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisted-function-declaration.expect.md deleted file mode 100644 index d2e51d07256e6..0000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisted-function-declaration.expect.md +++ /dev/null @@ -1,29 +0,0 @@ - -## Input - -```javascript -function component(a) { - let t = {a}; - x(t); // hoisted call - function x(p) { - p.foo(); - } - return t; -} - -``` - - -## Error - -``` - 1 | function component(a) { - 2 | let t = {a}; -> 3 | x(t); // hoisted call - | ^^^^ Todo: Unsupported declaration type for hoisting. variable "x" declared with FunctionDeclaration (3:3) - 4 | function x(p) { - 5 | p.foo(); - 6 | } -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisted-function-declaration.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisted-function-declaration.js deleted file mode 100644 index 3b0586d4376e6..0000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisted-function-declaration.js +++ /dev/null @@ -1,8 +0,0 @@ -function component(a) { - let t = {a}; - x(t); // hoisted call - function x(p) { - p.foo(); - } - return t; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md index 91f8e67d0c698..2045ee7901e96 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md @@ -24,13 +24,13 @@ export const FIXTURE_ENTRYPOINT = { ## Error ``` - 3 | return x; - 4 | } -> 5 | return baz(); // OK: FuncDecls are HoistableDeclarations that have both declaration and value hoisting - | ^^^^^ Todo: Unsupported declaration type for hoisting. variable "baz" declared with FunctionDeclaration (5:5) - 6 | function baz() { - 7 | return bar(); - 8 | } + 5 | return baz(); // OK: FuncDecls are HoistableDeclarations that have both declaration and value hoisting + 6 | function baz() { +> 7 | return bar(); + | ^^^ Todo: Support functions with unreachable code that may contain hoisted declarations (7:7) + 8 | } + 9 | } + 10 | ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hook-ref-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hook-ref-value.expect.md new file mode 100644 index 0000000000000..d92d918fe9f3c --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hook-ref-value.expect.md @@ -0,0 +1,34 @@ + +## Input + +```javascript +import {useEffect, useRef} from 'react'; + +function Component(props) { + const ref = useRef(); + useEffect(() => {}, [ref.current]); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [], +}; + +``` + + +## Error + +``` + 3 | function Component(props) { + 4 | const ref = useRef(); +> 5 | useEffect(() => {}, [ref.current]); + | ^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (5:5) + +InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (5:5) + 6 | } + 7 | + 8 | export const FIXTURE_ENTRYPOINT = { +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hook-ref-value.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hook-ref-value.js new file mode 100644 index 0000000000000..3276e4b4b44a9 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hook-ref-value.js @@ -0,0 +1,11 @@ +import {useEffect, useRef} from 'react'; + +function Component(props) { + const ref = useRef(); + useEffect(() => {}, [ref.current]); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-during-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-during-render.expect.md index c5e8b68c04002..02748366456fb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-during-render.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-during-render.expect.md @@ -15,10 +15,11 @@ function Component(props) { ## Error ``` + 2 | function Component(props) { 3 | const ref = useRef(null); - 4 | const value = ref.current; -> 5 | return value; - | ^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef). Cannot access ref value at freeze $22:TObject (5:5) +> 4 | const value = ref.current; + | ^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (4:4) + 5 | return value; 6 | } 7 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-disallow-mutating-refs-in-render-transitive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-disallow-mutating-refs-in-render-transitive.expect.md index 0d4cb1318911b..f23ff6f3c8c57 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-disallow-mutating-refs-in-render-transitive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-disallow-mutating-refs-in-render-transitive.expect.md @@ -24,7 +24,7 @@ function Component() { 7 | }; 8 | const changeRef = setRef; > 9 | changeRef(); - | ^^^^^^^^^ InvalidReact: This function accesses a ref value (the `current` property), which may not be accessed during render. (https://react.dev/reference/react/useRef). Function mutate? $39[11:13]:TObject accesses a ref (9:9) + | ^^^^^^^^^ InvalidReact: This function accesses a ref value (the `current` property), which may not be accessed during render. (https://react.dev/reference/react/useRef) (9:9) InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (9:9) 10 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-jsx-in-catch-in-outer-try-with-catch.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-jsx-in-catch-in-outer-try-with-catch.expect.md new file mode 100644 index 0000000000000..40cebff89a75e --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-jsx-in-catch-in-outer-try-with-catch.expect.md @@ -0,0 +1,38 @@ + +## Input + +```javascript +// @validateNoJSXInTryStatements +import {identity} from 'shared-runtime'; + +function Component(props) { + let el; + try { + let value; + try { + value = identity(props.foo); + } catch { + el =
; + } + } catch { + return null; + } + return el; +} + +``` + + +## Error + +``` + 9 | value = identity(props.foo); + 10 | } catch { +> 11 | el =
; + | ^^^^^^^^^^^^^^^^^^^^^ InvalidReact: Unexpected JSX element within a try statement. To catch errors in rendering a given component, wrap that component in an error boundary. (https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary) (11:11) + 12 | } + 13 | } catch { + 14 | return null; +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-jsx-in-catch-in-outer-try-with-catch.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-jsx-in-catch-in-outer-try-with-catch.js new file mode 100644 index 0000000000000..0935a1a63cd83 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-jsx-in-catch-in-outer-try-with-catch.js @@ -0,0 +1,17 @@ +// @validateNoJSXInTryStatements +import {identity} from 'shared-runtime'; + +function Component(props) { + let el; + try { + let value; + try { + value = identity(props.foo); + } catch { + el =
; + } + } catch { + return null; + } + return el; +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-jsx-in-try-with-catch.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-jsx-in-try-with-catch.expect.md new file mode 100644 index 0000000000000..ee1f5335ef624 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-jsx-in-try-with-catch.expect.md @@ -0,0 +1,31 @@ + +## Input + +```javascript +// @validateNoJSXInTryStatements +function Component(props) { + let el; + try { + el =
; + } catch { + return null; + } + return el; +} + +``` + + +## Error + +``` + 3 | let el; + 4 | try { +> 5 | el =
; + | ^^^^^^^ InvalidReact: Unexpected JSX element within a try statement. To catch errors in rendering a given component, wrap that component in an error boundary. (https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary) (5:5) + 6 | } catch { + 7 | return null; + 8 | } +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-jsx-in-try-with-catch.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-jsx-in-try-with-catch.js new file mode 100644 index 0000000000000..3e7747c875b3c --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-jsx-in-try-with-catch.js @@ -0,0 +1,10 @@ +// @validateNoJSXInTryStatements +function Component(props) { + let el; + try { + el =
; + } catch { + return null; + } + return el; +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-non-imported-reanimated-shared-value-writes.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-non-imported-reanimated-shared-value-writes.expect.md new file mode 100644 index 0000000000000..f1399a41b6fec --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-non-imported-reanimated-shared-value-writes.expect.md @@ -0,0 +1,36 @@ + +## Input + +```javascript +// @enableCustomTypeDefinitionForReanimated + +/** + * Test that a global (i.e. non-imported) useSharedValue is treated as an + * unknown hook. + */ +function SomeComponent() { + const sharedVal = useSharedValue(0); + return ( +